@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.
- package/(1.0.7)kabel.md +18 -0
- package/README.md +96 -0
- package/_READ_ME_MEDIA_/documentation/docs.md +293 -0
- package/_READ_ME_MEDIA_/workspace.png +0 -0
- package/comment-renderer/renderer.ts +228 -0
- package/controllers/base.ts +186 -0
- package/controllers/wasd.ts +132 -0
- package/docs/README.md +98 -0
- package/docs/_media/docs.md +289 -0
- package/docs/_media/index.html +168 -0
- package/docs/_media/workspace.png +0 -0
- package/docs/classes/CommentModel.md +271 -0
- package/docs/classes/CommentRenderer.md +457 -0
- package/docs/classes/ConnectableField.md +597 -0
- package/docs/classes/Connection.md +191 -0
- package/docs/classes/ContextMenuHTML.md +163 -0
- package/docs/classes/Coordinates.md +187 -0
- package/docs/classes/DropdownContainer.md +300 -0
- package/docs/classes/DummyField.md +393 -0
- package/docs/classes/Eventer.md +185 -0
- package/docs/classes/Field.md +461 -0
- package/docs/classes/InjectMsg.md +85 -0
- package/docs/classes/NodeSvg.md +1011 -0
- package/docs/classes/NumberField.md +559 -0
- package/docs/classes/OptConnectField.md +624 -0
- package/docs/classes/Renderer.md +1636 -0
- package/docs/classes/RendererConstants.md +343 -0
- package/docs/classes/Representer.md +95 -0
- package/docs/classes/RepresenterNode.md +175 -0
- package/docs/classes/TextField.md +559 -0
- package/docs/classes/Toolbox.md +172 -0
- package/docs/classes/WASDController.md +616 -0
- package/docs/classes/Widget.md +195 -0
- package/docs/classes/WorkspaceController.md +385 -0
- package/docs/classes/WorkspaceCoords.md +218 -0
- package/docs/classes/WorkspaceSvg.md +1380 -0
- package/docs/functions/clearMainWorkspace.md +20 -0
- package/docs/functions/getMainWorkspace.md +19 -0
- package/docs/functions/inject.md +35 -0
- package/docs/functions/setMainWorkspace.md +28 -0
- package/docs/globals.md +95 -0
- package/docs/interfaces/ColorStyle.md +43 -0
- package/docs/interfaces/ConnectorToFrom.md +57 -0
- package/docs/interfaces/DrawState.md +81 -0
- package/docs/interfaces/FieldConnectionData.md +25 -0
- package/docs/interfaces/FieldOptions.md +63 -0
- package/docs/interfaces/FieldRawBoxData.md +25 -0
- package/docs/interfaces/FieldVisualInfo.md +65 -0
- package/docs/interfaces/GridOptions.md +61 -0
- package/docs/interfaces/InjectOptions.md +133 -0
- package/docs/interfaces/InputFieldJson.md +50 -0
- package/docs/interfaces/KabelCommentRendering.md +31 -0
- package/docs/interfaces/KabelInterface.md +469 -0
- package/docs/interfaces/KabelNodeRendering.md +77 -0
- package/docs/interfaces/KabelUIX.md +105 -0
- package/docs/interfaces/KabelUtils.md +215 -0
- package/docs/interfaces/NodeEvents.md +42 -0
- package/docs/interfaces/NodeJson.md +104 -0
- package/docs/interfaces/NodePrototype.md +82 -0
- package/docs/interfaces/RegisteredEl.md +53 -0
- package/docs/interfaces/SerializedNode.md +128 -0
- package/docs/interfaces/TblxCategoryStruct.md +41 -0
- package/docs/interfaces/TblxFieldStruct.md +28 -0
- package/docs/interfaces/TblxNodeStruct.md +35 -0
- package/docs/interfaces/WidgetOptions.md +115 -0
- package/docs/interfaces/WidgetPrototypeList.md +15 -0
- package/docs/type-aliases/AnyField.md +13 -0
- package/docs/type-aliases/AnyFieldCls.md +13 -0
- package/docs/type-aliases/Color.md +13 -0
- package/docs/type-aliases/Connectable.md +13 -0
- package/docs/type-aliases/EventArgs.md +11 -0
- package/docs/type-aliases/EventSetupFn.md +25 -0
- package/docs/type-aliases/Hex.md +13 -0
- package/docs/type-aliases/RGBObject.md +37 -0
- package/docs/type-aliases/RGBString.md +13 -0
- package/docs/type-aliases/RGBTuple.md +13 -0
- package/docs/type-aliases/TblxObjStruct.md +52 -0
- package/docs/variables/CategoryColors.md +29 -0
- package/docs/variables/FieldMap.md +41 -0
- package/docs/variables/NodePrototypes.md +18 -0
- package/docs/variables/default.md +11 -0
- package/events/comment-drag-handle.ts +61 -0
- package/events/comment-input.ts +291 -0
- package/events/connection-line.ts +68 -0
- package/events/connector.ts +116 -0
- package/events/draggable.ts +119 -0
- package/events/events.ts +7 -0
- package/events/input-box.ts +213 -0
- package/events/node-x-btn.ts +25 -0
- package/index.d.ts +4 -0
- package/package.json +49 -0
- package/renderers/apollo/apollo.ts +21 -0
- package/renderers/apollo/constants.ts +40 -0
- package/renderers/apollo/renderer.ts +331 -0
- package/renderers/atlas/atlas.ts +15 -0
- package/renderers/constants.ts +87 -0
- package/renderers/renderer.ts +1288 -0
- package/renderers/representer-node.ts +52 -0
- package/renderers/representer.ts +25 -0
- package/src/category.ts +107 -0
- package/src/colors.ts +20 -0
- package/src/comment.ts +142 -0
- package/src/connection.ts +114 -0
- package/src/context-menu.ts +194 -0
- package/src/coordinates.ts +74 -0
- package/src/core.ts +202 -0
- package/src/ctx-menu-registry.ts +143 -0
- package/src/dropdown-menu.ts +215 -0
- package/src/field.ts +595 -0
- package/src/flyout.ts +165 -0
- package/src/fonts-manager.ts +38 -0
- package/src/grid.ts +162 -0
- package/src/headless-node.ts +27 -0
- package/src/index.ts +115 -0
- package/src/inject-headless.ts +18 -0
- package/src/inject.ts +213 -0
- package/src/main-workspace.ts +51 -0
- package/src/mutator.ts +40 -0
- package/src/node-types.ts +27 -0
- package/src/nodesvg.ts +756 -0
- package/src/prototypes.ts +9 -0
- package/src/renderer-map.ts +86 -0
- package/src/styles.css +224 -0
- package/src/toolbox.ts +125 -0
- package/src/types.ts +205 -0
- package/src/undo-redo.ts +87 -0
- package/src/visual-types.ts +29 -0
- package/src/widget-prototypes.ts +11 -0
- package/src/widget.ts +139 -0
- package/src/workspace-coords.ts +14 -0
- package/src/workspace-svg.ts +736 -0
- package/src/workspace.ts +155 -0
- package/test-server.js +61 -0
- package/themes/dark.ts +32 -0
- package/themes/default.ts +28 -0
- package/themes/themes.ts +9 -0
- package/tsconfig.json +25 -0
- package/typedoc.json +10 -0
- package/util/emitter.ts +33 -0
- package/util/env.ts +11 -0
- package/util/escape-html.ts +22 -0
- package/util/eventer.ts +108 -0
- package/util/has-prop.ts +4 -0
- package/util/parse-color.ts +42 -0
- package/util/path.ts +99 -0
- package/util/styler.ts +41 -0
- package/util/uid.ts +184 -0
- package/util/unescape-html.ts +22 -0
- package/util/user-state.ts +68 -0
- package/util/wait-anim-frames.ts +24 -0
- package/util/window-listeners.ts +62 -0
- package/webpack.config.js +80 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a 2D coordinate in space.
|
|
3
|
+
*/
|
|
4
|
+
class Coordinates {
|
|
5
|
+
/** X position */
|
|
6
|
+
x: number;
|
|
7
|
+
/** Y position */
|
|
8
|
+
y: number;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new Coordinates instance
|
|
12
|
+
* @param x - The x value (default 0)
|
|
13
|
+
* @param y - The y value (default 0)
|
|
14
|
+
*/
|
|
15
|
+
constructor(x: number = 0, y: number = 0) {
|
|
16
|
+
this.x = x;
|
|
17
|
+
this.y = y;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set the coordinates to new values
|
|
22
|
+
* @param x - The new x value
|
|
23
|
+
* @param y - The new y value
|
|
24
|
+
*/
|
|
25
|
+
set(x: number, y: number) {
|
|
26
|
+
this.x = x;
|
|
27
|
+
this.y = y;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns a copy of this coordinate
|
|
32
|
+
* @returns A new Coordinates instance with the same x and y
|
|
33
|
+
*/
|
|
34
|
+
clone(): Coordinates {
|
|
35
|
+
return new Coordinates(this.x, this.y);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculate the Euclidean distance to another coordinate
|
|
40
|
+
* @param other - The target coordinate
|
|
41
|
+
* @returns The distance as a number
|
|
42
|
+
*/
|
|
43
|
+
distanceTo(other: Coordinates): number {
|
|
44
|
+
const dx = this.x - other.x;
|
|
45
|
+
const dy = this.y - other.y;
|
|
46
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert the coordinate to a string representation
|
|
51
|
+
* @returns A string like "(x, y)"
|
|
52
|
+
*/
|
|
53
|
+
toString(): string {
|
|
54
|
+
return `(${this.x}, ${this.y})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Convert the coordinate to an array [x, y]
|
|
59
|
+
* @returns Tuple of numbers [x, y]
|
|
60
|
+
*/
|
|
61
|
+
toArray(): [number, number] {
|
|
62
|
+
return [this.x, this.y];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Convert the coordinate to an object { x, y }
|
|
67
|
+
* @returns Object with x and y properties
|
|
68
|
+
*/
|
|
69
|
+
toObject(): { x: number; y: number } {
|
|
70
|
+
return { x: this.x, y: this.y };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default Coordinates;
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import RendererConstants from '../renderers/constants'
|
|
2
|
+
import Renderer from '../renderers/renderer'
|
|
3
|
+
import CategoryColors from './colors'
|
|
4
|
+
import Connection, { Connectable } from './connection'
|
|
5
|
+
import Coordinates from './coordinates'
|
|
6
|
+
import Field, {
|
|
7
|
+
FieldOptions,
|
|
8
|
+
FieldVisualInfo,
|
|
9
|
+
AnyFieldCls,
|
|
10
|
+
AnyField,
|
|
11
|
+
DummyField,
|
|
12
|
+
FieldMap,
|
|
13
|
+
NumberField,
|
|
14
|
+
OptConnectField,
|
|
15
|
+
TextField
|
|
16
|
+
} from './field'
|
|
17
|
+
import inject, { InjectMsg, InjectOptions } from './inject'
|
|
18
|
+
import {
|
|
19
|
+
clearMainWorkspace,
|
|
20
|
+
getMainWorkspace,
|
|
21
|
+
setMainWorkspace
|
|
22
|
+
} from './main-workspace'
|
|
23
|
+
import NodeSvg, { NodeJson, NodeEvents, InputFieldJson } from './nodesvg'
|
|
24
|
+
import NodePrototypes from './prototypes'
|
|
25
|
+
import WorkspaceSvg from './workspace-svg'
|
|
26
|
+
import { NodePrototype } from './node-types'
|
|
27
|
+
import {
|
|
28
|
+
Color,
|
|
29
|
+
ColorStyle,
|
|
30
|
+
Hex,
|
|
31
|
+
RGBObject,
|
|
32
|
+
RGBString,
|
|
33
|
+
RGBTuple
|
|
34
|
+
} from './visual-types'
|
|
35
|
+
import { parseColor } from '../util/parse-color'
|
|
36
|
+
import eventer, { Eventer } from '../util/eventer'
|
|
37
|
+
import * as Path from '../util/path'
|
|
38
|
+
import * as SVG from '@svgdotjs/svg.js'
|
|
39
|
+
import * as UID from '../util/uid'
|
|
40
|
+
import hasProp from '../util/has-prop'
|
|
41
|
+
import EventEmitter from '../util/emitter'
|
|
42
|
+
import userState from '../util/user-state'
|
|
43
|
+
import '../events/events'
|
|
44
|
+
import WorkspaceController from '../controllers/base'
|
|
45
|
+
import WASDController from '../controllers/wasd'
|
|
46
|
+
import { RMap, RendererMap } from './renderer-map'
|
|
47
|
+
import styler, { Styler } from '../util/styler'
|
|
48
|
+
import WidgetPrototypes from './widget-prototypes'
|
|
49
|
+
import Widget from './widget'
|
|
50
|
+
import ContextOptsRegistry, { ContextMenu } from './ctx-menu-registry'
|
|
51
|
+
import { Showable } from './context-menu'
|
|
52
|
+
import escapeAttr from '../util/escape-html'
|
|
53
|
+
import unescapeAttr from '../util/unescape-html'
|
|
54
|
+
import waitFrames from '../util/wait-anim-frames'
|
|
55
|
+
import CommentModel from './comment'
|
|
56
|
+
import CommentRenderer from '../comment-renderer/renderer'
|
|
57
|
+
import dropdownContainer from './dropdown-menu'
|
|
58
|
+
import Representer from '../renderers/representer'
|
|
59
|
+
import { RepresenterNode } from '../renderers/representer-node'
|
|
60
|
+
import windowListeners, { addWindowListener, clearWindowListeners, removeWindowListener } from '../util/window-listeners'
|
|
61
|
+
import * as FontManager from './fonts-manager';
|
|
62
|
+
import env from "../util/env";
|
|
63
|
+
import Workspace from './workspace'
|
|
64
|
+
import injectHeadless from './inject-headless'
|
|
65
|
+
import createHeadlessNode from './headless-node';
|
|
66
|
+
import * as apollo from '../renderers/apollo/apollo';
|
|
67
|
+
import * as atlas from '../renderers/atlas/atlas';
|
|
68
|
+
import KabelWSTheme from '../themes/default'
|
|
69
|
+
import KabelDarkTheme from '../themes/dark'
|
|
70
|
+
/** Register default renderers. */
|
|
71
|
+
RendererMap['default'] = atlas.Renderer;
|
|
72
|
+
RendererMap[atlas.Renderer.NAME] = atlas.Renderer;
|
|
73
|
+
RendererMap[apollo.Renderer.NAME] = apollo.Renderer;
|
|
74
|
+
|
|
75
|
+
if (env.isBrowser) {
|
|
76
|
+
// Use FontsManager to load default Kabel fonts.
|
|
77
|
+
FontManager.loadGoogleFont('Fredoka');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Utility method to register a field globally by name
|
|
81
|
+
* @param name - Field identifier
|
|
82
|
+
* @param cls - Class constructor for the field
|
|
83
|
+
*/
|
|
84
|
+
Field.register = function (name: string, cls: Function) {
|
|
85
|
+
FieldMap[name] = cls as AnyFieldCls
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Utility method to unregister a field globally by name
|
|
90
|
+
* @param name - Field identifier
|
|
91
|
+
*/
|
|
92
|
+
Field.unregister = function (name: string) {
|
|
93
|
+
delete FieldMap[name]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Central Kabel object exposing all main modules, utilities, and defaults
|
|
98
|
+
*/
|
|
99
|
+
const Kabel = {
|
|
100
|
+
env, // Environment information
|
|
101
|
+
UIX: { // User experience enhancing utilities.
|
|
102
|
+
/** Event manager, loads events from '../events' and lets us attach them to svg.js elements to give them behavior that's seperated from the renderer. */
|
|
103
|
+
events: eventer as Eventer,
|
|
104
|
+
/** Font manager, used to load fonts. */
|
|
105
|
+
FontManager,
|
|
106
|
+
/** * State Manager, Makes things possible: E.G (the 'typing' state when you type in a input box..) * Used in controllers so you dont move when typing characters like a w s or d etc. */
|
|
107
|
+
userState,
|
|
108
|
+
/** Window listeners manager */
|
|
109
|
+
windowListeners: {
|
|
110
|
+
addWindowListener,
|
|
111
|
+
removeWindowListener,
|
|
112
|
+
clearWindowListeners,
|
|
113
|
+
windowListeners
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
Themes: {
|
|
117
|
+
Classic: KabelWSTheme,
|
|
118
|
+
Dark: KabelDarkTheme
|
|
119
|
+
},
|
|
120
|
+
/** Context menu manager */
|
|
121
|
+
ContextMenu,
|
|
122
|
+
/**
|
|
123
|
+
* Utility methods and constants for various purposes.
|
|
124
|
+
* @property Path - Utility methods for handling SVG paths.
|
|
125
|
+
* @property waitFrames - Utility method to wait for a certain number of animation frames.
|
|
126
|
+
*/
|
|
127
|
+
Utils: {
|
|
128
|
+
Path,
|
|
129
|
+
waitFrames,
|
|
130
|
+
SVG, // Re-exporting svg.js for convenience
|
|
131
|
+
parseColor,
|
|
132
|
+
UID,
|
|
133
|
+
EventEmitter,
|
|
134
|
+
hasProp,
|
|
135
|
+
styler,
|
|
136
|
+
Styler,
|
|
137
|
+
escapeHTML: escapeAttr,
|
|
138
|
+
unescapeHTML: unescapeAttr
|
|
139
|
+
},
|
|
140
|
+
Widget,
|
|
141
|
+
CategoryColors,
|
|
142
|
+
Connection,
|
|
143
|
+
Coordinates,
|
|
144
|
+
Field,
|
|
145
|
+
DummyField,
|
|
146
|
+
FieldMap,
|
|
147
|
+
NumberField,
|
|
148
|
+
OptConnectField,
|
|
149
|
+
TextField,
|
|
150
|
+
inject,
|
|
151
|
+
injectHeadless,
|
|
152
|
+
createHeadlessNode,
|
|
153
|
+
InjectMsg,
|
|
154
|
+
clearMainWorkspace,
|
|
155
|
+
getMainWorkspace,
|
|
156
|
+
setMainWorkspace,
|
|
157
|
+
NodeSvg,
|
|
158
|
+
Nodes: NodePrototypes,
|
|
159
|
+
Widgets: WidgetPrototypes,
|
|
160
|
+
WorkspaceSvg,
|
|
161
|
+
Workspace,
|
|
162
|
+
WorkspaceController,
|
|
163
|
+
WASDController,
|
|
164
|
+
nodeRendering: {
|
|
165
|
+
SVG: SVG, // also re-export svg.js here for easier access in renderers
|
|
166
|
+
rendererMap: RMap,
|
|
167
|
+
Apollo: apollo,
|
|
168
|
+
Atlas: atlas,
|
|
169
|
+
Renderer: Renderer,
|
|
170
|
+
RendererConstants: RendererConstants,
|
|
171
|
+
Representer: Representer,
|
|
172
|
+
RepresenterNode: RepresenterNode
|
|
173
|
+
},
|
|
174
|
+
atlas,
|
|
175
|
+
apollo,
|
|
176
|
+
commentRendering: { CommentModel, CommentRenderer },
|
|
177
|
+
Dropdown: dropdownContainer
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Provides a getter/setter for the main workspace
|
|
182
|
+
* @property _mainWorkspace - Get or set the currently active workspace
|
|
183
|
+
*/
|
|
184
|
+
Object.defineProperty(Kabel, '_mainWorkspace', {
|
|
185
|
+
get(): WorkspaceSvg | Workspace | null {
|
|
186
|
+
return getMainWorkspace()
|
|
187
|
+
},
|
|
188
|
+
set(v: WorkspaceSvg | Workspace | undefined | null | false | 0 | string) {
|
|
189
|
+
if (
|
|
190
|
+
v === undefined ||
|
|
191
|
+
v === null ||
|
|
192
|
+
v === false ||
|
|
193
|
+
v === 0 ||
|
|
194
|
+
typeof v === 'string'
|
|
195
|
+
) {
|
|
196
|
+
return clearMainWorkspace()
|
|
197
|
+
}
|
|
198
|
+
return setMainWorkspace(v)
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
export default Kabel
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import CommentModel from "./comment";
|
|
2
|
+
import { ContextMenuOpts, Showable } from "./context-menu";
|
|
3
|
+
import NodeSvg from "./nodesvg";
|
|
4
|
+
import WorkspaceSvg from "./workspace-svg";
|
|
5
|
+
|
|
6
|
+
/** Registry for all context menu options */
|
|
7
|
+
const ContextOptsRegistry: ContextMenuOpts[] = [];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Global context menu manager
|
|
11
|
+
*/
|
|
12
|
+
const ContextMenu = {
|
|
13
|
+
/**
|
|
14
|
+
* Register a new context menu option
|
|
15
|
+
* @param id - Unique identifier for the option
|
|
16
|
+
* @param option - Configuration for the context menu item
|
|
17
|
+
*/
|
|
18
|
+
registerOption(id: string, option: Omit<ContextMenuOpts, 'id'>) {
|
|
19
|
+
const opt = {
|
|
20
|
+
id,
|
|
21
|
+
click: option.click,
|
|
22
|
+
label: option.label,
|
|
23
|
+
onHoverStart: option.onHoverStart || (() => { }),
|
|
24
|
+
onHoverEnd: option.onHoverEnd || (() => { }),
|
|
25
|
+
showFor: option.showFor || 'any'
|
|
26
|
+
};
|
|
27
|
+
ContextOptsRegistry.push(opt);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Unregister an existing context menu option by ID
|
|
32
|
+
* @param id - ID of the option to remove
|
|
33
|
+
*/
|
|
34
|
+
unregisterOption(id: string) {
|
|
35
|
+
const index = ContextOptsRegistry.findIndex(opt => opt.id === id);
|
|
36
|
+
if (index >= 0) ContextOptsRegistry.splice(index, 1);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ----- Default options -----
|
|
41
|
+
|
|
42
|
+
ContextMenu.registerOption('k_delete', {
|
|
43
|
+
showFor: 'node',
|
|
44
|
+
label: 'Delete',
|
|
45
|
+
click: (t) => {
|
|
46
|
+
const target = t as NodeSvg;
|
|
47
|
+
if (!target.workspace) return;
|
|
48
|
+
target.workspace.removeNode(target);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
ContextMenu.registerOption('k_deleteall', {
|
|
53
|
+
showFor: 'ws',
|
|
54
|
+
label: 'Delete all',
|
|
55
|
+
click: (t) => {
|
|
56
|
+
const target = t as WorkspaceSvg;
|
|
57
|
+
const isSure = window.confirm(`Are you sure you want to delete ${Array.from(target._nodeDB.keys()).length} nodes?`);
|
|
58
|
+
if (!isSure) return;
|
|
59
|
+
for (let [id, _] of target._nodeDB) {
|
|
60
|
+
target.removeNodeById(id);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
ContextMenu.registerOption('k_addcomment', {
|
|
66
|
+
showFor: ['ws', 'node'],
|
|
67
|
+
label: 'Add Comment',
|
|
68
|
+
click: (t) => {
|
|
69
|
+
const target = t;
|
|
70
|
+
if (target instanceof NodeSvg) {
|
|
71
|
+
target.addComment();
|
|
72
|
+
target.setCommentText('Comment!');
|
|
73
|
+
} else if (target instanceof WorkspaceSvg) {
|
|
74
|
+
const model = target.addComment();
|
|
75
|
+
const pos = target.screenToWorkspace(target._ctxMenu.widget.coords.x, target._ctxMenu.widget.coords.y);
|
|
76
|
+
model.relativeCoords.set(pos.x, pos.y);
|
|
77
|
+
model.setText('Comment!');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
ContextMenu.registerOption('k_undo', {
|
|
82
|
+
showFor: ['ws', 'node', 'comment'],
|
|
83
|
+
label: 'Undo',
|
|
84
|
+
click: (t) => {
|
|
85
|
+
if (t instanceof NodeSvg) {
|
|
86
|
+
t.workspace?.history?.undo();
|
|
87
|
+
} else if (t instanceof CommentModel) {
|
|
88
|
+
t.getWorkspace()?.history?.undo();
|
|
89
|
+
} else if (t instanceof WorkspaceSvg) {
|
|
90
|
+
t.history.undo();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
onDraw(el, ws, opt) {
|
|
94
|
+
console.log('onDrawCalled');
|
|
95
|
+
if (!ws.history.canUndo()) {
|
|
96
|
+
(el as HTMLElement).classList.add('disabled')
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
ContextMenu.registerOption('k_redo', {
|
|
101
|
+
showFor: ['ws', 'node', 'comment'],
|
|
102
|
+
label: 'Redo',
|
|
103
|
+
click: (t) => {
|
|
104
|
+
if (t instanceof NodeSvg) {
|
|
105
|
+
t.workspace?.history?.redo();
|
|
106
|
+
} else if (t instanceof CommentModel) {
|
|
107
|
+
t.getWorkspace()?.history?.redo();
|
|
108
|
+
} else if (t instanceof WorkspaceSvg) {
|
|
109
|
+
t.history.redo();
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
onDraw(el, ws, opt) {
|
|
113
|
+
console.log('onDrawCalled');
|
|
114
|
+
if (!ws.history.canRedo()) {
|
|
115
|
+
(el as HTMLElement).classList.add('disabled')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
ContextMenu.registerOption('k_deletecomment', {
|
|
120
|
+
showFor: 'comment',
|
|
121
|
+
label: 'Delete Comment',
|
|
122
|
+
click: (t) => {
|
|
123
|
+
const target = t as CommentModel;
|
|
124
|
+
if (target.isNodeComment() && target._parent instanceof NodeSvg) {
|
|
125
|
+
target._parent.removeComment();
|
|
126
|
+
} else {
|
|
127
|
+
target.getWorkspace().removeComment(target);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
ContextMenu.registerOption('k_duplicate', {
|
|
133
|
+
showFor: 'node',
|
|
134
|
+
label: 'Duplicate',
|
|
135
|
+
click: t => {
|
|
136
|
+
const node = t as NodeSvg;
|
|
137
|
+
if (!node.workspace) return;
|
|
138
|
+
node.workspace.cloneNode(node);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export { ContextMenu };
|
|
143
|
+
export default ContextOptsRegistry;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { G } from "@svgdotjs/svg.js";
|
|
2
|
+
import Field, { AnyField } from "./field";
|
|
3
|
+
import NodeSvg from "./nodesvg";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Allowed owner types for the dropdown container.
|
|
7
|
+
* Can be either a NodeSvg or a Field.
|
|
8
|
+
*/
|
|
9
|
+
export type AllowedOwner = NodeSvg | AnyField;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get absolute position of an HTMLElement relative to the document.
|
|
13
|
+
* @param el - The HTML element to measure.
|
|
14
|
+
* @returns The bounding box with scroll offset.
|
|
15
|
+
*/
|
|
16
|
+
function getAbsolutePosition(el: HTMLElement) {
|
|
17
|
+
const rect = el.getBoundingClientRect();
|
|
18
|
+
return {
|
|
19
|
+
x: rect.left + window.scrollX,
|
|
20
|
+
y: rect.top + window.scrollY,
|
|
21
|
+
width: rect.width,
|
|
22
|
+
height: rect.height,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Options for creating a dropdown menu.
|
|
28
|
+
*/
|
|
29
|
+
export interface DropdownOptions {
|
|
30
|
+
/** List of items to display in the dropdown */
|
|
31
|
+
items: { label: string; value: string }[];
|
|
32
|
+
/** Callback when an item is selected */
|
|
33
|
+
onSelect?: (value: string, item: { label: string; value: string }) => void;
|
|
34
|
+
/** Optional fixed width of the dropdown */
|
|
35
|
+
width?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Dropdown container for NodeSvg or Field elements.
|
|
40
|
+
* Supports singleton behavior (only one dropdown visible at a time).
|
|
41
|
+
*/
|
|
42
|
+
class DropdownContainer {
|
|
43
|
+
private static current: DropdownContainer | null = null;
|
|
44
|
+
private owner: AllowedOwner | null = null;
|
|
45
|
+
private rootEl: HTMLDivElement;
|
|
46
|
+
private options: DropdownOptions | null = null;
|
|
47
|
+
private constraint: { x: number; y: number; width: number; height: number };
|
|
48
|
+
private offset: { dx: number; dy: number };
|
|
49
|
+
private currentRemoveListener: (() => void) | null = null;
|
|
50
|
+
/**
|
|
51
|
+
* Creates the dropdown container and attaches it to the DOM.
|
|
52
|
+
*/
|
|
53
|
+
constructor() {
|
|
54
|
+
this.rootEl = document.createElement("div");
|
|
55
|
+
this.rootEl.className = "KabelDropdownMenu";
|
|
56
|
+
this.rootEl.style.position = "absolute";
|
|
57
|
+
this.rootEl.style.display = "none";
|
|
58
|
+
document.body.appendChild(this.rootEl);
|
|
59
|
+
|
|
60
|
+
this.constraint = { x: 0, y: 0, width: 0, height: 0 };
|
|
61
|
+
this.offset = { dx: 0, dy: 0 };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Move the dropdown by an offset.
|
|
66
|
+
* @param dx - horizontal offset
|
|
67
|
+
* @param dy - vertical offset
|
|
68
|
+
*/
|
|
69
|
+
move(dx: number, dy: number) {
|
|
70
|
+
this.offset.dx = dx;
|
|
71
|
+
this.offset.dy = dy;
|
|
72
|
+
this.updatePosition();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Update the dropdown position based on constraint and offset.
|
|
77
|
+
*/
|
|
78
|
+
private updatePosition() {
|
|
79
|
+
const { x, y, height } = this.constraint;
|
|
80
|
+
const { dx, dy } = this.offset;
|
|
81
|
+
this.rootEl.style.left = `${x + dx}px`;
|
|
82
|
+
this.rootEl.style.top = `${y + height + dy}px`; // anchored below
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set inner HTML content of the dropdown.
|
|
87
|
+
* @param html - HTML string
|
|
88
|
+
*/
|
|
89
|
+
setContent(html: string) {
|
|
90
|
+
this.rootEl.innerHTML = html;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Append an element as a child to the dropdown.
|
|
95
|
+
* @param element - Element to append
|
|
96
|
+
* @returns The appended element
|
|
97
|
+
*/
|
|
98
|
+
appendChild(element: Element) {
|
|
99
|
+
this.rootEl.appendChild(element);
|
|
100
|
+
return element;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Show the dropdown for a given owner.
|
|
105
|
+
* @param owner - NodeSvg or Field that owns this dropdown
|
|
106
|
+
* @param options - Dropdown configuration options
|
|
107
|
+
*/
|
|
108
|
+
show(owner: AllowedOwner, options: DropdownOptions) {
|
|
109
|
+
if (!owner.svgGroup) return;
|
|
110
|
+
this.hide(); // close existing dropdown first
|
|
111
|
+
this.owner = owner;
|
|
112
|
+
if (options) this.options = options;
|
|
113
|
+
if (this.currentRemoveListener) this.currentRemoveListener();
|
|
114
|
+
this.currentRemoveListener = null;
|
|
115
|
+
const groupRect = owner.svgGroup.node.getBoundingClientRect();
|
|
116
|
+
this.constraint = {
|
|
117
|
+
x: groupRect.left + window.scrollX,
|
|
118
|
+
y: groupRect.top + window.scrollY,
|
|
119
|
+
width: groupRect.width,
|
|
120
|
+
height: groupRect.height,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
this.offset = { dx: 0, dy: 0 };
|
|
124
|
+
|
|
125
|
+
this.rootEl.innerHTML = "";
|
|
126
|
+
if (options.width) this.rootEl.style.width = `${options.width}px`;
|
|
127
|
+
this.rootEl.style.display = "block";
|
|
128
|
+
|
|
129
|
+
// Render items
|
|
130
|
+
if (options.items) {
|
|
131
|
+
options.items.forEach((item) => {
|
|
132
|
+
const el = document.createElement("div");
|
|
133
|
+
el.className = "KabelDropdownItem";
|
|
134
|
+
el.textContent = item.label;
|
|
135
|
+
el.onclick = () => {
|
|
136
|
+
options.onSelect?.(item.value, item);
|
|
137
|
+
this.hide();
|
|
138
|
+
};
|
|
139
|
+
this.rootEl.appendChild(el);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (owner instanceof NodeSvg) {
|
|
143
|
+
// Add a move listener to the node's workspace.
|
|
144
|
+
const ws = owner.workspace;
|
|
145
|
+
let remove = ws?.addMoveListener(() => {
|
|
146
|
+
if (this.owner !== owner) {
|
|
147
|
+
remove!(); // disconnect when owner changes.
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.hideIfOwner(owner)
|
|
151
|
+
});
|
|
152
|
+
this.currentRemoveListener = remove as () => void;
|
|
153
|
+
}
|
|
154
|
+
if (owner instanceof Field) {
|
|
155
|
+
// Add a move listener to the field's workspace.
|
|
156
|
+
const ws = owner.node!.workspace;
|
|
157
|
+
let remove = ws?.addMoveListener(() => {
|
|
158
|
+
if (this.owner !== owner) {
|
|
159
|
+
console.log("Disconnecting..");
|
|
160
|
+
remove!(); // disconnect when owner changes.
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
console.log("Hiding..");
|
|
164
|
+
this.hide();
|
|
165
|
+
});
|
|
166
|
+
this.currentRemoveListener = remove as () => void;
|
|
167
|
+
}
|
|
168
|
+
this.updatePosition();
|
|
169
|
+
DropdownContainer.current = this;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Hide this dropdown.
|
|
174
|
+
*/
|
|
175
|
+
hide() {
|
|
176
|
+
if (DropdownContainer.current !== this) return;
|
|
177
|
+
this.rootEl.style.display = "none";
|
|
178
|
+
this.rootEl.innerHTML = "";
|
|
179
|
+
this.owner = null;
|
|
180
|
+
this.options = null;
|
|
181
|
+
DropdownContainer.current = null;
|
|
182
|
+
if (this.currentRemoveListener) this.currentRemoveListener();
|
|
183
|
+
this.currentRemoveListener = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Hide this dropdown if the given owner currently owns it.
|
|
188
|
+
* @param owner - The owner to check
|
|
189
|
+
*/
|
|
190
|
+
hideIfOwner(owner: AllowedOwner) {
|
|
191
|
+
if (this.owner === owner) {
|
|
192
|
+
this.hide();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** @returns True if the dropdown is currently visible */
|
|
197
|
+
isVisible(): boolean {
|
|
198
|
+
return DropdownContainer.current === this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** @returns The current owner of the dropdown, or null if none */
|
|
202
|
+
getOwner(): AllowedOwner | null {
|
|
203
|
+
return this.owner;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** @returns The currently visible dropdown container singleton */
|
|
207
|
+
static getCurrent(): DropdownContainer | null {
|
|
208
|
+
return DropdownContainer.current;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Singleton export
|
|
213
|
+
const dropdownContainer = new DropdownContainer();
|
|
214
|
+
export default dropdownContainer;
|
|
215
|
+
export { DropdownContainer };
|