import path from "path"; import React from "react"; import { observable, action, runInAction, makeObservable, computed, toJS } from "mobx"; import { observer } from "mobx-react"; import classNames from "classnames"; import { clipboard, nativeImage } from "electron"; import { registerClass, PropertyType, makeDerivedClassInfo, IEezObject, EezObject, ClassInfo, getParent, MessageType, getId, IMessage, IObjectClassInfo } from "project-editor/core/object"; import { getAncestorOfType, getChildOfObject, getClassInfo, getListLabel, getProjectStore, Message, ProjectStore, propertyNotFoundMessage, propertyNotSetMessage, Section } from "project-editor/store"; import type { IFlowContext, IResizeHandler } from "project-editor/flow/flow-interfaces"; import { guid } from "eez-studio-shared/guid"; import { ActionComponent, AutoSize, Component, ComponentInput, ComponentInputSpan, ComponentOutput, ComponentOutputSpan, CustomOutput, componentOutputUnique, makeAssignableExpressionProperty, makeExpressionProperty, outputIsOptionalIfAtLeastOneOutputExists } from "project-editor/flow/component"; import { getProject, ProjectType, findAction, findPage } from "project-editor/project/project"; import { Assets, DataBuffer } from "project-editor/build/assets"; import { buildAssignableExpression, buildExpression, checkAssignableExpression, checkExpression, evalConstantExpression } from "project-editor/flow/expression"; import { calcComponentGeometry } from "project-editor/flow/editor/render"; import { getStructureFromType, migrateType, ValueType, VariableTypeUI } from "project-editor/features/variable/value-type"; import { specificGroup } from "project-editor/ui-components/PropertyGrid/groups"; import { COMPONENT_TYPE_START_ACTION, COMPONENT_TYPE_END_ACTION, COMPONENT_TYPE_INPUT_ACTION, COMPONENT_TYPE_OUTPUT_ACTION, COMPONENT_TYPE_WATCH_VARIABLE_ACTION, COMPONENT_TYPE_EVAL_EXPR_ACTION, COMPONENT_TYPE_SET_VARIABLE_ACTION, COMPONENT_TYPE_SWITCH_ACTION, COMPONENT_TYPE_COMPARE_ACTION, COMPONENT_TYPE_IS_TRUE_ACTION, COMPONENT_TYPE_CONSTANT_ACTION, COMPONENT_TYPE_LOG_ACTION, COMPONENT_TYPE_CALL_ACTION_ACTION, COMPONENT_TYPE_DELAY_ACTION, COMPONENT_TYPE_ERROR_ACTION, COMPONENT_TYPE_CATCH_ERROR_ACTION, COMPONENT_TYPE_COUNTER_ACTION, COMPONENT_TYPE_LOOP_ACTION, COMPONENT_TYPE_SHOW_PAGE_ACTION, COMPONENT_TYPE_SHOW_MESSAGE_BOX_ACTION, COMPONENT_TYPE_SHOW_KEYBOARD_ACTION, COMPONENT_TYPE_SHOW_KEYPAD_ACTION, COMPONENT_TYPE_NOOP_ACTION, COMPONENT_TYPE_COMMENT_ACTION, COMPONENT_TYPE_SELECT_LANGUAGE_ACTION, COMPONENT_TYPE_SET_PAGE_DIRECTION_ACTION, COMPONENT_TYPE_ANIMATE_ACTION, COMPONENT_TYPE_ON_EVENT_ACTION, COMPONENT_TYPE_OVERRIDE_STYLE_ACTION, COMPONENT_TYPE_SORT_ARRAY_ACTION, COMPONENT_TYPE_TEST_AND_SET_ACTION, COMPONENT_TYPE_LABEL_IN_ACTION, COMPONENT_TYPE_LABEL_OUT_ACTION, COMPONENT_TYPE_SET_COLOR_THEME_ACTION } from "project-editor/flow/components/component-types"; import { makeEndInstruction } from "project-editor/flow/expression/instructions"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { CALL_ACTION_ICON, CALL_NATIVE_ACTION_ICON, CLIPBOARD_WRITE_ICON, FOCUS_WIDGET_ICON, LANGUAGE_ICON, LOG_ICON, PALETTE_ICON, PLAY_AUDIO_ICON, PRINT_TO_PDF_ICON } from "project-editor/ui-components/icons"; import { humanize } from "eez-studio-shared/string"; import { LeftArrow, RightArrow } from "project-editor/ui-components/icons"; import type { IDashboardComponentContext } from "eez-studio-types"; import { FLOW_EVENT_OPEN_PAGE, FLOW_EVENT_CLOSE_PAGE, FLOW_EVENT_KEYDOWN } from "project-editor/flow/runtime/flow-events"; import { DashboardComponentContext } from "project-editor/flow/runtime/worker-dashboard-component-context"; import { getAdditionalFlowPropertiesForUserProperties, UserPropertyValues, userPropertyValuesProperty } from "project-editor/flow/user-property"; const NOT_NAMED_LABEL = ""; //////////////////////////////////////////////////////////////////////////////// export class StartActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_START_ACTION, icon: ( ), componentHeaderColor: "#74c8ce" }); getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: false }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class EndActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_END_ACTION, icon: ( ), componentHeaderColor: "#74c8ce" }); getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class InputActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_INPUT_ACTION, properties: [ { name: "name", type: PropertyType.String, propertyGridGroup: specificGroup }, { name: "inputType", displayName: "Type", type: PropertyType.String, propertyGridColumnComponent: VariableTypeUI, propertyGridGroup: specificGroup } ], beforeLoadHook: (object: InputActionComponent, objectJS: any) => { migrateType(objectJS, "inputType"); }, check: ( inputActionComponent: InputActionComponent, messages: IMessage[] ) => { if (!inputActionComponent.name) { messages.push( propertyNotSetMessage(inputActionComponent, "name") ); } if (!inputActionComponent.inputType) { messages.push( propertyNotSetMessage(inputActionComponent, "inputType") ); } }, label: (component: InputActionComponent) => { if (!component.name) { return NOT_NAMED_LABEL; } return component.name; }, icon: ( ), componentHeaderColor: "#abc2a6" }); name: string; inputType: ValueType; override makeEditable() { super.makeEditable(); makeObservable(this, { name: observable, inputType: observable }); } getOutputs() { return [ { name: "@seqout", type: this.inputType, isSequenceOutput: true, isOptionalOutput: false }, ...super.getOutputs() ]; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { const flow = ProjectEditor.getFlow(this); dataBuffer.writeUint8(flow.inputComponents.indexOf(this)); } } //////////////////////////////////////////////////////////////////////////////// export class OutputActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_OUTPUT_ACTION, properties: [ { name: "name", type: PropertyType.String, propertyGridGroup: specificGroup }, { name: "outputType", displayName: "Type", type: PropertyType.String, propertyGridColumnComponent: VariableTypeUI, propertyGridGroup: specificGroup } ], beforeLoadHook: (object: OutputActionComponent, objectJS: any) => { migrateType(objectJS, "outputType"); }, check: ( outputActionComponent: OutputActionComponent, messages: IMessage[] ) => { if (!outputActionComponent.name) { messages.push( propertyNotSetMessage(outputActionComponent, "name") ); } if (!outputActionComponent.outputType) { messages.push( propertyNotSetMessage(outputActionComponent, "outputType") ); } }, label: (component: OutputActionComponent) => { if (!component.name) { return NOT_NAMED_LABEL; } return component.name; }, icon: ( ), componentHeaderColor: "#abc2a6" }); name: string; outputType: ValueType; override makeEditable() { super.makeEditable(); makeObservable(this, { name: observable, outputType: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { const flow = ProjectEditor.getFlow(this); dataBuffer.writeUint8(flow.outputComponents.indexOf(this)); } } //////////////////////////////////////////////////////////////////////////////// export class EvalExprActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_EVAL_EXPR_ACTION, label: () => "Evaluate", componentPaletteLabel: "Evaluate", properties: [ makeExpressionProperty( { name: "expression", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ) ], icon: ( ), componentHeaderColor: "#A6BBCF" }); expression: string; override makeEditable() { super.makeEditable(); makeObservable(this, { expression: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null", isSequenceOutput: true, isOptionalOutput: true }, { name: "result", type: "any", isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.expression}
); } } //////////////////////////////////////////////////////////////////////////////// export class WatchVariableActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_WATCH_VARIABLE_ACTION, label: () => "Watch", componentPaletteLabel: "Watch", properties: [ makeExpressionProperty( { name: "variable", displayName: "Expression", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ) ], icon: ( ), componentHeaderColor: "#A6BBCF" }); variable: string; override makeEditable() { super.makeEditable(); makeObservable(this, { variable: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "variable", displayName: "changed", type: "any" as ValueType, isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } getBody() { return (
{this.variable}
); } } //////////////////////////////////////////////////////////////////////////////// export class EvalJSExprActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { label: () => "Eval JS", componentPaletteLabel: "Eval JS", componentPaletteGroupName: "Dashboard Specific", properties: [ { name: "expression", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, monospaceFont: true, flowProperty: "template-literal" } ], beforeLoadHook: ( component: EvalJSExprActionComponent, jsComponent: Partial ) => { if ( !jsComponent.customOutputs || jsComponent.customOutputs.length == 0 ) { jsComponent.customOutputs = [ { name: "result", type: "any" } ] as any; } }, icon: ( ), componentHeaderColor: "#A6BBCF", defaultValue: { customOutputs: [ { name: "result", type: "any" } ] }, execute: (context: IDashboardComponentContext) => { const expression = context.getStringParam(0); const expressionValues = context.getExpressionListParam(4); const values: any = {}; for (let i = 0; i < expressionValues.length; i++) { const name = `_val${i}`; values[name] = expressionValues[i]; } try { let result = eval(expression); context.propagateValue("result", result); context.propagateValueThroughSeqout(); } catch (err) { console.info( "Error in EvalJSExprActionComponent_execute", err.toString() ); context.throwError(err.toString()); } } }); expression: string; override makeEditable() { super.makeEditable(); makeObservable(this, { expression: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.expression}
); } expandExpressionForBuild() { let expression = this.expression; let valueExpressions: any[] = []; const PARAMS_REGEXP = /\{([^\s][^\}]+)\}/m; function parse(expression: string) { const inputs = new Set(); if (expression) { PARAMS_REGEXP.lastIndex = 0; let str = expression; while (true) { let matches = str.match(PARAMS_REGEXP); if (!matches) { break; } const input = matches[1].trim(); inputs.add(input); str = str.substring(matches.index! + matches[1].length); } } return Array.from(inputs.keys()); } parse(expression).forEach((valueExpression, i) => { const name = `_val${i}`; valueExpressions.push(valueExpression); let regex = new RegExp(`{${valueExpression}}`, "g"); expression = expression.replace(regex, `values.${name}`); }); return { expression, valueExpressions }; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { const { expression, valueExpressions } = this.expandExpressionForBuild(); dataBuffer.writeObjectOffset(() => dataBuffer.writeString(expression)); dataBuffer.writeArray(valueExpressions, valueExpression => { try { // as property buildExpression(assets, dataBuffer, this, valueExpression); } catch (err) { assets.projectStore.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, err, getChildOfObject(this, "expression") ); dataBuffer.writeUint16NonAligned(makeEndInstruction()); } }); } } //////////////////////////////////////////////////////////////////////////////// class SetVariableEntry extends EezObject { variable: string; value: string; static classInfo: ClassInfo = { properties: [ makeAssignableExpressionProperty( { name: "variable", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ), makeExpressionProperty( { name: "value", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ) ], check: (setVariableItem: SetVariableEntry, messages: IMessage[]) => { try { checkAssignableExpression( getParent(getParent(setVariableItem)!)! as Component, setVariableItem.variable ); } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid assignable expression: ${err}`, getChildOfObject(setVariableItem, "variable") ) ); } try { checkExpression( getParent(getParent(setVariableItem)!)! as Component, setVariableItem.value ); } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid expression: ${err}`, getChildOfObject(setVariableItem, "value") ) ); } }, defaultValue: {}, listLabel: (entry: SetVariableEntry, collapsed) => !collapsed ? ( "" ) : ( <> {entry.variable} {entry.value} ) }; override makeEditable() { super.makeEditable(); makeObservable(this, { variable: observable, value: observable }); } } export class SetVariableActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SET_VARIABLE_ACTION, properties: [ { name: "entries", displayName: "Set variable entries", type: PropertyType.Array, typeClass: SetVariableEntry, propertyGridGroup: specificGroup, partOfNavigation: false, enumerable: false, defaultValue: [] } ], beforeLoadHook: ( component: SetVariableActionComponent, objectJS: any ) => { if (objectJS.entries == undefined) { objectJS.entries = [ { variable: objectJS.variable, value: objectJS.value } ]; delete objectJS.variable; delete objectJS.value; } }, icon: ( ), componentHeaderColor: "#A6BBCF", defaultValue: { entries: [{}] } }); entries: SetVariableEntry[]; override makeEditable() { super.makeEditable(); makeObservable(this, { entries: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.entries.map((entry, i) => (
                        {`#${i + 1} `}
                        {getListLabel(entry, true)}
                    
))}
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { dataBuffer.writeArray(this.entries, entry => { dataBuffer.writeObjectOffset(() => buildAssignableExpression( assets, dataBuffer, this, entry.variable ) ); dataBuffer.writeObjectOffset(() => buildExpression(assets, dataBuffer, this, entry.value) ); }); } } //////////////////////////////////////////////////////////////////////////////// class SwitchTest extends EezObject { condition: string; outputName: string; outputValue: string; static classInfo: ClassInfo = { properties: [ makeExpressionProperty( { name: "condition", displayName: "When", type: PropertyType.MultilineText }, "boolean" ), { name: "outputName", displayName: "Then output", type: PropertyType.String, unique: componentOutputUnique }, makeExpressionProperty( { name: "outputValue", displayName: "With value", type: PropertyType.MultilineText }, "any" ) ], listLabel: (test: SwitchTest, collapsed) => !collapsed ? "" : `WHEN ${test.condition} THEN OUTPUT ${test.outputName}${ test.outputValue ? ` WITH VALUE ${test.outputValue}` : "" }`, check: (switchTest: SwitchTest, messages: IMessage[]) => { try { checkExpression( getParent(getParent(switchTest)!)! as Component, switchTest.condition ); } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid condition expression: ${err}`, getChildOfObject(switchTest, "condition") ) ); } if (switchTest.outputValue) { try { checkExpression( getParent(getParent(switchTest)!)! as Component, switchTest.outputValue ); } catch (err) { messages.push( new Message( MessageType.ERROR, `Invalid output value expression: ${err}`, getChildOfObject(switchTest, "outputValue") ) ); } } }, updateObjectValueHook: (switchTest: SwitchTest, values: any) => { if ( values.outputName != undefined && switchTest.outputName != values.outputName ) { const component = getAncestorOfType( switchTest, Component.classInfo ); if (component) { ProjectEditor.getFlow( component ).rerouteConnectionLinesOutput( component, switchTest.outputName, values.outputName ); } } }, deleteObjectRefHook: (switchTest: SwitchTest) => { const component = getAncestorOfType( switchTest, Component.classInfo ) as Component; ProjectEditor.getFlow(component).deleteConnectionLinesFromOutput( component, switchTest.outputName ); }, defaultValue: {} }; override makeEditable() { super.makeEditable(); makeObservable(this, { condition: observable, outputName: observable, outputValue: observable }); } } export class SwitchActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SWITCH_ACTION, label: () => "SwitchCase", componentPaletteLabel: "SwitchCase", properties: [ { name: "tests", displayName: "Cases", type: PropertyType.Array, typeClass: SwitchTest, propertyGridGroup: specificGroup, partOfNavigation: false, enumerable: false, defaultValue: [] } ], icon: ( ), componentHeaderColor: "#AAAA66", defaultValue: { tests: [{}] } }); tests: SwitchTest[]; override makeEditable() { super.makeEditable(); makeObservable(this, { tests: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...this.tests .filter(test => !!test.outputName) .map(test => ({ name: test.outputName, type: "any" as ValueType, isSequenceOutput: true, isOptionalOutput: false })), ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.tests.map((test, i) => (
                        {`#${i + 1} `}
                        {test.condition}
                    
))}
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { dataBuffer.writeArray(this.tests, test => { dataBuffer.writeUint32( this.buildOutputs.findIndex( output => output.name == test.outputName ) ); dataBuffer.writeObjectOffset(() => { buildExpression(assets, dataBuffer, this, test.condition); }); dataBuffer.writeObjectOffset(() => { buildExpression( assets, dataBuffer, this, test.outputValue || "true" ); }); }); } } //////////////////////////////////////////////////////////////////////////////// export class CompareActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_COMPARE_ACTION, properties: [ makeExpressionProperty( { name: "A", displayName: "A", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ), makeExpressionProperty( { name: "B", displayName: "B", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: (object: CompareActionComponent) => { return object.operator == "NOT"; } }, "any" ), makeExpressionProperty( { name: "C", displayName: "C", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: (object: CompareActionComponent) => { return object.operator !== "BETWEEN"; } }, "any" ), { name: "operator", type: PropertyType.Enum, enumItems: [ { id: "=", label: "=" }, { id: "<", label: "<" }, { id: ">", label: ">" }, { id: "<=", label: "<=" }, { id: ">=", label: ">=" }, { id: "<>", label: "<>" }, { id: "NOT", label: "NOT" }, { id: "AND", label: "AND" }, { id: "OR", label: "OR" }, { id: "XOR", label: "XOR" }, { id: "BETWEEN", label: "BETWEEN" } ], propertyGridGroup: specificGroup } ], icon: ( ), componentHeaderColor: "#AAAA66", defaultValue: { operator: "=" } }); A: string; B: string; C: string; operator: string; override makeEditable() { super.makeEditable(); makeObservable(this, { A: observable, B: observable, C: observable, operator: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "True", type: "boolean", isSequenceOutput: true, isOptionalOutput: outputIsOptionalIfAtLeastOneOutputExists }, { name: "False", type: "boolean", isSequenceOutput: true, isOptionalOutput: outputIsOptionalIfAtLeastOneOutputExists }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (this.operator == "NOT") { return (
                        {" NOT "}
                        {this.isInputProperty("A") ? "A" : this.A}
                    
); } if (this.operator == "BETWEEN") { return (
                        {this.isInputProperty("B") ? "B" : this.B} {" <= "}
                        {this.isInputProperty("A") ? "A" : this.A} {" <= "}
                        {this.isInputProperty("C") ? "C" : this.C}
                    
); } return (
                    {this.isInputProperty("A") ? "A" : this.A} {this.operator}{" "}
                    {this.isInputProperty("B") ? "B" : this.B}
                
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { let condition; if (this.operator == "=") { condition = `(${this.A}) == (${this.B})`; } else if (this.operator == "<") { condition = `(${this.A}) < (${this.B})`; } else if (this.operator == ">") { condition = `(${this.A}) > (${this.B})`; } else if (this.operator == "<=") { condition = `(${this.A}) <= (${this.B})`; } else if (this.operator == ">=") { condition = `(${this.A}) >= (${this.B})`; } else if (this.operator == "<>") { condition = `(${this.A}) != (${this.B})`; } else if (this.operator == "NOT") { condition = `!(${this.A})`; } else if (this.operator == "AND") { condition = `(${this.A}) && (${this.B})`; } else if (this.operator == "OR") { condition = `(${this.A}) || (${this.B})`; } else if (this.operator == "XOR") { condition = `((${this.A}) && !(${this.B})) || (!(${this.A}) && (${this.B}))`; } else { condition = `((${this.A}) >= (${this.B})) && ((${this.A}) <= (${this.C}))`; } buildExpression(assets, dataBuffer, this, condition); } } //////////////////////////////////////////////////////////////////////////////// export class IsTrueActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_IS_TRUE_ACTION, properties: [ makeExpressionProperty( { name: "value", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "boolean" ) ], icon: ( ), componentHeaderColor: "#AAAA66", defaultValue: { value: "value", customInputs: [ { name: "value", type: "any" } ] } }); value: any; override makeEditable() { super.makeEditable(); makeObservable(this, { value: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "True", displayName: "Yes", type: "boolean", isSequenceOutput: true, isOptionalOutput: outputIsOptionalIfAtLeastOneOutputExists }, { name: "False", displayName: "No", type: "boolean", isSequenceOutput: true, isOptionalOutput: outputIsOptionalIfAtLeastOneOutputExists }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if ( this.customInputs.length == 1 && this.customInputs[0].name == this.value ) { return null; } return (
{this.value}
); } } //////////////////////////////////////////////////////////////////////////////// export class ConstantActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_CONSTANT_ACTION, properties: [ makeExpressionProperty( { name: "value", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, expressionIsConstant: true }, "string" ) ], icon: ( ), componentHeaderColor: "#C0C0C0" }); value: string; override makeEditable() { super.makeEditable(); makeObservable(this, { value: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "value", displayName: this.value, type: "any", isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { try { const { value, valueType } = evalConstantExpression( assets.rootProject, this.value ); dataBuffer.writeUint16(assets.getConstantIndex(value, valueType)); } catch (err) { assets.projectStore.outputSectionsStore.write( Section.OUTPUT, MessageType.ERROR, err.toString(), getChildOfObject(this, "value") ); dataBuffer.writeUint16(assets.getConstantIndex(null, "null")); } } } //////////////////////////////////////////////////////////////////////////////// export class DateNowActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { icon: ( ), componentHeaderColor: "#C0C0C0", execute: (context: IDashboardComponentContext) => { context.propagateValue("value", Date.now()); context.propagateValueThroughSeqout(); } }); getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "value", type: "date", isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class SortArrayActionComponent extends ActionComponent { array: string; structureName: string; structureFieldName: string; ascending: boolean; ignoreCase: boolean; override makeEditable() { super.makeEditable(); makeObservable(this, { array: observable, structureName: observable, structureFieldName: observable, ascending: observable, ignoreCase: observable }); } static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SORT_ARRAY_ACTION, properties: [ makeExpressionProperty( { name: "array", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "array:any" ), { name: "structureName", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "variables/structures", propertyGridGroup: specificGroup }, { name: "structureFieldName", type: PropertyType.Enum, enumItems: (component: SortArrayActionComponent) => { if (!component.structureName) { return []; } const project = ProjectEditor.getProject(component); const struct = getStructureFromType( project, `struct:${component.structureName}` ); if (!struct) { return []; } return struct.fields.map(field => ({ id: field.name, label: field.name })); }, propertyGridGroup: specificGroup, disabled: (component: SortArrayActionComponent) => { if (!component.structureName) { return true; } const project = ProjectEditor.getProject(component); if ( !getStructureFromType( project, `struct:${component.structureName}` ) ) { return true; } return false; } }, { name: "ascending", type: PropertyType.Boolean, checkboxStyleSwitch: true, propertyGridGroup: specificGroup }, { name: "ignoreCase", type: PropertyType.Boolean, checkboxStyleSwitch: true, propertyGridGroup: specificGroup } ], icon: ( ), componentHeaderColor: "#C0C0C0", defaultValue: { ignoreCase: true, ascending: true }, check: (component: SortArrayActionComponent, messages: IMessage[]) => { if (component.structureName) { const project = ProjectEditor.getProject(component); const struct = getStructureFromType( project, `struct:${component.structureName}` ); if (!struct) { messages.push( propertyNotFoundMessage(component, "structureName") ); } else if (!component.structureFieldName) { messages.push( propertyNotSetMessage(component, "structureName") ); } else if ( !struct.fieldsMap.get(component.structureFieldName) ) { messages.push( propertyNotFoundMessage(component, "structureFieldName") ); } } } }); getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "result", type: "any" as ValueType, isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { let bodyText; if (this.structureName) { bodyText = `${this.array} BY ${this.structureName}.${this.structureFieldName}`; } else { bodyText = `${this.array}`; } bodyText += this.ascending ? " ASCENDING" : " DESCENDING"; bodyText += this.ignoreCase ? " IGNORE CASE" : " CASE SENSITIVE"; return (
{bodyText}
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // arrayType if (this.structureName) { dataBuffer.writeInt32( assets.getTypeIndex(`array:struct:${this.structureName}`) ); } else { dataBuffer.writeInt32(-1); } // structFieldIndex dataBuffer.writeInt32( assets.projectStore.typesStore.getFieldIndex( `struct:${this.structureName}`, this.structureFieldName ) ?? -1 ); // flags const SORT_ARRAY_FLAG_ASCENDING = 1 << 0; const SORT_ARRAY_FLAG_IGNORE_CASE = 1 << 1; let flags = 0; if (this.ascending) { flags |= SORT_ARRAY_FLAG_ASCENDING; } if (this.ignoreCase) { flags |= SORT_ARRAY_FLAG_IGNORE_CASE; } dataBuffer.writeUint32(flags); } } //////////////////////////////////////////////////////////////////////////////// export class ReadSettingActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { componentPaletteGroupName: "Dashboard Specific", properties: [ makeExpressionProperty( { name: "key", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], icon: ( ), componentHeaderColor: "#C0DEED", execute: (context: IDashboardComponentContext) => { let key = context.evalProperty("key"); if ( key == undefined || typeof key != "string" || key.trim() == "" ) { context.throwError(`Invalid key property`); return; } key = key.trim(); context.propagateValue( "value", context.WasmFlowRuntime.readSettings(key) || null ); context.propagateValueThroughSeqout(); } }); key: string; override makeEditable() { super.makeEditable(); makeObservable(this, { key: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "value", type: "any", isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { let key; if (flowContext.flowState) { key = flowContext.flowState.evalExpression(this, this.key); } else { key = this.key; } return key ? (
{key}
) : null; } } //////////////////////////////////////////////////////////////////////////////// export class WriteSettingsActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { componentPaletteGroupName: "Dashboard Specific", componentPaletteLabel: "WriteSetting", properties: [ makeExpressionProperty( { name: "key", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ), makeExpressionProperty( { name: "value", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ) ], icon: ( ), componentHeaderColor: "#C0DEED", execute: (context: IDashboardComponentContext) => { let key = context.evalProperty("key"); if ( key == undefined || typeof key != "string" || key.trim() == "" ) { context.throwError(`Invalid key property`); return; } key = key.trim(); let value = context.evalProperty("value"); context.WasmFlowRuntime.writeSettings(key, value); context.propagateValueThroughSeqout(); } }); key: string; value: string; override makeEditable() { super.makeEditable(); makeObservable(this, { key: observable, value: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class LogActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_LOG_ACTION, properties: [ makeExpressionProperty( { name: "value", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], beforeLoadHook: (object: LogActionComponent, objectJS: any) => { if ( !objectJS.hasOwnProperty("value") && objectJS.customInputs == undefined ) { objectJS.customInputs = [ { name: "value", type: "string" } ]; objectJS.value = "value"; } }, icon: LOG_ICON, componentHeaderColor: "#C0DEED" }); value: string; override makeEditable() { super.makeEditable(); makeObservable(this, { value: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if ( this.value === "value" && this.customInputs.find(customInput => customInput.name === "value") ) { return null; } return (
{this.value}
); } } //////////////////////////////////////////////////////////////////////////////// export class PlayAudioActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { enabledInComponentPalette: (projectType: ProjectType) => projectType === ProjectType.DASHBOARD, properties: [ makeExpressionProperty( { name: "audioFile", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], defaultValue: {}, icon: PLAY_AUDIO_ICON, componentHeaderColor: "#C9E9D2", execute: (context: IDashboardComponentContext) => { const audioFile = context.evalProperty("audioFile"); if (audioFile == undefined) { context.throwError(`Invalid Audio file property`); return; } // Create an AudioContext const audioContext = new window.AudioContext(); // Function to play audio function playAudio(url: string) { fetch(url) .then(response => response.arrayBuffer()) .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer) ) .then(audioBuffer => { const source = audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(audioContext.destination); source.start(0); }) .catch(error => console.error("Error loading audio:", error) ); } playAudio(audioFile); context.propagateValueThroughSeqout(); } }); audioFile: string; override makeEditable() { super.makeEditable(); makeObservable(this, { audioFile: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (!this.audioFile) { return null; } return (
{path.basename(this.audioFile)}
); } } //////////////////////////////////////////////////////////////////////////////// export class CallActionActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_CALL_ACTION_ACTION, properties: [ { name: "action", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "actions", propertyGridGroup: specificGroup }, userPropertyValuesProperty ], getAdditionalFlowProperties: getAdditionalFlowPropertiesForUserProperties, label: (component: CallActionActionComponent) => { if (!component.action) { return "CallAction"; } return component.action; }, icon: CALL_ACTION_ICON, getIcon: ( object?: CallActionActionComponent, componentClass?: IObjectClassInfo, projectStore?: ProjectStore ) => { let actionName; if (object) { actionName = object.action; projectStore = ProjectEditor.getProjectStore(object); } else if (componentClass) { actionName = componentClass.props?.action; } if (projectStore && actionName) { const action = findAction(projectStore.project, actionName); if (action && action.implementationType == "native") { return CALL_NATIVE_ACTION_ICON; } } return undefined; }, componentHeaderColor: ( object?: CallActionActionComponent, componentClass?: IObjectClassInfo, projectStore?: ProjectStore ) => { let actionName; if (object) { actionName = object.action; projectStore = ProjectEditor.getProjectStore(object); } else if (componentClass) { actionName = componentClass.props?.action; } if (projectStore && actionName) { const action = findAction(projectStore.project, actionName); if (action && action.implementationType == "native") { return "#9CBA93"; } } return "#C7E9C0"; }, open: (object: CallActionActionComponent) => { object.open(); }, check: (component: CallActionActionComponent, messages: IMessage[]) => { if (!component.action) { messages.push(propertyNotSetMessage(component, "action")); } else { const action = findAction( getProject(component), component.action ); if (!action) { messages.push( new Message( MessageType.ERROR, `Action "${component.action}" not found`, getChildOfObject(component, "action") ) ); } } } }); action: string; userPropertyValues: UserPropertyValues; override makeEditable() { super.makeEditable(); makeObservable(this, { action: observable, userPropertyValues: observable }); } getInputs(): ComponentInput[] { let inputs: ComponentInput[]; const action = findAction(getProject(this), this.action); if (action) { if (action.implementationType == "native") { inputs = [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false } ]; } else { inputs = action.inputComponents.map( (inputActionComponent: InputActionComponent) => ({ name: inputActionComponent.objID, displayName: inputActionComponent.name ? inputActionComponent.name : NOT_NAMED_LABEL, type: inputActionComponent.inputType, isSequenceInput: false, isOptionalInput: false }) ); inputs.unshift({ name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: !action.startComponent, alwaysBuild: action.startComponent ? true : false }); } } else { inputs = []; } return [...inputs, ...super.getInputs()]; } getOutputs() { let outputs: ComponentOutput[]; const action = findAction(getProject(this), this.action); if (action) { if (action.implementationType == "native") { outputs = [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true } ]; } else { outputs = action.outputComponents.map( (outputActionComponent: OutputActionComponent) => ({ name: outputActionComponent.objID, displayName: outputActionComponent.name ? outputActionComponent.name : NOT_NAMED_LABEL, type: outputActionComponent.outputType, isSequenceOutput: false, isOptionalOutput: true }) ); if (action.endComponent) { outputs.unshift({ name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }); } } } else { outputs = []; } return [...outputs, ...super.getOutputs()]; } open() { const action = findAction(getProject(this), this.action); if (action) { getProjectStore(this).navigationStore.showObjects( [action], true, false, false ); } } getBody(flowContext: IFlowContext): React.ReactNode { const action = findAction( flowContext.projectStore.project, this.action ); if (!action) { return null; } if (action.userProperties.length == 0) { return null; } return (
{action.userProperties.map(userProperty => (
                        <>
                            {userProperty.displayName || userProperty.name}
                            {userProperty.assignable ? (
                                
                            ) : (
                                
                            )}
                            {this.userPropertyValues.values[userProperty.id] ||
                                ""}
                        
                    
))}
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { const action = findAction(getProject(this), this.action); if (action) { if ( assets.option == "buildFiles" && action.implementationType == "native" ) { // flowIndex dataBuffer.writeInt16( assets.flows.length + assets.getWidgetActionIndex(this, "action") ); // inputsStartIndex dataBuffer.writeUint8(0); // outputsStartIndex dataBuffer.writeUint8(0); } else { // flowIndex const flowIndex = assets.flows.indexOf(action); dataBuffer.writeInt16(flowIndex); // inputsStartIndex if (action.inputComponents.length > 0) { dataBuffer.writeUint8( this.buildInputs.findIndex( input => input.name == action.inputComponents[0].objID ) ); } else { dataBuffer.writeUint8(0); } // outputsStartIndex if (action.outputComponents.length > 0) { dataBuffer.writeUint8( this.buildOutputs.findIndex( output => output.name == action.outputComponents[0].objID ) ); } else { dataBuffer.writeUint8(0); } } } else { // flowIndex dataBuffer.writeInt16(-1); // inputsStartIndex dataBuffer.writeUint8(0); // outputsStartIndex dataBuffer.writeUint8(0); } } } //////////////////////////////////////////////////////////////////////////////// export class DynamicCallActionActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { properties: [ makeExpressionProperty( { name: "action", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], icon: ( ), componentHeaderColor: "#C7E9C0", componentPaletteGroupName: "Dashboard Specific", execute: (context: IDashboardComponentContext) => { const actionName = context.evalProperty("action"); if (actionName == undefined || typeof actionName != "string") { context.throwError(`Invalid action name property`); return; } const flowIndex = context.WasmFlowRuntime.assetsMap.actionFlowIndexes[actionName]; if (flowIndex == undefined) { context.throwError(`Invalid action name: ${actionName}`); return; } context.executeCallAction(flowIndex); } }); action: string; override makeEditable() { super.makeEditable(); makeObservable(this, { action: observable }); } getInputs(): ComponentInput[] { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // flowIndex dataBuffer.writeInt16(-1); // inputsStartIndex if (this.customInputs.length > 0) { dataBuffer.writeUint8( this.buildInputs.findIndex( input => input.name == this.customInputs[0].name ) ); } else { dataBuffer.writeUint8(0); } // outputsStartIndex if (this.customOutputs.length > 0) { dataBuffer.writeUint8( this.buildOutputs.findIndex( output => output.name == this.customOutputs[0].name ) ); } else { dataBuffer.writeUint8(0); } } } //////////////////////////////////////////////////////////////////////////////// export class DelayActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_DELAY_ACTION, properties: [ makeExpressionProperty( { name: "milliseconds", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ) ], icon: ( ), componentHeaderColor: "#E6E0F8" }); milliseconds: string; override makeEditable() { super.makeEditable(); makeObservable(this, { milliseconds: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: false }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.milliseconds} ms
); } } //////////////////////////////////////////////////////////////////////////////// export class ErrorActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_ERROR_ACTION, properties: [ makeExpressionProperty( { name: "message", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], icon: ( ), componentHeaderColor: "#fc9b9b" }); message: string; override makeEditable() { super.makeEditable(); makeObservable(this, { message: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (this.isInputProperty("message")) { return null; } return (
{this.message}
); } } //////////////////////////////////////////////////////////////////////////////// export class CatchErrorActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_CATCH_ERROR_ACTION, properties: [], icon: ( ), componentHeaderColor: "#FFAAAA" }); getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, { name: "Message", type: "string", isSequenceOutput: false, isOptionalOutput: false }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class CounterActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_COUNTER_ACTION, properties: [ makeExpressionProperty( { name: "countValue", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ) ], icon: ( ), componentHeaderColor: "#E2D96E" }); countValue: number; override makeEditable() { super.makeEditable(); makeObservable(this, { countValue: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: false }, { name: "done", type: "null", isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.countValue}
); } } //////////////////////////////////////////////////////////////////////////////// export class LoopActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_LOOP_ACTION, properties: [ makeAssignableExpressionProperty( { name: "variable", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), makeExpressionProperty( { name: "from", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), makeExpressionProperty( { name: "to", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), makeExpressionProperty( { name: "step", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), { name: "version", type: PropertyType.Number, hideInDocumentation: "all", hideInPropertyGrid: true } ], beforeLoadHook: (object: IEezObject, jsObject: any) => { if (jsObject.version == undefined) { jsObject.version = 1; jsObject.to = jsObject.to + " - 1"; } }, check: (object: LoopActionComponent, messages: IMessage[]) => { if ( object.variableOutput && object.variableOutput.type != "integer" && object.variableOutput.type != "float" && object.variableOutput.type != "double" ) { messages.push( new Message( MessageType.ERROR, `Output "${object.variableOutput.name}" type must be integer, float or double`, getChildOfObject(object.variableOutput, "name") ) ); } }, icon: ( ), componentHeaderColor: "#E2D96E", defaultValue: { from: "0", step: "1", version: 1 } }); variable: string; from: string; to: string; step: string; override makeEditable() { super.makeEditable(); makeObservable(this, { variable: observable, from: observable, to: observable, step: observable }); } getInputs() { return [ { name: "start", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, { name: "next", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } get variableOutput(): CustomOutput | undefined { return this.customOutputs.find( output => output.name == this.variable?.trim() ); } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: this.variableOutput != undefined }, { name: "done", type: "null", isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
                    {this.variable} [ {this.from} ... {this.to} ]
                    {this.step !== "1" ? ` step ${this.step}` : ""}
                
); } } //////////////////////////////////////////////////////////////////////////////// export class OnEventActionComponent extends ActionComponent { event: string; override makeEditable() { super.makeEditable(); makeObservable(this, { event: observable }); } static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_ON_EVENT_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL, properties: [ { name: "event", type: PropertyType.Enum, enumItems: (object: IEezObject) => { if ( ProjectEditor.getProject(object).projectTypeTraits .isDashboard ) { return [ { id: "page_open", label: "Page open" }, { id: "page_close", label: "Page close" }, { id: "keydown", label: "Keydown" } ]; } return [ { id: "page_open", label: "Page open" }, { id: "page_close", label: "Page close" } ]; }, propertyGridGroup: specificGroup } ], icon: ( ), componentHeaderColor: "#DEB887" }); getBody(flowContext: IFlowContext): React.ReactNode { return (
{humanize(this.event)}
); } getOutputs() { return [ { name: "@seqout", type: "null" as const, isSequenceOutput: true, isOptionalOutput: false }, { name: "event", type: "any" as const, isSequenceOutput: false, isOptionalOutput: true }, ...super.getOutputs() ]; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // event let event: number = 0; if (this.event == "page_open") { event = FLOW_EVENT_OPEN_PAGE; } else if (this.event == "page_close") { event = FLOW_EVENT_CLOSE_PAGE; } else if (this.event == "keydown") { event = FLOW_EVENT_KEYDOWN; } dataBuffer.writeUint8(event); } } //////////////////////////////////////////////////////////////////////////////// export class ShowPageActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SHOW_PAGE_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL, properties: [ { name: "page", type: PropertyType.ObjectReference, propertyGridGroup: specificGroup, referencedObjectCollectionPath: "userPages" } ], check: (object: ShowPageActionComponent, messages: IMessage[]) => { if (!object.page) { messages.push(propertyNotSetMessage(object, "page")); } else { let page = findPage(getProject(object), object.page); if (!page) { messages.push(propertyNotFoundMessage(object, "page")); } } }, icon: ( ), componentHeaderColor: "#DEB887" }); page: string; override makeEditable() { super.makeEditable(); makeObservable(this, { page: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.page}
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // page let page: number = 0; if (this.page) { page = assets.getPageIndex(this, "page"); } dataBuffer.writeInt16(page); } } //////////////////////////////////////////////////////////////////////////////// const MESSAGE_BOX_TYPE_INFO = 1; const MESSAGE_BOX_TYPE_ERROR = 2; const MESSAGE_BOX_TYPE_QUESTION = 3; export class ShowMessageBoxActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SHOW_MESSAGE_BOX_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL && projectType !== ProjectType.DASHBOARD, properties: [ { name: "messageType", type: PropertyType.Enum, enumItems: [ { id: MESSAGE_BOX_TYPE_INFO, label: "Info" }, { id: MESSAGE_BOX_TYPE_ERROR, label: "Error" }, { id: MESSAGE_BOX_TYPE_QUESTION, label: "Question" } ], propertyGridGroup: specificGroup }, makeExpressionProperty( { name: "message", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ), makeExpressionProperty( { name: "buttons", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, disabled: (component: ShowMessageBoxActionComponent) => component.messageType != MESSAGE_BOX_TYPE_QUESTION }, "array:string" ) ], defaultValue: { messageType: MESSAGE_BOX_TYPE_INFO }, icon: ( ), componentHeaderColor: "#DEB887" }); messageType: number; message: string; buttons: string; override makeEditable() { super.makeEditable(); makeObservable(this, { messageType: observable, message: observable, buttons: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
                    {this.messageType == MESSAGE_BOX_TYPE_INFO
                        ? "Info: "
                        : this.messageType == MESSAGE_BOX_TYPE_ERROR
                        ? "Error: "
                        : this.messageType == MESSAGE_BOX_TYPE_QUESTION
                        ? "Question: "
                        : ""}
                    : {this.message}
                
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // type dataBuffer.writeUint8(this.messageType); } } //////////////////////////////////////////////////////////////////////////////// export class ShowKeyboardActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SHOW_KEYBOARD_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL && projectType !== ProjectType.DASHBOARD, properties: [ makeExpressionProperty( { name: "label", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ), makeExpressionProperty( { name: "initalText", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ), makeExpressionProperty( { name: "minChars", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), makeExpressionProperty( { name: "maxChars", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), { name: "password", type: PropertyType.Boolean, propertyGridGroup: specificGroup } ], icon: ( ), componentHeaderColor: "#DEB887" }); label: string; initalText: string; minChars: string; maxChars: string; password: boolean; override makeEditable() { super.makeEditable(); makeObservable(this, { label: observable, initalText: observable, minChars: observable, maxChars: observable, password: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "result", type: "string" as ValueType, isSequenceOutput: false, isOptionalOutput: false }, { name: "canceled", type: "null" as ValueType, isSequenceOutput: false, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
                    {this.label ? this.label + ": " : ""} {this.initalText}
                
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // type dataBuffer.writeUint8(this.password ? 1 : 0); } } //////////////////////////////////////////////////////////////////////////////// export class ShowKeypadActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SHOW_KEYPAD_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL && projectType !== ProjectType.DASHBOARD, properties: [ makeExpressionProperty( { name: "label", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ), makeExpressionProperty( { name: "initalValue", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "float" ), makeExpressionProperty( { name: "min", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), makeExpressionProperty( { name: "max", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ), makeExpressionProperty( { name: "precision", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "float" ), makeExpressionProperty( { name: "unit", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "string" ) ], icon: ( ), componentHeaderColor: "#DEB887" }); label: string; initalValue: string; min: string; max: string; precision: string; unit: string; override makeEditable() { super.makeEditable(); makeObservable(this, { label: observable, initalValue: observable, min: observable, max: observable, precision: observable, unit: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "result", type: "float" as ValueType, isSequenceOutput: false, isOptionalOutput: false }, { name: "canceled", type: "null" as ValueType, isSequenceOutput: false, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
                    {this.label && this.label != '""' ? this.label + ": " : ""}{" "}
                    {this.initalValue}
                
); } } //////////////////////////////////////////////////////////////////////////////// export class SelectLanguageActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SELECT_LANGUAGE_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL, properties: [ makeExpressionProperty( { name: "language", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ) ], icon: LANGUAGE_ICON, componentHeaderColor: "#DEB887" }); language: string; override makeEditable() { super.makeEditable(); makeObservable(this, { language: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class SetPageDirectionActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SET_PAGE_DIRECTION_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL, properties: [ { name: "direction", type: PropertyType.Enum, enumItems: [ { id: "LTR", label: "LTR" }, { id: "RTL", label: "RTL" } ], propertyGridGroup: specificGroup } ], icon: ( ), componentHeaderColor: "#DEB887" }); direction: string; override makeEditable() { super.makeEditable(); makeObservable(this, { direction: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.direction}
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { dataBuffer.writeUint8(this.direction == "LTR" ? 0 : 1); } } export class OverrideStyleActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_OVERRIDE_STYLE_ACTION, componentPaletteGroupName: "GUI", enabledInComponentPalette: (projectType: ProjectType) => projectType !== ProjectType.LVGL && projectType !== ProjectType.DASHBOARD, properties: [ { name: "fromStyle", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "allStyles", propertyGridGroup: specificGroup }, { name: "toStyle", type: PropertyType.ObjectReference, referencedObjectCollectionPath: "allStyles", propertyGridGroup: specificGroup } ], icon: ( ), componentHeaderColor: "#DEB887" }); fromStyle: string; toStyle: string; override makeEditable() { super.makeEditable(); makeObservable(this, { fromStyle: observable, toStyle: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
                    {this.fromStyle}
                    
                    {this.toStyle}
                
); } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // fromStyle dataBuffer.writeInt16(assets.getStyleIndex(this, "fromStyle")); // toStyle dataBuffer.writeInt16(assets.getStyleIndex(this, "toStyle")); } } //////////////////////////////////////////////////////////////////////////////// export class AnimateActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_ANIMATE_ACTION, componentPaletteGroupName: "GUI", properties: [ makeExpressionProperty( { name: "from", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "float" ), makeExpressionProperty( { name: "to", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "float" ), makeExpressionProperty( { name: "speed", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "float" ) ], beforeLoadHook: ( component: AnimateActionComponent, jsComponent: Partial ) => { if (jsComponent.from == undefined) { jsComponent.from = "Flow.pageTimelinePosition()"; } if ((jsComponent as any).time != undefined) { jsComponent.to = (jsComponent as any).time; } if (jsComponent.speed == undefined) { jsComponent.speed = "1"; } }, icon: ( ), componentHeaderColor: "#DEB887", defaultValue: { from: "Flow.pageTimelinePosition()" } }); from: string; to: string; speed: string; override makeEditable() { super.makeEditable(); makeObservable(this, { from: observable, to: observable, speed: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
                    {this.from != "Flow.pageTimelinePosition()"
                        ? `From: ${this.from} s, `
                        : ""}
                    To: {this.to} s
                    {this.speed != "1" ? `, Speed: ${this.speed}` : ""}
                
); } } //////////////////////////////////////////////////////////////////////////////// export class SetColorThemeActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_SET_COLOR_THEME_ACTION, componentPaletteGroupName: "GUI", properties: [ makeExpressionProperty( { name: "theme", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ) ], icon: PALETTE_ICON, componentHeaderColor: "#DEB887" }); theme: string; override makeEditable() { super.makeEditable(); makeObservable(this, { theme: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class ClipboardWriteActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { componentPaletteGroupName: "GUI", properties: [ makeExpressionProperty( { name: "data", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "any" ) ], icon: CLIPBOARD_WRITE_ICON, componentHeaderColor: "#DEB887", defaultValue: {}, execute: (context: IDashboardComponentContext) => { let data = context.evalProperty("data"); if ( data == undefined || !( typeof data == "string" || data instanceof Buffer || data instanceof Uint8Array ) ) { context.throwError(`Invalid data property`); return; } if (typeof data == "string") { clipboard.writeText(data); } else { let image = nativeImage.createFromBuffer( data instanceof Uint8Array ? Buffer.from(data) : data ); clipboard.writeImage(image); } context.propagateValueThroughSeqout(); } }); data: string; override makeEditable() { super.makeEditable(); makeObservable(this, { data: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class NoopActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_NOOP_ACTION, componentPaletteLabel: "NoOp", properties: [ { name: "name", type: PropertyType.String, propertyGridGroup: specificGroup } ], label: (component: InputActionComponent) => { return component.name ?? ""; }, icon: ( ), componentHeaderColor: "#fff5c2" }); name: string; override makeEditable() { super.makeEditable(); makeObservable(this, { name: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// const TrixEditor = observer( class TrixEditor extends React.Component<{ component: CommentActionComponent; flowContext: IFlowContext; value: string; setValue: (value: string) => void; }> { inputId = guid(); editorId = guid(); trixEditor: any; onChange = () => { const { component, flowContext } = this.props; const geometry = calcComponentGeometry( component, this.trixEditor.closest( ".EezStudio_ComponentEnclosure" )! as HTMLElement, flowContext ); runInAction(() => { component.geometry = geometry; }); }; onFocus = () => { const trixToolbar = this.trixEditor.parentElement?.querySelector("trix-toolbar"); if (trixToolbar instanceof HTMLElement) { trixToolbar.style.visibility = "visible"; } if (this.trixEditor.innerHTML != this.props.value) { this.props.setValue(this.trixEditor.innerHTML); } }; onBlur = () => { const trixToolbar = this.trixEditor.parentElement?.querySelector("trix-toolbar"); if (trixToolbar instanceof HTMLElement) { if (!document.activeElement?.classList.contains("trix-input")) { trixToolbar.style.visibility = ""; } } if (this.trixEditor.innerHTML != this.props.value) { this.props.setValue(this.trixEditor.innerHTML); } }; onAttachmentAdd = (event: any) => { const reader = new FileReader(); reader.addEventListener( "load", () => { event.attachment.setAttributes({ url: reader.result }); (this.trixEditor as any).editor.loadHTML( this.trixEditor.innerHTML ); }, false ); reader.readAsDataURL(event.attachment.file); }; setup() { if (this.trixEditor) { this.trixEditor.removeEventListener( "trix-change", this.onChange, false ); this.trixEditor.removeEventListener( "trix-focus", this.onFocus, false ); this.trixEditor.removeEventListener( "trix-blur", this.onBlur, false ); this.trixEditor.removeEventListener( "trix-attachment-add", this.onAttachmentAdd, false ); } this.trixEditor = document.getElementById( this.editorId ) as HTMLElement; if (this.props.value != this.trixEditor.innerHTML) { (this.trixEditor as any).editor.loadHTML(this.props.value); } this.trixEditor.addEventListener( "trix-change", this.onChange, false ); this.trixEditor.addEventListener("trix-focus", this.onFocus, false); this.trixEditor.addEventListener("trix-blur", this.onBlur, false); this.trixEditor.addEventListener( "trix-attachment-add", this.onAttachmentAdd, false ); } componentDidMount(): void { this.setup(); } render() { var attributes: { [key: string]: string } = { id: this.editorId, input: this.inputId }; return (
{React.createElement("trix-editor", attributes)}
); } } ); export class CommentActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_COMMENT_ACTION, label: (object: CommentActionComponent) => object.description, properties: [ { name: "text", type: PropertyType.String, hideInPropertyGrid: true, hideInDocumentation: "all" }, { name: "collapsed", type: PropertyType.Boolean, hideInPropertyGrid: true, hideInDocumentation: "none" }, { name: "expandedWidth", type: PropertyType.Number, hideInPropertyGrid: true, hideInDocumentation: "none" } ], beforeLoadHook: ( object: CommentActionComponent, jsObject: Partial ) => { if (jsObject.collapsed == undefined) { jsObject.collapsed = false; } if (jsObject.expandedWidth == undefined) { jsObject.expandedWidth = jsObject.width; } }, icon: ( ), componentHeaderColor: "#fff5c2", isFlowExecutableComponent: false, getResizeHandlers(object: CommentActionComponent) { return object.getResizeHandlers(); }, defaultValue: { left: 0, top: 0, width: 435, height: 134, collapsed: false }, open: (object: CommentActionComponent) => { const collapsed = !object.collapsed; if (collapsed) { ProjectEditor.getProjectStore(object).updateObject(object, { collapsed: !object.collapsed, expandedWidth: object.width }); } else { ProjectEditor.getProjectStore(object).updateObject(object, { collapsed: !object.collapsed, width: object.expandedWidth }); } } }); text: string; collapsed: boolean; expandedWidth: number; override makeEditable() { super.makeEditable(); makeObservable(this, { text: observable, collapsed: observable, expandedWidth: observable }); } get autoSize(): AutoSize { return this.collapsed ? "both" : "height"; } getResizeHandlers(): IResizeHandler[] | undefined | false { return this.collapsed ? [] : [ { x: 0, y: 50, type: "w-resize" }, { x: 100, y: 50, type: "e-resize" } ]; } getClassName(flowContext: IFlowContext) { return classNames( super.getClassName(flowContext), "EezStudio_CommentActionComponent" ); } getBody(flowContext: IFlowContext): React.ReactNode { return ( !this.collapsed && ( { const projectStore = getProjectStore(this); projectStore.updateObject(this, { text: value }); })} > ) ); } } //////////////////////////////////////////////////////////////////////////////// export class SyncLockActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_DELAY_ACTION, properties: [ makeExpressionProperty( { name: "milliseconds", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "integer" ) ], icon: ( ), componentHeaderColor: "#E6E0F8" }); milliseconds: string; override makeEditable() { super.makeEditable(); makeObservable(this, { milliseconds: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: false }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.milliseconds} ms
); } } export class TestAndSetActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_TEST_AND_SET_ACTION, properties: [ makeAssignableExpressionProperty( { name: "variable", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "boolean" ) ], icon: ( ), componentHeaderColor: "#AAAA66", defaultValue: {} }); variable: string; override makeEditable() { super.makeEditable(); makeObservable(this, { variable: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs(): ComponentOutput[] { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { return (
{this.variable}
); } } //////////////////////////////////////////////////////////////////////////////// export class LabelOutActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_LABEL_OUT_ACTION, icon: ( ), componentHeaderColor: "#c9e4de", componentPaletteLabel: "Label OUT", properties: [ { name: "label", type: PropertyType.String, propertyGridGroup: specificGroup }, { name: "customInputs", type: PropertyType.Array, hideInPropertyGrid: true, hideInDocumentation: "all" }, { name: "customOutputs", type: PropertyType.Array, hideInPropertyGrid: true, hideInDocumentation: "all" }, { name: "catchError", type: PropertyType.Boolean, hideInPropertyGrid: true, hideInDocumentation: "all" } ], check: (component: LabelOutActionComponent, messages: IMessage[]) => { if (!component.label) { messages.push(propertyNotSetMessage(component, "label")); } else if (!component.labelInComponent) { messages.push( new Message( MessageType.ERROR, `Label IN component with label "${component.label}" is not found`, getChildOfObject(component, "label") ) ); } } }); label: string; constructor() { super(); makeObservable(this, { labelInComponent: computed }); } override makeEditable() { super.makeEditable(); makeObservable(this, { label: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: false } ]; } get labelInComponent() { const components = getParent(this) as Component[]; for (let i = 0; i < components.length; i++) { const component = components[i]; if ( component instanceof LabelInActionComponent && component.label == this.label ) { return component; } } return undefined; } buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) { // labelInComponentIndex const labelInComponent = this.labelInComponent; dataBuffer.writeUint16( labelInComponent ? assets.getComponentIndex(labelInComponent) : -1 ); } getClassName(flowContext: IFlowContext) { return "eez-action LabelOutActionComponent"; } override render(flowContext: IFlowContext) { const classInfo = getClassInfo(this); let titleStyle: React.CSSProperties | undefined; if (classInfo.componentHeaderColor) { let backgroundColor; if (typeof classInfo.componentHeaderColor == "string") { backgroundColor = classInfo.componentHeaderColor; } else { backgroundColor = classInfo.componentHeaderColor(this); } titleStyle = { backgroundColor }; } return ( <>
{this.label ? this.label : ""}
); } } //////////////////////////////////////////////////////////////////////////////// export class LabelInActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { flowComponentId: COMPONENT_TYPE_LABEL_IN_ACTION, icon: ( ), componentHeaderColor: "#c9e4de", componentPaletteLabel: "Label IN", properties: [ { name: "label", type: PropertyType.String, propertyGridGroup: specificGroup }, { name: "customInputs", type: PropertyType.Array, hideInPropertyGrid: true, hideInDocumentation: "all" }, { name: "customOutputs", type: PropertyType.Array, hideInPropertyGrid: true, hideInDocumentation: "all" }, { name: "catchError", type: PropertyType.Boolean, hideInPropertyGrid: true, hideInDocumentation: "all" } ], check: ( thisComponent: LabelInActionComponent, messages: IMessage[] ) => { if (!thisComponent.label) { messages.push(propertyNotSetMessage(thisComponent, "label")); } else { const components = getParent(thisComponent) as Component[]; for (let i = 0; i < components.length; i++) { const component = components[i]; if ( component instanceof LabelInActionComponent && component != thisComponent && component.label == thisComponent.label ) { messages.push( new Message( MessageType.ERROR, `Label name "${component.label}" is not unique, each Label IN component must have unique label.`, getChildOfObject(thisComponent, "label") ) ); break; } } return undefined; } } }); label: string; override makeEditable() { super.makeEditable(); makeObservable(this, { label: observable }); } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: false } ]; } getClassName(flowContext: IFlowContext) { return "eez-action LabelInActionComponent"; } override render(flowContext: IFlowContext) { const classInfo = getClassInfo(this); let titleStyle: React.CSSProperties | undefined; if (classInfo.componentHeaderColor) { let backgroundColor; if (typeof classInfo.componentHeaderColor == "string") { backgroundColor = classInfo.componentHeaderColor; } else { backgroundColor = classInfo.componentHeaderColor(this); } titleStyle = { backgroundColor }; } return ( <>
{this.label ? this.label : ""}
); } } //////////////////////////////////////////////////////////////////////////////// const DEFAULT_OPTIONS = `{ landscape: false, scale: 1, pageSize: "Letter", margins: { marginType: "default" } }`; export class PrintToPDFActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { componentPaletteGroupName: "GUI", properties: [ makeExpressionProperty( { name: "widget", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "widget" ), makeExpressionProperty( { name: "options", type: PropertyType.MultilineText, propertyGridGroup: specificGroup, isOptional: true }, "json" ) ], defaultValue: { options: DEFAULT_OPTIONS }, icon: PRINT_TO_PDF_ICON, componentHeaderColor: "#DEB887", execute: (context: IDashboardComponentContext) => { const widget = context.evalProperty("widget"); if (widget == undefined) { context.throwError(`Invalid Widget property`); return; } const widgetInfo = context.WasmFlowRuntime.getWidgetHandleInfo(widget); if (!widgetInfo) { context.throwError(`Invalid Widget handle`); return; } const widgetContext = new DashboardComponentContext( context.WasmFlowRuntime, widgetInfo.flowStateIndex, widgetInfo.componentIndex ); const executionState = widgetContext.getComponentExecutionState(); if (!executionState) { context.throwError(`Widget not initialized`); return; } if (!executionState.printWidget) { context.throwError(`Widget doesn't support printing`); return; } const options = context.evalProperty("options"); if (options != undefined && typeof options != "object") { context.throwError(`Invalid Options property`); return; } executionState.printWidget(options ? toJS(options) : {}); context.propagateValueThroughSeqout(); } }); widget: string; override makeEditable() { super.makeEditable(); makeObservable(this, { widget: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } } //////////////////////////////////////////////////////////////////////////////// export class FocusWidgetActionComponent extends ActionComponent { static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, { componentPaletteGroupName: "GUI", properties: [ makeExpressionProperty( { name: "widget", type: PropertyType.MultilineText, propertyGridGroup: specificGroup }, "widget" ) ], defaultValue: {}, icon: FOCUS_WIDGET_ICON, componentHeaderColor: "#DEB887", execute: (context: IDashboardComponentContext) => { const widget = context.evalProperty("widget"); if (widget == undefined) { context.throwError(`Invalid Widget property`); return; } const widgetInfo = context.WasmFlowRuntime.getWidgetHandleInfo(widget); if (!widgetInfo) { context.throwError(`Invalid Widget handle`); return; } const widgetContext = new DashboardComponentContext( context.WasmFlowRuntime, widgetInfo.flowStateIndex, widgetInfo.componentIndex ); const executionState = widgetContext.getComponentExecutionState(); if (!executionState) { context.throwError(`Widget not initialized`); return; } if (!executionState.focus) { context.throwError(`Widget doesn't support focus`); return; } executionState.focus(); context.propagateValueThroughSeqout(); } }); widget: string; override makeEditable() { super.makeEditable(); makeObservable(this, { widget: observable }); } getInputs() { return [ { name: "@seqin", type: "any" as ValueType, isSequenceInput: true, isOptionalInput: true }, ...super.getInputs() ]; } getOutputs() { return [ { name: "@seqout", type: "null" as ValueType, isSequenceOutput: true, isOptionalOutput: true }, ...super.getOutputs() ]; } getBody(flowContext: IFlowContext): React.ReactNode { if (!this.widget) { return null; } return (
{this.widget}
); } } //////////////////////////////////////////////////////////////////////////////// registerClass("StartActionComponent", StartActionComponent); registerClass("EndActionComponent", EndActionComponent); registerClass("InputActionComponent", InputActionComponent); registerClass("OutputActionComponent", OutputActionComponent); registerClass("EvalExprActionComponent", EvalExprActionComponent); registerClass("EvalJSExprActionComponent", EvalJSExprActionComponent); registerClass("WatchVariableActionComponent", WatchVariableActionComponent); registerClass("SetVariableActionComponent", SetVariableActionComponent); registerClass("SwitchActionComponent", SwitchActionComponent); registerClass("CompareActionComponent", CompareActionComponent); registerClass("IsTrueActionComponent", IsTrueActionComponent); registerClass("TestAndSetActionComponent", TestAndSetActionComponent); registerClass("DelayActionComponent", DelayActionComponent); registerClass("LoopActionComponent", LoopActionComponent); registerClass("CounterActionComponent", CounterActionComponent); registerClass("ConstantActionComponent", ConstantActionComponent); registerClass("DateNowActionComponent", DateNowActionComponent); registerClass("SortArrayActionComponent", SortArrayActionComponent); registerClass("LogActionComponent", LogActionComponent); registerClass("PlayAudioActionComponent", PlayAudioActionComponent); registerClass("ReadSettingActionComponent", ReadSettingActionComponent); registerClass("WriteSettingsActionComponent", WriteSettingsActionComponent); registerClass("CallActionActionComponent", CallActionActionComponent); registerClass( "DynamicCallActionActionComponent", DynamicCallActionActionComponent ); registerClass("OnEventActionComponent", OnEventActionComponent); registerClass("ShowPageActionComponent", ShowPageActionComponent); registerClass("ShowMessageBoxActionComponent", ShowMessageBoxActionComponent); registerClass("ShowKeyboardActionComponent", ShowKeyboardActionComponent); registerClass("ShowKeypadActionComponent", ShowKeypadActionComponent); registerClass("SelectLanguageActionComponent", SelectLanguageActionComponent); registerClass( "SetPageDirectionActionComponent", SetPageDirectionActionComponent ); registerClass("OverrideStyleActionComponent", OverrideStyleActionComponent); registerClass("AnimateActionComponent", AnimateActionComponent); registerClass("SetColorThemeActionComponent", SetColorThemeActionComponent); registerClass("ClipboardWriteActionComponent", ClipboardWriteActionComponent); registerClass("ErrorActionComponent", ErrorActionComponent); registerClass("CatchErrorActionComponent", CatchErrorActionComponent); registerClass("LabelOutActionComponent", LabelOutActionComponent); registerClass("LabelInActionComponent", LabelInActionComponent); registerClass("NoopActionComponent", NoopActionComponent); registerClass("CommentActionComponent", CommentActionComponent); registerClass("PrintToPDFActionComponent", PrintToPDFActionComponent); registerClass("FocusWidgetActionComponent", FocusWidgetActionComponent);