sequential-workflow-designer
Version:
Customizable no-code component for building flow-based programming applications.
1,224 lines (1,189 loc) • 204 kB
JavaScript
(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