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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
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 ? (
) : 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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
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 (
);
}
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 (
);
}
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 (
);
}
}
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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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 (
);
}
}
////////////////////////////////////////////////////////////////////////////////
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);