From 281cf4d87d30dec9ac2c0138549eb9662c14d5dc Mon Sep 17 00:00:00 2001 From: Valentin Serra Date: Thu, 6 Jun 2024 11:55:17 +0200 Subject: [PATCH] feat: enable passing of documentation link to form properties panel Related to https://github.com/camunda/team-hto/issues/630 --- .../properties-panel/PropertiesPanel.js | 17 ++- .../PropertiesPanelHeaderProvider.js | 103 ++++++++---------- .../PropertiesPanelHeaderProvider.spec.js | 28 ++++- .../test/spec/Playground.spec.js | 41 +++++++ 4 files changed, 125 insertions(+), 64 deletions(-) diff --git a/packages/form-js-editor/src/features/properties-panel/PropertiesPanel.js b/packages/form-js-editor/src/features/properties-panel/PropertiesPanel.js index 507fc00d2..a905aacfa 100644 --- a/packages/form-js-editor/src/features/properties-panel/PropertiesPanel.js +++ b/packages/form-js-editor/src/features/properties-panel/PropertiesPanel.js @@ -6,16 +6,18 @@ import { reduce, isArray } from 'min-dash'; import { FormPropertiesPanelContext } from './context'; -import { PropertiesPanelHeaderProvider } from './PropertiesPanelHeaderProvider'; +import { getPropertiesPanelHeaderProvider } from './PropertiesPanelHeaderProvider'; import { PropertiesPanelPlaceholderProvider } from './PropertiesPanelPlaceholderProvider'; +const EMPTY = {}; + export function PropertiesPanel(props) { const { eventBus, getProviders, injector } = props; const formEditor = injector.get('formEditor'); const modeling = injector.get('modeling'); const selectionModule = injector.get('selection'); - const propertiesPanelConfig = injector.get('config.propertiesPanel') || {}; + const propertiesPanelConfig = injector.get('config.propertiesPanel') || EMPTY; const { feelPopupContainer } = propertiesPanelConfig; @@ -83,6 +85,17 @@ export function PropertiesPanel(props) { ); }, [providers, selectedFormField, editField]); + const formFields = getService('formFields'); + + const PropertiesPanelHeaderProvider = useMemo( + () => + getPropertiesPanelHeaderProvider({ + getDocumentationRef: propertiesPanelConfig.getDocumentationRef, + formFields, + }), + [formFields, propertiesPanelConfig], + ); + return (
{ - const { type } = field; - - if (headerlessTypes.includes(type)) { - return ''; - } - - if (type === 'text') { - return textToLabel(field.text); - } - - if (type === 'image') { - return field.alt; - } - - if (type === 'default') { - return field.id; - } - - return field.label; - }, - - getElementIcon: (field) => { - const { type } = field; - - // @Note: We know that we are inside the properties panel context, - // so we can savely use the hook here. - // eslint-disable-next-line react-hooks/rules-of-hooks - const fieldDefinition = useService('formFields').get(type).config; - - const Icon = fieldDefinition.icon || iconsByType(type); - - if (Icon) { - return () => ; - } else if (fieldDefinition.iconUrl) { - return getPaletteIcon({ iconUrl: fieldDefinition.iconUrl, label: fieldDefinition.label }); - } - }, - - getTypeLabel: (field) => { - const { type } = field; - - if (type === 'default') { - return 'Form'; - } - - // @Note: We know that we are inside the properties panel context, - // so we can savely use the hook here. - // eslint-disable-next-line react-hooks/rules-of-hooks - const fieldDefinition = useService('formFields').get(type).config; - - return fieldDefinition.label || type; - }, -}; +export function getPropertiesPanelHeaderProvider(options = {}) { + const { getDocumentationRef, formFields } = options; + + return { + getElementLabel: (field) => { + const { type } = field; + if (headerlessTypes.includes(type)) { + return ''; + } + if (type === 'text') { + return textToLabel(field.text); + } + if (type === 'image') { + return field.alt; + } + if (type === 'default') { + return field.id; + } + return field.label; + }, + + getElementIcon: (field) => { + const { type } = field; + const fieldDefinition = formFields.get(type).config; + const Icon = fieldDefinition.icon || iconsByType(type); + if (Icon) { + return () => ; + } else if (fieldDefinition.iconUrl) { + return getPaletteIcon({ iconUrl: fieldDefinition.iconUrl, label: fieldDefinition.label }); + } + }, + + getTypeLabel: (field) => { + const { type } = field; + if (type === 'default') { + return 'Form'; + } + const fieldDefinition = formFields.get(type).config; + return fieldDefinition.label || type; + }, + + getDocumentationRef, + }; +} diff --git a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanelHeaderProvider.spec.js b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanelHeaderProvider.spec.js index 453ecbcfb..4a519f30e 100644 --- a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanelHeaderProvider.spec.js +++ b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanelHeaderProvider.spec.js @@ -2,7 +2,7 @@ import { cleanup, render } from '@testing-library/preact/pure'; import { FormFields } from '@bpmn-io/form-js-viewer'; -import { PropertiesPanelHeaderProvider } from '../../../../src/features/properties-panel/PropertiesPanelHeaderProvider'; +import { getPropertiesPanelHeaderProvider } from '../../../../src/features/properties-panel/PropertiesPanelHeaderProvider'; import { MockPropertiesPanelContext, TestPropertiesPanel } from './helper'; @@ -50,6 +50,22 @@ describe('PropertiesPanelHeaderProvider', function () { expect(label.innerText).to.eql(field.label); }); + it('should render documentation link', function () { + // given + const field = { type: 'textfield' }; + + const getDocumentationRef = () => 'https://example.com/'; + + // when + const { container } = renderHeader({ field, getDocumentationRef }); + + // then + const documentationLink = container.querySelector('.bio-properties-panel-header-link'); + + expect(documentationLink).to.exist; + expect(documentationLink.href).to.eql(getDocumentationRef()); + }); + describe('extension support', function () { it('should render type label from config', function () { // given @@ -130,7 +146,7 @@ describe('PropertiesPanelHeaderProvider', function () { // helpers ///////// -function renderHeader({ services, ...restOptions }) { +function renderHeader({ services = {}, getDocumentationRef, ...restOptions }) { const defaultField = { type: 'textfield' }; const options = { @@ -140,7 +156,13 @@ function renderHeader({ services, ...restOptions }) { return render( - + , ); } diff --git a/packages/form-js-playground/test/spec/Playground.spec.js b/packages/form-js-playground/test/spec/Playground.spec.js index 62003c2cd..45a426811 100644 --- a/packages/form-js-playground/test/spec/Playground.spec.js +++ b/packages/form-js-playground/test/spec/Playground.spec.js @@ -144,6 +144,47 @@ describe('playground', function () { expect(playground).to.exist; }); + it('should render with documentation links', async function () { + // given + const data = {}; + + const getDocumentationRef = (field) => { + if (field.type === 'default') { + return 'https://example.com/default'; + } + + return 'https://example.com/other'; + }; + + // when + await act(() => { + playground = new Playground({ + container, + schema, + data, + propertiesPanel: { + getDocumentationRef, + }, + }); + }); + + // then + expect(playground).to.exist; + + const documentationLink = domQuery('.bio-properties-panel-header-link', container); + expect(documentationLink).to.exist; + expect(documentationLink.href).to.eql('https://example.com/default'); + + // when + const formField = domQuery('.fjs-form-field', container); + await act(() => formField.click()); + + // then + const documentationLinkSelected = domQuery('.bio-properties-panel-header-link', container); + expect(documentationLinkSelected).to.exist; + expect(documentationLinkSelected.href).to.eql('https://example.com/other'); + }); + it('should append sample data', async function () { // given const attrs = {