@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.
Files changed (43) hide show
  1. package/addon/components/template-builder/canvas.hbs +23 -0
  2. package/addon/components/template-builder/canvas.js +116 -0
  3. package/addon/components/template-builder/element-renderer.hbs +126 -0
  4. package/addon/components/template-builder/element-renderer.js +398 -0
  5. package/addon/components/template-builder/layers-panel.hbs +99 -0
  6. package/addon/components/template-builder/layers-panel.js +146 -0
  7. package/addon/components/template-builder/properties-panel/field.hbs +7 -0
  8. package/addon/components/template-builder/properties-panel/field.js +9 -0
  9. package/addon/components/template-builder/properties-panel/section.hbs +24 -0
  10. package/addon/components/template-builder/properties-panel/section.js +19 -0
  11. package/addon/components/template-builder/properties-panel.hbs +576 -0
  12. package/addon/components/template-builder/properties-panel.js +413 -0
  13. package/addon/components/template-builder/queries-panel.hbs +84 -0
  14. package/addon/components/template-builder/queries-panel.js +88 -0
  15. package/addon/components/template-builder/query-form.hbs +260 -0
  16. package/addon/components/template-builder/query-form.js +309 -0
  17. package/addon/components/template-builder/toolbar.hbs +134 -0
  18. package/addon/components/template-builder/toolbar.js +106 -0
  19. package/addon/components/template-builder/variable-picker.hbs +210 -0
  20. package/addon/components/template-builder/variable-picker.js +181 -0
  21. package/addon/components/template-builder.hbs +119 -0
  22. package/addon/components/template-builder.js +567 -0
  23. package/addon/helpers/string-starts-with.js +14 -0
  24. package/addon/services/template-builder.js +72 -0
  25. package/addon/styles/addon.css +1 -0
  26. package/addon/styles/components/badge.css +66 -12
  27. package/addon/styles/components/template-builder.css +297 -0
  28. package/addon/utils/get-currency.js +1 -1
  29. package/app/components/template-builder/canvas.js +1 -0
  30. package/app/components/template-builder/element-renderer.js +1 -0
  31. package/app/components/template-builder/layers-panel.js +1 -0
  32. package/app/components/template-builder/properties-panel/field.js +1 -0
  33. package/app/components/template-builder/properties-panel/section.js +1 -0
  34. package/app/components/template-builder/properties-panel.js +1 -0
  35. package/app/components/template-builder/queries-panel.js +1 -0
  36. package/app/components/template-builder/query-form.js +1 -0
  37. package/app/components/template-builder/toolbar.js +1 -0
  38. package/app/components/template-builder/variable-picker.js +1 -0
  39. package/app/components/template-builder.js +1 -0
  40. package/app/helpers/string-starts-with.js +1 -0
  41. package/app/services/template-builder.js +1 -0
  42. package/package.json +3 -2
  43. 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,7 @@
1
+ {{! Properties Panel Field }}
2
+ <div class="tb-prop-field {{if (eq @span '2') 'col-span-2'}}">
3
+ {{#if @label}}
4
+ <label class="block text-xs text-gray-500 dark:text-gray-400 mb-0.5">{{@label}}</label>
5
+ {{/if}}
6
+ {{yield}}
7
+ </div>
@@ -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
+ }