@kabel-project/kabel 1.0.7 → 1.0.8
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.
- package/dist/comment-renderer/renderer.d.ts +32 -0
- package/dist/comment-renderer/renderer.d.ts.map +1 -0
- package/dist/controllers/base.d.ts +39 -0
- package/dist/controllers/base.d.ts.map +1 -0
- package/dist/controllers/wasd.d.ts +33 -0
- package/dist/controllers/wasd.d.ts.map +1 -0
- package/dist/events/comment-drag-handle.d.ts +2 -0
- package/dist/events/comment-drag-handle.d.ts.map +1 -0
- package/dist/events/comment-input.d.ts +4 -0
- package/dist/events/comment-input.d.ts.map +1 -0
- package/dist/events/connection-line.d.ts +2 -0
- package/dist/events/connection-line.d.ts.map +1 -0
- package/dist/events/connector.d.ts +2 -0
- package/dist/events/connector.d.ts.map +1 -0
- package/dist/events/draggable.d.ts +2 -0
- package/dist/events/draggable.d.ts.map +1 -0
- package/{events/events.ts → dist/events/events.d.ts} +8 -7
- package/dist/events/events.d.ts.map +1 -0
- package/dist/events/input-box.d.ts +2 -0
- package/dist/events/input-box.d.ts.map +1 -0
- package/dist/events/node-x-btn.d.ts +2 -0
- package/dist/events/node-x-btn.d.ts.map +1 -0
- package/dist/kabel.js +2 -0
- package/dist/kabel.js.map +1 -0
- package/dist/renderers/apollo/apollo.d.ts +12 -0
- package/dist/renderers/apollo/apollo.d.ts.map +1 -0
- package/dist/renderers/apollo/constants.d.ts +21 -0
- package/dist/renderers/apollo/constants.d.ts.map +1 -0
- package/dist/renderers/apollo/renderer.d.ts +97 -0
- package/dist/renderers/apollo/renderer.d.ts.map +1 -0
- package/{renderers/atlas/atlas.ts → dist/renderers/atlas/atlas.d.ts} +6 -15
- package/dist/renderers/atlas/atlas.d.ts.map +1 -0
- package/dist/renderers/constants.d.ts +51 -0
- package/dist/renderers/constants.d.ts.map +1 -0
- package/dist/renderers/renderer.d.ts +470 -0
- package/dist/renderers/renderer.d.ts.map +1 -0
- package/dist/renderers/representer-node.d.ts +27 -0
- package/dist/renderers/representer-node.d.ts.map +1 -0
- package/dist/renderers/representer.d.ts +13 -0
- package/dist/renderers/representer.d.ts.map +1 -0
- package/dist/src/category.d.ts +48 -0
- package/dist/src/category.d.ts.map +1 -0
- package/{src/colors.ts → dist/src/colors.d.ts} +21 -20
- package/dist/src/colors.d.ts.map +1 -0
- package/dist/src/comment.d.ts +88 -0
- package/dist/src/comment.d.ts.map +1 -0
- package/dist/src/connection.d.ts +52 -0
- package/dist/src/connection.d.ts.map +1 -0
- package/dist/src/context-menu.d.ts +76 -0
- package/dist/src/context-menu.d.ts.map +1 -0
- package/dist/src/coordinates.d.ts +52 -0
- package/dist/src/coordinates.d.ts.map +1 -0
- package/dist/src/core.d.ts +153 -0
- package/dist/src/core.d.ts.map +1 -0
- package/dist/src/ctx-menu-registry.d.ts +22 -0
- package/dist/src/ctx-menu-registry.d.ts.map +1 -0
- package/dist/src/dropdown-menu.d.ts +87 -0
- package/dist/src/dropdown-menu.d.ts.map +1 -0
- package/dist/src/field.d.ts +305 -0
- package/dist/src/field.d.ts.map +1 -0
- package/dist/src/flyout.d.ts +41 -0
- package/dist/src/flyout.d.ts.map +1 -0
- package/dist/src/fonts-manager.d.ts +6 -0
- package/dist/src/fonts-manager.d.ts.map +1 -0
- package/dist/src/grid.d.ts +60 -0
- package/dist/src/grid.d.ts.map +1 -0
- package/dist/src/headless-node.d.ts +11 -0
- package/dist/src/headless-node.d.ts.map +1 -0
- package/dist/src/index.d.ts +38 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/inject-headless.d.ts +4 -0
- package/dist/src/inject-headless.d.ts.map +1 -0
- package/{src/inject.ts → dist/src/inject.d.ts} +142 -213
- package/dist/src/inject.d.ts.map +1 -0
- package/{src/main-workspace.ts → dist/src/main-workspace.d.ts} +31 -51
- package/dist/src/main-workspace.d.ts.map +1 -0
- package/dist/src/mutator.d.ts +2 -0
- package/dist/src/mutator.d.ts.map +1 -0
- package/{src/node-types.ts → dist/src/node-types.d.ts} +26 -27
- package/dist/src/node-types.d.ts.map +1 -0
- package/dist/src/nodesvg.d.ts +266 -0
- package/dist/src/nodesvg.d.ts.map +1 -0
- package/{src/prototypes.ts → dist/src/prototypes.d.ts} +10 -9
- package/dist/src/prototypes.d.ts.map +1 -0
- package/{src/renderer-map.ts → dist/src/renderer-map.d.ts} +51 -86
- package/dist/src/renderer-map.d.ts.map +1 -0
- package/dist/src/toolbox.d.ts +51 -0
- package/dist/src/toolbox.d.ts.map +1 -0
- package/{src/types.ts → dist/src/types.d.ts} +159 -205
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/undo-redo.d.ts +15 -0
- package/dist/src/undo-redo.d.ts.map +1 -0
- package/{src/visual-types.ts → dist/src/visual-types.d.ts} +34 -29
- package/dist/src/visual-types.d.ts.map +1 -0
- package/dist/src/widget-prototypes.d.ts +10 -0
- package/dist/src/widget-prototypes.d.ts.map +1 -0
- package/dist/src/widget.d.ts +62 -0
- package/dist/src/widget.d.ts.map +1 -0
- package/{src/workspace-coords.ts → dist/src/workspace-coords.d.ts} +10 -14
- package/dist/src/workspace-coords.d.ts.map +1 -0
- package/dist/src/workspace-svg.d.ts +371 -0
- package/dist/src/workspace-svg.d.ts.map +1 -0
- package/dist/src/workspace.d.ts +50 -0
- package/dist/src/workspace.d.ts.map +1 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/default.d.ts +4 -0
- package/dist/themes/default.d.ts.map +1 -0
- package/dist/themes/themes.d.ts +6 -0
- package/dist/themes/themes.d.ts.map +1 -0
- package/dist/util/emitter.d.ts +10 -0
- package/dist/util/emitter.d.ts.map +1 -0
- package/dist/util/env.d.ts +7 -0
- package/dist/util/env.d.ts.map +1 -0
- package/{util/escape-html.ts → dist/util/escape-html.d.ts} +16 -22
- package/dist/util/escape-html.d.ts.map +1 -0
- package/dist/util/eventer.d.ts +29 -0
- package/dist/util/eventer.d.ts.map +1 -0
- package/dist/util/has-prop.d.ts +2 -0
- package/dist/util/has-prop.d.ts.map +1 -0
- package/dist/util/parse-color.d.ts +6 -0
- package/dist/util/parse-color.d.ts.map +1 -0
- package/dist/util/path.d.ts +25 -0
- package/dist/util/path.d.ts.map +1 -0
- package/dist/util/styler.d.ts +12 -0
- package/dist/util/styler.d.ts.map +1 -0
- package/dist/util/uid.d.ts +42 -0
- package/dist/util/uid.d.ts.map +1 -0
- package/{util/unescape-html.ts → dist/util/unescape-html.d.ts} +16 -22
- package/dist/util/unescape-html.d.ts.map +1 -0
- package/dist/util/user-state.d.ts +37 -0
- package/dist/util/user-state.d.ts.map +1 -0
- package/{util/wait-anim-frames.ts → dist/util/wait-anim-frames.d.ts} +11 -24
- package/dist/util/wait-anim-frames.d.ts.map +1 -0
- package/dist/util/window-listeners.d.ts +8 -0
- package/dist/util/window-listeners.d.ts.map +1 -0
- package/package.json +4 -1
- package/(1.0.7)kabel.md +0 -18
- package/_READ_ME_MEDIA_/documentation/docs.md +0 -293
- package/_READ_ME_MEDIA_/workspace.png +0 -0
- package/comment-renderer/renderer.ts +0 -228
- package/controllers/base.ts +0 -186
- package/controllers/wasd.ts +0 -132
- package/docs/README.md +0 -98
- package/docs/_media/docs.md +0 -289
- package/docs/_media/workspace.png +0 -0
- package/docs/classes/CommentModel.md +0 -271
- package/docs/classes/CommentRenderer.md +0 -457
- package/docs/classes/ConnectableField.md +0 -597
- package/docs/classes/Connection.md +0 -191
- package/docs/classes/ContextMenuHTML.md +0 -163
- package/docs/classes/Coordinates.md +0 -187
- package/docs/classes/DropdownContainer.md +0 -300
- package/docs/classes/DummyField.md +0 -393
- package/docs/classes/Eventer.md +0 -185
- package/docs/classes/Field.md +0 -461
- package/docs/classes/InjectMsg.md +0 -85
- package/docs/classes/NodeSvg.md +0 -1011
- package/docs/classes/NumberField.md +0 -559
- package/docs/classes/OptConnectField.md +0 -624
- package/docs/classes/Renderer.md +0 -1636
- package/docs/classes/RendererConstants.md +0 -343
- package/docs/classes/Representer.md +0 -95
- package/docs/classes/RepresenterNode.md +0 -175
- package/docs/classes/TextField.md +0 -559
- package/docs/classes/Toolbox.md +0 -172
- package/docs/classes/WASDController.md +0 -616
- package/docs/classes/Widget.md +0 -195
- package/docs/classes/WorkspaceController.md +0 -385
- package/docs/classes/WorkspaceCoords.md +0 -218
- package/docs/classes/WorkspaceSvg.md +0 -1380
- package/docs/functions/clearMainWorkspace.md +0 -20
- package/docs/functions/getMainWorkspace.md +0 -19
- package/docs/functions/inject.md +0 -35
- package/docs/functions/setMainWorkspace.md +0 -28
- package/docs/globals.md +0 -95
- package/docs/interfaces/ColorStyle.md +0 -43
- package/docs/interfaces/ConnectorToFrom.md +0 -57
- package/docs/interfaces/DrawState.md +0 -81
- package/docs/interfaces/FieldConnectionData.md +0 -25
- package/docs/interfaces/FieldOptions.md +0 -63
- package/docs/interfaces/FieldRawBoxData.md +0 -25
- package/docs/interfaces/FieldVisualInfo.md +0 -65
- package/docs/interfaces/GridOptions.md +0 -61
- package/docs/interfaces/InjectOptions.md +0 -133
- package/docs/interfaces/InputFieldJson.md +0 -50
- package/docs/interfaces/KabelCommentRendering.md +0 -31
- package/docs/interfaces/KabelInterface.md +0 -469
- package/docs/interfaces/KabelNodeRendering.md +0 -77
- package/docs/interfaces/KabelUIX.md +0 -105
- package/docs/interfaces/KabelUtils.md +0 -215
- package/docs/interfaces/NodeEvents.md +0 -42
- package/docs/interfaces/NodeJson.md +0 -104
- package/docs/interfaces/NodePrototype.md +0 -82
- package/docs/interfaces/RegisteredEl.md +0 -53
- package/docs/interfaces/SerializedNode.md +0 -128
- package/docs/interfaces/TblxCategoryStruct.md +0 -41
- package/docs/interfaces/TblxFieldStruct.md +0 -28
- package/docs/interfaces/TblxNodeStruct.md +0 -35
- package/docs/interfaces/WidgetOptions.md +0 -115
- package/docs/interfaces/WidgetPrototypeList.md +0 -15
- package/docs/type-aliases/AnyField.md +0 -13
- package/docs/type-aliases/AnyFieldCls.md +0 -13
- package/docs/type-aliases/Color.md +0 -13
- package/docs/type-aliases/Connectable.md +0 -13
- package/docs/type-aliases/EventArgs.md +0 -11
- package/docs/type-aliases/EventSetupFn.md +0 -25
- package/docs/type-aliases/Hex.md +0 -13
- package/docs/type-aliases/RGBObject.md +0 -37
- package/docs/type-aliases/RGBString.md +0 -13
- package/docs/type-aliases/RGBTuple.md +0 -13
- package/docs/type-aliases/TblxObjStruct.md +0 -52
- package/docs/variables/CategoryColors.md +0 -29
- package/docs/variables/FieldMap.md +0 -41
- package/docs/variables/NodePrototypes.md +0 -18
- package/docs/variables/default.md +0 -11
- package/events/comment-drag-handle.ts +0 -61
- package/events/comment-input.ts +0 -291
- package/events/connection-line.ts +0 -68
- package/events/connector.ts +0 -116
- package/events/draggable.ts +0 -119
- package/events/input-box.ts +0 -213
- package/events/node-x-btn.ts +0 -25
- package/index.d.ts +0 -4
- package/renderers/apollo/apollo.ts +0 -21
- package/renderers/apollo/constants.ts +0 -40
- package/renderers/apollo/renderer.ts +0 -331
- package/renderers/constants.ts +0 -87
- package/renderers/renderer.ts +0 -1288
- package/renderers/representer-node.ts +0 -52
- package/renderers/representer.ts +0 -25
- package/src/category.ts +0 -107
- package/src/comment.ts +0 -142
- package/src/connection.ts +0 -114
- package/src/context-menu.ts +0 -194
- package/src/coordinates.ts +0 -74
- package/src/core.ts +0 -202
- package/src/ctx-menu-registry.ts +0 -143
- package/src/dropdown-menu.ts +0 -215
- package/src/field.ts +0 -595
- package/src/flyout.ts +0 -165
- package/src/fonts-manager.ts +0 -38
- package/src/grid.ts +0 -162
- package/src/headless-node.ts +0 -27
- package/src/index.ts +0 -115
- package/src/inject-headless.ts +0 -18
- package/src/mutator.ts +0 -40
- package/src/nodesvg.ts +0 -756
- package/src/styles.css +0 -224
- package/src/toolbox.ts +0 -125
- package/src/undo-redo.ts +0 -87
- package/src/widget-prototypes.ts +0 -11
- package/src/widget.ts +0 -139
- package/src/workspace-svg.ts +0 -736
- package/src/workspace.ts +0 -155
- package/test-server.js +0 -61
- package/themes/dark.ts +0 -32
- package/themes/default.ts +0 -28
- package/themes/themes.ts +0 -9
- package/tsconfig.json +0 -25
- package/typedoc.json +0 -10
- package/util/emitter.ts +0 -33
- package/util/env.ts +0 -11
- package/util/eventer.ts +0 -108
- package/util/has-prop.ts +0 -4
- package/util/parse-color.ts +0 -42
- package/util/path.ts +0 -99
- package/util/styler.ts +0 -41
- package/util/uid.ts +0 -184
- package/util/user-state.ts +0 -68
- package/util/window-listeners.ts +0 -62
- package/webpack.config.js +0 -80
- /package/{docs/_media → dist}/index.html +0 -0
package/renderers/renderer.ts
DELETED
|
@@ -1,1288 +0,0 @@
|
|
|
1
|
-
import RendererConstants from "./constants";
|
|
2
|
-
import WorkspaceSvg from '../src/workspace-svg';
|
|
3
|
-
import NodeSvg from '../src/nodesvg';
|
|
4
|
-
import * as Path from '../util/path';
|
|
5
|
-
import { parseColor } from "../util/parse-color";
|
|
6
|
-
import { G, Path as SvgPath, Svg, StrokeData, Element, List, Rect } from "@svgdotjs/svg.js";
|
|
7
|
-
import { Color, ColorStyle, Hex } from "../src/visual-types";
|
|
8
|
-
import Field, { AnyField, ConnectableField, DummyField, FieldRawBoxData, OptConnectField } from "../src/field";
|
|
9
|
-
import eventer from '../util/eventer';
|
|
10
|
-
import Connection, { Connectable } from "../src/connection";
|
|
11
|
-
import escapeAttr from '../util/escape-html';
|
|
12
|
-
import unescapeAttr from '../util/unescape-html';
|
|
13
|
-
import CommentRenderer from "../comment-renderer/renderer";
|
|
14
|
-
import { FieldVisualInfo } from "../src/field";
|
|
15
|
-
import { RepresenterNode } from "./representer-node";
|
|
16
|
-
import type Representer from "./representer";
|
|
17
|
-
|
|
18
|
-
export interface ConnectorToFrom {
|
|
19
|
-
to: Connection,
|
|
20
|
-
from: Connection,
|
|
21
|
-
fromCircle?: SvgPath,
|
|
22
|
-
toCircle?: SvgPath,
|
|
23
|
-
originConn: Connection,
|
|
24
|
-
originCircle: SvgPath
|
|
25
|
-
}
|
|
26
|
-
export interface DrawState {
|
|
27
|
-
id: string;
|
|
28
|
-
shadow?: SvgPath;
|
|
29
|
-
topbar?: SvgPath | null;
|
|
30
|
-
bg?: SvgPath | null;
|
|
31
|
-
group?: G | null;
|
|
32
|
-
fieldCol?: G | null;
|
|
33
|
-
fieldPosY?: number | null; // starts under topbar, goes down by field height each time one is drawn. This determines position
|
|
34
|
-
xButton?: G;
|
|
35
|
-
pendingConnections: ConnectorToFrom[]
|
|
36
|
-
}
|
|
37
|
-
export interface NodeMeasurements {
|
|
38
|
-
width: number;
|
|
39
|
-
height: number;
|
|
40
|
-
fields: { width: number; height: number }[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
class Renderer {
|
|
44
|
-
/**
|
|
45
|
-
* Set of constants the renderer uses for drawing nodes.
|
|
46
|
-
*/
|
|
47
|
-
_constants!: RendererConstants;
|
|
48
|
-
/**
|
|
49
|
-
* The current node being rendered.
|
|
50
|
-
*/
|
|
51
|
-
_currentNode: NodeSvg | null;
|
|
52
|
-
/**
|
|
53
|
-
* The SVG group element for the current node.
|
|
54
|
-
*/
|
|
55
|
-
_nodeGroup: G | null;
|
|
56
|
-
/**
|
|
57
|
-
* The current drawing state for the node.
|
|
58
|
-
*/
|
|
59
|
-
_nodeDraw: DrawState | null;
|
|
60
|
-
/**
|
|
61
|
-
* The workspace this renderer is associated with.
|
|
62
|
-
*/
|
|
63
|
-
_ws: WorkspaceSvg;
|
|
64
|
-
/**
|
|
65
|
-
* Array of stored draw states for rendered nodes.
|
|
66
|
-
*/
|
|
67
|
-
_drawStates: DrawState[];
|
|
68
|
-
/**
|
|
69
|
-
* Comment renderer instance used for rendering comments.
|
|
70
|
-
*/
|
|
71
|
-
_commentDrawer!: CommentRenderer;
|
|
72
|
-
/**
|
|
73
|
-
* Representer instance used for building node representations.
|
|
74
|
-
*/
|
|
75
|
-
representer!: Representer;
|
|
76
|
-
/**
|
|
77
|
-
* Constant overrides provided during renderer instantiation.
|
|
78
|
-
*/
|
|
79
|
-
constantOverrides: Partial<RendererConstants>;
|
|
80
|
-
/**
|
|
81
|
-
* Tag used for node group elements in the SVG.
|
|
82
|
-
*/
|
|
83
|
-
static get NODE_G_TAG() {
|
|
84
|
-
return 'AtlasNodeSVG';
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Tag used for renderer elements in the SVG.
|
|
88
|
-
*/
|
|
89
|
-
static get ELEMENT_TAG() {
|
|
90
|
-
return 'AtlasElement'
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Tag used for connection line elements in the SVG.
|
|
94
|
-
*/
|
|
95
|
-
static get CONN_LINE_TAG() {
|
|
96
|
-
return 'AtlasConnectionLine';
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Tag used for connector elements in the SVG.
|
|
100
|
-
*/
|
|
101
|
-
static get CONNECTOR_TAG() {
|
|
102
|
-
return 'AtlasConnectionBubble';
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Tag used for line X mark elements in the SVG.
|
|
106
|
-
*/
|
|
107
|
-
static get LINE_X_MARK_TAG() {
|
|
108
|
-
return 'AtlasLineXMark';
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Tag used for background pattern elements in the SVG. This is constant, do not modify it in subclasses.
|
|
112
|
-
*/
|
|
113
|
-
static get BACKGROUND_PATTERN() {
|
|
114
|
-
/** ! CONSTANT ! DO NOT CHANGE ! CONSTANT ! */
|
|
115
|
-
return 'WorkspaceBgPattern'
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Name of the renderer.
|
|
119
|
-
*/
|
|
120
|
-
static get NAME() {
|
|
121
|
-
return 'atlas'; // default is called atlas.
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Constructor for the Renderer class.
|
|
125
|
-
* @param workspace - The workspace associated with the renderer.
|
|
126
|
-
* @param overrides - Optional constant overrides for the renderer.
|
|
127
|
-
*/
|
|
128
|
-
constructor(workspace: WorkspaceSvg, overrides: Partial<RendererConstants> = {}) {
|
|
129
|
-
this._ws = workspace;
|
|
130
|
-
this._currentNode = null;
|
|
131
|
-
this._nodeGroup = null;
|
|
132
|
-
this._nodeDraw = null;
|
|
133
|
-
this._drawStates = [];
|
|
134
|
-
this.constantOverrides = overrides;
|
|
135
|
-
this.init();
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Initializes the renderer by setting up the comment renderer, representer, and constants.
|
|
139
|
-
*/
|
|
140
|
-
init() {
|
|
141
|
-
this.initCommentRenderer();
|
|
142
|
-
this.initRepresenter();
|
|
143
|
-
this.initConstants();
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Initializes the renderer constants with any provided overrides.
|
|
147
|
-
*/
|
|
148
|
-
initConstants() {
|
|
149
|
-
this._constants = new RendererConstants(this.constantOverrides);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Initializes the representer for the renderer.
|
|
153
|
-
*/
|
|
154
|
-
initRepresenter() {
|
|
155
|
-
const Representer = (require('./representer').default);
|
|
156
|
-
this.representer = new Representer();
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Initializes the comment renderer for the workspace.
|
|
160
|
-
*/
|
|
161
|
-
initCommentRenderer() {
|
|
162
|
-
this._commentDrawer = new CommentRenderer(this.getWs());
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Sets the connection to be processed.
|
|
166
|
-
* @param c - The connection to set.
|
|
167
|
-
*/
|
|
168
|
-
setConnect(c: ConnectorToFrom) {
|
|
169
|
-
this.state?.pendingConnections?.push?.(c);
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Sets the renderer constants.
|
|
173
|
-
* @param c - Partial constants to override.
|
|
174
|
-
* @returns The updated constants.
|
|
175
|
-
*/
|
|
176
|
-
setConstants(c: Partial<RendererConstants> = {}) {
|
|
177
|
-
return Object.assign(this._constants, c);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Gets the renderer constants, merging with node style if applicable.
|
|
181
|
-
*/
|
|
182
|
-
get constants(): RendererConstants {
|
|
183
|
-
if (!this.node) return this._constants;
|
|
184
|
-
|
|
185
|
-
const { primary, secondary, tertiary, ...restColors } = this.node.colors;
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
...this._constants,
|
|
189
|
-
...restColors
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Sets the renderer constants.
|
|
194
|
-
*/
|
|
195
|
-
set constants(c: Partial<RendererConstants>) {
|
|
196
|
-
this.setConstants(c);
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Gets the current node being rendered.
|
|
200
|
-
*/
|
|
201
|
-
get node() {
|
|
202
|
-
return this._currentNode;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Gets the SVG.js instance from the workspace.
|
|
206
|
-
*/
|
|
207
|
-
get svg(): Svg {
|
|
208
|
-
return this.getWs().svg; // Svg.js instance
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Gets the current drawing state.
|
|
212
|
-
*/
|
|
213
|
-
get state(): null | undefined | DrawState {
|
|
214
|
-
return this._nodeDraw;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Gets the workspace associated with the renderer.
|
|
218
|
-
* @returns The workspace instance.
|
|
219
|
-
*/
|
|
220
|
-
getWs() {
|
|
221
|
-
return this._ws;
|
|
222
|
-
}
|
|
223
|
-
// MEASURING -
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Gets the base measurements for a node.
|
|
227
|
-
* @returns The base width and height of the node.
|
|
228
|
-
*/
|
|
229
|
-
getNodeBaseMeasurements() {
|
|
230
|
-
const c = this.constants;
|
|
231
|
-
return {
|
|
232
|
-
width: c.NODE_BASE_WIDTH,
|
|
233
|
-
height: c.NODE_BASE_HEIGHT
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Measures the width of the given text.
|
|
238
|
-
* @param text - The text to measure.
|
|
239
|
-
* @param fontSize - The font size to use.
|
|
240
|
-
* @param fontFamily - The font family to use.
|
|
241
|
-
* @returns The width of the text.
|
|
242
|
-
*/
|
|
243
|
-
measureTextWidth(text: string, fontSize?: number, fontFamily?: string): number {
|
|
244
|
-
const c = this.constants;
|
|
245
|
-
|
|
246
|
-
// fallback in case SVG is not ready
|
|
247
|
-
if (!this.svg) return text.length * (fontSize ?? c.FONT_SIZE) * 0.6;
|
|
248
|
-
|
|
249
|
-
const txt = this.svg.text(text)
|
|
250
|
-
.font({
|
|
251
|
-
family: fontFamily ?? c.FONT_FAMILY,
|
|
252
|
-
size: fontSize ?? c.FONT_SIZE,
|
|
253
|
-
anchor: 'start'
|
|
254
|
-
})
|
|
255
|
-
.opacity(0); // hide it
|
|
256
|
-
|
|
257
|
-
const width = txt.bbox().width;
|
|
258
|
-
txt.remove(); // clean up
|
|
259
|
-
return width;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Measures the height of the given text.
|
|
263
|
-
* @param text - The text to measure.
|
|
264
|
-
* @param fontSize - The font size to use.
|
|
265
|
-
* @param fontFamily - The font family to use.
|
|
266
|
-
* @returns The height of the text.
|
|
267
|
-
*/
|
|
268
|
-
measureTextHeight(text: string, fontSize?: number, fontFamily?: string): number {
|
|
269
|
-
const c = this.constants;
|
|
270
|
-
|
|
271
|
-
// fallback in case SVG is not ready
|
|
272
|
-
if (!this.svg) return (fontSize ?? c.FONT_SIZE);
|
|
273
|
-
|
|
274
|
-
const txt = this.svg.text(text)
|
|
275
|
-
.font({
|
|
276
|
-
family: fontFamily ?? c.FONT_FAMILY,
|
|
277
|
-
size: fontSize ?? c.FONT_SIZE,
|
|
278
|
-
anchor: 'start'
|
|
279
|
-
})
|
|
280
|
-
.opacity(0); // hide it
|
|
281
|
-
|
|
282
|
-
const height = txt.bbox().height;
|
|
283
|
-
txt.remove(); // clean up
|
|
284
|
-
return height;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Measures the dimensions of a raw input field.
|
|
288
|
-
* @param text - The text content of the raw field.
|
|
289
|
-
* @returns The width and height of the raw field.
|
|
290
|
-
*/
|
|
291
|
-
measureRawField(text: string = "") {
|
|
292
|
-
const c = this.constants;
|
|
293
|
-
const textW = this.measureTextWidth(text);
|
|
294
|
-
const width = Math.max(c.FIELD_RAW_BASE_WIDTH, textW + c.INPUT_BOX_PADDING * 2);
|
|
295
|
-
const height = c.FIELD_RAW_BASE_HEIGHT;
|
|
296
|
-
return { width, height };
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Measures the label of a field.
|
|
300
|
-
* @param field - The field to measure the label for.
|
|
301
|
-
* @returns The width and height of the label.
|
|
302
|
-
*/
|
|
303
|
-
measureLabel(field: AnyField): { width: number, height: number } {
|
|
304
|
-
const c = this.constants;
|
|
305
|
-
const label = field.getLabel?.();
|
|
306
|
-
if (!label) return { width: 0, height: 0 };
|
|
307
|
-
|
|
308
|
-
const width = this.measureTextWidth(label);
|
|
309
|
-
const height = this.measureTextHeight(label);
|
|
310
|
-
|
|
311
|
-
return { width, height };
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Measures the raw input of a field.
|
|
315
|
-
* @param field - The field to measure the raw input for.
|
|
316
|
-
* @returns The width and height of the raw input.
|
|
317
|
-
*/
|
|
318
|
-
measureRaw(field: AnyField): { width: number, height: number } {
|
|
319
|
-
if (!field.hasRaw()) return { width: 0, height: 0 };
|
|
320
|
-
|
|
321
|
-
const c = this.constants;
|
|
322
|
-
const raw = this.measureRawField(field.getValue?.() ?? "");
|
|
323
|
-
return {
|
|
324
|
-
width: raw.width,
|
|
325
|
-
height: raw.height
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Measures the custom editor of a field.
|
|
330
|
-
* @param field - The field to measure the custom editor for.
|
|
331
|
-
* @returns The width and height of the custom editor.
|
|
332
|
-
*/
|
|
333
|
-
measureCustom(field: AnyField): { width: number, height: number } {
|
|
334
|
-
if (!field.isCustomEditor()) return { width: 0, height: 0 };
|
|
335
|
-
|
|
336
|
-
const c = this.constants;
|
|
337
|
-
const m = field.measureMyself();
|
|
338
|
-
if (!m) return { width: 0, height: 0 };
|
|
339
|
-
|
|
340
|
-
let width = m.width as number;
|
|
341
|
-
let height = m.height as number;
|
|
342
|
-
|
|
343
|
-
return { width, height };
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Gets the padding to apply when measuring a field.
|
|
347
|
-
* @returns The width and height padding for the field.
|
|
348
|
-
*/
|
|
349
|
-
getFieldMeasurementPadding() {
|
|
350
|
-
return { width: this.constants.FIELD_SPACEX, height: 0 }
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Measures the overall dimensions of a field.
|
|
354
|
-
* @param field - The field to measure.
|
|
355
|
-
* @returns The width and height of the field.
|
|
356
|
-
*/
|
|
357
|
-
measureField(field: AnyField) {
|
|
358
|
-
// parts of the measurement, correct order matters.
|
|
359
|
-
const parts = [
|
|
360
|
-
this.getFieldMeasurementPadding(),
|
|
361
|
-
this.measureLabel(field),
|
|
362
|
-
this.measureRaw(field),
|
|
363
|
-
this.measureCustom(field),
|
|
364
|
-
this.getFieldMeasurementPadding()
|
|
365
|
-
];
|
|
366
|
-
|
|
367
|
-
let width = 0, height = 0;
|
|
368
|
-
for (const { width: w, height: h } of parts) {
|
|
369
|
-
width += w;
|
|
370
|
-
height = Math.max(height, h);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return { width, height };
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Measures the overall dimensions of the current node.
|
|
377
|
-
* @returns The measurements of the node including width, height, and field dimensions.
|
|
378
|
-
*/
|
|
379
|
-
measureNodeDimensions(): NodeMeasurements | void | null {
|
|
380
|
-
if (!this.node) return;
|
|
381
|
-
|
|
382
|
-
const c = this.constants;
|
|
383
|
-
|
|
384
|
-
const base = this.measureBaseAndLabel();
|
|
385
|
-
|
|
386
|
-
const fieldResult = this.measureFields(
|
|
387
|
-
c.TOPBAR_HEIGHT + c.FIELD_SPACEY,
|
|
388
|
-
base.width,
|
|
389
|
-
base.height
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
width: fieldResult.width,
|
|
394
|
-
height: fieldResult.height + c.FOOTER_HEIGHT,
|
|
395
|
-
fields: fieldResult.fields
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Measures all fields of the current node.
|
|
400
|
-
* @param startY The starting Y position for the fields.
|
|
401
|
-
* @param startWidth The starting width of the node.
|
|
402
|
-
* @param startHeight The starting height of the node.
|
|
403
|
-
* @returns The width, height, and field dimensions.
|
|
404
|
-
*/
|
|
405
|
-
measureFields(
|
|
406
|
-
startY: number,
|
|
407
|
-
startWidth: number,
|
|
408
|
-
startHeight: number
|
|
409
|
-
): {
|
|
410
|
-
width: number;
|
|
411
|
-
height: number;
|
|
412
|
-
fields: { width: number; height: number }[];
|
|
413
|
-
} {
|
|
414
|
-
const c = this.constants;
|
|
415
|
-
const node = this.node!;
|
|
416
|
-
|
|
417
|
-
let y = startY;
|
|
418
|
-
let totalWidth = startWidth;
|
|
419
|
-
let totalHeight = startHeight;
|
|
420
|
-
const fields: { width: number; height: number }[] = [];
|
|
421
|
-
|
|
422
|
-
for (const field of node.allFields()) {
|
|
423
|
-
const m = this.measureField(field);
|
|
424
|
-
fields.push(m);
|
|
425
|
-
|
|
426
|
-
totalWidth = Math.max(
|
|
427
|
-
totalWidth,
|
|
428
|
-
m.width + c.FIELD_MARGIN_X * 2
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
const bottom = y + m.height;
|
|
432
|
-
if (bottom + c.FIELD_MARGIN_Y > totalHeight) {
|
|
433
|
-
totalHeight = bottom + c.FIELD_MARGIN_Y;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
y += m.height + c.FIELD_MARGIN_Y;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return { width: totalWidth, height: totalHeight, fields };
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Measures the base dimensions of the current node including label.
|
|
443
|
-
* @returns The width and height of the node base and label.
|
|
444
|
-
*/
|
|
445
|
-
private measureBaseAndLabel(): { width: number; height: number } {
|
|
446
|
-
const c = this.constants;
|
|
447
|
-
const node = this.node!;
|
|
448
|
-
|
|
449
|
-
const base = this.getNodeBaseMeasurements();
|
|
450
|
-
let width = base.width;
|
|
451
|
-
let height = base.height;
|
|
452
|
-
|
|
453
|
-
if (node.labelText) {
|
|
454
|
-
const labelW = this.measureTextWidth(
|
|
455
|
-
node.labelText,
|
|
456
|
-
c.FONT_SIZE,
|
|
457
|
-
c.FONT_FAMILY
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
width = Math.max(
|
|
461
|
-
width,
|
|
462
|
-
labelW + c.TOPBAR_LABEL_MARGIN_X * 2
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return { width, height };
|
|
467
|
-
}
|
|
468
|
-
// renderNode METHOD + NODE DRAW INITING.
|
|
469
|
-
/**
|
|
470
|
-
* Renders the specified node by drawing it and building its representation.
|
|
471
|
-
* @param nodeIdOrNode The node or node ID to render.
|
|
472
|
-
* @returns Void.
|
|
473
|
-
*/
|
|
474
|
-
renderNode(nodeIdOrNode: NodeSvg | string) {
|
|
475
|
-
this.startNode(nodeIdOrNode);
|
|
476
|
-
if (!this.node) return;
|
|
477
|
-
this.drawNode();
|
|
478
|
-
this.representer.build(this.node as NodeSvg, this, this.state as DrawState);
|
|
479
|
-
this.storeState();
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Starts rendering the specified node.
|
|
483
|
-
* @param nodeIdOrNode The node or node ID to start rendering.
|
|
484
|
-
*/
|
|
485
|
-
startNode(nodeIdOrNode: NodeSvg | string) {
|
|
486
|
-
const ws = this.getWs();
|
|
487
|
-
if (nodeIdOrNode instanceof NodeSvg) {
|
|
488
|
-
this._currentNode = nodeIdOrNode;
|
|
489
|
-
} else {
|
|
490
|
-
const node = ws.getNode(nodeIdOrNode);
|
|
491
|
-
if (node instanceof NodeSvg) {
|
|
492
|
-
this._currentNode = node;
|
|
493
|
-
} else {
|
|
494
|
-
this._currentNode = null;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
// DRAW-STATE MANAGEMENT -
|
|
499
|
-
/**
|
|
500
|
-
* Build a draw state for the given node group and ID.
|
|
501
|
-
* @param nodeGroup - The SVG group element for the node.
|
|
502
|
-
* @param id - The ID of the node.
|
|
503
|
-
* @returns - The constructed DrawState object.
|
|
504
|
-
*/
|
|
505
|
-
drawState(nodeGroup: G, id: string): DrawState {
|
|
506
|
-
return {
|
|
507
|
-
id,
|
|
508
|
-
group: nodeGroup,
|
|
509
|
-
fieldPosY: 0,
|
|
510
|
-
pendingConnections: []
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Stores the current draw state.
|
|
515
|
-
*/
|
|
516
|
-
storeState() {
|
|
517
|
-
this._drawStates.push(this.state as DrawState);
|
|
518
|
-
}
|
|
519
|
-
// DRAWING + EVENT INITIALIZING VIA EVENTER
|
|
520
|
-
/**
|
|
521
|
-
* Draws a raw input field.
|
|
522
|
-
* @param fieldGroup - The SVG group element for the field.
|
|
523
|
-
* @param field - The field to draw.
|
|
524
|
-
* @param startX - The starting X position for the field.
|
|
525
|
-
* @returns The rectangle and text elements of the raw field.
|
|
526
|
-
*/
|
|
527
|
-
drawFieldRaw(fieldGroup: G, field: AnyField, startX: number = 0) {
|
|
528
|
-
const c = this.constants;
|
|
529
|
-
const value = field.getDisplayValue?.() ?? "";
|
|
530
|
-
const { width, height } = this.measureRawField(value);
|
|
531
|
-
|
|
532
|
-
const rect = fieldGroup.rect(width, height)
|
|
533
|
-
.fill(parseColor(c.FIELD_RAW_COLOR))
|
|
534
|
-
.stroke({ color: parseColor(c.FIELD_RAW_OUTLINE_COLOR), width: c.FIELD_RAW_OUTLINE_STROKE })
|
|
535
|
-
.radius(3);
|
|
536
|
-
|
|
537
|
-
const txt = fieldGroup.text(value)
|
|
538
|
-
.font({
|
|
539
|
-
family: c.FONT_FAMILY,
|
|
540
|
-
size: c.FONT_SIZE,
|
|
541
|
-
anchor: c.INPUT_BOX_TEXT_ANCHOR
|
|
542
|
-
})
|
|
543
|
-
.fill(parseColor(c.FIELD_RAW_TEXT_COLOR));
|
|
544
|
-
txt.node.style.userSelect = 'none';
|
|
545
|
-
|
|
546
|
-
const rawBox: FieldRawBoxData = {
|
|
547
|
-
box: rect,
|
|
548
|
-
txt
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const textBBox = txt.bbox();
|
|
552
|
-
const offsetY = (height - textBBox.height) / 2;
|
|
553
|
-
|
|
554
|
-
// move relative to startX (after label)
|
|
555
|
-
rect.move(startX, 0);
|
|
556
|
-
txt.move(startX + c.INPUT_BOX_PADDING, offsetY);
|
|
557
|
-
eventer.addElement(rect, "k_inputbox", {
|
|
558
|
-
field, // the field object that has .getValue() and .setValue(v)
|
|
559
|
-
text: txt, // the svg.js Text element you drew
|
|
560
|
-
renderer: this, // the renderer instance, must have .measureRawField and .constants
|
|
561
|
-
startX // x-offset where the box should start (after label)
|
|
562
|
-
}).tagElement(rect, [(this.constructor as typeof Renderer).ELEMENT_TAG, `node_${this.node!.id}`])
|
|
563
|
-
|
|
564
|
-
return { rect, txt, rawBox };
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Draws the label of a field.
|
|
569
|
-
* @param fieldGroup - The SVG group element for the field.
|
|
570
|
-
* @param field - The field to draw the label for.
|
|
571
|
-
* @param startX - The starting X position for the label.
|
|
572
|
-
* @returns The width used by the label including spacing.
|
|
573
|
-
*/
|
|
574
|
-
drawFieldLabel(fieldGroup: G, field: AnyField, startX: number = 0): number {
|
|
575
|
-
const c = this.constants;
|
|
576
|
-
const label = field.getLabel?.();
|
|
577
|
-
if (!label) return 0;
|
|
578
|
-
|
|
579
|
-
const txt = fieldGroup.text(label)
|
|
580
|
-
.font({
|
|
581
|
-
family: c.FONT_FAMILY,
|
|
582
|
-
size: c.FONT_SIZE,
|
|
583
|
-
anchor: 'start'
|
|
584
|
-
})
|
|
585
|
-
.fill(parseColor(c.FONT_COLOR));
|
|
586
|
-
txt.node.style.userSelect = 'none';
|
|
587
|
-
const bbox = txt.bbox();
|
|
588
|
-
const offsetY = (Math.max(c.FIELD_RAW_BASE_HEIGHT, bbox.height) - bbox.height) / 2;
|
|
589
|
-
|
|
590
|
-
// move label relative to startX
|
|
591
|
-
txt.move(startX, offsetY);
|
|
592
|
-
|
|
593
|
-
// return width used for next element
|
|
594
|
-
return bbox.width + c.LABEL_SPACING;
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Draws the X button on the node's top bar.
|
|
598
|
-
* @returns Void.
|
|
599
|
-
*/
|
|
600
|
-
drawNodeXButton() {
|
|
601
|
-
const node = this.node;
|
|
602
|
-
const state = this._nodeDraw;
|
|
603
|
-
if (!node || !state || !state.group) return;
|
|
604
|
-
|
|
605
|
-
const c = this.constants;
|
|
606
|
-
|
|
607
|
-
const measurements = this.measureNodeDimensions();
|
|
608
|
-
const width = measurements?.width ?? c.NODE_BASE_WIDTH;
|
|
609
|
-
|
|
610
|
-
const btnSize = c.TOPBAR_HEIGHT * 0.6;
|
|
611
|
-
const padding = (c.TOPBAR_HEIGHT - btnSize) / 2;
|
|
612
|
-
|
|
613
|
-
// Button group
|
|
614
|
-
const xGroup = state.group.group().attr({ class: 'node-x-clse' });
|
|
615
|
-
eventer.addElement(xGroup, 'k_closenode', {
|
|
616
|
-
workspace: this.getWs(),
|
|
617
|
-
node
|
|
618
|
-
}).tagElement(xGroup, [(this.constructor as typeof Renderer).ELEMENT_TAG, `node_${node.id}`]);
|
|
619
|
-
// Background
|
|
620
|
-
xGroup.rect(btnSize, btnSize)
|
|
621
|
-
.fill('#ffffff00')
|
|
622
|
-
.radius(2)
|
|
623
|
-
.move(width - btnSize - padding, padding);
|
|
624
|
-
|
|
625
|
-
// X mark
|
|
626
|
-
const txt = xGroup.text('×')
|
|
627
|
-
.font({
|
|
628
|
-
family: c.FONT_FAMILY,
|
|
629
|
-
size: btnSize * 0.8,
|
|
630
|
-
weight: 'bold',
|
|
631
|
-
anchor: 'middle'
|
|
632
|
-
})
|
|
633
|
-
.fill('#fff')
|
|
634
|
-
.attr({
|
|
635
|
-
'text-anchor': 'middle', // horizontal centering
|
|
636
|
-
'dominant-baseline': 'middle' // vertical centering
|
|
637
|
-
});
|
|
638
|
-
txt.node.style.userSelect = 'none';
|
|
639
|
-
// Apply transform to center it inside the button
|
|
640
|
-
txt.transform({
|
|
641
|
-
translateX: width - btnSize / 2 - padding,
|
|
642
|
-
translateY: padding + btnSize / 2
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
state.xButton = xGroup;
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* Draws a connector on the specified side of the node.
|
|
649
|
-
* @param nodeGroup - The SVG group element for the node.
|
|
650
|
-
* @param nodeBg - The background SVG path of the node.
|
|
651
|
-
* @param y - The Y position for the connector.
|
|
652
|
-
* @param side - The side to draw the connector on ('left' or 'right').
|
|
653
|
-
* @param color - The color of the connector.
|
|
654
|
-
* @returns The SVG path of the connector or null if not drawn.
|
|
655
|
-
*/
|
|
656
|
-
drawConnector(nodeGroup: G, nodeBg: SvgPath, y: number, side: 'left' | 'right', color: string): SvgPath | void | undefined | null {
|
|
657
|
-
const c = this.constants;
|
|
658
|
-
if (!nodeGroup || !nodeBg) return null;
|
|
659
|
-
|
|
660
|
-
const bbox = nodeBg.bbox(); // get dimensions of the background
|
|
661
|
-
const group = nodeGroup; // attach connector to top-level node group
|
|
662
|
-
const x = side === 'left' ? 0 : bbox.width;
|
|
663
|
-
|
|
664
|
-
if (c.CONNECTOR_TRIANGLE) {
|
|
665
|
-
// small triangle connector
|
|
666
|
-
const triSize = c.CONNECTOR_TRI_SIZE;
|
|
667
|
-
let path = Path.roundedTri(triSize, triSize, 1);
|
|
668
|
-
|
|
669
|
-
// flip triangle horizontally for left side
|
|
670
|
-
if (side === 'left') path = Path.rotatePath(path, 180, triSize / 2, triSize / 2);
|
|
671
|
-
|
|
672
|
-
const tri = group.path(path)
|
|
673
|
-
.fill(parseColor(color as Hex))
|
|
674
|
-
.stroke({ color: parseColor('#00000000'), width: 0 });
|
|
675
|
-
tri.attr({
|
|
676
|
-
class: (this.constructor as typeof Renderer).CONNECTOR_TAG
|
|
677
|
-
})
|
|
678
|
-
const offsetX = side === 'left' ? -triSize : 0;
|
|
679
|
-
tri.transform({ translateX: x + offsetX, translateY: y - triSize / 2 });
|
|
680
|
-
|
|
681
|
-
return tri;
|
|
682
|
-
} else {
|
|
683
|
-
// circle connector
|
|
684
|
-
const radius = c.CONNECTOR_RADIUS;
|
|
685
|
-
const circlePath = Path.circle(radius);
|
|
686
|
-
|
|
687
|
-
const circ = group.path(circlePath)
|
|
688
|
-
.fill(parseColor(color as Hex))
|
|
689
|
-
.stroke({ color: parseColor('#00000000'), width: 0 })
|
|
690
|
-
.move(x - radius, y - radius); // center circle at (x, y)
|
|
691
|
-
circ.attr({
|
|
692
|
-
class: (this.constructor as typeof Renderer).CONNECTOR_TAG
|
|
693
|
-
})
|
|
694
|
-
return circ;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* Draws the label on the node's top bar.
|
|
700
|
-
* @param nodeGroup - The SVG group element for the node.
|
|
701
|
-
* @returns Void.
|
|
702
|
-
*/
|
|
703
|
-
drawNodeLabel(nodeGroup: G) {
|
|
704
|
-
const node = this.node;
|
|
705
|
-
const c = this.constants;
|
|
706
|
-
if (!node) return;
|
|
707
|
-
|
|
708
|
-
if (node.labelText) {
|
|
709
|
-
const txt = nodeGroup.text(node.labelText)
|
|
710
|
-
.font({
|
|
711
|
-
family: c.FONT_FAMILY,
|
|
712
|
-
size: c.FONT_SIZE,
|
|
713
|
-
anchor: 'start',
|
|
714
|
-
weight: c.TOPBAR_LABEL_BOLDED ? '600' : 'normal'
|
|
715
|
-
})
|
|
716
|
-
.fill(parseColor(c.FONT_COLOR));
|
|
717
|
-
|
|
718
|
-
txt.node.style.userSelect = 'none';
|
|
719
|
-
|
|
720
|
-
const bbox = txt.bbox();
|
|
721
|
-
const offsetY = (c.TOPBAR_HEIGHT - bbox.height) / 2;
|
|
722
|
-
|
|
723
|
-
txt.move(c.TOPBAR_LABEL_MARGIN_X, offsetY + c.TOPBAR_LABEL_MARGIN_Y);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* Draws the current node.
|
|
729
|
-
* @returns Void.
|
|
730
|
-
*/
|
|
731
|
-
drawNode() {
|
|
732
|
-
if (!this.node) return;
|
|
733
|
-
|
|
734
|
-
const node = this.node;
|
|
735
|
-
const state = this.drawState(this.createNodeGroup(node), node.id);
|
|
736
|
-
this._nodeDraw = state;
|
|
737
|
-
|
|
738
|
-
const measurements = this.measureNodeDimensions();
|
|
739
|
-
|
|
740
|
-
this.drawNodeBase(state, measurements as NodeMeasurements);
|
|
741
|
-
this.drawNodeTopbar(state, this.getNodeColors(), measurements as NodeMeasurements);
|
|
742
|
-
this.drawNodeXButton();
|
|
743
|
-
this.drawNodeLabel(state.group!);
|
|
744
|
-
this.makeNodeDraggable(state.group!, state.topbar!, node);
|
|
745
|
-
|
|
746
|
-
this.createFieldGroup(state);
|
|
747
|
-
this.drawAllFieldsForNode(measurements as NodeMeasurements);
|
|
748
|
-
|
|
749
|
-
this.drawPreviousNextConnections(state, node, state.group!, measurements as NodeMeasurements);
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Creates the SVG group for the given node.
|
|
753
|
-
* @param node - The node to create the group for.
|
|
754
|
-
* @returns The created SVG group element.
|
|
755
|
-
*/
|
|
756
|
-
createNodeGroup(node: NodeSvg): G {
|
|
757
|
-
const nodeGroup = this.svg.group().attr({
|
|
758
|
-
'data-node-id': escapeAttr(node.id),
|
|
759
|
-
'class': (this.constructor as typeof Renderer).NODE_G_TAG
|
|
760
|
-
});
|
|
761
|
-
const screenPos = this._ws.workspaceToScreen(node.relativeCoords.x, node.relativeCoords.y);
|
|
762
|
-
nodeGroup.attr({ transform: `translate(${screenPos.x}, ${screenPos.y})` });
|
|
763
|
-
return nodeGroup;
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Draws the base and shadow of the node.
|
|
767
|
-
* @param state - The current drawing state.
|
|
768
|
-
* @param measurements - The measurements of the node.
|
|
769
|
-
*/
|
|
770
|
-
drawNodeBase(state: DrawState, measurements: NodeMeasurements | null) {
|
|
771
|
-
const c = this.constants;
|
|
772
|
-
const width = measurements?.width ?? c.NODE_BASE_WIDTH;
|
|
773
|
-
const height = measurements?.height ?? c.NODE_BASE_HEIGHT;
|
|
774
|
-
const radius = c.CORNER_RADIUS;
|
|
775
|
-
state.bg = state.group!.path(Path.roundedRect(width, height, radius))
|
|
776
|
-
.fill(parseColor(c.NODE_BG_COLOR))
|
|
777
|
-
.stroke({ color: parseColor(c.NODE_OUTLINE_COLOR), width: 2 } as StrokeData);
|
|
778
|
-
state.shadow = state.group!.path(Path.roundedRect(width, height, radius))
|
|
779
|
-
.fill('rgba(0,0,0,0.2)')
|
|
780
|
-
.move(Number(state.bg.x()) + 5, Number(state.bg.y()) + 5)
|
|
781
|
-
.back(); // make sure it's behind the node
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Draw the node's topbar
|
|
785
|
-
* @param state - The draw state
|
|
786
|
-
* @param colors - The colors of the node.
|
|
787
|
-
* @param measurements - The measurement data of the node.
|
|
788
|
-
*/
|
|
789
|
-
drawNodeTopbar(state: DrawState, colors: ColorStyle, measurements: NodeMeasurements | null) {
|
|
790
|
-
const c = this.constants;
|
|
791
|
-
const width = measurements?.width ?? c.NODE_BASE_WIDTH;
|
|
792
|
-
const radius = c.CORNER_RADIUS;
|
|
793
|
-
state.topbar = state.group!.path(Path.roundedRect(width, c.TOPBAR_HEIGHT, radius))
|
|
794
|
-
.fill(parseColor(colors.primary))
|
|
795
|
-
.stroke({ color: parseColor(colors.tertiary), width: 2 } as StrokeData);
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Make a node draggable.
|
|
799
|
-
* @param nodeGroup - The node group to make draggable
|
|
800
|
-
* @param dragHandle - The drag handle
|
|
801
|
-
* @param node - The nodesvg
|
|
802
|
-
*/
|
|
803
|
-
makeNodeDraggable(nodeGroup: G, dragHandle: SvgPath, node: NodeSvg) {
|
|
804
|
-
eventer.addElement(nodeGroup, 'k_draggable', {
|
|
805
|
-
dragel: dragHandle,
|
|
806
|
-
group: nodeGroup,
|
|
807
|
-
node,
|
|
808
|
-
type: 2
|
|
809
|
-
}).tagElement(nodeGroup, [(this.constructor as typeof Renderer).ELEMENT_TAG, `node_${node.id}`]);
|
|
810
|
-
}
|
|
811
|
-
/** Create the field group for the node */
|
|
812
|
-
createFieldGroup(state: DrawState) {
|
|
813
|
-
const c = this.constants;
|
|
814
|
-
const fieldsGroup = state.group!.group();
|
|
815
|
-
fieldsGroup.attr({ transform: `translate(0, ${c.TOPBAR_HEIGHT + c.FIELD_SPACEY})` });
|
|
816
|
-
state.fieldCol = fieldsGroup;
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* Draw a field on a node.
|
|
820
|
-
* @param field - The field to draw
|
|
821
|
-
* @param measurements - The node's measurements
|
|
822
|
-
* @param idx - Index of the field in the fieldColumn list
|
|
823
|
-
* @param y - the y position of the field
|
|
824
|
-
* @returns
|
|
825
|
-
*/
|
|
826
|
-
drawFieldForNode(field: AnyField, measurements: NodeMeasurements, idx: number, y: number) {
|
|
827
|
-
const node = this.node;
|
|
828
|
-
const state = this.state;
|
|
829
|
-
const c = this.constants;
|
|
830
|
-
if (!node || !state || !state.fieldCol) return;
|
|
831
|
-
const fieldsGroup = state.fieldCol;
|
|
832
|
-
const nodeGroup = state.group as G;
|
|
833
|
-
const fm = measurements?.fields[idx];
|
|
834
|
-
if (!fm) return;
|
|
835
|
-
|
|
836
|
-
// default left alignment
|
|
837
|
-
let alignX = c.FIELD_MARGIN_X;
|
|
838
|
-
|
|
839
|
-
const fieldGroup = fieldsGroup.group();
|
|
840
|
-
fieldGroup.attr({ transform: `translate(${alignX}, ${y})` });
|
|
841
|
-
field.svgGroup = fieldGroup;
|
|
842
|
-
state.fieldPosY = y;
|
|
843
|
-
|
|
844
|
-
// draw label first, get its used width
|
|
845
|
-
const xUsed = this.drawFieldLabel(fieldGroup, field);
|
|
846
|
-
if (field.isCustomEditor()) {
|
|
847
|
-
// field fully owns drawing
|
|
848
|
-
field.drawMyself({
|
|
849
|
-
measuredWidth: fm.width,
|
|
850
|
-
measuredHeight: fm.height,
|
|
851
|
-
xUsed,
|
|
852
|
-
fieldGroup,
|
|
853
|
-
nodeGroup,
|
|
854
|
-
svg: this.svg,
|
|
855
|
-
background: state!.bg as unknown as Rect
|
|
856
|
-
});
|
|
857
|
-
} else {
|
|
858
|
-
let rawData, cBubbleData = undefined;
|
|
859
|
-
// if raw, draw right after label
|
|
860
|
-
if (field.hasRaw()) {
|
|
861
|
-
const { rawBox } = this.drawFieldRaw(fieldGroup, field, xUsed);
|
|
862
|
-
rawData = rawBox;
|
|
863
|
-
}
|
|
864
|
-
if (field.hasConnectable() && (field as ConnectableField)!.connection) {
|
|
865
|
-
const halfHeight = (fm.height + (field.hasRaw() ? 0 : c.FIELD_MARGIN_Y)) / 2;
|
|
866
|
-
const absY = c.TOPBAR_HEIGHT + c.FIELD_SPACEY + y + halfHeight;
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const c3 = this.drawConnector(
|
|
870
|
-
nodeGroup,
|
|
871
|
-
state.bg as SvgPath,
|
|
872
|
-
absY,
|
|
873
|
-
'right',
|
|
874
|
-
parseColor(c.FIELD_CONN_COLOR)
|
|
875
|
-
);
|
|
876
|
-
if (c3) {
|
|
877
|
-
const c = {
|
|
878
|
-
from: (field as ConnectableField)!.connection as Connection,
|
|
879
|
-
to: ((field as ConnectableField)!.connection.getTo() as NodeSvg)?.previousConnection as Connection,
|
|
880
|
-
fromCircle: c3 as SvgPath,
|
|
881
|
-
originCircle: c3 as SvgPath,
|
|
882
|
-
originConn: (field as ConnectableField)!.connection as Connection
|
|
883
|
-
};
|
|
884
|
-
cBubbleData = {
|
|
885
|
-
connector: c3,
|
|
886
|
-
connState: c
|
|
887
|
-
}
|
|
888
|
-
this.setConnect(c);
|
|
889
|
-
eventer.addElement(c3, 'k_connectbubble', {
|
|
890
|
-
connection: (field as ConnectableField)!.connection as Connection,
|
|
891
|
-
field
|
|
892
|
-
}).tagElement(c3, [(this.constructor as typeof Renderer).ELEMENT_TAG, `node_${node.id}`]);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
field.onDraw(rawData, cBubbleData);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Draw all fields on a node
|
|
900
|
-
* @param nodeMeasurements - The node's measurements
|
|
901
|
-
* @returns The final Y position.
|
|
902
|
-
*/
|
|
903
|
-
drawAllFieldsForNode(nodeMeasurements: NodeMeasurements | null = null) {
|
|
904
|
-
const node = this.node;
|
|
905
|
-
let y = 0;
|
|
906
|
-
const state = this.state;
|
|
907
|
-
const c = this.constants;
|
|
908
|
-
if (!node || !state || !state.fieldCol) return;
|
|
909
|
-
const fieldsGroup = state.fieldCol;
|
|
910
|
-
const nodeGroup = state.group as G;
|
|
911
|
-
|
|
912
|
-
const measurements = nodeMeasurements ?? this.measureNodeDimensions();
|
|
913
|
-
|
|
914
|
-
node.allFields().forEach((field, idx) => {
|
|
915
|
-
this.drawFieldForNode(field, measurements as NodeMeasurements, idx, y);
|
|
916
|
-
const fm = measurements?.fields[idx];
|
|
917
|
-
if (!fm) return;
|
|
918
|
-
y += fm.height + c.FIELD_MARGIN_Y;
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
state.fieldPosY = y;
|
|
922
|
-
return y;
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Draw the previous and next connections of a node.
|
|
926
|
-
* @param state - The draw-state
|
|
927
|
-
* @param node - The node-svg
|
|
928
|
-
* @param nodeGroup - the node's group
|
|
929
|
-
* @param measurements - the node's measurements
|
|
930
|
-
* @returns Void
|
|
931
|
-
*/
|
|
932
|
-
drawPreviousNextConnections(state: DrawState, node: NodeSvg, nodeGroup: G, measurements: { width: number, height: number } | null = null) {
|
|
933
|
-
if (!state || !node) return;
|
|
934
|
-
if (!state.bg) return;
|
|
935
|
-
const c = this.constants;
|
|
936
|
-
const colors: ColorStyle = this.getNodeColors();
|
|
937
|
-
/**
|
|
938
|
-
* Draw connectors.
|
|
939
|
-
*/
|
|
940
|
-
const bbox = state.bg?.bbox();
|
|
941
|
-
const cY = (bbox?.height ?? measurements?.height) - c.FOOTER_HEIGHT;
|
|
942
|
-
|
|
943
|
-
// Previous connection (left)
|
|
944
|
-
if (node.previousConnection) {
|
|
945
|
-
const c1 = this.drawConnector(nodeGroup, state.bg, cY, 'left', parseColor(colors.primary) as string);
|
|
946
|
-
if (c1) {
|
|
947
|
-
const c = ({
|
|
948
|
-
from: node.previousConnection,
|
|
949
|
-
to: this.resolveConnectable(node.previousConnection.getFrom(), node.previousConnection) as Connection,
|
|
950
|
-
fromCircle: c1 as SvgPath,
|
|
951
|
-
originConn: node.previousConnection,
|
|
952
|
-
originCircle: c1
|
|
953
|
-
});
|
|
954
|
-
this.setConnect(c);
|
|
955
|
-
eventer.addElement(c1, 'k_connectbubble', {
|
|
956
|
-
connection: node.previousConnection,
|
|
957
|
-
node
|
|
958
|
-
}).tagElement(c1, [(this.constructor as typeof Renderer).ELEMENT_TAG, `node_${node.id}`]);
|
|
959
|
-
// fill any waiting connectors from other nodes
|
|
960
|
-
this._fillOtherNodeConnectorCircle(node.previousConnection, c1 as SvgPath, true);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// Next connection (right)
|
|
965
|
-
if (node.nextConnection) {
|
|
966
|
-
const c2 = this.drawConnector(nodeGroup, state.bg, cY, 'right', parseColor(colors.primary) as string);
|
|
967
|
-
if (c2) {
|
|
968
|
-
const c = ({
|
|
969
|
-
from: node.nextConnection,
|
|
970
|
-
to: this.resolveConnectable(node.nextConnection.getTo(), node.nextConnection) as Connection,
|
|
971
|
-
fromCircle: c2 as SvgPath,
|
|
972
|
-
originConn: node.nextConnection,
|
|
973
|
-
originCircle: c2
|
|
974
|
-
});
|
|
975
|
-
this.setConnect(c);
|
|
976
|
-
eventer.addElement(c2, 'k_connectbubble', {
|
|
977
|
-
connection: node.nextConnection,
|
|
978
|
-
node
|
|
979
|
-
}).tagElement(c2, [(this.constructor as typeof Renderer).ELEMENT_TAG, `node_${node.id}`]);
|
|
980
|
-
// fill any waiting connectors from other nodes
|
|
981
|
-
this._fillOtherNodeConnectorCircle(node.nextConnection, c2 as SvgPath, false);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
// REFRESHING TRANSFORMS AND COMMENTS -
|
|
986
|
-
/**
|
|
987
|
-
* Refreshes the comment transforms.
|
|
988
|
-
* @returns Void.
|
|
989
|
-
*/
|
|
990
|
-
refreshComments() {
|
|
991
|
-
return this._commentDrawer?.refreshCommentTransforms?.();
|
|
992
|
-
}
|
|
993
|
-
/**
|
|
994
|
-
* Clears all comments from the workspace.
|
|
995
|
-
* @returns Void.
|
|
996
|
-
*/
|
|
997
|
-
clearComments() {
|
|
998
|
-
return this._commentDrawer?.clearAllComments?.();
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Draws all comments in the workspace.
|
|
1002
|
-
* @returns Void.
|
|
1003
|
-
*/
|
|
1004
|
-
drawComments() {
|
|
1005
|
-
return this._commentDrawer?.drawAllComments?.();
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* Gets the current zoom level of the workspace.
|
|
1009
|
-
* @returns The zoom level.
|
|
1010
|
-
*/
|
|
1011
|
-
getZoom() {
|
|
1012
|
-
return this._ws.getZoom() ?? 1;
|
|
1013
|
-
}
|
|
1014
|
-
/**
|
|
1015
|
-
* Applies the current zoom level to the specified node group.
|
|
1016
|
-
* @param nodeG - The SVG group element of the node.
|
|
1017
|
-
* @param node - The node to apply zoom to.
|
|
1018
|
-
*/
|
|
1019
|
-
applyZoomToNode(nodeG: G, node: NodeSvg) {
|
|
1020
|
-
const zoom = this.getZoom();
|
|
1021
|
-
const { x, y } = this._ws.workspaceToScreen(node.relativeCoords.x, node.relativeCoords.y);
|
|
1022
|
-
nodeG.attr({ transform: `translate(${x}, ${y}) scale(${zoom})` });
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Refreshes the transforms of all nodes in the workspace.
|
|
1026
|
-
* @returns Void.
|
|
1027
|
-
*/
|
|
1028
|
-
refreshNodeTransforms() {
|
|
1029
|
-
const nodeGroups = this.svg.find(`.${(this.constructor as typeof Renderer).NODE_G_TAG}`);
|
|
1030
|
-
const zoom = this.getZoom();
|
|
1031
|
-
|
|
1032
|
-
for (let nodeG of nodeGroups) {
|
|
1033
|
-
const node = this._ws.getNode(unescapeAttr(nodeG.attr('data-node-id')));
|
|
1034
|
-
if (!node) continue;
|
|
1035
|
-
const { x, y } = this._ws.workspaceToScreen(node.relativeCoords.x, node.relativeCoords.y);
|
|
1036
|
-
nodeG.attr({ transform: `translate(${x}, ${y}) scale(${zoom})` });
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
this.refreshConnectionLines();
|
|
1040
|
-
this.refreshComments();
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* Refreshes all connection lines in the workspace.
|
|
1045
|
-
* @returns Void.
|
|
1046
|
-
*/
|
|
1047
|
-
refreshConnectionLines() {
|
|
1048
|
-
this.clearLines();
|
|
1049
|
-
this.drawLinesForAllNodes();
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Gets the colors for the current node.
|
|
1053
|
-
* @returns The color style of the node.
|
|
1054
|
-
*/
|
|
1055
|
-
getNodeColors(): ColorStyle {
|
|
1056
|
-
const node: NodeSvg = this.node as NodeSvg;
|
|
1057
|
-
const colors: ColorStyle = node.colors ?? {
|
|
1058
|
-
primary: '#000',
|
|
1059
|
-
secondary: '#000',
|
|
1060
|
-
tertirary: '#000',
|
|
1061
|
-
category: ''
|
|
1062
|
-
};
|
|
1063
|
-
return colors;
|
|
1064
|
-
}
|
|
1065
|
-
// CONNECTOR BUBBLE HANDLING + CONNECTION RESOLVING -
|
|
1066
|
-
/**
|
|
1067
|
-
* Fill every node's connector bubble data with the corresponding bubble its connected to on a sibling node.
|
|
1068
|
-
*/
|
|
1069
|
-
fillAllNodeConnectorBubbles() {
|
|
1070
|
-
for (const state of this._drawStates) {
|
|
1071
|
-
for (const connPair of state.pendingConnections) {
|
|
1072
|
-
const { originConn } = connPair;
|
|
1073
|
-
if (!originConn) continue;
|
|
1074
|
-
|
|
1075
|
-
// Only try to fill missing sides with real circles from other pending connections
|
|
1076
|
-
if (!connPair.fromCircle) {
|
|
1077
|
-
const match = this._drawStates
|
|
1078
|
-
.flatMap(s => s.pendingConnections)
|
|
1079
|
-
.find(p => p.originConn === connPair.from && p.originCircle);
|
|
1080
|
-
if (match) connPair.fromCircle = match.originCircle;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
if (!connPair.toCircle) {
|
|
1084
|
-
const match = this._drawStates
|
|
1085
|
-
.flatMap(s => s.pendingConnections)
|
|
1086
|
-
.find(p => p.originConn === connPair.to && p.originCircle);
|
|
1087
|
-
if (match) connPair.toCircle = match.originCircle;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Resolves the connectable to the appropriate connection based on the originating connection.
|
|
1094
|
-
* @param connectable - The connectable entity (NodeSvg or Field).
|
|
1095
|
-
* @param fromConn - The originating connection.
|
|
1096
|
-
* @returns
|
|
1097
|
-
*/
|
|
1098
|
-
resolveConnectable(connectable: Connectable, fromConn: Connection): Connection | null | undefined {
|
|
1099
|
-
if (!connectable || !fromConn) return undefined;
|
|
1100
|
-
|
|
1101
|
-
// If the connection is an input (previous), return the connectable's output (next) connection
|
|
1102
|
-
if (fromConn.isPrevious) {
|
|
1103
|
-
|
|
1104
|
-
if (connectable instanceof NodeSvg) return connectable.nextConnection;
|
|
1105
|
-
// @ts-ignore
|
|
1106
|
-
if (connectable instanceof Field) return connectable.connection;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// If the connection is an output (next), return the connectable's input (previous) connection
|
|
1110
|
-
if (!fromConn.isPrevious) {
|
|
1111
|
-
if (connectable instanceof NodeSvg) return connectable.previousConnection;
|
|
1112
|
-
// @ts-ignore
|
|
1113
|
-
if (connectable instanceof Field) return connectable.connection;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Fills in the connector circle for other nodes based on the given connection.
|
|
1118
|
-
* @param conn - The connection to match.
|
|
1119
|
-
* @param circle - The SVG path of the connector circle.
|
|
1120
|
-
* @param isPrevious - Whether the connection is a previous connection.
|
|
1121
|
-
*/
|
|
1122
|
-
_fillOtherNodeConnectorCircle(conn: Connection, circle: SvgPath, isPrevious: boolean) {
|
|
1123
|
-
for (const state of this._drawStates) {
|
|
1124
|
-
for (const connPair of state.pendingConnections) {
|
|
1125
|
-
// Only fill if the connection actually matches
|
|
1126
|
-
if (isPrevious) {
|
|
1127
|
-
// fill toCircle if this connPair expects 'conn' as its 'to'
|
|
1128
|
-
if (connPair.to === conn && !connPair.toCircle) {
|
|
1129
|
-
connPair.toCircle = circle;
|
|
1130
|
-
}
|
|
1131
|
-
} else {
|
|
1132
|
-
// fill fromCircle if this connPair expects 'conn' as its 'from'
|
|
1133
|
-
if (connPair.from === conn && !connPair.fromCircle) {
|
|
1134
|
-
connPair.fromCircle = circle;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
// LINE DRAWING -
|
|
1141
|
-
/**
|
|
1142
|
-
* Draw the connection lines between node's connector bubbles.
|
|
1143
|
-
*/
|
|
1144
|
-
drawLinesForAllNodes() {
|
|
1145
|
-
const c = this.constants;
|
|
1146
|
-
const wsSvg = this._ws.svg;
|
|
1147
|
-
|
|
1148
|
-
this.fillAllNodeConnectorBubbles();
|
|
1149
|
-
const drawnCircles = new Set<SvgPath>(); // store circles we've already drawn lines from/to
|
|
1150
|
-
|
|
1151
|
-
for (const state of this._drawStates) {
|
|
1152
|
-
for (const connPair of state.pendingConnections) {
|
|
1153
|
-
const { fromCircle, toCircle } = connPair;
|
|
1154
|
-
if (connPair.from !== connPair.originConn) continue;
|
|
1155
|
-
if (!fromCircle || !toCircle) continue;
|
|
1156
|
-
|
|
1157
|
-
// skip if either circle was already used
|
|
1158
|
-
if (drawnCircles.has(fromCircle) || drawnCircles.has(toCircle)) continue;
|
|
1159
|
-
|
|
1160
|
-
// mark circles as used
|
|
1161
|
-
drawnCircles.add(fromCircle);
|
|
1162
|
-
drawnCircles.add(toCircle);
|
|
1163
|
-
// Get DOM elements
|
|
1164
|
-
const fromEl = fromCircle.node as SVGPathElement;
|
|
1165
|
-
const toEl = toCircle.node as SVGPathElement;
|
|
1166
|
-
|
|
1167
|
-
// Use getBBox + getScreenCTM for absolute coordinates
|
|
1168
|
-
const fromBBox = fromEl.getBBox();
|
|
1169
|
-
const toBBox = toEl.getBBox();
|
|
1170
|
-
|
|
1171
|
-
const fromCTM = fromEl.getScreenCTM()!;
|
|
1172
|
-
const toCTM = toEl.getScreenCTM()!;
|
|
1173
|
-
|
|
1174
|
-
const startX = fromBBox.x + fromBBox.width / 2;
|
|
1175
|
-
const startY = fromBBox.y + fromBBox.height / 2;
|
|
1176
|
-
const endX = toBBox.x + toBBox.width / 2;
|
|
1177
|
-
const endY = toBBox.y + toBBox.height / 2;
|
|
1178
|
-
|
|
1179
|
-
// Transform to screen coordinates
|
|
1180
|
-
const absStartX = startX * fromCTM.a + startY * fromCTM.c + fromCTM.e;
|
|
1181
|
-
const absStartY = startX * fromCTM.b + startY * fromCTM.d + fromCTM.f;
|
|
1182
|
-
const absEndX = endX * toCTM.a + endY * toCTM.c + toCTM.e;
|
|
1183
|
-
const absEndY = endX * toCTM.b + endY * toCTM.d + toCTM.f;
|
|
1184
|
-
|
|
1185
|
-
// Draw the line
|
|
1186
|
-
let pathStr: string;
|
|
1187
|
-
if (c.CONNECTOR_LINE_CURVED) {
|
|
1188
|
-
const dx = Math.abs(absEndX - absStartX);
|
|
1189
|
-
const cp1x = absStartX + Math.sign(absEndX - absStartX) * Math.max(30, dx * 0.3);
|
|
1190
|
-
const cp2x = absEndX - Math.sign(absEndX - absStartX) * Math.max(30, dx * 0.3);
|
|
1191
|
-
pathStr = `M ${absStartX} ${absStartY} C ${cp1x} ${absStartY}, ${cp2x} ${absEndY}, ${absEndX} ${absEndY}`;
|
|
1192
|
-
} else {
|
|
1193
|
-
pathStr = `M ${absStartX} ${absStartY} L ${absEndX} ${absEndY}`;
|
|
1194
|
-
}
|
|
1195
|
-
const zoom = this._ws.getZoom();
|
|
1196
|
-
const strokeWidth = c.CONNECTOR_LINE_WIDTH * zoom;
|
|
1197
|
-
const line = wsSvg.path(pathStr)
|
|
1198
|
-
.stroke({ color: parseColor(fromCircle.fill() as Color), width: strokeWidth })
|
|
1199
|
-
.fill('none')
|
|
1200
|
-
.attr({ class: (this.constructor as typeof Renderer).CONN_LINE_TAG });
|
|
1201
|
-
|
|
1202
|
-
eventer.addElement(line, 'k_connline', {
|
|
1203
|
-
fromConn: connPair.from,
|
|
1204
|
-
toConn: connPair.to,
|
|
1205
|
-
renderer: this
|
|
1206
|
-
}).tagElement(line, [(this.constructor as typeof Renderer).ELEMENT_TAG, (this.constructor as typeof Renderer).LINE_X_MARK_TAG]);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
/**
|
|
1212
|
-
* Clear connection lines and their X marks.
|
|
1213
|
-
*/
|
|
1214
|
-
clearLines() {
|
|
1215
|
-
for (let line of this.getWs().svg.find(`.${(this.constructor as typeof Renderer).CONN_LINE_TAG}`)) {
|
|
1216
|
-
line.remove();
|
|
1217
|
-
}
|
|
1218
|
-
for (let mark of this.getWs().svg.find(`.${(this.constructor as typeof Renderer).LINE_X_MARK_TAG}`)) {
|
|
1219
|
-
mark.remove();
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Clear the entire screen.
|
|
1224
|
-
*/
|
|
1225
|
-
clearScreen() {
|
|
1226
|
-
// Destroy elements that were tagged (eventer system)
|
|
1227
|
-
eventer.destroyByTag((this.constructor as typeof Renderer).ELEMENT_TAG);
|
|
1228
|
-
|
|
1229
|
-
// Remove all SVG children **except the background pattern rect**
|
|
1230
|
-
this._ws.svg.children().forEach((el) => {
|
|
1231
|
-
const isBackground = el.hasClass((this.constructor as typeof Renderer).BACKGROUND_PATTERN);
|
|
1232
|
-
const isDefs = el.node.tagName == 'defs';
|
|
1233
|
-
const isBgRect = el.classes().includes(WorkspaceSvg.BACKGROUND_CLASS);
|
|
1234
|
-
if (!isBackground && !isDefs && !isBgRect) el.remove();
|
|
1235
|
-
});
|
|
1236
|
-
|
|
1237
|
-
// Reset internal draw state
|
|
1238
|
-
this._drawStates = [];
|
|
1239
|
-
}
|
|
1240
|
-
/**
|
|
1241
|
-
* Remove pending connections for a specific connection
|
|
1242
|
-
* @param conn - The connection
|
|
1243
|
-
*/
|
|
1244
|
-
undoPendingConnsFor(conn: ConnectorToFrom) {
|
|
1245
|
-
for (let state of this._drawStates) {
|
|
1246
|
-
for (let conn0 of state.pendingConnections) {
|
|
1247
|
-
if (conn0.toCircle == conn.originCircle) {
|
|
1248
|
-
delete conn0.toCircle;
|
|
1249
|
-
}
|
|
1250
|
-
if (conn0.fromCircle == conn.originCircle) {
|
|
1251
|
-
delete conn0.fromCircle;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
/**
|
|
1257
|
-
* Called whenever a node must be visually rendered or rerendered.
|
|
1258
|
-
* Implementations must be idempotent.
|
|
1259
|
-
* @param node - The node to render/rerender
|
|
1260
|
-
* @returns SVG group of the node.
|
|
1261
|
-
*/
|
|
1262
|
-
rerenderNode(node: NodeSvg) {
|
|
1263
|
-
// wipe old drawstate + events for this node
|
|
1264
|
-
const idx = this._drawStates.findIndex(s => s.id === node.id);
|
|
1265
|
-
if (idx !== -1) {
|
|
1266
|
-
const state = this._drawStates[idx];
|
|
1267
|
-
for (let pendingConn of state!.pendingConnections) {
|
|
1268
|
-
this.undoPendingConnsFor(pendingConn);
|
|
1269
|
-
}
|
|
1270
|
-
state!.group?.remove();
|
|
1271
|
-
eventer.destroyByTag(`node_${node.id}`);
|
|
1272
|
-
this._drawStates.splice(idx, 1);
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
// rebuild node
|
|
1276
|
-
this.startNode(node);
|
|
1277
|
-
this.drawNode();
|
|
1278
|
-
this.representer.build(this.node as NodeSvg, this, this.state as DrawState);
|
|
1279
|
-
this.storeState();
|
|
1280
|
-
|
|
1281
|
-
// refresh *all* lines once the node is back in place
|
|
1282
|
-
this.refreshNodeTransforms();
|
|
1283
|
-
return (node.svg as RepresenterNode).getRaw();
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
export default Renderer;
|