@fleetbase/ember-ui 0.3.22 → 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/layout/header/smart-nav-menu/dropdown.hbs +77 -68
- package/addon/components/layout/header/smart-nav-menu/dropdown.js +7 -54
- 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/smart-nav-menu.css +35 -29
- 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
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Layout::Header::SmartNavMenu::Dropdown
|
|
3
3
|
Phase 2: multi-column card grid with search filter.
|
|
4
4
|
|
|
5
|
-
Shortcuts are
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Shortcuts are already registered as first-class items in the universe
|
|
6
|
+
registry by menu-service.registerHeaderMenuItem(), so @items already
|
|
7
|
+
contains both parent extension items and their shortcut siblings.
|
|
8
|
+
No client-side expansion is needed.
|
|
8
9
|
|
|
9
10
|
Args:
|
|
10
11
|
@items - Array of MenuItem objects to display
|
|
@@ -15,9 +16,13 @@
|
|
|
15
16
|
@onQuickPin - Action to pin an item directly from the dropdown
|
|
16
17
|
@atPinnedLimit - Boolean: true when the bar is full (pin button hidden)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
Click-area pattern:
|
|
20
|
+
Each card is wrapped in a `snm-dropdown-card-wrap` div (position: relative).
|
|
21
|
+
The card itself is the <LinkToExternal> (or <a> for onClick items), making
|
|
22
|
+
the entire card surface clickable. The pin button is a sibling of the card
|
|
23
|
+
inside the wrapper, absolutely positioned in the top-right corner so it
|
|
24
|
+
sits above the card link and receives its own clicks without violating the
|
|
25
|
+
no-nested-interactive rule.
|
|
21
26
|
}}
|
|
22
27
|
<div
|
|
23
28
|
class="snm-dropdown snm-dropdown--wide"
|
|
@@ -38,7 +43,7 @@
|
|
|
38
43
|
</button>
|
|
39
44
|
</div>
|
|
40
45
|
|
|
41
|
-
{{! ── Search bar
|
|
46
|
+
{{! ── Search bar ──────────────────────────────────────────────────────── }}
|
|
42
47
|
<div class="snm-dropdown-search-bar">
|
|
43
48
|
<span class="snm-dropdown-search-icon" aria-hidden="true">
|
|
44
49
|
<FaIcon @icon="magnifying-glass" @size="xs" />
|
|
@@ -76,15 +81,15 @@
|
|
|
76
81
|
{{else}}
|
|
77
82
|
{{#each this.filteredItems as |item|}}
|
|
78
83
|
{{#if item._isShortcut}}
|
|
79
|
-
{{! ── Shortcut
|
|
80
|
-
<div class="snm-dropdown-card"
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
>
|
|
84
|
+
{{! ── Shortcut card ───────────────────────────────────── }}
|
|
85
|
+
<div class="snm-dropdown-card-wrap">
|
|
86
|
+
<LinkToExternal
|
|
87
|
+
@route={{item.route}}
|
|
88
|
+
id={{concat (dasherize (or item.route item.id "sc")) "-dropdown-card"}}
|
|
89
|
+
class="snm-dropdown-card"
|
|
90
|
+
title={{item.title}}
|
|
91
|
+
>
|
|
92
|
+
<div class="snm-dropdown-card-header">
|
|
88
93
|
<span class="snm-dropdown-card-icon">
|
|
89
94
|
{{#if item.iconComponent}}
|
|
90
95
|
{{component (lazy-engine-component item.iconComponent) options=item.iconComponentOptions}}
|
|
@@ -92,43 +97,42 @@
|
|
|
92
97
|
<FaIcon @icon={{or item.icon "circle-dot"}} @prefix={{item.iconPrefix}} @size="sm" />
|
|
93
98
|
{{/if}}
|
|
94
99
|
</span>
|
|
95
|
-
{{! Title row: shortcut name + always-visible muted parent attribution }}
|
|
96
100
|
<span class="snm-dropdown-card-title-group">
|
|
97
101
|
<span class="snm-dropdown-card-title">{{item.title}}</span>
|
|
98
102
|
{{#if item._parentTitle}}
|
|
99
103
|
<span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span>
|
|
100
104
|
{{/if}}
|
|
101
105
|
</span>
|
|
102
|
-
</
|
|
103
|
-
{{#
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{{/
|
|
106
|
+
</div>
|
|
107
|
+
{{#if item.description}}
|
|
108
|
+
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
109
|
+
{{else if item._parentTitle}}
|
|
110
|
+
<p class="snm-dropdown-card-description snm-dropdown-card-description--from"><em>from {{item._parentTitle}}</em></p>
|
|
111
|
+
{{/if}}
|
|
112
|
+
</LinkToExternal>
|
|
113
|
+
{{#unless @atPinnedLimit}}
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
class="snm-dropdown-pin-btn"
|
|
117
|
+
title="Pin to navigation bar"
|
|
118
|
+
aria-label="Pin {{item.title}} to navigation bar"
|
|
119
|
+
{{on "click" (fn @onQuickPin item)}}
|
|
120
|
+
>
|
|
121
|
+
<FaIcon @icon="thumbtack" @size="xs" />
|
|
122
|
+
</button>
|
|
123
|
+
{{/unless}}
|
|
120
124
|
</div>
|
|
121
125
|
{{else}}
|
|
122
126
|
{{! ── Primary extension card ──────────────────────────── }}
|
|
123
|
-
<div class="snm-dropdown-card"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
>
|
|
127
|
+
<div class="snm-dropdown-card-wrap">
|
|
128
|
+
{{#if item.onClick}}
|
|
129
|
+
<a
|
|
130
|
+
href="javascript:;"
|
|
131
|
+
class="snm-dropdown-card"
|
|
132
|
+
title={{item.title}}
|
|
133
|
+
{{on "click" (fn this.handleItemClick item)}}
|
|
134
|
+
>
|
|
135
|
+
<div class="snm-dropdown-card-header">
|
|
132
136
|
<span class="snm-dropdown-card-icon">
|
|
133
137
|
{{#if item.iconComponent}}
|
|
134
138
|
{{component (lazy-engine-component item.iconComponent) options=item.iconComponentOptions}}
|
|
@@ -142,14 +146,19 @@
|
|
|
142
146
|
<span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span>
|
|
143
147
|
{{/if}}
|
|
144
148
|
</span>
|
|
145
|
-
</
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
</div>
|
|
150
|
+
{{#if item.description}}
|
|
151
|
+
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
152
|
+
{{/if}}
|
|
153
|
+
</a>
|
|
154
|
+
{{else}}
|
|
155
|
+
<LinkToExternal
|
|
156
|
+
@route={{item.route}}
|
|
157
|
+
id={{concat (dasherize (or item.route item.id "nav")) "-dropdown-card"}}
|
|
158
|
+
class="snm-dropdown-card"
|
|
159
|
+
title={{item.title}}
|
|
160
|
+
>
|
|
161
|
+
<div class="snm-dropdown-card-header">
|
|
153
162
|
<span class="snm-dropdown-card-icon">
|
|
154
163
|
{{#if item.iconComponent}}
|
|
155
164
|
{{component (lazy-engine-component item.iconComponent) options=item.iconComponentOptions}}
|
|
@@ -163,23 +172,23 @@
|
|
|
163
172
|
<span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span>
|
|
164
173
|
{{/if}}
|
|
165
174
|
</span>
|
|
166
|
-
</
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class="snm-dropdown-pin-btn"
|
|
172
|
-
title="Pin to navigation bar"
|
|
173
|
-
aria-label="Pin {{item.title}} to navigation bar"
|
|
174
|
-
{{on "click" (fn @onQuickPin item)}}
|
|
175
|
-
>
|
|
176
|
-
<FaIcon @icon="thumbtack" @size="xs" />
|
|
177
|
-
</button>
|
|
178
|
-
{{/unless}}
|
|
179
|
-
</div>
|
|
180
|
-
{{#if item.description}}
|
|
181
|
-
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
175
|
+
</div>
|
|
176
|
+
{{#if item.description}}
|
|
177
|
+
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
178
|
+
{{/if}}
|
|
179
|
+
</LinkToExternal>
|
|
182
180
|
{{/if}}
|
|
181
|
+
{{#unless @atPinnedLimit}}
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
class="snm-dropdown-pin-btn"
|
|
185
|
+
title="Pin to navigation bar"
|
|
186
|
+
aria-label="Pin {{item.title}} to navigation bar"
|
|
187
|
+
{{on "click" (fn @onQuickPin item)}}
|
|
188
|
+
>
|
|
189
|
+
<FaIcon @icon="thumbtack" @size="xs" />
|
|
190
|
+
</button>
|
|
191
|
+
{{/unless}}
|
|
183
192
|
</div>
|
|
184
193
|
{{/if}}
|
|
185
194
|
{{/each}}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { action } from '@ember/object';
|
|
4
|
-
import { dasherize } from '@ember/string';
|
|
5
4
|
import { isArray } from '@ember/array';
|
|
6
5
|
import { htmlSafe } from '@ember/template';
|
|
7
6
|
|
|
@@ -25,62 +24,16 @@ export default class LayoutHeaderSmartNavMenuDropdownComponent extends Component
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
|
-
*
|
|
29
|
-
* The resulting array interleaves parent items and their shortcuts in
|
|
30
|
-
* registration order, matching the AWS Console pattern.
|
|
27
|
+
* Returns the items array as-is for filtering.
|
|
31
28
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* Shortcuts are already registered as first-class items in the universe
|
|
30
|
+
* registry (with `_isShortcut: true` and `_parentTitle` set) by
|
|
31
|
+
* `menu-service.registerHeaderMenuItem()` at boot time. There is no need
|
|
32
|
+
* to expand `item.shortcuts` here — doing so would produce a duplicate card
|
|
33
|
+
* for every shortcut (one from the registry, one from the expansion).
|
|
34
34
|
*/
|
|
35
35
|
get expandedItems() {
|
|
36
|
-
|
|
37
|
-
const result = [];
|
|
38
|
-
for (const item of items) {
|
|
39
|
-
result.push(item);
|
|
40
|
-
if (isArray(item.shortcuts)) {
|
|
41
|
-
for (const sc of item.shortcuts) {
|
|
42
|
-
const scId = sc.id ?? dasherize(item.id + '-sc-' + sc.title);
|
|
43
|
-
result.push({
|
|
44
|
-
// ── Identity ────────────────────────────────────────
|
|
45
|
-
id: scId,
|
|
46
|
-
slug: sc.slug ?? scId,
|
|
47
|
-
title: sc.title,
|
|
48
|
-
text: sc.text ?? sc.title,
|
|
49
|
-
label: sc.label ?? sc.title,
|
|
50
|
-
|
|
51
|
-
// ── Routing ──────────────────────────────────────────
|
|
52
|
-
route: sc.route ?? item.route,
|
|
53
|
-
queryParams: sc.queryParams ?? {},
|
|
54
|
-
routeParams: sc.routeParams ?? [],
|
|
55
|
-
|
|
56
|
-
// ── Icons (full surface) ─────────────────────────────
|
|
57
|
-
icon: sc.icon ?? item.icon ?? 'arrow-right',
|
|
58
|
-
iconPrefix: sc.iconPrefix ?? item.iconPrefix ?? null,
|
|
59
|
-
iconSize: sc.iconSize ?? null,
|
|
60
|
-
iconClass: sc.iconClass ?? null,
|
|
61
|
-
iconComponent: sc.iconComponent ?? null,
|
|
62
|
-
iconComponentOptions: sc.iconComponentOptions ?? {},
|
|
63
|
-
|
|
64
|
-
// ── Metadata ─────────────────────────────────────────
|
|
65
|
-
description: sc.description ?? null,
|
|
66
|
-
tags: isArray(sc.tags) ? sc.tags : isArray(item.tags) ? item.tags : null,
|
|
67
|
-
|
|
68
|
-
// ── Behaviour ────────────────────────────────────────
|
|
69
|
-
onClick: sc.onClick ?? null,
|
|
70
|
-
disabled: sc.disabled ?? false,
|
|
71
|
-
|
|
72
|
-
// ── Styling ───────────────────────────────────────────
|
|
73
|
-
class: sc.class ?? null,
|
|
74
|
-
|
|
75
|
-
// ── Internal flags ────────────────────────────────────
|
|
76
|
-
_isShortcut: true,
|
|
77
|
-
_parentTitle: item.title,
|
|
78
|
-
_parentId: item.id,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return result;
|
|
36
|
+
return this.args.items ?? [];
|
|
84
37
|
}
|
|
85
38
|
|
|
86
39
|
get filteredItems() {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{{! Template Builder Canvas }}
|
|
2
|
+
{{! This component is a thin rendering shell. Each ElementRenderer owns its
|
|
3
|
+
own interact.js instance and emits @onMove / @onResize / @onSelect. }}
|
|
4
|
+
<div
|
|
5
|
+
id={{this.canvasId}}
|
|
6
|
+
class="tb-canvas shadow-xl mx-auto select-none"
|
|
7
|
+
style={{this.canvasStyle}}
|
|
8
|
+
{{on "click" this.handleDeselectAll}}
|
|
9
|
+
...attributes
|
|
10
|
+
>
|
|
11
|
+
{{#each this.elements key="uuid" as |element|}}
|
|
12
|
+
<TemplateBuilder::ElementRenderer
|
|
13
|
+
@element={{element}}
|
|
14
|
+
@isSelected={{eq element.uuid this.selectedUuid}}
|
|
15
|
+
@zoom={{this.zoom}}
|
|
16
|
+
@canvasWidth={{this.canvasWidthPx}}
|
|
17
|
+
@canvasHeight={{this.canvasHeightPx}}
|
|
18
|
+
@onSelect={{this.handleSelectElement}}
|
|
19
|
+
@onMove={{@onMoveElement}}
|
|
20
|
+
@onResize={{@onResizeElement}}
|
|
21
|
+
/>
|
|
22
|
+
{{/each}}
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { guidFor } from '@ember/object/internals';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TemplateBuilderCanvasComponent
|
|
7
|
+
*
|
|
8
|
+
* Renders the document canvas — a pixel-accurate representation of the
|
|
9
|
+
* template page. Each element is rendered by an ElementRenderer component
|
|
10
|
+
* that owns its own interact.js instance, drag/resize behaviour, and
|
|
11
|
+
* selection tap handling.
|
|
12
|
+
*
|
|
13
|
+
* This component is intentionally thin:
|
|
14
|
+
* - Computes canvas dimensions and style from @template
|
|
15
|
+
* - Tracks which element is selected (by uuid primitive)
|
|
16
|
+
* - Handles deselect-all when the canvas background is clicked
|
|
17
|
+
* - Passes @canvasWidth / @canvasHeight to each ElementRenderer so it can
|
|
18
|
+
* clamp drag/resize to the canvas boundary
|
|
19
|
+
*
|
|
20
|
+
* It has no knowledge of interact.js, DOM nodes, or element data mutations.
|
|
21
|
+
*
|
|
22
|
+
* @argument {Object} template - The template object (width, height, unit, background_color, content)
|
|
23
|
+
* @argument {Object} selectedElement - The currently selected element (or null)
|
|
24
|
+
* @argument {Number} zoom - Zoom level (1 = 100%)
|
|
25
|
+
* @argument {Function} onSelectElement - Called with (element) when an element is tapped
|
|
26
|
+
* @argument {Function} onMoveElement - Called with (uuid, { x, y }) after a drag ends
|
|
27
|
+
* @argument {Function} onResizeElement - Called with (uuid, { x, y, width, height }) after a resize ends
|
|
28
|
+
* @argument {Function} onDeselectAll - Called when the canvas background is clicked
|
|
29
|
+
*/
|
|
30
|
+
export default class TemplateBuilderCanvasComponent extends Component {
|
|
31
|
+
canvasId = `tb-canvas-${guidFor(this)}`;
|
|
32
|
+
|
|
33
|
+
// No local selection state. The canvas derives selectedUuid from
|
|
34
|
+
// @selectedElement (owned by TemplateBuilderComponent) so that selections
|
|
35
|
+
// made from the layers panel, keyboard shortcuts, or any other source are
|
|
36
|
+
// always reflected here without duplication.
|
|
37
|
+
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
// Canvas dimensions
|
|
40
|
+
// -------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
get zoom() {
|
|
43
|
+
return this.args.zoom ?? 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get canvasWidthPx() {
|
|
47
|
+
const { template } = this.args;
|
|
48
|
+
if (!template) return 794;
|
|
49
|
+
return this._unitToPx(template.width ?? 210, template.unit ?? 'mm');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get canvasHeightPx() {
|
|
53
|
+
const { template } = this.args;
|
|
54
|
+
if (!template) return 1123;
|
|
55
|
+
return this._unitToPx(template.height ?? 297, template.unit ?? 'mm');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get canvasStyle() {
|
|
59
|
+
const w = this.canvasWidthPx * this.zoom;
|
|
60
|
+
const h = this.canvasHeightPx * this.zoom;
|
|
61
|
+
const bg = this.args.template?.background_color ?? '#ffffff';
|
|
62
|
+
return `width:${w}px; height:${h}px; background:${bg}; position:relative; overflow:hidden;`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get elements() {
|
|
66
|
+
return this.args.template?.content ?? [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get selectedUuid() {
|
|
70
|
+
return this.args.selectedElement?.uuid ?? null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// -------------------------------------------------------------------------
|
|
74
|
+
// Selection
|
|
75
|
+
// -------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
@action
|
|
78
|
+
handleSelectElement(element) {
|
|
79
|
+
// Delegate entirely to the parent. The parent sets selectedElement,
|
|
80
|
+
// which flows back down as @selectedElement, which drives selectedUuid.
|
|
81
|
+
if (this.args.onSelectElement) {
|
|
82
|
+
this.args.onSelectElement(element);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@action
|
|
87
|
+
handleDeselectAll(event) {
|
|
88
|
+
// Only deselect when the user clicks the canvas background directly.
|
|
89
|
+
// If the click originated from a child element (an ElementRenderer),
|
|
90
|
+
// interact.js has already fired its tap event and called handleSelectElement.
|
|
91
|
+
// We must not clear the selection here in that case.
|
|
92
|
+
if (event.target !== event.currentTarget) return;
|
|
93
|
+
if (this.args.onDeselectAll) {
|
|
94
|
+
this.args.onDeselectAll();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
// Helpers
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
_unitToPx(value, unit) {
|
|
103
|
+
const PPI = 96;
|
|
104
|
+
switch (unit) {
|
|
105
|
+
case 'mm':
|
|
106
|
+
return Math.round((value / 25.4) * PPI);
|
|
107
|
+
case 'cm':
|
|
108
|
+
return Math.round((value / 2.54) * PPI);
|
|
109
|
+
case 'in':
|
|
110
|
+
return Math.round(value * PPI);
|
|
111
|
+
case 'px':
|
|
112
|
+
default:
|
|
113
|
+
return Math.round(value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{{! Template Builder Element Renderer }}
|
|
2
|
+
{{! Each element owns its own interact.js instance. did-insert sets it up;
|
|
3
|
+
will-destroy tears it down. The parent canvas passes @onMove, @onResize,
|
|
4
|
+
and @onSelect callbacks — it never touches the DOM directly. }}
|
|
5
|
+
<div
|
|
6
|
+
class="tb-element {{this.selectionClass}}"
|
|
7
|
+
style={{this.wrapperStyle}}
|
|
8
|
+
data-element-type={{this.elementType}}
|
|
9
|
+
data-element-uuid={{@element.uuid}}
|
|
10
|
+
{{did-insert this.handleInsert}}
|
|
11
|
+
{{did-update this.handleUpdate @element}}
|
|
12
|
+
{{will-destroy this.handleDestroy}}
|
|
13
|
+
>
|
|
14
|
+
{{! TEXT }}
|
|
15
|
+
{{#if this.isText}}
|
|
16
|
+
<div class="tb-element-text" style={{this.textStyle}}>
|
|
17
|
+
{{this.textContent}}
|
|
18
|
+
</div>
|
|
19
|
+
{{/if}}
|
|
20
|
+
|
|
21
|
+
{{! IMAGE }}
|
|
22
|
+
{{#if this.isImage}}
|
|
23
|
+
{{#if this.imageSrc}}
|
|
24
|
+
<img
|
|
25
|
+
src={{this.imageSrc}}
|
|
26
|
+
alt={{this.imageAlt}}
|
|
27
|
+
style={{this.imageStyle}}
|
|
28
|
+
draggable="false"
|
|
29
|
+
/>
|
|
30
|
+
{{else}}
|
|
31
|
+
<div class="w-full h-full flex items-center justify-center bg-gray-100 dark:bg-gray-700 border border-dashed border-gray-300 dark:border-gray-600 rounded">
|
|
32
|
+
<div class="text-center text-gray-400 dark:text-gray-500 text-xs">
|
|
33
|
+
<FaIcon @icon="image" @size="lg" class="mb-1 block" />
|
|
34
|
+
<span>Image</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
{{/if}}
|
|
38
|
+
{{/if}}
|
|
39
|
+
|
|
40
|
+
{{! TABLE }}
|
|
41
|
+
{{#if this.isTable}}
|
|
42
|
+
<div class="tb-element-table w-full h-full overflow-auto">
|
|
43
|
+
<table class="w-full border-collapse text-xs" style={{this.tableBorderStyle}}>
|
|
44
|
+
{{#if this.tableColumns}}
|
|
45
|
+
<thead>
|
|
46
|
+
<tr>
|
|
47
|
+
{{#each this.tableColumns as |col|}}
|
|
48
|
+
<th class="border px-2 py-1 text-left" style={{this.tableHeaderStyle}}>
|
|
49
|
+
{{col.label}}
|
|
50
|
+
</th>
|
|
51
|
+
{{/each}}
|
|
52
|
+
</tr>
|
|
53
|
+
</thead>
|
|
54
|
+
<tbody>
|
|
55
|
+
{{#if this.tableRows.length}}
|
|
56
|
+
{{#each this.tableRows as |row|}}
|
|
57
|
+
<tr>
|
|
58
|
+
{{#each this.tableColumns as |col|}}
|
|
59
|
+
<td class="border px-2 py-1" style={{this.tableCellStyle}}>
|
|
60
|
+
{{get row col.key}}
|
|
61
|
+
</td>
|
|
62
|
+
{{/each}}
|
|
63
|
+
</tr>
|
|
64
|
+
{{/each}}
|
|
65
|
+
{{else}}
|
|
66
|
+
{{! Preview placeholder rows }}
|
|
67
|
+
{{#each (array 1 2 3)}}
|
|
68
|
+
<tr>
|
|
69
|
+
{{#each this.tableColumns}}
|
|
70
|
+
<td class="border px-2 py-1 text-gray-400" style={{this.tableCellStyle}}>—</td>
|
|
71
|
+
{{/each}}
|
|
72
|
+
</tr>
|
|
73
|
+
{{/each}}
|
|
74
|
+
{{/if}}
|
|
75
|
+
</tbody>
|
|
76
|
+
{{else}}
|
|
77
|
+
<tbody>
|
|
78
|
+
<tr>
|
|
79
|
+
<td class="border px-2 py-1 text-gray-400 text-center" colspan="1">No columns defined</td>
|
|
80
|
+
</tr>
|
|
81
|
+
</tbody>
|
|
82
|
+
{{/if}}
|
|
83
|
+
</table>
|
|
84
|
+
</div>
|
|
85
|
+
{{/if}}
|
|
86
|
+
|
|
87
|
+
{{! LINE }}
|
|
88
|
+
{{#if this.isLine}}
|
|
89
|
+
<div class="tb-element-line" style={{this.lineStyle}}>
|
|
90
|
+
<div style={{this.lineInnerStyle}}></div>
|
|
91
|
+
</div>
|
|
92
|
+
{{/if}}
|
|
93
|
+
|
|
94
|
+
{{! SHAPE }}
|
|
95
|
+
{{#if this.isShape}}
|
|
96
|
+
<div class="tb-element-shape" style={{this.shapeStyle}}></div>
|
|
97
|
+
{{/if}}
|
|
98
|
+
|
|
99
|
+
{{! QR CODE }}
|
|
100
|
+
{{#if this.isQrCode}}
|
|
101
|
+
<div class="w-full h-full flex items-center justify-center bg-gray-50 dark:bg-gray-800 border border-dashed border-gray-300 dark:border-gray-600 rounded">
|
|
102
|
+
<div class="text-center text-gray-400 dark:text-gray-500 text-xs">
|
|
103
|
+
<FaIcon @icon="qrcode" @size="2x" class="mb-1 block" />
|
|
104
|
+
<span class="block truncate max-w-full px-1">{{this.codeLabel}}</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
{{/if}}
|
|
108
|
+
|
|
109
|
+
{{! BARCODE }}
|
|
110
|
+
{{#if this.isBarcode}}
|
|
111
|
+
<div class="w-full h-full flex items-center justify-center bg-gray-50 dark:bg-gray-800 border border-dashed border-gray-300 dark:border-gray-600 rounded">
|
|
112
|
+
<div class="text-center text-gray-400 dark:text-gray-500 text-xs">
|
|
113
|
+
<FaIcon @icon="barcode" @size="2x" class="mb-1 block" />
|
|
114
|
+
<span class="block truncate max-w-full px-1">{{this.codeLabel}}</span>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
{{/if}}
|
|
118
|
+
|
|
119
|
+
{{! Selection handles (shown when selected) }}
|
|
120
|
+
{{#if @isSelected}}
|
|
121
|
+
<div class="tb-element-handle tb-handle-nw"></div>
|
|
122
|
+
<div class="tb-element-handle tb-handle-ne"></div>
|
|
123
|
+
<div class="tb-element-handle tb-handle-sw"></div>
|
|
124
|
+
<div class="tb-element-handle tb-handle-se"></div>
|
|
125
|
+
{{/if}}
|
|
126
|
+
</div>
|