@kabel-project/kabel 1.0.7 → 1.0.9
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 +5 -2
- 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
|
@@ -1,228 +0,0 @@
|
|
|
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;
|
package/controllers/base.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import WorkspaceSvg from '../src/workspace-svg';
|
|
2
|
-
import userState from '../util/user-state';
|
|
3
|
-
|
|
4
|
-
interface Vec2 { x: number; y: number; }
|
|
5
|
-
|
|
6
|
-
export default class WorkspaceController {
|
|
7
|
-
workspace: WorkspaceSvg;
|
|
8
|
-
|
|
9
|
-
keysDown: Set<string>;
|
|
10
|
-
mouseBtns: Set<number>;
|
|
11
|
-
mousePos: Vec2;
|
|
12
|
-
lastMousePos: Vec2;
|
|
13
|
-
isDragging: boolean;
|
|
14
|
-
|
|
15
|
-
wheelDelta: number;
|
|
16
|
-
movedListeners: (() => void)[];
|
|
17
|
-
_lastMoveFire = 0;
|
|
18
|
-
_moveThrottleMs = 100; // bump this to whatever doesn't lag you
|
|
19
|
-
_queuedMove = false;
|
|
20
|
-
_moveTimeout: any = null;
|
|
21
|
-
_updateInt: any;
|
|
22
|
-
|
|
23
|
-
constructor(workspace: WorkspaceSvg) {
|
|
24
|
-
this.workspace = workspace;
|
|
25
|
-
|
|
26
|
-
this.keysDown = new Set();
|
|
27
|
-
this.mouseBtns = new Set();
|
|
28
|
-
this.mousePos = { x: 0, y: 0 };
|
|
29
|
-
this.lastMousePos = { x: 0, y: 0 };
|
|
30
|
-
this.isDragging = false;
|
|
31
|
-
this.wheelDelta = 0;
|
|
32
|
-
this.movedListeners = [];
|
|
33
|
-
|
|
34
|
-
this._setupListeners();
|
|
35
|
-
|
|
36
|
-
this._updateInt = setInterval(() => this.update(), 16);
|
|
37
|
-
}
|
|
38
|
-
addMoveListener(cb: () => void) {
|
|
39
|
-
this.movedListeners.push(cb);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
removeMoveListener(cb: () => void) {
|
|
43
|
-
const i = this.movedListeners.indexOf(cb);
|
|
44
|
-
if (i !== -1) this.movedListeners.splice(i, 1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
fireDidMove() {
|
|
48
|
-
if (typeof queueMicrotask !== 'undefined') {
|
|
49
|
-
queueMicrotask(() => {
|
|
50
|
-
for (let cb of this.movedListeners) {
|
|
51
|
-
try { cb(); } catch (e) { console.error(e); }
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
} else {
|
|
55
|
-
Promise.resolve().then(() => {
|
|
56
|
-
for (let cb of this.movedListeners) {
|
|
57
|
-
try { cb(); } catch (e) { console.error(e); }
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
_throttledFireDidMove() {
|
|
64
|
-
const now = performance.now();
|
|
65
|
-
|
|
66
|
-
// enough time passed → fire instantly
|
|
67
|
-
if (now - this._lastMoveFire >= this._moveThrottleMs) {
|
|
68
|
-
this._lastMoveFire = now;
|
|
69
|
-
this.fireDidMove();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// otherwise queue ONE fire
|
|
74
|
-
if (!this._queuedMove) {
|
|
75
|
-
this._queuedMove = true;
|
|
76
|
-
const wait = this._moveThrottleMs - (now - this._lastMoveFire);
|
|
77
|
-
|
|
78
|
-
this._moveTimeout = setTimeout(() => {
|
|
79
|
-
this._queuedMove = false;
|
|
80
|
-
this._lastMoveFire = performance.now();
|
|
81
|
-
this.fireDidMove();
|
|
82
|
-
}, wait);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
getZoom() {
|
|
86
|
-
return 1;
|
|
87
|
-
}
|
|
88
|
-
canMove() {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
private _setupListeners() {
|
|
92
|
-
window.addEventListener('keydown', e => this.keysDown.add(e.key));
|
|
93
|
-
window.addEventListener('keyup', e => this.keysDown.delete(e.key));
|
|
94
|
-
|
|
95
|
-
window.addEventListener('mousedown', e => this.mouseBtns.add(e.button));
|
|
96
|
-
window.addEventListener('mouseup', e => this.mouseBtns.delete(e.button));
|
|
97
|
-
|
|
98
|
-
window.addEventListener('mousemove', e => {
|
|
99
|
-
this.lastMousePos = { ...this.mousePos };
|
|
100
|
-
this.mousePos = { x: e.clientX, y: e.clientY };
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
window.addEventListener('wheel', e => {
|
|
104
|
-
this.wheelDelta = e.deltaY;
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
window.addEventListener('mousedown', e => {
|
|
108
|
-
if (e.button === 0) this.isDragging = true;
|
|
109
|
-
});
|
|
110
|
-
window.addEventListener('mouseup', e => {
|
|
111
|
-
if (e.button === 0) this.isDragging = false;
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
update() {
|
|
116
|
-
// Can handle keyboard shortcuts or auto-pan here
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// --- Camera methods ---
|
|
120
|
-
pan(dx: number, dy: number) {
|
|
121
|
-
const x = this.workspace._camera.x, y = this.workspace._camera.y;
|
|
122
|
-
this.workspace._camera.x += dx;
|
|
123
|
-
this.workspace._camera.y += dy;
|
|
124
|
-
if (x == this.workspace._camera.x && y == this.workspace._camera.y) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
this._throttledFireDidMove();
|
|
128
|
-
this.workspace.didMove = true;
|
|
129
|
-
this.workspace.fireMoveListeners();
|
|
130
|
-
this.refreshPos();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
setCamera(pos: Vec2) {
|
|
134
|
-
const x = this.workspace._camera.x, y = this.workspace._camera.y;
|
|
135
|
-
this.workspace._camera.x = pos.x;
|
|
136
|
-
this.workspace._camera.y = pos.y;
|
|
137
|
-
if (x == this.workspace._camera.x && y == this.workspace._camera.y) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
this._throttledFireDidMove();
|
|
141
|
-
this.workspace.didMove = true;
|
|
142
|
-
this.workspace.fireMoveListeners();
|
|
143
|
-
this.refreshPos();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
centerOn(pos: Vec2) {
|
|
147
|
-
const wsSize = this.workspace.getSize?.() ?? { width: 0, height: 0 };
|
|
148
|
-
this.setCamera({
|
|
149
|
-
x: pos.x - wsSize.width / 2,
|
|
150
|
-
y: pos.y - wsSize.height / 2
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// --- Coordinate conversion ---
|
|
157
|
-
screenToWorkspace(x: number, y: number): Vec2 {
|
|
158
|
-
const cam = this.workspace._camera;
|
|
159
|
-
return {
|
|
160
|
-
x: x + cam.x,
|
|
161
|
-
y: y + cam.y
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
workspaceToScreen(x: number, y: number): Vec2 {
|
|
166
|
-
const cam = this.workspace._camera;
|
|
167
|
-
return {
|
|
168
|
-
x: (x - cam.x),
|
|
169
|
-
y: (y - cam.y)
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// --- Refresh ---
|
|
174
|
-
refreshPos() {
|
|
175
|
-
this.workspace.refresh?.();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
redraw() {
|
|
179
|
-
this.workspace.redraw?.();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// --- Cleanup ---
|
|
183
|
-
stop() {
|
|
184
|
-
clearInterval(this._updateInt);
|
|
185
|
-
}
|
|
186
|
-
}
|
package/controllers/wasd.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import WorkspaceController from './base';
|
|
2
|
-
import WorkspaceSvg from '../src/workspace-svg';
|
|
3
|
-
import userState from '../util/user-state';
|
|
4
|
-
|
|
5
|
-
interface Vec2 { x: number; y: number; }
|
|
6
|
-
|
|
7
|
-
export default class WASDController extends WorkspaceController {
|
|
8
|
-
moveSpeed: number;
|
|
9
|
-
doAccelerate?: boolean;
|
|
10
|
-
accelSpeed: number;
|
|
11
|
-
friction: number;
|
|
12
|
-
velocity: Vec2;
|
|
13
|
-
zoom: number = 1;
|
|
14
|
-
zoomSpeed: number;
|
|
15
|
-
minZoom: number;
|
|
16
|
-
maxZoom: number;
|
|
17
|
-
isFalloff: boolean;
|
|
18
|
-
constructor(workspace: WorkspaceSvg, moveSpeed?: number) {
|
|
19
|
-
super(workspace);
|
|
20
|
-
this.moveSpeed = workspace.options.moveSpeed || moveSpeed || 5;
|
|
21
|
-
|
|
22
|
-
this.doAccelerate = workspace.options?.controls?.wasdSmooth ?? false;
|
|
23
|
-
this.accelSpeed = workspace.options?.controls?.wasdAccelerate ?? 0.2;
|
|
24
|
-
this.friction = workspace.options?.controls?.wasdFriction ?? 0.85;
|
|
25
|
-
|
|
26
|
-
this.velocity = { x: 0, y: 0 };
|
|
27
|
-
this.isFalloff = false;
|
|
28
|
-
|
|
29
|
-
// Zoom settings
|
|
30
|
-
this.zoomSpeed = workspace.options?.controls?.zoomSpeed ?? 0.1;
|
|
31
|
-
this.minZoom = workspace.options?.controls?.minZoom ?? 0.1;
|
|
32
|
-
this.maxZoom = workspace.options?.controls?.maxZoom ?? 4;
|
|
33
|
-
this.zoom = 1;
|
|
34
|
-
// Bind wheel for zoom
|
|
35
|
-
workspace._wsTop.addEventListener('wheel', this.onWheel.bind(this), { passive: false });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
canMove() {
|
|
39
|
-
return !userState.hasState('typing');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
update() {
|
|
43
|
-
super.update();
|
|
44
|
-
if (!this.canMove()) return;
|
|
45
|
-
|
|
46
|
-
let inputX = 0;
|
|
47
|
-
let inputY = 0;
|
|
48
|
-
|
|
49
|
-
if (this.keysDown.has('w') || this.keysDown.has('ArrowUp')) inputY -= 1;
|
|
50
|
-
if (this.keysDown.has('s') || this.keysDown.has('ArrowDown')) inputY += 1;
|
|
51
|
-
if (this.keysDown.has('a') || this.keysDown.has('ArrowLeft')) inputX -= 1;
|
|
52
|
-
if (this.keysDown.has('d') || this.keysDown.has('ArrowRight')) inputX += 1;
|
|
53
|
-
|
|
54
|
-
if (this.doAccelerate) {
|
|
55
|
-
// Accelerate velocity towards input direction
|
|
56
|
-
this.velocity.x += inputX * this.accelSpeed;
|
|
57
|
-
this.velocity.y += inputY * this.accelSpeed;
|
|
58
|
-
|
|
59
|
-
// Apply friction
|
|
60
|
-
this.velocity.x *= this.friction;
|
|
61
|
-
this.velocity.y *= this.friction;
|
|
62
|
-
if (inputX == 0 && inputY == 0) {
|
|
63
|
-
this.isFalloff = true;
|
|
64
|
-
} else {
|
|
65
|
-
this.isFalloff = false;
|
|
66
|
-
}
|
|
67
|
-
// Only pan if velocity is noticeable
|
|
68
|
-
if (Math.abs(this.velocity.x) > 0.01 || Math.abs(this.velocity.y) > 0.01) {
|
|
69
|
-
this.pan(this.velocity.x, this.velocity.y);
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// Instant movement
|
|
73
|
-
const dx = inputX * this.moveSpeed;
|
|
74
|
-
const dy = inputY * this.moveSpeed;
|
|
75
|
-
if (dx !== 0 || dy !== 0) this.pan(dx, dy);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
pan(dx: number, dy: number) {
|
|
79
|
-
const x = this.workspace._camera.x, y = this.workspace._camera.y;
|
|
80
|
-
this.workspace._camera.x += dx;
|
|
81
|
-
this.workspace._camera.y += dy;
|
|
82
|
-
if (x == this.workspace._camera.x && y == this.workspace._camera.y) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (!this.isFalloff) {
|
|
86
|
-
this._throttledFireDidMove();
|
|
87
|
-
this.workspace.didMove = true;
|
|
88
|
-
this.workspace.fireMoveListeners();
|
|
89
|
-
this.refreshPos();
|
|
90
|
-
}
|
|
91
|
-
this.refreshPos();
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Handles wheel events for zooming.
|
|
95
|
-
* Zooms around the mouse position for intuitive zooming.
|
|
96
|
-
*/
|
|
97
|
-
onWheel(e: WheelEvent) {
|
|
98
|
-
e.preventDefault();
|
|
99
|
-
|
|
100
|
-
const oldZoom = this.getZoom();
|
|
101
|
-
let delta = -e.deltaY * this.zoomSpeed * 0.01; // normalize wheel
|
|
102
|
-
let newZoom = oldZoom * (1 + delta);
|
|
103
|
-
|
|
104
|
-
// Clamp zoom
|
|
105
|
-
newZoom = Math.max(this.minZoom, Math.min(this.maxZoom, newZoom));
|
|
106
|
-
|
|
107
|
-
// Zoom around cursor
|
|
108
|
-
const mouseX = e.clientX;
|
|
109
|
-
const mouseY = e.clientY;
|
|
110
|
-
|
|
111
|
-
const wsMouse = this.workspace.screenToWorkspace(mouseX, mouseY);
|
|
112
|
-
|
|
113
|
-
// Apply new zoom
|
|
114
|
-
this.setZoom(newZoom);
|
|
115
|
-
|
|
116
|
-
// Adjust camera so the point under cursor stays stable
|
|
117
|
-
this.workspace._camera.x = wsMouse.x - mouseX / newZoom;
|
|
118
|
-
this.workspace._camera.y = wsMouse.y - mouseY / newZoom;
|
|
119
|
-
|
|
120
|
-
this.workspace.refresh();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Returns current zoom level */
|
|
124
|
-
getZoom() {
|
|
125
|
-
return this.zoom;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Sets zoom directly */
|
|
129
|
-
setZoom(zoom: number) {
|
|
130
|
-
this.zoom = zoom;
|
|
131
|
-
}
|
|
132
|
-
}
|
package/docs/README.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
**Kabel Project Docs v1.0.6**
|
|
2
|
-
|
|
3
|
-
***
|
|
4
|
-
|
|
5
|
-
# Kabel
|
|
6
|
-
[](https://www.npmjs.com/package/@kabel-project/kabel)
|
|
7
|
-
[](LICENSE)
|
|
8
|
-
|
|
9
|
-
Node-based visual editor framework with an API inspired by Google’s Blockly project.
|
|
10
|
-
Fully extensible with custom nodes, fields, and renderers.
|
|
11
|
-
Written in TypeScript and ready to use out of the box.
|
|
12
|
-
|
|
13
|
-

|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
|
|
19
|
-
You can install Kabel in two main ways:
|
|
20
|
-
|
|
21
|
-
**Using npm (recommended):**
|
|
22
|
-
```bash
|
|
23
|
-
cd path/to/your/project
|
|
24
|
-
npm install @kabel-project/kabel
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Cloning from GitHub (Experimental builds):**
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
git clone https://github.com/FentFentFent/Kabel.git --depth 1
|
|
31
|
-
cd Kabel
|
|
32
|
-
npm install
|
|
33
|
-
npm run build
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Then import Kabel from the build:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
import Kabel from './Kabel/dist/kabel.js';
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
##### Quick Starter HTML
|
|
43
|
-
|
|
44
|
-
```html
|
|
45
|
-
<div id="workspace-container" style="width:800px;height:600px;"></div>
|
|
46
|
-
<script type="module">
|
|
47
|
-
import Kabel from 'kabel';
|
|
48
|
-
Kabel.inject('workspace-container', {
|
|
49
|
-
/* Your options here... */
|
|
50
|
-
});
|
|
51
|
-
</script>
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Features
|
|
57
|
-
|
|
58
|
-
* ⚡ Extensible: create custom nodes, fields, and renderers.
|
|
59
|
-
* 🛠️ TypeScript support out of the box.
|
|
60
|
-
* 🎨 Renderer override system for custom visuals.
|
|
61
|
-
* ⌨️ Built-in workspace controls (WASD movement, drag, etc.).
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Example
|
|
66
|
-
|
|
67
|
-
```js
|
|
68
|
-
import Kabel from 'kabel';
|
|
69
|
-
|
|
70
|
-
const ws = Kabel.inject('workspace-container', {
|
|
71
|
-
controls: { wasd: true, wasdSmooth: true }
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Register a simple node
|
|
75
|
-
Kabel.Nodes['example'] = {
|
|
76
|
-
init() {
|
|
77
|
-
this.jsonInit({
|
|
78
|
-
labelText: 'Hello Kabel',
|
|
79
|
-
type: 'example_node',
|
|
80
|
-
primaryColor: '#cc0c00'
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
For another example refer to [Kabel Test](_media/index.html)
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Documentation
|
|
91
|
-
|
|
92
|
-
Please refer to [Kabel Guide](_media/docs.md)
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## License
|
|
97
|
-
|
|
98
|
-
MIT
|