@elicdavis/node-flow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.github/workflows/deploy.yml +57 -0
  2. package/README.md +159 -0
  3. package/dev.tsconfig.json +18 -0
  4. package/docs/promotional-image.png +0 -0
  5. package/esbuild.ts +32 -0
  6. package/index.html +311 -0
  7. package/package.json +50 -0
  8. package/src/camera.ts +30 -0
  9. package/src/connection.ts +249 -0
  10. package/src/contextMenu.ts +367 -0
  11. package/src/draggable.ts +13 -0
  12. package/src/elements/base.ts +192 -0
  13. package/src/elements/container.ts +119 -0
  14. package/src/elements/interfaces.ts +50 -0
  15. package/src/elements/text.ts +86 -0
  16. package/src/graph.ts +535 -0
  17. package/src/graphSubsystem.ts +31 -0
  18. package/src/index.ts +28 -0
  19. package/src/input.ts +153 -0
  20. package/src/markdown/entry.ts +323 -0
  21. package/src/markdown/lexicalParser.ts +148 -0
  22. package/src/markdown/markdown.ts +14 -0
  23. package/src/markdown/syntaxParser.ts +344 -0
  24. package/src/markdown/token.ts +43 -0
  25. package/src/metadata.ts +3 -0
  26. package/src/node.ts +1130 -0
  27. package/src/nodes/factory.ts +87 -0
  28. package/src/nodes/publisher.ts +98 -0
  29. package/src/nodes/subsystem.ts +760 -0
  30. package/src/notes/note.ts +295 -0
  31. package/src/notes/subsystem.ts +262 -0
  32. package/src/organize.ts +181 -0
  33. package/src/pass/subsystem.ts +64 -0
  34. package/src/performance.ts +9 -0
  35. package/src/popup.ts +81 -0
  36. package/src/popups/form.ts +63 -0
  37. package/src/popups/string.ts +33 -0
  38. package/src/port.ts +223 -0
  39. package/src/quickMenu.ts +330 -0
  40. package/src/styles/box.ts +104 -0
  41. package/src/styles/canvasTextAlign.ts +8 -0
  42. package/src/styles/canvasTextBaseline.ts +9 -0
  43. package/src/styles/cursor.ts +18 -0
  44. package/src/styles/stroke.ts +39 -0
  45. package/src/styles/text.ts +90 -0
  46. package/src/styles/textBox.ts +82 -0
  47. package/src/theme.ts +78 -0
  48. package/src/types/box.ts +126 -0
  49. package/src/types/list.ts +41 -0
  50. package/src/types/pool.ts +66 -0
  51. package/src/types/text.ts +136 -0
  52. package/src/types/vector2.ts +34 -0
  53. package/src/utils/color.ts +136 -0
  54. package/src/utils/html.ts +21 -0
  55. package/src/utils/math.ts +8 -0
  56. package/src/utils/string.ts +98 -0
  57. package/src/widgets/button.ts +107 -0
  58. package/src/widgets/color.ts +151 -0
  59. package/src/widgets/factory.ts +46 -0
  60. package/src/widgets/image.ts +163 -0
  61. package/src/widgets/number.ts +153 -0
  62. package/src/widgets/slider.ts +216 -0
  63. package/src/widgets/string.ts +128 -0
  64. package/src/widgets/text.ts +92 -0
  65. package/src/widgets/toggle.ts +205 -0
  66. package/src/widgets/widget.ts +12 -0
  67. package/tsconfig.json +15 -0
  68. package/tsup.config.ts +10 -0
@@ -0,0 +1,98 @@
1
+ const binarySearch = ({ max, getValue, match }) => {
2
+ let min = 0;
3
+
4
+ while (min <= max) {
5
+ let guess = Math.floor((min + max) / 2);
6
+ const compareVal = getValue(guess);
7
+
8
+ if (compareVal === match) return guess;
9
+ if (compareVal < match) min = guess + 1;
10
+ else max = guess - 1;
11
+ }
12
+
13
+ return max;
14
+ };
15
+
16
+ export function fitString(ctx: CanvasRenderingContext2D, str: string, maxWidth: number): string {
17
+ let width = ctx.measureText(str).width;
18
+ const ellipsis = '…';
19
+ const ellipsisWidth = ctx.measureText(ellipsis).width;
20
+ if (width <= maxWidth || width <= ellipsisWidth) {
21
+ return str;
22
+ }
23
+
24
+ const index = binarySearch({
25
+ max: str.length,
26
+ getValue: guess => ctx.measureText(str.substring(0, guess)).width,
27
+ match: maxWidth - ellipsisWidth,
28
+ });
29
+
30
+ return str.substring(0, index) + ellipsis;
31
+ };
32
+
33
+ export function splitString(ctx: CanvasRenderingContext2D, str: string, maxWidth: number): Array<string> {
34
+ let width = ctx.measureText(str).width;
35
+ if (width <= maxWidth) {
36
+ return [str];
37
+ }
38
+
39
+ let index = binarySearch({
40
+ max: str.length,
41
+ getValue: guess => ctx.measureText(str.substring(0, guess)).width,
42
+ match: maxWidth,
43
+ });
44
+
45
+ for (let backward = index - 1; backward >= 1; backward--) {
46
+ if (str.charAt(backward) === " ") {
47
+ index = backward + 1;
48
+ break;
49
+ }
50
+ }
51
+
52
+ return [str.substring(0, index), str.substring(index)];
53
+ };
54
+
55
+
56
+ export function splitStringIntoLines(ctx: CanvasRenderingContext2D, str: string, maxWidth: number): Array<string> {
57
+ let width = ctx.measureText(str).width;
58
+ if (width <= maxWidth) {
59
+ return [str];
60
+ }
61
+
62
+ const strings = new Array<string>;
63
+ let remaining = str;
64
+ while (remaining !== "") {
65
+ let index = binarySearch({
66
+ max: remaining.length,
67
+ getValue: guess => ctx.measureText(remaining.substring(0, guess)).width,
68
+ match: maxWidth,
69
+ });
70
+
71
+ if (index === remaining.length) {
72
+ strings.push(remaining.substring(0, index))
73
+ break;
74
+ }
75
+
76
+ for (let backward = index - 1; backward >= 1; backward--) {
77
+ if (remaining.charAt(backward) === " ") {
78
+ index = backward + 1;
79
+ break;
80
+ }
81
+ }
82
+
83
+ strings.push(remaining.substring(0, index))
84
+ remaining = remaining.substring(index)
85
+ }
86
+
87
+ return strings;
88
+ };
89
+
90
+ // https://stackoverflow.com/a/18234317/4974261
91
+ export function Format(str: string, ...args: Array<string>): string {
92
+ if (arguments.length) {
93
+ for (let key in args) {
94
+ str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
95
+ }
96
+ }
97
+ return str;
98
+ };
@@ -0,0 +1,107 @@
1
+ import { Theme } from "../theme";
2
+ import { TextBoxStyle, TextBoxStyleConfig, TextBoxStyleWithFallback } from "../styles/textBox";
3
+ import { Box, InBox } from "../types/box";
4
+ import { CopyVector2, Vector2 } from "../types/vector2";
5
+ import { height, width } from "./widget";
6
+
7
+
8
+ export interface ButtonWidgetConfig {
9
+ text?: string;
10
+ callback?: () => void;
11
+ idleStyle?: TextBoxStyleConfig;
12
+ hoverStyle?: TextBoxStyleConfig;
13
+ clickStyle?: TextBoxStyleConfig;
14
+ }
15
+
16
+ export class ButtonWidget {
17
+
18
+ #text: string;
19
+
20
+ #idleStyle: TextBoxStyle;
21
+
22
+ #hoverStyle: TextBoxStyle;
23
+
24
+ #clickStyle: TextBoxStyle;
25
+
26
+ #callback?: () => void;
27
+
28
+ #gettingClicked: boolean;
29
+
30
+ constructor(config?: ButtonWidgetConfig) {
31
+ this.#text = config?.text === undefined ? "Button" : config?.text;
32
+ this.#callback = config?.callback
33
+ this.#gettingClicked = false;
34
+
35
+ this.#idleStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.idleStyle, {
36
+ box: {
37
+ color: Theme.Widget.BackgroundColor,
38
+ border: {
39
+ size: Theme.Widget.Border.Size,
40
+ color: Theme.Widget.Border.Color,
41
+ }
42
+ },
43
+ text: { color: Theme.Widget.FontColor },
44
+ }));
45
+ this.#hoverStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.hoverStyle, {
46
+ box: {
47
+ color: Theme.Widget.Hover.BackgroundColor,
48
+ border: {
49
+ size: Theme.Widget.Border.Size,
50
+ color: Theme.Widget.Border.Color,
51
+ }
52
+ },
53
+ text: { color: Theme.Widget.FontColor },
54
+ }));
55
+ this.#clickStyle = new TextBoxStyle(TextBoxStyleWithFallback(config?.clickStyle, {
56
+ box: {
57
+ color: Theme.Widget.Button.Click.BackgroundColor,
58
+ border: {
59
+ size: Theme.Widget.Border.Size,
60
+ color: Theme.Widget.Border.Color,
61
+ }
62
+ },
63
+ text: { color: Theme.Widget.FontColor },
64
+ }))
65
+ }
66
+
67
+ Size(): Vector2 {
68
+ return { "x": width, "y": height }
69
+ }
70
+
71
+ Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
72
+ const box = {
73
+ Position: { x: 0, y: 0 },
74
+ Size: {
75
+ x: width * scale,
76
+ y: height * scale
77
+ }
78
+ };
79
+ CopyVector2(box.Position, position);
80
+
81
+ let style: TextBoxStyle = this.#idleStyle;
82
+
83
+ if (mousePosition !== undefined && !this.#gettingClicked) {
84
+ if (InBox(box, mousePosition)) {
85
+ style = this.#hoverStyle;
86
+ }
87
+ }
88
+
89
+ if (this.#gettingClicked) {
90
+ style = this.#clickStyle;
91
+ }
92
+ style.Draw(ctx, box, scale, this.#text);
93
+
94
+ return box
95
+ }
96
+
97
+ ClickStart(): void {
98
+ this.#gettingClicked = true;
99
+ }
100
+
101
+ ClickEnd(): void {
102
+ this.#gettingClicked = false;
103
+ if (this.#callback !== undefined) {
104
+ this.#callback();
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,151 @@
1
+ import { HexToColor } from "../utils/color";
2
+ import { TextStyleConfig } from "../styles/text";
3
+ import { Box } from "../types/box";
4
+ import { CopyVector2, Vector2 } from "../types/vector2";
5
+ import { height, width } from "./widget";
6
+ import { Popup } from "../popup";
7
+ import { TextBoxStyle } from '../styles/textBox';
8
+ import { Theme } from "../theme";
9
+ import { FlowNode } from "../node";
10
+
11
+ export interface ColorWidgetConfig {
12
+ value?: string;
13
+
14
+ property?: string;
15
+
16
+ textStyle?: TextStyleConfig;
17
+
18
+ callback?: (newColor: string) => void;
19
+ }
20
+
21
+ function contrastColor(color: string): string {
22
+ const c = HexToColor(color);
23
+ if (c === null) {
24
+ return "black";
25
+ }
26
+
27
+ if ((c.r + c.g + c.b) / 3 > 0.5) {
28
+ return "black"
29
+ }
30
+
31
+ return "white";
32
+ }
33
+
34
+ export class ColorWidget {
35
+
36
+ #node: FlowNode;
37
+
38
+ #nodeProperty: string | undefined;
39
+
40
+ #value: string;
41
+
42
+ #contrast: string;
43
+
44
+ #textBoxStyle: TextBoxStyle;
45
+
46
+ #callback?: (newColor: string) => void;
47
+
48
+ constructor(node: FlowNode, config?: ColorWidgetConfig) {
49
+ this.#value = "#000000"
50
+ this.#contrast = contrastColor(this.#value);
51
+ this.Set(config?.value === undefined ? "#000000" : config?.value);
52
+
53
+ this.#node = node;
54
+ this.#nodeProperty = config?.property;
55
+ this.#textBoxStyle = new TextBoxStyle({
56
+ box: {
57
+ color: this.#value,
58
+ border: {
59
+ color: this.#contrast,
60
+ size: Theme.Widget.Border.Size
61
+ }
62
+ },
63
+ text: config?.textStyle
64
+ });
65
+ this.#callback = config?.callback;
66
+
67
+ if (this.#nodeProperty !== undefined) {
68
+ this.#node.addPropertyChangeListener(this.#nodeProperty, (oldVal, newVal) => {
69
+ this.Set(newVal);
70
+ });
71
+ }
72
+ }
73
+
74
+ Size(): Vector2 {
75
+ return { "x": width, "y": height }
76
+ }
77
+
78
+ Set(value: string): void {
79
+ if (this.#value === value) {
80
+ return;
81
+ }
82
+
83
+ this.#value = value;
84
+
85
+ if (this.#callback !== undefined) {
86
+ this.#callback(this.#value);
87
+ }
88
+
89
+ if (this.#nodeProperty !== undefined) {
90
+ this.#node.setProperty(this.#nodeProperty, this.#value);
91
+ }
92
+
93
+ // Update Styling
94
+ this.#contrast = contrastColor(this.#value);
95
+ this.#textBoxStyle.setBoxColor(this.#value);
96
+ this.#textBoxStyle.setBorderColor(this.#contrast);
97
+ this.#textBoxStyle.setTextColor(this.#contrast);
98
+ }
99
+
100
+ ClickStart(): void {
101
+ }
102
+
103
+ ClickEnd(): void {
104
+ let input: HTMLInputElement | null = null;
105
+
106
+ const popup = new Popup({
107
+ title: "Set Color",
108
+ options: ["Set", "Cancel"],
109
+ content: () => {
110
+ const container = document.createElement('div');
111
+
112
+ input = document.createElement('input')
113
+ input.type = "color";
114
+ input.value = this.#value;
115
+ input.name = "color"
116
+ container.append(input);
117
+
118
+ const label = document.createElement("label");
119
+ label.htmlFor = "color";
120
+ label.textContent = "Color";
121
+ container.append(label);
122
+
123
+ return container;
124
+ },
125
+ onClose: (button: string | null): void => {
126
+ if (button !== "Set" || input === null) {
127
+ return;
128
+ }
129
+
130
+ this.Set(input.value);
131
+ },
132
+ });
133
+
134
+ popup.Show();
135
+ }
136
+
137
+ Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
138
+ const box = {
139
+ Position: { x: 0, y: 0 },
140
+ Size: {
141
+ x: width * scale,
142
+ y: height * scale
143
+ }
144
+ };
145
+ CopyVector2(box.Position, position);
146
+
147
+ this.#textBoxStyle.Draw(ctx, box, scale, this.#value)
148
+
149
+ return box;
150
+ }
151
+ }
@@ -0,0 +1,46 @@
1
+ import { Widget } from "./widget";
2
+ import { NumberWidget, NumberWidgetConfig } from './number';
3
+ import { ButtonWidget, ButtonWidgetConfig } from './button';
4
+ import { ColorWidget, ColorWidgetConfig } from './color';
5
+ import { SliderWidget, SliderWidgetConfig } from './slider';
6
+ import { StringWidget, StringWidgetConfig } from './string';
7
+ import { ToggleWidget, ToggleWidgetConfig } from './toggle';
8
+ import { FlowNode } from "../node";
9
+ import { ImageWidget, ImageWidgetConfig } from "./image";
10
+ import { TextWidget, TextWidgetConfig } from "./text";
11
+
12
+ export type WidgetBuilder = (node: FlowNode, confg?: any) => Widget;
13
+
14
+ class WidgetFactory {
15
+
16
+ #registeredWidgets: Map<string, WidgetBuilder>
17
+
18
+ constructor() {
19
+ this.#registeredWidgets = new Map<string, WidgetBuilder>();
20
+ }
21
+
22
+ register(widgetType: string, builder: WidgetBuilder): void {
23
+ this.#registeredWidgets.set(widgetType, builder);
24
+ }
25
+
26
+ create(node: FlowNode, widgetType: string, config: any): Widget {
27
+ const builder = this.#registeredWidgets.get(widgetType)
28
+ if (builder === undefined) {
29
+ throw new Error("no builder registered for widget: " + widgetType);
30
+ }
31
+ return builder(node, config);
32
+ }
33
+ }
34
+
35
+ const GlobalWidgetFactory = new WidgetFactory();
36
+
37
+ GlobalWidgetFactory.register("button", (node: FlowNode, config?: ButtonWidgetConfig) => new ButtonWidget(config));
38
+ GlobalWidgetFactory.register("number", (node: FlowNode, config?: NumberWidgetConfig) => new NumberWidget(node, config));
39
+ GlobalWidgetFactory.register("color", (node: FlowNode, config?: ColorWidgetConfig) => new ColorWidget(node, config));
40
+ GlobalWidgetFactory.register("slider", (node: FlowNode, config?: SliderWidgetConfig) => new SliderWidget(node, config));
41
+ GlobalWidgetFactory.register("string", (node: FlowNode, config?: StringWidgetConfig) => new StringWidget(node, config));
42
+ GlobalWidgetFactory.register("toggle", (node: FlowNode, config?: ToggleWidgetConfig) => new ToggleWidget(node, config));
43
+ GlobalWidgetFactory.register("image", (node: FlowNode, config?: ImageWidgetConfig) => new ImageWidget(config));
44
+ GlobalWidgetFactory.register("text", (node: FlowNode, config?: TextWidgetConfig) => new TextWidget(node, config));
45
+
46
+ export { GlobalWidgetFactory };
@@ -0,0 +1,163 @@
1
+ import { Box } from "../types/box";
2
+ import { CopyVector2, Vector2 } from "../types/vector2";
3
+
4
+ const margin = 15;
5
+
6
+ export interface ImageWidgetConfig {
7
+ image?: string;
8
+ maxWidth?: number
9
+ maxHeight?: number
10
+ blob?: Blob;
11
+ }
12
+
13
+ /**
14
+ * constructor(lightNode, nodeManager, id, parameterData, app) {
15
+ this.lightNode = lightNode;
16
+ this.id = id;
17
+ this.app = app;
18
+ this.lightNode.title = parameterData.name;
19
+
20
+ this.lightNode.onDropFile = (file) => {
21
+ // console.log(file)
22
+ var reader = new FileReader();
23
+ reader.onload = (evt) => {
24
+ console.log(evt.target.result)
25
+ nodeManager.nodeParameterChanged({
26
+ id: id,
27
+ data: evt.target.result,
28
+ binary: true
29
+ });
30
+ }
31
+ reader.readAsArrayBuffer(file);
32
+
33
+ const url = URL.createObjectURL(file);
34
+ // this.loadImage(this._url, function (img) {
35
+ // that.size[1] = (img.height / img.width) * that.size[0];
36
+ // });
37
+ this.loadImgFromURL(url);
38
+ }
39
+ }
40
+
41
+ loadImgFromURL(url) {
42
+ const img = document.createElement("img");
43
+ img.src = url;
44
+ img.onload = () => {
45
+ // if (callback) {
46
+ // callback(this);
47
+ // }
48
+ // console.log("Image loaded, size: " + img.width + "x" + img.height);
49
+ // this.dirty = true;
50
+ // that.boxcolor = "#9F9";f
51
+ // that.setDirtyCanvas(true);
52
+ this.lightNode.widgets[0].image = img
53
+ this.lightNode.setSize(this.lightNode.computeSize());
54
+ };
55
+ img.onerror = () => {
56
+ console.log("error loading the image:" + url);
57
+ }
58
+ }
59
+
60
+ update(parameterData) {
61
+ console.log("image parameter", parameterData)
62
+ const curVal = parameterData.currentValue;
63
+ this.app.RequestManager.getParameterValue(this.id, (response) => {
64
+ const url = URL.createObjectURL(response);
65
+ this.loadImgFromURL(url)
66
+ })
67
+ }
68
+ */
69
+
70
+ export class ImageWidget {
71
+
72
+ #url: string | undefined;
73
+
74
+ #image: HTMLImageElement | undefined;
75
+
76
+ #maxWidth: number;
77
+
78
+ #maxHeight: number;
79
+
80
+ constructor(config?: ImageWidgetConfig) {
81
+ this.#maxWidth = config?.maxWidth === undefined ? 150 : config?.maxWidth;
82
+ this.#maxHeight = config?.maxHeight === undefined ? 150 : config?.maxHeight;
83
+ if (config?.image) {
84
+ this.SetUrl(config?.image);
85
+ } else if (config?.blob) {
86
+ this.SetBlob(config?.blob);
87
+ }
88
+ }
89
+
90
+ SetBlob(blob: Blob) {
91
+ const urlCreator = window.URL || window.webkitURL;
92
+ const imageUrl = urlCreator.createObjectURL(blob);
93
+ this.SetUrl(imageUrl);
94
+ }
95
+
96
+ SetUrl(url: string) {
97
+ this.#image = undefined;
98
+ this.#url = url;
99
+
100
+ const img = document.createElement("img");
101
+ img.src = url;
102
+ img.onload = () => {
103
+ this.#image = img;
104
+ };
105
+ img.onerror = (event) => {
106
+ console.log("error loading image:", url, event);
107
+ }
108
+ }
109
+
110
+ Size(): Vector2 {
111
+ if (this.#image === undefined) {
112
+ return { "x": 0, "y": 0 }
113
+ }
114
+
115
+ let adjust = 1;
116
+ if (this.#image.width > this.#maxWidth) {
117
+ adjust = this.#maxWidth / this.#image.width
118
+ }
119
+
120
+ if (this.#image.height > this.#maxHeight) {
121
+ let heightAdjust = this.#maxHeight / this.#image.height
122
+ if (heightAdjust < adjust) {
123
+ adjust = heightAdjust;
124
+ }
125
+ }
126
+
127
+ return {
128
+ "x": adjust * this.#image.width,
129
+ "y": adjust * this.#image.height
130
+ }
131
+ }
132
+
133
+ ClickStart(): void {
134
+ }
135
+
136
+ ClickEnd(): void {
137
+ }
138
+
139
+ Draw(ctx: CanvasRenderingContext2D, position: Vector2, scale: number, mousePosition: Vector2 | undefined): Box {
140
+ const size = this.Size();
141
+ const box: Box = {
142
+ Position: { x: 0, y: 0 },
143
+ Size: {
144
+ x: size.x * scale,
145
+ y: size.y * scale,
146
+ }
147
+ }
148
+ CopyVector2(box.Position, position);
149
+
150
+ if (!this.#image) {
151
+ return box;
152
+ }
153
+
154
+ ctx.drawImage(
155
+ this.#image,
156
+ position.x,
157
+ position.y,
158
+ box.Size.x,
159
+ box.Size.y
160
+ );
161
+ return box;
162
+ }
163
+ }