diff --git a/packages/form-js-editor/src/features/properties-panel/Util.js b/packages/form-js-editor/src/features/properties-panel/Util.js index ee23d4fa4..bfebec1ea 100644 --- a/packages/form-js-editor/src/features/properties-panel/Util.js +++ b/packages/form-js-editor/src/features/properties-panel/Util.js @@ -86,6 +86,7 @@ export const INPUTS = [ 'taglist', 'textfield', 'textarea', + 'filepicker', ]; export const OPTIONS_INPUTS = ['checklist', 'radio', 'select', 'taglist']; diff --git a/packages/form-js-editor/src/features/properties-panel/entries/AcceptEntry.js b/packages/form-js-editor/src/features/properties-panel/entries/AcceptEntry.js new file mode 100644 index 000000000..3ef481ee1 --- /dev/null +++ b/packages/form-js-editor/src/features/properties-panel/entries/AcceptEntry.js @@ -0,0 +1,65 @@ +import { get } from 'min-dash'; + +import { useService, useVariables } from '../hooks'; + +import { FeelTemplatingEntry, isFeelEntryEdited } from '@bpmn-io/properties-panel'; + +export function AcceptEntry(props) { + const { editField, field } = props; + + const entries = []; + + entries.push({ + id: 'accept', + component: Accept, + editField: editField, + field: field, + isEdited: isFeelEntryEdited, + isDefaultVisible: (field) => field.type === 'filepicker', + }); + + return entries; +} + +function Accept(props) { + const { editField, field, id } = props; + + const debounce = useService('debounce'); + + const variables = useVariables().map((name) => ({ name })); + + const path = ['accept']; + + const getValue = () => { + return get(field, path, ''); + }; + + const setValue = (value) => { + return editField(field, path, value); + }; + + return FeelTemplatingEntry({ + debounce, + element: field, + getValue, + id, + label: 'Supported file formats', + singleLine: true, + setValue, + variables, + description, + }); +} + +// helpers ////////// + +const description = ( + <> + A comma-separated list of{' '} + + file type specifiers + + +); diff --git a/packages/form-js-editor/src/features/properties-panel/entries/DefaultValueEntry.js b/packages/form-js-editor/src/features/properties-panel/entries/DefaultValueEntry.js index ff93c718b..e46f1d0aa 100644 --- a/packages/form-js-editor/src/features/properties-panel/entries/DefaultValueEntry.js +++ b/packages/form-js-editor/src/features/properties-panel/entries/DefaultValueEntry.js @@ -36,7 +36,7 @@ export function DefaultValueEntry(props) { }; } - const defaulValueBase = { + const defaultValueBase = { editField, field, id: 'defaultValue', @@ -44,21 +44,21 @@ export function DefaultValueEntry(props) { }; entries.push({ - ...defaulValueBase, + ...defaultValueBase, component: DefaultValueCheckbox, isEdited: isSelectEntryEdited, isDefaultVisible: isDefaultVisible((field) => field.type === 'checkbox'), }); entries.push({ - ...defaulValueBase, + ...defaultValueBase, component: DefaultValueNumber, isEdited: isTextFieldEntryEdited, isDefaultVisible: isDefaultVisible((field) => field.type === 'number'), }); entries.push({ - ...defaulValueBase, + ...defaultValueBase, component: DefaultValueSingleSelect, isEdited: isSelectEntryEdited, isDefaultVisible: isDefaultVisible((field) => field.type === 'radio' || field.type === 'select'), @@ -67,14 +67,14 @@ export function DefaultValueEntry(props) { // todo(Skaiir): implement a multiselect equivalent (cf. https://github.com/bpmn-io/form-js/issues/265) entries.push({ - ...defaulValueBase, + ...defaultValueBase, component: DefaultValueTextfield, isEdited: isTextFieldEntryEdited, isDefaultVisible: isDefaultVisible((field) => field.type === 'textfield'), }); entries.push({ - ...defaulValueBase, + ...defaultValueBase, component: DefaultValueTextarea, isEdited: isTextAreaEntryEdited, isDefaultVisible: isDefaultVisible((field) => field.type === 'textarea'), diff --git a/packages/form-js-editor/src/features/properties-panel/entries/DescriptionEntry.js b/packages/form-js-editor/src/features/properties-panel/entries/DescriptionEntry.js index c5b0f1909..efeb1bd61 100644 --- a/packages/form-js-editor/src/features/properties-panel/entries/DescriptionEntry.js +++ b/packages/form-js-editor/src/features/properties-panel/entries/DescriptionEntry.js @@ -17,7 +17,7 @@ export function DescriptionEntry(props) { editField: editField, field: field, isEdited: isFeelEntryEdited, - isDefaultVisible: (field) => INPUTS.includes(field.type), + isDefaultVisible: (field) => field.type !== 'filepicker' && INPUTS.includes(field.type), }); return entries; diff --git a/packages/form-js-editor/src/features/properties-panel/entries/MultipleEntry.js b/packages/form-js-editor/src/features/properties-panel/entries/MultipleEntry.js new file mode 100644 index 000000000..f1ccc4b71 --- /dev/null +++ b/packages/form-js-editor/src/features/properties-panel/entries/MultipleEntry.js @@ -0,0 +1,52 @@ +import { get } from 'min-dash'; + +import { useService, useVariables } from '../hooks'; + +import { FeelToggleSwitchEntry, isFeelEntryEdited } from '@bpmn-io/properties-panel'; + +export function MultipleEntry(props) { + const { editField, field } = props; + + const entries = []; + + entries.push({ + id: 'multiple', + component: Multiple, + editField: editField, + field: field, + isEdited: isFeelEntryEdited, + isDefaultVisible: (field) => field.type === 'filepicker', + }); + + return entries; +} + +function Multiple(props) { + const { editField, field, id } = props; + + const debounce = useService('debounce'); + + const variables = useVariables().map((name) => ({ name })); + + const path = ['multiple']; + + const getValue = () => { + return get(field, path, ''); + }; + + const setValue = (value) => { + return editField(field, path, value); + }; + + return FeelToggleSwitchEntry({ + debounce, + element: field, + feel: 'optional', + getValue, + id, + label: 'Upload multiple files', + inline: true, + setValue, + variables, + }); +} diff --git a/packages/form-js-editor/src/features/properties-panel/entries/index.js b/packages/form-js-editor/src/features/properties-panel/entries/index.js index 452144a1c..3636c9970 100644 --- a/packages/form-js-editor/src/features/properties-panel/entries/index.js +++ b/packages/form-js-editor/src/features/properties-panel/entries/index.js @@ -40,3 +40,5 @@ export { HeadersSourceSelectEntry } from './HeadersSourceSelectEntry'; export { ColumnsExpressionEntry } from './ColumnsExpressionEntry'; export { StaticColumnsSourceEntry } from './StaticColumnsSourceEntry'; export { VersionTagEntry } from './VersionTagEntry'; +export { AcceptEntry } from './AcceptEntry'; +export { MultipleEntry } from './MultipleEntry'; diff --git a/packages/form-js-editor/src/features/properties-panel/groups/GeneralGroup.js b/packages/form-js-editor/src/features/properties-panel/groups/GeneralGroup.js index 66c1a73fd..a8cd25ccd 100644 --- a/packages/form-js-editor/src/features/properties-panel/groups/GeneralGroup.js +++ b/packages/form-js-editor/src/features/properties-panel/groups/GeneralGroup.js @@ -24,6 +24,8 @@ import { PaginationEntry, RowCountEntry, VersionTagEntry, + AcceptEntry, + MultipleEntry, } from '../entries'; export function GeneralGroup(field, editField, getService) { @@ -48,6 +50,8 @@ export function GeneralGroup(field, editField, getService) { ...ImageSourceEntry({ field, editField }), ...AltTextEntry({ field, editField }), ...SelectEntries({ field, editField }), + ...AcceptEntry({ field, editField }), + ...MultipleEntry({ field, editField }), ...DisabledEntry({ field, editField }), ...ReadonlyEntry({ field, editField }), ...TableDataSourceEntry({ field, editField }), diff --git a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js index 9b86a5dc5..98e19aeeb 100644 --- a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js +++ b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js @@ -3416,6 +3416,26 @@ describe('properties panel', function () { }); }); }); + + describe('filepicker', function () { + it('entries', function () { + // given + const field = schema.components.find(({ key }) => key === 'files'); + + bootstrapPropertiesPanel({ + container, + field, + }); + + // then + expectPanelStructure(container, { + General: ['Field label', 'Key', 'Supported file formats', 'Upload multiple files', 'Disabled', 'Read only'], + Condition: [], + Validation: ['Required'], + 'Custom properties': [], + }); + }); + }); }); describe('custom properties', function () { diff --git a/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js b/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js index 4e843e273..8e02868fb 100644 --- a/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js +++ b/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js @@ -349,7 +349,7 @@ describe('GeneralGroup', function () { it('should render for INPUTS', function () { // given - for (const type of INPUTS) { + for (const type of INPUTS.filter((type) => type !== 'filepicker')) { const field = { type }; // when @@ -580,11 +580,14 @@ describe('GeneralGroup', function () { }); describe('for all other INPUTS', () => { - const otherInputTypes = INPUTS.filter((i) => !OPTIONS_INPUTS.includes(i)); + const nonDefaultValueInputs = new Set(['datetime', 'filepicker']); + const defaultValueInputs = INPUTS.filter( + (input) => !OPTIONS_INPUTS.includes(input) && !nonDefaultValueInputs.has(input), + ); it('should render', function () { // given - for (const type of otherInputTypes) { + for (const type of defaultValueInputs) { const field = { type }; // when @@ -593,8 +596,22 @@ describe('GeneralGroup', function () { // then const defaultValueEntry = findEntry('defaultValue', container); - if (type === 'datetime') expect(defaultValueEntry).to.not.exist; - else expect(defaultValueEntry).to.exist; + expect(defaultValueEntry).to.exist; + } + }); + + it('should not render', function () { + // given + for (const type of nonDefaultValueInputs) { + const field = { type }; + + // when + const { container } = renderGeneralGroup({ field }); + + // then + const defaultValueEntry = findEntry('defaultValue', container); + + expect(defaultValueEntry).to.not.exist; } }); }); diff --git a/packages/form-js-editor/test/spec/form.json b/packages/form-js-editor/test/spec/form.json index 375b4fafc..f8ac1c932 100644 --- a/packages/form-js-editor/test/spec/form.json +++ b/packages/form-js-editor/test/spec/form.json @@ -238,6 +238,14 @@ "alt": "The bpmn.io logo", "type": "image" }, + { + "label": "Image files", + "type": "filepicker", + "id": "files", + "key": "files", + "multiple": true, + "accept": ".jpg,.png" + }, { "id": "Spacer_1", "type": "spacer", diff --git a/packages/form-json-schema/src/defs/component.json b/packages/form-json-schema/src/defs/component.json index 12e694feb..31e157b2f 100644 --- a/packages/form-json-schema/src/defs/component.json +++ b/packages/form-json-schema/src/defs/component.json @@ -271,7 +271,7 @@ "multiple": { "$id": "#/component/multiple", "description": "Allow multiple files to be selected.", - "type": "boolean" + "type": ["boolean", "string"] } }, "required": ["type"] diff --git a/packages/form-json-schema/src/defs/rules/rules-allowed-properties.json b/packages/form-json-schema/src/defs/rules/rules-allowed-properties.json index c0e02a43f..576276d57 100644 --- a/packages/form-json-schema/src/defs/rules/rules-allowed-properties.json +++ b/packages/form-json-schema/src/defs/rules/rules-allowed-properties.json @@ -415,6 +415,21 @@ "multiple": false } } + }, + { + "if": { + "properties": { + "type": { + "const": "filepicker" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "description": false + } + } } ] }