@grafloria/element 0.3.23 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/package.json +7 -9
- package/src/index.d.ts +16 -16
- package/src/index.js +58 -339
- package/src/lib/dashboard-kit/dashboard.d.ts +1 -1
- package/src/lib/dashboard-kit/dashboard.js +22 -26
- package/src/lib/dashboard-kit/grid-binder.d.ts +1 -1
- package/src/lib/dashboard-kit/grid-binder.js +62 -65
- package/src/lib/dashboard-kit/grid-mapping.js +14 -26
- package/src/lib/dashboard-kit/index.d.ts +5 -5
- package/src/lib/dashboard-kit/index.js +5 -30
- package/src/lib/dashboard-kit/styles.js +4 -8
- package/src/lib/dashboard-kit/widgets.d.ts +1 -1
- package/src/lib/dashboard-kit/widgets.js +16 -26
- package/src/lib/diagram-kit/card.d.ts +2 -2
- package/src/lib/diagram-kit/card.js +22 -32
- package/src/lib/diagram-kit/editing.js +15 -20
- package/src/lib/diagram-kit/er.d.ts +1 -1
- package/src/lib/diagram-kit/er.js +15 -18
- package/src/lib/diagram-kit/handles.d.ts +4 -4
- package/src/lib/diagram-kit/handles.js +16 -29
- package/src/lib/diagram-kit/index.d.ts +10 -10
- package/src/lib/diagram-kit/index.js +10 -50
- package/src/lib/diagram-kit/join-guidance.js +9 -18
- package/src/lib/diagram-kit/rows.js +1 -4
- package/src/lib/diagram-kit/styles.js +4 -8
- package/src/lib/diagram-kit/uml.js +10 -13
- package/src/lib/diagram-kit/update.d.ts +1 -1
- package/src/lib/diagram-kit/update.js +19 -26
- package/src/lib/grafloria-flow-element.js +19 -24
- package/src/lib/grafloria.d.ts +4 -4
- package/src/lib/grafloria.js +12 -17
- package/src/lib/load.d.ts +3 -3
- package/src/lib/load.js +19 -22
- package/src/lib/node-type-registry.js +6 -14
- package/src/lib/stencil-kit/builders.js +4 -10
- package/src/lib/stencil-kit/card-builders.js +14 -17
- package/src/lib/stencil-kit/index.d.ts +7 -7
- package/src/lib/stencil-kit/index.js +6 -16
- package/src/lib/stencil-kit/palette.js +14 -17
- package/src/lib/stencil-kit/shape-data.js +25 -28
- package/src/lib/stencil-kit/styles.js +1 -4
- package/esm/src/index.js +0 -295
- package/esm/src/lib/dashboard-kit/dashboard.js +0 -590
- package/esm/src/lib/dashboard-kit/grid-binder.js +0 -1633
- package/esm/src/lib/dashboard-kit/grid-mapping.js +0 -164
- package/esm/src/lib/dashboard-kit/index.js +0 -8
- package/esm/src/lib/dashboard-kit/styles.js +0 -205
- package/esm/src/lib/dashboard-kit/widgets.js +0 -379
- package/esm/src/lib/diagram-kit/card.js +0 -199
- package/esm/src/lib/diagram-kit/editing.js +0 -448
- package/esm/src/lib/diagram-kit/er.js +0 -160
- package/esm/src/lib/diagram-kit/handles.js +0 -312
- package/esm/src/lib/diagram-kit/index.js +0 -15
- package/esm/src/lib/diagram-kit/join-guidance.js +0 -322
- package/esm/src/lib/diagram-kit/rows.js +0 -144
- package/esm/src/lib/diagram-kit/styles.js +0 -118
- package/esm/src/lib/diagram-kit/uml.js +0 -135
- package/esm/src/lib/diagram-kit/update.js +0 -244
- package/esm/src/lib/grafloria-flow-element.js +0 -266
- package/esm/src/lib/grafloria.js +0 -69
- package/esm/src/lib/load.js +0 -252
- package/esm/src/lib/node-type-registry.js +0 -42
- package/esm/src/lib/stencil-kit/builders.js +0 -18
- package/esm/src/lib/stencil-kit/card-builders.js +0 -105
- package/esm/src/lib/stencil-kit/index.js +0 -9
- package/esm/src/lib/stencil-kit/palette.js +0 -427
- package/esm/src/lib/stencil-kit/shape-data.js +0 -537
- package/esm/src/lib/stencil-kit/styles.js +0 -126
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { __awaiter } from "tslib";
|
|
2
|
-
import { updateEntity, updateClass } from './update';
|
|
3
|
-
const deep = (v) => JSON.parse(JSON.stringify(v));
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Base
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
export class CardHandle {
|
|
8
|
-
constructor(api, id) {
|
|
9
|
-
this.api = api;
|
|
10
|
-
this.id = id;
|
|
11
|
-
}
|
|
12
|
-
/** The live node, or undefined once deleted. Handles never cache it. */
|
|
13
|
-
liveNode() {
|
|
14
|
-
return this.api.getModel().getNode(this.id);
|
|
15
|
-
}
|
|
16
|
-
get exists() {
|
|
17
|
-
return this.liveNode() !== undefined;
|
|
18
|
-
}
|
|
19
|
-
/** Escape hatch to the underlying NodeModel (typed loosely on purpose). */
|
|
20
|
-
get node() {
|
|
21
|
-
return this.liveNode();
|
|
22
|
-
}
|
|
23
|
-
get width() {
|
|
24
|
-
var _a, _b;
|
|
25
|
-
return (_b = (_a = this.liveNode()) === null || _a === void 0 ? void 0 : _a.size.width) !== null && _b !== void 0 ? _b : 0;
|
|
26
|
-
}
|
|
27
|
-
get height() {
|
|
28
|
-
var _a, _b;
|
|
29
|
-
return (_b = (_a = this.liveNode()) === null || _a === void 0 ? void 0 : _a.size.height) !== null && _b !== void 0 ? _b : 0;
|
|
30
|
-
}
|
|
31
|
-
select() {
|
|
32
|
-
var _a, _b, _c;
|
|
33
|
-
const model = this.api.getModel();
|
|
34
|
-
const node = model.getNode(this.id);
|
|
35
|
-
if (node)
|
|
36
|
-
(_a = model.selectNode) === null || _a === void 0 ? void 0 : _a.call(model, node);
|
|
37
|
-
(_c = (_b = this.api).renderNow) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
38
|
-
}
|
|
39
|
-
undo() {
|
|
40
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
-
var _a, _b, _c, _d, _e;
|
|
42
|
-
yield ((_c = (_b = (_a = this.api).getEngine) === null || _b === void 0 ? void 0 : _b.call(_a)) === null || _c === void 0 ? void 0 : _c.undo());
|
|
43
|
-
(_e = (_d = this.api).renderNow) === null || _e === void 0 ? void 0 : _e.call(_d);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
redo() {
|
|
47
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
-
var _a, _b, _c, _d, _e;
|
|
49
|
-
yield ((_c = (_b = (_a = this.api).getEngine) === null || _b === void 0 ? void 0 : _b.call(_a)) === null || _c === void 0 ? void 0 : _c.redo());
|
|
50
|
-
(_e = (_d = this.api).renderNow) === null || _e === void 0 ? void 0 : _e.call(_d);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Row-selection events for THIS card only (the kit's `axk:row-select`,
|
|
55
|
-
* filtered). ER cards resolve the selection to a typed {@link ErField} when
|
|
56
|
-
* possible. Returns an unbind function.
|
|
57
|
-
*/
|
|
58
|
-
onRowSelect(cb) {
|
|
59
|
-
const handler = (event) => {
|
|
60
|
-
var _a, _b, _c;
|
|
61
|
-
const selected = (_b = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.selected) !== null && _b !== void 0 ? _b : null;
|
|
62
|
-
if (selected && selected.nodeId !== this.id)
|
|
63
|
-
return; // another card
|
|
64
|
-
if (!selected) {
|
|
65
|
-
cb({ field: null, selected: null });
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const field = this instanceof ErTable && selected.name !== undefined
|
|
69
|
-
? ((_c = this.columns.get(selected.name)) !== null && _c !== void 0 ? _c : null)
|
|
70
|
-
: null;
|
|
71
|
-
cb({ field, selected });
|
|
72
|
-
};
|
|
73
|
-
this.api.container.addEventListener('axk:row-select', handler);
|
|
74
|
-
return () => this.api.container.removeEventListener('axk:row-select', handler);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// ER
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
/** A column of an {@link ErTable} — itself just (table, name): stateless. */
|
|
81
|
-
export class ErField {
|
|
82
|
-
constructor(table, columnName) {
|
|
83
|
-
this.table = table;
|
|
84
|
-
this.columnName = columnName;
|
|
85
|
-
}
|
|
86
|
-
col() {
|
|
87
|
-
return this.table.spec.columns.find((c) => c.name === this.columnName);
|
|
88
|
-
}
|
|
89
|
-
get exists() {
|
|
90
|
-
return this.col() !== undefined;
|
|
91
|
-
}
|
|
92
|
-
get name() {
|
|
93
|
-
return this.columnName;
|
|
94
|
-
}
|
|
95
|
-
get index() {
|
|
96
|
-
return this.table.spec.columns.findIndex((c) => c.name === this.columnName);
|
|
97
|
-
}
|
|
98
|
-
get type() {
|
|
99
|
-
var _a;
|
|
100
|
-
return (_a = this.col()) === null || _a === void 0 ? void 0 : _a.type;
|
|
101
|
-
}
|
|
102
|
-
get pk() {
|
|
103
|
-
var _a;
|
|
104
|
-
return ((_a = this.col()) === null || _a === void 0 ? void 0 : _a.pk) === true;
|
|
105
|
-
}
|
|
106
|
-
get fk() {
|
|
107
|
-
var _a;
|
|
108
|
-
return ((_a = this.col()) === null || _a === void 0 ? void 0 : _a.fk) === true;
|
|
109
|
-
}
|
|
110
|
-
/** Rename — the field port keeps its id, so attached edges stay glued. */
|
|
111
|
-
rename(name) {
|
|
112
|
-
return this.table.columns.renameAt(this.index, name);
|
|
113
|
-
}
|
|
114
|
-
setType(type) {
|
|
115
|
-
return this.table.columns.patchAt(this.index, { type });
|
|
116
|
-
}
|
|
117
|
-
setKeys(keys) {
|
|
118
|
-
return this.table.columns.patchAt(this.index, keys);
|
|
119
|
-
}
|
|
120
|
-
/** Remove this column (its ports and attached edges are dropped). */
|
|
121
|
-
remove() {
|
|
122
|
-
return this.table.columns.removeAt(this.index);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/** The columns collection of an {@link ErTable}. Iterable of {@link ErField}. */
|
|
126
|
-
export class ErColumnList {
|
|
127
|
-
constructor(table) {
|
|
128
|
-
this.table = table;
|
|
129
|
-
}
|
|
130
|
-
cols() {
|
|
131
|
-
return this.table.spec.columns;
|
|
132
|
-
}
|
|
133
|
-
get length() {
|
|
134
|
-
return this.cols().length;
|
|
135
|
-
}
|
|
136
|
-
names() {
|
|
137
|
-
return this.cols().map((c) => c.name);
|
|
138
|
-
}
|
|
139
|
-
at(index) {
|
|
140
|
-
const c = this.cols()[index];
|
|
141
|
-
return c ? new ErField(this.table, c.name) : undefined;
|
|
142
|
-
}
|
|
143
|
-
get(name) {
|
|
144
|
-
return this.cols().some((c) => c.name === name) ? new ErField(this.table, name) : undefined;
|
|
145
|
-
}
|
|
146
|
-
[Symbol.iterator]() {
|
|
147
|
-
return this.cols()
|
|
148
|
-
.map((c) => new ErField(this.table, c.name))[Symbol.iterator]();
|
|
149
|
-
}
|
|
150
|
-
add(column, opts = {}) {
|
|
151
|
-
var _a;
|
|
152
|
-
const next = [...this.cols()];
|
|
153
|
-
next.splice((_a = opts.at) !== null && _a !== void 0 ? _a : next.length, 0, Object.assign({}, column));
|
|
154
|
-
return this.table.update({ columns: next });
|
|
155
|
-
}
|
|
156
|
-
removeAt(index) {
|
|
157
|
-
const next = this.cols().filter((_, i) => i !== index);
|
|
158
|
-
return this.table.update({ columns: next });
|
|
159
|
-
}
|
|
160
|
-
renameAt(index, name) {
|
|
161
|
-
return this.patchAt(index, { name });
|
|
162
|
-
}
|
|
163
|
-
patchAt(index, patch) {
|
|
164
|
-
const next = this.cols().map((c, i) => (i === index ? Object.assign(Object.assign({}, c), patch) : c));
|
|
165
|
-
return this.table.update({ columns: next });
|
|
166
|
-
}
|
|
167
|
-
move(from, to) {
|
|
168
|
-
const next = [...this.cols()];
|
|
169
|
-
const [c] = next.splice(from, 1);
|
|
170
|
-
next.splice(to, 0, c);
|
|
171
|
-
return this.table.update({ columns: next });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
export class ErTable extends CardHandle {
|
|
175
|
-
constructor() {
|
|
176
|
-
super(...arguments);
|
|
177
|
-
this.columns = new ErColumnList(this);
|
|
178
|
-
}
|
|
179
|
-
/** A deep COPY of the stored entity spec — never a live reference. */
|
|
180
|
-
get spec() {
|
|
181
|
-
var _a, _b;
|
|
182
|
-
const raw = (_b = (_a = this.liveNode()) === null || _a === void 0 ? void 0 : _a.getMetadata) === null || _b === void 0 ? void 0 : _b.call(_a, 'kitEntity');
|
|
183
|
-
if (!raw)
|
|
184
|
-
throw new Error(`erTable('${this.id}'): the node is gone`);
|
|
185
|
-
return deep(raw);
|
|
186
|
-
}
|
|
187
|
-
get name() {
|
|
188
|
-
var _a;
|
|
189
|
-
return (_a = this.spec.name) !== null && _a !== void 0 ? _a : this.id;
|
|
190
|
-
}
|
|
191
|
-
rename(name) {
|
|
192
|
-
return this.update({ name });
|
|
193
|
-
}
|
|
194
|
-
resize(size) {
|
|
195
|
-
return this.update(size);
|
|
196
|
-
}
|
|
197
|
-
/** The raw delta path — everything above funnels through here. */
|
|
198
|
-
update(delta) {
|
|
199
|
-
return updateEntity(this.api, this.id, delta);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
// UML
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
/** attributes / methods of a {@link UmlClass} (members are plain strings). */
|
|
206
|
-
export class UmlMemberList {
|
|
207
|
-
constructor(cls, kind) {
|
|
208
|
-
this.cls = cls;
|
|
209
|
-
this.kind = kind;
|
|
210
|
-
}
|
|
211
|
-
list() {
|
|
212
|
-
var _a;
|
|
213
|
-
return (_a = this.cls.spec[this.kind]) !== null && _a !== void 0 ? _a : [];
|
|
214
|
-
}
|
|
215
|
-
get length() {
|
|
216
|
-
return this.list().length;
|
|
217
|
-
}
|
|
218
|
-
at(index) {
|
|
219
|
-
return this.list()[index];
|
|
220
|
-
}
|
|
221
|
-
add(member, opts = {}) {
|
|
222
|
-
var _a;
|
|
223
|
-
const next = [...this.list()];
|
|
224
|
-
next.splice((_a = opts.at) !== null && _a !== void 0 ? _a : next.length, 0, member);
|
|
225
|
-
return this.cls.update({ [this.kind]: next });
|
|
226
|
-
}
|
|
227
|
-
removeAt(index) {
|
|
228
|
-
return this.cls.update({ [this.kind]: this.list().filter((_, i) => i !== index) });
|
|
229
|
-
}
|
|
230
|
-
renameAt(index, member) {
|
|
231
|
-
return this.cls.update({ [this.kind]: this.list().map((m, i) => (i === index ? member : m)) });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
export class UmlClass extends CardHandle {
|
|
235
|
-
constructor() {
|
|
236
|
-
super(...arguments);
|
|
237
|
-
this.attributes = new UmlMemberList(this, 'attributes');
|
|
238
|
-
this.methods = new UmlMemberList(this, 'methods');
|
|
239
|
-
}
|
|
240
|
-
/** A deep COPY of the stored class spec — never a live reference. */
|
|
241
|
-
get spec() {
|
|
242
|
-
var _a, _b;
|
|
243
|
-
const raw = (_b = (_a = this.liveNode()) === null || _a === void 0 ? void 0 : _a.getMetadata) === null || _b === void 0 ? void 0 : _b.call(_a, 'kitClass');
|
|
244
|
-
if (!raw)
|
|
245
|
-
throw new Error(`umlClass('${this.id}'): the node is gone`);
|
|
246
|
-
return deep(raw);
|
|
247
|
-
}
|
|
248
|
-
get name() {
|
|
249
|
-
var _a;
|
|
250
|
-
return (_a = this.spec.name) !== null && _a !== void 0 ? _a : this.id;
|
|
251
|
-
}
|
|
252
|
-
get abstract() {
|
|
253
|
-
return this.spec.abstract === true;
|
|
254
|
-
}
|
|
255
|
-
get stereotype() {
|
|
256
|
-
return this.spec.stereotype;
|
|
257
|
-
}
|
|
258
|
-
rename(name) {
|
|
259
|
-
return this.update({ name });
|
|
260
|
-
}
|
|
261
|
-
resize(size) {
|
|
262
|
-
return this.update(size);
|
|
263
|
-
}
|
|
264
|
-
setAbstract(abstract) {
|
|
265
|
-
return this.update({ abstract });
|
|
266
|
-
}
|
|
267
|
-
setStereotype(stereotype) {
|
|
268
|
-
return this.update({ stereotype });
|
|
269
|
-
}
|
|
270
|
-
update(delta) {
|
|
271
|
-
return updateClass(this.api, this.id, delta);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// ---------------------------------------------------------------------------
|
|
275
|
-
// Factories
|
|
276
|
-
// ---------------------------------------------------------------------------
|
|
277
|
-
function requireKit(api, id, key, what) {
|
|
278
|
-
var _a;
|
|
279
|
-
const node = api.getModel().getNode(id);
|
|
280
|
-
if (!node)
|
|
281
|
-
throw new Error(`${what}('${id}'): no node with that id`);
|
|
282
|
-
if (!((_a = node.getMetadata) === null || _a === void 0 ? void 0 : _a.call(node, key))) {
|
|
283
|
-
throw new Error(`${what}('${id}'): that node is not an ${what === 'erTable' ? 'ER table' : 'UML class'} (missing metadata.${key})`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/** Typed handle for a kit ER table. Throws for unknown ids / non-kit nodes. */
|
|
287
|
-
export function erTable(api, id) {
|
|
288
|
-
requireKit(api, id, 'kitEntity', 'erTable');
|
|
289
|
-
return new ErTable(api, id);
|
|
290
|
-
}
|
|
291
|
-
/** Typed handle for a kit UML class. Throws for unknown ids / non-kit nodes. */
|
|
292
|
-
export function umlClass(api, id) {
|
|
293
|
-
requireKit(api, id, 'kitClass', 'umlClass');
|
|
294
|
-
return new UmlClass(api, id);
|
|
295
|
-
}
|
|
296
|
-
/** Every kit ER table in the diagram, as handles. */
|
|
297
|
-
export function erTables(api) {
|
|
298
|
-
var _a, _b;
|
|
299
|
-
const model = api.getModel();
|
|
300
|
-
return ((_b = (_a = model.getNodes) === null || _a === void 0 ? void 0 : _a.call(model)) !== null && _b !== void 0 ? _b : [])
|
|
301
|
-
.filter((n) => { var _a; return (_a = n.getMetadata) === null || _a === void 0 ? void 0 : _a.call(n, 'kitEntity'); })
|
|
302
|
-
.map((n) => new ErTable(api, n.id));
|
|
303
|
-
}
|
|
304
|
-
/** Every kit UML class in the diagram, as handles. */
|
|
305
|
-
export function umlClasses(api) {
|
|
306
|
-
var _a, _b;
|
|
307
|
-
const model = api.getModel();
|
|
308
|
-
return ((_b = (_a = model.getNodes) === null || _a === void 0 ? void 0 : _a.call(model)) !== null && _b !== void 0 ? _b : [])
|
|
309
|
-
.filter((n) => { var _a; return (_a = n.getMetadata) === null || _a === void 0 ? void 0 : _a.call(n, 'kitClass'); })
|
|
310
|
-
.map((n) => new UmlClass(api, n.id));
|
|
311
|
-
}
|
|
312
|
-
//# sourceMappingURL=handles.js.map
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export { erDiagram, erRowCenterY, ER_ROW_H, ER_HEAD_H, } from './er';
|
|
2
|
-
export { umlDiagram, } from './uml';
|
|
3
|
-
export { ensureDiagramKitStyles, DIAGRAM_KIT_STYLE_ID } from './styles';
|
|
4
|
-
export { bindRowInteractions } from './rows';
|
|
5
|
-
export { updateEntity, updateClass, addColumnAt, removeColumnAt, renameColumnAt, } from './update';
|
|
6
|
-
export { bindCardEditing } from './editing';
|
|
7
|
-
export { matchColumns, rowIndexFromY } from './card';
|
|
8
|
-
// The card CONTENT builders — shared with the stencil palette so a dropped
|
|
9
|
-
// "Entity"/"Class" is the identical card erDiagram()/umlDiagram() produce.
|
|
10
|
-
export { entityCardContent, entityAutoHeight, classCardContent, classAutoHeight } from './card';
|
|
11
|
-
// Live join guidance — score every other table's columns against a connection
|
|
12
|
-
// drag's source column and tint the kit rows by fit (the query-builder story).
|
|
13
|
-
export { scoreMatch, matchTier, assignTiers, singularize, bindJoinGuidance, ensureJoinGuidanceStyles, JOIN_GUIDANCE_STYLE_ID, } from './join-guidance';
|
|
14
|
-
export { erTable, umlClass, erTables, umlClasses, CardHandle, ErTable, ErField, ErColumnList, UmlClass, UmlMemberList, } from './handles';
|
|
15
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Join guidance — live "which column should I join to?" scoring + tinting for
|
|
3
|
-
* kit table cards, the feature a visual query builder is actually about.
|
|
4
|
-
*
|
|
5
|
-
* Ported verbatim from a production BI tool's Query Studio (the scoring is ~55
|
|
6
|
-
* pure lines there too). While a connection drag is in progress, every column
|
|
7
|
-
* on every OTHER table is scored against the drag's source column and its card
|
|
8
|
-
* row is tinted by tier:
|
|
9
|
-
*
|
|
10
|
-
* score 3 — PK↔FK naming convention: `orders.customer_id` ↔ `customers.id`
|
|
11
|
-
* (`<singular(table)>_id` on one end, `id` on the other), or exact
|
|
12
|
-
* FK column-name equality across tables;
|
|
13
|
-
* score 2 — a PK flag on one end and an FK flag on the other;
|
|
14
|
-
* score 1 — same column name, or both names end in "id";
|
|
15
|
-
* score 0 — same table, or nothing.
|
|
16
|
-
*
|
|
17
|
-
* Tiers: the single best candidate scoring >= 2 is `top` (gold, "★ BEST"),
|
|
18
|
-
* every other >= 2 is `good` (green), 1 is `ok` (blue), 0 is `none` (dimmed).
|
|
19
|
-
* The SOURCE table is never tinted, and every tint clears when the drag ends —
|
|
20
|
-
* complete, cancelled or escaped.
|
|
21
|
-
*
|
|
22
|
-
* Split the same way the rest of the kit is:
|
|
23
|
-
* - `scoreMatch` / `matchTier` / `assignTiers` / `singularize` are PURE
|
|
24
|
-
* (unit-specced in the element suite);
|
|
25
|
-
* - `bindJoinGuidance(api)` is the runtime: it listens to the engine's own
|
|
26
|
-
* connection lifecycle events (`connection:start` / `:complete` /
|
|
27
|
-
* `:cancel`) and paints tier classes onto the kit's `.axk-row` elements.
|
|
28
|
-
*
|
|
29
|
-
* Row tints go on as DOM classes — the renderer's foreignObject subtrees are
|
|
30
|
-
* patch-opaque, so classes survive the per-frame VNode patch. Port tints go in
|
|
31
|
-
* as a generated per-drag <style> element keyed by `data-port-id`: port glyphs
|
|
32
|
-
* ARE re-patched every frame, so a class added to the SVG element would be
|
|
33
|
-
* wiped mid-drag, while a stylesheet wins the cascade and survives.
|
|
34
|
-
*/
|
|
35
|
-
/** Naive English singular — enough for schema names (orders, categories, statuses). */
|
|
36
|
-
export function singularize(word) {
|
|
37
|
-
const w = word.toLowerCase();
|
|
38
|
-
if (w.endsWith('ies') && w.length > 3)
|
|
39
|
-
return w.slice(0, -3) + 'y';
|
|
40
|
-
if (/(?:ses|xes|zes|ches|shes)$/.test(w))
|
|
41
|
-
return w.slice(0, -2);
|
|
42
|
-
if (w.endsWith('s') && !w.endsWith('ss'))
|
|
43
|
-
return w.slice(0, -1);
|
|
44
|
-
return w;
|
|
45
|
-
}
|
|
46
|
-
/** The PK↔FK naming convention: `a` is `<singular(b.table)>_id` and `b` is `id`. */
|
|
47
|
-
function conventionMatch(a, b) {
|
|
48
|
-
return (a.column.name.toLowerCase() === `${singularize(b.table)}_id` &&
|
|
49
|
-
b.column.name.toLowerCase() === 'id');
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Score a candidate join between two columns. Pure; the exact production
|
|
53
|
-
* logic. Higher is better; 0 means "no reason to join these".
|
|
54
|
-
*/
|
|
55
|
-
export function scoreMatch(a, b) {
|
|
56
|
-
if (!a || !b || a.table === b.table)
|
|
57
|
-
return 0;
|
|
58
|
-
const an = a.column.name.toLowerCase();
|
|
59
|
-
const bn = b.column.name.toLowerCase();
|
|
60
|
-
// 3 — the naming convention (either direction), or exact FK-name equality.
|
|
61
|
-
if (conventionMatch(a, b) || conventionMatch(b, a))
|
|
62
|
-
return 3;
|
|
63
|
-
if (an === bn && an.endsWith('_id') && (a.column.fk === true || b.column.fk === true))
|
|
64
|
-
return 3;
|
|
65
|
-
// 2 — PK flag on one end, FK flag on the other.
|
|
66
|
-
if ((a.column.pk === true && b.column.fk === true) || (a.column.fk === true && b.column.pk === true))
|
|
67
|
-
return 2;
|
|
68
|
-
// 1 — same name, or both look like identifiers.
|
|
69
|
-
if (an === bn)
|
|
70
|
-
return 1;
|
|
71
|
-
if (an.endsWith('id') && bn.endsWith('id'))
|
|
72
|
-
return 1;
|
|
73
|
-
return 0;
|
|
74
|
-
}
|
|
75
|
-
/** Per-score tier (rank-free half; `assignTiers` promotes the single best to `top`). */
|
|
76
|
-
export function matchTier(score) {
|
|
77
|
-
if (score >= 2)
|
|
78
|
-
return 'good';
|
|
79
|
-
if (score >= 1)
|
|
80
|
-
return 'ok';
|
|
81
|
-
return 'none';
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Tier every candidate score at once: the FIRST candidate holding the maximum
|
|
85
|
-
* score is `top` when that maximum is >= 2 — exactly one gold row per drag.
|
|
86
|
-
*/
|
|
87
|
-
export function assignTiers(scores) {
|
|
88
|
-
const max = scores.reduce((m, s) => (s > m ? s : m), 0);
|
|
89
|
-
let topIndex = -1;
|
|
90
|
-
if (max >= 2)
|
|
91
|
-
topIndex = scores.indexOf(max);
|
|
92
|
-
return scores.map((s, i) => (i === topIndex ? 'top' : matchTier(s)));
|
|
93
|
-
}
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
// Runtime binding
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
const TIER_CLASS = {
|
|
98
|
-
top: 'axk-match-top',
|
|
99
|
-
good: 'axk-match-good',
|
|
100
|
-
ok: 'axk-match-ok',
|
|
101
|
-
none: 'axk-match-none',
|
|
102
|
-
};
|
|
103
|
-
const CHIP_CLASS = 'axk-match-chip';
|
|
104
|
-
const PORT_STYLE_ID = 'grafloria-join-guidance-ports';
|
|
105
|
-
export const JOIN_GUIDANCE_STYLE_ID = 'grafloria-join-guidance-styles';
|
|
106
|
-
/** The tier tints — the production palette, verbatim. */
|
|
107
|
-
const GUIDANCE_CSS = `
|
|
108
|
-
.axk-row.axk-match-top { background: #fffbeb;
|
|
109
|
-
box-shadow: inset 4px 0 0 #f59e0b, inset 0 0 0 1px #fcd34d; }
|
|
110
|
-
.axk-row.axk-match-top .axk-col { font-weight: 800; color: #92600a; }
|
|
111
|
-
.axk-row.axk-match-good { background: #dcfce7; box-shadow: inset 4px 0 0 #16a34a; }
|
|
112
|
-
.axk-row.axk-match-good .axk-col { font-weight: 700; color: #14532d; }
|
|
113
|
-
.axk-row.axk-match-ok { background: #dbeafe; box-shadow: inset 4px 0 0 #2563eb; }
|
|
114
|
-
.axk-row.axk-match-none { opacity: .4; }
|
|
115
|
-
.${CHIP_CLASS} { font-size: 9px; font-weight: 800; text-transform: uppercase;
|
|
116
|
-
background: #f59e0b; color: #4a2e05; border-radius: 4px; padding: 1px 4px;
|
|
117
|
-
margin-left: 4px; flex: 0 0 auto; letter-spacing: .3px; }
|
|
118
|
-
`;
|
|
119
|
-
/** Inject the guidance tint stylesheet once. Safe to call repeatedly / in SSR. */
|
|
120
|
-
export function ensureJoinGuidanceStyles(doc = typeof document !== 'undefined' ? document : undefined) {
|
|
121
|
-
if (!doc)
|
|
122
|
-
return;
|
|
123
|
-
if (doc.getElementById(JOIN_GUIDANCE_STYLE_ID))
|
|
124
|
-
return;
|
|
125
|
-
const style = doc.createElement('style');
|
|
126
|
-
style.id = JOIN_GUIDANCE_STYLE_ID;
|
|
127
|
-
style.textContent = GUIDANCE_CSS;
|
|
128
|
-
doc.head.appendChild(style);
|
|
129
|
-
}
|
|
130
|
-
const esc = (value) => typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(value) : value.replace(/"/g, '\\"');
|
|
131
|
-
/** The production port glows: gold 4px, green 3px, blue 2px; `none` recedes. */
|
|
132
|
-
function defaultPortCss(tier, selector) {
|
|
133
|
-
switch (tier) {
|
|
134
|
-
case 'top':
|
|
135
|
-
return `${selector} { fill: #f59e0b !important; stroke: #f59e0b !important; filter: drop-shadow(0 0 4px rgba(245,158,11,.4)) drop-shadow(0 0 2px rgba(245,158,11,.4)); }`;
|
|
136
|
-
case 'good':
|
|
137
|
-
return `${selector} { fill: #16a34a !important; stroke: #16a34a !important; filter: drop-shadow(0 0 3px rgba(22,163,74,.35)); }`;
|
|
138
|
-
case 'ok':
|
|
139
|
-
return `${selector} { fill: #2563eb !important; stroke: #2563eb !important; filter: drop-shadow(0 0 2px rgba(37,99,235,.3)); }`;
|
|
140
|
-
default:
|
|
141
|
-
return '';
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Bind live join guidance to a diagram instance. Listens to the engine's
|
|
146
|
-
* connection lifecycle; while a drag is live, every kit-card row on every
|
|
147
|
-
* other table wears its tier class (and the best one its "★ BEST" chip), and
|
|
148
|
-
* the matching port glyphs glow via a generated stylesheet. Everything clears
|
|
149
|
-
* when the drag ends, however it ends.
|
|
150
|
-
*/
|
|
151
|
-
export function bindJoinGuidance(api, options = {}) {
|
|
152
|
-
var _a, _b, _c, _d, _e;
|
|
153
|
-
ensureJoinGuidanceStyles(api.container.ownerDocument);
|
|
154
|
-
const chipText = (_a = options.chipText) !== null && _a !== void 0 ? _a : '★ BEST';
|
|
155
|
-
const portCss = (_b = options.portCss) !== null && _b !== void 0 ? _b : defaultPortCss;
|
|
156
|
-
const kitOf = (node) => {
|
|
157
|
-
var _a;
|
|
158
|
-
const raw = (_a = node === null || node === void 0 ? void 0 : node.getMetadata) === null || _a === void 0 ? void 0 : _a.call(node, 'kitEntity');
|
|
159
|
-
return raw && typeof raw === 'object' ? raw : null;
|
|
160
|
-
};
|
|
161
|
-
const defaultResolve = (portId, nodeId) => {
|
|
162
|
-
var _a, _b;
|
|
163
|
-
const model = api.getModel();
|
|
164
|
-
const nodes = nodeId
|
|
165
|
-
? [model.getNode(nodeId)].filter((n) => !!n)
|
|
166
|
-
: ((_b = (_a = model.getNodes) === null || _a === void 0 ? void 0 : _a.call(model)) !== null && _b !== void 0 ? _b : []).filter((n) => { var _a; return !!((_a = n.getPort) === null || _a === void 0 ? void 0 : _a.call(n, portId)); });
|
|
167
|
-
for (const node of nodes) {
|
|
168
|
-
const kit = kitOf(node);
|
|
169
|
-
if (!(kit === null || kit === void 0 ? void 0 : kit.columns))
|
|
170
|
-
continue;
|
|
171
|
-
for (const column of kit.columns) {
|
|
172
|
-
if (portId === `${node.id}.${column.name}-in` ||
|
|
173
|
-
portId === `${node.id}.${column.name}-out` ||
|
|
174
|
-
portId.startsWith(`${node.id}__${column.name}__`)) {
|
|
175
|
-
return { nodeId: node.id, column: column.name };
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return null;
|
|
180
|
-
};
|
|
181
|
-
const resolvePort = (_c = options.resolvePort) !== null && _c !== void 0 ? _c : defaultResolve;
|
|
182
|
-
const active = new Map();
|
|
183
|
-
let portStyle = null;
|
|
184
|
-
const rowsOf = (nodeId) => {
|
|
185
|
-
const group = api.container.querySelector(`[data-node-id="${esc(nodeId)}"]`);
|
|
186
|
-
return group ? Array.from(group.querySelectorAll('.axk-row')) : [];
|
|
187
|
-
};
|
|
188
|
-
const clear = () => {
|
|
189
|
-
for (const cls of Object.values(TIER_CLASS)) {
|
|
190
|
-
for (const el of Array.from(api.container.querySelectorAll(`.${cls}`)))
|
|
191
|
-
el.classList.remove(cls);
|
|
192
|
-
}
|
|
193
|
-
for (const chip of Array.from(api.container.querySelectorAll(`.${CHIP_CLASS}`)))
|
|
194
|
-
chip.remove();
|
|
195
|
-
portStyle === null || portStyle === void 0 ? void 0 : portStyle.remove();
|
|
196
|
-
portStyle = null;
|
|
197
|
-
active.clear();
|
|
198
|
-
};
|
|
199
|
-
const portsForColumn = (node, column) => {
|
|
200
|
-
var _a, _b;
|
|
201
|
-
const ports = (_b = (_a = node.getPorts) === null || _a === void 0 ? void 0 : _a.call(node)) !== null && _b !== void 0 ? _b : [];
|
|
202
|
-
return ports
|
|
203
|
-
.map((p) => p.id)
|
|
204
|
-
.filter((id) => id === `${node.id}.${column}-in` ||
|
|
205
|
-
id === `${node.id}.${column}-out` ||
|
|
206
|
-
id.startsWith(`${node.id}__${column}__`));
|
|
207
|
-
};
|
|
208
|
-
const onStart = (data) => {
|
|
209
|
-
var _a, _b, _c;
|
|
210
|
-
clear();
|
|
211
|
-
const payload = data;
|
|
212
|
-
const sourcePort = payload === null || payload === void 0 ? void 0 : payload.sourcePort;
|
|
213
|
-
if (!sourcePort)
|
|
214
|
-
return;
|
|
215
|
-
const source = resolvePort(sourcePort.id, sourcePort.nodeId);
|
|
216
|
-
if (!source)
|
|
217
|
-
return;
|
|
218
|
-
const model = api.getModel();
|
|
219
|
-
const sourceNode = model.getNode(source.nodeId);
|
|
220
|
-
const sourceKit = kitOf(sourceNode);
|
|
221
|
-
const sourceColumn = (_a = sourceKit === null || sourceKit === void 0 ? void 0 : sourceKit.columns) === null || _a === void 0 ? void 0 : _a.find((c) => c.name === source.column);
|
|
222
|
-
if (!sourceColumn)
|
|
223
|
-
return;
|
|
224
|
-
const sourceEnd = { table: source.nodeId, column: sourceColumn };
|
|
225
|
-
// Every column of every OTHER kit table is a candidate, in node order.
|
|
226
|
-
const candidates = [];
|
|
227
|
-
for (const node of (_c = (_b = model.getNodes) === null || _b === void 0 ? void 0 : _b.call(model)) !== null && _c !== void 0 ? _c : []) {
|
|
228
|
-
if (node.id === source.nodeId)
|
|
229
|
-
continue; // the source table is NEVER tinted
|
|
230
|
-
const kit = kitOf(node);
|
|
231
|
-
if (!(kit === null || kit === void 0 ? void 0 : kit.columns))
|
|
232
|
-
continue;
|
|
233
|
-
kit.columns.forEach((column, rowIndex) => candidates.push({ node, rowIndex, column }));
|
|
234
|
-
}
|
|
235
|
-
if (candidates.length === 0)
|
|
236
|
-
return;
|
|
237
|
-
// The VALIDATOR outranks the score. A column can be the best textual
|
|
238
|
-
// match and still be un-connectable (its table pair already joined, or a
|
|
239
|
-
// custom validator refuses it) — the engine records those as
|
|
240
|
-
// invalidTargetPorts at drag start, and the renderer paints them red on
|
|
241
|
-
// hover. Tinting the same port gold would be the guidance lying about a
|
|
242
|
-
// target the drop will refuse (caught by the demo gate: the seeded
|
|
243
|
-
// customers⋈orders pair made customers.id both '★ BEST' and invalid).
|
|
244
|
-
const invalid = (() => {
|
|
245
|
-
var _a, _b, _c, _d, _e;
|
|
246
|
-
try {
|
|
247
|
-
const engine = (_a = api.getEngine) === null || _a === void 0 ? void 0 : _a.call(api);
|
|
248
|
-
const map = (_e = (_d = (_c = (_b = engine === null || engine === void 0 ? void 0 : engine.getConnectionStateManager) === null || _b === void 0 ? void 0 : _b.call(engine)) === null || _c === void 0 ? void 0 : _c.getState) === null || _d === void 0 ? void 0 : _d.call(_c)) === null || _e === void 0 ? void 0 : _e.invalidTargetPorts;
|
|
249
|
-
return new Set(map ? [...map.keys()] : []);
|
|
250
|
-
}
|
|
251
|
-
catch (_f) {
|
|
252
|
-
return new Set();
|
|
253
|
-
}
|
|
254
|
-
})();
|
|
255
|
-
const scores = candidates.map((c) => {
|
|
256
|
-
var _a, _b, _c;
|
|
257
|
-
const targetIn = `${c.node.id}.${c.column.name}-in`;
|
|
258
|
-
if (invalid.has(targetIn))
|
|
259
|
-
return 0;
|
|
260
|
-
const port = ((_c = (_b = (_a = c.node).getPort) === null || _b === void 0 ? void 0 : _b.call(_a, targetIn)) !== null && _c !== void 0 ? _c : null);
|
|
261
|
-
if ((port === null || port === void 0 ? void 0 : port.id) && invalid.has(port.id))
|
|
262
|
-
return 0;
|
|
263
|
-
return scoreMatch(sourceEnd, { table: c.node.id, column: c.column });
|
|
264
|
-
});
|
|
265
|
-
const tiers = assignTiers(scores);
|
|
266
|
-
const cssRules = [];
|
|
267
|
-
candidates.forEach((candidate, i) => {
|
|
268
|
-
var _a;
|
|
269
|
-
const tier = tiers[i];
|
|
270
|
-
const byRow = (_a = active.get(candidate.node.id)) !== null && _a !== void 0 ? _a : new Map();
|
|
271
|
-
byRow.set(candidate.rowIndex, tier);
|
|
272
|
-
active.set(candidate.node.id, byRow);
|
|
273
|
-
const row = rowsOf(candidate.node.id)[candidate.rowIndex];
|
|
274
|
-
if (row) {
|
|
275
|
-
row.classList.add(TIER_CLASS[tier]);
|
|
276
|
-
if (tier === 'top') {
|
|
277
|
-
const chip = api.container.ownerDocument.createElement('span');
|
|
278
|
-
chip.className = CHIP_CLASS;
|
|
279
|
-
chip.textContent = chipText;
|
|
280
|
-
row.appendChild(chip);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (tier !== 'none') {
|
|
284
|
-
for (const portId of portsForColumn(candidate.node, candidate.column.name)) {
|
|
285
|
-
const rule = portCss(tier, `[data-port-id="${esc(portId)}"]`);
|
|
286
|
-
if (rule)
|
|
287
|
-
cssRules.push(rule);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
if (cssRules.length > 0) {
|
|
292
|
-
portStyle = api.container.ownerDocument.createElement('style');
|
|
293
|
-
portStyle.id = PORT_STYLE_ID;
|
|
294
|
-
portStyle.textContent = cssRules.join('\n');
|
|
295
|
-
api.container.ownerDocument.head.appendChild(portStyle);
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
const bus = (_e = (_d = api.getEngine) === null || _d === void 0 ? void 0 : _d.call(api)) === null || _e === void 0 ? void 0 : _e.eventBus;
|
|
299
|
-
const offs = [];
|
|
300
|
-
if (bus) {
|
|
301
|
-
offs.push(bus.on('connection:start', onStart));
|
|
302
|
-
offs.push(bus.on('connection:complete', clear));
|
|
303
|
-
offs.push(bus.on('connection:cancel', clear));
|
|
304
|
-
// The event-type constant spells it 'cancelled'; the state manager emits
|
|
305
|
-
// 'cancel'. Subscribe to both so a future spelling fix cannot leave tints on.
|
|
306
|
-
offs.push(bus.on('connection:cancelled', clear));
|
|
307
|
-
}
|
|
308
|
-
return {
|
|
309
|
-
activeTiers: () => {
|
|
310
|
-
const copy = new Map();
|
|
311
|
-
for (const [nodeId, byRow] of active)
|
|
312
|
-
copy.set(nodeId, new Map(byRow));
|
|
313
|
-
return copy;
|
|
314
|
-
},
|
|
315
|
-
dispose: () => {
|
|
316
|
-
for (const off of offs)
|
|
317
|
-
off();
|
|
318
|
-
clear();
|
|
319
|
-
},
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
//# sourceMappingURL=join-guidance.js.map
|