sequential-workflow-editor-model
Version:
This package contains the model for [Sequential Workflow Editor](https://github.com/nocode-js/sequential-workflow-editor).
1,267 lines (1,226 loc) • 59.5 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.sequentialWorkflowEditorModel = {}));
})(this, (function (exports) { 'use strict';
class DefaultValueContext {
static create(activator, propertyContext) {
return new DefaultValueContext(activator, propertyContext);
}
constructor(activator, propertyContext) {
this.activator = activator;
this.propertyContext = propertyContext;
this.getPropertyValue = this.propertyContext.getPropertyValue;
this.formatPropertyValue = this.propertyContext.formatPropertyValue;
this.activateStep = this.activator.activateStep;
}
}
const SEPARATOR = '/';
class Path {
static create(path) {
if (typeof path === 'string') {
path = path.split(SEPARATOR);
}
return new Path(path);
}
static root() {
return new Path([]);
}
constructor(parts) {
this.parts = parts;
}
read(object) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let result = object;
for (const part of this.parts) {
result = result[part];
if (result === undefined) {
throw new Error(`Cannot read path: ${this.parts.join(SEPARATOR)}`);
}
}
return result;
}
write(object, value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let result = object;
for (let i = 0; i < this.parts.length - 1; i++) {
result = result[this.parts[i]];
if (result === undefined) {
throw new Error(`Cannot write path: ${this.toString()}`);
}
}
result[this.last()] = value;
}
equals(other) {
if (typeof other === 'string') {
other = Path.create(other);
}
return this.parts.length === other.parts.length && this.startsWith(other);
}
add(part) {
return new Path([...this.parts, part]);
}
last() {
if (this.parts.length === 0) {
throw new Error('Root path has no last part');
}
return this.parts[this.parts.length - 1];
}
startsWith(other) {
if (typeof other === 'string') {
other = Path.create(other);
}
if (this.parts.length < other.parts.length) {
return false;
}
for (let i = 0; i < other.parts.length; i++) {
if (this.parts[i] !== other.parts[i]) {
return false;
}
}
return true;
}
toString() {
return this.parts.join(SEPARATOR);
}
}
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;
}
}
function readPropertyValue(name, model, object) {
const nameStr = String(name);
const path = Path.create(['properties', nameStr]);
if (!model.dependencies.some(dep => dep.equals(path))) {
throw new Error(`Property ${nameStr} is not registered as dependency`);
}
return path.read(object);
}
class PropertyContext {
static create(object, propertyModel, definitionModel) {
return new PropertyContext(object, propertyModel, definitionModel);
}
constructor(object, propertyModel, definitionModel) {
this.object = object;
this.propertyModel = propertyModel;
this.definitionModel = definitionModel;
/**
* @returns the type of the step, or `null` if the object is root.
*/
this.tryGetStepType = () => {
const type = this.object.type;
return type ? type : null;
};
/**
* Get the value of a property by name.
* @param name The name of the property.
* @returns The value of the property.
*/
this.getPropertyValue = (name) => {
return readPropertyValue(name, this.propertyModel, this.object);
};
/**
* @returns The supported value types for variables.
*/
this.getValueTypes = () => {
return this.definitionModel.valueTypes;
};
/**
* Format a property value using a formatter function.
* @param name The name of the property.
* @param formatter The formatter function.
* @param undefinedValue The value to return if the property value is `null` or `undefined`.
*/
this.formatPropertyValue = (name, formatter, undefinedValue) => {
const value = this.getPropertyValue(name);
if (value === undefined || value === null) {
return undefinedValue || '?';
}
return formatter(value);
};
}
}
class ModelActivator {
static create(definitionModel, uidGenerator) {
return new ModelActivator(definitionModel, uidGenerator);
}
constructor(definitionModel, uidGenerator) {
this.definitionModel = definitionModel;
this.uidGenerator = uidGenerator;
this.activateDefinition = () => {
const definition = {
properties: {}
};
this.activatePropertiesInOrder(definition, this.definitionModel.root.properties);
const propertyContext = PropertyContext.create(definition, this.definitionModel.root.sequence, this.definitionModel);
const defaultValueContext = DefaultValueContext.create(this, propertyContext);
const sequence = this.definitionModel.root.sequence.value.getDefaultValue(defaultValueContext);
return Object.assign(Object.assign({}, definition), { sequence });
};
this.activateStep = (stepType) => {
const model = this.definitionModel.steps[stepType];
if (!model) {
throw new Error(`Unknown step type: ${stepType}`);
}
const step = {
id: this.uidGenerator(),
type: stepType,
componentType: model.componentType,
properties: {}
};
this.activatePropertiesInOrder(step, model.properties);
const propertyContext = PropertyContext.create(step, model.name, this.definitionModel);
const defaultValueContext = DefaultValueContext.create(this, propertyContext);
const name = model.name.value.getDefaultValue(defaultValueContext);
return Object.assign(Object.assign({}, step), { name });
};
}
activatePropertiesInOrder(object, models) {
let i;
for (i = 0; i < models.length; i++) {
const model = models[i];
if (model.dependencies.length === 0) {
this.activateProperty(object, model);
}
}
for (i = 0; i < models.length; i++) {
const model = models[i];
if (model.dependencies.length > 0) {
this.activateProperty(object, model);
}
}
}
activateProperty(object, model) {
const propertyContext = PropertyContext.create(object, model, this.definitionModel);
const defaultValueContext = DefaultValueContext.create(this, propertyContext);
const defaultValue = model.value.getDefaultValue(defaultValueContext);
model.value.path.write(object, defaultValue);
}
}
const anyVariablesValueModelId = 'anyVariables';
const createAnyVariablesValueModel = (configuration) => ({
create: (path) => {
var _a;
return ({
id: anyVariablesValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Variables',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue() {
return {
variables: []
};
},
getVariableDefinitions: () => null,
validate(context) {
const errors = {};
const value = context.getValue();
value.variables.forEach((variable, index) => {
if (!context.hasVariable(variable.name, variable.type)) {
errors[index] = context.i18n('anyVariables.variableIsLost', 'Variable :name is lost', {
name: variable.name
});
return;
}
if (configuration.valueTypes && !configuration.valueTypes.includes(variable.type)) {
errors[index] = context.i18n('anyVariables.invalidVariableType', 'Variable :name has invalid type', {
name: variable.name
});
return;
}
});
return Object.keys(errors).length > 0 ? errors : null;
}
});
}
});
function createValidationSingleError(error) {
return {
$: error
};
}
function booleanValueModelValidator(context) {
const value = context.getValue();
if (typeof value !== 'boolean') {
return createValidationSingleError(context.i18n('boolean.invalidType', 'The value must be a boolean'));
}
return null;
}
const booleanValueModelId = 'boolean';
const createBooleanValueModel = (configuration) => ({
create: (path) => {
var _a;
return ({
id: booleanValueModelId,
editorId: configuration.editorId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Boolean',
path,
configuration,
getDefaultValue() {
if (configuration.defaultValue !== undefined) {
return configuration.defaultValue;
}
return false;
},
getVariableDefinitions: () => null,
validate: booleanValueModelValidator
});
}
});
function branchesValueModelValidator(context) {
const configuration = context.model.configuration;
const branches = context.getValue();
if (typeof branches !== 'object') {
return createValidationSingleError(context.i18n('branches.mustBeObject', 'The value must be object'));
}
const branchNames = Object.keys(branches);
if (branchNames.length === 0) {
return createValidationSingleError(context.i18n('branches.empty', 'No branches defined'));
}
if (!configuration.dynamic) {
const configurationBranchNames = Object.keys(configuration.branches);
if (branchNames.length !== configurationBranchNames.length) {
return createValidationSingleError(context.i18n('branches.invalidLength', 'Invalid number of branches'));
}
const missingBranchName = configurationBranchNames.find(branchName => !branchNames.includes(branchName));
if (missingBranchName) {
return createValidationSingleError(context.i18n('branches.missingBranch', 'Missing branch: :name', { name: missingBranchName }));
}
}
return null;
}
const branchesValueModelId = 'branches';
const createBranchesValueModel = (configuration) => ({
create: (path) => ({
id: branchesValueModelId,
editorId: configuration.editorId,
label: 'Branches',
path,
configuration,
getDefaultValue(context) {
const branches = Object.keys(configuration.branches).reduce((result, branchName) => {
result[branchName] = configuration.branches[branchName].map(type => context.activateStep(type));
return result;
}, {});
return branches;
},
getVariableDefinitions: () => null,
validate: branchesValueModelValidator
})
});
function choiceValueModelValidator(context) {
const value = context.getValue();
const configuration = context.model.configuration;
if (!configuration.choices.includes(value)) {
return createValidationSingleError(context.i18n('choice.notSupportedValue', 'Value is not supported'));
}
return null;
}
const choiceValueModelId = 'choice';
function createChoiceValueModel(configuration) {
if (configuration.choices.length < 1) {
throw new Error('At least one choice must be provided.');
}
return {
create: (path) => {
var _a;
return ({
id: choiceValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Choice',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue() {
if (configuration.defaultValue) {
if (!configuration.choices.includes(configuration.defaultValue)) {
throw new Error(`Default value "${configuration.defaultValue}" does not match any of the choices`);
}
return configuration.defaultValue;
}
return configuration.choices[0];
},
getVariableDefinitions: () => null,
validate: choiceValueModelValidator
});
}
};
}
const dynamicValueModelId = 'dynamic';
function createDynamicValueModel(configuration) {
if (configuration.models.length < 1) {
throw new Error('Dynamic value model must have at least one choice');
}
return {
create(path) {
const valuePath = path.add('value');
const subModels = configuration.models.map(modelFactory => modelFactory.create(valuePath));
return {
id: dynamicValueModelId,
label: 'Dynamic value',
path,
configuration,
subModels,
getDefaultValue(context) {
const model = subModels[0];
return {
modelId: model.id,
value: model.getDefaultValue(context)
};
},
getVariableDefinitions: () => null,
validate(context) {
const value = context.getValue();
const model = subModels.find(m => m.id === value.modelId);
if (!model) {
const availableModels = subModels.map(m => m.id).join(', ');
throw new Error(`Cannot find sub model id: ${value.modelId} (available: ${availableModels})`);
}
const childContext = context.createChildContext(model);
return model.validate(childContext);
}
};
}
};
}
class GeneratedStringContext {
static create(context) {
return new GeneratedStringContext(context);
}
constructor(context) {
this.context = context;
this.getPropertyValue = this.context.getPropertyValue;
this.formatPropertyValue = this.context.formatPropertyValue;
}
}
const generatedStringValueModelId = 'generatedString';
function createGeneratedStringValueModel(configuration) {
return {
create: (path) => {
var _a;
return ({
id: generatedStringValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Generated string',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue(context) {
const subContext = GeneratedStringContext.create(context);
return configuration.generator(subContext);
},
getVariableDefinitions: () => null,
validate(context) {
const subContext = GeneratedStringContext.create(context);
const value = configuration.generator(subContext);
if (context.getValue() !== value) {
return createValidationSingleError(context.i18n('generatedString.differentValue', 'Generator returns different value than the current value'));
}
return null;
}
});
}
};
}
const nullableAnyVariableValueModelId = 'nullableAnyVariable';
const createNullableAnyVariableValueModel = (configuration) => ({
create: (path) => {
var _a;
return ({
id: nullableAnyVariableValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Variable',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue() {
return null;
},
getVariableDefinitions: () => null,
validate(context) {
const value = context.getValue();
if (configuration.isRequired && !value) {
return createValidationSingleError(context.i18n('nullableAnyVariable.variableIsRequired', 'The variable is required'));
}
if (value) {
if (!context.hasVariable(value.name, value.type)) {
return createValidationSingleError(context.i18n('nullableAnyVariable.variableIsLost', 'The variable :name is lost', { name: value.name }));
}
if (configuration.valueTypes && !configuration.valueTypes.includes(value.type)) {
return createValidationSingleError(context.i18n('nullableAnyVariable.invalidVariableType', 'The variable :name has invalid type', { name: value.name }));
}
}
return null;
}
});
}
});
const nullableVariableValueModelId = 'nullableVariable';
const createNullableVariableValueModel = (configuration) => ({
create: (path) => {
var _a;
return ({
id: nullableVariableValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Variable',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue() {
return null;
},
getVariableDefinitions: () => null,
validate(context) {
const value = context.getValue();
if (configuration.isRequired && !value) {
return createValidationSingleError(context.i18n('nullableVariable.variableIsRequired', 'The variable is required'));
}
if (value && value.name) {
if (!context.hasVariable(value.name, configuration.valueType)) {
return createValidationSingleError(context.i18n('nullableVariable.variableIsLost', 'The variable :name is not found', {
name: value.name
}));
}
}
return null;
}
});
}
});
const MAX_LENGTH = 32;
function variableNameValidator(i18n, name) {
if (!name) {
return i18n('variableName.required', 'Variable name is required');
}
if (name.length > MAX_LENGTH) {
return i18n('variableName.maxLength', 'Variable name must be :n characters or less', {
n: String(MAX_LENGTH)
});
}
if (!/^[A-Za-z][a-zA-Z_0-9-]*$/.test(name)) {
return i18n('variableName.invalidCharacters', 'Variable name contains invalid characters');
}
return null;
}
const nullableVariableDefinitionValueModelId = 'nullableVariableDefinition';
const createNullableVariableDefinitionValueModel = (configuration) => ({
create: (path) => ({
id: nullableVariableDefinitionValueModelId,
label: 'Variable definition',
path,
configuration,
getDefaultValue() {
return configuration.defaultValue || null;
},
getVariableDefinitions(context) {
const value = context.getValue();
if (value) {
return [value];
}
return null;
},
validate(context) {
const value = context.getValue();
if (configuration.isRequired && !value) {
return createValidationSingleError(context.i18n('nullableVariableDefinition.variableIsRequired', 'The variable is required'));
}
if (value) {
const nameError = variableNameValidator(context.i18n, value.name);
if (nameError) {
return createValidationSingleError(nameError);
}
if (value.type !== configuration.valueType) {
return createValidationSingleError(context.i18n('nullableVariableDefinition.expectedType', 'Variable type must be :type', {
type: configuration.valueType
}));
}
if (context.isVariableDuplicated(value.name)) {
return createValidationSingleError(context.i18n('nullableVariableDefinition.variableIsDuplicated', 'Variable name is already used'));
}
}
return null;
}
})
});
function numberValueModelValidator(context) {
const value = context.getValue();
const configuration = context.model.configuration;
if (isNaN(value) || typeof value !== 'number') {
return createValidationSingleError(context.i18n('number.valueMustBeNumber', 'The value must be a number'));
}
if (configuration.min !== undefined && value < configuration.min) {
return createValidationSingleError(context.i18n('number.valueTooLow', 'The value must be at least :min', {
min: String(configuration.min)
}));
}
if (configuration.max !== undefined && value > configuration.max) {
return createValidationSingleError(context.i18n('number.valueTooHigh', 'The value must be at most :max', {
max: String(configuration.max)
}));
}
return null;
}
const numberValueModelId = 'number';
const createNumberValueModel = (configuration) => ({
create: (path) => {
var _a;
return ({
id: numberValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Number',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue() {
if (configuration.defaultValue !== undefined) {
return configuration.defaultValue;
}
if (configuration.min !== undefined) {
return configuration.min;
}
return 0;
},
getVariableDefinitions: () => null,
validate: numberValueModelValidator
});
}
});
const sequenceValueModelId = 'sequence';
const createSequenceValueModel = (configuration) => ({
create: (path) => ({
id: sequenceValueModelId,
label: 'Sequence',
path,
configuration,
getDefaultValue(context) {
return configuration.sequence.map(type => context.activateStep(type));
},
getVariableDefinitions: () => null,
validate: () => null
})
});
function stringValueModelValidator(context) {
const value = context.getValue();
const configuration = context.model.configuration;
if (typeof value !== 'string') {
return createValidationSingleError(context.i18n('string.valueMustBeString', 'The value must be a string'));
}
if (configuration.minLength !== undefined && value.length < configuration.minLength) {
return createValidationSingleError(context.i18n('string.valueTooShort', 'The value must be at least :min characters long', {
min: String(configuration.minLength)
}));
}
if (configuration.pattern && !configuration.pattern.test(value)) {
return createValidationSingleError(context.i18n('string.valueDoesNotMatchPattern', 'The value does not match the required pattern'));
}
return null;
}
const stringValueModelId = 'string';
const createStringValueModel = (configuration) => ({
create: (path) => {
var _a;
return ({
id: stringValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'String',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue() {
return configuration.defaultValue || '';
},
getVariableDefinitions: () => null,
validate: stringValueModelValidator
});
}
});
function stringDictionaryValueModelValidator(context) {
const errors = {};
const value = context.getValue();
const configuration = context.model.configuration;
const count = value.items.length;
if (configuration.uniqueKeys) {
for (let index = 0; index < count; index++) {
const key = value.items[index].key;
const duplicate = value.items.findIndex((item, i) => i !== index && item.key === key);
if (duplicate >= 0) {
errors[index] = context.i18n('stringDictionary.duplicatedKey', 'Key name is duplicated');
}
}
}
for (let index = 0; index < count; index++) {
const item = value.items[index];
if (!item.key) {
errors[index] = context.i18n('stringDictionary.keyIsRequired', 'Key is required');
}
if (configuration.valueMinLength !== undefined && item.value.length < configuration.valueMinLength) {
errors[index] = context.i18n('stringDictionary.valueTooShort', 'Value must be at least :min characters long', {
min: String(configuration.valueMinLength)
});
}
}
return Object.keys(errors).length > 0 ? errors : null;
}
const stringDictionaryValueModelId = 'stringDictionary';
const createStringDictionaryValueModel = (configuration) => ({
create: (path) => {
var _a;
return ({
id: stringDictionaryValueModelId,
label: (_a = configuration.label) !== null && _a !== void 0 ? _a : 'Dictionary',
editorId: configuration.editorId,
path,
configuration,
getDefaultValue() {
return {
items: []
};
},
getVariableDefinitions: () => null,
validate: stringDictionaryValueModelValidator
});
}
});
const variableDefinitionsValueModelId = 'variableDefinitions';
const createVariableDefinitionsValueModel = (configuration) => ({
create: (path) => ({
id: variableDefinitionsValueModelId,
label: 'Variable definitions',
path,
configuration,
getDefaultValue() {
var _a;
return ((_a = configuration.defaultValue) !== null && _a !== void 0 ? _a : {
variables: []
});
},
getVariableDefinitions(context) {
return context.getValue().variables.filter(variable => !!variable.name);
},
validate: (context) => {
const errors = {};
const value = context.getValue();
value.variables.forEach((variable, index) => {
const nameError = variableNameValidator(context.i18n, variable.name);
if (nameError) {
errors[index] = nameError;
return;
}
const isDuplicated = value.variables.some((v, i) => i !== index && v.name === variable.name);
if (isDuplicated) {
errors[index] = context.i18n('variableDefinitions.variableNameIsDuplicated', 'Variable name is duplicated');
return;
}
if (context.isVariableDuplicated(variable.name)) {
errors[index] = context.i18n('variableDefinitions.variableNameIsDuplicated', 'Variable name is already used');
return;
}
if (configuration.valueTypes && !configuration.valueTypes.includes(variable.type)) {
errors[index] = context.i18n('variableDefinitions.valueTypeIsNotAllowed', 'Value type is not allowed');
}
});
return Object.keys(errors).length > 0 ? errors : null;
}
})
});
function buildLabel(value) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
class PropertyModelBuilder {
constructor(path, circularDependencyDetector) {
this.path = path;
this.circularDependencyDetector = circularDependencyDetector;
this._dependencies = [];
}
/**
* @returns `true` if the model of the value is set, otherwise `false`.
*/
hasValue() {
return !!this._value;
}
/**
* Sets the model of the value.
* @param valueModelFactory The factory function that creates the model of the value.
* @example `builder.value(stringValueModel({ defaultValue: 'Some value' }));`
*/
value(valueModelFactory) {
if (this._value) {
throw new Error(`Model is already set for ${this.path.toString()}`);
}
this._value = valueModelFactory;
return this;
}
/**
* Sets the label of the property.
* @param label The label of the property.
* @example `builder.label('Foo');`
*/
label(label) {
this._label = label;
return this;
}
/**
* Sets the hint of the property. The hint is displayed in the property editor.
* @param hint The hint of the property.
* @example `builder.hint('This is a hint');`
*/
hint(hint) {
this._hint = hint;
return this;
}
/**
* Sets which properties the property depends on. Values of dependent properties are available in the custom validator.
* @param propertyName Name of the property in the step.
*/
dependentProperty(propertyName) {
const propName = String(propertyName);
const path = Path.create(['properties', propName]);
if (this._dependencies.some(dep => dep.equals(path))) {
throw new Error(`Dependency ${propName} is already set`);
}
this._dependencies.push(path);
this.circularDependencyDetector.check(this.path, path);
return this;
}
/**
* Sets the custom validator of the property.
* @param validator The custom validator of the property.
* @example `builder.customValidator({ validate(context) { return 'error'; } });`
*/
validator(validator) {
if (this._validator) {
throw new Error('Custom validator is already set');
}
this._validator = validator;
return this;
}
build() {
if (!this._value) {
throw new Error(`Model is not set for ${this.path.toString()}`);
}
return {
path: this.path,
label: this._label || this.getDefaultLabel(),
hint: this._hint,
dependencies: this._dependencies,
validator: this._validator,
value: this._value.create(this.path)
};
}
getDefaultLabel() {
return buildLabel(this.path.last());
}
}
class CircularDependencyDetector {
constructor() {
this.dependencies = [];
}
check(source, target) {
if (this.dependencies.some(dep => dep.source.equals(target))) {
throw new Error(`It is not allowed to depend on dependency with dependency: ${source.toString()} <-> ${target.toString()}`);
}
this.dependencies.push({ source, target });
}
}
const namePath = Path.create(['name']);
class StepModelBuilder {
constructor(type, componentType) {
this.type = type;
this.componentType = componentType;
this.circularDependencyDetector = new CircularDependencyDetector();
this._toolbox = true;
this.nameBuilder = new PropertyModelBuilder(namePath, this.circularDependencyDetector);
this.propertyBuilder = [];
if (!type) {
throw new Error('Step type is empty');
}
if (!componentType) {
throw new Error('Component type is empty');
}
}
/**
* Sets the label of the step. This field is used in the toolbox and the editor to display the step.
*/
label(label) {
this._label = label;
return this;
}
/**
* Sets the description of the step.
* @param description The description of the step.
* @example `builder.description('This step does something useful.');`
*/
description(description) {
this._description = description;
return this;
}
/**
* Sets the category of the step. This field is used in the toolbox to group steps.
* @param category The category of the step.
* @example `builder.category('Utilities');`
*/
category(category) {
this._category = category;
return this;
}
/**
* Sets whether the step should be displayed in the toolbox. Default is `true`.
* @param toolbox Whether the step should be displayed in the toolbox.
* @example `builder.toolbox(false);`
*/
toolbox(toolbox) {
this._toolbox = toolbox;
return this;
}
/**
* Sets the validator of the step.
* @param validator The validator.
*/
validator(validator) {
this._validator = validator;
return this;
}
/**
* @returns The builder for the `name` property.
* @example `builder.name().value(createStringValueModel({ defaultValue: 'Some name' })).label('Name');`
*/
name() {
return this.nameBuilder;
}
/**
* @param propertyName Name of the property in the step.
* @returns The builder for the property.
* @example `builder.property('foo').value(createStringValueModel({ defaultValue: 'Some value' })).label('Foo');`
*/
property(propertyName) {
const path = Path.create(['properties', String(propertyName)]);
const builder = new PropertyModelBuilder(path, this.circularDependencyDetector);
this.propertyBuilder.push(builder);
return builder;
}
build() {
var _a;
if (!this.nameBuilder.hasValue()) {
this.nameBuilder.value(createStringValueModel({
defaultValue: buildLabel(this.type),
minLength: 1
}));
}
return {
type: this.type,
componentType: this.componentType,
label: (_a = this._label) !== null && _a !== void 0 ? _a : buildLabel(this.type),
category: this._category,
description: this._description,
toolbox: this._toolbox,
validator: this._validator,
name: this.nameBuilder.build(),
properties: this.propertyBuilder.map(builder => builder.build())
};
}
}
function createStepModel(type, componentType, build) {
const builder = new StepModelBuilder(type, componentType);
build(builder);
return builder.build();
}
const branchesPath = Path.create('branches');
class BranchedStepModelBuilder extends StepModelBuilder {
constructor() {
super(...arguments);
this.branchesBuilder = new PropertyModelBuilder(branchesPath, this.circularDependencyDetector);
}
/**
* @returns the builder for the branches property.
* @example `builder.branches().value(createBranchesValueModel(...));`
*/
branches() {
return this.branchesBuilder;
}
build() {
if (!this.branchesBuilder.hasValue()) {
throw new Error(`Branches configuration is not set for ${this.type}`);
}
const model = super.build();
model.properties.push(this.branchesBuilder.build());
return model;
}
}
function createBranchedStepModel(type, componentType, build) {
const builder = new BranchedStepModelBuilder(type, componentType);
build(builder);
return builder.build();
}
const sequencePath$1 = Path.create(['sequence']);
class RootModelBuilder {
constructor() {
this.circularDependencyDetector = new CircularDependencyDetector();
this.propertyBuilders = [];
this.sequenceBuilder = new PropertyModelBuilder(sequencePath$1, this.circularDependencyDetector);
}
/**
* @param propertyName Name of the property.
* @returns The builder for the property.
* @example `builder.property('foo').value(createStringValueModel({ defaultValue: 'Some value' })).label('Foo');`
*/
property(propertyName) {
const path = Path.create(['properties', String(propertyName)]);
const builder = new PropertyModelBuilder(path, this.circularDependencyDetector);
this.propertyBuilders.push(builder);
return builder;
}
/**
* @returns the builder for the sequence property.
* @example `builder.sequence().value(createSequenceValueModel(...));`
*/
sequence() {
return this.sequenceBuilder;
}
build() {
if (!this.sequenceBuilder.hasValue()) {
this.sequenceBuilder.value(createSequenceValueModel({
sequence: []
}));
}
return {
sequence: this.sequenceBuilder.build(),
properties: this.propertyBuilders.map(builder => builder.build())
};
}
}
function createRootModel(build) {
const builder = new RootModelBuilder();
build(builder);
return builder.build();
}
class DefinitionModelBuilder {
constructor() {
this.stepModels = {};
// Nothing...
}
root(modelOrCallback) {
if (this.rootModel) {
throw new Error('Root model is already defined');
}
if (typeof modelOrCallback === 'function') {
this.rootModel = createRootModel(modelOrCallback);
}
else {
this.rootModel = modelOrCallback;
}
return this;
}
steps(models) {
for (const model of models) {
this.step(model);
}
return this;
}
step(model) {
if (this.stepModels[model.type]) {
throw new Error(`Step model with type ${model.type} is already defined`);
}
this.stepModels[model.type] = model;
return this;
}
valueTypes(valueTypes) {
if (this._valueTypes) {
throw new Error('Value types are already set');
}
this._valueTypes = valueTypes;
return this;
}
build() {
var _a;
if (!this.rootModel) {
throw new Error('Root model is not defined');
}
return {
root: this.rootModel,
steps: this.stepModels,
valueTypes: (_a = this._valueTypes) !== null && _a !== void 0 ? _a : []
};
}
}
function createDefinitionModel(build) {
const builder = new DefinitionModelBuilder();
build(builder);
return builder.build();
}
const sequencePath = Path.create('sequence');
class SequentialStepModelBuilder extends StepModelBuilder {
constructor() {
super(...arguments);
this.sequenceBuilder = new PropertyModelBuilder(sequencePath, this.circularDependencyDetector);
}
/**
* @returns the builder for the sequence property.
* @example `builder.sequence().value(createSequenceValueModel(...));`
*/
sequence() {
return this.sequenceBuilder;
}
build() {
if (!this.sequenceBuilder.hasValue()) {
this.sequenceBuilder.value(createSequenceValueModel({
sequence: []
}));
}
const model = super.build();
model.properties.push(this.sequenceBuilder.build());
return model;
}
}
function createSequentialStepModel(type, componentType, build) {
const builder = new SequentialStepModelBuilder(type, componentType);
build(builder);
return builder.build();
}
class ScopedPropertyContext {
static create(propertyContext, parentsProvider, i18n) {
return new ScopedPropertyContext(propertyContext, i18n, parentsProvider);
}
constructor(propertyContext, i18n, parentsProvider) {
this.propertyContext = propertyContext;
this.i18n = i18n;
this.parentsProvider = parentsProvider;
this.tryGetStepType = this.propertyContext.tryGetStepType;
this.getPropertyValue = this.propertyContext.getPropertyValue;
this.formatPropertyValue = this.propertyContext.formatPropertyValue;
this.getValueTypes = this.propertyContext.getValueTypes;
this.hasVariable = (variableName, valueType) => {
return this.getVariables().some(v => v.name === variableName && (valueType === null || v.type === valueType));
};
this.findFirstUndefinedVariable = (variableNames) => {
const variables = new Set(this.getVariables().map(v => v.name));
return variableNames.find(name => !variables.has(name));
};
this.isVariableDuplicated = (variableName) => {
return this.getVariables().filter(v => v.name === variableName).length > 1;
};
this.tryGetVariableType = (variableName) => {
const variable = this.getVariables().find(v => v.name === variableName);
return variable ? variable.type : null;
};
this.getVariables = () => {
return this.parentsProvider.getVariables();
};
}
}
class ValueContext {
static createFromDefinitionContext(valueModel, propertyModel, definitionContext, i18n) {
const propertyContext = PropertyContext.create(definitionContext.object, propertyModel, definitionContext.definitionModel);
const scopedPropertyContext = ScopedPropertyContext.create(propertyContext, definitionContext.parentsProvider, i18n);
return new ValueContext(valueModel, scopedPropertyContext);
}
constructor(model, scopedPropertyContext) {
this.model = model;
this.scopedPropertyContext = scopedPropertyContext;
this.onValueChanged = new SimpleEvent();
this.tryGetStepType = this.scopedPropertyContext.tryGetStepType;
this.getPropertyValue = this.scopedPropertyContext.getPropertyValue;
this.formatPropertyValue = this.scopedPropertyContext.formatPropertyValue;
this.getValueTypes = this.scopedPropertyContext.getValueTypes;
this.hasVariable = this.scopedPropertyContext.hasVariable;
this.findFirstUndefinedVariable = this.scopedPropertyContext.findFirstUndefinedVariable;
this.isVariableDuplicated = this.scopedPropertyContext.isVariableDuplicated;
this.tryGetVariableType = this.scopedPropertyContext.tryGetVariableType;
this.getVariables = this.scopedPropertyContext.getVariables;
this.i18n = this.scopedPropertyContext.i18n;
this.getValue = () => {
return this.model.path.read(this.scopedPropertyContext.propertyContext.object);
};
this.setValue = (value) => {
this.model.path.write(this.scopedPropertyContext.propertyContext.object, value);
this.onValueChanged.forward(this.model.path);
};
this.validate = () => {
return this.model.validate(this);
};
}
createChildContext(childValueModel) {
const context = new ValueContext(childValueModel, this.scopedPropertyContext);
context.onValueChanged.subscribe(this.onValueChanged.forward);
return context;
}
}
class ParentsProvider {
static createForStep(step, definition, definitionModel, definitionWalker, i18n) {
return new ParentsProvider(step, definition, definitionModel, definitionWalker, i18n);
}
static createForRoot(definition, definitionModel, definitionWalker, i18n) {
return new ParentsProvider(null, definition, definitionModel, definitionWalker, i18n);
}
constructor(step, definition, definitionModel, definitionWalker, i18n) {
this.step = step;
this.definition = definition;
this.definitionModel = definitionModel;
this.definitionWalker = definitionWalker;
this.i18n = i18n;
this.getParentStepTypes = () => {
if (this.step) {
const parents = this.definitionWalker.getParents(this.definition, this.step);
return parents.slice(0, parents.length - 1).filter(p => typeof p === 'object').map(p => p.type);
}
return [];
};
}
getVariables() {
const result = [];
const rootContext = DefinitionContext.createForRoot(this.definition, this.definitionModel, this.definitionWalker, this.i18n);
this.appendVariables(result, null, this.definitionModel.root.properties, rootContext);
if (this.step) {
const parents = this.definitionWalker.getParents(this.definition, this.step);
const count = parents.length;
for (let index = 0; index < count; index++) {
const parent = parents[index];
if (typeof parent === 'string') {
continue;
}
const parentModel = this.definitionModel.steps[parent.type];
if (!parentModel) {
throw new Error(`Unknown step type: ${parent.type}`);
}
const parentContext = DefinitionContext.createForStep(parent, this.definition, this.definitionModel, this.definitionWalker, this.i18n);
this.appendVariables(result, parent.id, parentModel.properties, parentContext);
}
}
return result;
}
appendVariables(result, stepId, propertyModels, definitionContext) {
for (const propertyModel of propertyModels) {
const valueContext = ValueContext.createFromDefinitionContext(propertyModel.value, propertyModel, definitionContext, this.i18n);
const definitions = propertyModel.value.getVariableDefinitions(valueContext);
if (!definitions) {
continue;
}
for (const definition of definitions) {
result.push({
name: definition.name,
type: definition.type,
stepId,
valueModelPath: propertyModel.value.path
});
}
}
}
}
class DefinitionContext {
static createForStep(step, definition, definitionModel, definitionWalker, i18n) {
const parentsProvider = ParentsProvider.createForS