(null);
+ const definitionJson = JSON.stringify(definition.value, null, 2);
+
+ useEffect(() =>
+ {
+ console.log(`definition updated, isValid=${definition.isValid}`);
+ }, [definition]);
+
+ useEffect(() =>
+ {
+ if (moveViewportToStep)
+ {
+ if (controller.isReady())
+ {
+ controller.moveViewportToStep(moveViewportToStep);
+ }
+ setMoveViewportToStep(null);
+ }
+ }, [controller, moveViewportToStep]);
+
+ function toggleVisibilityClicked()
+ {
+ setIsVisible(!isVisible);
+ }
+
+ function toggleSelectionClicked()
+ {
+ const id = definition.value.sequence[0].id;
+ setSelectedStepId(selectedStepId ? null : id);
+ }
+
+ function toggleIsReadonlyClicked()
+ {
+ setIsReadonly(!isReadonly);
+ }
+
+ function toggleToolboxClicked()
+ {
+ setIsToolboxCollapsed(!isToolboxCollapsed);
+ }
+
+ function toggleEditorClicked()
+ {
+ setIsEditorCollapsed(!isEditorCollapsed);
+ }
+
+ function moveViewportToFirstStepClicked()
+ {
+ const fistStep = definition.value.sequence[0];
+ if (fistStep)
+ {
+ setMoveViewportToStep(fistStep.id);
+ }
+ }
+
+ async function appendStepClicked()
+ {
+ const newStep = createTaskStep();
+
+ const newDefinition = ObjectCloner.deepClone(definition.value);
+ newDefinition.sequence.push(newStep);
+ // We need to wait for the controller to finish the operation before we can select the new step
+ await controller.replaceDefinition(newDefinition);
+
+ setSelectedStepId(newStep.id);
+ setMoveViewportToStep(newStep.id);
+ }
+
+ function reloadDefinitionClicked()
+ {
+ const newDefinition = ObjectCloner.deepClone(startDefinition);
+ setSelectedStepId(null);
+ setDefinition(wrapDefinition(newDefinition));
+ }
+
+ function yesOrNo(value: boolean)
+ {
+ return value ? "✅ Yes" : "⛔ No";
+ }
+
+ return (
+ <>
+ {isVisible && (
+ }
+ stepEditor={}
+ isEditorCollapsed={isEditorCollapsed}
+ onIsEditorCollapsedChanged={setIsEditorCollapsed}
+ controller={controller}
+ />
+ )}
+
+
+ - Definition: {definitionJson.length} bytes
+ - Selected step: {selectedStepId}
+ - Is readonly: {yesOrNo(isReadonly)}
+ - Is valid: {definition.isValid === undefined ? "?" : yesOrNo(definition.isValid)}
+ - Is toolbox collapsed: {yesOrNo(isToolboxCollapsed)}
+ - Is editor collapsed: {yesOrNo(isEditorCollapsed)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default WorkflowPreview;
diff --git a/src/qqq/components/workflows/model.ts b/src/qqq/components/workflows/model.ts
new file mode 100644
index 0000000..677dc64
--- /dev/null
+++ b/src/qqq/components/workflows/model.ts
@@ -0,0 +1,75 @@
+import {BranchedStep, Definition, Step} from "sequential-workflow-designer";
+
+export interface WorkflowDefinition extends Definition
+{
+ properties: {
+ alfa?: string;
+ };
+}
+
+export interface TaskStep extends Step
+{
+ componentType: "task";
+ type: "task";
+ properties: {
+ x?: string;
+ y?: string;
+ warehouse?: string;
+ wmsConnection?: string;
+ wmsSystem?: string;
+ };
+}
+
+export type OptimizationStepType =
+ "determineWarehouseRouting" |
+ "determineLineHaulLane" |
+ "validateLineItems" |
+ "determineCoolingCategory" |
+ "validateOptimizationRules" |
+ "validateAddress" |
+ "determineCarrierService" |
+ "determineTNT" |
+ "determineOrderServiceDates" |
+ "orderMatchesFilterSelector";
+
+
+export interface WarehouseOptimizationStep extends Step
+{
+ componentType: "task";
+ type: OptimizationStepType;
+ properties: {
+ x?: string;
+ y?: string;
+ warehouse?: string;
+ wmsConnection?: string;
+ wmsSystem?: string;
+ isValid?: boolean;
+ };
+}
+
+export interface SwitchStep extends BranchedStep
+{
+ componentType: "switch";
+ type: "switch";
+ properties: {
+ x?: string;
+ y?: string;
+ warehouse?: string;
+ wmsConnection?: string;
+ wmsSystem?: string;
+ };
+}
+
+export interface ContainerStep extends Step
+{
+ componentType: "container";
+ type: "container";
+ properties: {
+ x?: string;
+ y?: string;
+ warehouse?: string;
+ wmsConnection?: string;
+ wmsSystem?: string;
+ };
+ sequence: (WarehouseOptimizationStep | SwitchStep)[];
+}
diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css
index ef5d407..5cb1d2f 100644
--- a/src/qqq/styles/qqq-override-styles.css
+++ b/src/qqq/styles/qqq-override-styles.css
@@ -787,3 +787,504 @@ input[type="search"]::-webkit-search-results-decoration
{
margin: 2rem 1rem;
}
+
+
+.sqd-designer-react {
+ width: 100vw;
+ height: 90vh;
+}
+
+.sqd-editor {
+ padding: 10px;
+}
+input:read-only {
+ opacity: 0.35;
+}
+
+.sqd-editor {
+ padding: 10px;
+}
+input:read-only {
+ opacity: 0.35;
+}
+
+
+/* internal */
+.sqd-theme-light .sqd-toolbox {
+ background: #fff;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.15);
+ border-radius: 10px;
+}
+.sqd-theme-light .sqd-toolbox-header-title {
+ color: #000;
+}
+.sqd-theme-light .sqd-toolbox-filter {
+ background: #fff;
+ color: #000;
+ border: 1px solid #c3c3c3;
+ border-radius: 10px;
+}
+.sqd-theme-light .sqd-toolbox-filter:focus {
+ border-color: #939393;
+}
+.sqd-theme-light .sqd-toolbox-group-title {
+ color: #000;
+ background: #e5e5e5;
+ border-radius: 10px;
+}
+
+.sqd-theme-light .sqd-toolbox-item {
+ color: #000;
+ border: 1px solid #c3c3c3;
+ box-shadow: 0 2px 2px rgba(0, 0, 0, 0.15);
+ background: #fff;
+ border-radius: 5px;
+}
+.sqd-theme-light .sqd-toolbox-item:hover {
+ border-color: #939393;
+ background: #fff;
+}
+.sqd-theme-light .sqd-toolbox-item .sqd-toolbox-item-icon.sqd-no-icon {
+ background: #c6c6c6;
+ border-radius: 4px;
+}
+
+.sqd-theme-light .sqd-control-bar {
+ background: #fff;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.15);
+ border-radius: 10px;
+}
+.sqd-theme-light .sqd-control-bar-button {
+ border: 1px solid #c3c3c3;
+ background: #fff;
+ border-radius: 5px;
+}
+.sqd-theme-light .sqd-control-bar-button:hover {
+ border-color: #939393;
+ background: #fff;
+}
+.sqd-theme-light .sqd-control-bar-button .sqd-icon-path {
+ fill: #000;
+}
+.sqd-theme-light .sqd-control-bar-button.sqd-delete .sqd-icon-path {
+ fill: #e01a24;
+}
+
+.sqd-theme-light .sqd-smart-editor {
+ background: #fff;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
+}
+.sqd-theme-light .sqd-smart-editor-toggle {
+ background: #fff;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
+}
+
+.sqd-theme-light.sqd-context-menu {
+ background: #fff;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
+ border-radius: 4px;
+}
+
+.sqd-theme-light .sqd-context-menu-group {
+ color: #888;
+}
+
+.sqd-theme-light .sqd-context-menu-item {
+ color: #000;
+ border-radius: 4px;
+}
+
+.sqd-theme-light .sqd-context-menu-item:hover {
+ background: #eee;
+}
+
+.sqd-theme-light.sqd-designer {
+ background: #f9f9f9;
+}
+
+.sqd-theme-light .sqd-line-grid-path {
+ stroke: #e3e3e3;
+ stroke-width: 1;
+}
+
+.sqd-theme-light .sqd-join {
+ stroke-width: 2;
+ stroke: #000;
+}
+
+.sqd-theme-light .sqd-region {
+ stroke: #cecece;
+ stroke-width: 2;
+ stroke-dasharray: 3;
+}
+.sqd-theme-light .sqd-region.sqd-selected {
+ stroke: #ed4800;
+ stroke-width: 2;
+ stroke-dasharray: 0;
+}
+
+.sqd-theme-light .sqd-placeholder .sqd-placeholder-rect {
+ fill: #d8d8d8;
+ stroke: #6a6a6a;
+ stroke-width: 1;
+ stroke-dasharray: 3;
+}
+.sqd-theme-light .sqd-placeholder.sqd-hover .sqd-placeholder-rect {
+ fill: #ed4800;
+}
+.sqd-theme-light .sqd-placeholder-icon-path {
+ fill: #2b2b2b;
+}
+.sqd-theme-light .sqd-placeholder.sqd-hover .sqd-placeholder-icon-path {
+ fill: #fff;
+}
+
+.sqd-theme-light .sqd-validation-error {
+ fill: #ffa200;
+}
+.sqd-theme-light .sqd-validation-error-icon-path {
+ fill: #000;
+}
+
+.sqd-theme-light .sqd-root-start-stop-circle {
+ fill: #2c18df;
+}
+.sqd-theme-light .sqd-root-start-stop-icon {
+ fill: #fff;
+}
+
+.sqd-theme-light .sqd-step-task .sqd-step-task-rect {
+ fill: #fff;
+ stroke-width: 1;
+ stroke: #c3c3c3;
+ filter: drop-shadow(0 1.5px 1.5px rgba(0, 0, 0, 0.15));
+}
+.sqd-theme-light .sqd-step-task .sqd-step-task-rect.sqd-selected {
+ stroke: #ed4800;
+ stroke-width: 2;
+}
+.sqd-theme-light .sqd-step-task .sqd-step-task-text {
+ fill: #000;
+}
+.sqd-theme-light .sqd-step-task .sqd-step-task-empty-icon {
+ fill: #c6c6c6;
+}
+.sqd-theme-light .sqd-step-task .sqd-input {
+ fill: #fff;
+ stroke-width: 2;
+ stroke: #000;
+}
+.sqd-theme-light .sqd-step-task .sqd-output {
+ fill: #000;
+ stroke-width: 0;
+}
+
+.sqd-theme-light .sqd-step-switch > .sqd-label-primary > .sqd-label-text {
+ fill: #fff;
+}
+.sqd-theme-light .sqd-step-switch > .sqd-label-primary > .sqd-label-rect {
+ fill: #2411db;
+ stroke-width: 0;
+}
+.sqd-theme-light .sqd-step-switch > .sqd-label-secondary > .sqd-label-rect {
+ fill: #000;
+ stroke-width: 0;
+}
+.sqd-theme-light .sqd-step-switch > .sqd-label-secondary > .sqd-label-text {
+ fill: #fff;
+}
+.sqd-theme-light .sqd-step-switch > g > .sqd-input {
+ fill: #fff;
+ stroke-width: 2;
+ stroke: #000;
+}
+
+.sqd-theme-light .sqd-step-container > .sqd-label > .sqd-label-text {
+ fill: #fff;
+}
+.sqd-theme-light .sqd-step-container > .sqd-label > .sqd-label-rect {
+ fill: #2411db;
+ stroke-width: 0;
+}
+.sqd-theme-light .sqd-step-container > g > .sqd-input {
+ fill: #fff;
+ stroke-width: 2;
+ stroke: #000;
+}
+
+
+/* .sqd-designer */
+.sqd-designer {
+ position: relative;
+ display: flex;
+ width: 100%;
+ height: 100%;
+}
+
+.sqd-designer,
+.sqd-drag,
+.sqd-context-menu {
+ font-size: 13px;
+ line-height: 1em;
+}
+
+.sqd-hidden {
+ display: none !important;
+}
+
+.sqd-disabled {
+ opacity: 0.25;
+}
+
+/* .sqd-toolbox */
+.sqd-toolbox,
+.sqd-toolbox-filter {
+ font-size: 11px;
+ line-height: 1.2em;
+}
+
+.sqd-toolbox {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 20;
+ box-sizing: border-box;
+ width: 250px;
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.sqd-toolbox-header {
+ position: relative;
+ padding: 15px 10px;
+ cursor: pointer;
+}
+
+.sqd-toolbox-header-title {
+ display: block;
+ font-size: 1.2em;
+ line-height: 1em;
+ font-weight: bold;
+}
+
+.sqd-toolbox-toggle-icon {
+ position: absolute;
+ top: 50%;
+ right: 10px;
+ width: 16px;
+ height: 16px;
+ margin: -8px 0 0;
+}
+
+.sqd-toolbox-header:hover .sqd-toolbox-toggle-icon {
+ opacity: 0.6;
+}
+
+.sqd-scrollbox {
+ position: relative;
+ overflow: hidden;
+}
+
+.sqd-scrollbox-body {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+}
+
+.sqd-toolbox-filter {
+ display: block;
+ box-sizing: border-box;
+ padding: 6px 8px;
+ outline: none;
+ width: 110px;
+ margin: 0 10px 10px;
+ box-sizing: border-box;
+}
+
+.sqd-toolbox-group-title {
+ text-align: center;
+ padding: 5px 0;
+ margin: 0 10px 10px;
+}
+
+.sqd-toolbox-item {
+ position: relative;
+ box-sizing: border-box;
+ margin: 0 10px 10px;
+ cursor: move;
+ width: 90%;
+}
+
+.sqd-toolbox-item-icon {
+ position: absolute;
+ top: 50%;
+ left: 5px;
+ margin-top: -10px;
+ width: 20px;
+ height: 20px;
+}
+
+.sqd-toolbox-item-icon-image {
+ width: 100%;
+ height: 100%;
+}
+
+.sqd-toolbox-item-text {
+ position: relative;
+ display: block;
+ padding: 10px 10px 10px 30px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.sqd-drag {
+ position: absolute;
+ z-index: 9999999;
+ pointer-events: none;
+}
+
+/* .sqd-control-bar */
+.sqd-control-bar {
+ position: absolute;
+ bottom: 10px;
+ left: 10px;
+ z-index: 20;
+ padding: 8px 0 8px 8px;
+ white-space: nowrap;
+}
+
+.sqd-control-bar-button {
+ display: inline-block;
+ width: 32px;
+ height: 32px;
+ margin-right: 8px;
+ cursor: pointer;
+ box-sizing: border-box;
+}
+
+.sqd-control-bar-button-icon {
+ width: 24px;
+ height: 24px;
+ margin: 3px 0 0 3px;
+}
+
+.sqd-control-bar-button.sqd-disabled .sqd-control-bar-button-icon {
+ opacity: 0.2;
+}
+
+/* .sqd-smart-editor */
+.sqd-smart-editor-toggle {
+ position: absolute;
+ top: 0;
+ z-index: 29;
+ width: 36px;
+ height: 64px;
+ border-bottom-left-radius: 10px;
+ cursor: pointer;
+}
+
+.sqd-smart-editor-toggle-icon {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 24px;
+ height: 24px;
+ margin: -12px 0 0 -12px;
+}
+
+.sqd-smart-editor-toggle:hover .sqd-smart-editor-toggle-icon {
+ opacity: 0.6;
+}
+
+.sqd-smart-editor {
+ z-index: 30;
+}
+
+.sqd-layout-desktop .sqd-smart-editor {
+ position: relative;
+ width: 300px;
+}
+
+.sqd-layout-desktop .sqd-smart-editor-toggle {
+ right: 300px;
+}
+
+.sqd-layout-desktop .sqd-smart-editor-toggle.sqd-collapsed {
+ right: 0;
+}
+
+.sqd-layout-mobile .sqd-smart-editor {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 41px;
+}
+
+.sqd-layout-mobile .sqd-smart-editor-toggle {
+ left: 5px;
+}
+
+.sqd-layout-mobile .sqd-smart-editor-toggle.sqd-collapsed {
+ left: auto;
+ right: 0;
+}
+
+/* .sqd-context-menu */
+.sqd-context-menu {
+ position: absolute;
+ z-index: 2000000000;
+ overflow: hidden;
+ padding: 5px;
+}
+
+.sqd-context-menu-group,
+.sqd-context-menu-item {
+ width: 130px;
+ padding: 8px 10px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.sqd-context-menu-group {
+ font-size: 11px;
+ line-height: 1em;
+}
+
+.sqd-context-menu-item {
+ cursor: pointer;
+ transition: background 70ms;
+}
+
+/* .sqd-workspace */
+.sqd-workspace {
+ flex: 1;
+ position: relative;
+ display: block;
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.sqd-workspace-canvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: move;
+}
+
+.sqd-label-text {
+ text-anchor: middle;
+ dominant-baseline: central;
+}
+
+.sqd-placeholder .sqd-placeholder-rect {
+ transition: fill 100ms;
+}
+
+.sqd-step-task-text {
+ text-anchor: left;
+ dominant-baseline: central;
+}