UNPKG

sequential-workflow-editor

Version:

![Sequential Workflow Editor](.github/cover.png)

1,366 lines (1,325 loc) 62 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('sequential-workflow-editor-model')) : typeof define === 'function' && define.amd ? define(['exports', 'sequential-workflow-editor-model'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.sequentialWorkflowEditor = {}, global.sequentialWorkflowEditorModel)); })(this, (function (exports, sequentialWorkflowEditorModel) { 'use strict'; class Html { 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) { Html.attrs(element, attributes); } return element; } static toggleClass(element, isEnabled, className) { if (isEnabled) { element.classList.add(className); } else { element.classList.remove(className); } } } const ns = 'http://www.w3.org/2000/svg'; class Icons { static createSvg(icon, cls) { const svg = document.createElementNS(ns, 'svg'); svg.setAttribute('viewBox', '0 -960 960 960'); svg.classList.add(cls); const path = document.createElementNS(ns, 'path'); path.setAttribute('d', icon); svg.appendChild(path); return svg; } } Icons.help = 'M419-334q1-87 20.5-129t65.5-76q39-31 57.5-61.109T581-666q0-39-25.5-64.5T486-756q-46 0-75 26t-43 67l-120-52q27-74 87-120.5T485.756-882q109.228 0 168.236 62.148Q713-757.703 713-669q0 60-21 105.5T625-478q-46 40-57 65.5T557-334H419Zm66.788 282Q447-52 420-79t-27-65.496q0-38.495 26.92-65.5Q446.841-237 485.92-237 525-237 552-209.996q27 27.005 27 65.5Q579-106 551.788-79q-27.213 27-66 27Z'; Icons.close = 'm249-183-66-66 231-231-231-231 66-66 231 231 231-231 66 66-231 231 231 231-66 66-231-231-231 231Z'; Icons.add = 'M433-183v-250H183v-94h250v-250h94v250h250v94H527v250h-94Z'; function buttonComponent(label, configuration) { function onClicked(e) { e.preventDefault(); onClick.forward(); } function setIcon(d) { if (icon) { icon.getElementsByTagName('path')[0].setAttribute('d', d); } else { throw new Error('This button does not have icon'); } } function setLabel(label) { if (configuration === null || configuration === void 0 ? void 0 : configuration.icon) { throw new Error('Cannot change label on button with icon'); } else { view.innerText = label; } } const onClick = new sequentialWorkflowEditorModel.SimpleEvent(); let className = 'swe-button'; if (configuration === null || configuration === void 0 ? void 0 : configuration.size) { className += ` swe-button-${configuration.size}`; } if (configuration === null || configuration === void 0 ? void 0 : configuration.theme) { className += ` swe-button-${configuration.theme}`; } const view = Html.element('button', { class: className, title: label, 'aria-label': label }); let icon; if (configuration === null || configuration === void 0 ? void 0 : configuration.icon) { icon = Icons.createSvg(configuration.icon, 'swe-button-icon'); view.appendChild(icon); } else { view.innerText = label; } view.addEventListener('click', onClicked, false); return { view, onClick, setIcon, setLabel }; } function validationErrorComponent() { const view = Html.element('div', { class: 'swe-validation-error' }); const onIsHiddenChanged = new sequentialWorkflowEditorModel.SimpleEvent(); let child = null; function isHidden() { return child === null; } function setError(error) { const oldState = isHidden(); if (child) { view.removeChild(child); child = null; } if (error) { child = Html.element('div', { class: 'swe-validation-error-text' }); child.textContent = error; view.appendChild(child); } const newState = isHidden(); if (oldState !== newState) { onIsHiddenChanged.forward(newState); } } function setDefaultError(result) { setError(result && result['$']); } return { onIsHiddenChanged, view, isHidden, setError, setDefaultError }; } function dynamicListComponent(initialItems, itemComponentFactory, context, configuration) { const onChanged = new sequentialWorkflowEditorModel.SimpleEvent(); const items = [...initialItems]; function forward() { onChanged.forward([...items]); } function onItemChanged(newItem, index) { items[index] = newItem; forward(); validateList(); } function onItemDeleted(index) { if (configuration && configuration.canDelete) { const error = configuration.canDelete(items[index]); if (error) { window.alert(error); return; } } items.splice(index, 1); forward(); reloadList(); } function add(item) { items.push(item); forward(); reloadList(); } function forEach(callback) { components.forEach(callback); } function reloadList() { if (emptyRow) { view.removeChild(emptyRow); emptyRow = null; } components.forEach(component => view.removeChild(component.view)); components.length = 0; if (items.length > 0) { items.forEach((item, index) => { const component = itemComponentFactory(item, context.i18n, index); component.onItemChanged.subscribe(item => onItemChanged(item, index)); component.onDeleteClicked.subscribe(() => onItemDeleted(index)); view.insertBefore(component.view, validation.view); components.push(component); }); } else if (configuration === null || configuration === void 0 ? void 0 : configuration.emptyMessage) { emptyRow = Html.element('div', { class: 'swe-dynamic-list-empty-row' }); emptyRow.innerText = configuration.emptyMessage; view.insertBefore(emptyRow, validation.view); } validateList(); } function validateList() { const result = context.validate(); for (let i = 0; i < components.length; i++) { components[i].validate(result ? result[i] : null); } validation.setError(result && result.$ ? result.$ : null); } let emptyRow = null; const view = Html.element('div', { class: 'swe-dynamic-list' }); const validation = validationErrorComponent(); view.appendChild(validation.view); const components = []; reloadList(); return { onChanged, view, add, forEach }; } function appendMultilineText(target, text) { const lines = text.split(/\r?\n/g); for (let i = 0; i < lines.length; i++) { if (i > 0) { target.appendChild(document.createElement('br')); } const line = document.createTextNode(lines[i]); target.appendChild(line); } } function filterValueTypes(types, allowedTypes) { if (!allowedTypes) { return types; } const result = []; for (const type of types) { if (allowedTypes.includes(type)) { result.push(type); } } return result; } class StackedSimpleEvent { constructor() { this.event = new sequentialWorkflowEditorModel.SimpleEvent(); this.stack = []; this.to = null; } push(value) { this.stack.push(value); if (this.to) { return; } this.to = setTimeout(() => { this.to = null; this.event.forward(this.stack); this.stack.length = 0; }); } subscribe(listener) { this.event.subscribe(listener); } } function formatVariableName(name) { return `$${name}`; } function formatVariableNameWithType(name, type) { return `${formatVariableName(name)} (${type})`; } function inputComponent(startValue, configuration) { var _a; const onChanged = new sequentialWorkflowEditorModel.SimpleEvent(); function setValue(value) { view.value = value; } function getValue() { return view.value; } function setReadonly(readonly) { if (readonly) { view.setAttribute('readonly', 'readonly'); } else { view.removeAttribute('readonly'); } } const view = Html.element('input', { class: 'swe-input swe-stretched', type: (_a = configuration === null || configuration === void 0 ? void 0 : configuration.type) !== null && _a !== void 0 ? _a : 'text' }); if (configuration === null || configuration === void 0 ? void 0 : configuration.placeholder) { view.setAttribute('placeholder', configuration.placeholder); } if (configuration === null || configuration === void 0 ? void 0 : configuration.isReadonly) { setReadonly(true); } view.value = startValue; view.addEventListener('input', () => { onChanged.forward(view.value); }); return { view, onChanged, setValue, getValue, setReadonly }; } function prependedInputComponent(prefix, component) { const view = Html.element('div', { class: 'swe-prepended-input' }); const pref = Html.element('span', { class: 'swe-prepended-input-prefix' }); pref.innerText = prefix; view.appendChild(pref); view.appendChild(component.view); return Object.assign(Object.assign({}, component), { view }); } function rowComponent(elements, configuration) { let viewClass = 'swe-row'; if (configuration && configuration.class) { viewClass += ' ' + configuration.class; } const view = Html.element('div', { class: viewClass }); elements.forEach((element, index) => { const grow = configuration && configuration.cols ? configuration.cols[index] : 1; let className = 'swe-col'; if (grow) { className += ` swe-col-${grow}`; } const col = Html.element('div', { class: className }); col.appendChild(element); view.appendChild(col); }); return { view }; } function selectComponent(configuration) { function setValues(values) { options.forEach(option => view.removeChild(option)); options.length = 0; for (let i = 0; i < values.length; i++) { const option = document.createElement('option'); option.value = values[i]; option.innerText = values[i]; view.appendChild(option); options.push(option); } } function getSelectedIndex() { return view.selectedIndex; } function selectIndex(index) { view.selectedIndex = index; } function onSelectChanged() { onSelected.forward(getSelectedIndex()); } const onSelected = new sequentialWorkflowEditorModel.SimpleEvent(); let className = 'swe-select'; if (configuration === null || configuration === void 0 ? void 0 : configuration.size) { className += ` swe-select-${configuration.size}`; } if (configuration === null || configuration === void 0 ? void 0 : configuration.stretched) { className += ' swe-stretched'; } const view = Html.element('select', { class: className }); const options = []; view.addEventListener('change', onSelectChanged, false); return { view, setValues, getSelectedIndex, selectIndex, onSelected }; } function textareaComponent(startValue, configuration) { var _a, _b; const onChanged = new sequentialWorkflowEditorModel.SimpleEvent(); function setValue(value) { view.value = value; } function getValue() { return view.value; } const view = Html.element('textarea', { class: 'swe-textarea swe-stretched', rows: (_b = (_a = configuration === null || configuration === void 0 ? void 0 : configuration.rows) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '4' }); if (configuration === null || configuration === void 0 ? void 0 : configuration.placeholder) { view.setAttribute('placeholder', configuration.placeholder); } view.value = startValue; view.addEventListener('input', () => { onChanged.forward(view.value); }); return { view, onChanged, setValue, getValue }; } function valueEditorContainerComponent(elements) { const view = document.createElement('div'); view.className = 'swe-value-editor-container'; elements.forEach(element => view.appendChild(element)); return { view }; } const stringValueEditorId = 'string'; const defaultMultiline = 4; function createStringValueEditor(configuration) { return (context) => { function validate() { validation.setDefaultError(context.validate()); } const startValue = context.getValue(); const multiline = context.model.configuration.multiline; const input = multiline ? textareaComponent(startValue, { rows: multiline === true ? defaultMultiline : multiline }) : inputComponent(startValue); input.onChanged.subscribe(value => { context.setValue(value); validate(); }); const row = rowComponent([input.view], { class: configuration === null || configuration === void 0 ? void 0 : configuration.class }); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view }; }; } class StringValueEditorEditorExtension { static create(configuration) { return new StringValueEditorEditorExtension(configuration); } constructor(configuration) { this.configuration = configuration; this.valueEditors = [ { editorId: this.configuration.editorId, factory: createStringValueEditor(this.configuration) } ]; } } const numberValueEditorId = 'number'; function numberValueEditor(context) { function validate() { validation.setDefaultError(context.validate()); } const startValue = String(context.getValue()); const input = inputComponent(startValue, { type: 'number' }); input.onChanged.subscribe(value => { const num = value.length > 0 ? Number(value) : NaN; context.setValue(num); validate(); }); const row = rowComponent([input.view]); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view }; } function variableDefinitionItemComponent(variable, context) { function validate(error) { validation.setError(error); } function onTypeChanged(index) { const type = valueTypes[index]; variable.type = type; onItemChanged.forward(variable); } function onNameChanged(value) { variable.name = value; onItemChanged.forward(variable); } const onItemChanged = new sequentialWorkflowEditorModel.SimpleEvent(); const onDeleteClicked = new sequentialWorkflowEditorModel.SimpleEvent(); const view = Html.element('div', { class: 'swe-variable-definition-item' }); const input = prependedInputComponent('$', inputComponent(variable.name, { placeholder: context.i18n('variableDefinitions.namePlaceholder', 'Variable name') })); input.onChanged.subscribe(onNameChanged); const valueTypes = filterValueTypes(context.getValueTypes(), context.model.configuration.valueTypes); const typeSelect = selectComponent({ stretched: true }); typeSelect.setValues(valueTypes); typeSelect.selectIndex(valueTypes.findIndex(type => type === variable.type)); typeSelect.onSelected.subscribe(onTypeChanged); const deleteButton = buttonComponent(context.i18n('variableDefinitions.delete', 'Delete'), { size: 'small', theme: 'secondary', icon: Icons.close }); deleteButton.onClick.subscribe(() => onDeleteClicked.forward()); const validation = validationErrorComponent(); const row = rowComponent([input.view, typeSelect.view, deleteButton.view], { cols: [2, 1, null] }); view.appendChild(row.view); view.appendChild(validation.view); return { view, onItemChanged, onDeleteClicked, validate }; } const variableDefinitionsValueEditorId = 'variableDefinitions'; function variableDefinitionsValueEditor(context) { function onChanged(variables) { context.setValue({ variables }); } function onAddClicked() { list.add({ name: '', type: context.getValueTypes()[0] }); } const list = dynamicListComponent(context.getValue().variables, item => variableDefinitionItemComponent(item, context), context, { emptyMessage: context.i18n('variableDefinitions.noVariablesDefined', 'No variables defined') }); list.onChanged.subscribe(onChanged); const addButton = buttonComponent(context.i18n('variableDefinitions.newVariable', 'New variable'), { size: 'small', icon: Icons.add }); addButton.onClick.subscribe(onAddClicked); const container = valueEditorContainerComponent([list.view]); return { view: container.view, controlView: addButton.view }; } function filterVariablesByType(variables, valueType) { if (!valueType) { return variables; } const filter = Array.isArray(valueType) ? (variable) => valueType.includes(variable.type) : (variable) => variable.type === valueType; return variables.filter(filter); } const nullableVariableValueEditorId = 'nullableVariable'; function nullableVariableValueEditor(context) { function validate() { validation.setDefaultError(context.validate()); } function onChanged(selectedIndex) { if (selectedIndex === 0) { context.setValue(null); } else { context.setValue({ name: variables[selectedIndex - 1].name }); } validate(); } const startValue = context.getValue(); const variables = filterVariablesByType(context.getVariables(), context.model.configuration.valueType); const select = selectComponent({ stretched: true }); select.setValues([ context.i18n('nullableVariable.selectType', '- Select: :type -', { type: context.model.configuration.valueType }), ...variables.map(variable => formatVariableNameWithType(variable.name, variable.type)) ]); if (startValue) { select.selectIndex(variables.findIndex(variable => variable.name === startValue.name) + 1); } else { select.selectIndex(0); } select.onSelected.subscribe(index => onChanged(index)); const row = rowComponent([select.view]); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view }; } const nullableVariableDefinitionValueEditorId = 'nullableVariableDefinition'; function nullableVariableDefinitionValueEditor(context) { var _a; function validate() { validation.setDefaultError(context.validate()); } const startValue = ((_a = context.getValue()) === null || _a === void 0 ? void 0 : _a.name) || ''; const input = prependedInputComponent('$', inputComponent(startValue)); input.onChanged.subscribe(value => { context.setValue(value ? { name: value, type: context.model.configuration.valueType } : null); validate(); }); const row = rowComponent([input.view]); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view }; } function anyVariableItemComponent(variable, i18n) { function validate(error) { validation.setError(error); } const onDeleteClicked = new sequentialWorkflowEditorModel.SimpleEvent(); const view = Html.element('div'); const name = Html.element('span'); name.innerText = formatVariableNameWithType(variable.name, variable.type); const deleteButton = buttonComponent(i18n('anyVariable.delete', 'Delete'), { size: 'small', theme: 'secondary', icon: Icons.close }); deleteButton.onClick.subscribe(() => onDeleteClicked.forward()); const validation = validationErrorComponent(); const row = rowComponent([name, deleteButton.view], { cols: [1, null] }); view.appendChild(row.view); view.appendChild(validation.view); return { view, onDeleteClicked, onItemChanged: new sequentialWorkflowEditorModel.SimpleEvent(), validate }; } function anyVariableSelectorComponent(context) { var _a; function getSelectedValueType() { return valueTypes[typeSelect.getSelectedIndex()]; } function getSelectedVariableName() { const index = variableSelect.getSelectedIndex(); return variables && index > 0 ? variables[index - 1].name : null; } function reloadVariableSelector() { variables = filterVariablesByType(context.getVariables(), getSelectedValueType()); const variableNames = variables.map(variable => formatVariableName(variable.name)); variableSelect.setValues([context.i18n('anyVariable.select', '- Select -'), ...variableNames]); } function onAddClicked() { const type = getSelectedValueType(); const name = getSelectedVariableName(); if (name) { onAdded.forward({ type, name }); variableSelect.selectIndex(0); } } const onAdded = new sequentialWorkflowEditorModel.SimpleEvent(); const valueTypes = (_a = context.model.configuration.valueTypes) !== null && _a !== void 0 ? _a : context.getValueTypes(); const typeSelect = selectComponent({ stretched: true }); typeSelect.setValues(valueTypes); typeSelect.onSelected.subscribe(reloadVariableSelector); const variableSelect = selectComponent({ stretched: true }); let variables = null; const addButton = buttonComponent(context.i18n('anyVariable.addVariable', 'Add variable'), { icon: Icons.add }); addButton.onClick.subscribe(onAddClicked); const row = rowComponent([typeSelect.view, variableSelect.view, addButton.view], { cols: [1, 1, null] }); reloadVariableSelector(); return { view: row.view, onAdded }; } const anyVariablesValueEditorId = 'anyVariables'; function anyVariablesValueEditor(context) { function onChanged(variables) { context.setValue({ variables }); } function onNewAdded(newVariable) { if (context.getValue().variables.some(v => v.name === newVariable.name)) { // TODO: variable is already added, some message? return; } list.add(newVariable); } const selector = anyVariableSelectorComponent(context); selector.onAdded.subscribe(onNewAdded); const list = dynamicListComponent(context.getValue().variables, anyVariableItemComponent, context, { emptyMessage: context.i18n('anyVariables.noVariablesSelected', 'No variables selected') }); list.onChanged.subscribe(onChanged); const container = valueEditorContainerComponent([selector.view, list.view]); return { view: container.view }; } const dynamicValueEditorId = 'dynamic'; function dynamicValueEditor(context, services) { if (!context.model.subModels) { throw new Error('subModels is required'); } const subModels = context.model.subModels; function reloadDependencies() { if (editor && editor.reloadDependencies) { editor.reloadDependencies(); } } function reloadEditor() { if (editor) { placeholder.removeChild(editor.view); if (subControl) { control.removeChild(subControl); subControl = null; } } const value = context.getValue(); const model = subModels.find(model => model.id === value.modelId); if (!model || !model.id) { throw new Error(`Model not found: ${value.modelId}`); } const childContext = context.createChildContext(model); editor = services.valueEditorFactoryResolver.resolve(model.id, model.editorId)(childContext, services); placeholder.appendChild(editor.view); if (editor.controlView) { subControl = Html.element('span', { class: 'swe-dynamic-sub-control' }); subControl.appendChild(editor.controlView); control.appendChild(subControl); } } function onTypeChanged() { const newModel = subModels[subModelSelect.getSelectedIndex()]; const defaultValueContext = sequentialWorkflowEditorModel.DefaultValueContext.create(services.activator, context.scopedPropertyContext.propertyContext); const defaultValue = { modelId: newModel.id, value: newModel.getDefaultValue(defaultValueContext) }; context.setValue(defaultValue); reloadEditor(); } const startValue = context.getValue(); const control = Html.element('div', { class: 'swe-dynamic-control' }); const subModelSelect = selectComponent({ size: 'small' }); subModelSelect.setValues(context.model.subModels.map(model => { return context.i18n(`dynamic.${model.id}.label`, model.label); })); subModelSelect.selectIndex(context.model.subModels.findIndex(model => model.id === startValue.modelId)); subModelSelect.onSelected.subscribe(onTypeChanged); control.appendChild(subModelSelect.view); const placeholder = Html.element('div', { class: 'swe-dynamic-placeholder' }); const container = valueEditorContainerComponent([placeholder]); let editor = null; let subControl = null; reloadEditor(); return { view: container.view, controlView: control, reloadDependencies }; } function createStepI18nPrefix(stepType) { return stepType ? `step.${stepType}.property:` : 'root.property:'; } const choiceValueEditorId = 'choice'; function choiceValueEditor(context) { function validate() { validation.setDefaultError(context.validate()); } function onSelected(index) { const value = choices[index]; context.setValue(value); validate(); } const select = selectComponent({ stretched: true }); const stepType = context.tryGetStepType(); const i18nPrefix = createStepI18nPrefix(stepType); const choices = context.model.configuration.choices; const translatedChoices = choices.map(choice => { const pathStr = context.model.path.toString(); const key = `${i18nPrefix}${pathStr}:choice:${choice}`; return context.i18n(key, choice); }); select.setValues(translatedChoices); const startIndex = choices.indexOf(context.getValue()); select.selectIndex(startIndex); select.onSelected.subscribe(onSelected); const row = rowComponent([select.view]); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view }; } const nullableAnyVariableValueEditorId = 'nullableAnyVariable'; function nullableAnyVariableValueEditor(context) { function validate() { validation.setDefaultError(context.validate()); } function onChanged(selectedIndex) { if (selectedIndex === 0) { context.setValue(null); } else { const variable = variables[selectedIndex - 1]; context.setValue({ name: variable.name, type: variable.type }); } validate(); } const startValue = context.getValue(); const variables = filterVariablesByType(context.getVariables(), context.model.configuration.valueTypes); const select = selectComponent({ stretched: true }); const expectedTypes = context.model.configuration.valueTypes ? context.model.configuration.valueTypes.join(', ') : null; const actionText = expectedTypes ? context.i18n('nullableAnyVariable.selectTypes', '- Select: :types -', { types: expectedTypes }) : context.i18n('nullableAnyVariable.select', '- Select -'); select.setValues([actionText, ...variables.map(variable => formatVariableNameWithType(variable.name, variable.type))]); if (startValue) { select.selectIndex(variables.findIndex(variable => variable.name === startValue.name) + 1); } else { select.selectIndex(0); } select.onSelected.subscribe(index => onChanged(index)); const row = rowComponent([select.view]); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view }; } const booleanValueEditorId = 'boolean'; function booleanValueEditor(context) { function validate() { validation.setDefaultError(context.validate()); } function onSelected(index) { context.setValue(index === 1); validate(); } const select = selectComponent({ stretched: true }); select.setValues([context.i18n('boolean.false', 'False'), context.i18n('boolean.true', 'True')]); select.selectIndex(context.getValue() ? 1 : 0); select.onSelected.subscribe(onSelected); const row = rowComponent([select.view]); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view }; } const generatedStringValueEditorId = 'generatedString'; function generatedStringValueEditor(context) { const generatedContext = sequentialWorkflowEditorModel.GeneratedStringContext.create(context); function validate() { validation.setDefaultError(context.validate()); } function reloadDependencies() { generate(); validate(); } function generate() { const generated = context.model.configuration.generator(generatedContext); if (input.getValue() !== generated) { input.setValue(generated); context.setValue(generated); } } const startValue = context.getValue(); const input = inputComponent(startValue, { isReadonly: true }); const row = rowComponent([input.view]); const validation = validationErrorComponent(); const container = valueEditorContainerComponent([row.view, validation.view]); validate(); return { view: container.view, reloadDependencies }; } function stringDictionaryItemComponent(item, i18n) { function validate(error) { validation.setError(error); } function onChanged() { onItemChanged.forward({ key: keyInput.getValue(), value: valueInput.getValue() }); } const onItemChanged = new sequentialWorkflowEditorModel.SimpleEvent(); const onDeleteClicked = new sequentialWorkflowEditorModel.SimpleEvent(); const keyInput = inputComponent(item.key, { placeholder: i18n('stringDictionary.key', 'Key') }); keyInput.onChanged.subscribe(onChanged); const valueInput = inputComponent(item.value, { placeholder: i18n('stringDictionary.value', 'Value') }); valueInput.onChanged.subscribe(onChanged); const deleteButton = buttonComponent(i18n('stringDictionary.delete', 'Delete'), { size: 'small', theme: 'secondary', icon: Icons.close }); deleteButton.onClick.subscribe(onDeleteClicked.forward); const row = rowComponent([keyInput.view, valueInput.view, deleteButton.view], { cols: [2, 3, null] }); const validation = validationErrorComponent(); const view = Html.element('div', { class: 'swe-dictionary-item' }); view.appendChild(row.view); view.appendChild(validation.view); return { view, onItemChanged, onDeleteClicked, validate }; } function stringDictionaryValueEditor(context) { function onChanged(items) { context.setValue({ items }); } function onAddClicked() { list.add({ key: '', value: '' }); } const list = dynamicListComponent(context.getValue().items, stringDictionaryItemComponent, context, { emptyMessage: context.i18n('stringDictionary.noItems', 'No items') }); list.onChanged.subscribe(onChanged); const container = valueEditorContainerComponent([list.view]); const addButton = buttonComponent(context.i18n('stringDictionary.addItem', 'Add item'), { size: 'small', icon: Icons.add }); addButton.onClick.subscribe(onAddClicked); return { view: container.view, controlView: addButton.view }; } function hiddenValueEditor() { const container = valueEditorContainerComponent([]); return { view: container.view, isHidden: () => true }; } const defaultMap = { [anyVariablesValueEditorId]: anyVariablesValueEditor, [booleanValueEditorId]: booleanValueEditor, [choiceValueEditorId]: choiceValueEditor, [nullableAnyVariableValueEditorId]: nullableAnyVariableValueEditor, [dynamicValueEditorId]: dynamicValueEditor, [generatedStringValueEditorId]: generatedStringValueEditor, [nullableVariableValueEditorId]: nullableVariableValueEditor, [nullableVariableDefinitionValueEditorId]: nullableVariableDefinitionValueEditor, [stringValueEditorId]: createStringValueEditor(), [sequentialWorkflowEditorModel.stringDictionaryValueModelId]: stringDictionaryValueEditor, [numberValueEditorId]: numberValueEditor, [variableDefinitionsValueEditorId]: variableDefinitionsValueEditor, [sequentialWorkflowEditorModel.sequenceValueModelId]: hiddenValueEditor, [sequentialWorkflowEditorModel.branchesValueModelId]: hiddenValueEditor }; class ValueEditorFactoryResolver { static create(extensions) { let map; if (extensions) { map = Object.assign({}, defaultMap); extensions.forEach(extension => { if (extension.valueEditors) { extension.valueEditors.forEach(e => (map[e.editorId] = e.factory)); } }); } else { map = defaultMap; } return new ValueEditorFactoryResolver(map); } constructor(map) { this.map = map; } resolve(valueModelId, editorId) { const id = editorId !== null && editorId !== void 0 ? editorId : valueModelId; const editor = this.map[id]; if (!editor) { throw new Error(`Editor id ${id} is not supported`); } return editor; } } const defaultResolvers = [sequentialResolver, branchedResolver]; function branchedResolver(step) { const branches = step.branches; if (branches) { return { type: StepChildrenType.branches, items: branches }; } return null; } function sequentialResolver(step) { const sequence = step.sequence; if (sequence) { return { type: StepChildrenType.sequence, items: sequence }; } return null; } var StepChildrenType; (function (StepChildrenType) { StepChildrenType[StepChildrenType["sequence"] = 1] = "sequence"; StepChildrenType[StepChildrenType["branches"] = 2] = "branches"; })(StepChildrenType || (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 StepChildrenType.sequence: { const parentSequence = children.items; if (this.find(parentSequence, needSequence, needStepId, result)) { result.push({ step, index, parentSequence }); return true; } } break; case 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 StepChildrenType.sequence: { const sequence = children.items; if (!this.iterateSequence(sequence, callback)) { return false; } } break; case 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; } } function propertyValidationErrorComponent(validator, context) { const validation = validationErrorComponent(); function validate() { const error = validator.validate(context); validation.setError(error); } validate(); return { view: validation.view, validate, isHidden: validation.isHidden }; } function propertyHint(text) { let content = null; const view = Html.element('div', { class: 'swe-property-hint' }); function toggle() { if (content) { view.removeChild(content); content = null; } else { content = Html.element('div', { class: 'swe-property-hint-text' }); appendMultilineText(content, text); view.appendChild(content); } } return { view, toggle }; } class PropertyEditor { static create(propertyModel, stepType, definitionContext, editorServices) { const valueContext = sequentialWorkflowEditorModel.ValueContext.createFromDefinitionContext(propertyModel.value, propertyModel, definitionContext, editorServices.i18n); const valueEditorFactory = editorServices.valueEditorFactoryResolver.resolve(propertyModel.value.id, propertyModel.value.editorId); const valueEditor = valueEditorFactory(valueContext, editorServices); let hint = null; const nameClassName = propertyModel.path.last(); const pathStr = propertyModel.path.toString(); const view = Html.element('div', { class: `swe-property swe-name-${nameClassName}` }); view.setAttribute('data-path', pathStr); const header = Html.element('div', { class: 'swe-property-header' }); const label = Html.element('h4', { class: 'swe-property-header-label' }); const i18nPrefix = createStepI18nPrefix(stepType); label.innerText = editorServices.i18n(i18nPrefix + pathStr, propertyModel.label); header.appendChild(label); view.appendChild(header); if (propertyModel.hint) { const toggle = Html.element('span', { class: 'swe-property-header-hint-toggle' }); const toggleIcon = Icons.createSvg(Icons.help, 'swe-property-header-hint-toggle-icon'); toggle.appendChild(toggleIcon); toggle.addEventListener('click', () => hint === null || hint === void 0 ? void 0 : hint.toggle(), false); header.appendChild(toggle); hint = propertyHint(propertyModel.hint); view.appendChild(hint.view); } view.appendChild(valueEditor.view); let control = null; if (valueEditor.controlView) { control = Html.element('div', { class: 'swe-property-header-control' }); control.appendChild(valueEditor.controlView); header.appendChild(control); } let propertyValidationError = null; if (propertyModel.validator) { const valueContext = sequentialWorkflowEditorModel.ValueContext.createFromDefinitionContext(propertyModel.value, propertyModel, definitionContext, editorServices.i18n); const validatorContext = sequentialWorkflowEditorModel.PropertyValidatorContext.create(valueContext); propertyValidationError = propertyValidationErrorComponent(propertyModel.validator, validatorContext); view.appendChild(propertyValidationError.view); } const editor = new PropertyEditor(view, valueContext.onValueChanged, valueEditor, control, propertyValidationError); if (propertyValidationError) { valueContext.onValueChanged.subscribe(editor.onValueChangedHandler); } if (valueEditor.onIsHiddenChanged) { valueEditor.onIsHiddenChanged.subscribe(editor.onEditorIsHiddenChanged); } editor.reloadVisibility(); return editor; } constructor(view, onValueChanged, valueEditor, control, propertyValidationError) { this.view = view; this.onValueChanged = onValueChanged; this.valueEditor