From b535947f1dfd0cf7de0318342a86a13cd9aaadd8 Mon Sep 17 00:00:00 2001 From: theaubmov Date: Mon, 1 Jan 2024 20:13:39 +0100 Subject: [PATCH 1/4] Properties Panel Commits --- app/app.js | 14 +- app/css/app.css | 314 ++++++++++++++++-- app/css/bpmn-js.css | 40 +++ app/favicon.png | Bin 0 -> 4081 bytes app/fileOperations.js | 107 ++++++ app/index.html | 189 ++++++++--- .../DataObjectPropertiesProvider.js | 1 + .../DataStorePropertiesProvider.js | 1 + app/spiffworkflow/InputOutput/IoPalette.js | 277 ++++++++++++++- .../CallActivityPropertiesProvider.js | 1 + .../ConditionsPropertiesProvider.js | 2 + app/spiffworkflow/eventSelect.js | 1 + .../extensions/extensionHelpers.js | 2 +- .../ExtensionsPropertiesProvider.js | 30 +- app/spiffworkflow/index.js | 5 + .../MessagesPropertiesProvider.js | 1 + .../properties/PropertiesPanelProvider.js | 148 +++++++++ test/spec/BpmnInputOutputSpec.js | 4 +- 18 files changed, 1042 insertions(+), 95 deletions(-) create mode 100644 app/css/bpmn-js.css create mode 100644 app/favicon.png create mode 100644 app/spiffworkflow/properties/PropertiesPanelProvider.js diff --git a/app/app.js b/app/app.js index edfa6fa..29bd744 100644 --- a/app/app.js +++ b/app/app.js @@ -23,6 +23,13 @@ try { keyboard: { bindTo: document }, propertiesPanel: { parent: panelEl, + layout: { + groups: { + general: { + open: true + } + } + } }, additionalModules: [ spiffworkflow, @@ -206,7 +213,7 @@ bpmnModeler.on('import.parse.complete', event => { refs.forEach(ref => { const props = { id: ref.id, - name: ref.id ? typeof(ref.name) === 'undefined': ref.name, + name: ref.id ? typeof (ref.name) === 'undefined' : ref.name, }; let elem = bpmnModeler._moddle.create(desc, props); elem.$parent = ref.element; @@ -214,7 +221,10 @@ bpmnModeler.on('import.parse.complete', event => { }); }); -bpmnModeler.importXML(diagramXML).then(() => {}); +bpmnModeler.importXML(diagramXML).then(() => { + // Zoom up and center workflow in the middle of the canvas + bpmnModeler.get('canvas').zoom('fit-viewport', 'auto'); +}); // This handles the download and upload buttons - it isn't specific to // the BPMN modeler or these extensions, just a quick way to allow you to diff --git a/app/css/app.css b/app/css/app.css index 295da8b..44b5e80 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -1,6 +1,8 @@ - -html, body { +html, +body { height: 100%; + margin: 0; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } .hidden { @@ -9,8 +11,7 @@ html, body { #container { display: flex; - width: 100%; - height: 100%; + height: calc(100% - 80px); } #modeler { @@ -18,16 +19,14 @@ html, body { } #panel { - background-color: #fafafa; - border: solid 1px #ccc; - border-radius: 2px; - font-family: 'Arial', sans-serif; - padding: 10px; - min-width: 400px; -} - -.djs-label { + background-color: white; + border-left: 1px solid #5F5F5F; + /* background-color: #fafafa; */ + /* border: solid 1px #ccc; */ + /* border-radius: 2px; */ font-family: 'Arial', sans-serif; + /* padding: 10px; */ + min-width: 350px; } .spiffworkflow-properties-panel-button { @@ -38,46 +37,307 @@ html, body { /* Style buttons */ .bpmn-js-spiffworkflow-btn { - background-color: DodgerBlue; - border: none; - color: white; + background-color: #ffffff; + color: #393939; + border: 1px solid #393939; padding: 8px 15px; cursor: pointer; font-size: 16px; margin: 12px; } +.main-btn { + background-color: #0F62FE; + color: white; + border: 1px solid #0F62FE; + padding: 8px 15px; + cursor: pointer; + font-size: 16px; + margin: 12px; +} + +.main-btn i { + margin-left: 15px; +} + /* Darker background on mouse-over */ .bpmn-js-spiffworkflow-btn:hover { - background-color: RoyalBlue; + background-color: rgb(0, 0, 0); + border: 1px solid #000000; + color: white; } /* Code Editor -- provided as a div overlay */ .overlay { - position: fixed; /* Sit on top of the page content */ - display: none; /* Hidden by default */ - width: 100%; /* Full width (cover the whole page) */ - height: 100%; /* Full height (cover the whole page) */ + position: fixed; + /* Sit on top of the page content */ + display: none; + /* Hidden by default */ + width: 100%; + /* Full width (cover the whole page) */ + height: 100%; + /* Full height (cover the whole page) */ top: 0; left: 0; right: 0; bottom: 0; - background-color: rgba(0,0,0,0.5); /* Black background with opacity */ - z-index: 200; /* BPMN Canvas has some huge z-indexes, pop-up tools are 100 for ex.*/ + background-color: rgba(0, 0, 0, 0.5); + /* Black background with opacity */ + z-index: 200; + /* BPMN Canvas has some huge z-indexes, pop-up tools are 100 for ex.*/ } -#code_editor, #markdown_editor { +#code_editor, +#markdown_editor { background-color: #ccc; margin: 50px auto 10px auto; max-width: 800px; } -#code_buttons, #markdown_buttons { +#code_buttons, +#markdown_buttons { margin: 50px auto 10px auto; max-width: 800px; right: 10px; } -.djs-palette.two-column.open { - width: 95px; +/* Header */ +#header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: #ffffff; + color: black; + border-bottom: 1px solid #5F5F5F; } + +#header-actions-center { + /* Adjust as needed */ + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + border: 0.75px solid #8F8F8F; +} + +.header-btn { + background: none; + color: #3c3c3c; + border: none; + padding: 10px 10px; + cursor: pointer; + margin-left: 5px; + font-size: 20px; +} + +.header-btn i { + margin-right: 5px; + /* Icon spacing */ +} + + +#process-info h1 { + margin: 0; + font-size: 24px; +} + +#process-info p { + margin: 5px 0 0 0; + font-size: 14px; + color: #666; +} + +#header-actions { + display: flex; + align-items: center; +} + +.bpmn-js-spiffworkflow-btn { + margin-left: 8px; +} + +/* Left sidebar */ +#left-sidebar { + display: flex; + max-width: 300px; + background-color: #ffffff; + /* background-color: #f4f4f4; */ + overflow: hidden; + height: calc(100% - 80px); + float: left; +} + +.tabs { + display: flex; + flex-direction: column; + border-right: 1px solid #5F5F5F; +} + +.tab-button { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 50px; + background: none; + border: none; + cursor: pointer; + width: 100%; +} + +.tab-button i { + font-size: 18px; + color: #333; + /* margin-bottom: 5px; */ + padding: 5px; +} + +.tab-button .text { + font-size: 12px; + display: block; +} + +.tab-button.active i { + color: #146D83; +} + +.tab-button i { + transition: color 0.2s; +} + +.tab-button.active i { + transition: color 0.2s; +} + +.tab-button:hover { + background-color: #ddd; +} + +.tab-button.active, +.tab-button:hover { + background-color: #e9e9e9; + color: DodgerBlue; +} + +.tab-content { + display: none; + height: 100%; + width: 300px; + border-right: 1px solid #5F5F5F; +} + +.tab-content.active { + display: block; +} + +#container::after { + content: ""; + clear: both; + display: table; +} + +#BPMNElements { + border-bottom: 1px solid #5F5F5F; +} + +.bpmn-elements-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #5F5F5F; + padding: 10px; +} + +.bpmn-elements-title { + font-weight: bold; +} + +.bpmn-elements-toggle { + background: none; + border: none; + cursor: pointer; + padding: 5px; + font-size: 1em; +} + + +.group-title { + display: flex; + align-items: center; + justify-content: space-between; + padding-block: 5px; + padding-inline: 10px; + /* background: #f8f8f8; */ + /* background: #f5f5f58c; */ + /* border-bottom: 1px solid #ddd; */ + /* cursor: pointer; */ +} + +.group-title span { + font-weight: 600; +} + +.group.collapsed .entry { + display: none; +} + +.group-toggle { + background: none; + border: none; + cursor: pointer; +} + +.entry-label { + color: #22242A; + text-align: center; + font-family: unset; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: normal; +} + +.entries-container { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 10px; +} + +.entry { + flex: 0 0 calc(25% - 10px); + display: flex; + align-items: center; + justify-content: center; + margin-block: 5px; +} + +.property-tabs { + display: -webkit-box; + width: 100%; + list-style-type: none; + padding: 0; + margin: 0; + border-right: unset; + justify-content: space-between; + text-align: center; +} + +.property-tabs li { + padding: 10px 20px; + cursor: pointer; + border-bottom: 2px solid grey; + + width: 50%; +} + +.property-tabs li.active { + border-bottom: 2px solid blue; + font-weight: bold; +} + +.tabs-group .tab-content { + width: 100%; + border: unset; +} \ No newline at end of file diff --git a/app/css/bpmn-js.css b/app/css/bpmn-js.css new file mode 100644 index 0000000..cbf179c --- /dev/null +++ b/app/css/bpmn-js.css @@ -0,0 +1,40 @@ + +/* Override default palette styles */ + +.djs-palette { + position: relative; + display: block; + width: 100% !important; + height: 90%; + overflow-y: auto; + padding: 0; + margin: 0; + left: 0; + top: 0; +} + +.djs-palette.two-column.open { + width: 95px; +} + +.djs-palette-entries { + display: grid; +} + +.djs-palette .entry, +.djs-palette .djs-palette-toggle { + justify-items: center; + height: unset; + width: unset; + line-height: unset; + display: inline-grid; + cursor: pointer; +} + +.bio-properties-panel-header { + background-color: #FAFAFA; +} + +.djs-label { + font-family: 'Arial', sans-serif; +} \ No newline at end of file diff --git a/app/favicon.png b/app/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1e572b2be0f04200af85de0b62dc57391624a8db GIT binary patch literal 4081 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 literal 0 HcmV?d00001 diff --git a/app/fileOperations.js b/app/fileOperations.js index 86a4a24..eb74d70 100644 --- a/app/fileOperations.js +++ b/app/fileOperations.js @@ -32,6 +32,28 @@ export default function setupFileOperations(bpmnModeler) { uploadBtn.addEventListener('click', (_event) => { openFile(bpmnModeler); }); + + // Handle header actions + const headerButtons = document.querySelectorAll('.header-btn'); + headerButtons.forEach(function (btn) { + btn.addEventListener('click', function (event) { + const action = event.target.closest('.header-btn').getAttribute('data-action'); + handleHeaderAction(action, bpmnModeler); + }); + }); + + // Handle sidebar toggle button + const toggleButtons = document.querySelectorAll('.bpmn-elements-toggle'); + toggleButtons.forEach(function (btn) { + btn.addEventListener('click', function (event) { + // Use a data attribute to identify which tab to toggle + const tabTarget = event.target.closest('button').getAttribute('data-tab-target'); + toggleTab(tabTarget); + }); + }); + + // Setup tabs after modeler is initialized + setupTabs(); } function clickElem(elem) { @@ -77,3 +99,88 @@ export function openFile(bpmnModeler) { document.body.appendChild(fileInput); clickElem(fileInput); } + +/** **************************************** + * Tab functionality + */ +function openTab(event, tabName) { + + // Hide all tab contents + const tabContents = document.querySelectorAll('.tab-content'); + tabContents.forEach(content => { + content.classList.remove('active'); + }); + + // Remove active class from all tabs + const tabButtons = document.querySelectorAll('.tab-button'); + tabButtons.forEach(button => { + button.classList.remove('active'); + }); + + // append active tab content class to the clicked tab button + document.getElementById(tabName).classList.add('active'); + event.currentTarget.classList.add('active'); +} + +function setupTabs() { + const tabs = document.querySelectorAll('.tab-button'); + tabs.forEach(tab => { + tab.addEventListener('click', function (event) { + openTab(event, this.getAttribute('data-tab-target')); + }); + }); +} + +function toggleTab(tabId) { + const tabContent = document.getElementById(tabId); + const allTabContents = document.querySelectorAll('.tab-content'); + + // Remove 'active' from all tabs + allTabContents.forEach(function (tab) { + tab.classList.remove('active'); + }); +} + +/** + * Header functionality + */ +function handleHeaderAction(action, bpmnModeler) { + var commandStack = bpmnModeler.get('commandStack'); + var paletteProvider = bpmnModeler.get('paletteProvider'); + var canvas = bpmnModeler.get('canvas'); + switch (action) { + case 'zoom-in': + bpmnModeler.get('zoomScroll').stepZoom(1); + break; + case 'zoom-out': + bpmnModeler.get('zoomScroll').stepZoom(-1); + break; + case 'expand': + canvas.zoom('fit-viewport', 'auto'); + break; + case 'undo': + commandStack.undo(); + break; + case 'redo': + commandStack.redo(); + break; + case 'hand': + const handTool = paletteProvider._handTool; + handTool.activateHand(); + break; + case 'lasso': + const lassoTool = paletteProvider._lassoTool; + lassoTool.activateSelection(event); + break; + case 'space': + const spaceTool = paletteProvider._spaceTool; + spaceTool.activateSelection(); + break; + case 'connect': + const globalConnect = paletteProvider._globalConnect; + globalConnect.start(); + break; + default: + console.log('Unknown action:', action); + } +} \ No newline at end of file diff --git a/app/index.html b/app/index.html index ca7d9aa..0e9f839 100644 --- a/app/index.html +++ b/app/index.html @@ -1,22 +1,19 @@ - - - bpmn-js-spiffworkflow - + + bpmn-js-spiffworkflow + + - - - - + + + + - + + @@ -32,33 +29,145 @@ - - -
-
-
-
- -
-
-
- -
-
-
-
- -
-
- -
-
- - + + + +
+
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+ +
+
+ + + - + + \ No newline at end of file diff --git a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js index 79afcf6..a62ae90 100644 --- a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js +++ b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js @@ -68,6 +68,7 @@ function createDataObjectSelector(element, translate, moddle, commandStack, mode return { id: 'data_object_properties', label: translate('Data Object Properties'), + isDefault: true, entries: [ { id: 'selectDataObject', diff --git a/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js b/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js index ce227d1..acca651 100644 --- a/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js +++ b/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js @@ -51,6 +51,7 @@ function createCustomDataStoreGroup( const group = { label: translate('Custom Data Store Properties'), id: 'custom-datastore-properties', + isDefault: true, entries: [], }; diff --git a/app/spiffworkflow/InputOutput/IoPalette.js b/app/spiffworkflow/InputOutput/IoPalette.js index e8f9567..2eb1da5 100644 --- a/app/spiffworkflow/InputOutput/IoPalette.js +++ b/app/spiffworkflow/InputOutput/IoPalette.js @@ -4,19 +4,34 @@ import translate from 'diagram-js/lib/i18n/translate/translate'; /** * Add data inputs and data outputs to the panel. */ -export default function IoPalette(palette, create, elementFactory,) { +export default function IoPalette(palette, create, elementFactory, eventBus, handTool, globalConnect, lassoTool, spaceTool) { this._create = create; this._elementFactory = elementFactory; + + this._handTool = handTool; + this._globalConnect = globalConnect; + this._lassoTool = lassoTool; + this._spaceTool = spaceTool; + + eventBus.on('palette.create', function (event) { + this.init(event); + }.bind(this)); + palette.registerProvider(this); } IoPalette.$inject = [ 'palette', 'create', - 'elementFactory' + 'elementFactory', + 'eventBus', + 'handTool', + 'globalConnect', + 'lassoTool', + 'spaceTool' ]; -IoPalette.prototype.getPaletteEntries = function() { +IoPalette.prototype.getPaletteEntries = function (e) { let input_type = 'bpmn:DataInput'; let output_type = 'bpmn:DataOutput'; @@ -24,7 +39,7 @@ IoPalette.prototype.getPaletteEntries = function() { function createListener(event, type) { let shape = elementFactory.createShape(assign({ type: type }, {})); - shape.width = 36; // Fix up the shape dimensions from the defaults. + shape.width = 36; shape.height = 50; create.start(event, shape); } @@ -37,26 +52,268 @@ IoPalette.prototype.getPaletteEntries = function() { createListener(event, output_type); } + function createShape(type, options = {}) { + return function (event) { + let shape = elementFactory.createShape(assign({ type: type }, options)); + create.start(event, shape); + }; + } + return { + // Events + 'create.start-event': { + group: 'events', + className: 'bpmn-icon-start-event-none', + title: translate('Start'), + action: { + dragstart: createShape('bpmn:StartEvent'), + click: createShape('bpmn:StartEvent') + } + }, + 'create.intermediate-event': { + group: 'events', + className: 'bpmn-icon-intermediate-event-none', + title: translate('Intermediate'), + action: { + dragstart: createShape('bpmn:IntermediateCatchEvent'), + click: createShape('bpmn:IntermediateCatchEvent') + } + }, + 'create.end-event': { + group: 'events', + className: 'bpmn-icon-end-event-none', + title: translate('End'), + action: { + dragstart: createShape('bpmn:EndEvent'), + click: createShape('bpmn:EndEvent') + } + }, + // Activities + 'create.task': { + group: 'activities', + className: 'bpmn-icon-task', + title: translate('Task'), + action: { + dragstart: createShape('bpmn:Task'), + click: createShape('bpmn:Task') + } + }, + 'create.user-task': { + group: 'activities', + className: 'bpmn-icon-user', + title: translate('User Task'), + action: { + dragstart: createShape('bpmn:UserTask'), + click: createShape('bpmn:UserTask') + } + }, + 'create.scirpt-task': { + group: 'activities', + className: 'bpmn-icon-script', + title: translate('Script Task'), + action: { + dragstart: createShape('bpmn:ScriptTask'), + click: createShape('bpmn:ScriptTask') + } + }, + 'create.service-task': { + group: 'activities', + className: 'bpmn-icon-service', + title: translate('Service Task'), + action: { + dragstart: createShape('bpmn:ServiceTask'), + click: createShape('bpmn:ServiceTask') + } + }, + // 'create.dmn-task': { + // group: 'activities', + // className: 'bpmn-icon-business-rule', + // title: translate('Business Rule Task'), + // action: { + // dragstart: createShape('bpmn:BusinessRuleTask'), + // click: createShape('bpmn:BusinessRuleTask') + // } + // }, + // Gateways + 'create.condition-gateaway': { + group: 'decisions', + className: 'bpmn-icon-gateway-xor', + title: translate('Decision'), + action: { + dragstart: createShape('bpmn:ExclusiveGateway'), + click: createShape('bpmn:ExclusiveGateway') + } + }, + 'create.parallel-gateaway': { + group: 'decisions', + className: 'bpmn-icon-gateway-parallel', + title: translate('Parallel'), + action: { + dragstart: createShape('bpmn:ParallelGateway'), + click: createShape('bpmn:ParallelGateway') + } + }, + 'create.eventbased-gateaway': { + group: 'decisions', + className: 'bpmn-icon-gateway-eventbased', + title: translate('Event Based'), + action: { + dragstart: createShape('bpmn:EventBasedGateway'), + click: createShape('bpmn:EventBasedGateway') + } + }, + 'create.inclusive-gateaway': { + group: 'decisions', + className: 'bpmn-icon-gateway-or', + title: translate('xOR'), + action: { + dragstart: createShape('bpmn:InclusiveGateway'), + click: createShape('bpmn:InclusiveGateway') + } + }, + // Data Object + 'create.data-store': { + group: 'data', + className: 'bpmn-icon-data-store', + title: translate('Data Store'), + action: { + dragstart: createShape('bpmn:DataStoreReference'), + click: createShape('bpmn:DataStoreReference') + } + }, + 'create.data-object': { + group: 'data', + className: 'bpmn-icon-data-object', + title: translate('Data Object'), + action: { + dragstart: createShape('bpmn:DataObjectReference'), + click: createShape('bpmn:DataObjectReference') + } + }, 'create.data-input': { - group: 'data-object', + group: 'data', className: 'bpmn-icon-data-input', - title: translate('Create DataInput'), + title: translate('Data Input'), action: { dragstart: createInputListener, click: createInputListener } }, 'create.data-output': { - group: 'data-object', + group: 'data', className: 'bpmn-icon-data-output', - title: translate('Create DataOutput'), + title: translate('Data Output'), action: { dragstart: createOutputListener, click: createOutputListener } - } - + }, + // Advanced + 'create.call-activity': { + group: 'advanced', + className: 'bpmn-icon-call-activity', + title: translate('Call Activity'), + action: { + dragstart: createShape('bpmn:CallActivity'), + click: createShape('bpmn:CallActivity') + } + }, + 'create.participant': { + group: 'advanced', + className: 'bpmn-icon-participant', + title: translate('Participant'), + action: { + dragstart: createShape('bpmn:Participant'), + click: createShape('bpmn:Participant') + } + }, + 'create.sub-process-expanded': { + group: 'advanced', + className: 'bpmn-icon-subprocess-expanded', + title: translate('Sub Process'), + action: { + dragstart: createShape('bpmn:SubProcess', { isExpanded: true }), + click: createShape('bpmn:SubProcess', { isExpanded: true }) + } + }, + 'create.transaction': { + group: 'advanced', + className: 'bpmn-icon-transaction', + title: translate('Transaction'), + action: { + dragstart: createShape('bpmn:Transaction', { isExpanded: true }), + click: createShape('bpmn:Transaction', { isExpanded: true }) + } + }, }; }; +IoPalette.prototype.init = function (event) { + + // Override Palette DOM Generated by BPMN-JS Library + const paletteContainer = event.container; + const bpmnElementsDiv = document.getElementById('BPMNElements'); + + setTimeout(() => { + + // Query all group elements + const groups = paletteContainer.querySelectorAll('.group'); + + groups.forEach(group => { + const groupName = group.getAttribute('data-group'); + const title = groupName.charAt(0).toUpperCase() + groupName.slice(1).replace(/-/g, ' '); // Capitalize and format the title + + // Check if group title already exists + let header = group.querySelector('.group-title'); + let entriesContainer = group.querySelector('.entries-container'); // Container for entries + + if (!header) { + // Creation the collapsible header + header = document.createElement('div'); + header.classList.add('group-title'); + + // Creation the title span + const titleSpan = document.createElement('span'); + titleSpan.textContent = title; + header.appendChild(titleSpan); + + // Creation the toggle button + const toggleButton = document.createElement('button'); + toggleButton.classList.add('group-toggle'); + toggleButton.innerHTML = ''; + header.appendChild(toggleButton); + + // Insert the header + group.insertBefore(header, group.firstChild); + + // Create the entries container + entriesContainer = document.createElement('div'); + entriesContainer.classList.add('entries-container'); + group.appendChild(entriesContainer); // Append entries container after the header + + toggleButton.addEventListener('click', function () { + entriesContainer.style.display = entriesContainer.style.display === 'none' ? '' : 'none'; + toggleButton.innerHTML = entriesContainer.style.display === 'none' ? '' : ''; + }); + } + + const entries = group.querySelectorAll('.entry'); + entries.forEach(entry => { + entriesContainer.appendChild(entry); + + let label = entry.querySelector('.entry-label'); + if (!label) { + label = document.createElement('span'); + label.classList.add('entry-label'); + entry.appendChild(label); + } + label.textContent = entry.getAttribute('title'); + }); + }); + + // Move the palette + bpmnElementsDiv.appendChild(paletteContainer); + + }, 0); + +}; \ No newline at end of file diff --git a/app/spiffworkflow/callActivity/propertiesPanel/CallActivityPropertiesProvider.js b/app/spiffworkflow/callActivity/propertiesPanel/CallActivityPropertiesProvider.js index 8aee91d..732e5a1 100644 --- a/app/spiffworkflow/callActivity/propertiesPanel/CallActivityPropertiesProvider.js +++ b/app/spiffworkflow/callActivity/propertiesPanel/CallActivityPropertiesProvider.js @@ -36,6 +36,7 @@ function createCalledElementGroup(element, translate, moddle, commandStack) { return { id: 'called_element', label: translate('Called Element'), + isDefault: true, entries: [ { id: `called_element_text_field`, diff --git a/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js b/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js index 05de8ff..8daaa74 100644 --- a/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js +++ b/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js @@ -48,6 +48,8 @@ function createConditionsGroup(element, translate, moddle, commandStack) { return { id: 'conditions', label: translate('Conditions'), + // is default property is mainly used to mark this group as a high priority group that located in General Tab + isDefault: true, entries: conditionGroup( element, moddle, diff --git a/app/spiffworkflow/eventSelect.js b/app/spiffworkflow/eventSelect.js index ffd823f..4f906d7 100644 --- a/app/spiffworkflow/eventSelect.js +++ b/app/spiffworkflow/eventSelect.js @@ -87,6 +87,7 @@ function getConfigureGroupForType(eventDetails, label, includeCode, getSelect) { return { id: `${idPrefix}-group`, label: label, + isDefault: true, entries, } } diff --git a/app/spiffworkflow/extensions/extensionHelpers.js b/app/spiffworkflow/extensions/extensionHelpers.js index c0059f7..0d9c874 100644 --- a/app/spiffworkflow/extensions/extensionHelpers.js +++ b/app/spiffworkflow/extensions/extensionHelpers.js @@ -95,7 +95,7 @@ export function setExtensionValue(element, name, value, moddle, commandStack, bu } function getExtension(businessObject, name) { - if (!businessObject || !businessObject.extensionElements) { + if (!businessObject.extensionElements) { return null; } const extensionElements = businessObject.extensionElements.get('values'); diff --git a/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js b/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js index d1d7a11..6abe53a 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js +++ b/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js @@ -9,14 +9,14 @@ import { ServiceTaskParameterArray, ServiceTaskOperatorSelect, ServiceTaskResultTextInput, } from './SpiffExtensionServiceProperties'; -import {OPTION_TYPE, spiffExtensionOptions, SpiffExtensionSelect} from './SpiffExtensionSelect'; -import {SpiffExtensionLaunchButton} from './SpiffExtensionLaunchButton'; -import {SpiffExtensionTextArea} from './SpiffExtensionTextArea'; -import {SpiffExtensionTextInput} from './SpiffExtensionTextInput'; -import {SpiffExtensionCheckboxEntry} from './SpiffExtensionCheckboxEntry'; -import {hasEventDefinition} from 'bpmn-js/lib/util/DiUtil'; +import { OPTION_TYPE, spiffExtensionOptions, SpiffExtensionSelect } from './SpiffExtensionSelect'; +import { SpiffExtensionLaunchButton } from './SpiffExtensionLaunchButton'; +import { SpiffExtensionTextArea } from './SpiffExtensionTextArea'; +import { SpiffExtensionTextInput } from './SpiffExtensionTextInput'; +import { SpiffExtensionCheckboxEntry } from './SpiffExtensionCheckboxEntry'; +import { hasEventDefinition } from 'bpmn-js/lib/util/DiUtil'; import { PropertyDescription } from 'bpmn-js-properties-panel/'; -import {setExtensionValue} from "../extensionHelpers"; +import { setExtensionValue } from "../extensionHelpers"; const LOW_PRIORITY = 500; @@ -112,6 +112,8 @@ function createScriptGroup(element, translate, moddle, commandStack) { return { id: 'spiff_script', label: translate('Script'), + // is default property is mainly used to mark this group as a high priority group that located in General Tab + isDefault: true, entries: scriptGroup({ element, moddle, @@ -154,7 +156,7 @@ function preScriptPostScriptGroup(element, translate, moddle, commandStack) { }), ]; const loopCharacteristics = element.businessObject.loopCharacteristics; - if (typeof(loopCharacteristics) !== 'undefined') { + if (typeof (loopCharacteristics) !== 'undefined') { entries.push({ id: 'scriptValence', component: ScriptValenceCheckbox, @@ -211,13 +213,14 @@ function createUserGroup(element, translate, moddle, commandStack) { setExtensionValue(element, 'formUiSchemaFilename', uiName, moddle, commandStack); const matches = spiffExtensionOptions[OPTION_TYPE.json_schema_files].filter((opt) => opt.value === value); if (matches.length === 0) { - spiffExtensionOptions[OPTION_TYPE.json_schema_files].push({label: value, value: value}); + spiffExtensionOptions[OPTION_TYPE.json_schema_files].push({ label: value, value: value }); } } return { id: 'user_task_properties', label: translate('Web Form (with Json Schemas)'), + isDefault: true, entries: [ { element, @@ -288,7 +291,7 @@ function createBusinessRuleGroup(element, translate, moddle, commandStack) { * @param moddle * @returns entries */ -function createUserInstructionsGroup ( +function createUserInstructionsGroup( element, translate, moddle, @@ -329,7 +332,7 @@ function createUserInstructionsGroup ( * @param moddle * @returns entries */ -function createAllowGuestGroup ( +function createAllowGuestGroup( element, translate, moddle, @@ -379,14 +382,14 @@ function createAllowGuestGroup ( * @param moddle * @returns entries */ -function createSignalButtonGroup ( +function createSignalButtonGroup( element, translate, moddle, commandStack ) { let description = -

If attached to a user/manual task, setting this value will display a button which a user can click to immediately fire this signal event. +

If attached to a user/manual task, setting this value will display a button which a user can click to immediately fire this signal event.

return { id: 'signal_button', @@ -417,6 +420,7 @@ function createServiceGroup(element, translate, moddle, commandStack) { return { id: 'service_task_properties', label: translate('Spiffworkflow Service Properties'), + isDefault: true, entries: [ { element, diff --git a/app/spiffworkflow/index.js b/app/spiffworkflow/index.js index 7cfa5a2..7650d8d 100644 --- a/app/spiffworkflow/index.js +++ b/app/spiffworkflow/index.js @@ -18,6 +18,7 @@ import EscalationPropertiesProvider from './escalations/propertiesPanel/Escalati import CallActivityPropertiesProvider from './callActivity/propertiesPanel/CallActivityPropertiesProvider'; import StandardLoopPropertiesProvider from './loops/propertiesPanel/StandardLoopPropertiesProvider'; import MultiInstancePropertiesProvider from './loops/propertiesPanel/MultiInstancePropertiesProvider'; +import PropertiesPanelProvider from './properties/PropertiesPanelProvider'; export default { __depends__: [RulesModule], @@ -36,6 +37,8 @@ export default { 'escalationPropertiesProvider', 'callActivityPropertiesProvider', 'ioPalette', + 'paletteProvider', + 'propertiesPanelProvider', 'ioRules', 'ioInterceptor', 'dataObjectRenderer', @@ -57,6 +60,8 @@ export default { messagesPropertiesProvider: ['type', MessagesPropertiesProvider], callActivityPropertiesProvider: ['type', CallActivityPropertiesProvider], ioPalette: ['type', IoPalette], + paletteProvider: ['type', IoPalette], + propertiesPanelProvider: ['type', PropertiesPanelProvider], ioRules: ['type', IoRules], ioInterceptor: ['type', IoInterceptor], multiInstancePropertiesProvider: ['type', MultiInstancePropertiesProvider], diff --git a/app/spiffworkflow/messages/propertiesPanel/MessagesPropertiesProvider.js b/app/spiffworkflow/messages/propertiesPanel/MessagesPropertiesProvider.js index 6abf6c7..41dfee7 100644 --- a/app/spiffworkflow/messages/propertiesPanel/MessagesPropertiesProvider.js +++ b/app/spiffworkflow/messages/propertiesPanel/MessagesPropertiesProvider.js @@ -181,6 +181,7 @@ function createMessageGroup( return { id: 'messages', label: translate('Message'), + isDefault: true, entries, }; } diff --git a/app/spiffworkflow/properties/PropertiesPanelProvider.js b/app/spiffworkflow/properties/PropertiesPanelProvider.js new file mode 100644 index 0000000..ad1b8c1 --- /dev/null +++ b/app/spiffworkflow/properties/PropertiesPanelProvider.js @@ -0,0 +1,148 @@ +const LOW_PRIORITY = 800; + +export default function PropertiesPanelProvider(propertiesPanel, eventBus) { + let elId; + + // eventBus.on('propertiesPanel.providersChanged', function (event) { + // console.log('------------------- propertiesPanel.providersChanged', event); + // }); + + // eventBus.on('propertiesPanel.getProviders', function (event) { + // console.log('------------------- propertiesPanel.getProviders', event); + // }); + + // eventBus.on('propertiesPanel.setLayout', function (event) { + // console.log('------------------- propertiesPanel.setLayout', event); + // }); + + // eventBus.on('propertiesPanel.layoutChanged', function (event) { + // console.log('------------------- propertiesPanel.layoutChanged', event); + // }); + + this.getGroups = function (element) { + return function (groups) { + // console.log('PropertiesPanelProvider -> getGroups: ', groups, propertiesPanel, element); + // Only render the properties panel once per element + if (element.id !== elId || !elId) { + elId = element.id; + this.render(groups); + } + return groups; + }.bind(this); + }; + + propertiesPanel.registerProvider(LOW_PRIORITY, this); +} + +PropertiesPanelProvider.$inject = ['propertiesPanel', 'eventBus']; + +PropertiesPanelProvider.prototype.render = function (groups) { + + setTimeout(() => { + const propertiesPanelContainer = document.querySelector('.bio-properties-panel-container'); + if (!propertiesPanelContainer) return; + + // Within that big container, find the part where we can scroll + const scrollContainer = propertiesPanelContainer.querySelector('.bio-properties-panel-scroll-container'); + if (!scrollContainer) return; + + // This function makes the groups able to open and close. + function makeGroupCollapsible(group) { + const header = group.querySelector('.bio-properties-panel-group-header'); + const entries = group.querySelector('.bio-properties-panel-group-entries'); + + if (header && entries) { + header.classList.add('open'); + entries.classList.add('open'); + + // When you click the header, it should open or close + header.addEventListener('click', function() { + header.classList.toggle('open'); + entries.classList.toggle('open'); + }); + } + } + + // This function decides what to show based on which tab is clicked. + function updateTabContent(activeTab) { + const allGroups = scrollContainer.querySelectorAll('.bio-properties-panel-group'); + allGroups.forEach(group => group.style.display = 'none'); // Hide everything first. + + groups.forEach(group => { + const groupElement = scrollContainer.querySelector(`[data-group-id="group-${group.id}"]`); + if (groupElement) { + // If we're on the "General" tab, show the general groups and any group that's set as default. + if (activeTab.dataset.tab === 'general' && (group.id === 'general' || group.isDefault)) { + groupElement.style.display = ''; + if (group.isDefault) { + makeGroupCollapsible(groupElement); + } + } else if (activeTab.dataset.tab === 'advanced' && group.id !== 'general' && !group.isDefault) { + groupElement.style.display = ''; + } + } + }); + } + + // Add a click event to each tab to change what's shown + document.querySelectorAll('.tabs li').forEach(tab => { + tab.addEventListener('click', function(event) { + document.querySelectorAll('.tabs li').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + updateTabContent(tab); + }); + }); + + // Function to always start with the 'General' tab opened + function resetTabsToShowGeneral() { + const generalTab = scrollContainer.querySelector('li[data-tab="general"]'); + const advancedTab = scrollContainer.querySelector('li[data-tab="advanced"]'); + + if (generalTab && advancedTab) { + generalTab.classList.add('active'); + advancedTab.classList.remove('active'); + updateTabContent(generalTab); + } + } + + // Create the tabs if they don't exist yet. + if (!scrollContainer.querySelector('.tabs')) { + const tabsHeader = document.createElement('ul'); + tabsHeader.className = 'tabs property-tabs'; + + const generalTab = document.createElement('li'); + generalTab.textContent = 'General'; + generalTab.dataset.tab = 'general'; + generalTab.className = 'active'; + tabsHeader.appendChild(generalTab); + + const advancedTab = document.createElement('li'); + advancedTab.textContent = 'Advanced'; + advancedTab.dataset.tab = 'advanced'; + tabsHeader.appendChild(advancedTab); + + scrollContainer.insertBefore(tabsHeader, scrollContainer.firstChild); + } + + // Make sure each tab can do its thing when clicked. + const tabs = scrollContainer.querySelectorAll('.tabs li'); + tabs.forEach(tab => { + if (!tab.dataset.listenerAttached) { + tab.addEventListener('click', function(event) { + tabs.forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + updateTabContent(tab); + }); + tab.dataset.listenerAttached = 'true'; // This is just to make sure we don't add the same event more than once. + } + }); + + // When we first load, show the right tab and its contents. + const activeTab = document.querySelector('.tabs li.active'); + if (activeTab) { + updateTabContent(activeTab); + } + + resetTabsToShowGeneral(); + }, 0); +} diff --git a/test/spec/BpmnInputOutputSpec.js b/test/spec/BpmnInputOutputSpec.js index 0d2c201..9e7286b 100644 --- a/test/spec/BpmnInputOutputSpec.js +++ b/test/spec/BpmnInputOutputSpec.js @@ -22,8 +22,8 @@ describe('BPMN Input / Output', function() { it('should have a data input and data output in the properties panel', function() { var paletteElement = domQuery('.djs-palette', CONTAINER); var entries = domQueryAll('.entry', paletteElement); - expect(entries[11].title).to.equals('Create DataInput'); - expect(entries[12].title).to.equals('Create DataOutput'); + expect(entries[14].title).to.equals('Data Input'); + expect(entries[15].title).to.equals('Data Output'); }); }); From b4ebd6927981367c107df5d6ae4efbb662ef14ec Mon Sep 17 00:00:00 2001 From: Ayoub Ait Lachgar <44379029+theaubmov@users.noreply.github.com> Date: Fri, 5 Jan 2024 08:54:52 +0100 Subject: [PATCH 2/4] fix release-1 issue --- .../properties/PropertiesPanelProvider.js | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/app/spiffworkflow/properties/PropertiesPanelProvider.js b/app/spiffworkflow/properties/PropertiesPanelProvider.js index ad1b8c1..df738b4 100644 --- a/app/spiffworkflow/properties/PropertiesPanelProvider.js +++ b/app/spiffworkflow/properties/PropertiesPanelProvider.js @@ -37,7 +37,7 @@ export default function PropertiesPanelProvider(propertiesPanel, eventBus) { PropertiesPanelProvider.$inject = ['propertiesPanel', 'eventBus']; PropertiesPanelProvider.prototype.render = function (groups) { - + setTimeout(() => { const propertiesPanelContainer = document.querySelector('.bio-properties-panel-container'); if (!propertiesPanelContainer) return; @@ -46,20 +46,29 @@ PropertiesPanelProvider.prototype.render = function (groups) { const scrollContainer = propertiesPanelContainer.querySelector('.bio-properties-panel-scroll-container'); if (!scrollContainer) return; + // Functions : + // This function makes the groups able to open and close. function makeGroupCollapsible(group) { const header = group.querySelector('.bio-properties-panel-group-header'); const entries = group.querySelector('.bio-properties-panel-group-entries'); + const arrow = header.querySelector('svg'); - if (header && entries) { + let isFirstClick = true; + + if (header && entries && arrow) { header.classList.add('open'); entries.classList.add('open'); - - // When you click the header, it should open or close - header.addEventListener('click', function() { - header.classList.toggle('open'); - entries.classList.toggle('open'); - }); + arrow.classList.add('bio-properties-panel-arrow-down'); + arrow.classList.remove('bio-properties-panel-arrow-right'); + + // Handles the first click, to prevent the bpmn js library from handling it instead + header.addEventListener('click', function(event) { + if (isFirstClick) { + header.click(); + isFirstClick = false; + } + }, { once: true }); } } @@ -75,7 +84,7 @@ PropertiesPanelProvider.prototype.render = function (groups) { if (activeTab.dataset.tab === 'general' && (group.id === 'general' || group.isDefault)) { groupElement.style.display = ''; if (group.isDefault) { - makeGroupCollapsible(groupElement); + makeGroupCollapsible(groupElement); } } else if (activeTab.dataset.tab === 'advanced' && group.id !== 'general' && !group.isDefault) { groupElement.style.display = ''; @@ -84,15 +93,6 @@ PropertiesPanelProvider.prototype.render = function (groups) { }); } - // Add a click event to each tab to change what's shown - document.querySelectorAll('.tabs li').forEach(tab => { - tab.addEventListener('click', function(event) { - document.querySelectorAll('.tabs li').forEach(t => t.classList.remove('active')); - tab.classList.add('active'); - updateTabContent(tab); - }); - }); - // Function to always start with the 'General' tab opened function resetTabsToShowGeneral() { const generalTab = scrollContainer.querySelector('li[data-tab="general"]'); @@ -105,6 +105,15 @@ PropertiesPanelProvider.prototype.render = function (groups) { } } + // Add a click event to each tab to change what's shown + document.querySelectorAll('.tabs li').forEach(tab => { + tab.addEventListener('click', function (event) { + document.querySelectorAll('.tabs li').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + updateTabContent(tab); + }); + }); + // Create the tabs if they don't exist yet. if (!scrollContainer.querySelector('.tabs')) { const tabsHeader = document.createElement('ul'); @@ -128,7 +137,7 @@ PropertiesPanelProvider.prototype.render = function (groups) { const tabs = scrollContainer.querySelectorAll('.tabs li'); tabs.forEach(tab => { if (!tab.dataset.listenerAttached) { - tab.addEventListener('click', function(event) { + tab.addEventListener('click', function (event) { tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); updateTabContent(tab); @@ -144,5 +153,5 @@ PropertiesPanelProvider.prototype.render = function (groups) { } resetTabsToShowGeneral(); - }, 0); + }, 0); } From 7889b4d54acd4309940cdd1f62ff6b5aaabb30d5 Mon Sep 17 00:00:00 2001 From: Ayoub Ait Lachgar <44379029+theaubmov@users.noreply.github.com> Date: Fri, 5 Jan 2024 09:08:12 +0100 Subject: [PATCH 3/4] handle second issue --- .../properties/PropertiesPanelProvider.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/spiffworkflow/properties/PropertiesPanelProvider.js b/app/spiffworkflow/properties/PropertiesPanelProvider.js index df738b4..70d89c8 100644 --- a/app/spiffworkflow/properties/PropertiesPanelProvider.js +++ b/app/spiffworkflow/properties/PropertiesPanelProvider.js @@ -1,7 +1,7 @@ const LOW_PRIORITY = 800; export default function PropertiesPanelProvider(propertiesPanel, eventBus) { - let elId; + let el; // eventBus.on('propertiesPanel.providersChanged', function (event) { // console.log('------------------- propertiesPanel.providersChanged', event); @@ -21,10 +21,12 @@ export default function PropertiesPanelProvider(propertiesPanel, eventBus) { this.getGroups = function (element) { return function (groups) { - // console.log('PropertiesPanelProvider -> getGroups: ', groups, propertiesPanel, element); - // Only render the properties panel once per element - if (element.id !== elId || !elId) { - elId = element.id; + // Only render when : el is undefined (editor onload state) or user selects new element or user changes the type of a selected element + if (!el || element.id !== el.id || (element.type !== el.type && element.id === el.id)) { + el = { + id: element.id, + type: element.type + } this.render(groups); } return groups; @@ -39,6 +41,7 @@ PropertiesPanelProvider.$inject = ['propertiesPanel', 'eventBus']; PropertiesPanelProvider.prototype.render = function (groups) { setTimeout(() => { + console.log('Render ;;;') const propertiesPanelContainer = document.querySelector('.bio-properties-panel-container'); if (!propertiesPanelContainer) return; @@ -63,12 +66,12 @@ PropertiesPanelProvider.prototype.render = function (groups) { arrow.classList.remove('bio-properties-panel-arrow-right'); // Handles the first click, to prevent the bpmn js library from handling it instead - header.addEventListener('click', function(event) { + header.addEventListener('click', function() { if (isFirstClick) { header.click(); isFirstClick = false; } - }, { once: true }); + }); } } From 6704584ad2d99a172b9169403cc3d38debe3837c Mon Sep 17 00:00:00 2001 From: Ayoub Ait Lachgar <44379029+theaubmov@users.noreply.github.com> Date: Fri, 5 Jan 2024 09:10:03 +0100 Subject: [PATCH 4/4] remove console --- app/spiffworkflow/properties/PropertiesPanelProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/spiffworkflow/properties/PropertiesPanelProvider.js b/app/spiffworkflow/properties/PropertiesPanelProvider.js index 70d89c8..32186f2 100644 --- a/app/spiffworkflow/properties/PropertiesPanelProvider.js +++ b/app/spiffworkflow/properties/PropertiesPanelProvider.js @@ -41,7 +41,6 @@ PropertiesPanelProvider.$inject = ['propertiesPanel', 'eventBus']; PropertiesPanelProvider.prototype.render = function (groups) { setTimeout(() => { - console.log('Render ;;;') const propertiesPanelContainer = document.querySelector('.bio-properties-panel-container'); if (!propertiesPanelContainer) return;