Merge pull request #61 from sartography/ux/release-1

Release 1 : New Layout UI
This commit is contained in:
Kevin Burnett 2024-01-09 06:49:00 -08:00 committed by GitHub
commit 6f9d721425
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1053 additions and 95 deletions

View File

@ -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

View File

@ -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;
}

40
app/css/bpmn-js.css Normal file
View File

@ -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;
}

BIN
app/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -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);
}
}

View File

@ -1,22 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<!--
IMPORTANT:
This is here to provide an exmaple of how you might use this library in your application.
You should be able to take this example, and modify it to suite your own needs.
-->
<title>bpmn-js-spiffworkflow</title>
<meta charset="utf-8"/>
<head>
<title>bpmn-js-spiffworkflow</title>
<meta charset="utf-8" />
<!-- here are the core dependencies you will need to include -->
<link rel="stylesheet" href="vendor/bpmn-js/assets/diagram-js.css"/>
<link rel="stylesheet" href="vendor/bpmn-js/assets/bpmn-js.css"/>
<link rel="stylesheet" href="vendor/bpmn-js/assets/bpmn-font/css/bpmn-embedded.css"/>
<link rel="stylesheet" href="vendor/bpmn-js-properties-panel/assets/properties-panel.css"/>
<link rel="stylesheet" href="vendor/bpmn-js/assets/diagram-js.css" />
<link rel="stylesheet" href="vendor/bpmn-js/assets/bpmn-js.css" />
<link rel="stylesheet" href="vendor/bpmn-js/assets/bpmn-font/css/bpmn-embedded.css" />
<link rel="stylesheet" href="vendor/bpmn-js-properties-panel/assets/properties-panel.css" />
<!-- Some local css settings -->
<link rel="stylesheet" href="css/app.css"/>
<link rel="stylesheet" href="css/app.css" />
<link rel="stylesheet" href="css/bpmn-js.css" />
<link rel="shortcut icon" href="#">
<!-- A python code editor, we are using CodeMirror here -- see app.js for how this is wired in -->
@ -32,33 +29,145 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div id="menu">
<button id="downloadButton" class="bpmn-js-spiffworkflow-btn"><i class="fa fa-download"></i> Download</button>
<button id="uploadButton" class="bpmn-js-spiffworkflow-btn">Open a file</button>
</div>
<div id="container">
<div id="modeler"></div>
<div id="panel"></div>
</div>
<!-- the following are overlays to provide editors for Python and Markdown -->
<div id="code_overlay" class="overlay">
<div id="code_editor"></div>
<div id="code_buttons">
<button id="saveCode" class="bpmn-js-spiffworkflow-btn">Save</button>
</div>
</div>
<div id="markdown_overlay" class="overlay">
<div id="markdown_editor">
<textarea id="markdown_textarea"></textarea>
</div>
<div id="markdown_buttons">
<button id="saveMarkdown" class="bpmn-js-spiffworkflow-btn">Save</button>
</div>
</div>
<!-- Here we load up our application, it's where the configuration happens. -->
<script src="app.js"></script>
<body>
<div id="header">
<div id="process-info">
<h1>Process Name</h1>
<p>. / Process Groups / bpmn_process.bpmn </p>
</div>
<div id="header-actions-center">
<button class="header-btn" title="Hand" data-action="hand">
<i class="bpmn-icon-hand-tool"></i>
</button>
<button class="header-btn" title="LassoTool" data-action="lasso">
<i class="bpmn-icon-lasso-tool"></i>
</button>
<button class="header-btn" title="SpaceTool" data-action="space">
<i class="bpmn-icon-space-tool"></i>
</button>
<button class="header-btn" title="Connect" data-action="connect">
<i class="bpmn-icon-connection-multi"></i>
</button>
<button class="header-btn" title="Zoom In" data-action="zoom-in">
<i class="fa fa-search-plus"></i>
</button>
<button class="header-btn" title="Zoom Out" data-action="zoom-out">
<i class="fa fa-search-minus"></i>
</button>
<button class="header-btn" title="Expand" data-action="expand">
<i class="fa fa-expand"></i>
</button>
<button class="header-btn" title="Undo" data-action="undo">
<i class="fa fa-undo"></i>
</button>
<button class="header-btn" title="Redo" data-action="redo">
<i class="fa fa-repeat"></i>
</button>
</div>
<div id="header-actions">
<button id="downloadButton" class="bpmn-js-spiffworkflow-btn">
Download
</button>
<button id="uploadButton" class="bpmn-js-spiffworkflow-btn">Open a file</button>
<button id="viewXmlButton" class="bpmn-js-spiffworkflow-btn">View XML</button>
<button id="saveButton" class="bpmn-js-spiffworkflow-btn main-btn">
Save <i class="fa fa-long-arrow-right"></i>
</button>
</div>
</div>
<div id="left-sidebar">
<div class="tabs">
<button class="tab-button active" data-tab-target="BPMNElements">
<i class="fa fa-th-large"></i>
</button>
<button class="tab-button" data-tab-target="SearchTab">
<i class="fa fa-search"></i>
</button>
<button class="tab-button" data-tab-target="ActionTab">
<i class="fa fa-bolt"></i>
</button>
<button class="tab-button" data-tab-target="ConnectorTab">
<i class="fa fa-cube"></i>
</button>
<button class="tab-button" data-tab-target="HistoryTab">
<i class="fa fa-clock-o"></i>
</button>
</div>
<div id="BPMNElements" class="tab-content active">
<div class="bpmn-elements-header">
<span class="bpmn-elements-title">BPMN Elements</span>
<button class="bpmn-elements-toggle">
<i class="fa fa-outdent" aria-hidden="true"></i>
</button>
</div>
<!-- Content for BPMN Elements -->
</div>
<div id="HistoryTab" class="tab-content">
<div class="bpmn-elements-header">
<span class="bpmn-elements-title">History</span>
<button class="bpmn-elements-toggle">
<i class="fa fa-outdent" aria-hidden="true"></i>
</button>
</div>
<!-- Content for BPMN Elements -->
History Tab Content
</div>
<div id="ActionTab" class="tab-content">
<div class="bpmn-elements-header">
<span class="bpmn-elements-title">Visual Activity</span>
<button class="bpmn-elements-toggle">
<i class="fa fa-outdent" aria-hidden="true"></i>
</button>
</div>
<!-- Content for BPMN Elements -->
Pre built Actions Tab Content
</div>
<div id="SearchTab" class="tab-content">
<div class="bpmn-elements-header">
<span class="bpmn-elements-title">Search</span>
<button class="bpmn-elements-toggle">
<i class="fa fa-outdent" aria-hidden="true"></i>
</button>
</div>
<!-- Content for BPMN Elements -->
Search Tab Content
</div>
<div id="ConnectorTab" class="tab-content">
<div class="bpmn-elements-header">
<span class="bpmn-elements-title">Integrations / Connectors </span>
<button class="bpmn-elements-toggle">
<i class="fa fa-outdent" aria-hidden="true"></i>
</button>
</div>
<!-- Content for BPMN Elements -->
Connectors Tab Content
</div>
</div>
<div id="container">
<div id="modeler"></div>
<div id="panel"></div>
</div>
<!-- the following are overlays to provide editors for Python and Markdown -->
<div id="code_overlay" class="overlay">
<div id="code_editor"></div>
<div id="code_buttons">
<button id="saveCode" class="bpmn-js-spiffworkflow-btn">Save</button>
</div>
</div>
<div id="markdown_overlay" class="overlay">
<div id="markdown_editor">
<textarea id="markdown_textarea"></textarea>
</div>
<div id="markdown_buttons">
<button id="saveMarkdown" class="bpmn-js-spiffworkflow-btn">Save</button>
</div>
</div>
<!-- Here we load up our application, it's where the configuration happens. -->
<script src="app.js"></script>
</body>
</html><!---->
</html>

View File

@ -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',

View File

@ -51,6 +51,7 @@ function createCustomDataStoreGroup(
const group = {
label: translate('Custom Data Store Properties'),
id: 'custom-datastore-properties',
isDefault: true,
entries: [],
};

View File

@ -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 = '<i class="fa fa-chevron-down" aria-hidden="true"></i>';
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' ? '<i class="fa fa-chevron-right" aria-hidden="true"></i>' : '<i class="fa fa-chevron-down" aria-hidden="true"></i>';
});
}
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);
};

View File

@ -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`,

View File

@ -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,

View File

@ -87,6 +87,7 @@ function getConfigureGroupForType(eventDetails, label, includeCode, getSelect) {
return {
id: `${idPrefix}-group`,
label: label,
isDefault: true,
entries,
}
}

View File

@ -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');

View File

@ -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 =
<p style={{maxWidth : "330px"}}> 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.
<p style={{ maxWidth: "330px" }}> 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.
</p>
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,

View File

@ -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],

View File

@ -181,6 +181,7 @@ function createMessageGroup(
return {
id: 'messages',
label: translate('Message'),
isDefault: true,
entries,
};
}

View File

@ -0,0 +1,159 @@
const LOW_PRIORITY = 800;
export default function PropertiesPanelProvider(propertiesPanel, eventBus) {
let el;
// 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) {
// 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;
}.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;
// 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');
let isFirstClick = true;
if (header && entries && arrow) {
header.classList.add('open');
entries.classList.add('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() {
if (isFirstClick) {
header.click();
isFirstClick = false;
}
});
}
}
// 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 = '';
}
}
});
}
// 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);
}
}
// 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');
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);
}

View File

@ -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');
});
});