@vibedev-si/dsh-media-viewer 0.1.2 → 0.1.3
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/README.md +3 -3
- package/client.js +9 -331
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
画廊在右侧栏的 **+ 菜单**里叫「媒体画廊」;每个播放器工具栏里的「画廊」按钮会直接打开它所在的文件夹。
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## 本版变更(0.1.3)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
撤回了 0.1.2 加进预览工具栏和画廊底部的「组件 / 插件中心」入口,它影响了侧栏观感,等重新设计后再考虑;预览、画廊与安全说明不受影响。
|
|
22
22
|
|
|
23
23
|
## 安装
|
|
24
24
|
|
|
@@ -76,7 +76,7 @@ client 是无需构建的纯 JS(`client.js`),host 是 `index.js`,没有
|
|
|
76
76
|
- **Audio** — mp3 / wav / m4a / flac…, live spectrum, playback speed, loop.
|
|
77
77
|
- **HTML** — scripts allowed by default; relative CSS / JS / images / video / `fetch()` all load; preview ⇄ source, phone / tablet / desktop widths, script toggle. Runs in an opaque-origin sandboxed iframe, so a previewed page cannot reach the GUI.
|
|
78
78
|
- **Media gallery** — thumbnail grid for a folder with type filters, search, sort, optional recursion, and a lightbox with ← / → navigation.
|
|
79
|
-
- **
|
|
79
|
+
- **Withdrawn in 0.1.3** — the 「组件」 / plugin-centre entry 0.1.2 added to the preview toolbar and the gallery footer is gone, because it changed how the sidebar looked; it will be reconsidered after a redesign, and previewing, the gallery and the security notes are unaffected.
|
|
80
80
|
|
|
81
81
|
Why it exists: the built-in `/sidebar/file` and `/sidebar/html` routes cap files at 20 MB, read them fully into memory, have no Range support, and their fence rejects the `cross-site` sub-resource requests a sandboxed iframe makes. This plugin ships its own `/mp/*` routes instead. Install it by package name from your DSH plugin manager, or `dsh plugin --profile <name> add @vibedev-si/dsh-media-viewer`, then reload the page.
|
|
82
82
|
|
package/client.js
CHANGED
|
@@ -158,12 +158,6 @@ window.__ModuleLoader__.load({
|
|
|
158
158
|
h("rect", { key: "d", x: 9, y: 9, width: 5, height: 5, rx: 1, stroke: "currentColor", strokeWidth: 1.5 }),
|
|
159
159
|
]);
|
|
160
160
|
}
|
|
161
|
-
function IconTools(size) {
|
|
162
|
-
return svg(size, [
|
|
163
|
-
h("rect", { key: "a", x: 2, y: 2, width: 12, height: 12, rx: 3, stroke: "currentColor", strokeWidth: 1.5 }),
|
|
164
|
-
pathEl("M8 5.4v5.2M5.4 8h5.2", { key: "b" }),
|
|
165
|
-
]);
|
|
166
|
-
}
|
|
167
161
|
function kindIcon(kind, size) {
|
|
168
162
|
return kind === "video" ? IconVideo(size) : kind === "audio" ? IconAudio(size) : kind === "html" ? IconHtml(size) : IconImage(size);
|
|
169
163
|
}
|
|
@@ -186,7 +180,7 @@ window.__ModuleLoader__.load({
|
|
|
186
180
|
} catch (e) { console.error("[dsh-media-viewer] open gallery failed", e); }
|
|
187
181
|
}
|
|
188
182
|
|
|
189
|
-
function CommonActions(props, abs, url
|
|
183
|
+
function CommonActions(props, abs, url) {
|
|
190
184
|
var out = [
|
|
191
185
|
h("a", { key: "new", href: url, target: "_blank", rel: "noopener", style: btn(), title: "在新窗口打开" }, "新窗口"),
|
|
192
186
|
h("a", { key: "dl", href: fileUrl(props.scope, abs, "download=1"), download: baseName(abs), style: btn(), title: "下载原文件" }, "下载"),
|
|
@@ -194,9 +188,6 @@ window.__ModuleLoader__.load({
|
|
|
194
188
|
if (!props.hideGallery) {
|
|
195
189
|
out.push(h(Btn, { key: "gal", title: "浏览所在文件夹的全部媒体", onClick: function () { openGallery(props, abs); } }, [IconGrid(12), "画廊"]));
|
|
196
190
|
}
|
|
197
|
-
if (typeof onTools === "function") {
|
|
198
|
-
out.push(h(Btn, { key: "tools", active: toolsOpen === true, title: "相关组件与插件中心", onClick: onTools }, [IconTools(12), "组件"]));
|
|
199
|
-
}
|
|
200
191
|
return out;
|
|
201
192
|
}
|
|
202
193
|
|
|
@@ -209,305 +200,9 @@ window.__ModuleLoader__.load({
|
|
|
209
200
|
}
|
|
210
201
|
|
|
211
202
|
function Shell(p) {
|
|
212
|
-
var kids = p.children ? (Array.isArray(p.children) ? p.children.slice() : [p.children]) : [];
|
|
213
|
-
if (p.tools) kids.push(p.tools);
|
|
214
203
|
return h("div", {
|
|
215
204
|
style: { display: "flex", flexDirection: "column", gap: "8px", padding: "10px", minWidth: 0, minHeight: 0, height: "100%", boxSizing: "border-box" },
|
|
216
|
-
},
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ── companion components (the plugin centre is the way to the rest) ─────
|
|
220
|
-
//
|
|
221
|
-
// What the Host actually has comes from its plugin inventory — a bundle the profile
|
|
222
|
-
// installed, or one the application ships itself. Every service below is read
|
|
223
|
-
// optionally and strictly, so a Host without a plugin manager, a panel layout, or the
|
|
224
|
-
// plugin navigation contract keeps this viewer working and simply shows the manual
|
|
225
|
-
// spec. Nothing here installs anything: the centre and the Plugins page own every
|
|
226
|
-
// install, approval and progress step, and nothing is read before a surface is opened.
|
|
227
|
-
|
|
228
|
-
/** The VibeDev components by package name; the centre is the way to all of them. */
|
|
229
|
-
var SUITE = { account: "@vibedev-si/dsh-vibedev", center: "@vibedev-si/dsh-ecosystem", film: "dsh-film" };
|
|
230
|
-
/** The main panel the VibeDev plugin centre registers its page under. */
|
|
231
|
-
var CENTER_PANEL = "vibedev-center";
|
|
232
|
-
/** The package a person can add on the Plugins page when nothing can open the centre. */
|
|
233
|
-
var CENTER_SPEC = SUITE.center;
|
|
234
|
-
/**
|
|
235
|
-
* The components this viewer's own work leans on, in the order the card lists them.
|
|
236
|
-
* A bundle already installed or shipped by the app is never named (see `bundlePresent`),
|
|
237
|
-
* and the viewer never recommends itself.
|
|
238
|
-
*/
|
|
239
|
-
var COMPANIONS = [
|
|
240
|
-
{ package: SUITE.account, line: "VibeDev 账号:用余额生成图片、视频和音频,模型请求走 VibeDev 网关。" },
|
|
241
|
-
{ package: SUITE.film, line: "影视工作台:剧本、分镜画布和导演台,把这个文件夹里的媒体排成片子。" },
|
|
242
|
-
];
|
|
243
|
-
/** The card's own copy, in the one language this bundle's UI uses. */
|
|
244
|
-
var SUITE_COPY = {
|
|
245
|
-
missingTitle: "相关组件",
|
|
246
|
-
missingIntro: "这些组件和右侧栏预览配合使用,装好后出现在它们各自的页面里。",
|
|
247
|
-
toolsTitle: "插件中心",
|
|
248
|
-
toolsIntro: "插件中心里可以浏览其余组件。",
|
|
249
|
-
keep: "这里只做提示:不会自动安装,也不会改动已装的东西;安装与授权都在插件中心里完成。",
|
|
250
|
-
open: "打开插件中心",
|
|
251
|
-
manual: "打不开插件中心时,可在「插件」页按包名安装:",
|
|
252
|
-
copy: "复制包名",
|
|
253
|
-
copied: "已复制",
|
|
254
|
-
tools: "组件",
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
/** A value read as a record, without trusting its declared type. */
|
|
258
|
-
function asRecord(v) { return typeof v === "object" && v !== null ? v : undefined; }
|
|
259
|
-
|
|
260
|
-
/** One property of a service, read without letting a hostile getter throw into the viewer. */
|
|
261
|
-
function safeGet(target, name) {
|
|
262
|
-
var record = asRecord(target);
|
|
263
|
-
if (record === undefined) return undefined;
|
|
264
|
-
try { return record[name]; } catch (e) { return undefined; }
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** One optional service, read strictly: absent (never throwing) on a Host that does not have it. */
|
|
268
|
-
function serviceOf(ctx, name) {
|
|
269
|
-
if (!ctx || typeof ctx.get !== "function") return undefined;
|
|
270
|
-
try { return ctx.get(name); } catch (e) { return undefined; }
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** The optional services the card uses, from the strict store first and the property proxy second. */
|
|
274
|
-
function suiteServices(ctx) {
|
|
275
|
-
var remote = asRecord(serviceOf(ctx, "remote") || safeGet(ctx, "remote"));
|
|
276
|
-
return {
|
|
277
|
-
manager: asRecord(serviceOf(ctx, "remote.pluginManager") || safeGet(remote, "pluginManager")),
|
|
278
|
-
slots: asRecord(serviceOf(ctx, "slots") || safeGet(ctx, "slots")),
|
|
279
|
-
layout: asRecord(serviceOf(ctx, "layout") || safeGet(ctx, "layout")),
|
|
280
|
-
navigation: asRecord(serviceOf(ctx, "pluginNavigation") || safeGet(ctx, "pluginNavigation")),
|
|
281
|
-
remote: remote,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** Whether the Host already has a bundle: the profile installed it, or the application provides it itself. */
|
|
286
|
-
function bundlePresent(bundle) {
|
|
287
|
-
return bundle.installed === true || (bundle.removable === false && !bundle.source);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* The present package names of one inventory answer, or undefined when the answer is not a
|
|
292
|
-
* readable list. An answer this cannot read is never read as "nothing is present": a refusal
|
|
293
|
-
* envelope, another shape, or a missing value means the caller knows nothing, not that every
|
|
294
|
-
* component is missing.
|
|
295
|
-
*/
|
|
296
|
-
function presentFrom(answer) {
|
|
297
|
-
var record = asRecord(answer);
|
|
298
|
-
if (record === undefined || record.ok === false) return undefined;
|
|
299
|
-
if (!Array.isArray(record.value)) return undefined;
|
|
300
|
-
var names = [];
|
|
301
|
-
for (var i = 0; i < record.value.length; i++) {
|
|
302
|
-
var bundle = asRecord(record.value[i]);
|
|
303
|
-
if (!bundle || !bundlePresent(bundle)) continue;
|
|
304
|
-
if (typeof bundle.name === "string" && bundle.name !== "" && names.indexOf(bundle.name) < 0) names.push(bundle.name);
|
|
305
|
-
}
|
|
306
|
-
return names;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Read the Host's plugin inventory once. A manager that cannot answer — absent, throwing,
|
|
311
|
-
* rejecting, or answering an envelope that is not a readable list — is not an inventory of
|
|
312
|
-
* nothing: `ok: false` makes the card offer the centre and claim no component is missing.
|
|
313
|
-
*/
|
|
314
|
-
function readPresence(ctx) {
|
|
315
|
-
var manager = suiteServices(ctx).manager;
|
|
316
|
-
var list = safeGet(manager, "listBundles");
|
|
317
|
-
if (typeof list !== "function") return Promise.resolve({ ok: false, present: [] });
|
|
318
|
-
try {
|
|
319
|
-
return Promise.resolve(list.call(manager)).then(
|
|
320
|
-
function (answer) {
|
|
321
|
-
var present = presentFrom(answer);
|
|
322
|
-
return present === undefined ? { ok: false, present: [] } : { ok: true, present: present };
|
|
323
|
-
},
|
|
324
|
-
function () { return { ok: false, present: [] }; });
|
|
325
|
-
} catch (e) {
|
|
326
|
-
return Promise.resolve({ ok: false, present: [] });
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** Hear about installs, removals and activations; a Host without the event bus returns a no-op. */
|
|
331
|
-
function observePresence(ctx, listener) {
|
|
332
|
-
var remote = suiteServices(ctx).remote;
|
|
333
|
-
var on = safeGet(remote, "$on");
|
|
334
|
-
if (typeof on !== "function") return function () {};
|
|
335
|
-
try {
|
|
336
|
-
var off = on.call(remote, "plugin-manager/changed", listener);
|
|
337
|
-
return typeof off === "function" ? function () { off(); } : function () {};
|
|
338
|
-
} catch (e) {
|
|
339
|
-
return function () {};
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* The centre's main panel, where this Host registered one. A slots entry keeps its
|
|
345
|
-
* identity in `options` (`options.key` is the panel id the layout selects); older
|
|
346
|
-
* shapes kept `key`/`id` at the top level, which is still read as a fallback.
|
|
347
|
-
*/
|
|
348
|
-
function centerPanel(ctx) {
|
|
349
|
-
var slots = suiteServices(ctx).slots;
|
|
350
|
-
var entries = safeGet(slots, "entries");
|
|
351
|
-
if (typeof entries !== "function") return undefined;
|
|
352
|
-
var listed;
|
|
353
|
-
try { listed = entries.call(slots, "main"); } catch (e) { return undefined; }
|
|
354
|
-
if (!Array.isArray(listed)) return undefined;
|
|
355
|
-
for (var i = 0; i < listed.length; i++) {
|
|
356
|
-
var record = asRecord(listed[i]);
|
|
357
|
-
var options = asRecord(safeGet(record, "options"));
|
|
358
|
-
var key = options ? (safeGet(options, "key") !== undefined ? safeGet(options, "key") : safeGet(options, "id"))
|
|
359
|
-
: (record ? (safeGet(record, "key") !== undefined ? safeGet(record, "key") : safeGet(record, "id")) : undefined);
|
|
360
|
-
if (key === CENTER_PANEL) return CENTER_PANEL;
|
|
361
|
-
}
|
|
362
|
-
return undefined;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Send the person to the centre, or to the Host's own way of installing it. A panel that
|
|
367
|
-
* is gone, a layout that refuses, and a missing navigation contract each fall through to
|
|
368
|
-
* the next, and the last answer is the spec a person can paste into the Plugins page.
|
|
369
|
-
*/
|
|
370
|
-
function openCenter(ctx) {
|
|
371
|
-
var services = suiteServices(ctx);
|
|
372
|
-
var panel = centerPanel(ctx);
|
|
373
|
-
var selectPanel = safeGet(services.layout, "selectPanel");
|
|
374
|
-
if (panel !== undefined && typeof selectPanel === "function") {
|
|
375
|
-
try { selectPanel.call(services.layout, panel); return { kind: "center" }; } catch (e) { /* the panel is gone */ }
|
|
376
|
-
}
|
|
377
|
-
var navigation = services.navigation;
|
|
378
|
-
var openBundle = safeGet(navigation, "openBundle");
|
|
379
|
-
if (typeof openBundle === "function") {
|
|
380
|
-
try { openBundle.call(navigation, SUITE.center); return { kind: "plugins" }; } catch (e) { /* no Plugins panel here */ }
|
|
381
|
-
}
|
|
382
|
-
var openInstall = safeGet(navigation, "openInstall");
|
|
383
|
-
if (typeof openInstall === "function") {
|
|
384
|
-
try { openInstall.call(navigation, { spec: CENTER_SPEC }); return { kind: "plugins" }; } catch (e) { /* nothing could open */ }
|
|
385
|
-
}
|
|
386
|
-
return { kind: "manual", spec: CENTER_SPEC };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/** Which of `wanted` this Host does not have yet, in the order the surface lists them. */
|
|
390
|
-
function missingFrom(present, wanted) {
|
|
391
|
-
return wanted.filter(function (want) { return present.indexOf(want.package) < 0; });
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/** What the card says: the absent components, or the one way to the rest. An unreadable inventory claims nothing. */
|
|
395
|
-
function suiteHint(read, wanted) {
|
|
396
|
-
if (!read.ok) return { kind: "tools", missing: [] };
|
|
397
|
-
var missing = missingFrom(read.present, wanted);
|
|
398
|
-
if (missing.length === 0) return { kind: "tools", missing: [] };
|
|
399
|
-
return {
|
|
400
|
-
kind: "missing",
|
|
401
|
-
missing: missing.map(function (want) { return { package: want.package, line: want.line }; }),
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/** Write text through the browser's clipboard, where the page has one. */
|
|
406
|
-
function writeClipboard(text) {
|
|
407
|
-
var clipboard = typeof navigator !== "undefined" && navigator ? safeGet(navigator, "clipboard") : undefined;
|
|
408
|
-
var write = safeGet(clipboard, "writeText");
|
|
409
|
-
if (typeof write !== "function") return Promise.resolve(false);
|
|
410
|
-
try {
|
|
411
|
-
return Promise.resolve(write.call(clipboard, text)).then(function () { return true; }, function () { return false; });
|
|
412
|
-
} catch (e) {
|
|
413
|
-
return Promise.resolve(false);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/** One read of what this Host has, shared by every card this bundle renders. */
|
|
418
|
-
function createSuiteStore(ctx, wanted) {
|
|
419
|
-
var snapshot = { read: false, found: { ok: false, present: [] }, hint: { kind: "tools", missing: [] } };
|
|
420
|
-
var listeners = [];
|
|
421
|
-
function publish(next) {
|
|
422
|
-
snapshot = next;
|
|
423
|
-
var copy = listeners.slice();
|
|
424
|
-
for (var i = 0; i < copy.length; i++) copy[i]();
|
|
425
|
-
}
|
|
426
|
-
function read() {
|
|
427
|
-
return readPresence(ctx).then(function (found) {
|
|
428
|
-
publish({ read: true, found: found, hint: suiteHint(found, wanted) });
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
return {
|
|
432
|
-
subscribe: function (listener) {
|
|
433
|
-
listeners.push(listener);
|
|
434
|
-
return function () {
|
|
435
|
-
var at = listeners.indexOf(listener);
|
|
436
|
-
if (at >= 0) listeners.splice(at, 1);
|
|
437
|
-
};
|
|
438
|
-
},
|
|
439
|
-
getSnapshot: function () { return snapshot; },
|
|
440
|
-
open: function () { return openCenter(ctx); },
|
|
441
|
-
copy: writeClipboard,
|
|
442
|
-
/** Start the first read and every later refresh; the returned disposer releases both. */
|
|
443
|
-
start: function () {
|
|
444
|
-
void read();
|
|
445
|
-
return observePresence(ctx, function () { void read(); });
|
|
446
|
-
},
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* The companion components this Host lacks, and the one quiet way into the plugin centre.
|
|
452
|
-
* It renders nothing until its own read settled, installs nothing, and opens nothing by
|
|
453
|
-
* itself: every line here is a statement about the Host, and the button is the only action.
|
|
454
|
-
*/
|
|
455
|
-
function SuiteCard(props) {
|
|
456
|
-
var store = useMemo(function () {
|
|
457
|
-
return props.store || createSuiteStore(props.ctx, COMPANIONS);
|
|
458
|
-
}, [props.ctx, props.store]);
|
|
459
|
-
var st = useState(store.getSnapshot()), snap = st[0], setSnap = st[1];
|
|
460
|
-
var mn = useState(null), manual = mn[0], setManual = mn[1];
|
|
461
|
-
var cp = useState(false), copied = cp[0], setCopied = cp[1];
|
|
462
|
-
|
|
463
|
-
useEffect(function () {
|
|
464
|
-
// Subscribe before the first read: a change published during it must still reach this card.
|
|
465
|
-
var off = store.subscribe(function () { setSnap(store.getSnapshot()); });
|
|
466
|
-
var stop = store.start();
|
|
467
|
-
return function () { off(); stop(); };
|
|
468
|
-
}, [store]);
|
|
469
|
-
|
|
470
|
-
if (!snap.read) return null;
|
|
471
|
-
var missing = snap.hint.kind === "missing";
|
|
472
|
-
var kids = [
|
|
473
|
-
h("div", { key: "t", style: { fontSize: "12px", fontWeight: 600 } }, missing ? SUITE_COPY.missingTitle : SUITE_COPY.toolsTitle),
|
|
474
|
-
h("div", { key: "i", style: { fontSize: "11px", color: C.muted, lineHeight: 1.6 } }, missing ? SUITE_COPY.missingIntro : SUITE_COPY.toolsIntro),
|
|
475
|
-
];
|
|
476
|
-
for (var i = 0; i < snap.hint.missing.length; i++) {
|
|
477
|
-
var line = snap.hint.missing[i];
|
|
478
|
-
kids.push(h("div", { key: "m:" + line.package, style: { fontSize: "12px", lineHeight: 1.6 } }, line.line));
|
|
479
|
-
}
|
|
480
|
-
kids.push(h("div", { key: "k", style: { fontSize: "11px", color: C.muted, lineHeight: 1.6 } }, SUITE_COPY.keep));
|
|
481
|
-
kids.push(h("div", { key: "a", style: { display: "flex", flexWrap: "wrap", gap: "6px", paddingTop: "2px" } }, [
|
|
482
|
-
h(Btn, {
|
|
483
|
-
key: "open", active: missing, title: "打开插件中心",
|
|
484
|
-
onClick: function () {
|
|
485
|
-
var target = store.open();
|
|
486
|
-
setCopied(false);
|
|
487
|
-
setManual(target.kind === "manual" ? { spec: target.spec } : null);
|
|
488
|
-
},
|
|
489
|
-
}, SUITE_COPY.open),
|
|
490
|
-
]));
|
|
491
|
-
if (manual) {
|
|
492
|
-
kids.push(h("div", { key: "mn", style: { fontSize: "11px", color: C.muted, lineHeight: 1.6 } }, SUITE_COPY.manual));
|
|
493
|
-
kids.push(h("code", {
|
|
494
|
-
key: "spec",
|
|
495
|
-
style: { fontSize: "12px", padding: "2px 6px", borderRadius: "6px", background: C.softer, border: "1px solid " + C.line, userSelect: "text", alignSelf: "flex-start" },
|
|
496
|
-
}, manual.spec));
|
|
497
|
-
kids.push(h("div", { key: "cp", style: { display: "flex", flexWrap: "wrap", gap: "6px" } }, [
|
|
498
|
-
h(Btn, {
|
|
499
|
-
key: "copy", title: "复制包名",
|
|
500
|
-
onClick: function () { void store.copy(manual.spec).then(function (written) { setCopied(written); }); },
|
|
501
|
-
}, copied ? SUITE_COPY.copied : SUITE_COPY.copy),
|
|
502
|
-
]));
|
|
503
|
-
}
|
|
504
|
-
return h("div", {
|
|
505
|
-
"data-mp-companions": "1",
|
|
506
|
-
style: {
|
|
507
|
-
display: "flex", flexDirection: "column", gap: "5px", flex: "none", boxSizing: "border-box",
|
|
508
|
-
padding: "9px 10px", borderRadius: "8px", border: "1px dashed " + C.line, background: C.softer, color: C.fg,
|
|
509
|
-
},
|
|
510
|
-
}, kids);
|
|
205
|
+
}, p.children);
|
|
511
206
|
}
|
|
512
207
|
|
|
513
208
|
// ── video ───────────────────────────────────────────────────────────────
|
|
@@ -521,14 +216,13 @@ window.__ModuleLoader__.load({
|
|
|
521
216
|
var er = useState(null), err = er[0], setErr = er[1];
|
|
522
217
|
var du = useState(null), dur = du[0], setDur = du[1];
|
|
523
218
|
var dims = useState(null), dim = dims[0], setDim = dims[1];
|
|
524
|
-
var tl = useState(false), tools = tl[0], setTools = tl[1];
|
|
525
219
|
|
|
526
220
|
useEffect(function () { setErr(null); setDur(null); setDim(null); }, [url]);
|
|
527
221
|
useEffect(function () { if (vref.current) vref.current.playbackRate = rate; }, [rate, url]);
|
|
528
222
|
|
|
529
223
|
var info = [dur != null ? fmtDur(dur) : null, dim ? dim : null, extOf(abs).toUpperCase()].filter(Boolean).join(" · ");
|
|
530
224
|
|
|
531
|
-
return h(Shell,
|
|
225
|
+
return h(Shell, null, [
|
|
532
226
|
h("div", { key: "stage", style: { flex: "1 1 auto", minHeight: 0, display: "flex", background: "#000", borderRadius: "8px", overflow: "hidden" } },
|
|
533
227
|
err
|
|
534
228
|
? h("div", { style: { margin: "auto", padding: "20px", textAlign: "center", color: "#ddd", fontSize: "13px", lineHeight: 1.7 } }, [
|
|
@@ -551,7 +245,7 @@ window.__ModuleLoader__.load({
|
|
|
551
245
|
h(Bar, { key: "bar", title: baseName(abs) + (info ? " · " + info : ""), path: abs }, [
|
|
552
246
|
h(RatePicker, { key: "rate", rate: rate, set: setRate }),
|
|
553
247
|
h(Btn, { key: "loop", active: loop, title: "循环播放", onClick: function () { setLoop(!loop); } }, "循环"),
|
|
554
|
-
CommonActions(props, abs, url
|
|
248
|
+
CommonActions(props, abs, url),
|
|
555
249
|
]),
|
|
556
250
|
]);
|
|
557
251
|
}
|
|
@@ -567,7 +261,6 @@ window.__ModuleLoader__.load({
|
|
|
567
261
|
var lp = useState(false), loop = lp[0], setLoop = lp[1];
|
|
568
262
|
var er = useState(null), err = er[0], setErr = er[1];
|
|
569
263
|
var du = useState(null), dur = du[0], setDur = du[1];
|
|
570
|
-
var tl = useState(false), tools = tl[0], setTools = tl[1];
|
|
571
264
|
|
|
572
265
|
useEffect(function () { setErr(null); setDur(null); }, [url]);
|
|
573
266
|
useEffect(function () { if (aref.current) aref.current.playbackRate = rate; }, [rate, url]);
|
|
@@ -618,7 +311,7 @@ window.__ModuleLoader__.load({
|
|
|
618
311
|
};
|
|
619
312
|
}, [url]);
|
|
620
313
|
|
|
621
|
-
return h(Shell,
|
|
314
|
+
return h(Shell, null, [
|
|
622
315
|
h("div", {
|
|
623
316
|
key: "stage",
|
|
624
317
|
style: {
|
|
@@ -640,7 +333,7 @@ window.__ModuleLoader__.load({
|
|
|
640
333
|
h(Bar, { key: "bar", title: baseName(abs) + " · " + [dur != null ? fmtDur(dur) : null, extOf(abs).toUpperCase()].filter(Boolean).join(" · "), path: abs }, [
|
|
641
334
|
h(RatePicker, { key: "rate", rate: rate, set: setRate }),
|
|
642
335
|
h(Btn, { key: "loop", active: loop, title: "循环播放", onClick: function () { setLoop(!loop); } }, "循环"),
|
|
643
|
-
CommonActions(props, abs, url
|
|
336
|
+
CommonActions(props, abs, url),
|
|
644
337
|
]),
|
|
645
338
|
]);
|
|
646
339
|
}
|
|
@@ -662,7 +355,6 @@ window.__ModuleLoader__.load({
|
|
|
662
355
|
var vp = useState("fit"), view = vp[0], setView = vp[1];
|
|
663
356
|
var rk = useState(0), reloadKey = rk[0], setReloadKey = rk[1];
|
|
664
357
|
var sr = useState({ state: "idle", text: "" }), source = sr[0], setSource = sr[1];
|
|
665
|
-
var tl = useState(false), tools = tl[0], setTools = tl[1];
|
|
666
358
|
|
|
667
359
|
var frameRef = useRef(null);
|
|
668
360
|
|
|
@@ -712,7 +404,7 @@ window.__ModuleLoader__.load({
|
|
|
712
404
|
}));
|
|
713
405
|
}
|
|
714
406
|
|
|
715
|
-
return h(Shell,
|
|
407
|
+
return h(Shell, null, [
|
|
716
408
|
stage,
|
|
717
409
|
h(Bar, { key: "bar", title: baseName(abs), path: abs }, [
|
|
718
410
|
h(Btn, { key: "m1", active: mode === "render", title: "渲染预览", onClick: function () { setMode("render"); } }, "预览"),
|
|
@@ -722,7 +414,7 @@ window.__ModuleLoader__.load({
|
|
|
722
414
|
}) : null,
|
|
723
415
|
h(Btn, { key: "js", active: scripts, title: scripts ? "脚本已允许,点击关闭(页面将重新加载)" : "脚本已禁用,点击允许", onClick: function () { setScripts(!scripts); } }, scripts ? "脚本 开" : "脚本 关"),
|
|
724
416
|
h(Btn, { key: "rl", title: "重新加载", onClick: function () { setReloadKey(reloadKey + 1); } }, "刷新"),
|
|
725
|
-
CommonActions(props, abs, url
|
|
417
|
+
CommonActions(props, abs, url),
|
|
726
418
|
]),
|
|
727
419
|
]);
|
|
728
420
|
}
|
|
@@ -942,8 +634,6 @@ window.__ModuleLoader__.load({
|
|
|
942
634
|
return h("div", { style: { position: "relative", height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", minWidth: 0 } }, [
|
|
943
635
|
header,
|
|
944
636
|
body,
|
|
945
|
-
// The card sits at the foot of the gallery: a statement about the Host, never a launch ad.
|
|
946
|
-
h("div", { key: "tools", style: { flex: "none", padding: "0 10px 10px" } }, h(SuiteCard, { ctx: props.ctx })),
|
|
947
637
|
sel >= 0 && items[sel]
|
|
948
638
|
? h(Lightbox, {
|
|
949
639
|
key: "lb", ctx: props.ctx, store: props.store, scope: scope, item: items[sel], index: sel, total: items.length,
|
|
@@ -991,19 +681,7 @@ window.__ModuleLoader__.load({
|
|
|
991
681
|
});
|
|
992
682
|
}
|
|
993
683
|
|
|
994
|
-
|
|
995
|
-
// exported so test/suite.test.mjs can drive it and the card through a fake loader and React.
|
|
996
|
-
module.exports = {
|
|
997
|
-
inject: inject,
|
|
998
|
-
apply: apply,
|
|
999
|
-
__suite: {
|
|
1000
|
-
SUITE: SUITE, CENTER_PANEL: CENTER_PANEL, CENTER_SPEC: CENTER_SPEC, COMPANIONS: COMPANIONS,
|
|
1001
|
-
suiteServices: suiteServices, bundlePresent: bundlePresent, presentFrom: presentFrom,
|
|
1002
|
-
readPresence: readPresence, observePresence: observePresence, centerPanel: centerPanel,
|
|
1003
|
-
openCenter: openCenter, missingFrom: missingFrom, suiteHint: suiteHint,
|
|
1004
|
-
createSuiteStore: createSuiteStore, SuiteCard: SuiteCard,
|
|
1005
|
-
},
|
|
1006
|
-
};
|
|
684
|
+
module.exports = { inject: inject, apply: apply };
|
|
1007
685
|
return module.exports;
|
|
1008
686
|
},
|
|
1009
687
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibedev-si/dsh-media-viewer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "右侧栏媒体预览:直接预览视频、音频和 HTML(大文件可拖进度条,HTML 默认允许脚本),并提供文件夹媒体画廊,需配合 dsh-better-sidebar · Inline video, audio and HTML preview plus a folder media gallery in the dsh-better-sidebar right column — range-streamed large files, sandboxed HTML with working relative assets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"registry": "https://registry.npmjs.org/"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
|
-
"test": "node test/route.test.mjs && node test/tokens.test.mjs
|
|
56
|
+
"test": "node test/route.test.mjs && node test/tokens.test.mjs"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"dsh-better-sidebar": ">=0.24.0"
|