@jxsuite/studio 9.0.0 → 10.0.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/chunks/{2020-89fz3pfy.js → 2020-j56903f1.js} +3 -3
- package/dist/chunks/adapter-wc5zvg9a.js +4 -0
- package/dist/chunks/{add-new-flow-nmkqpjym.js → add-new-flow-snx1s4b0.js} +3 -3
- package/dist/chunks/{cf-account-picker-2e06qxey.js → cf-account-picker-xyqw0jex.js} +3 -3
- package/dist/chunks/cf-settings-hp1pjm0n.js +4 -0
- package/dist/chunks/components-nkwphrx3.js +4 -0
- package/dist/chunks/content-source-6a253wmk.js +4 -0
- package/dist/chunks/{convert-to-repeater-j9hfcj4f.js → convert-to-repeater-fhdwyg14.js} +3 -3
- package/dist/chunks/editor-wqrc68ca.js +4 -0
- package/dist/chunks/entry-commands-nef0wrmp.js +4 -0
- package/dist/chunks/{extension-sections-thfke054.js → extension-sections-8ez2rrk8.js} +2 -2
- package/dist/chunks/file-ops-y3w71k1n.js +4 -0
- package/dist/chunks/files-de90apn9.js +4 -0
- package/dist/chunks/format-host-zfjz4rzm.js +4 -0
- package/dist/chunks/fs-events-77c0mr0x.js +4 -0
- package/dist/chunks/grid-open-4n4fjfpf.js +4 -0
- package/dist/chunks/{grid-view-7d2nx83y.js → grid-view-ka55z38f.js} +4 -4
- package/dist/chunks/iframe-host-na2mywz4.js +4 -0
- package/dist/chunks/index-ewmgdb7c.js +4 -0
- package/dist/chunks/{index-jxmt7vh8.js → index-qb98xhse.js} +3 -3
- package/dist/chunks/{javascript-vmqp56ke.js → javascript-ces13wny.js} +2 -2
- package/dist/chunks/{jsonMode-n3wmpant.js → jsonMode-6md16d4v.js} +2 -2
- package/dist/chunks/jx-validate-b309c020.js +4 -0
- package/dist/chunks/layers-8rn8y6qv.js +4 -0
- package/dist/chunks/layers-panel-eyah7h9z.js +4 -0
- package/dist/chunks/media-library-gs3np47y.js +4 -0
- package/dist/chunks/media-picker-tt7dk4tb.js +4 -0
- package/dist/chunks/{monaco-binding-wfhmz89d.js → monaco-binding-p8j80s4w.js} +3 -3
- package/dist/chunks/monaco-buffer-wsf6cmnq.js +4 -0
- package/dist/chunks/{monaco-setup-fty7a8vg.js → monaco-setup-wasktzfg.js} +2 -2
- package/dist/chunks/notify-qwst108r.js +4 -0
- package/dist/chunks/platform-c16mqvmp.js +4 -0
- package/dist/chunks/publish-panel-d34bhvwj.js +4 -0
- package/dist/chunks/{publish-quick-12hh8926.js → publish-quick-vr695j26.js} +3 -3
- package/dist/chunks/publish-workspace-2yr60mt4.js +4 -0
- package/dist/chunks/quick-search-e6xsdv0d.js +4 -0
- package/dist/chunks/references-gsj6trdv.js +4 -0
- package/dist/chunks/right-panel-33jv1knc.js +4 -0
- package/dist/chunks/source-control-bj9bejjv.js +4 -0
- package/dist/chunks/{studio-9ndgd55w.js → studio-03mqh8ax.js} +4 -4
- package/dist/chunks/{studio-apapy17g.js → studio-06j4en5h.js} +2 -2
- package/dist/chunks/{studio-aghsg0ds.js → studio-0ydxv2mr.js} +6 -6
- package/dist/chunks/{studio-ekzbgnjp.js → studio-1e8dw2w2.js} +4 -4
- package/dist/chunks/{studio-0nefxhar.js → studio-2hsqbq7b.js} +5 -5
- package/dist/chunks/{studio-sm2f0sc6.js → studio-2j86y34y.js} +5 -5
- package/dist/chunks/{studio-1cfyd1ys.js → studio-2ks6ka4g.js} +3 -3
- package/dist/chunks/{studio-vtey9w98.js → studio-34jrz12k.js} +4 -4
- package/dist/chunks/{studio-e4rq6x0q.js → studio-35vrtpdq.js} +4 -4
- package/dist/chunks/{studio-a9k86nq6.js → studio-38g0jdz7.js} +4 -4
- package/dist/chunks/{studio-9gzvypw4.js → studio-396srjbk.js} +4 -4
- package/dist/chunks/{studio-4j35hgsw.js → studio-3c8pp7pb.js} +4 -4
- package/dist/chunks/{studio-z1p91z5a.js → studio-3v5cagy9.js} +4 -4
- package/dist/chunks/{studio-a6e5fzve.js → studio-3w35qvay.js} +4 -4
- package/dist/chunks/{studio-ry4789ng.js → studio-3zge48z4.js} +6 -6
- package/dist/chunks/{studio-9ra9e7m4.js → studio-45gyxpcq.js} +4 -4
- package/dist/chunks/{studio-tfmhnwyr.js → studio-4kzbrr5t.js} +3 -3
- package/dist/chunks/{studio-tyzgte21.js → studio-5tzgq6fx.js} +4 -4
- package/dist/chunks/{studio-nqexty8x.js → studio-5wzdy1jc.js} +6 -6
- package/dist/chunks/{studio-14hh60j8.js → studio-771k2n12.js} +4 -4
- package/dist/chunks/{studio-nm86874b.js → studio-7dzyz9n8.js} +98 -98
- package/dist/chunks/{studio-k5n4es74.js → studio-7xag4na8.js} +4 -4
- package/dist/chunks/studio-88bmt9rh.js +5 -0
- package/dist/chunks/{studio-kyw47pet.js → studio-9b7177jq.js} +4 -4
- package/dist/chunks/{studio-yjh9gddd.js → studio-9cxmp61c.js} +5 -5
- package/dist/chunks/{studio-qd0cycm4.js → studio-9dz76yqc.js} +5 -5
- package/dist/chunks/{studio-cg54vtet.js → studio-agyvzfm4.js} +4 -4
- package/dist/chunks/{studio-x2trkhk5.js → studio-c1pxszat.js} +4 -4
- package/dist/chunks/{studio-qs5fmhtj.js → studio-cpkamjdn.js} +7 -7
- package/dist/chunks/{studio-n4qq1n57.js → studio-d038kqbs.js} +4 -4
- package/dist/chunks/{studio-fj6jzgxw.js → studio-d83m0wyq.js} +4 -4
- package/dist/chunks/studio-dbxv7w4s.js +5 -0
- package/dist/chunks/{studio-7q6jdd4h.js → studio-de1s2x9v.js} +4 -4
- package/dist/chunks/{studio-h4jbgne2.js → studio-dx73md4n.js} +6 -6
- package/dist/chunks/{studio-nfwacw31.js → studio-ekgtrndg.js} +4 -4
- package/dist/chunks/{studio-79r8msnr.js → studio-epenqmvk.js} +3 -3
- package/dist/chunks/{studio-sc35ttdk.js → studio-et2q4s8n.js} +4 -4
- package/dist/chunks/{studio-g45r5vm8.js → studio-ewarnzm4.js} +2 -2
- package/dist/chunks/{studio-nfmpmkqb.js → studio-fy7jzj9j.js} +4 -4
- package/dist/chunks/{studio-kqh7fxn6.js → studio-gr36vnte.js} +4 -4
- package/dist/chunks/{studio-spza69tb.js → studio-gwrhcrss.js} +5 -5
- package/dist/chunks/{studio-8hf8qcm1.js → studio-h310mbqm.js} +4 -4
- package/dist/chunks/{studio-qevf5z9d.js → studio-h5tpz5ke.js} +4 -4
- package/dist/chunks/studio-h7weqf2q.js +5 -0
- package/dist/chunks/{studio-gk9yk8dj.js → studio-hcsba201.js} +6 -6
- package/dist/chunks/{studio-ws2r1d7e.js → studio-hwejq03c.js} +4 -4
- package/dist/chunks/{studio-9179bsm7.js → studio-hy4ye0dx.js} +6 -6
- package/dist/chunks/{studio-myb4mf0h.js → studio-jttyqrmh.js} +4 -4
- package/dist/chunks/{studio-k6hbx2fc.js → studio-jw0q5j6k.js} +4 -4
- package/dist/chunks/{studio-s4fq9jht.js → studio-k3a8rp3d.js} +4 -4
- package/dist/chunks/{studio-by8x0jvc.js → studio-k3bcy02e.js} +4 -4
- package/dist/chunks/{studio-3hpcawh8.js → studio-m48v13s3.js} +3 -3
- package/dist/chunks/{studio-4qg61g9j.js → studio-ndskgqef.js} +4 -4
- package/dist/chunks/studio-p1n2xjda.js +5 -0
- package/dist/chunks/{studio-ae7a69jc.js → studio-pgm83a2f.js} +5 -5
- package/dist/chunks/{studio-0rg30pvs.js → studio-pqqq6v85.js} +4 -4
- package/dist/chunks/{studio-gskkgfe3.js → studio-r17asgs0.js} +4 -4
- package/dist/chunks/{studio-8pne0rx4.js → studio-ry81j4q1.js} +4 -4
- package/dist/chunks/{studio-mkw17ngg.js → studio-rzg8hnkf.js} +4 -4
- package/dist/chunks/{studio-trcfer2b.js → studio-s0fg2wav.js} +4 -4
- package/dist/chunks/{studio-gczve8zf.js → studio-tj2s0tks.js} +3 -3
- package/dist/chunks/{studio-tn7v8gey.js → studio-v9ywz2se.js} +5 -5
- package/dist/chunks/{studio-nvfa137z.js → studio-vswcpzk7.js} +5 -5
- package/dist/chunks/{studio-ztppbam0.js → studio-vzgmyt0v.js} +4 -4
- package/dist/chunks/{studio-fg4fj9ec.js → studio-wxdcd42g.js} +5 -5
- package/dist/chunks/{studio-xzgp0cp7.js → studio-wxhy1yh7.js} +4 -4
- package/dist/chunks/{studio-4rgem12q.js → studio-x08a3m0a.js} +4 -4
- package/dist/chunks/{studio-t6dqnnft.js → studio-x3mjr5cc.js} +4 -4
- package/dist/chunks/{studio-9ff0vqff.js → studio-x97ay0v4.js} +4 -4
- package/dist/chunks/{studio-32d8wyxq.js → studio-xdpz9rpe.js} +29 -29
- package/dist/chunks/{studio-6k9djnxw.js → studio-xh683qzy.js} +4 -4
- package/dist/chunks/{studio-c88gq88j.js → studio-xkxetm65.js} +5 -5
- package/dist/chunks/{studio-jr30g4ae.js → studio-y26hx9re.js} +4 -4
- package/dist/chunks/{studio-hcy9zeac.js → studio-yg3djpnm.js} +4 -4
- package/dist/chunks/studio-yx8hzqja.js +5 -0
- package/dist/chunks/{studio-ht81xjkr.js → studio-zg16eeq1.js} +4 -4
- package/dist/chunks/{studio-6rbaedw2.js → studio-zgx7svjd.js} +6 -6
- package/dist/chunks/{studio-ywf9y1e2.js → studio-zkq3r91p.js} +4 -4
- package/dist/chunks/{tsMode-byje4a01.js → tsMode-h6tkz0ea.js} +2 -2
- package/dist/chunks/workspace-home-zkkrxq3r.js +4 -0
- package/dist/chunks/workspace-tdqqjqaa.js +4 -0
- package/dist/chunks/{ws-client-h09h6sqg.js → ws-client-e951my95.js} +3 -3
- package/dist/iframe-entry.js +8 -8
- package/dist/manifest.json +72 -72
- package/dist/studio.js +64 -65
- package/package.json +11 -11
- package/src/canvas/iframe-editable-root.ts +32 -2
- package/src/format/format-host.ts +1 -1
- package/src/github/github-auth-status.ts +2 -2
- package/src/media/media-library.ts +73 -3
- package/src/packages/ensure-deps.ts +5 -5
- package/src/packages/pull-package-sync.ts +4 -4
- package/src/panels/activity-panel.ts +1 -1
- package/src/platforms/cloud.ts +48 -34
- package/src/settings/extension-commands.ts +1 -1
- package/src/settings/extension-rows.ts +1 -6
- package/src/settings/extensions-section.ts +3 -6
- package/src/surfaces/block-action-bar.json +1 -3
- package/src/surfaces/media-library.json +35 -0
- package/src/surfaces/media-library.ts +2 -0
- package/src/surfaces/menu.ts +62 -1
- package/src/surfaces/settings-packages.ts +1 -1
- package/src/types.ts +10 -8
- package/src/ui/layers.ts +1 -1
- package/src/ui/progress-modal.ts +3 -3
- package/dist/chunks/adapter-ay13gema.js +0 -4
- package/dist/chunks/cf-settings-5y04810c.js +0 -4
- package/dist/chunks/components-3vf75n83.js +0 -4
- package/dist/chunks/content-source-4f2raf4a.js +0 -4
- package/dist/chunks/editor-645krvtz.js +0 -4
- package/dist/chunks/entry-commands-3qmv8wkr.js +0 -4
- package/dist/chunks/file-ops-kkhp9ma7.js +0 -4
- package/dist/chunks/files-yswdhxrf.js +0 -4
- package/dist/chunks/format-host-4g8ex6bz.js +0 -4
- package/dist/chunks/fs-events-z9gpvc6t.js +0 -4
- package/dist/chunks/grid-open-fm4j183c.js +0 -4
- package/dist/chunks/iframe-host-q7fedsmr.js +0 -4
- package/dist/chunks/index-pt4wh98x.js +0 -4
- package/dist/chunks/jx-validate-19y62knt.js +0 -4
- package/dist/chunks/layers-panel-1qvc83y9.js +0 -4
- package/dist/chunks/layers-qf0bc7ze.js +0 -4
- package/dist/chunks/media-library-f9d5qcjb.js +0 -4
- package/dist/chunks/media-picker-rsb1fmk2.js +0 -4
- package/dist/chunks/monaco-buffer-ak0raadw.js +0 -4
- package/dist/chunks/notify-4qj70q0x.js +0 -4
- package/dist/chunks/platform-ehg6t9vd.js +0 -4
- package/dist/chunks/publish-panel-yhmda3cd.js +0 -4
- package/dist/chunks/publish-workspace-hx8qdv9p.js +0 -4
- package/dist/chunks/quick-search-r955m3q1.js +0 -4
- package/dist/chunks/references-8gxv0x7t.js +0 -4
- package/dist/chunks/right-panel-jarwqjpy.js +0 -4
- package/dist/chunks/source-control-84qt25m9.js +0 -4
- package/dist/chunks/studio-2bq7hheq.js +0 -5
- package/dist/chunks/studio-dvhxxxbd.js +0 -5
- package/dist/chunks/studio-ge08zwpv.js +0 -5
- package/dist/chunks/studio-nr0kcqc6.js +0 -5
- package/dist/chunks/studio-y47090vq.js +0 -5
- package/dist/chunks/workspace-7q4arnt3.js +0 -4
- package/dist/chunks/workspace-home-ge027kr6.js +0 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@atlaskit/pragmatic-drag-and-drop": "^4.0.0",
|
|
62
62
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^3.0.0",
|
|
63
|
-
"@jxsuite/ai": "^0.41.
|
|
64
|
-
"@jxsuite/collab": "^0.11.
|
|
65
|
-
"@jxsuite/create": "^1.
|
|
66
|
-
"@jxsuite/formulas": "^0.0.
|
|
67
|
-
"@jxsuite/markup": "^1.0.
|
|
68
|
-
"@jxsuite/protocol": "^
|
|
69
|
-
"@jxsuite/runtime": "^6.0.
|
|
70
|
-
"@jxsuite/schema": "^
|
|
71
|
-
"@jxsuite/site": "^2.2.
|
|
72
|
-
"@jxsuite/ui": "^0.8.
|
|
63
|
+
"@jxsuite/ai": "^0.41.1",
|
|
64
|
+
"@jxsuite/collab": "^0.11.4",
|
|
65
|
+
"@jxsuite/create": "^1.5.0",
|
|
66
|
+
"@jxsuite/formulas": "^0.0.26",
|
|
67
|
+
"@jxsuite/markup": "^1.0.2",
|
|
68
|
+
"@jxsuite/protocol": "^6.0.0",
|
|
69
|
+
"@jxsuite/runtime": "^6.0.1",
|
|
70
|
+
"@jxsuite/schema": "^5.0.0",
|
|
71
|
+
"@jxsuite/site": "^2.2.2",
|
|
72
|
+
"@jxsuite/ui": "^0.8.2",
|
|
73
73
|
"@vue/reactivity": "3.5.44",
|
|
74
74
|
"ajv": "^8.20.0",
|
|
75
75
|
"ajv-formats": "^3.0.1",
|
|
@@ -445,8 +445,9 @@ export function startEditableRoot(
|
|
|
445
445
|
* something no caret can enter, so the caret went on blinking in the block clicked BEFORE, with
|
|
446
446
|
* the component now selected in the editor and every keystroke still typed into that block.
|
|
447
447
|
*/
|
|
448
|
-
const onPointerDownCapture = (e:
|
|
449
|
-
|
|
448
|
+
const onPointerDownCapture = (e: PointerEvent) => {
|
|
449
|
+
// A secondary press asks for the element menu, not an edit: see `onMouseDownCapture`.
|
|
450
|
+
if (!editingAllowed() || e.button === 2) {
|
|
450
451
|
return;
|
|
451
452
|
}
|
|
452
453
|
const target = e.target instanceof Element ? e.target : null;
|
|
@@ -478,6 +479,33 @@ export function startEditableRoot(
|
|
|
478
479
|
}
|
|
479
480
|
};
|
|
480
481
|
|
|
482
|
+
/**
|
|
483
|
+
* A secondary press outside the active block selects, it never starts an edit.
|
|
484
|
+
*
|
|
485
|
+
* The whole canvas is one `contenteditable`, and a right-button press places the caret where it
|
|
486
|
+
* lands, as a left one does. That caret activated the block through `selectionchange`, so every
|
|
487
|
+
* right-click on a block also entered inline editing: the bridge's edit start ran ahead of the
|
|
488
|
+
* menu's mount in the same task, which held the element menu back by hundreds of milliseconds on
|
|
489
|
+
* a real page, and the block was left in edit mode after the menu closed. Cancelling the press's
|
|
490
|
+
* default keeps the caret where it was; `contextmenu` still fires, so the menu is unaffected. The
|
|
491
|
+
* block being edited keeps its native press, because the caret placed there is what the native
|
|
492
|
+
* spelling and edit menu reads. A right-click elsewhere leaves that block, as a left click
|
|
493
|
+
* would.
|
|
494
|
+
*/
|
|
495
|
+
const onMouseDownCapture = (e: MouseEvent) => {
|
|
496
|
+
if (e.button !== 2 || !editingAllowed()) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (activeEl && e.target instanceof Node && activeEl.contains(e.target)) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
e.preventDefault();
|
|
503
|
+
if (activeEl) {
|
|
504
|
+
deactivate();
|
|
505
|
+
doc.getSelection()?.removeAllRanges();
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
481
509
|
/**
|
|
482
510
|
* Reordering is the block action bar's drag handle, full stop. A `contenteditable` region is
|
|
483
511
|
* natively draggable by its text, which would both fight text selection and move content behind
|
|
@@ -501,6 +529,7 @@ export function startEditableRoot(
|
|
|
501
529
|
container.addEventListener("compositionend", onCompositionEnd);
|
|
502
530
|
container.addEventListener("dragstart", onDragStart);
|
|
503
531
|
doc.addEventListener("pointerdown", onPointerDownCapture, true);
|
|
532
|
+
doc.addEventListener("mousedown", onMouseDownCapture, true);
|
|
504
533
|
|
|
505
534
|
return {
|
|
506
535
|
capture: () => captureDocSelection(container, deps.isEditableBlock),
|
|
@@ -542,6 +571,7 @@ export function startEditableRoot(
|
|
|
542
571
|
container.removeEventListener("compositionend", onCompositionEnd);
|
|
543
572
|
container.removeEventListener("dragstart", onDragStart);
|
|
544
573
|
doc.removeEventListener("pointerdown", onPointerDownCapture, true);
|
|
574
|
+
doc.removeEventListener("mousedown", onMouseDownCapture, true);
|
|
545
575
|
// Deactivating commits, so the pending tick is cancelled rather than fired.
|
|
546
576
|
deactivate();
|
|
547
577
|
},
|
|
@@ -217,7 +217,7 @@ async function shareProjectSchemas(
|
|
|
217
217
|
*/
|
|
218
218
|
export function refreshExtensionUi(platform: Pick<StudioPlatform, "fetchProjectSchemas">): void {
|
|
219
219
|
void shareProjectSchemas(platform);
|
|
220
|
-
// `installed`
|
|
220
|
+
// `installed` is a per-project fact, so an install or a project switch can move
|
|
221
221
|
// Them even when the shipped half has not changed.
|
|
222
222
|
void loadExtensionCatalog();
|
|
223
223
|
void import("../settings/extension-sections")
|
|
@@ -21,8 +21,8 @@ const STORAGE_KEY = "jx_github_token";
|
|
|
21
21
|
/**
|
|
22
22
|
* The GitHub OAuth app every Jx sign-in authorizes against: the browser's device flow in
|
|
23
23
|
* `github-auth.ts`, and the desktop launcher's RFC 8252 loopback redirect
|
|
24
|
-
* (`packages/desktop/src/github-signin.ts`). Here, in the module with no UI dependency,
|
|
25
|
-
* launcher's main process imports it too.
|
|
24
|
+
* (`packages/desktop/src/backend/github-signin.ts`). Here, in the module with no UI dependency,
|
|
25
|
+
* because the launcher's main process imports it too.
|
|
26
26
|
*/
|
|
27
27
|
export const GITHUB_OAUTH_CLIENT_ID = "Ov23liYVlMFpgjOEPXJH";
|
|
28
28
|
|
|
@@ -67,6 +67,7 @@ import { dirName, previewFileSrc } from "../files/media-paths";
|
|
|
67
67
|
import { forgetVersions } from "../files/asset-versions";
|
|
68
68
|
import { loadMediaUsages, peekMediaUsages } from "../files/media-usage";
|
|
69
69
|
import { onProjectMediaChange, projectMediaFiles } from "../ui/media-picker";
|
|
70
|
+
import { openMenu } from "../surfaces/menu";
|
|
70
71
|
import { openMediaTab } from "./media-open";
|
|
71
72
|
import { factsLine, mediaStageFor, specimenOf, usageOf } from "./media-pane";
|
|
72
73
|
import {
|
|
@@ -93,6 +94,7 @@ import type {
|
|
|
93
94
|
MediaTileRow,
|
|
94
95
|
} from "../surfaces/media-library";
|
|
95
96
|
import type { MediaPickDialogHandle } from "../surfaces/media-pick-dialog";
|
|
97
|
+
import type { MenuHandle, MenuRowProjection } from "../surfaces/menu";
|
|
96
98
|
import type { PreviewObserver } from "../browse/library-preview";
|
|
97
99
|
import type { Tab } from "../tabs/tab";
|
|
98
100
|
import type { CanvasSurface } from "../canvas/canvas-surface";
|
|
@@ -788,11 +790,11 @@ function actionsFor(mount: LibraryMount) {
|
|
|
788
790
|
activate(mount, selected);
|
|
789
791
|
}
|
|
790
792
|
},
|
|
793
|
+
contextMenu: (file: string, x: number, y: number) => showTileMenu(mount, file, x, y),
|
|
791
794
|
copyRef: () => {
|
|
792
795
|
const entry = selectedEntry(mount);
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
void navigator.clipboard.writeText(entry.ref);
|
|
796
|
+
if (entry) {
|
|
797
|
+
copySiteUrl(entry);
|
|
796
798
|
}
|
|
797
799
|
},
|
|
798
800
|
dragOut: () => {
|
|
@@ -913,6 +915,74 @@ function actionsFor(mount: LibraryMount) {
|
|
|
913
915
|
}
|
|
914
916
|
|
|
915
917
|
/** Enter or a double-click: open the viewer, or in pick mode, use the file. */
|
|
918
|
+
/** Put a file's site URL on the clipboard. */
|
|
919
|
+
function copySiteUrl(entry: ProjectMediaFile): void {
|
|
920
|
+
// `navigator.clipboard` is [SecureContext]: a plain-http host has none, whatever lib.dom says.
|
|
921
|
+
if ("clipboard" in navigator) {
|
|
922
|
+
void navigator.clipboard.writeText(entry.ref);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* The tile menu that is up, if any. One handle, because only one can be open: a second kit menu
|
|
928
|
+
* opened over the first is how two of them light-dismiss each other.
|
|
929
|
+
*/
|
|
930
|
+
let _tileMenu: MenuHandle | null = null;
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* A right-click on a tile: select it, then offer the detail pane's actions at the pointer.
|
|
934
|
+
*
|
|
935
|
+
* The same four verbs as the detail pane's (studio.md §22.6), in the order the pane draws them, and
|
|
936
|
+
* each calls what the pane's control calls, so the menu cannot drift from the pane. Selecting first
|
|
937
|
+
* is what makes the pane describe the file the menu acts on, as a right-click on a row does in the
|
|
938
|
+
* Content workspace.
|
|
939
|
+
*/
|
|
940
|
+
function showTileMenu(mount: LibraryMount, file: string, x: number, y: number): void {
|
|
941
|
+
select(mount, file);
|
|
942
|
+
const entry = selectedEntry(mount);
|
|
943
|
+
if (!entry) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
_tileMenu?.close();
|
|
947
|
+
const row = (
|
|
948
|
+
id: string,
|
|
949
|
+
title: string,
|
|
950
|
+
run: () => void,
|
|
951
|
+
extra: Partial<MenuRowProjection> = {},
|
|
952
|
+
): MenuRowProjection => ({
|
|
953
|
+
destructive: false,
|
|
954
|
+
disabled: false,
|
|
955
|
+
dividerAbove: false,
|
|
956
|
+
id,
|
|
957
|
+
run,
|
|
958
|
+
title,
|
|
959
|
+
...extra,
|
|
960
|
+
});
|
|
961
|
+
_tileMenu = openMenu({
|
|
962
|
+
label: `${entry.name} actions`,
|
|
963
|
+
onClosed: (handle) => {
|
|
964
|
+
if (_tileMenu === handle) {
|
|
965
|
+
_tileMenu = null;
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
origin: { x, y },
|
|
969
|
+
region: "media-tile",
|
|
970
|
+
rows: [
|
|
971
|
+
row("open", "Open in Viewer", () => openMediaTab(file)),
|
|
972
|
+
row("copy-url", "Copy Site URL", () => copySiteUrl(entry)),
|
|
973
|
+
row("replace", "Replace…", () => requestMediaReplace(file), { dividerAbove: true }),
|
|
974
|
+
row(
|
|
975
|
+
"delete",
|
|
976
|
+
"Delete",
|
|
977
|
+
() => {
|
|
978
|
+
void deleteMediaFile(file);
|
|
979
|
+
},
|
|
980
|
+
{ destructive: true, dividerAbove: true },
|
|
981
|
+
),
|
|
982
|
+
],
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
916
986
|
function activate(mount: LibraryMount, file: string): void {
|
|
917
987
|
if (mount.mode === "pick") {
|
|
918
988
|
const entry = selectedEntry(mount);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
3
|
* Ensure the active project's dependencies are installed before the editor loads. When the backend
|
|
4
|
-
* reports node_modules is missing,
|
|
5
|
-
*
|
|
4
|
+
* reports node_modules is missing, install them behind a blocking progress modal so component and
|
|
5
|
+
* format resolution see the real dependency tree. No-op on platforms without package support.
|
|
6
6
|
*
|
|
7
7
|
* It REPORTS whether an install actually ran, because "format resolution sees the real dependency
|
|
8
8
|
* tree" was only half true: project activation fetches the format registry before calling this, and
|
|
@@ -17,7 +17,7 @@ import { shouldInstallAutomation } from "../services/automation";
|
|
|
17
17
|
|
|
18
18
|
export async function ensureDependenciesInstalled(): Promise<boolean> {
|
|
19
19
|
// Automation/screenshot runs open projects read-only to drive the canvas — they must never mutate
|
|
20
|
-
// The project on disk (
|
|
20
|
+
// The project on disk (an install would litter node_modules and a lockfile into starter templates).
|
|
21
21
|
if (shouldInstallAutomation(location.search)) {
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
@@ -35,7 +35,7 @@ export async function ensureDependenciesInstalled(): Promise<boolean> {
|
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
37
|
const progress = showProgressModal({
|
|
38
|
-
status: "
|
|
38
|
+
status: "Installing dependencies…",
|
|
39
39
|
title: "Installing dependencies",
|
|
40
40
|
});
|
|
41
41
|
try {
|
|
@@ -44,7 +44,7 @@ export async function ensureDependenciesInstalled(): Promise<boolean> {
|
|
|
44
44
|
progress.done();
|
|
45
45
|
return true;
|
|
46
46
|
}
|
|
47
|
-
progress.fail(result.log ?? "
|
|
47
|
+
progress.fail(result.log ?? "Install failed");
|
|
48
48
|
} catch (error) {
|
|
49
49
|
progress.fail(error instanceof Error ? error.message : String(error));
|
|
50
50
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* updates. The on-open @jxsuite update rewrites package.json and bun.lock without committing, so a
|
|
5
5
|
* teammate pushing the same update makes a later `git pull` refuse to merge. Since those local
|
|
6
6
|
* changes are machine-generated and reproducible, we discard them, pull, and re-run the package
|
|
7
|
-
* sync (re-apply the update if the pulled project is still behind, or
|
|
7
|
+
* sync (re-apply the update if the pulled project is still behind, or an install if the pull
|
|
8
8
|
* brought it). Detection is state-based (git status + content comparison against `@{u}`), never git
|
|
9
9
|
* stderr parsing, so it works identically across backends and locales.
|
|
10
10
|
*/
|
|
@@ -188,7 +188,7 @@ async function applyDiscard(plan: PackagePullPlan): Promise<void> {
|
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
190
|
* Bring packages back in line after a pull that touched (or followed a discard of) package files:
|
|
191
|
-
* re-apply the @jxsuite update if the pulled project is still behind, otherwise
|
|
191
|
+
* re-apply the @jxsuite update if the pulled project is still behind, otherwise an install so
|
|
192
192
|
* node_modules matches the pulled lockfile. Never throws — a successful pull must not turn into an
|
|
193
193
|
* error banner; failures surface through the progress modal.
|
|
194
194
|
*/
|
|
@@ -209,7 +209,7 @@ async function syncPackagesAfterPull(): Promise<void> {
|
|
|
209
209
|
return;
|
|
210
210
|
}
|
|
211
211
|
const progress = showProgressModal({
|
|
212
|
-
status: "
|
|
212
|
+
status: "Installing dependencies…",
|
|
213
213
|
title: "Syncing dependencies",
|
|
214
214
|
});
|
|
215
215
|
try {
|
|
@@ -217,7 +217,7 @@ async function syncPackagesAfterPull(): Promise<void> {
|
|
|
217
217
|
if (result.ok) {
|
|
218
218
|
progress.done();
|
|
219
219
|
} else {
|
|
220
|
-
progress.fail(result.log ?? "
|
|
220
|
+
progress.fail(result.log ?? "Install failed");
|
|
221
221
|
}
|
|
222
222
|
} catch (error) {
|
|
223
223
|
progress.fail(errorMessage(error));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* What this replaces: `ui/progress-modal.ts` was **the only surface in Studio with a real error
|
|
6
6
|
* view** — a red headline, the captured log in a `<pre>`, an explicit Close — and it had four call
|
|
7
7
|
* sites, all package operations. It blocked the whole app, offered no cancel, and vanished
|
|
8
|
-
* completely on success, so
|
|
8
|
+
* completely on success, so an install that hung had no exit and one that failed had a log you
|
|
9
9
|
* could read exactly once. Every OTHER long operation — git clone, publish, media upload, site
|
|
10
10
|
* import, project open — got a three-second grey line or nothing at all.
|
|
11
11
|
*
|
package/src/platforms/cloud.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* before Studio boots — while project-less mode (null) powers the /studio welcome screen.
|
|
8
8
|
*
|
|
9
9
|
* Cloud omissions (Studio degrades per @jxsuite/protocol's route table): pickDirectory, package
|
|
10
|
-
* install
|
|
11
|
-
*
|
|
10
|
+
* install and the install-on-open check (a cloud session has no `node_modules`; add, remove and
|
|
11
|
+
* set-versions update `package.json` and `package-lock.json` through the session's package
|
|
12
|
+
* service), multi-window, gitClone, resolveClass, component discovery, code services.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { negotiateCollab } from "@jxsuite/collab/negotiate";
|
|
@@ -43,6 +44,8 @@ import type {
|
|
|
43
44
|
ImportSiteOptions,
|
|
44
45
|
OfferedAction,
|
|
45
46
|
PackageInfo,
|
|
47
|
+
PackageOpResult,
|
|
48
|
+
PackageVersionInfo,
|
|
46
49
|
ProjectListEntry,
|
|
47
50
|
SiteBuildResult,
|
|
48
51
|
ProjectSchemasResponse,
|
|
@@ -947,17 +950,6 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
947
950
|
}
|
|
948
951
|
}
|
|
949
952
|
|
|
950
|
-
async function writePackageJson(pkg: JsonObject): Promise<void> {
|
|
951
|
-
const res = await api("/file", {
|
|
952
|
-
method: "PUT",
|
|
953
|
-
headers: { "Content-Type": "application/json" },
|
|
954
|
-
body: JSON.stringify({ path: "package.json", content: `${JSON.stringify(pkg, null, 2)}\n` }),
|
|
955
|
-
});
|
|
956
|
-
if (!res.ok) {
|
|
957
|
-
throw new Error(await errorMessage(res, "Failed to update package.json"));
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
953
|
// The bound session's /raw tree; see `documentBaseUrl` below for why both members ride together.
|
|
962
954
|
const repoAssets: Pick<StudioPlatform, "assetSpace" | "documentBaseUrl"> = {};
|
|
963
955
|
if (base) {
|
|
@@ -1284,10 +1276,12 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1284
1276
|
*
|
|
1285
1277
|
* A Worker ships a fixed set of extension packages (specs/extensions.md §5.5), decided by the
|
|
1286
1278
|
* platform build rather than by this repository's `extensions/` tree, so the gateway is the
|
|
1287
|
-
* only thing that knows.
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1279
|
+
* only thing that knows. An extension the Worker does not bundle is dropped from the registry
|
|
1280
|
+
* before composition, so advertising it would promise a toggle that silently does nothing.
|
|
1281
|
+
*
|
|
1282
|
+
* What the Worker bundles is what it can EDIT with, not what the project gets: the site is
|
|
1283
|
+
* built in Pages CI from the project's own install, so an entry the project has not declared is
|
|
1284
|
+
* install-then-enable here exactly as on every other backend (specs/extensions.md §3).
|
|
1291
1285
|
*
|
|
1292
1286
|
* `installed` here means DECLARED. Nothing resolves a module in a Worker: `addPackage` is a
|
|
1293
1287
|
* manifest edit and resolution happens later in Pages CI, so the manifest is the only fact this
|
|
@@ -1309,7 +1303,6 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1309
1303
|
...Object.keys(dependencyBlock(pkg, "devDependencies")),
|
|
1310
1304
|
]);
|
|
1311
1305
|
for (const entry of entries) {
|
|
1312
|
-
entry.bundled = true;
|
|
1313
1306
|
entry.installed = declared.has(entry.name);
|
|
1314
1307
|
}
|
|
1315
1308
|
return entries;
|
|
@@ -1435,28 +1428,49 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1435
1428
|
return null;
|
|
1436
1429
|
},
|
|
1437
1430
|
|
|
1438
|
-
// ─── Packages (
|
|
1431
|
+
// ─── Packages (the session's lockfile service; nothing installs) ───
|
|
1439
1432
|
|
|
1433
|
+
/* Each mutation runs npm's installer lockfile-only on the platform, which writes
|
|
1434
|
+
`package.json` and `package-lock.json` back into the shared tree as one change, so a commit
|
|
1435
|
+
carries a lockfile a build can install from frozen (server.md §4.1). A failure changes
|
|
1436
|
+
nothing and its message names the cause. */
|
|
1440
1437
|
async addPackage(name: string) {
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1438
|
+
const res = await api("/packages/add", {
|
|
1439
|
+
body: JSON.stringify({ name }),
|
|
1440
|
+
headers: { "Content-Type": "application/json" },
|
|
1441
|
+
method: "POST",
|
|
1442
|
+
});
|
|
1443
|
+
if (!res.ok) {
|
|
1444
|
+
throw new Error(await errorMessage(res, `Failed to add ${name}`));
|
|
1445
|
+
}
|
|
1449
1446
|
},
|
|
1450
1447
|
|
|
1451
1448
|
async removePackage(name: string) {
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1449
|
+
const res = await api("/packages/remove", {
|
|
1450
|
+
body: JSON.stringify({ name }),
|
|
1451
|
+
headers: { "Content-Type": "application/json" },
|
|
1452
|
+
method: "POST",
|
|
1453
|
+
});
|
|
1454
|
+
if (!res.ok) {
|
|
1455
|
+
throw new Error(await errorMessage(res, `Failed to remove ${name}`));
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
|
|
1459
|
+
async setPackageVersions(updates: { name: string; version: string; dev?: boolean }[]) {
|
|
1460
|
+
const res = await api("/packages/set-versions", {
|
|
1461
|
+
body: JSON.stringify({ updates }),
|
|
1462
|
+
headers: { "Content-Type": "application/json" },
|
|
1463
|
+
method: "POST",
|
|
1464
|
+
});
|
|
1465
|
+
if (!res.ok) {
|
|
1466
|
+
return { log: await errorMessage(res, "Failed to update versions"), ok: false };
|
|
1458
1467
|
}
|
|
1459
|
-
|
|
1468
|
+
return readJson<PackageOpResult>(res);
|
|
1469
|
+
},
|
|
1470
|
+
|
|
1471
|
+
async packageVersions() {
|
|
1472
|
+
const res = await api("/packages/versions");
|
|
1473
|
+
return res.ok ? readJson<PackageVersionInfo[]>(res) : [];
|
|
1460
1474
|
},
|
|
1461
1475
|
|
|
1462
1476
|
async listPackages(): Promise<PackageInfo[]> {
|
|
@@ -135,7 +135,7 @@ export async function enableExtension(specifier: string): Promise<void> {
|
|
|
135
135
|
let installed = false;
|
|
136
136
|
notifySettingsDocument();
|
|
137
137
|
try {
|
|
138
|
-
if (!row.installed
|
|
138
|
+
if (!row.installed) {
|
|
139
139
|
/*
|
|
140
140
|
* `beginActivity`, not `showProgressModal`. `studio-ui-guidelines.md` §13.3 reserves blocking
|
|
141
141
|
* for an operation that cannot proceed while the author edits, and this one can — the only
|
|
@@ -32,8 +32,6 @@ export interface ExtensionRow {
|
|
|
32
32
|
origin: ExtensionOrigin;
|
|
33
33
|
enabled: boolean;
|
|
34
34
|
installed: boolean;
|
|
35
|
-
/** The host resolves it without a project install, so enabling is a config write alone. */
|
|
36
|
-
bundled: boolean;
|
|
37
35
|
/** Why this backend cannot run it, as one sentence. Absent means it can. */
|
|
38
36
|
unavailable?: string | undefined;
|
|
39
37
|
/** Enabled but not installed — the state that fails the next build. */
|
|
@@ -69,11 +67,9 @@ export function buildRows(packages: PackageInfo[] | null = null): ExtensionRow[]
|
|
|
69
67
|
for (const entry of getExtensionCatalog()) {
|
|
70
68
|
const specifier = entry.specifier ?? entry.name;
|
|
71
69
|
const here = entry.installed ?? installed.has(entry.name);
|
|
72
|
-
const bundled = entry.bundled ?? false;
|
|
73
70
|
const info = resolved.get(entry.name);
|
|
74
71
|
const row: ExtensionRow = {
|
|
75
|
-
broken: enabled.has(specifier) && !here
|
|
76
|
-
bundled,
|
|
72
|
+
broken: enabled.has(specifier) && !here,
|
|
77
73
|
enabled: enabled.has(specifier),
|
|
78
74
|
installed: here,
|
|
79
75
|
name: entry.name,
|
|
@@ -102,7 +98,6 @@ export function buildRows(packages: PackageInfo[] | null = null): ExtensionRow[]
|
|
|
102
98
|
const info = resolved.get(specifier);
|
|
103
99
|
const row: ExtensionRow = {
|
|
104
100
|
broken: !installed.has(specifier) && info === undefined,
|
|
105
|
-
bundled: false,
|
|
106
101
|
enabled: true,
|
|
107
102
|
installed: installed.has(specifier),
|
|
108
103
|
name: specifier,
|
|
@@ -105,12 +105,9 @@ function noteFor(row: ExtensionRow): RowNote {
|
|
|
105
105
|
noteTone: "warn",
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
if (!row.installed
|
|
108
|
+
if (!row.installed) {
|
|
109
109
|
return { note: "Not installed. Turning this on installs it first.", noteTone: "note" };
|
|
110
110
|
}
|
|
111
|
-
if (row.bundled) {
|
|
112
|
-
return { note: "Ships with this backend, so it needs no install.", noteTone: "note" };
|
|
113
|
-
}
|
|
114
111
|
return { note: "", noteTone: "none" };
|
|
115
112
|
}
|
|
116
113
|
|
|
@@ -131,7 +128,7 @@ function blockedReason(row: ExtensionRow): string {
|
|
|
131
128
|
* Refused while the extension is still enabled, and DISABLED with the reason rather than hidden
|
|
132
129
|
* (`studio-ui-guidelines.md` §10): removing the package while `project.json` still names it
|
|
133
130
|
* manufactures exactly the broken state this section exists to eliminate. It is absent only when
|
|
134
|
-
* there is nothing to remove — a package the project never installed
|
|
131
|
+
* there is nothing to remove — a package the project never installed.
|
|
135
132
|
*/
|
|
136
133
|
function removeFor(
|
|
137
134
|
row: ExtensionRow,
|
|
@@ -140,7 +137,7 @@ function removeFor(
|
|
|
140
137
|
? `Turn ${row.title} off first. Removing the package while project.json still names it would fail the next build.`
|
|
141
138
|
: "";
|
|
142
139
|
return {
|
|
143
|
-
canRemove: row.installed
|
|
140
|
+
canRemove: row.installed,
|
|
144
141
|
removeDisabled: refusal !== "" || extensionOpInFlight() !== null,
|
|
145
142
|
removeHint: refusal === "" ? `Remove ${row.name}` : refusal,
|
|
146
143
|
removeLabel: `Remove ${row.name}`,
|
|
@@ -104,9 +104,7 @@
|
|
|
104
104
|
"display": "contents"
|
|
105
105
|
},
|
|
106
106
|
"& [part=\"link\"]": {
|
|
107
|
-
"
|
|
108
|
-
"gap": "var(--jx-space-2)",
|
|
109
|
-
"alignItems": "center",
|
|
107
|
+
"$description": "No display here. A closed popover is hidden by the platform's own `[popover]:not(:popover-open)` rule, which the kit lets through with `display: revert-layer`; a `display` declared on the part beats it, and the closed panel stayed laid out at the viewport origin, invisible at opacity 0 and still taking every click and the text cursor meant for the workspace tabs beneath it.",
|
|
110
108
|
"padding": "var(--jx-space-3)"
|
|
111
109
|
},
|
|
112
110
|
"& [part=\"link-field\"]": {
|
|
@@ -1027,6 +1027,41 @@
|
|
|
1027
1027
|
}
|
|
1028
1028
|
]
|
|
1029
1029
|
},
|
|
1030
|
+
"oncontextmenu": {
|
|
1031
|
+
"$prototype": "Function",
|
|
1032
|
+
"body": [
|
|
1033
|
+
{
|
|
1034
|
+
"$switch": {
|
|
1035
|
+
"$ref": "#/state/mode"
|
|
1036
|
+
},
|
|
1037
|
+
"cases": {
|
|
1038
|
+
"manage": [
|
|
1039
|
+
{
|
|
1040
|
+
"preventDefault": true
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
"operator": "call",
|
|
1044
|
+
"target": {
|
|
1045
|
+
"$ref": "#/state/contextMenu"
|
|
1046
|
+
},
|
|
1047
|
+
"value": [
|
|
1048
|
+
{
|
|
1049
|
+
"$ref": "$map/item/file"
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
"$ref": "event#/clientX"
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
"$ref": "event#/clientY"
|
|
1056
|
+
}
|
|
1057
|
+
]
|
|
1058
|
+
}
|
|
1059
|
+
]
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
],
|
|
1063
|
+
"description": "Manage mode only: the detail pane's actions at the pointer. Pick mode's dialog is modal, and a menu in the popover layer outside it would be inert, so its right-click is left to the platform."
|
|
1064
|
+
},
|
|
1030
1065
|
"children": [
|
|
1031
1066
|
{
|
|
1032
1067
|
"tagName": "div",
|
|
@@ -118,6 +118,8 @@ export interface MediaLibraryActions {
|
|
|
118
118
|
activate: (file: string) => void;
|
|
119
119
|
/** Enter on the grid: the same, for the selected file. */
|
|
120
120
|
activateSelected: () => void;
|
|
121
|
+
/** A right-click on a tile in manage mode: select it and open its actions at the pointer. */
|
|
122
|
+
contextMenu: (file: string, x: number, y: number) => void;
|
|
121
123
|
key: (key: string, ctrl: boolean) => void;
|
|
122
124
|
measure: (file: string, width: number, height: number) => void;
|
|
123
125
|
copyRef: () => void;
|