@fleetbase/ember-ui 0.3.23 → 0.3.24
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/addon/components/template-builder/canvas.hbs +23 -0
- package/addon/components/template-builder/canvas.js +116 -0
- package/addon/components/template-builder/element-renderer.hbs +126 -0
- package/addon/components/template-builder/element-renderer.js +398 -0
- package/addon/components/template-builder/layers-panel.hbs +99 -0
- package/addon/components/template-builder/layers-panel.js +146 -0
- package/addon/components/template-builder/properties-panel/field.hbs +7 -0
- package/addon/components/template-builder/properties-panel/field.js +9 -0
- package/addon/components/template-builder/properties-panel/section.hbs +24 -0
- package/addon/components/template-builder/properties-panel/section.js +19 -0
- package/addon/components/template-builder/properties-panel.hbs +576 -0
- package/addon/components/template-builder/properties-panel.js +413 -0
- package/addon/components/template-builder/queries-panel.hbs +84 -0
- package/addon/components/template-builder/queries-panel.js +88 -0
- package/addon/components/template-builder/query-form.hbs +260 -0
- package/addon/components/template-builder/query-form.js +309 -0
- package/addon/components/template-builder/toolbar.hbs +134 -0
- package/addon/components/template-builder/toolbar.js +106 -0
- package/addon/components/template-builder/variable-picker.hbs +210 -0
- package/addon/components/template-builder/variable-picker.js +181 -0
- package/addon/components/template-builder.hbs +119 -0
- package/addon/components/template-builder.js +567 -0
- package/addon/helpers/string-starts-with.js +14 -0
- package/addon/services/template-builder.js +72 -0
- package/addon/styles/addon.css +1 -0
- package/addon/styles/components/badge.css +66 -12
- package/addon/styles/components/template-builder.css +297 -0
- package/addon/utils/get-currency.js +1 -1
- package/app/components/template-builder/canvas.js +1 -0
- package/app/components/template-builder/element-renderer.js +1 -0
- package/app/components/template-builder/layers-panel.js +1 -0
- package/app/components/template-builder/properties-panel/field.js +1 -0
- package/app/components/template-builder/properties-panel/section.js +1 -0
- package/app/components/template-builder/properties-panel.js +1 -0
- package/app/components/template-builder/queries-panel.js +1 -0
- package/app/components/template-builder/query-form.js +1 -0
- package/app/components/template-builder/toolbar.js +1 -0
- package/app/components/template-builder/variable-picker.js +1 -0
- package/app/components/template-builder.js +1 -0
- package/app/helpers/string-starts-with.js +1 -0
- package/app/services/template-builder.js +1 -0
- package/package.json +3 -2
- package/tsconfig.declarations.json +8 -8
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{{! Template Builder Layers Panel }}
|
|
2
|
+
<div class="tb-layers-panel flex flex-col h-full" ...attributes>
|
|
3
|
+
{{! Header }}
|
|
4
|
+
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-gray-700">
|
|
5
|
+
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Layers</span>
|
|
6
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">{{this.sortedElements.length}}</span>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
{{! Element list }}
|
|
10
|
+
<div class="flex-1 overflow-y-auto">
|
|
11
|
+
{{#if this.sortedElements.length}}
|
|
12
|
+
{{#each this.sortedElements as |element|}}
|
|
13
|
+
{{! Outer row is a div so that real <button> children are valid. }}
|
|
14
|
+
<div
|
|
15
|
+
class="tb-layer-row group flex items-center px-2 py-1.5 cursor-pointer border-b border-transparent hover:bg-gray-50 dark:hover:bg-gray-800 {{if (this.isSelected element) 'bg-blue-50 dark:bg-blue-900/20 border-l-2 border-l-blue-500'}}"
|
|
16
|
+
{{on "click" (fn this.selectElement element)}}
|
|
17
|
+
>
|
|
18
|
+
{{! Type icon }}
|
|
19
|
+
<FaIcon
|
|
20
|
+
@icon={{this.elementIcon element.type}}
|
|
21
|
+
class="w-3 h-3 mr-2 flex-shrink-0 {{if (this.isSelected element) 'text-blue-500' 'text-gray-400 dark:text-gray-500'}}"
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
{{! Label / rename input }}
|
|
25
|
+
<div class="flex-1 min-w-0 mr-1">
|
|
26
|
+
{{#if (this.isRenaming element)}}
|
|
27
|
+
<input
|
|
28
|
+
type="text"
|
|
29
|
+
class="w-full text-xs bg-white dark:bg-gray-700 border border-blue-400 rounded px-1 py-0 outline-none"
|
|
30
|
+
value={{this.renameValue}}
|
|
31
|
+
{{on "input" (fn (mut this.renameValue) value="target.value")}}
|
|
32
|
+
{{on "keydown" (fn this.handleRenameKeydown element)}}
|
|
33
|
+
{{on "blur" (fn this.commitRename element)}}
|
|
34
|
+
{{did-insert (fn (mut this.renameValue) this.renameValue)}}
|
|
35
|
+
/>
|
|
36
|
+
{{else}}
|
|
37
|
+
<span
|
|
38
|
+
class="block text-xs truncate {{if (this.isSelected element) 'text-blue-700 dark:text-blue-300 font-medium' 'text-gray-700 dark:text-gray-300'}} {{unless (this.isVisible element) 'opacity-40'}}"
|
|
39
|
+
{{on "dblclick" (fn this.startRename element)}}
|
|
40
|
+
title="Double-click to rename"
|
|
41
|
+
>
|
|
42
|
+
{{this.elementLabel element}}
|
|
43
|
+
</span>
|
|
44
|
+
{{/if}}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{{! Action buttons (shown on hover or when selected) }}
|
|
48
|
+
<div class="flex items-center space-x-0.5 opacity-0 group-hover:opacity-100 {{if (this.isSelected element) 'opacity-100'}} transition-opacity">
|
|
49
|
+
{{! Visibility toggle }}
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
class="p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
|
|
53
|
+
title={{if (this.isVisible element) "Hide element" "Show element"}}
|
|
54
|
+
{{on "click" (fn this.toggleVisibility element)}}
|
|
55
|
+
>
|
|
56
|
+
<FaIcon @icon={{if (this.isVisible element) "eye" "eye-slash"}} class="w-3 h-3" />
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
{{! Move up }}
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
class="p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
|
|
63
|
+
title="Move layer up"
|
|
64
|
+
{{on "click" (fn this.moveUp element)}}
|
|
65
|
+
>
|
|
66
|
+
<FaIcon @icon="chevron-up" class="w-3 h-3" />
|
|
67
|
+
</button>
|
|
68
|
+
|
|
69
|
+
{{! Move down }}
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
class="p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
|
|
73
|
+
title="Move layer down"
|
|
74
|
+
{{on "click" (fn this.moveDown element)}}
|
|
75
|
+
>
|
|
76
|
+
<FaIcon @icon="chevron-down" class="w-3 h-3" />
|
|
77
|
+
</button>
|
|
78
|
+
|
|
79
|
+
{{! Delete }}
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
class="p-0.5 rounded hover:bg-red-100 dark:hover:bg-red-900/30 text-gray-400 dark:text-gray-500 hover:text-red-500 dark:hover:text-red-400"
|
|
83
|
+
title="Delete element"
|
|
84
|
+
{{on "click" (fn this.deleteElement element)}}
|
|
85
|
+
>
|
|
86
|
+
<FaIcon @icon="trash" class="w-3 h-3" />
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{{/each}}
|
|
91
|
+
{{else}}
|
|
92
|
+
<div class="flex flex-col items-center justify-center py-8 px-4 text-center">
|
|
93
|
+
<FaIcon @icon="layer-group" class="w-6 h-6 text-gray-300 dark:text-gray-600 mb-2" />
|
|
94
|
+
<p class="text-xs text-gray-400 dark:text-gray-500">No elements yet.</p>
|
|
95
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">Use the toolbar to add elements.</p>
|
|
96
|
+
</div>
|
|
97
|
+
{{/if}}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TemplateBuilderLayersPanelComponent
|
|
7
|
+
*
|
|
8
|
+
* Left panel showing the element tree (layers). Supports:
|
|
9
|
+
* - Selecting an element by clicking its row
|
|
10
|
+
* - Toggling element visibility
|
|
11
|
+
* - Deleting an element
|
|
12
|
+
* - Reordering via z_index controls (move up/down)
|
|
13
|
+
* - Renaming an element label
|
|
14
|
+
*
|
|
15
|
+
* @argument {Array} elements - The template content array (elements)
|
|
16
|
+
* @argument {Object} selectedElement - Currently selected element
|
|
17
|
+
* @argument {Function} onSelectElement - Called with element when row is clicked
|
|
18
|
+
* @argument {Function} onUpdateElement - Called with (uuid, changes) to update an element
|
|
19
|
+
* @argument {Function} onDeleteElement - Called with uuid to delete an element
|
|
20
|
+
* @argument {Function} onReorderElement - Called with (uuid, direction) — 'up' or 'down'
|
|
21
|
+
*/
|
|
22
|
+
export default class TemplateBuilderLayersPanelComponent extends Component {
|
|
23
|
+
@tracked renamingUuid = null;
|
|
24
|
+
@tracked renameValue = '';
|
|
25
|
+
|
|
26
|
+
get sortedElements() {
|
|
27
|
+
const elements = this.args.elements ?? [];
|
|
28
|
+
// Sort descending by z_index so highest layer is at top of list (like Figma/Sketch)
|
|
29
|
+
return [...elements].sort((a, b) => (b.z_index ?? 1) - (a.z_index ?? 1));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@action
|
|
33
|
+
elementIcon(type) {
|
|
34
|
+
const icons = {
|
|
35
|
+
text: 'font',
|
|
36
|
+
image: 'image',
|
|
37
|
+
table: 'table',
|
|
38
|
+
line: 'minus',
|
|
39
|
+
shape: 'square',
|
|
40
|
+
qr_code: 'qrcode',
|
|
41
|
+
barcode: 'barcode',
|
|
42
|
+
};
|
|
43
|
+
return icons[type] ?? 'layer-group';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@action
|
|
47
|
+
elementLabel(element) {
|
|
48
|
+
if (element.label) return element.label;
|
|
49
|
+
const typeLabels = {
|
|
50
|
+
text: 'Text',
|
|
51
|
+
image: 'Image',
|
|
52
|
+
table: 'Table',
|
|
53
|
+
line: 'Line',
|
|
54
|
+
shape: 'Shape',
|
|
55
|
+
qr_code: 'QR Code',
|
|
56
|
+
barcode: 'Barcode',
|
|
57
|
+
};
|
|
58
|
+
return typeLabels[element.type] ?? 'Element';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@action
|
|
62
|
+
isSelected(element) {
|
|
63
|
+
return this.args.selectedElement?.uuid === element.uuid;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@action
|
|
67
|
+
isVisible(element) {
|
|
68
|
+
return element.visible !== false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@action
|
|
72
|
+
isRenaming(element) {
|
|
73
|
+
return this.renamingUuid === element.uuid;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@action
|
|
77
|
+
selectElement(element, event) {
|
|
78
|
+
event.stopPropagation();
|
|
79
|
+
if (this.args.onSelectElement) {
|
|
80
|
+
this.args.onSelectElement(element);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@action
|
|
85
|
+
toggleVisibility(element, event) {
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
if (this.args.onUpdateElement) {
|
|
88
|
+
this.args.onUpdateElement(element.uuid, { visible: !this.isVisible(element) });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@action
|
|
93
|
+
deleteElement(element, event) {
|
|
94
|
+
event.stopPropagation();
|
|
95
|
+
if (this.args.onDeleteElement) {
|
|
96
|
+
this.args.onDeleteElement(element.uuid);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@action
|
|
101
|
+
moveUp(element, event) {
|
|
102
|
+
event.stopPropagation();
|
|
103
|
+
if (this.args.onReorderElement) {
|
|
104
|
+
this.args.onReorderElement(element.uuid, 'up');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@action
|
|
109
|
+
moveDown(element, event) {
|
|
110
|
+
event.stopPropagation();
|
|
111
|
+
if (this.args.onReorderElement) {
|
|
112
|
+
this.args.onReorderElement(element.uuid, 'down');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@action
|
|
117
|
+
startRename(element, event) {
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
this.renamingUuid = element.uuid;
|
|
120
|
+
this.renameValue = this.elementLabel(element);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@action
|
|
124
|
+
commitRename(element) {
|
|
125
|
+
if (this.renameValue.trim() && this.args.onUpdateElement) {
|
|
126
|
+
this.args.onUpdateElement(element.uuid, { label: this.renameValue.trim() });
|
|
127
|
+
}
|
|
128
|
+
this.renamingUuid = null;
|
|
129
|
+
this.renameValue = '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@action
|
|
133
|
+
cancelRename() {
|
|
134
|
+
this.renamingUuid = null;
|
|
135
|
+
this.renameValue = '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@action
|
|
139
|
+
handleRenameKeydown(element, event) {
|
|
140
|
+
if (event.key === 'Enter') {
|
|
141
|
+
this.commitRename(element);
|
|
142
|
+
} else if (event.key === 'Escape') {
|
|
143
|
+
this.cancelRename();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A labelled field row in the properties panel.
|
|
5
|
+
*
|
|
6
|
+
* @argument {String} label - Field label
|
|
7
|
+
* @argument {String} span - Optional: '2' for full-width (grid col-span-2)
|
|
8
|
+
*/
|
|
9
|
+
export default class TemplateBuilderPropertiesPanelFieldComponent extends Component {}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{{! Properties Panel Section }}
|
|
2
|
+
<div class="tb-prop-section border-b border-gray-100 dark:border-gray-800">
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
class="w-full flex items-center justify-between px-3 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
6
|
+
{{on "click" this.toggle}}
|
|
7
|
+
>
|
|
8
|
+
<div class="flex items-center space-x-2">
|
|
9
|
+
{{#if @icon}}
|
|
10
|
+
<FaIcon @icon={{@icon}} class="w-3 h-3 text-gray-400 dark:text-gray-500" />
|
|
11
|
+
{{/if}}
|
|
12
|
+
<span class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">{{@title}}</span>
|
|
13
|
+
</div>
|
|
14
|
+
<FaIcon
|
|
15
|
+
@icon={{if @isOpen "chevron-up" "chevron-down"}}
|
|
16
|
+
class="w-3 h-3 text-gray-400 dark:text-gray-500 transition-transform"
|
|
17
|
+
/>
|
|
18
|
+
</button>
|
|
19
|
+
{{#if @isOpen}}
|
|
20
|
+
<div class="px-3 pb-3 pt-2">
|
|
21
|
+
{{yield}}
|
|
22
|
+
</div>
|
|
23
|
+
{{/if}}
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collapsible section wrapper for the properties panel.
|
|
6
|
+
*
|
|
7
|
+
* @argument {String} title - Section title
|
|
8
|
+
* @argument {String} icon - FontAwesome icon name
|
|
9
|
+
* @argument {Boolean} isOpen - Whether the section is expanded
|
|
10
|
+
* @argument {Function} onToggle - Called when the header is clicked
|
|
11
|
+
*/
|
|
12
|
+
export default class TemplateBuilderPropertiesPanelSectionComponent extends Component {
|
|
13
|
+
@action
|
|
14
|
+
toggle() {
|
|
15
|
+
if (this.args.onToggle) {
|
|
16
|
+
this.args.onToggle();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|