@peter.prib/renderengine 0.2.4 → 0.2.5
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/REFERENCE.md +7 -3
- package/dist/renderengine-workspace.umd.js +3875 -3838
- package/dist/renderengine.umd.js +3823 -3786
- package/package.json +1 -1
- package/src/charting/DiagramEditor.js +3330 -3330
- package/src/charting/DiagramTypeRegistry.js +361 -326
- package/src/charting/Palette.js +119 -119
- package/src/charting/TemplateCatalog.js +61 -59
package/src/charting/Palette.js
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import { render } from '../workspace/dom.js';
|
|
2
|
-
import { CreateTemplateCommand } from './DiagramEditorCommands.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Palette — category picker plus a grid of draggable shapes.
|
|
6
|
-
*
|
|
7
|
-
* Previews are drawn by the templates themselves (Template#renderPreview), so a
|
|
8
|
-
* palette tile always matches what lands on the canvas.
|
|
9
|
-
*/
|
|
10
|
-
export class Palette {
|
|
11
|
-
/**
|
|
12
|
-
* @param {Object} options
|
|
13
|
-
* @param {HTMLElement} options.container - Element the palette renders into (contents replaced)
|
|
14
|
-
* @param {TemplateCatalog} options.catalog - Shapes offered in the current category
|
|
15
|
-
* @param {DiagramEditor} options.editor - Editor receiving drops
|
|
16
|
-
* @param {Array<{group: string, types: Array<{key: string, name: string, description?: string}>}>} [options.categories]
|
|
17
|
-
* Grouped categories for the picker; omit to hide the picker
|
|
18
|
-
* @param {string} [options.category] - Selected category key
|
|
19
|
-
* @param {(key: string) => void} [options.onCategoryChange]
|
|
20
|
-
*/
|
|
21
|
-
constructor(options = {}) {
|
|
22
|
-
this.container = options.container;
|
|
23
|
-
this.catalog = options.catalog;
|
|
24
|
-
this.editor = options.editor;
|
|
25
|
-
this.categories = options.categories ?? [];
|
|
26
|
-
this.category = options.category ?? null;
|
|
27
|
-
this.onCategoryChange = options.onCategoryChange ?? (() => {});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
render() {
|
|
31
|
-
if (!this.container) return;
|
|
32
|
-
const doc = this.container.ownerDocument;
|
|
33
|
-
render(this.container, [
|
|
34
|
-
this.categories.length ? this._pickerSpec() : '',
|
|
35
|
-
{
|
|
36
|
-
style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '6px', overflowY: 'auto', flex: '1 1 auto', alignContent: 'start' },
|
|
37
|
-
children: this.catalog.getAll().map(entry => this._tileSpec(entry, doc)),
|
|
38
|
-
},
|
|
39
|
-
], doc);
|
|
40
|
-
Object.assign(this.container.style, { display: 'flex', flexDirection: 'column', gap: '8px', minHeight: '0' });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
_pickerSpec() {
|
|
44
|
-
const selected = this.categories.flatMap(g => g.types).find(t => t.key === this.category);
|
|
45
|
-
return {
|
|
46
|
-
tag: 'select',
|
|
47
|
-
attrs: { 'aria-label': 'Diagram type', title: selected?.description },
|
|
48
|
-
style: { width: '100%', padding: '6px', fontSize: '12px', flex: '0 0 auto' },
|
|
49
|
-
on: { change: (e) => this.onCategoryChange(e.target.value) },
|
|
50
|
-
children: this.categories.map(({ group, types }) => ({
|
|
51
|
-
tag: 'optgroup',
|
|
52
|
-
attrs: { label: group },
|
|
53
|
-
children: types.map(t => ({
|
|
54
|
-
tag: 'option',
|
|
55
|
-
attrs: { value: t.key, selected: t.key === this.category, title: t.description },
|
|
56
|
-
text: t.name,
|
|
57
|
-
})),
|
|
58
|
-
})),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
_tileSpec(entry, doc) {
|
|
63
|
-
return {
|
|
64
|
-
attrs: { draggable: 'true', title: `Drag to add ${entry.name}` },
|
|
65
|
-
style: {
|
|
66
|
-
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px',
|
|
67
|
-
padding: '6px 4px', background: '#fff', border: '1px solid #ddd', borderRadius: '4px',
|
|
68
|
-
cursor: 'grab', userSelect: 'none',
|
|
69
|
-
},
|
|
70
|
-
on: { dragstart: (e) => this._startDrag(e, entry) },
|
|
71
|
-
children: [
|
|
72
|
-
entry.factory('__preview', 0, 0).renderPreview(40, doc),
|
|
73
|
-
{
|
|
74
|
-
text: entry.name,
|
|
75
|
-
style: { fontSize: '11px', color: '#333', textAlign: 'center', lineHeight: '1.2', overflowWrap: 'anywhere' },
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
_startDrag(e, entry) {
|
|
82
|
-
if (!e.dataTransfer) return;
|
|
83
|
-
e.dataTransfer.effectAllowed = 'copy';
|
|
84
|
-
e.dataTransfer.setData('templateType', entry.type);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Create the dragged shape at a drop point given in SVG viewBox coordinates,
|
|
89
|
-
* snapping to the grid when it is shown.
|
|
90
|
-
*/
|
|
91
|
-
handleDrop(e, x, y) {
|
|
92
|
-
const templateType = e.dataTransfer.getData('templateType');
|
|
93
|
-
if (!templateType) return false;
|
|
94
|
-
|
|
95
|
-
let snapX = x, snapY = y;
|
|
96
|
-
if (this.editor.showGrid && this.editor.gridSize) {
|
|
97
|
-
const gridSize = this.editor.gridSize;
|
|
98
|
-
snapX = Math.round(x / gridSize) * gridSize;
|
|
99
|
-
snapY = Math.round(y / gridSize) * gridSize;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const template = this.catalog.create(templateType, this._generateId(templateType), snapX, snapY);
|
|
103
|
-
if (!template) return false;
|
|
104
|
-
|
|
105
|
-
if (this.editor.history) {
|
|
106
|
-
this.editor.history.execute(new CreateTemplateCommand(this.editor, template));
|
|
107
|
-
} else {
|
|
108
|
-
this.editor.addTemplate(template);
|
|
109
|
-
}
|
|
110
|
-
this.editor.render();
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
_generateId(templateType) {
|
|
115
|
-
let counter = 1;
|
|
116
|
-
while (this.editor.getTemplate(`${templateType}_${counter}`)) counter++;
|
|
117
|
-
return `${templateType}_${counter}`;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
1
|
+
import { render } from '../workspace/dom.js';
|
|
2
|
+
import { CreateTemplateCommand } from './DiagramEditorCommands.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Palette — category picker plus a grid of draggable shapes.
|
|
6
|
+
*
|
|
7
|
+
* Previews are drawn by the templates themselves (Template#renderPreview), so a
|
|
8
|
+
* palette tile always matches what lands on the canvas.
|
|
9
|
+
*/
|
|
10
|
+
export class Palette {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} options
|
|
13
|
+
* @param {HTMLElement} options.container - Element the palette renders into (contents replaced)
|
|
14
|
+
* @param {TemplateCatalog} options.catalog - Shapes offered in the current category
|
|
15
|
+
* @param {DiagramEditor} options.editor - Editor receiving drops
|
|
16
|
+
* @param {Array<{group: string, types: Array<{key: string, name: string, description?: string}>}>} [options.categories]
|
|
17
|
+
* Grouped categories for the picker; omit to hide the picker
|
|
18
|
+
* @param {string} [options.category] - Selected category key
|
|
19
|
+
* @param {(key: string) => void} [options.onCategoryChange]
|
|
20
|
+
*/
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.container = options.container;
|
|
23
|
+
this.catalog = options.catalog;
|
|
24
|
+
this.editor = options.editor;
|
|
25
|
+
this.categories = options.categories ?? [];
|
|
26
|
+
this.category = options.category ?? null;
|
|
27
|
+
this.onCategoryChange = options.onCategoryChange ?? (() => {});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render() {
|
|
31
|
+
if (!this.container) return;
|
|
32
|
+
const doc = this.container.ownerDocument;
|
|
33
|
+
render(this.container, [
|
|
34
|
+
this.categories.length ? this._pickerSpec() : '',
|
|
35
|
+
{
|
|
36
|
+
style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '6px', overflowY: 'auto', flex: '1 1 auto', alignContent: 'start' },
|
|
37
|
+
children: this.catalog.getAll().map(entry => this._tileSpec(entry, doc)),
|
|
38
|
+
},
|
|
39
|
+
], doc);
|
|
40
|
+
Object.assign(this.container.style, { display: 'flex', flexDirection: 'column', gap: '8px', minHeight: '0' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_pickerSpec() {
|
|
44
|
+
const selected = this.categories.flatMap(g => g.types).find(t => t.key === this.category);
|
|
45
|
+
return {
|
|
46
|
+
tag: 'select',
|
|
47
|
+
attrs: { 'aria-label': 'Diagram type', title: selected?.description },
|
|
48
|
+
style: { width: '100%', padding: '6px', fontSize: '12px', flex: '0 0 auto' },
|
|
49
|
+
on: { change: (e) => this.onCategoryChange(e.target.value) },
|
|
50
|
+
children: this.categories.map(({ group, types }) => ({
|
|
51
|
+
tag: 'optgroup',
|
|
52
|
+
attrs: { label: group },
|
|
53
|
+
children: types.map(t => ({
|
|
54
|
+
tag: 'option',
|
|
55
|
+
attrs: { value: t.key, selected: t.key === this.category, title: t.description },
|
|
56
|
+
text: t.name,
|
|
57
|
+
})),
|
|
58
|
+
})),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_tileSpec(entry, doc) {
|
|
63
|
+
return {
|
|
64
|
+
attrs: { draggable: 'true', title: entry.description ? `${entry.name}: ${entry.description}\nDrag to add` : `Drag to add ${entry.name}` },
|
|
65
|
+
style: {
|
|
66
|
+
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px',
|
|
67
|
+
padding: '6px 4px', background: '#fff', border: '1px solid #ddd', borderRadius: '4px',
|
|
68
|
+
cursor: 'grab', userSelect: 'none',
|
|
69
|
+
},
|
|
70
|
+
on: { dragstart: (e) => this._startDrag(e, entry) },
|
|
71
|
+
children: [
|
|
72
|
+
entry.factory('__preview', 0, 0).renderPreview(40, doc),
|
|
73
|
+
{
|
|
74
|
+
text: entry.name,
|
|
75
|
+
style: { fontSize: '11px', color: '#333', textAlign: 'center', lineHeight: '1.2', overflowWrap: 'anywhere' },
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_startDrag(e, entry) {
|
|
82
|
+
if (!e.dataTransfer) return;
|
|
83
|
+
e.dataTransfer.effectAllowed = 'copy';
|
|
84
|
+
e.dataTransfer.setData('templateType', entry.type);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create the dragged shape at a drop point given in SVG viewBox coordinates,
|
|
89
|
+
* snapping to the grid when it is shown.
|
|
90
|
+
*/
|
|
91
|
+
handleDrop(e, x, y) {
|
|
92
|
+
const templateType = e.dataTransfer.getData('templateType');
|
|
93
|
+
if (!templateType) return false;
|
|
94
|
+
|
|
95
|
+
let snapX = x, snapY = y;
|
|
96
|
+
if (this.editor.showGrid && this.editor.gridSize) {
|
|
97
|
+
const gridSize = this.editor.gridSize;
|
|
98
|
+
snapX = Math.round(x / gridSize) * gridSize;
|
|
99
|
+
snapY = Math.round(y / gridSize) * gridSize;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const template = this.catalog.create(templateType, this._generateId(templateType), snapX, snapY);
|
|
103
|
+
if (!template) return false;
|
|
104
|
+
|
|
105
|
+
if (this.editor.history) {
|
|
106
|
+
this.editor.history.execute(new CreateTemplateCommand(this.editor, template));
|
|
107
|
+
} else {
|
|
108
|
+
this.editor.addTemplate(template);
|
|
109
|
+
}
|
|
110
|
+
this.editor.render();
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_generateId(templateType) {
|
|
115
|
+
let counter = 1;
|
|
116
|
+
while (this.editor.getTemplate(`${templateType}_${counter}`)) counter++;
|
|
117
|
+
return `${templateType}_${counter}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,59 +1,61 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TemplateCatalog — registry of template types that can be added to a diagram.
|
|
3
|
-
* Each entry describes a template type and how to create instances.
|
|
4
|
-
*/
|
|
5
|
-
export class TemplateCatalog {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.entries = new Map(); // type -> { name, icon, factory, properties }
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Register a template type.
|
|
12
|
-
* @param {string} type - Unique type identifier
|
|
13
|
-
* @param {Object} spec - Template specification
|
|
14
|
-
* @param {string} spec.name - Display name
|
|
15
|
-
* @param {string} spec.icon - Icon/emoji (e.g., "📦")
|
|
16
|
-
* @param {Function} spec.factory - Factory function: (id, x, y) => Template instance
|
|
17
|
-
* @param {Object} spec.properties - Default properties for this type
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*
|
|
50
|
-
* @param {
|
|
51
|
-
* @param {
|
|
52
|
-
* @
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
/**
|
|
2
|
+
* TemplateCatalog — registry of template types that can be added to a diagram.
|
|
3
|
+
* Each entry describes a template type and how to create instances.
|
|
4
|
+
*/
|
|
5
|
+
export class TemplateCatalog {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.entries = new Map(); // type -> { name, icon, factory, properties }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register a template type.
|
|
12
|
+
* @param {string} type - Unique type identifier
|
|
13
|
+
* @param {Object} spec - Template specification
|
|
14
|
+
* @param {string} spec.name - Display name
|
|
15
|
+
* @param {string} spec.icon - Icon/emoji (e.g., "📦")
|
|
16
|
+
* @param {Function} spec.factory - Factory function: (id, x, y) => Template instance
|
|
17
|
+
* @param {Object} spec.properties - Default properties for this type
|
|
18
|
+
* @param {string} [spec.description] - What it is (the palette shows it on hover)
|
|
19
|
+
*/
|
|
20
|
+
register(type, spec) {
|
|
21
|
+
if (!spec.factory) {
|
|
22
|
+
throw new Error(`TemplateCatalog.register: factory required for type '${type}'`);
|
|
23
|
+
}
|
|
24
|
+
this.entries.set(type, {
|
|
25
|
+
type,
|
|
26
|
+
name: spec.name || type,
|
|
27
|
+
icon: spec.icon || '■',
|
|
28
|
+
factory: spec.factory,
|
|
29
|
+
properties: spec.properties || {},
|
|
30
|
+
...(spec.description && { description: spec.description }),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get a template type entry.
|
|
36
|
+
*/
|
|
37
|
+
get(type) {
|
|
38
|
+
return this.entries.get(type);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get all entries.
|
|
43
|
+
*/
|
|
44
|
+
getAll() {
|
|
45
|
+
return Array.from(this.entries.values());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a template instance of given type.
|
|
50
|
+
* @param {string} type
|
|
51
|
+
* @param {string} id - Instance id
|
|
52
|
+
* @param {number} x - X position
|
|
53
|
+
* @param {number} y - Y position
|
|
54
|
+
* @returns {Template|null}
|
|
55
|
+
*/
|
|
56
|
+
create(type, id, x, y) {
|
|
57
|
+
const entry = this.get(type);
|
|
58
|
+
if (!entry) return null;
|
|
59
|
+
return entry.factory(id, x, y);
|
|
60
|
+
}
|
|
61
|
+
}
|