import React from 'react'; import styled from 'styled-components'; import { injectIntl, InjectedIntlProps } from 'react-intl'; import SectionMessage from '@atlaskit/section-message'; import Button from '@atlaskit/button/custom-theme-button'; import Select from '@atlaskit/select'; import AddCircleIcon from '@atlaskit/icon/glyph/add-circle'; import { ExtensionManifest } from '@atlaskit/editor-common'; import { Fieldset, Parameters, FieldDefinition, } from '@atlaskit/editor-common/extensions'; import { messages } from '../messages'; // eslint-disable-next-line import/no-cycle import FormContent from '../FormContent'; import { OnFieldChange } from '../types'; import { getNameFromDuplicateField, isDuplicateField } from '../utils'; type OptionType = { label: string; value: string; }; import { gridSize } from '@atlaskit/theme/constants'; import { multiply } from '@atlaskit/theme/math'; import { N40A } from '@atlaskit/theme/colors'; const ActionsWrapper = styled.div` border-top: 1px solid ${N40A}; margin-top: ${multiply(gridSize, 2)}px; padding-top: ${multiply(gridSize, 2)}px; `; const populateFromParameters = ( parameters: Parameters, fields: FieldDefinition[], ): string[] | undefined => { if (Object.keys(parameters).length) { const keys = Object.keys(parameters); const existingFieldKeys = keys.filter((key) => fields.find((field) => field.name === getNameFromDuplicateField(key)), ); if (existingFieldKeys.length > 0) { return existingFieldKeys; } } }; const populateFromRequired = ( fields: FieldDefinition[], ): string[] | undefined => { return fields.filter((field) => field.isRequired).map((field) => field.name); }; const getInitialFields = ( parameters: Parameters = {}, fields: FieldDefinition[], isDynamic?: boolean, ): Set => { if (!isDynamic) { return new Set(fields.map((field) => field.name)); } const dynamicFields: string[] = []; const fromRequired = populateFromRequired(fields); if (fromRequired) { dynamicFields.push(...fromRequired); } const fromParameters = populateFromParameters(parameters, fields); if (fromParameters) { dynamicFields.push(...fromParameters); } if ( dynamicFields.length === 0 && Array.isArray(fields) && fields.length > 0 ) { dynamicFields.push(fields[0].name); } return new Set(dynamicFields); }; type Props = { name: string; extensionManifest: ExtensionManifest; field: Fieldset; parameters?: Parameters; onFieldChange: OnFieldChange; firstVisibleFieldName?: string; error?: string; } & InjectedIntlProps; type State = { isAdding: boolean; visibleFields: Set; currentParameters: Parameters; selectedFields: FieldDefinition[]; selectOptions: OptionType[]; }; class FieldsetField extends React.Component { constructor(props: Props) { super(props); const initialFields = getInitialFields( props.parameters, props.field.fields, props.field.options.isDynamic, ); this.state = { isAdding: false, currentParameters: props.parameters || {}, visibleFields: initialFields, selectedFields: this.getSelectedFields(initialFields), selectOptions: this.getSelectOptions(initialFields), }; } getSelectedFields = (visibleFields: Set) => { const { field } = this.props; return [...visibleFields].map((fieldName) => { const originalFieldDef = field.fields.find( (field) => field.name === getNameFromDuplicateField(fieldName), ) as FieldDefinition; const fieldDef = { ...originalFieldDef, name: fieldName, }; // for duplicate fields we only want the first one to actually be required if (originalFieldDef.name !== fieldName && fieldDef.isRequired === true) { fieldDef.isRequired = false; } return fieldDef; }); }; getSelectOptions = (visibleFields: Set) => { const { field } = this.props; return field.fields .filter( (field: FieldDefinition) => field.allowDuplicates || !visibleFields.has(field.name), ) .map((field: FieldDefinition) => ({ value: field.name, label: field.label, })); }; setIsAdding = (value: boolean) => { this.setState((state) => ({ ...state, isAdding: value, })); }; setCurrentParameters = (parameters: Parameters) => { this.setState( (state) => ({ ...state, currentParameters: parameters, }), // callback required so autosave can be triggered on // the right moment if fields are being removed () => this.props.onFieldChange(this.props.field.name, true), ); }; setVisibleFields = (fields: Set) => { this.setState((state) => ({ ...state, visibleFields: fields, selectedFields: this.getSelectedFields(fields), selectOptions: this.getSelectOptions(fields), })); }; onSelectItem = (option: OptionType) => { const { visibleFields } = this.state; let newItem = option.value; const duplicates = [...visibleFields].filter( (field) => getNameFromDuplicateField(field) === newItem, ); if (duplicates.length > 0) { newItem += `:${duplicates.length}`; } this.setVisibleFields(new Set([...visibleFields, newItem])); this.setIsAdding(false); }; onClickRemove = (fieldName: string) => { const { visibleFields, currentParameters } = this.state; visibleFields.delete(fieldName); this.setVisibleFields(new Set(visibleFields)); const newParameters = { ...currentParameters }; delete newParameters[fieldName]; // if any there are duplicate fields that come after the one removed, we want to reduce their // duplicate index eg. label:2 -> label:1 if (isDuplicateField(fieldName)) { const [key, idx] = fieldName.split(':'); let currentIdx = +idx; while (currentParameters[`${key}:${currentIdx + 1}`]) { newParameters[`${key}:${currentIdx}`] = currentParameters[`${key}:${currentIdx + 1}`]; currentIdx++; } delete newParameters[`${key}:${currentIdx}`]; } this.setCurrentParameters(newParameters); }; renderActions = () => { const { intl } = this.props; const { selectOptions, isAdding } = this.state; if (selectOptions.length === 0) { return null; } return ( {isAdding ? (