@kabel-project/kabel 1.0.7

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 (152) hide show
  1. package/(1.0.7)kabel.md +18 -0
  2. package/README.md +96 -0
  3. package/_READ_ME_MEDIA_/documentation/docs.md +293 -0
  4. package/_READ_ME_MEDIA_/workspace.png +0 -0
  5. package/comment-renderer/renderer.ts +228 -0
  6. package/controllers/base.ts +186 -0
  7. package/controllers/wasd.ts +132 -0
  8. package/docs/README.md +98 -0
  9. package/docs/_media/docs.md +289 -0
  10. package/docs/_media/index.html +168 -0
  11. package/docs/_media/workspace.png +0 -0
  12. package/docs/classes/CommentModel.md +271 -0
  13. package/docs/classes/CommentRenderer.md +457 -0
  14. package/docs/classes/ConnectableField.md +597 -0
  15. package/docs/classes/Connection.md +191 -0
  16. package/docs/classes/ContextMenuHTML.md +163 -0
  17. package/docs/classes/Coordinates.md +187 -0
  18. package/docs/classes/DropdownContainer.md +300 -0
  19. package/docs/classes/DummyField.md +393 -0
  20. package/docs/classes/Eventer.md +185 -0
  21. package/docs/classes/Field.md +461 -0
  22. package/docs/classes/InjectMsg.md +85 -0
  23. package/docs/classes/NodeSvg.md +1011 -0
  24. package/docs/classes/NumberField.md +559 -0
  25. package/docs/classes/OptConnectField.md +624 -0
  26. package/docs/classes/Renderer.md +1636 -0
  27. package/docs/classes/RendererConstants.md +343 -0
  28. package/docs/classes/Representer.md +95 -0
  29. package/docs/classes/RepresenterNode.md +175 -0
  30. package/docs/classes/TextField.md +559 -0
  31. package/docs/classes/Toolbox.md +172 -0
  32. package/docs/classes/WASDController.md +616 -0
  33. package/docs/classes/Widget.md +195 -0
  34. package/docs/classes/WorkspaceController.md +385 -0
  35. package/docs/classes/WorkspaceCoords.md +218 -0
  36. package/docs/classes/WorkspaceSvg.md +1380 -0
  37. package/docs/functions/clearMainWorkspace.md +20 -0
  38. package/docs/functions/getMainWorkspace.md +19 -0
  39. package/docs/functions/inject.md +35 -0
  40. package/docs/functions/setMainWorkspace.md +28 -0
  41. package/docs/globals.md +95 -0
  42. package/docs/interfaces/ColorStyle.md +43 -0
  43. package/docs/interfaces/ConnectorToFrom.md +57 -0
  44. package/docs/interfaces/DrawState.md +81 -0
  45. package/docs/interfaces/FieldConnectionData.md +25 -0
  46. package/docs/interfaces/FieldOptions.md +63 -0
  47. package/docs/interfaces/FieldRawBoxData.md +25 -0
  48. package/docs/interfaces/FieldVisualInfo.md +65 -0
  49. package/docs/interfaces/GridOptions.md +61 -0
  50. package/docs/interfaces/InjectOptions.md +133 -0
  51. package/docs/interfaces/InputFieldJson.md +50 -0
  52. package/docs/interfaces/KabelCommentRendering.md +31 -0
  53. package/docs/interfaces/KabelInterface.md +469 -0
  54. package/docs/interfaces/KabelNodeRendering.md +77 -0
  55. package/docs/interfaces/KabelUIX.md +105 -0
  56. package/docs/interfaces/KabelUtils.md +215 -0
  57. package/docs/interfaces/NodeEvents.md +42 -0
  58. package/docs/interfaces/NodeJson.md +104 -0
  59. package/docs/interfaces/NodePrototype.md +82 -0
  60. package/docs/interfaces/RegisteredEl.md +53 -0
  61. package/docs/interfaces/SerializedNode.md +128 -0
  62. package/docs/interfaces/TblxCategoryStruct.md +41 -0
  63. package/docs/interfaces/TblxFieldStruct.md +28 -0
  64. package/docs/interfaces/TblxNodeStruct.md +35 -0
  65. package/docs/interfaces/WidgetOptions.md +115 -0
  66. package/docs/interfaces/WidgetPrototypeList.md +15 -0
  67. package/docs/type-aliases/AnyField.md +13 -0
  68. package/docs/type-aliases/AnyFieldCls.md +13 -0
  69. package/docs/type-aliases/Color.md +13 -0
  70. package/docs/type-aliases/Connectable.md +13 -0
  71. package/docs/type-aliases/EventArgs.md +11 -0
  72. package/docs/type-aliases/EventSetupFn.md +25 -0
  73. package/docs/type-aliases/Hex.md +13 -0
  74. package/docs/type-aliases/RGBObject.md +37 -0
  75. package/docs/type-aliases/RGBString.md +13 -0
  76. package/docs/type-aliases/RGBTuple.md +13 -0
  77. package/docs/type-aliases/TblxObjStruct.md +52 -0
  78. package/docs/variables/CategoryColors.md +29 -0
  79. package/docs/variables/FieldMap.md +41 -0
  80. package/docs/variables/NodePrototypes.md +18 -0
  81. package/docs/variables/default.md +11 -0
  82. package/events/comment-drag-handle.ts +61 -0
  83. package/events/comment-input.ts +291 -0
  84. package/events/connection-line.ts +68 -0
  85. package/events/connector.ts +116 -0
  86. package/events/draggable.ts +119 -0
  87. package/events/events.ts +7 -0
  88. package/events/input-box.ts +213 -0
  89. package/events/node-x-btn.ts +25 -0
  90. package/index.d.ts +4 -0
  91. package/package.json +49 -0
  92. package/renderers/apollo/apollo.ts +21 -0
  93. package/renderers/apollo/constants.ts +40 -0
  94. package/renderers/apollo/renderer.ts +331 -0
  95. package/renderers/atlas/atlas.ts +15 -0
  96. package/renderers/constants.ts +87 -0
  97. package/renderers/renderer.ts +1288 -0
  98. package/renderers/representer-node.ts +52 -0
  99. package/renderers/representer.ts +25 -0
  100. package/src/category.ts +107 -0
  101. package/src/colors.ts +20 -0
  102. package/src/comment.ts +142 -0
  103. package/src/connection.ts +114 -0
  104. package/src/context-menu.ts +194 -0
  105. package/src/coordinates.ts +74 -0
  106. package/src/core.ts +202 -0
  107. package/src/ctx-menu-registry.ts +143 -0
  108. package/src/dropdown-menu.ts +215 -0
  109. package/src/field.ts +595 -0
  110. package/src/flyout.ts +165 -0
  111. package/src/fonts-manager.ts +38 -0
  112. package/src/grid.ts +162 -0
  113. package/src/headless-node.ts +27 -0
  114. package/src/index.ts +115 -0
  115. package/src/inject-headless.ts +18 -0
  116. package/src/inject.ts +213 -0
  117. package/src/main-workspace.ts +51 -0
  118. package/src/mutator.ts +40 -0
  119. package/src/node-types.ts +27 -0
  120. package/src/nodesvg.ts +756 -0
  121. package/src/prototypes.ts +9 -0
  122. package/src/renderer-map.ts +86 -0
  123. package/src/styles.css +224 -0
  124. package/src/toolbox.ts +125 -0
  125. package/src/types.ts +205 -0
  126. package/src/undo-redo.ts +87 -0
  127. package/src/visual-types.ts +29 -0
  128. package/src/widget-prototypes.ts +11 -0
  129. package/src/widget.ts +139 -0
  130. package/src/workspace-coords.ts +14 -0
  131. package/src/workspace-svg.ts +736 -0
  132. package/src/workspace.ts +155 -0
  133. package/test-server.js +61 -0
  134. package/themes/dark.ts +32 -0
  135. package/themes/default.ts +28 -0
  136. package/themes/themes.ts +9 -0
  137. package/tsconfig.json +25 -0
  138. package/typedoc.json +10 -0
  139. package/util/emitter.ts +33 -0
  140. package/util/env.ts +11 -0
  141. package/util/escape-html.ts +22 -0
  142. package/util/eventer.ts +108 -0
  143. package/util/has-prop.ts +4 -0
  144. package/util/parse-color.ts +42 -0
  145. package/util/path.ts +99 -0
  146. package/util/styler.ts +41 -0
  147. package/util/uid.ts +184 -0
  148. package/util/unescape-html.ts +22 -0
  149. package/util/user-state.ts +68 -0
  150. package/util/wait-anim-frames.ts +24 -0
  151. package/util/window-listeners.ts +62 -0
  152. package/webpack.config.js +80 -0
@@ -0,0 +1,18 @@
1
+ **chore(release): Kabel 1.0.6 release**
2
+ - Implemented the Apollo renderer, a renderer that tries to match the "sticky note" style of other blueprint interfaces.
3
+ - Added shadow FX to default renderer.
4
+ - Added background pattern system under wsSvg.grid, options in Kabel.inject for grid added as well.
5
+ - Created new /themes folder, created dark and classic (Default) themes, added theme options to inject.
6
+ - Fixed input boxes (comment, raw field) so that when you click at a specific point, the caret starts there.
7
+ - Decided that where you click within a comment input is where it should start editing at.
8
+ - Added wsSvg.dragState to store information about the current block being dragged.
9
+ - Cleaned up the Flyout class to break up large methods into multiple. This makes extensions easier.
10
+ - Implemented headless inject method along with the WorkspaceSVG's headless counterpart class. (Creating simulated workspaces)
11
+ - Cleaned up renderer which was in preparation for the second default renderer.
12
+ - Added full JSDoc comments to renderer for cleanliness and maintainability.
13
+ - Cleaned up comment renderer for easier subclassing.
14
+ - Removed two useless frame-waits in WorkspaceSVG class.
15
+
16
+
17
+ **chore(release): Kabel 1.0.7 release**
18
+ - Very minor documentation update.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+
2
+ # Kabel
3
+ [![npm version](https://img.shields.io/npm/v/@kabel-project/kabel)](https://www.npmjs.com/package/@kabel-project/kabel)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+
6
+ Node-based visual editor framework with an API inspired by Google’s Blockly project.
7
+ Fully extensible with custom nodes, fields, and renderers.
8
+ Written in TypeScript and ready to use out of the box.
9
+
10
+ ![Kabel workspace screenshot](./_READ_ME_MEDIA_/workspace.png)
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ You can install Kabel in two main ways:
17
+
18
+ **Using npm (recommended):**
19
+ ```bash
20
+ cd path/to/your/project
21
+ npm install @kabel-project/kabel
22
+ ```
23
+
24
+ **Cloning from GitHub (Experimental builds):**
25
+
26
+ ```bash
27
+ git clone https://github.com/FentFentFent/Kabel.git --depth 1
28
+ cd Kabel
29
+ npm install
30
+ npm run build
31
+ ```
32
+
33
+ Then import Kabel from the build:
34
+
35
+ ```js
36
+ import Kabel from './Kabel/dist/kabel.js';
37
+ ```
38
+
39
+ ##### Quick Starter HTML
40
+
41
+ ```html
42
+ <div id="workspace-container" style="width:800px;height:600px;"></div>
43
+ <script type="module">
44
+ import Kabel from 'kabel';
45
+ Kabel.inject('workspace-container', {
46
+ /* Your options here... */
47
+ });
48
+ </script>
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Features
54
+
55
+ * ⚡ Extensible: create custom nodes, fields, and renderers.
56
+ * 🛠️ TypeScript support out of the box.
57
+ * 🎨 Renderer override system for custom visuals.
58
+ * ⌨️ Built-in workspace controls (WASD movement, drag, etc.).
59
+
60
+ ---
61
+
62
+ ## Example
63
+
64
+ ```js
65
+ import Kabel from 'kabel';
66
+
67
+ const ws = Kabel.inject('workspace-container', {
68
+ controls: { wasd: true, wasdSmooth: true }
69
+ });
70
+
71
+ // Register a simple node
72
+ Kabel.Nodes['example'] = {
73
+ init() {
74
+ this.jsonInit({
75
+ labelText: 'Hello Kabel',
76
+ type: 'example_node',
77
+ primaryColor: '#cc0c00'
78
+ });
79
+ }
80
+ };
81
+ ```
82
+
83
+ For another example refer to [Kabel Test](./dist/index.html)
84
+
85
+
86
+ ---
87
+
88
+ ## Documentation
89
+
90
+ Please refer to [Kabel Guide](./_READ_ME_MEDIA_/documentation/docs.md)
91
+
92
+ ---
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,293 @@
1
+
2
+ # Kabel Documentation
3
+
4
+ **Kabel** is a node-based visual programming editor for building interactive, logic-driven UIs in the browser. It’s built around JavaScript and uses SVG for rendering. Kabel supports node creation, custom fields, widgets, toolboxes, and smooth workspace controls.
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. Getting Started
11
+ 2. Setting Up Your First Workspace
12
+ 3. Creating Your First Node Prototype
13
+ 4. Creating a Toolbox
14
+ 5. Manipulating Nodes
15
+ 6. Serializing & Deserializing Nodes
16
+ 7. Finishing Up
17
+
18
+ ## In-depth Docs
19
+ [Kabel API Documentation](../../docs/globals.md)
20
+
21
+ ---
22
+
23
+ ## 1. Getting Started
24
+
25
+ You can include Kabel either via a script tag or import it as a module:
26
+
27
+ ```html
28
+ <script src="./kabel.js"></script>
29
+ ```
30
+
31
+ Once loaded:
32
+
33
+ ```js
34
+ if (typeof Kabel !== 'undefined') {
35
+ console.log('Kabel is ready!');
36
+ }
37
+ ```
38
+
39
+ That’s all you need to confirm it’s available before initializing a workspace.
40
+
41
+ ---
42
+
43
+ ## 2. Setting Up Your First Workspace
44
+
45
+ The **workspace** is where nodes live and interact. Start by creating an HTML container:
46
+
47
+ ```js
48
+ const el = document.createElement('div');
49
+ Object.assign(el.style, {
50
+ width: '100vw',
51
+ height: '100vh',
52
+ background: '#f0f0f0'
53
+ });
54
+ document.body.appendChild(el);
55
+ ```
56
+
57
+ Then inject Kabel into that container:
58
+
59
+ ```js
60
+ const ws = Kabel.inject(el, {
61
+ theme: 'Dark',
62
+ renderer: 'atlas', // Or `Apollo`
63
+ moveSpeed: 6,
64
+ controls: {
65
+ wasd: true,
66
+ wasdSmooth: true,
67
+ wasdAccelerate: 1,
68
+ wasdFriction: 0.9
69
+ },
70
+ toolbox: { /* defined later */ }
71
+ });
72
+ ```
73
+
74
+ This gives you a fullscreen workspace with smooth WASD movement controls.
75
+
76
+ ---
77
+
78
+ ## 3. Creating Your First Node Prototype
79
+
80
+ Nodes are defined once as **prototypes** under `Kabel.Nodes`. Here’s an example:
81
+
82
+ ```js
83
+ Kabel.Nodes['my_node'] = {
84
+ init() {
85
+ this.jsonInit({
86
+ previousConnection: true,
87
+ nextConnection: true,
88
+ labelText: 'TopbarText',
89
+ type: 'my_node',
90
+ primaryColor: '#cc0c00',
91
+ tertiaryColor: '#660500',
92
+ arguments: [
93
+ { name: 'FLD', type: 'field_str', label: 'LabelText: ', value: 'ValueText' },
94
+ { name: 'Dummy', type: 'field_dummy', label: 'Label with no input!' },
95
+ { name: 'Conn', type: 'connection', label: 'Label!' },
96
+ { name: 'Conn2', type: 'field_both', label: 'lowk: ', value: 'lowk' },
97
+ {
98
+ name: 'Dropdown',
99
+ type: 'dropdown',
100
+ label: 'lowk dropdown?: ',
101
+ options: ['Oooo option', { text: 'Opt', value: 'opt' }]
102
+ }
103
+ ]
104
+ });
105
+ }
106
+ };
107
+ ```
108
+
109
+ **Key options:**
110
+
111
+ * `previousConnection` / `nextConnection` → Whether the node can chain with others.
112
+ * `arguments` → Defines fields, inputs, and connection points.
113
+ * `jsonInit()` → Initializes node configuration.
114
+
115
+ ---
116
+
117
+ ## 4. Creating a Toolbox
118
+
119
+ Toolboxes organize your available nodes:
120
+
121
+ ```js
122
+ toolbox: {
123
+ type: 'category',
124
+ contents: [{
125
+ name: 'Cat',
126
+ color: '#cc0c00',
127
+ contents: [{
128
+ type: 'my_node',
129
+ arguments: {
130
+ //FIELD_NAME: FIELD_VALUE
131
+ }
132
+ }]
133
+ }]
134
+ }
135
+ ```
136
+
137
+ **Toolbox types:**
138
+
139
+ * `'category'` → Groups nodes visually into collapsible sections.
140
+ * `'flyout'` → Displays all nodes in one scrollable list.
141
+
142
+ **Category JSON fields:**
143
+
144
+ * `name`: The category name.
145
+ * `color`: The display color.
146
+ * `contents`: Array of node definitions.
147
+
148
+ > Currently, categories and flyouts can only contain nodes. Support for buttons or UI elements may be added later.
149
+
150
+ ---
151
+
152
+ ## 5. Manipulating Node Properties
153
+
154
+ Once a node is created, you can move, modify, or connect it programmatically:
155
+
156
+ ```js
157
+ const node = new Kabel.NodeSvg(Kabel.Nodes['my_node'], ws);
158
+ node.init();
159
+
160
+ const node2 = new Kabel.NodeSvg(Kabel.Nodes['my_node'], ws);
161
+ node2.init();
162
+ node2.relativeCoords.set(20, 0);
163
+ ```
164
+
165
+ Connect them:
166
+
167
+ ```js
168
+ node.nextConnection.to = node2;
169
+ node2.previousConnection.from = node;
170
+ ws.redraw();
171
+ ```
172
+
173
+ You can also manipulate the SVG directly (temporary, not saved across rerenders):
174
+
175
+ ```js
176
+ node.svg.highlight('#ff0');
177
+ node.svg.setScale(1);
178
+ node.svg.moveTo(0, 1);
179
+ node.svg.applyTransform('translate(0, 1)');
180
+ node.svg.getRaw(); // Returns the svg.js G element
181
+ ```
182
+
183
+ ---
184
+
185
+ ### Optional: Widgets
186
+
187
+ **Widgets** are custom UI overlays within the workspace:
188
+
189
+ ```js
190
+ Kabel.Widgets['testWidget'] = {
191
+ name: 'Test Widget',
192
+ width: 150,
193
+ height: 100,
194
+ coords: new Kabel.Coordinates(50, 50),
195
+ html: '<button id="counterBtn">Count: 0</button>',
196
+ init(widget, container) {
197
+ let count = 0;
198
+ const btn = container.querySelector('#counterBtn');
199
+ if (btn) {
200
+ btn.addEventListener('click', () => {
201
+ count++;
202
+ btn.textContent = 'Count: ' + count;
203
+ });
204
+ }
205
+ }
206
+ };
207
+
208
+ const myWidget = ws.newWidget('testWidget');
209
+ if (myWidget) myWidget.show();
210
+ ```
211
+
212
+ Widgets let you add custom interactivity or information panels.
213
+
214
+ ---
215
+
216
+ ## 6. Serializing Nodes
217
+
218
+ Kabel supports both **circular** and **non-circular** JSON serialization.
219
+
220
+ ### Circular Serialization
221
+
222
+ Contains live object references (not exportable as plain JSON, but usable within JS).
223
+
224
+ ```js
225
+ const circular = node.serialize();
226
+ ```
227
+
228
+ Example (simplified):
229
+
230
+ ```json
231
+ {
232
+ "id": "sisDG24Gp6lc7*S#m#I9c",
233
+ "type": "my_node",
234
+ "nextConnection": {
235
+ "node": "[CircularNodeRef]"
236
+ },
237
+ "fields": [...]
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ### Non-Circular Serialization
244
+
245
+ Safe for exporting or saving to files:
246
+
247
+ ```js
248
+ const nonCircular = node.toJson();
249
+ ```
250
+
251
+ Example:
252
+
253
+ ```json
254
+ {
255
+ "7f7#2Rrt16uFuH^2f2^ad": {
256
+ "id": "7f7#2Rrt16uFuH^2f2^ad",
257
+ "type": "my_node",
258
+ "colors": { "primary": "#cc0c00", "tertiary": "#660500" },
259
+ "label": "TopbarText",
260
+ "nextConnection": { "node": "jX^$kh)yHW5p^bx98vD#s" },
261
+ "fields": [...]
262
+ }
263
+ }
264
+ ```
265
+
266
+ ---
267
+
268
+ ### Workspace Serialization
269
+
270
+ ```js
271
+ // Serialize
272
+ const data = workspace.toJson(false); // false = non-circular
273
+
274
+ // Deserialize
275
+ workspace.fromJson(data);
276
+ ```
277
+
278
+ The serialization format automatically includes the circular/non-circular flag internally.
279
+
280
+ ---
281
+
282
+ ## 7. Finishing Up
283
+
284
+ You can expose internals for quick debugging:
285
+
286
+ ```js
287
+ window.ws = ws;
288
+ window.nodes = [node, node2];
289
+ ```
290
+
291
+ That’s it — you now have a working Kabel workspace with nodes, a toolbox, and serialization support.
292
+
293
+ ---
Binary file
@@ -0,0 +1,228 @@
1
+ import Renderer from '../renderers/renderer';
2
+ import WorkspaceSvg from '../src/workspace-svg';
3
+ import NodeSvg from '../src/nodesvg';
4
+ import CommentModel from '../src/comment';
5
+ import { Element, SVG, Svg } from '@svgdotjs/svg.js';
6
+ import eventer from '../util/eventer';
7
+
8
+ class CommentRenderer {
9
+ static get NAME() { return 'cr_atlas'; }
10
+ static get COMMENT_G_TAG() { return 'KabelCommentGroup'; }
11
+ static get COMMENT_LINE_TAG() { return 'KabelCommentLine'; }
12
+ static get COMMENT_TEXT_EL() { return 'KabelCommentText'; }
13
+ static get COMMENT_DRAG_EL() { return 'KabelCommentDragHandle'; }
14
+ static get RENDERER_TAG_EL() { return 'KabelCommentElTag'; }
15
+
16
+ workspace: WorkspaceSvg;
17
+
18
+ constructor(workspace: WorkspaceSvg) {
19
+ this.workspace = workspace;
20
+ }
21
+
22
+ encodeForSvg(s: string) {
23
+ return s.replace(/ /g, "\u00A0");
24
+ }
25
+
26
+ ensureTspansPreserve(node: HTMLElement) {
27
+ Array.from(node.childNodes).forEach((child) => {
28
+ if (child.nodeType === 1) {
29
+ (child as HTMLElement).setAttribute("xml:space", "preserve");
30
+ }
31
+ });
32
+ }
33
+
34
+ measureTextBbox(textEl: Element) {
35
+ return textEl.bbox();
36
+ }
37
+
38
+ drawComment(comment: CommentModel) {
39
+ const svg = this.getSvg();
40
+ this._removeExistingElements(comment);
41
+
42
+ const g = svg.group().addClass(CommentRenderer.COMMENT_G_TAG);
43
+ g.attr({
44
+ 'comment-data': JSON.stringify({
45
+ id: comment.id,
46
+ isws: comment.isWorkspaceComment()
47
+ })
48
+ });
49
+ comment.svgGroup = g;
50
+
51
+ const padding = 4;
52
+ const handleRadius = 6;
53
+ const textOffsetX = padding + handleRadius * 2 + 4;
54
+
55
+ const displayEncoded = this.encodeForSvg(comment.getText());
56
+ const textEl = this._createTextElement(g, displayEncoded, textOffsetX, padding);
57
+
58
+ const bbox = this.measureTextBbox(textEl);
59
+
60
+ const rect = this._createBackgroundRect(g, bbox, padding, handleRadius);
61
+ const dragHandle = this._createDragHandle(g, bbox, padding, handleRadius);
62
+
63
+ this._addEventer(rect, dragHandle, comment, textEl);
64
+
65
+ const { screenPos, zoom } = this._computeScreenPosition(comment);
66
+ g.attr({ transform: `translate(${screenPos.x}, ${screenPos.y}) scale(${zoom})` });
67
+
68
+ if (comment._parent instanceof NodeSvg && comment._parent.svgGroup) {
69
+ this._drawLineToNode(comment, padding);
70
+ }
71
+ }
72
+
73
+ refreshCommentTransforms() {
74
+ for (const comment of [
75
+ ...this.workspace.getComments(),
76
+ ...Array.from(this.workspace._nodeDB.values())
77
+ .map(n => n.getComment())
78
+ .filter(c => !!c) as CommentModel[]
79
+ ]) {
80
+ if (!comment.svgGroup) continue;
81
+
82
+ const { screenPos, zoom } = this._computeScreenPosition(comment);
83
+ comment.svgGroup.attr({ transform: `translate(${screenPos.x}, ${screenPos.y}) scale(${zoom})` });
84
+
85
+ if (comment.svgLine) {
86
+ comment.svgLine.remove();
87
+ comment.svgLine = undefined;
88
+ }
89
+
90
+ if (comment._parent instanceof NodeSvg) {
91
+ this._drawLineToNode(comment, 4);
92
+ }
93
+ }
94
+ }
95
+
96
+ private _removeExistingElements(comment: CommentModel) {
97
+ if (comment.svgGroup) comment.svgGroup.remove();
98
+ if (comment.svgLine) comment.svgLine.remove();
99
+ }
100
+
101
+ private _createTextElement(parent: Element, text: string, x: number, y: number) {
102
+ //@ts-ignore
103
+ const textEl = parent.text(text)
104
+ .font({ family: 'Arial', size: 12 })
105
+ .fill('#000')
106
+ .move(x, y)
107
+ .addClass(CommentRenderer.COMMENT_TEXT_EL);
108
+
109
+ textEl.node.style.userSelect = 'none';
110
+ // @ts-ignore
111
+ textEl.node.style!.webkitUserSelect = 'none';
112
+ // @ts-ignore
113
+ textEl.node.style!.msUserSelect = 'none';
114
+ // @ts-ignore
115
+ textEl.node.style!.MozUserSelect = 'none';
116
+ return textEl;
117
+ }
118
+
119
+ private _createBackgroundRect(parent: Element, bbox: any, padding: number, handleRadius: number) {
120
+ //@ts-ignore
121
+ return parent.rect(bbox.width + padding * 2 + handleRadius * 2 + 4, bbox.height + padding * 2)
122
+ .fill('#ffffcc')
123
+ .stroke({ color: '#cccc99', width: 1 })
124
+ .radius(4)
125
+ .back();
126
+ }
127
+
128
+ private _createDragHandle(parent: Element, bbox: any, padding: number, handleRadius: number) {
129
+ //@ts-ignore
130
+ return parent.circle(handleRadius * 2)
131
+ .fill('#adad7d')
132
+ .move(padding, padding + (bbox.height / 2) - handleRadius)
133
+ .addClass(CommentRenderer.COMMENT_DRAG_EL);
134
+ }
135
+
136
+ private _addEventer(rect: Element, dragHandle: Element, comment: CommentModel, textEl: Element) {
137
+ eventer.addElement(dragHandle, 'k_draghandle', { comment })
138
+ .tagElement(dragHandle, CommentRenderer.RENDERER_TAG_EL);
139
+ eventer.addElement(rect, "k_commentinp", { comment, text: textEl, renderer: this })
140
+ .tagElement(rect, CommentRenderer.RENDERER_TAG_EL);
141
+ }
142
+
143
+ private _computeScreenPosition(comment: CommentModel) {
144
+ let workspaceX = comment.relativeCoords.x;
145
+ let workspaceY = comment.relativeCoords.y;
146
+
147
+ if (comment._parent instanceof NodeSvg) {
148
+ const nodePos = comment._parent.relativeCoords;
149
+ workspaceX += nodePos.x;
150
+ workspaceY += nodePos.y;
151
+ }
152
+
153
+ const screenPos = this.workspace.workspaceToScreen(workspaceX, workspaceY);
154
+ const zoom = this.workspace.getZoom();
155
+ return { screenPos, zoom };
156
+ }
157
+
158
+ private _drawLineToNode(comment: CommentModel, padding: number) {
159
+ if (!comment || !(comment._parent instanceof NodeSvg) || !comment.svgGroup) return;
160
+ const svg = this.getSvg();
161
+ const ws = this.workspace;
162
+
163
+ if (comment.svgLine) comment.svgLine.remove();
164
+
165
+ const nodeWSPos = comment._parent.relativeCoords;
166
+ const nodeBBox = comment._parent.svgGroup!.bbox();
167
+ const nodeTopCenter = ws.workspaceToScreen(
168
+ nodeWSPos.x + nodeBBox.width / 2,
169
+ nodeWSPos.y
170
+ );
171
+
172
+ const parentWSPos = comment._parent.relativeCoords;
173
+ const commentRelPos = comment.relativeCoords;
174
+ const commentAbsX = parentWSPos.x + commentRelPos.x;
175
+ const commentAbsY = parentWSPos.y + commentRelPos.y;
176
+ const commentBBox = comment.svgGroup!.bbox();
177
+ const commentTopCenter = ws.workspaceToScreen(
178
+ commentAbsX + (commentBBox.width + padding * 2) / 2,
179
+ commentAbsY
180
+ );
181
+
182
+ comment.svgLine = svg.line(
183
+ nodeTopCenter.x, nodeTopCenter.y,
184
+ commentTopCenter.x, commentTopCenter.y
185
+ )
186
+ .stroke({ color: '#888', width: 1, dasharray: '3,2' })
187
+ .addClass(CommentRenderer.COMMENT_LINE_TAG)
188
+ .back();
189
+ }
190
+
191
+ clearAllComments() {
192
+ const svg = this.getSvg();
193
+ svg.find(`.${CommentRenderer.COMMENT_G_TAG}`).forEach(el => el.remove());
194
+ svg.find(`.${CommentRenderer.COMMENT_LINE_TAG}`).forEach(el => el.remove());
195
+ eventer.destroyByTag(CommentRenderer.RENDERER_TAG_EL);
196
+
197
+ for (let [_, node] of this.workspace._nodeDB) {
198
+ const comment = node.getComment();
199
+ if (comment) comment.svgGroup = undefined;
200
+ }
201
+
202
+ for (const comment of this.workspace.getComments()) {
203
+ comment.svgGroup = undefined;
204
+ }
205
+ }
206
+
207
+ clearCommentLines() {
208
+ const svg = this.getSvg();
209
+ svg.find(`.${CommentRenderer.COMMENT_LINE_TAG}`).forEach(el => el.remove());
210
+ }
211
+
212
+ drawAllComments() {
213
+ for (const comment of this.workspace.getComments()) this.drawComment(comment);
214
+ for (let [_, node] of this.workspace._nodeDB) {
215
+ const comment = node.getComment();
216
+ if (comment) this.drawComment(comment);
217
+ }
218
+ }
219
+
220
+ getSvg(): Svg {
221
+ return this.workspace.svg;
222
+ }
223
+ getWs(): WorkspaceSvg {
224
+ return this.workspace;
225
+ }
226
+ }
227
+
228
+ export default CommentRenderer;