UNPKG

sequential-workflow-designer

Version:

Customizable no-code component for building flow-based programming applications.

1,224 lines (1,189 loc) 204 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.sequentialWorkflowDesigner = {})); })(this, (function (exports) { 'use strict'; class Dom { static svg(name, attributes) { const element = document.createElementNS('http://www.w3.org/2000/svg', name); if (attributes) { Dom.attrs(element, attributes); } return element; } static translate(element, x, y) { element.setAttribute('transform', `translate(${x}, ${y})`); } static attrs(element, attributes) { Object.keys(attributes).forEach(name => { const value = attributes[name]; element.setAttribute(name, typeof value === 'string' ? value : value.toString()); }); } static element(name, attributes) { const element = document.createElement(name); if (attributes) { Dom.attrs(element, attributes); } return element; } static toggleClass(element, isEnabled, className) { if (isEnabled) { element.classList.add(className); } else { element.classList.remove(className); } } } // Source: https://fonts.google.com/icons or https://github.com/google/material-design-icons class Icons { static appendPath(parent, pathClassName, d, size) { const g = Dom.svg('g'); const scale = size / 48; const path = Dom.svg('path', { d, class: pathClassName, transform: `scale(${scale})` }); g.appendChild(path); parent.appendChild(g); return g; } static createSvg(className, d) { const icon = Dom.svg('svg', { class: className, viewBox: '0 0 48 48' }); const path = Dom.svg('path', { d, class: 'sqd-icon-path' }); icon.appendChild(path); return icon; } } Icons.folderIn = 'M42.05 42.25H11.996v-7.12h17.388L6 11.746 11.546 6.2 34.93 29.584V12.196h7.12V42.25z'; Icons.folderOut = 'M6 6.2h30.054v7.12H18.666L42.05 36.704l-5.546 5.546L13.12 18.866v17.388H6V6.2z'; Icons.center = 'M9 42q-1.2 0-2.1-.9Q6 40.2 6 39v-8.6h3V39h8.6v3Zm21.4 0v-3H39v-8.6h3V39q0 1.2-.9 2.1-.9.9-2.1.9ZM24 31.15q-3.15 0-5.15-2-2-2-2-5.15 0-3.15 2-5.15 2-2 5.15-2 3.15 0 5.15 2 2 2 2 5.15 0 3.15-2 5.15-2 2-5.15 2ZM6 17.6V9q0-1.2.9-2.1Q7.8 6 9 6h8.6v3H9v8.6Zm33 0V9h-8.6V6H39q1.2 0 2.1.9.9.9.9 2.1v8.6Z'; Icons.zoomIn = 'M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875ZM17.3 24.3v-4.1h-4.1v-3h4.1v-4.05h3v4.05h4.05v3H20.3v4.1Z'; Icons.zoomOut = 'M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875Zm-5.1-8.35v-3H23.8v3Z'; Icons.undo = 'M14 38v-3h14.45q3.5 0 6.025-2.325Q37 30.35 37 26.9t-2.525-5.775Q31.95 18.8 28.45 18.8H13.7l5.7 5.7-2.1 2.1L8 17.3 17.3 8l2.1 2.1-5.7 5.7h14.7q4.75 0 8.175 3.2Q40 22.2 40 26.9t-3.425 7.9Q33.15 38 28.4 38Z'; Icons.redo = 'M19.6 38q-4.75 0-8.175-3.2Q8 31.6 8 26.9t3.425-7.9q3.425-3.2 8.175-3.2h14.7l-5.7-5.7L30.7 8l9.3 9.3-9.3 9.3-2.1-2.1 5.7-5.7H19.55q-3.5 0-6.025 2.325Q11 23.45 11 26.9t2.525 5.775Q16.05 35 19.55 35H34v3Z'; Icons.move = 'm24 44-8.15-8.15 2.2-2.2 4.45 4.45v-9.45h3v9.45l4.45-4.45 2.2 2.2ZM11.9 31.9 4 24l7.95-7.95 2.2 2.2L9.9 22.5h9.45v3H9.9l4.2 4.2Zm24.2 0-2.2-2.2 4.2-4.2h-9.4v-3h9.4l-4.2-4.2 2.2-2.2L44 24ZM22.5 19.3V9.9l-4.2 4.2-2.2-2.2L24 4l7.9 7.9-2.2 2.2-4.2-4.2v9.4Z'; Icons.delete = 'm16.5 33.6 7.5-7.5 7.5 7.5 2.1-2.1-7.5-7.5 7.5-7.5-2.1-2.1-7.5 7.5-7.5-7.5-2.1 2.1 7.5 7.5-7.5 7.5ZM24 44q-4.1 0-7.75-1.575-3.65-1.575-6.375-4.3-2.725-2.725-4.3-6.375Q4 28.1 4 24q0-4.15 1.575-7.8 1.575-3.65 4.3-6.35 2.725-2.7 6.375-4.275Q19.9 4 24 4q4.15 0 7.8 1.575 3.65 1.575 6.35 4.275 2.7 2.7 4.275 6.35Q44 19.85 44 24q0 4.1-1.575 7.75-1.575 3.65-4.275 6.375t-6.35 4.3Q28.15 44 24 44Z'; Icons.folderUp = 'M22.5 34h3V23.75l3.7 3.7 2.1-2.1-7.3-7.3-7.3 7.3 2.1 2.1 3.7-3.7ZM7.05 40q-1.2 0-2.1-.925-.9-.925-.9-2.075V11q0-1.15.9-2.075Q5.85 8 7.05 8h14l3 3h17q1.15 0 2.075.925.925.925.925 2.075v23q0 1.15-.925 2.075Q42.2 40 41.05 40Zm0-29v26h34V14H22.8l-3-3H7.05Zm0 0v26Z'; Icons.close = 'm12.45 37.65-2.1-2.1L21.9 24 10.35 12.45l2.1-2.1L24 21.9l11.55-11.55 2.1 2.1L26.1 24l11.55 11.55-2.1 2.1L24 26.1Z'; Icons.options = 'm19.4 44-1-6.3q-.95-.35-2-.95t-1.85-1.25l-5.9 2.7L4 30l5.4-3.95q-.1-.45-.125-1.025Q9.25 24.45 9.25 24q0-.45.025-1.025T9.4 21.95L4 18l4.65-8.2 5.9 2.7q.8-.65 1.85-1.25t2-.9l1-6.35h9.2l1 6.3q.95.35 2.025.925Q32.7 11.8 33.45 12.5l5.9-2.7L44 18l-5.4 3.85q.1.5.125 1.075.025.575.025 1.075t-.025 1.05q-.025.55-.125 1.05L44 30l-4.65 8.2-5.9-2.7q-.8.65-1.825 1.275-1.025.625-2.025.925l-1 6.3ZM24 30.5q2.7 0 4.6-1.9 1.9-1.9 1.9-4.6 0-2.7-1.9-4.6-1.9-1.9-4.6-1.9-2.7 0-4.6 1.9-1.9 1.9-1.9 4.6 0 2.7 1.9 4.6 1.9 1.9 4.6 1.9Z'; Icons.expand = 'm24 30.75-12-12 2.15-2.15L24 26.5l9.85-9.85L36 18.8Z'; Icons.alert = 'M24 42q-1.45 0-2.475-1.025Q20.5 39.95 20.5 38.5q0-1.45 1.025-2.475Q22.55 35 24 35q1.45 0 2.475 1.025Q27.5 37.05 27.5 38.5q0 1.45-1.025 2.475Q25.45 42 24 42Zm-3.5-12V6h7v24Z'; Icons.play = 'M14.75 40.15V7.55l25.6 16.3Z'; Icons.stop = 'M10.75 37.25V10.7H37.3v26.55Z'; Icons.folder = 'M7.05 40q-1.2 0-2.1-.925-.9-.925-.9-2.075V11q0-1.15.9-2.075Q5.85 8 7.05 8h14l3 3h17q1.15 0 2.075.925.925.925.925 2.075v23q0 1.15-.925 2.075Q42.2 40 41.05 40Z'; class ObjectCloner { static deepClone(instance) { if (typeof window.structuredClone !== 'undefined') { return window.structuredClone(instance); } return JSON.parse(JSON.stringify(instance)); } } class Vector { constructor(x, y) { this.x = x; this.y = y; } add(v) { return new Vector(this.x + v.x, this.y + v.y); } subtract(v) { return new Vector(this.x - v.x, this.y - v.y); } multiplyByScalar(s) { return new Vector(this.x * s, this.y * s); } divideByScalar(s) { return new Vector(this.x / s, this.y / s); } round() { return new Vector(Math.round(this.x), Math.round(this.y)); } distance() { return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); } } function getAbsolutePosition(element) { const rect = element.getBoundingClientRect(); return new Vector(rect.x + window.scrollX, rect.y + window.scrollY); } class Uid { static next() { const bytes = new Uint8Array(16); window.crypto.getRandomValues(bytes); return Array.from(bytes, v => v.toString(16).padStart(2, '0')).join(''); } } class SimpleEvent { constructor() { this.listeners = []; this.forward = (value) => { if (this.listeners.length > 0) { this.listeners.forEach(listener => listener(value)); } }; } subscribe(listener) { this.listeners.push(listener); } unsubscribe(listener) { const index = this.listeners.indexOf(listener); if (index >= 0) { this.listeners.splice(index, 1); } else { throw new Error('Unknown listener'); } } count() { return this.listeners.length; } first() { return new Promise(resolve => { const handler = (value) => { this.unsubscribe(handler); resolve(value); }; this.subscribe(handler); }); } } function race(timeout, a, b, c, d) { const value = [undefined, undefined, undefined, undefined]; const result = new SimpleEvent(); let scheduled = false; function forward() { if (scheduled) { return; } scheduled = true; setTimeout(() => { try { result.forward(value); } finally { scheduled = false; value.fill(undefined); } }, timeout); } function subscribe(event, index) { event.subscribe(v => { value[index] = v; forward(); }); } subscribe(a, 0); subscribe(b, 1); if (c) { subscribe(c, 2); } if (d) { subscribe(d, 3); } return result; } class ControlBarApi { static create(state, historyController, stateModifier) { const api = new ControlBarApi(state, historyController, stateModifier); race(0, state.onIsReadonlyChanged, state.onSelectedStepIdChanged, state.onIsDragDisabledChanged, api.isUndoRedoSupported() ? state.onDefinitionChanged : undefined).subscribe(api.onStateChanged.forward); return api; } constructor(state, historyController, stateModifier) { this.state = state; this.historyController = historyController; this.stateModifier = stateModifier; this.onStateChanged = new SimpleEvent(); } isDragDisabled() { return this.state.isDragDisabled; } setIsDragDisabled(isDragDisabled) { this.state.setIsDragDisabled(isDragDisabled); } toggleIsDragDisabled() { this.setIsDragDisabled(!this.isDragDisabled()); } isUndoRedoSupported() { return !!this.historyController; } tryUndo() { if (this.canUndo() && this.historyController) { this.historyController.undo(); return true; } return false; } canUndo() { return !!this.historyController && this.historyController.canUndo() && !this.state.isReadonly && !this.state.isDragging; } tryRedo() { if (this.canRedo() && this.historyController) { this.historyController.redo(); return true; } return false; } canRedo() { return !!this.historyController && this.historyController.canRedo() && !this.state.isReadonly && !this.state.isDragging; } tryDelete() { if (this.canDelete() && this.state.selectedStepId) { this.stateModifier.tryDelete(this.state.selectedStepId); return true; } return false; } canDelete() { return (!!this.state.selectedStepId && !this.state.isReadonly && !this.state.isDragging && this.stateModifier.isDeletable(this.state.selectedStepId)); } } exports.KeyboardAction = void 0; (function (KeyboardAction) { KeyboardAction["delete"] = "delete"; })(exports.KeyboardAction || (exports.KeyboardAction = {})); exports.DefinitionChangeType = void 0; (function (DefinitionChangeType) { DefinitionChangeType[DefinitionChangeType["stepNameChanged"] = 1] = "stepNameChanged"; DefinitionChangeType[DefinitionChangeType["stepPropertyChanged"] = 2] = "stepPropertyChanged"; DefinitionChangeType[DefinitionChangeType["stepChildrenChanged"] = 3] = "stepChildrenChanged"; DefinitionChangeType[DefinitionChangeType["stepDeleted"] = 4] = "stepDeleted"; DefinitionChangeType[DefinitionChangeType["stepMoved"] = 5] = "stepMoved"; DefinitionChangeType[DefinitionChangeType["stepInserted"] = 6] = "stepInserted"; DefinitionChangeType[DefinitionChangeType["rootPropertyChanged"] = 7] = "rootPropertyChanged"; DefinitionChangeType[DefinitionChangeType["rootReplaced"] = 8] = "rootReplaced"; })(exports.DefinitionChangeType || (exports.DefinitionChangeType = {})); class EditorRenderer { static create(state, selectedStepIdProvider, definitionWalker, handler) { const raceEvent = race(0, state.onDefinitionChanged, selectedStepIdProvider.onSelectedStepIdChanged, state.onIsReadonlyChanged); const listener = new EditorRenderer(state, selectedStepIdProvider, definitionWalker, handler, raceEvent); raceEvent.subscribe(listener.raceEventHandler); listener.renderIfStepChanged(selectedStepIdProvider.selectedStepId); return listener; } constructor(state, selectedStepIdProvider, definitionWalker, handler, raceEvent) { this.state = state; this.selectedStepIdProvider = selectedStepIdProvider; this.definitionWalker = definitionWalker; this.handler = handler; this.raceEvent = raceEvent; this.currentStepId = undefined; this.raceEventHandler = ([definitionChanged, selectedStepId, isReadonlyChanged]) => { if (isReadonlyChanged !== undefined) { this.render(this.selectedStepIdProvider.selectedStepId); } else if (definitionChanged) { if (definitionChanged.changeType === exports.DefinitionChangeType.rootReplaced) { this.render(this.selectedStepIdProvider.selectedStepId); } else { this.renderIfStepChanged(this.selectedStepIdProvider.selectedStepId); } } else if (selectedStepId !== undefined) { this.renderIfStepChanged(selectedStepId); } }; } destroy() { this.raceEvent.unsubscribe(this.raceEventHandler); } render(stepId) { const step = stepId ? this.definitionWalker.getById(this.state.definition, stepId) : null; this.currentStepId = stepId; this.handler(step); } renderIfStepChanged(stepId) { if (this.currentStepId !== stepId) { this.render(stepId); } } } class EditorApi { constructor(state, definitionWalker, stateModifier) { this.state = state; this.definitionWalker = definitionWalker; this.stateModifier = stateModifier; } isCollapsed() { return this.state.isEditorCollapsed; } isReadonly() { return this.state.isReadonly; } toggleIsCollapsed() { this.state.setIsEditorCollapsed(!this.state.isEditorCollapsed); } subscribeIsCollapsed(listener) { this.state.onIsEditorCollapsedChanged.subscribe(listener); } getDefinition() { return this.state.definition; } addDefinitionModifierDependency(dependency) { this.stateModifier.addDependency(dependency); } runRenderer(rendererHandler, customSelectedStepIdProvider) { const selectedStepIdProvider = customSelectedStepIdProvider || this.state; return EditorRenderer.create(this.state, selectedStepIdProvider, this.definitionWalker, rendererHandler); } createStepEditorContext(stepId) { if (!stepId) { throw new Error('Step id is empty'); } return { notifyPropertiesChanged: () => { this.state.notifyDefinitionChanged(exports.DefinitionChangeType.stepPropertyChanged, stepId); }, notifyNameChanged: () => { this.state.notifyDefinitionChanged(exports.DefinitionChangeType.stepNameChanged, stepId); }, notifyChildrenChanged: () => { this.state.notifyDefinitionChanged(exports.DefinitionChangeType.stepChildrenChanged, stepId); this.stateModifier.updateDependencies(); } }; } createRootEditorContext() { return { notifyPropertiesChanged: () => { this.state.notifyDefinitionChanged(exports.DefinitionChangeType.rootPropertyChanged, null); } }; } } class PathBarApi { constructor(state, definitionWalker) { this.state = state; this.definitionWalker = definitionWalker; this.onStateChanged = race(0, this.state.onFolderPathChanged, this.state.onDefinitionChanged); } setFolderPath(path) { this.state.setFolderPath(path); } getFolderPath() { return this.state.folderPath; } getFolderPathStepNames() { return this.state.folderPath.map(stepId => { return this.definitionWalker.getById(this.state.definition, stepId).name; }); } } class DragStepView { static create(step, theme, componentContext) { var _a; const body = (_a = componentContext.shadowRoot) !== null && _a !== void 0 ? _a : document.body; const layer = Dom.element('div', { class: `sqd-drag sqd-theme-${theme}` }); body.appendChild(layer); const component = componentContext.services.draggedComponent.create(layer, step, componentContext); return new DragStepView(component, layer, body); } constructor(component, layer, body) { this.component = component; this.layer = layer; this.body = body; } setPosition(position) { this.layer.style.top = position.y + 'px'; this.layer.style.left = position.x + 'px'; } remove() { this.component.destroy(); this.body.removeChild(this.layer); } } class PlaceholderFinder { static create(placeholders, state) { const checker = new PlaceholderFinder(placeholders, state); state.onViewportChanged.subscribe(checker.clearCacheHandler); window.addEventListener('scroll', checker.clearCacheHandler, false); return checker; } constructor(placeholders, state) { this.placeholders = placeholders; this.state = state; this.clearCacheHandler = () => this.clearCache(); } find(vLt, vWidth, vHeight) { var _a; if (!this.cache) { const scroll = new Vector(window.scrollX, window.scrollY); this.cache = this.placeholders.map(placeholder => { const rect = placeholder.getClientRect(); return { placeholder, lt: new Vector(rect.x, rect.y).add(scroll), br: new Vector(rect.x + rect.width, rect.y + rect.height).add(scroll) }; }); this.cache.sort((a, b) => a.lt.y - b.lt.y); } const vR = vLt.x + vWidth; const vB = vLt.y + vHeight; return (_a = this.cache.find(p => { return Math.max(vLt.x, p.lt.x) < Math.min(vR, p.br.x) && Math.max(vLt.y, p.lt.y) < Math.min(vB, p.br.y); })) === null || _a === void 0 ? void 0 : _a.placeholder; } destroy() { this.state.onViewportChanged.unsubscribe(this.clearCacheHandler); window.removeEventListener('scroll', this.clearCacheHandler, false); } clearCache() { this.cache = undefined; } } class DragStepBehavior { static create(designerContext, step, draggedStepComponent) { const view = DragStepView.create(step, designerContext.theme, designerContext.componentContext); return new DragStepBehavior(view, designerContext.workspaceController, designerContext.placeholderController, designerContext.state, step, designerContext.stateModifier, draggedStepComponent); } constructor(view, workspaceController, placeholderController, designerState, step, stateModifier, draggedStepComponent) { this.view = view; this.workspaceController = workspaceController; this.placeholderController = placeholderController; this.designerState = designerState; this.step = step; this.stateModifier = stateModifier; this.draggedStepComponent = draggedStepComponent; } onStart(position) { let offset = null; if (this.draggedStepComponent) { this.draggedStepComponent.setIsDisabled(true); this.draggedStepComponent.setIsDragging(true); const hasSameSize = this.draggedStepComponent.view.width === this.view.component.width && this.draggedStepComponent.view.height === this.view.component.height; if (hasSameSize) { // Mouse cursor will be positioned on the same place as the source component. const pagePosition = this.draggedStepComponent.view.getClientPosition(); offset = position.subtract(pagePosition); } } if (!offset) { // Mouse cursor will be positioned in the center of the component. offset = new Vector(this.view.component.width, this.view.component.height).divideByScalar(2); } this.view.setPosition(position.subtract(offset)); this.designerState.setIsDragging(true); const { placeholders, components } = this.resolvePlaceholders(this.draggedStepComponent); this.state = { placeholders, components, startPosition: position, finder: PlaceholderFinder.create(placeholders, this.designerState), offset }; placeholders.forEach(placeholder => placeholder.setIsVisible(true)); components.forEach(component => component.setIsDragging(true)); } onMove(delta) { if (this.state) { const newPosition = this.state.startPosition.subtract(delta).subtract(this.state.offset); this.view.setPosition(newPosition); const placeholder = this.state.finder.find(newPosition, this.view.component.width, this.view.component.height); if (this.currentPlaceholder !== placeholder) { if (this.currentPlaceholder) { this.currentPlaceholder.setIsHover(false); } if (placeholder) { placeholder.setIsHover(true); } this.currentPlaceholder = placeholder; } } } onEnd(interrupt) { if (!this.state) { throw new Error('Invalid state'); } this.state.placeholders.forEach(placeholder => placeholder.setIsVisible(false)); this.state.components.forEach(component => component.setIsDragging(false)); this.state.finder.destroy(); this.state = undefined; this.view.remove(); this.designerState.setIsDragging(false); let modified = false; if (!interrupt && this.currentPlaceholder) { if (this.draggedStepComponent) { modified = this.stateModifier.tryMove(this.draggedStepComponent.parentSequence, this.draggedStepComponent.step, this.currentPlaceholder.parentSequence, this.currentPlaceholder.index); } else { modified = this.stateModifier.tryInsert(this.step, this.currentPlaceholder.parentSequence, this.currentPlaceholder.index); } } if (!modified) { if (this.draggedStepComponent) { this.draggedStepComponent.setIsDisabled(false); this.draggedStepComponent.setIsDragging(false); } if (this.currentPlaceholder) { this.currentPlaceholder.setIsHover(false); } } this.currentPlaceholder = undefined; } resolvePlaceholders(skipComponent) { const result = this.workspaceController.resolvePlaceholders(skipComponent); if (this.placeholderController.canShow) { const canShow = this.placeholderController.canShow; result.placeholders = result.placeholders.filter(placeholder => canShow(placeholder.parentSequence, placeholder.index, this.step.componentType, this.step.type)); } return result; } } class ToolboxApi { constructor(state, designerContext, behaviorController, toolboxDataProvider, uidGenerator) { this.state = state; this.designerContext = designerContext; this.behaviorController = behaviorController; this.toolboxDataProvider = toolboxDataProvider; this.uidGenerator = uidGenerator; } isCollapsed() { return this.state.isToolboxCollapsed; } toggleIsCollapsed() { this.state.setIsToolboxCollapsed(!this.state.isToolboxCollapsed); } subscribeIsCollapsed(listener) { this.state.onIsToolboxCollapsedChanged.subscribe(listener); } getAllGroups() { return this.toolboxDataProvider.getAllGroups(); } applyFilter(allGroups, filter) { return this.toolboxDataProvider.applyFilter(allGroups, filter); } /** * @param position Mouse or touch position. * @param step Step definition. * @returns If started dragging returns true, otherwise returns false. */ tryDrag(position, step) { if (!this.state.isReadonly) { const newStep = this.activateStep(step); this.behaviorController.start(position, DragStepBehavior.create(this.designerContext, newStep)); return true; } return false; } activateStep(step) { const newStep = ObjectCloner.deepClone(step); newStep.id = this.uidGenerator ? this.uidGenerator() : Uid.next(); return newStep; } } const regexp = /^[a-zA-Z][a-zA-Z0-9_-]+$/; class StepTypeValidator { static validate(type) { if (!regexp.test(type)) { throw new Error(`Step type "${type}" contains not allowed characters`); } } } class ToolboxDataProvider { constructor(iconProvider, i18n, configuration) { this.iconProvider = iconProvider; this.i18n = i18n; this.configuration = configuration; this.createItemData = (step) => { StepTypeValidator.validate(step.type); const iconUrl = this.iconProvider.getIconUrl(step); const rawLabel = this.configuration && this.configuration.labelProvider ? this.configuration.labelProvider(step) : step.name; const label = this.i18n(`toolbox.item.${step.type}.label`, rawLabel); const description = this.configuration && this.configuration.descriptionProvider ? this.configuration.descriptionProvider(step) : label; const lowerCaseLabel = label.toLowerCase(); return { iconUrl, label, description, lowerCaseLabel, step }; }; } getAllGroups() { if (!this.configuration) { return []; } return this.configuration.groups.map(group => { return { name: group.name, items: group.steps.map(this.createItemData) }; }); } applyFilter(allGroups, filter) { if (!filter) { return allGroups; } const lowerCaseFilter = filter.toLowerCase(); return allGroups .map(group => { return { name: group.name, items: group.items.filter(s => { return s.lowerCaseLabel.includes(lowerCaseFilter); }) }; }) .filter(group => group.items.length > 0); } } function animate(interval, handler) { const iv = setInterval(tick, 15); const startTime = Date.now(); const anim = { isAlive: true, stop: () => { anim.isAlive = false; clearInterval(iv); } }; function tick() { const progress = Math.min((Date.now() - startTime) / interval, 1); handler(progress); if (progress === 1) { anim.stop(); } } return anim; } class ViewportAnimator { constructor(state) { this.state = state; } execute(target) { if (this.animation && this.animation.isAlive) { this.animation.stop(); } const startPosition = this.state.viewport.position; const startScale = this.state.viewport.scale; const deltaPosition = startPosition.subtract(target.position); const deltaScale = startScale - target.scale; this.animation = animate(150, progress => { const newScale = startScale - deltaScale * progress; this.state.setViewport({ position: startPosition.subtract(deltaPosition.multiplyByScalar(progress)), scale: newScale }); }); } } class ZoomByWheelCalculator { static calculate(controller, current, canvasPosition, e) { if (e.deltaY === 0) { return null; } const nextScale = controller.getNextScale(current.scale, e.deltaY < 0); let scale; const absDeltaY = Math.abs(e.deltaY); if (absDeltaY < controller.smoothDeltaYLimit) { const fraction = absDeltaY / controller.smoothDeltaYLimit; const step = nextScale.next - nextScale.current; scale = current.scale + step * fraction; } else { scale = nextScale.next; } const mousePoint = new Vector(e.pageX, e.pageY).subtract(canvasPosition); // The real point is point on canvas with no scale. const mouseRealPoint = mousePoint.divideByScalar(current.scale).subtract(current.position.divideByScalar(current.scale)); const position = mouseRealPoint.multiplyByScalar(-scale).add(mousePoint); return { position, scale }; } } class ViewportApi { constructor(state, workspaceController, viewportController) { this.state = state; this.workspaceController = workspaceController; this.viewportController = viewportController; this.animator = new ViewportAnimator(this.state); } limitScale(scale) { return this.viewportController.limitScale(scale); } resetViewport() { const defaultViewport = this.viewportController.getDefault(); this.state.setViewport(defaultViewport); } zoom(direction) { const viewport = this.viewportController.getZoomed(direction); if (viewport) { this.state.setViewport(viewport); } } moveViewportToStep(stepId) { const component = this.workspaceController.getComponentByStepId(stepId); const canvasPosition = this.workspaceController.getCanvasPosition(); const clientPosition = component.view.getClientPosition(); const componentPosition = clientPosition.subtract(canvasPosition); const componentSize = new Vector(component.view.width, component.view.height); const viewport = this.viewportController.getFocusedOnComponent(componentPosition, componentSize); this.animator.execute(viewport); } handleWheelEvent(e) { const canvasPosition = this.workspaceController.getCanvasPosition(); const newViewport = ZoomByWheelCalculator.calculate(this.viewportController, this.state.viewport, canvasPosition, e); if (newViewport) { this.state.setViewport(newViewport); } } } const defaultResolvers = [sequentialResolver, branchedResolver]; function branchedResolver(step) { const branches = step.branches; if (branches) { return { type: exports.StepChildrenType.branches, items: branches }; } return null; } function sequentialResolver(step) { const sequence = step.sequence; if (sequence) { return { type: exports.StepChildrenType.sequence, items: sequence }; } return null; } exports.StepChildrenType = void 0; (function (StepChildrenType) { StepChildrenType[StepChildrenType["sequence"] = 1] = "sequence"; StepChildrenType[StepChildrenType["branches"] = 2] = "branches"; })(exports.StepChildrenType || (exports.StepChildrenType = {})); class DefinitionWalker { constructor(resolvers) { this.resolvers = resolvers ? resolvers.concat(defaultResolvers) : defaultResolvers; } /** * Returns children of the step. If the step doesn't have children, returns null. * @param step The step. */ getChildren(step) { const count = this.resolvers.length; for (let i = 0; i < count; i++) { const result = this.resolvers[i](step); if (result) { return result; } } return null; } /** * Returns the parents of the step or the sequence. * @param definition The definition. * @param needle The step, stepId or sequence to find. * @returns The parents of the step or the sequence. */ getParents(definition, needle) { const result = []; let searchSequence = null; let searchStepId = null; if (Array.isArray(needle)) { searchSequence = needle; } else if (typeof needle === 'string') { searchStepId = needle; } else { searchStepId = needle.id; } if (this.find(definition.sequence, searchSequence, searchStepId, result)) { result.reverse(); return result.map(item => { return typeof item === 'string' ? item : item.step; }); } throw new Error(searchStepId ? `Cannot get parents of step: ${searchStepId}` : 'Cannot get parents of sequence'); } findParentSequence(definition, stepId) { const result = []; if (this.find(definition.sequence, null, stepId, result)) { return result[0]; } return null; } getParentSequence(definition, stepId) { const result = this.findParentSequence(definition, stepId); if (!result) { throw new Error(`Cannot find step by id: ${stepId}`); } return result; } findById(definition, stepId) { const result = this.findParentSequence(definition, stepId); return result ? result.step : null; } getById(definition, stepId) { return this.getParentSequence(definition, stepId).step; } forEach(definition, callback) { this.iterateSequence(definition.sequence, callback); } forEachSequence(sequence, callback) { this.iterateSequence(sequence, callback); } forEachChildren(step, callback) { this.iterateStep(step, callback); } find(sequence, needSequence, needStepId, result) { if (needSequence && sequence === needSequence) { return true; } const count = sequence.length; for (let index = 0; index < count; index++) { const step = sequence[index]; if (needStepId && step.id === needStepId) { result.push({ step, index, parentSequence: sequence }); return true; } const children = this.getChildren(step); if (children) { switch (children.type) { case exports.StepChildrenType.sequence: { const parentSequence = children.items; if (this.find(parentSequence, needSequence, needStepId, result)) { result.push({ step, index, parentSequence }); return true; } } break; case exports.StepChildrenType.branches: { const branches = children.items; const branchNames = Object.keys(branches); for (const branchName of branchNames) { const parentSequence = branches[branchName]; if (this.find(parentSequence, needSequence, needStepId, result)) { result.push(branchName); result.push({ step, index, parentSequence }); return true; } } } break; default: throw new Error(`Not supported step children type: ${children.type}`); } } } return false; } iterateSequence(sequence, callback) { const count = sequence.length; for (let index = 0; index < count; index++) { const step = sequence[index]; if (callback(step, index, sequence) === false) { return false; } if (!this.iterateStep(step, callback)) { return false; } } return true; } iterateStep(step, callback) { const children = this.getChildren(step); if (children) { switch (children.type) { case exports.StepChildrenType.sequence: { const sequence = children.items; if (!this.iterateSequence(sequence, callback)) { return false; } } break; case exports.StepChildrenType.branches: { const sequences = Object.values(children.items); for (const sequence of sequences) { if (!this.iterateSequence(sequence, callback)) { return false; } } } break; default: throw new Error(`Not supported step children type: ${children.type}`); } } return true; } } class WorkspaceApi { constructor(state, definitionWalker, workspaceController) { this.state = state; this.definitionWalker = definitionWalker; this.workspaceController = workspaceController; } getViewport() { return this.state.viewport; } setViewport(viewport) { this.state.setViewport(viewport); } getCanvasPosition() { return this.workspaceController.getCanvasPosition(); } getCanvasSize() { return this.workspaceController.getCanvasSize(); } getRootComponentSize() { return this.workspaceController.getRootComponentSize(); } updateRootComponent() { this.workspaceController.updateRootComponent(); } updateBadges() { this.workspaceController.updateBadges(); } updateCanvasSize() { this.workspaceController.updateCanvasSize(); } getRootSequence() { const stepId = this.state.tryGetLastStepIdFromFolderPath(); if (stepId) { const parentStep = this.definitionWalker.getParentSequence(this.state.definition, stepId); const children = this.definitionWalker.getChildren(parentStep.step); if (!children || children.type !== exports.StepChildrenType.sequence) { throw new Error('Cannot find single sequence in folder step'); } return { sequence: children.items, parentStep }; } return { sequence: this.state.definition.sequence, parentStep: null }; } } class DesignerApi { static create(context) { const workspace = new WorkspaceApi(context.state, context.definitionWalker, context.workspaceController); const viewportController = context.services.viewportController.create(workspace); const toolboxDataProvider = new ToolboxDataProvider(context.componentContext.iconProvider, context.i18n, context.configuration.toolbox); return new DesignerApi(context.configuration.shadowRoot, ControlBarApi.create(context.state, context.historyController, context.stateModifier), new ToolboxApi(context.state, context, context.behaviorController, toolboxDataProvider, context.configuration.uidGenerator), new EditorApi(context.state, context.definitionWalker, context.stateModifier), workspace, new ViewportApi(context.state, context.workspaceController, viewportController), new PathBarApi(context.state, context.definitionWalker), context.definitionWalker, context.i18n); } constructor(shadowRoot, controlBar, toolbox, editor, workspace, viewport, pathBar, definitionWalker, i18n) { this.shadowRoot = shadowRoot; this.controlBar = controlBar; this.toolbox = toolbox; this.editor = editor; this.workspace = workspace; this.viewport = viewport; this.pathBar = pathBar; this.definitionWalker = definitionWalker; this.i18n = i18n; } } const TYPE = 'selectStep'; class SelectStepBehaviorEndToken { static is(token) { return Boolean(token) && token.type === TYPE; } constructor(stepId, time) { this.stepId = stepId; this.time = time; this.type = TYPE; } } const BADGE_GAP = 4; class DefaultBadgesDecorator { constructor(position, badges, g) { this.position = position; this.badges = badges; this.g = g; } update() { let offsetX = 0; let maxHeight = 0; let j = 0; for (let i = 0; i < this.badges.length; i++) { const badge = this.badges[i]; if (badge && badge.view) { offsetX += j === 0 ? badge.view.width / 2 : badge.view.width; maxHeight = Math.max(maxHeight, badge.view.height); Dom.translate(badge.view.g, -offsetX, 0); offsetX += BADGE_GAP; j++; } } Dom.translate(this.g, this.position.x, this.position.y + -maxHeight / 2); } } class Badges { static createForStep(stepContext, view, componentContext) { const g = createG(view.g); const badges = componentContext.services.badges.map(ext => ext.createForStep(g, view, stepContext, componentContext)); const decorator = componentContext.services.stepBadgesDecorator.create(g, view, badges); return new Badges(badges, decorator); } static createForRoot(parentElement, position, componentContext) { const g = createG(parentElement); const badges = componentContext.services.badges.map(ext => { return ext.createForRoot ? ext.createForRoot(g, componentContext) : null; }); const decorator = new DefaultBadgesDecorator(position, badges, g); return new Badges(badges, decorator); } constructor(badges, decorator) { this.badges = badges; this.decorator = decorator; } update(result) { const count = this.badges.length; for (let i = 0; i < count; i++) { const badge = this.badges[i]; if (badge) { result[i] = badge.update(result[i]); } } this.decorator.update(); } resolveClick(click) { for (const badge of this.badges) { const command = badge && badge.resolveClick(click); if (command) { return command; } } return null; } } function createG(parentElement) { const g = Dom.svg('g', { class: 'sqd-badges' }); parentElement.appendChild(g); return g; } class ValidationErrorBadgeView { static create(parent, cfg) { const g = Dom.svg('g'); const halfOfSize = cfg.size / 2; const circle = Dom.svg('path', { class: 'sqd-validation-error', d: `M 0 ${-halfOfSize} l ${halfOfSize} ${cfg.size} l ${-cfg.size} 0 Z` }); Dom.translate(circle, halfOfSize, halfOfSize); g.appendChild(circle); const icon = Icons.appendPath(g, 'sqd-validation-error-icon-path', Icons.alert, cfg.iconSize); const offsetX = (cfg.size - cfg.iconSize) / 2; const offsetY = offsetX * 1.5; Dom.translate(icon, offsetX, offsetY); parent.appendChild(g); return new ValidationErrorBadgeView(parent, g, cfg.size, cfg.size); } constructor(parent, g, width, height) { this.parent = parent; this.g = g; this.width = width; this.height = height; } destroy() { this.parent.removeChild(this.g); } } class ValidatorFactory { static createForStep(stepContext, view, componentContext) { return () => { if (!componentContext.validator.validateStep(stepContext.step, stepContext.parentSequence)) { return false; } if (view.haveCollapsedChildren) { let allChildrenValid = true; componentContext.definitionWalker.forEachChildren(stepContext.step, (step, _, parentSequence) => { if (!componentContext.validator.validateStep(step, parentSequence)) { allChildrenValid = false; return false; } }); if (!allChildrenValid) { return false; } } return true; }; } static createForRoot(componentContext) { return () => { return componentContext.validator.validateRoot(); }; } } class ValidationErrorBadge { static createForStep(parentElement, view, stepContext, componentContext, configuration) { const validator = ValidatorFactory.createForStep(stepContext, view, componentContext); return new ValidationErrorBadge(parentElement, validator, configuration); } static createForRoot(parentElement, componentContext, configuration) { const validator = ValidatorFactory.createForRoot(componentContext); return new ValidationErrorBadge(parentElement, validator, configuration); } constructor(parentElement, validator, configuration) { this.parentElement = parentElement; this.validator = validator; this.configuration = configuration; this.view = null; } update(result) { const isValid = this.validator(); if (isValid) { if (this.view) { this.view.destroy(); this.view = null; } } else if (!this.view) { this.view = ValidationErrorBadgeView.create(this.parentElement, this.configuration); } return isValid && result; } resolveClick() { return null; } } const defaultConfiguration$7 = { view: { size: 22, iconSize: 12 } }; class ValidationErrorBadgeExtension { static create(configuration) { return new ValidationErrorBadgeExtension(configuration !== null && configuration !== void 0 ? configuration : defaultConfiguration$7); } constructor(configuration) { this.configuration = configuration; this.id = 'validationError'; this.createStartValue = () => true; } createForStep(parentElement, view, stepContext, componentContext) { return ValidationErrorBadge.createForStep(parentElement, view, stepContext, componentContext, this.configuration.view); } createForRoot(parentElement, componentContext) { return ValidationErrorBadge.createForRoot(parentElement, componentContext, this.configuration.view); } } class ComponentDom { static stepG(componentClassName, type, id) { return Dom.svg('g', { class: `sqd