@jxsuite/studio 0.26.1 → 0.27.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/dist/studio.js +192 -53
- package/dist/studio.js.map +6 -6
- package/package.json +5 -5
- package/src/panels/canvas-dnd.ts +42 -21
- package/src/panels/dnd.ts +14 -3
- package/src/tabs/transact.ts +5 -1
- package/src/ui/media-picker.ts +192 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
|
|
34
34
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.2.0",
|
|
35
|
-
"@jxsuite/parser": "^0.
|
|
36
|
-
"@jxsuite/runtime": "^0.
|
|
37
|
-
"@jxsuite/schema": "^0.
|
|
35
|
+
"@jxsuite/parser": "^0.27.0",
|
|
36
|
+
"@jxsuite/runtime": "^0.27.0",
|
|
37
|
+
"@jxsuite/schema": "^0.27.0",
|
|
38
38
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
39
39
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
40
40
|
"@spectrum-web-components/action-button": "^1.12.1",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"@types/mdast": "^4.0.4",
|
|
89
89
|
"@webref/css": "^8.5.8",
|
|
90
90
|
"@webref/elements": "^2.7.1",
|
|
91
|
-
"@webref/idl": "^3.
|
|
91
|
+
"@webref/idl": "^3.81.0"
|
|
92
92
|
},
|
|
93
93
|
"//vue-reactivity": "Exact pin required — must match @jxsuite/runtime to avoid cross-boundary proxy bugs"
|
|
94
94
|
}
|
package/src/panels/canvas-dnd.ts
CHANGED
|
@@ -42,6 +42,21 @@ export function registerPanelDnD(panel: CanvasPanel) {
|
|
|
42
42
|
const { canvas, dropLine } = panel;
|
|
43
43
|
const allEls = canvas.querySelectorAll("*");
|
|
44
44
|
|
|
45
|
+
// Drop-target callbacks fire on EVERY target in the stack (innermost → outermost),
|
|
46
|
+
// and every canvas element is a drop target — so the indicator and the drop are
|
|
47
|
+
// driven from the monitor using only the innermost target.
|
|
48
|
+
/** Innermost drop target if it belongs to this panel's canvas, else null */
|
|
49
|
+
const innermostCanvasTarget = (location: {
|
|
50
|
+
current: { dropTargets: { data: Record<string | symbol, unknown>; element: Element }[] };
|
|
51
|
+
}) => {
|
|
52
|
+
const target = location.current.dropTargets[0];
|
|
53
|
+
if (!target) return null;
|
|
54
|
+
const tEl = target.element as HTMLElement;
|
|
55
|
+
const tPath = target.data.path;
|
|
56
|
+
if (!canvas.contains(tEl) || !Array.isArray(tPath)) return null;
|
|
57
|
+
return { el: tEl, path: tPath as JxPath, isLeaf: !!target.data._isVoid };
|
|
58
|
+
};
|
|
59
|
+
|
|
45
60
|
const monitorCleanup = monitorForElements({
|
|
46
61
|
onDragStart({ location }) {
|
|
47
62
|
view.lastDragInput = location.current.input;
|
|
@@ -52,8 +67,34 @@ export function registerPanelDnD(panel: CanvasPanel) {
|
|
|
52
67
|
},
|
|
53
68
|
onDrag({ location }) {
|
|
54
69
|
view.lastDragInput = location.current.input;
|
|
70
|
+
const target = innermostCanvasTarget(location);
|
|
71
|
+
if (target) {
|
|
72
|
+
if (_activeDropEl && _activeDropEl !== target.el) {
|
|
73
|
+
_activeDropEl.classList.remove("canvas-drop-target");
|
|
74
|
+
}
|
|
75
|
+
_activeDropEl = target.el;
|
|
76
|
+
showCanvasDropIndicator(target.el, target.path, target.isLeaf, panel);
|
|
77
|
+
} else if (location.current.dropTargets.length > 0) {
|
|
78
|
+
// Pointer is over a non-canvas target (e.g. a layer row) — hide this panel's
|
|
79
|
+
// indicator. When over dead space (no targets at all) keep the last indicator
|
|
80
|
+
// visible so it persists for the whole drag.
|
|
81
|
+
if (_activeDropEl && canvas.contains(_activeDropEl)) {
|
|
82
|
+
_activeDropEl.classList.remove("canvas-drop-target");
|
|
83
|
+
_activeDropEl = null;
|
|
84
|
+
}
|
|
85
|
+
dropLine.style.display = "none";
|
|
86
|
+
}
|
|
55
87
|
},
|
|
56
|
-
onDrop() {
|
|
88
|
+
onDrop({ source, location }) {
|
|
89
|
+
const target = innermostCanvasTarget(location);
|
|
90
|
+
if (target) {
|
|
91
|
+
const { instruction, targetPath } = getCanvasDropResult(
|
|
92
|
+
target.el,
|
|
93
|
+
target.path,
|
|
94
|
+
target.isLeaf,
|
|
95
|
+
);
|
|
96
|
+
applyDropInstruction(instruction, source.data, targetPath);
|
|
97
|
+
}
|
|
57
98
|
_activeDropEl?.classList.remove("canvas-drop-target");
|
|
58
99
|
_activeDropEl = null;
|
|
59
100
|
for (const p of canvasPanels) {
|
|
@@ -90,26 +131,6 @@ export function registerPanelDnD(panel: CanvasPanel) {
|
|
|
90
131
|
getData() {
|
|
91
132
|
return { path: elPath, _isVoid: isLeaf };
|
|
92
133
|
},
|
|
93
|
-
onDragEnter({ location }) {
|
|
94
|
-
view.lastDragInput = location.current.input;
|
|
95
|
-
if (_activeDropEl && _activeDropEl !== el) {
|
|
96
|
-
_activeDropEl.classList.remove("canvas-drop-target");
|
|
97
|
-
}
|
|
98
|
-
_activeDropEl = el as HTMLElement;
|
|
99
|
-
showCanvasDropIndicator(el as HTMLElement, elPath, isLeaf, panel);
|
|
100
|
-
},
|
|
101
|
-
onDrag({ location }) {
|
|
102
|
-
view.lastDragInput = location.current.input;
|
|
103
|
-
showCanvasDropIndicator(el as HTMLElement, elPath, isLeaf, panel);
|
|
104
|
-
},
|
|
105
|
-
onDragLeave() {},
|
|
106
|
-
onDrop({ source }) {
|
|
107
|
-
dropLine.style.display = "none";
|
|
108
|
-
(el as HTMLElement).classList.remove("canvas-drop-target");
|
|
109
|
-
_activeDropEl = null;
|
|
110
|
-
const { instruction, targetPath } = getCanvasDropResult(el as HTMLElement, elPath, isLeaf);
|
|
111
|
-
applyDropInstruction(instruction, source.data, targetPath);
|
|
112
|
-
},
|
|
113
134
|
});
|
|
114
135
|
view.canvasDndCleanups.push(cleanup);
|
|
115
136
|
}
|
package/src/panels/dnd.ts
CHANGED
|
@@ -115,9 +115,8 @@ export function registerLayersDnD() {
|
|
|
115
115
|
onDrag({ self }: DragSelfArgs) {
|
|
116
116
|
showLayerDropGap(row, self.data, container);
|
|
117
117
|
},
|
|
118
|
-
onDragLeave
|
|
119
|
-
|
|
120
|
-
},
|
|
118
|
+
// No onDragLeave clear — the gap persists while the pointer crosses dead space
|
|
119
|
+
// between rows; the monitor clears it when the drag moves to a non-tree target.
|
|
121
120
|
onDrop() {
|
|
122
121
|
clearLayerDropGap(container);
|
|
123
122
|
},
|
|
@@ -128,6 +127,12 @@ export function registerLayersDnD() {
|
|
|
128
127
|
|
|
129
128
|
// Global monitor
|
|
130
129
|
const monitorCleanup = monitorForElements({
|
|
130
|
+
onDropTargetChange({ location }: DragMonitorDropArgs) {
|
|
131
|
+
// Clear the layer gap when the drag moves onto a non-tree target (e.g. a canvas
|
|
132
|
+
// element). When there is no target at all, keep the gap so it persists.
|
|
133
|
+
const inner = location.current.dropTargets[0];
|
|
134
|
+
if (inner && !extractInstruction(inner.data)) clearLayerDropGap(container);
|
|
135
|
+
},
|
|
131
136
|
onDrop({ source, location }: DragMonitorDropArgs) {
|
|
132
137
|
clearLayerDropGap(container);
|
|
133
138
|
const target = location.current.dropTargets[0];
|
|
@@ -321,6 +326,10 @@ export function applyDropInstruction(
|
|
|
321
326
|
const fromPath = srcData.path as JxPath;
|
|
322
327
|
const targetParent = parentElementPath(targetPath) as JxPath;
|
|
323
328
|
const targetIdx = childIndex(targetPath) as number;
|
|
329
|
+
// Reordering requires a parent and a numeric index; root-level paths can't be
|
|
330
|
+
// reordered around.
|
|
331
|
+
if (instruction.type !== "make-child" && (!targetParent || typeof targetIdx !== "number"))
|
|
332
|
+
return;
|
|
324
333
|
|
|
325
334
|
switch (instruction.type) {
|
|
326
335
|
case "reorder-above":
|
|
@@ -341,6 +350,8 @@ export function applyDropInstruction(
|
|
|
341
350
|
} else if (srcData.type === "block") {
|
|
342
351
|
const targetParent = parentElementPath(targetPath) as JxPath;
|
|
343
352
|
const targetIdx = childIndex(targetPath) as number;
|
|
353
|
+
if (instruction.type !== "make-child" && (!targetParent || typeof targetIdx !== "number"))
|
|
354
|
+
return;
|
|
344
355
|
|
|
345
356
|
switch (instruction.type) {
|
|
346
357
|
case "reorder-above":
|
package/src/tabs/transact.ts
CHANGED
|
@@ -100,6 +100,7 @@ export function mutateInsertNode(
|
|
|
100
100
|
nodeDef: JxMutableNode,
|
|
101
101
|
) {
|
|
102
102
|
const parent = getNodeAtPath(tab.doc.document, parentPath);
|
|
103
|
+
if (!parent) return;
|
|
103
104
|
if (!parent.children) parent.children = [];
|
|
104
105
|
parent.children.splice(index, 0, structuredClone(nodeDef));
|
|
105
106
|
}
|
|
@@ -159,9 +160,12 @@ export function mutateMoveNode(tab: Tab, fromPath: JxPath, toParentPath: JxPath,
|
|
|
159
160
|
const doc = tab.doc.document;
|
|
160
161
|
const fromParentPath = parentElementPath(fromPath) as JxPath;
|
|
161
162
|
const fromIdx = childIndex(fromPath) as number;
|
|
163
|
+
if (!fromParentPath || typeof fromIdx !== "number") return;
|
|
162
164
|
const fromParent = getNodeAtPath(doc, fromParentPath);
|
|
163
|
-
const [node] = (fromParent.children as JxMutableNode[]).splice(fromIdx, 1);
|
|
164
165
|
const toParent = getNodeAtPath(doc, toParentPath);
|
|
166
|
+
if (!fromParent || !Array.isArray(fromParent.children) || !toParent) return;
|
|
167
|
+
const [node] = (fromParent.children as JxMutableNode[]).splice(fromIdx, 1);
|
|
168
|
+
if (node === undefined) return;
|
|
165
169
|
if (!toParent.children) toParent.children = [];
|
|
166
170
|
let adjustedIndex = toIndex;
|
|
167
171
|
if (fromParent === toParent && fromIdx < toIndex) adjustedIndex--;
|
package/src/ui/media-picker.ts
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* files from the project's public/ directory, with thumbnail previews for images.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { html, nothing } from "lit-html";
|
|
9
|
+
import { html, render as litRender, nothing } from "lit-html";
|
|
10
10
|
import { live } from "lit-html/directives/live.js";
|
|
11
|
+
import { ref } from "lit-html/directives/ref.js";
|
|
11
12
|
import { getPlatform } from "../platform";
|
|
12
13
|
import { debouncedStyleCommit } from "../store";
|
|
14
|
+
import { getLayerSlot } from "./layers";
|
|
13
15
|
|
|
14
16
|
// ─── Media file cache ────────────────────────────────────────────────────────
|
|
15
17
|
|
|
@@ -92,6 +94,184 @@ export function invalidateMediaCache() {
|
|
|
92
94
|
mediaCacheLoaded = false;
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
// ─── Popover state ───────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/** @type {((val: string) => void) | null} */
|
|
100
|
+
let _popoverOnCommit: ((val: string) => void) | null = null;
|
|
101
|
+
|
|
102
|
+
/** @type {HTMLElement | null} */
|
|
103
|
+
let _popoverAnchorEl: HTMLElement | null = null;
|
|
104
|
+
|
|
105
|
+
/** @type {HTMLInputElement | null} */
|
|
106
|
+
let _popoverFilterEl: HTMLInputElement | null = null;
|
|
107
|
+
|
|
108
|
+
let _popoverFilter = "";
|
|
109
|
+
|
|
110
|
+
function dismissMediaPickerPopover() {
|
|
111
|
+
_popoverFilter = "";
|
|
112
|
+
_popoverOnCommit = null;
|
|
113
|
+
_popoverAnchorEl = null;
|
|
114
|
+
_popoverFilterEl = null;
|
|
115
|
+
document.removeEventListener("keydown", onPopoverKeydown, true);
|
|
116
|
+
document.removeEventListener("mousedown", onPopoverOutsideClick, true);
|
|
117
|
+
litRender(nothing, getLayerSlot("popover", "media-picker"));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {KeyboardEvent} e */
|
|
121
|
+
function onPopoverKeydown(e: KeyboardEvent) {
|
|
122
|
+
if (e.key === "Escape") {
|
|
123
|
+
dismissMediaPickerPopover();
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
e.stopPropagation();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** @param {MouseEvent} e */
|
|
130
|
+
function onPopoverOutsideClick(e: MouseEvent) {
|
|
131
|
+
const host = getLayerSlot("popover", "media-picker");
|
|
132
|
+
if (!host.contains(e.target as Node)) {
|
|
133
|
+
dismissMediaPickerPopover();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function renderMediaPickerPopover() {
|
|
138
|
+
const host = getLayerSlot("popover", "media-picker");
|
|
139
|
+
const rect = _popoverAnchorEl?.getBoundingClientRect();
|
|
140
|
+
if (!rect) return;
|
|
141
|
+
|
|
142
|
+
const query = _popoverFilter.toLowerCase();
|
|
143
|
+
const filtered = query
|
|
144
|
+
? mediaCache.filter(
|
|
145
|
+
(m) => m.path.toLowerCase().includes(query) || m.name.toLowerCase().includes(query),
|
|
146
|
+
)
|
|
147
|
+
: mediaCache;
|
|
148
|
+
const options = filtered.slice(0, 50);
|
|
149
|
+
|
|
150
|
+
// Compute initial position below the anchor
|
|
151
|
+
let left = rect.left;
|
|
152
|
+
let top = rect.bottom + 4;
|
|
153
|
+
|
|
154
|
+
// Estimate popover dimensions for viewport clamping before first paint
|
|
155
|
+
const estimatedWidth = 280;
|
|
156
|
+
const estimatedHeight = Math.min(options.length * 36 + 48, 360);
|
|
157
|
+
|
|
158
|
+
if (left + estimatedWidth > window.innerWidth - 8) {
|
|
159
|
+
left = Math.max(8, window.innerWidth - estimatedWidth - 8);
|
|
160
|
+
}
|
|
161
|
+
if (top + estimatedHeight > window.innerHeight - 8) {
|
|
162
|
+
top = Math.max(8, rect.top - estimatedHeight - 4);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let _popoverEl: HTMLElement | null = null;
|
|
166
|
+
|
|
167
|
+
litRender(
|
|
168
|
+
html`
|
|
169
|
+
<sp-popover
|
|
170
|
+
open
|
|
171
|
+
${ref((el) => {
|
|
172
|
+
_popoverEl = (el as HTMLElement | undefined) || null;
|
|
173
|
+
})}
|
|
174
|
+
style="position:fixed;left:${left}px;top:${top}px;z-index:30;max-height:360px;overflow-y:auto;min-width:240px"
|
|
175
|
+
>
|
|
176
|
+
<input
|
|
177
|
+
class="media-picker-filter"
|
|
178
|
+
type="text"
|
|
179
|
+
placeholder="Search images…"
|
|
180
|
+
autocomplete="off"
|
|
181
|
+
style="display:block;width:100%;box-sizing:border-box;padding:6px 10px;border:none;border-bottom:1px solid var(--border, #444);outline:none;font-size:13px;background:transparent;color:inherit"
|
|
182
|
+
${ref((el) => {
|
|
183
|
+
_popoverFilterEl = (el as HTMLInputElement | null) || null;
|
|
184
|
+
})}
|
|
185
|
+
@input=${(e: Event) => {
|
|
186
|
+
_popoverFilter = (e.target as HTMLInputElement).value;
|
|
187
|
+
renderMediaPickerPopover();
|
|
188
|
+
}}
|
|
189
|
+
@click=${(e: MouseEvent) => e.stopPropagation()}
|
|
190
|
+
/>
|
|
191
|
+
<sp-menu
|
|
192
|
+
style="min-width:220px"
|
|
193
|
+
@change=${(e: Event) => {
|
|
194
|
+
_popoverOnCommit?.((e.target as HTMLInputElement).value);
|
|
195
|
+
dismissMediaPickerPopover();
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
${options.length
|
|
199
|
+
? options.map(
|
|
200
|
+
(m) => html`
|
|
201
|
+
<sp-menu-item value=${m.path}>
|
|
202
|
+
${m.isImage
|
|
203
|
+
? html`<img
|
|
204
|
+
slot="icon"
|
|
205
|
+
src=${m.path}
|
|
206
|
+
alt=""
|
|
207
|
+
style="width:24px;height:24px;object-fit:cover;border-radius:2px"
|
|
208
|
+
/>`
|
|
209
|
+
: nothing}
|
|
210
|
+
${m.name}
|
|
211
|
+
</sp-menu-item>
|
|
212
|
+
`,
|
|
213
|
+
)
|
|
214
|
+
: html`<sp-menu-item disabled>No matches</sp-menu-item>`}
|
|
215
|
+
${filtered.length > 50
|
|
216
|
+
? html`<sp-menu-item disabled>…${filtered.length - 50} more</sp-menu-item>`
|
|
217
|
+
: nothing}
|
|
218
|
+
</sp-menu>
|
|
219
|
+
</sp-popover>
|
|
220
|
+
`,
|
|
221
|
+
host,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Fine-tune position after render using actual measured dimensions
|
|
225
|
+
requestAnimationFrame(() => {
|
|
226
|
+
if (_popoverEl) {
|
|
227
|
+
const popoverRect = _popoverEl.getBoundingClientRect();
|
|
228
|
+
let adjLeft = popoverRect.left;
|
|
229
|
+
let adjTop = popoverRect.top;
|
|
230
|
+
let needsAdjust = false;
|
|
231
|
+
|
|
232
|
+
if (popoverRect.right > window.innerWidth - 4) {
|
|
233
|
+
adjLeft = Math.max(4, window.innerWidth - popoverRect.width - 4);
|
|
234
|
+
needsAdjust = true;
|
|
235
|
+
}
|
|
236
|
+
if (popoverRect.bottom > window.innerHeight - 4) {
|
|
237
|
+
adjTop = Math.max(4, window.innerHeight - popoverRect.height - 4);
|
|
238
|
+
needsAdjust = true;
|
|
239
|
+
}
|
|
240
|
+
if (popoverRect.left < 4) {
|
|
241
|
+
adjLeft = 4;
|
|
242
|
+
needsAdjust = true;
|
|
243
|
+
}
|
|
244
|
+
if (popoverRect.top < 4) {
|
|
245
|
+
adjTop = 4;
|
|
246
|
+
needsAdjust = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (needsAdjust) {
|
|
250
|
+
_popoverEl.style.left = `${adjLeft}px`;
|
|
251
|
+
_popoverEl.style.top = `${adjTop}px`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (_popoverFilterEl) _popoverFilterEl.focus();
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @param {HTMLElement} anchorEl
|
|
261
|
+
* @param {(val: string) => void} onCommit
|
|
262
|
+
*/
|
|
263
|
+
function showMediaPickerPopover(anchorEl: HTMLElement, onCommit: (val: string) => void) {
|
|
264
|
+
dismissMediaPickerPopover();
|
|
265
|
+
_popoverOnCommit = onCommit;
|
|
266
|
+
_popoverAnchorEl = anchorEl;
|
|
267
|
+
_popoverFilter = "";
|
|
268
|
+
renderMediaPickerPopover();
|
|
269
|
+
document.addEventListener("keydown", onPopoverKeydown, true);
|
|
270
|
+
requestAnimationFrame(() => {
|
|
271
|
+
document.addEventListener("mousedown", onPopoverOutsideClick, true);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
95
275
|
// ─── Render ──────────────────────────────────────────────────────────────────
|
|
96
276
|
|
|
97
277
|
/**
|
|
@@ -111,17 +291,6 @@ export function renderMediaPicker(prop: string, value: string, onCommit: (val: s
|
|
|
111
291
|
currentValue.slice(currentValue.lastIndexOf(".")).toLowerCase(),
|
|
112
292
|
);
|
|
113
293
|
|
|
114
|
-
// Filter media options based on current input
|
|
115
|
-
const query = currentValue.toLowerCase();
|
|
116
|
-
const filtered = query
|
|
117
|
-
? mediaCache.filter(
|
|
118
|
-
(m) => m.path.toLowerCase().includes(query) || m.name.toLowerCase().includes(query),
|
|
119
|
-
)
|
|
120
|
-
: mediaCache;
|
|
121
|
-
|
|
122
|
-
// Limit displayed options
|
|
123
|
-
const options = filtered.slice(0, 20);
|
|
124
|
-
|
|
125
294
|
return html`
|
|
126
295
|
<div class="media-picker">
|
|
127
296
|
${isImage && currentValue
|
|
@@ -138,37 +307,17 @@ export function renderMediaPicker(prop: string, value: string, onCommit: (val: s
|
|
|
138
307
|
></sp-textfield>
|
|
139
308
|
${mediaCache.length > 0
|
|
140
309
|
? html`
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
(m) => html`
|
|
153
|
-
<sp-menu-item value=${m.path}>
|
|
154
|
-
${m.isImage
|
|
155
|
-
? html`<img
|
|
156
|
-
slot="icon"
|
|
157
|
-
src=${m.path}
|
|
158
|
-
alt=""
|
|
159
|
-
style="width:24px;height:24px;object-fit:cover;border-radius:2px"
|
|
160
|
-
/>`
|
|
161
|
-
: nothing}
|
|
162
|
-
${m.name}
|
|
163
|
-
</sp-menu-item>
|
|
164
|
-
`,
|
|
165
|
-
)}
|
|
166
|
-
${filtered.length > 20
|
|
167
|
-
? html`<sp-menu-item disabled>...${filtered.length - 20} more</sp-menu-item>`
|
|
168
|
-
: nothing}
|
|
169
|
-
</sp-menu>
|
|
170
|
-
</sp-popover>
|
|
171
|
-
</overlay-trigger>
|
|
310
|
+
<sp-action-button
|
|
311
|
+
size="xs"
|
|
312
|
+
quiet
|
|
313
|
+
title="Browse media"
|
|
314
|
+
@click=${(e: MouseEvent) => {
|
|
315
|
+
loadMediaCache();
|
|
316
|
+
showMediaPickerPopover(e.currentTarget as HTMLElement, onCommit);
|
|
317
|
+
}}
|
|
318
|
+
>
|
|
319
|
+
<sp-icon-image slot="icon"></sp-icon-image>
|
|
320
|
+
</sp-action-button>
|
|
172
321
|
`
|
|
173
322
|
: nothing}
|
|
174
323
|
</div>
|