Skip to content

Commit

Permalink
external inline value
Browse files Browse the repository at this point in the history
  • Loading branch information
GabiGrin committed Jan 4, 2024
1 parent 811e6a4 commit 1c9fb0d
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { MacroNodeDefinition } from "@flyde/core";
import React from "react";
import * as Blueprint from "@blueprintjs/core";
import * as BlueprintSelect from "@blueprintjs/select";

export function loadMacroEditor(
macroNode: MacroNodeDefinition<any>
Expand All @@ -9,8 +11,10 @@ export function loadMacroEditor(

const exportId = `__MacroNode__${id}`;

// ensure React is available for the window loaded component
// ensure React and BP are available for the editor comps
w.React = React;
w.Blueprint = Blueprint;
w.BlueprintSelect = BlueprintSelect;

try {
// eslint-disable-next-line no-eval
Expand Down
51 changes: 51 additions & 0 deletions stdlib/src/Values/InlineValue.flyde.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { MacroNode } from "@flyde/core";
import { getVariables } from "./getInlineVariables";

export interface InlineValueConfig {
type: "string" | "boolean" | "number" | "json" | "expression";
value: string;
}

export const InlineValue: MacroNode<InlineValueConfig> = {
id: "InlineValue",
displayName: "Inline Value",
description: "A static value or JS expression",
runFnBuilder: (config) => {
return (inputs, outputs, adv) => {
if (config.type === "expression") {
try {
const resFn = eval(`(inputs) => ${config.value}`);
outputs.value.next(resFn(inputs));
} catch (e) {
adv.onError(e);
}
} else {
outputs.value.next(config.value);
}
};
},
definitionBuilder: (config) => {
const inputNames =
config.type === "expression" ? getVariables(config.value) : [];
return {
defaultStyle: {
size: "small",
icon: "code",
},
displayName: "Inline Value",
description: "A static value or JS expression",
inputs: Object.fromEntries(inputNames.map((input) => [input, {}]) ?? []),
outputs: {
value: {
displayName: "Value",
description: "Emits the value configured",
},
},
};
},
defaultData: {
type: "string",
value: "",
},
editorComponentBundlePath: "../../../dist/ui/InlineValue.js",
};
146 changes: 146 additions & 0 deletions stdlib/src/Values/InlineValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {
FormGroup,
HTMLSelect,
InputGroup,
NumericInput,
TextArea,
} from "@blueprintjs/core";
import type { InlineValueConfig } from "./InlineValue.flyde";
import React, { useCallback, useMemo } from "react";
import { getVariables } from "./getInlineVariables";

const types: InlineValueConfig["type"][] = [
"string",
"number",
"json",
"boolean",
"expression",
];

const defaultValuePerType = {
string: (currValue: any) => `${currValue}`,
number: (currValue: any) =>
isNaN(Number(currValue)) ? 0 : Number(currValue),
json: (currValue: any) => JSON.stringify(currValue),
boolean: (currValue: any) => !!currValue,
expression: (currValue: any) => currValue,
};

function InlineValueEditor(props: {
value: InlineValueConfig;
onChange: (value: InlineValueConfig) => void;
}) {
const { value, onChange } = props;

const changeType = useCallback(
(type) => {
onChange({ value: defaultValuePerType[type](value.value), type });
},
[value, onChange]
);

const changeValue = useCallback(
(_val) => {
onChange({ ...value, value: _val });
},
[value, onChange]
);

const editorPanel = useMemo(() => {
switch (value.type) {
case "string":
return (
<FormGroup label="Value:">
<InputGroup
type="text"
value={value.value}
onChange={(e) => changeValue(e.target.value)}
/>
</FormGroup>
);
case "number":
return (
<FormGroup label="Value:">
<NumericInput
value={value.value}
onValueChange={(e) => changeValue(e)}
/>
</FormGroup>
);
case "json":
return (
<FormGroup label="Value:">
<TextArea
value={value.value}
onChange={(e) => changeValue(e.target.value)}
/>
</FormGroup>
);
case "boolean":
return (
<FormGroup label="Value:">
<HTMLSelect
value={value.value}
onChange={(e) => changeValue(e.target.value === "true")}
>
<option value="true">true</option>
<option value="false">false</option>
</HTMLSelect>
</FormGroup>
);
case "expression": {
const vars = getVariables(value.value);
return (
<>
<FormGroup
label="Value:"
helperText={`Accepts any valid JS extension. `}
>
<TextArea
value={value.value}
fill
onChange={(e) => changeValue(e.target.value)}
/>
</FormGroup>
<div>
{vars.length > 0 ? (
<small>
External inputs exposed from this expression:{" "}
<em>{vars.join(", ")}</em>
</small>
) : (
<small>
Expose external inputs by using the "inputs" object. For
example, "inputs.a + inputs.b" will expose 2 inputs, a and b,
and sum them.
</small>
)}
</div>
</>
);
}
}
}, [value, changeValue]);

return (
<div>
<FormGroup label="Value type:">
<HTMLSelect
value={value.type}
onChange={(e) =>
changeType(e.target.value as InlineValueConfig["type"])
}
>
{types.map((type) => (
<option key={type} value={type}>
{type}
</option>
))}
</HTMLSelect>
</FormGroup>
{editorPanel}
</div>
);
}

export default InlineValueEditor;
5 changes: 5 additions & 0 deletions stdlib/src/Values/getInlineVariables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const getVariables = (code: string) => {
return (code.match(/inputs\.([a-zA-Z]\w*)/g) || []).map((v) =>
v.replace(/inputs\./, "")
);
};
8 changes: 8 additions & 0 deletions stdlib/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const pairs = [
{ entry: "./src/Lists/ListFrom.tsx", name: "ListFrom" },
{ entry: "./src/Lists/SpreadList.tsx", name: "SpreadList" },
{ entry: "./src/ControlFlow/RoundRobin.tsx", name: "RoundRobin" },
{ entry: "./src/Values/InlineValue.tsx", name: "InlineValue" },
// {
// entry: "./src/macro-node-simple/InlineValueEditor.tsx",
// name: "InlineValue",
Expand Down Expand Up @@ -41,4 +42,11 @@ module.exports = pairs.map(({ entry, name }) => ({
},
],
},
externals: {
// Do not bundle React and ReactDOM, assume they're available externally
react: "React",
"react-dom": "ReactDOM",
"@blueprintjs/core": "Blueprint",
"@blueprintjs/select": "BlueprintSelect",
},
}));

0 comments on commit 1c9fb0d

Please sign in to comment.