@jxsuite/studio 10.1.0 → 10.3.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-mkb0qa6p.js → 2020-906vx2dq.js} +3 -3
- package/dist/chunks/adapter-hnvad61x.js +4 -0
- package/dist/chunks/add-new-flow-3tkn72ey.js +4 -0
- package/dist/chunks/cf-account-picker-xb8vvt2h.js +4 -0
- package/dist/chunks/cf-settings-mkbcz4z2.js +4 -0
- package/dist/chunks/components-w5zqwva1.js +4 -0
- package/dist/chunks/content-source-1vzxq1ba.js +4 -0
- package/dist/chunks/convert-to-repeater-gwzh9gar.js +4 -0
- package/dist/chunks/editor-wvf5e6j9.js +4 -0
- package/dist/chunks/entry-commands-mx2whrjx.js +4 -0
- package/dist/chunks/extension-sections-338ee35t.js +4 -0
- package/dist/chunks/file-ops-9bxvk59j.js +4 -0
- package/dist/chunks/files-t07yqqej.js +4 -0
- package/dist/chunks/format-host-5ja5gxyk.js +4 -0
- package/dist/chunks/fs-events-3424ezp3.js +4 -0
- package/dist/chunks/grid-open-1mpq9mzb.js +4 -0
- package/dist/chunks/{grid-view-r22jsqht.js → grid-view-w9nfy203.js} +15 -15
- package/dist/chunks/icons-gb967qc8.js +4 -0
- package/dist/chunks/iframe-host-wsg0bn73.js +4 -0
- package/dist/chunks/image-bake-4afr4y6a.js +4 -0
- package/dist/chunks/image-editor-x7t8jbjn.js +4 -0
- package/dist/chunks/index-f9qr7kzh.js +4 -0
- package/dist/chunks/{index-pxwwf00y.js → index-yjrfeks8.js} +3 -3
- package/dist/chunks/{javascript-pzqryby6.js → javascript-hy2mws8v.js} +3 -3
- package/dist/chunks/{jsonMode-tcnyxyap.js → jsonMode-vj30te2h.js} +6 -6
- package/dist/chunks/jx-validate-ttr5ewf0.js +4 -0
- package/dist/chunks/layers-m49nmwvx.js +4 -0
- package/dist/chunks/layers-panel-hvy2e549.js +4 -0
- package/dist/chunks/lit-html-k2mb9k0r.js +4 -0
- package/dist/chunks/media-library-ae67q4at.js +4 -0
- package/dist/chunks/media-open-yp9jm834.js +4 -0
- package/dist/chunks/media-picker-s41ngdfr.js +4 -0
- package/dist/chunks/{monaco-binding-bqpge64f.js → monaco-binding-a51py33c.js} +3 -3
- package/dist/chunks/monaco-buffer-pa0g6ax7.js +4 -0
- package/dist/chunks/{monaco-setup-1mkab762.js → monaco-setup-g9kterhx.js} +3 -3
- package/dist/chunks/notify-bwehrp7q.js +4 -0
- package/dist/chunks/parse-hee7ne9b.js +4 -0
- package/dist/chunks/platform-kv0k65cy.js +4 -0
- package/dist/chunks/publish-panel-gzfxcm0t.js +4 -0
- package/dist/chunks/publish-quick-w3nxd7q4.js +4 -0
- package/dist/chunks/publish-workspace-dppd82tc.js +4 -0
- package/dist/chunks/quick-search-tdxaam55.js +4 -0
- package/dist/chunks/references-vqyr5s9x.js +4 -0
- package/dist/chunks/right-panel-0yrw4cgz.js +4 -0
- package/dist/chunks/source-control-6fk72me2.js +4 -0
- package/dist/chunks/{studio-0g2sm01j.js → studio-0939t1q1.js} +4 -4
- package/dist/chunks/studio-0gvvqj3h.js +5 -0
- package/dist/chunks/studio-0khd8xtx.js +4 -0
- package/dist/chunks/{studio-rf8d6p9f.js → studio-0w3541dg.js} +6 -6
- package/dist/chunks/{studio-9b7177jq.js → studio-0x6f3nk9.js} +4 -4
- package/dist/chunks/studio-1410q5dw.js +5 -0
- package/dist/chunks/{studio-8qejv8yf.js → studio-15hwqgd8.js} +4 -4
- package/dist/chunks/studio-1ks5d9y4.js +5 -0
- package/dist/chunks/{studio-881z1xrx.js → studio-2ggfzrrd.js} +4 -4
- package/dist/chunks/{studio-jw0q5j6k.js → studio-321ym1tt.js} +4 -4
- package/dist/chunks/studio-3c10v2q1.js +5 -0
- package/dist/chunks/{studio-9rcbvksg.js → studio-3na1r48j.js} +8 -8
- package/dist/chunks/{studio-ggm7zf70.js → studio-3q7mhbgx.js} +5 -5
- package/dist/chunks/studio-3qr08sxq.js +4 -0
- package/dist/chunks/studio-3xk9zdgb.js +5 -0
- package/dist/chunks/studio-423tfdte.js +5 -0
- package/dist/chunks/{studio-brdp85q7.js → studio-45mj4jqe.js} +4 -4
- package/dist/chunks/studio-4f7thq0p.js +5 -0
- package/dist/chunks/{studio-w67vfgy5.js → studio-4fnb5648.js} +4 -4
- package/dist/chunks/studio-4zd6an2b.js +5 -0
- package/dist/chunks/studio-53wy9ek3.js +5 -0
- package/dist/chunks/studio-5n193zqd.js +5 -0
- package/dist/chunks/{studio-x2metr13.js → studio-5tet77de.js} +3 -3
- package/dist/chunks/studio-7rrdbvex.js +5 -0
- package/dist/chunks/studio-8kx6m0rg.js +5 -0
- package/dist/chunks/{studio-va0e63sk.js → studio-90zkgne5.js} +7 -7
- package/dist/chunks/studio-9c6cbq2c.js +4 -0
- package/dist/chunks/studio-9dq7rwc7.js +5 -0
- package/dist/chunks/studio-9qx1ywbv.js +9 -0
- package/dist/chunks/studio-a65kbs2e.js +9 -0
- package/dist/chunks/studio-aam8zt8z.js +5 -0
- package/dist/chunks/{studio-8jb31dcf.js → studio-abh9kfdh.js} +4 -4
- package/dist/chunks/studio-b4e0ftkt.js +5 -0
- package/dist/chunks/studio-b68k0z00.js +5 -0
- package/dist/chunks/{studio-dy2xtt7a.js → studio-bct64zmj.js} +4 -4
- package/dist/chunks/studio-bz0b5brj.js +6 -0
- package/dist/chunks/studio-cfnn70h1.js +5 -0
- package/dist/chunks/studio-daksw8kh.js +5 -0
- package/dist/chunks/{studio-mwnmzx7g.js → studio-e7vjyp04.js} +7 -7
- package/dist/chunks/studio-ea6r1zyr.js +5 -0
- package/dist/chunks/studio-egnjpyde.js +9 -0
- package/dist/chunks/studio-ejrypvmq.js +5 -0
- package/dist/chunks/studio-eqcddvc2.js +928 -0
- package/dist/chunks/studio-f8tyeyzt.js +5 -0
- package/dist/chunks/studio-fj6c905s.js +5 -0
- package/dist/chunks/{studio-hsr3k06v.js → studio-fkrr7kgg.js} +5 -5
- package/dist/chunks/studio-fspe7m9t.js +5 -0
- package/dist/chunks/{studio-4zqfw5ha.js → studio-g0hxtcga.js} +4 -4
- package/dist/chunks/studio-g9brbewz.js +5 -0
- package/dist/chunks/studio-ggx3j7nd.js +5 -0
- package/dist/chunks/{studio-jh6t5qz4.js → studio-hfxttkyr.js} +4 -4
- package/dist/chunks/studio-hh394phe.js +5 -0
- package/dist/chunks/{studio-07k0geap.js → studio-j0zf5x2x.js} +4 -4
- package/dist/chunks/studio-jeb7hth3.js +5 -0
- package/dist/chunks/studio-k0xzrse2.js +64 -0
- package/dist/chunks/studio-k1xqrgm5.js +5 -0
- package/dist/chunks/studio-k2evngrj.js +7 -0
- package/dist/chunks/studio-k3we3kqb.js +5 -0
- package/dist/chunks/{studio-nf0pqpbj.js → studio-n9qba0gx.js} +4 -4
- package/dist/chunks/studio-nf8esxxc.js +5 -0
- package/dist/chunks/studio-nn4f9m9c.js +5 -0
- package/dist/chunks/studio-nskxe9q1.js +6 -0
- package/dist/chunks/{studio-4nkm4404.js → studio-nvcn2ykc.js} +4 -4
- package/dist/chunks/studio-nwynxqtb.js +5 -0
- package/dist/chunks/{studio-f2j9et3h.js → studio-nzemfbkk.js} +8 -8
- package/dist/chunks/studio-p2a03pgm.js +5 -0
- package/dist/chunks/{studio-skynbac3.js → studio-pm17wdc4.js} +4 -4
- package/dist/chunks/{studio-9cp96v90.js → studio-q80fg1d6.js} +7 -7
- package/dist/chunks/studio-r050sez0.js +6 -0
- package/dist/chunks/{studio-sd07xva5.js → studio-s7t4vp1d.js} +5 -5
- package/dist/chunks/studio-s9fng7m8.js +7 -0
- package/dist/chunks/studio-sce30r7b.js +4 -0
- package/dist/chunks/{studio-zxm98z2m.js → studio-stahswgb.js} +6 -6
- package/dist/chunks/studio-swedpd82.js +5 -0
- package/dist/chunks/studio-thz5t120.js +5 -0
- package/dist/chunks/studio-tpxcryf2.js +5 -0
- package/dist/chunks/{studio-tpy3rdxn.js → studio-v5wktj6f.js} +4 -4
- package/dist/chunks/{studio-7vb997ff.js → studio-vpevkfxw.js} +4 -4
- package/dist/chunks/studio-waekn290.js +5 -0
- package/dist/chunks/studio-wfct4h6n.js +5 -0
- package/dist/chunks/studio-wgc54sxf.js +5 -0
- package/dist/chunks/studio-wqfaqw4m.js +5 -0
- package/dist/chunks/studio-wse1rs8t.js +7 -0
- package/dist/chunks/studio-x0gsfr1d.js +16 -0
- package/dist/chunks/studio-xpx91fby.js +6 -0
- package/dist/chunks/studio-y1b86ast.js +5 -0
- package/dist/chunks/studio-yrcrncev.js +5 -0
- package/dist/chunks/{studio-zacvfm31.js → studio-yzpskb0j.js} +4 -4
- package/dist/chunks/studio-zfdbmhp3.js +5 -0
- package/dist/chunks/{tsMode-6vwhy6ty.js → tsMode-jg1dtphj.js} +7 -7
- package/dist/chunks/workspace-9btkc8tk.js +4 -0
- package/dist/chunks/workspace-home-qcc4z5ag.js +4 -0
- package/dist/chunks/{ws-client-zcxkfgmf.js → ws-client-arz2qsy9.js} +3 -3
- package/dist/iframe-entry.js +13 -13
- package/dist/manifest.json +83 -77
- package/dist/studio.js +74 -75
- package/package.json +5 -5
- package/src/browse/library-model.ts +56 -23
- package/src/browse/library-pane.ts +44 -29
- package/src/browse/library-preview.ts +6 -2
- package/src/canvas/asset-refs.ts +13 -4
- package/src/canvas/asset-resolve.ts +54 -11
- package/src/canvas/canvas-render.ts +25 -0
- package/src/canvas/iframe-entry.ts +5 -1
- package/src/canvas/iframe-host.ts +16 -6
- package/src/canvas/iframe-protocol.ts +9 -1
- package/src/commands/context.ts +6 -0
- package/src/commands/live-context.ts +5 -2
- package/src/editor/shortcuts.ts +6 -0
- package/src/files/asset-versions.ts +108 -2
- package/src/files/file-ops.ts +11 -1
- package/src/files/files.ts +111 -48
- package/src/files/media-paths.ts +12 -4
- package/src/files/media-upload.ts +195 -21
- package/src/files/new-file-name.ts +39 -1
- package/src/media/image-bake.ts +164 -0
- package/src/media/image-editor.ts +846 -0
- package/src/media/image-format.ts +262 -0
- package/src/media/image-recipe.ts +651 -0
- package/src/media/media-commands.ts +65 -1
- package/src/media/media-library-model.ts +1 -1
- package/src/media/media-library.ts +175 -15
- package/src/media/media-open.ts +156 -3
- package/src/media/media-pane.ts +8 -4
- package/src/panels/data-grid.ts +0 -4
- package/src/panels/pane-context.ts +6 -1
- package/src/panels/settings-menu.ts +3 -3
- package/src/panels/settings-pane.ts +42 -56
- package/src/panels/tab-strip.ts +12 -3
- package/src/services/ai-import-tools.ts +6 -3
- package/src/services/ai-models.ts +65 -11
- package/src/services/ai-settings.ts +45 -5
- package/src/services/document-assistant.ts +5 -2
- package/src/services/settings/definitions.ts +19 -0
- package/src/settings/contributed-section.ts +16 -7
- package/src/settings/css-vars-editor.ts +4 -4
- package/src/settings/general-settings.ts +1 -4
- package/src/settings/preferences-accounts.ts +2 -2
- package/src/settings/preferences-dialog.ts +4 -2
- package/src/settings/project-sections.ts +1 -1
- package/src/settings/section-registry.ts +5 -4
- package/src/settings/settings-document.ts +33 -24
- package/src/settings/settings-workspace.ts +205 -0
- package/src/shell.ts +31 -6
- package/src/studio.ts +13 -2
- package/src/surfaces/ai-credentials-form.json +59 -5
- package/src/surfaces/ai-credentials-form.ts +15 -2
- package/src/surfaces/commandbar.ts +7 -3
- package/src/surfaces/image-editor.json +1390 -0
- package/src/surfaces/image-editor.ts +272 -0
- package/src/surfaces/media-library.json +129 -1
- package/src/surfaces/media-library.ts +17 -2
- package/src/surfaces/settings-contributed.json +4 -4
- package/src/surfaces/settings-contributed.ts +2 -2
- package/src/surfaces/settings-pane.json +262 -180
- package/src/surfaces/settings-pane.ts +63 -14
- package/src/types.ts +2 -1
- package/src/ui/ai-credentials-form.ts +128 -11
- package/src/ui/regions.ts +5 -5
- package/src/workspace/tab-workspace.ts +5 -3
- package/src/workspace/workspace-home.ts +18 -4
- package/src/workspace/workspace-ids.ts +40 -7
- package/src/workspace/workspace-switch.ts +3 -1
- package/src/workspace/workspace.ts +22 -1
- package/dist/chunks/adapter-xcpsmecx.js +0 -4
- package/dist/chunks/add-new-flow-ptmhmzr9.js +0 -4
- package/dist/chunks/cf-account-picker-z1sgwpwd.js +0 -4
- package/dist/chunks/cf-settings-xqvhxxe9.js +0 -4
- package/dist/chunks/components-whsc81a7.js +0 -4
- package/dist/chunks/content-source-jwtxg78w.js +0 -4
- package/dist/chunks/convert-to-repeater-f92ey3kp.js +0 -4
- package/dist/chunks/editor-zdqdpq1x.js +0 -4
- package/dist/chunks/entry-commands-w8p2fbpt.js +0 -4
- package/dist/chunks/extension-sections-y2p1te2r.js +0 -4
- package/dist/chunks/file-ops-13ab4swj.js +0 -4
- package/dist/chunks/files-zbc8p6v1.js +0 -4
- package/dist/chunks/format-host-zjsbktbx.js +0 -4
- package/dist/chunks/fs-events-t6x0hch6.js +0 -4
- package/dist/chunks/grid-open-fyzdwpkh.js +0 -4
- package/dist/chunks/icons-853n6qt3.js +0 -4
- package/dist/chunks/iframe-host-kbyqc7pt.js +0 -4
- package/dist/chunks/index-6nvj89vb.js +0 -4
- package/dist/chunks/jx-validate-5sp8a4rm.js +0 -4
- package/dist/chunks/layers-panel-qrxj9vr2.js +0 -4
- package/dist/chunks/layers-tpzayggx.js +0 -4
- package/dist/chunks/lit-html-x6nrxsx3.js +0 -4
- package/dist/chunks/media-library-ywppnjrz.js +0 -4
- package/dist/chunks/media-picker-etxgc8qq.js +0 -4
- package/dist/chunks/monaco-buffer-qrnqhk0b.js +0 -4
- package/dist/chunks/notify-kcv38wwe.js +0 -4
- package/dist/chunks/parse-df2kkmhc.js +0 -4
- package/dist/chunks/platform-dtsysmyd.js +0 -4
- package/dist/chunks/publish-panel-evb1rcxt.js +0 -4
- package/dist/chunks/publish-quick-m9vkhahq.js +0 -4
- package/dist/chunks/publish-workspace-ktk9h536.js +0 -4
- package/dist/chunks/quick-search-2f47fxcw.js +0 -4
- package/dist/chunks/references-5w6ajjv7.js +0 -4
- package/dist/chunks/right-panel-kz0a6byf.js +0 -4
- package/dist/chunks/source-control-1phx90zk.js +0 -4
- package/dist/chunks/studio-0hr000ba.js +0 -5
- package/dist/chunks/studio-1wyp7dt5.js +0 -5
- package/dist/chunks/studio-20ap8dds.js +0 -5
- package/dist/chunks/studio-291gqh98.js +0 -5
- package/dist/chunks/studio-37twdaf7.js +0 -64
- package/dist/chunks/studio-4smcnv33.js +0 -5
- package/dist/chunks/studio-56egy6vf.js +0 -5
- package/dist/chunks/studio-5dmgmyw4.js +0 -5
- package/dist/chunks/studio-5e7p1vgd.js +0 -5
- package/dist/chunks/studio-5m0w815d.js +0 -5
- package/dist/chunks/studio-695k95bw.js +0 -5
- package/dist/chunks/studio-6vwvshw8.js +0 -6
- package/dist/chunks/studio-732mw0g8.js +0 -5
- package/dist/chunks/studio-76t560kn.js +0 -6
- package/dist/chunks/studio-7823f0g8.js +0 -5
- package/dist/chunks/studio-8486jn4r.js +0 -5
- package/dist/chunks/studio-848rk4p1.js +0 -5
- package/dist/chunks/studio-8x8616xd.js +0 -5
- package/dist/chunks/studio-a3s6frde.js +0 -4
- package/dist/chunks/studio-ahf3cspq.js +0 -4
- package/dist/chunks/studio-bkmv1h3e.js +0 -9
- package/dist/chunks/studio-cgb97rwv.js +0 -5
- package/dist/chunks/studio-ch8gt8vt.js +0 -5
- package/dist/chunks/studio-cqf1k4qk.js +0 -5
- package/dist/chunks/studio-cvz0e00f.js +0 -5
- package/dist/chunks/studio-dbgv4fv9.js +0 -16
- package/dist/chunks/studio-eesyjg7h.js +0 -9
- package/dist/chunks/studio-et6t8g4m.js +0 -4
- package/dist/chunks/studio-fep7hr66.js +0 -7
- package/dist/chunks/studio-fyf7j57j.js +0 -5
- package/dist/chunks/studio-g5prntab.js +0 -5
- package/dist/chunks/studio-ga77mf07.js +0 -5
- package/dist/chunks/studio-h7gy12h2.js +0 -5
- package/dist/chunks/studio-j933k8nr.js +0 -5
- package/dist/chunks/studio-jgbvqcgh.js +0 -5
- package/dist/chunks/studio-k4xc5g6h.js +0 -5
- package/dist/chunks/studio-p0nf2e1f.js +0 -6
- package/dist/chunks/studio-pd9dsqpq.js +0 -5
- package/dist/chunks/studio-pmzg17fj.js +0 -5
- package/dist/chunks/studio-pzfa2vmb.js +0 -5
- package/dist/chunks/studio-qyb5bebs.js +0 -9
- package/dist/chunks/studio-rt498t2v.js +0 -5
- package/dist/chunks/studio-s0fg2wav.js +0 -5
- package/dist/chunks/studio-t3j8htp7.js +0 -7
- package/dist/chunks/studio-thj81e98.js +0 -7
- package/dist/chunks/studio-tyw7tk0x.js +0 -5
- package/dist/chunks/studio-vcz63n42.js +0 -5
- package/dist/chunks/studio-vffwe9k7.js +0 -5
- package/dist/chunks/studio-vjhej0w5.js +0 -928
- package/dist/chunks/studio-vxjap704.js +0 -5
- package/dist/chunks/studio-xebavr9s.js +0 -5
- package/dist/chunks/studio-yg5n49za.js +0 -5
- package/dist/chunks/studio-yhz5b313.js +0 -5
- package/dist/chunks/studio-zsjpsw08.js +0 -5
- package/dist/chunks/workspace-d65bz1qk.js +0 -4
- package/dist/chunks/workspace-home-vbg9b3jh.js +0 -4
- package/src/settings/settings-dialog.ts +0 -192
- package/src/surfaces/settings-dialog.json +0 -51
- package/src/surfaces/settings-dialog.ts +0 -169
|
@@ -30,7 +30,7 @@ import { resolveI18n } from "@jxsuite/schema/locale";
|
|
|
30
30
|
import { componentRegistry } from "../files/components";
|
|
31
31
|
import { getNodeAtPath, projectState } from "../store";
|
|
32
32
|
import { shell } from "../shell";
|
|
33
|
-
import { activeTab, workspace } from "../workspace/workspace";
|
|
33
|
+
import { activeTab, tabHoldsEditBesideDocument, workspace } from "../workspace/workspace";
|
|
34
34
|
import { primarySelection } from "../tabs/selection";
|
|
35
35
|
import { canRedo, canUndo } from "../tabs/transact";
|
|
36
36
|
import { hasTyping } from "../tabs/typing";
|
|
@@ -140,7 +140,10 @@ export function createLiveContext(sources: LiveContextSources): () => CommandCon
|
|
|
140
140
|
|
|
141
141
|
ctx.document.open = tab !== null;
|
|
142
142
|
// Typing still in a source buffer is unsaved work too, though the document has not seen it.
|
|
143
|
-
|
|
143
|
+
// So is an image edit, which is held beside the document rather than in it (§9.5).
|
|
144
|
+
ctx.document.dirty = tab
|
|
145
|
+
? tab.doc.dirty === true || hasTyping(tab.id) || tabHoldsEditBesideDocument(tab)
|
|
146
|
+
: false;
|
|
144
147
|
ctx.document.mode = tab?.doc.mode ?? "";
|
|
145
148
|
ctx.document.canUndo = tab ? canUndo(tab) : false;
|
|
146
149
|
ctx.document.canRedo = tab ? canRedo(tab) : false;
|
package/src/editor/shortcuts.ts
CHANGED
|
@@ -1006,10 +1006,16 @@ const SELF_SCROLLING_MODES: ReadonlySet<string> = new Set([
|
|
|
1006
1006
|
/* `media` — the Media viewer's own scrolling column (`media/media-pane.ts`). There is no artboard
|
|
1007
1007
|
here either: a wheel over an image is a scroll down the page it sits on, not a zoom. */
|
|
1008
1008
|
"media",
|
|
1009
|
+
/* `image` — the Image editor (`media/image-editor.ts`). Its picture fits the stage and its
|
|
1010
|
+
controls scroll in their own column, so a wheel has no artboard to pan or zoom. */
|
|
1011
|
+
"image",
|
|
1009
1012
|
/* A workspace's home — `workspace/workspace-home.ts`. A centred column with nothing to zoom. */
|
|
1010
1013
|
"media-home",
|
|
1011
1014
|
"preview",
|
|
1012
1015
|
"publish-home",
|
|
1016
|
+
/* Project Settings' home — `settings/settings-workspace.ts`. Two columns, each scrolling its own
|
|
1017
|
+
list, with nothing to zoom. */
|
|
1018
|
+
"settings-home",
|
|
1013
1019
|
/* The Styles workspace's Variables view — `style/variables-view.ts`. An `overflow-y: auto`
|
|
1014
1020
|
column over the stage; without it here the wheel panned an artboard that does not exist. */
|
|
1015
1021
|
"tokens",
|
|
@@ -98,6 +98,22 @@ let floor = 0;
|
|
|
98
98
|
const dirtiedPaths = new Map<string, number>();
|
|
99
99
|
/** The sequence number at which each directory prefix (`dir/`) was last forgotten. */
|
|
100
100
|
const dirtiedPrefixes = new Map<string, number>();
|
|
101
|
+
/**
|
|
102
|
+
* Studio's own repaint keys, by normalized project path: a token minted when Studio has just
|
|
103
|
+
* written a path's bytes itself, which every asset URL builder appends as `"r"` (see
|
|
104
|
+
* {@link noteLocalWrite}). A map of its own, so nothing that reads a backend version changes
|
|
105
|
+
* meaning.
|
|
106
|
+
*/
|
|
107
|
+
const revisions = new Map<string, string>();
|
|
108
|
+
/** Counts the repaint keys minted this session. */
|
|
109
|
+
let revisionCount = 0;
|
|
110
|
+
/**
|
|
111
|
+
* The session the keys are minted in, so a key cannot repeat across a reload: a revalidating cache
|
|
112
|
+
* still holds `?r=1` from the last session, for bytes that may be older than this session's.
|
|
113
|
+
*/
|
|
114
|
+
const REVISION_SESSION = Date.now().toString(36);
|
|
115
|
+
/** The memoized repaint-key snapshot, or null once a key changed under it. */
|
|
116
|
+
let revisionsSnapshot: Readonly<Record<string, string>> | null = null;
|
|
101
117
|
/** The memoized canvas snapshot, or null once a media version changed under it. */
|
|
102
118
|
let snapshot: Readonly<Record<string, string>> | null = null;
|
|
103
119
|
/**
|
|
@@ -119,6 +135,28 @@ function mediaChanged(): void {
|
|
|
119
135
|
assetVersionsEpoch.value += 1;
|
|
120
136
|
}
|
|
121
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Mint a path's repaint key again, if it holds one, reporting whether a media path's did. The key
|
|
140
|
+
* has a URL's cached pixels behind it, so when the bytes change by any other door (a source-control
|
|
141
|
+
* discard, say) the old key would go on showing the picture they replaced.
|
|
142
|
+
*/
|
|
143
|
+
function remintRevision(path: string): boolean {
|
|
144
|
+
if (!revisions.has(path)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
revisionCount += 1;
|
|
148
|
+
revisions.set(path, `${REVISION_SESSION}.${revisionCount.toString(36)}`);
|
|
149
|
+
revisionsSnapshot = null;
|
|
150
|
+
return isVersionedMediaPath(path);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Drop a path's repaint key, if Studio holds one. */
|
|
154
|
+
function dropRevision(path: string): void {
|
|
155
|
+
if (revisions.delete(path)) {
|
|
156
|
+
revisionsSnapshot = null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
122
160
|
/**
|
|
123
161
|
* Begin a listing, returning the sequence number to pass to {@link noteListing} with its answer.
|
|
124
162
|
* Call it BEFORE the request goes out.
|
|
@@ -173,6 +211,9 @@ export function noteListing(entries: readonly DirEntry[], since: number): void {
|
|
|
173
211
|
continue;
|
|
174
212
|
} else {
|
|
175
213
|
versions.set(path, version);
|
|
214
|
+
/* A version the host vouches for supersedes Studio's own repaint key: it names the same new
|
|
215
|
+
bytes, and it is the one a host may cache. */
|
|
216
|
+
dropRevision(path);
|
|
176
217
|
}
|
|
177
218
|
if (isVersionedMediaPath(path)) {
|
|
178
219
|
changed = true;
|
|
@@ -212,6 +253,9 @@ export function forgetVersions(paths: readonly string[], prefixDirs: readonly st
|
|
|
212
253
|
if (versions.delete(path) && isVersionedMediaPath(path)) {
|
|
213
254
|
changed = true;
|
|
214
255
|
}
|
|
256
|
+
if (remintRevision(path)) {
|
|
257
|
+
changed = true;
|
|
258
|
+
}
|
|
215
259
|
}
|
|
216
260
|
for (const raw of prefixDirs) {
|
|
217
261
|
const dir = normalizeProjectPath(raw);
|
|
@@ -220,6 +264,11 @@ export function forgetVersions(paths: readonly string[], prefixDirs: readonly st
|
|
|
220
264
|
}
|
|
221
265
|
const prefix = `${dir}/`;
|
|
222
266
|
dirtiedPrefixes.set(prefix, seq);
|
|
267
|
+
for (const path of revisions.keys()) {
|
|
268
|
+
if (path.startsWith(prefix) && remintRevision(path)) {
|
|
269
|
+
changed = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
223
272
|
for (const path of versions.keys()) {
|
|
224
273
|
if (path.startsWith(prefix)) {
|
|
225
274
|
versions.delete(path);
|
|
@@ -286,9 +335,66 @@ export function forgetAllVersions(): void {
|
|
|
286
335
|
}
|
|
287
336
|
|
|
288
337
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
338
|
+
* Record that Studio itself has just written new bytes to `path`, and return the repaint key minted
|
|
339
|
+
* for it (studio.md §3.4).
|
|
340
|
+
*
|
|
341
|
+
* The version above only exists where a host vouches for one, which is the cloud's repo space
|
|
342
|
+
* alone. Everywhere else an asset URL carries no key at all, so after an in-place overwrite (the
|
|
343
|
+
* Media workspace's Replace, the Image editor's save) the URL is byte-identical before and after,
|
|
344
|
+
* and every `<img>` already decoded in the document keeps the old pixels. This key is the second,
|
|
345
|
+
* clearly separate channel: a monotonic token both asset-URL builders append as `"r"` in BOTH of
|
|
346
|
+
* their branches. It is deliberately not `v`, because §3.4 binds that name to a version the host
|
|
347
|
+
* issued and may answer immutably, and a token Studio minted is neither. It is dropped when a
|
|
348
|
+
* listing vouches for a version, which then names the same bytes.
|
|
349
|
+
*/
|
|
350
|
+
export function noteLocalWrite(path: string): string | undefined {
|
|
351
|
+
const key = normalizeProjectPath(path);
|
|
352
|
+
if (key === "") {
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
revisionCount += 1;
|
|
356
|
+
const token = `${REVISION_SESSION}.${revisionCount.toString(36)}`;
|
|
357
|
+
revisions.set(key, token);
|
|
358
|
+
revisionsSnapshot = null;
|
|
359
|
+
if (isVersionedMediaPath(key)) {
|
|
360
|
+
// The canvas host watches the same epoch, so a frame is reposted the new keys.
|
|
361
|
+
mediaChanged();
|
|
362
|
+
}
|
|
363
|
+
return token;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** The repaint key Studio minted for `path`, or undefined when Studio has not written it. */
|
|
367
|
+
export function localRevisionOf(path: string): string | undefined {
|
|
368
|
+
return revisions.get(normalizeProjectPath(path));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Whether Studio holds a repaint key for any path. */
|
|
372
|
+
export function hasLocalRevisions(): boolean {
|
|
373
|
+
return revisions.size > 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Every repaint key as a plain record, for the canvas iframe. Memoized like
|
|
378
|
+
* {@link assetVersionsSnapshot}, so identity is a cheap "did anything move" test.
|
|
379
|
+
*/
|
|
380
|
+
export function localRevisionsSnapshot(): Readonly<Record<string, string>> {
|
|
381
|
+
if (revisionsSnapshot) {
|
|
382
|
+
return revisionsSnapshot;
|
|
383
|
+
}
|
|
384
|
+
revisionsSnapshot =
|
|
385
|
+
revisions.size === 0 ? EMPTY_SNAPSHOT : Object.freeze(Object.fromEntries(revisions));
|
|
386
|
+
return revisionsSnapshot;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Drop every version, dirt record and repaint key — a project switch, and between tests. A listing
|
|
391
|
+
* begun before the reset is ignored when it lands: it lists the project that was closed.
|
|
291
392
|
*/
|
|
292
393
|
export function resetAssetVersions(): void {
|
|
293
394
|
forgetAllVersions();
|
|
395
|
+
if (revisions.size > 0) {
|
|
396
|
+
revisions.clear();
|
|
397
|
+
revisionsSnapshot = null;
|
|
398
|
+
mediaChanged();
|
|
399
|
+
}
|
|
294
400
|
}
|
package/src/files/file-ops.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import { loadUsages, usageWarning } from "../services/references";
|
|
25
25
|
import { isMediaFile } from "./media-upload";
|
|
26
|
+
import { IMAGE_MODE, imageEditorDirty, saveImage } from "../media/image-editor";
|
|
27
|
+
import { canvasModeOfTab } from "../canvas/canvas-surface";
|
|
26
28
|
import { loadMediaUsages } from "./media-usage";
|
|
27
29
|
import { showConfirmDialog } from "../ui/layers";
|
|
28
30
|
import { locateDocument } from "../services/code-services";
|
|
@@ -312,7 +314,9 @@ function reportSaved(tab: Tab) {
|
|
|
312
314
|
* `source` alternate has the same stub behind it — `canvas-render.ts`'s `sourceContent` falls
|
|
313
315
|
* through to the same `JSON.stringify` when no format class claims the path — so a guard reading
|
|
314
316
|
* `canvasMode === "media"` would have left the one media type that offers a text editor still able
|
|
315
|
-
* to overwrite itself with a placeholder it never showed anyone.
|
|
317
|
+
* to overwrite itself with a placeholder it never showed anyone. It stays keyed on the file now
|
|
318
|
+
* that an image can be edited (§9.5): the Image editor writes through its own door, `saveImage`,
|
|
319
|
+
* and the stub document is still not something any mode may serialize.
|
|
316
320
|
*/
|
|
317
321
|
function hasSerializableDocument(tab: Tab): boolean {
|
|
318
322
|
return !isMediaFile(tab.documentPath ?? "");
|
|
@@ -373,6 +377,12 @@ export async function saveFile(tab: Tab | null = activeTab.value): Promise<boole
|
|
|
373
377
|
from one that worked. `warn` rather than `error` — nothing is broken and there is nothing to fix;
|
|
374
378
|
the file is simply not the kind of thing Save writes. */
|
|
375
379
|
if (!hasSerializableDocument(tab)) {
|
|
380
|
+
/* An image with an edit has a write of its own (§9.5), and ⌘S is the same key for the same
|
|
381
|
+
intent there. Asked by the edit as well as the mode, so the close prompt's Save writes a
|
|
382
|
+
pending crop even from the read-only viewer it was left behind in. */
|
|
383
|
+
if (canvasModeOfTab(tab) === IMAGE_MODE || imageEditorDirty(tab)) {
|
|
384
|
+
return saveImage(tab);
|
|
385
|
+
}
|
|
376
386
|
notify.warn(
|
|
377
387
|
`${tab.documentPath ?? "This file"} is a media file, so there is no document for Save to write.`,
|
|
378
388
|
atTabPath(tab, {
|
package/src/files/files.ts
CHANGED
|
@@ -75,6 +75,7 @@ import {
|
|
|
75
75
|
} from "../workspace/workspace";
|
|
76
76
|
import { openCsvGridTab, openPagesGrid } from "../grid/grid-open";
|
|
77
77
|
import { isViewableMedia, openMediaTab } from "../media/media-open";
|
|
78
|
+
import { imageFileChanged } from "../media/image-editor";
|
|
78
79
|
import { activeRegistry } from "../commands/active-registry";
|
|
79
80
|
import { runActiveReported, runReported } from "../commands/run-reported";
|
|
80
81
|
import { collectionOfPath } from "../content/entry-model";
|
|
@@ -99,6 +100,7 @@ import {
|
|
|
99
100
|
composeNewFileName,
|
|
100
101
|
extensionOfName,
|
|
101
102
|
OTHER_FORMAT,
|
|
103
|
+
renameRefusal,
|
|
102
104
|
validateNewFileName,
|
|
103
105
|
} from "./new-file-name";
|
|
104
106
|
import type { FormatChoice, NewFileNameCheck } from "./new-file-name";
|
|
@@ -1510,47 +1512,44 @@ function releaseFileTreeDnD(): void {
|
|
|
1510
1512
|
* @param {() => void} renderLeftPanel
|
|
1511
1513
|
*/
|
|
1512
1514
|
async function moveFileEntry(oldPath: string, newPath: string, renderLeftPanel: () => void) {
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
+
const name = newPath.split("/").pop() ?? newPath;
|
|
1516
|
+
const newParent = parentDir(newPath);
|
|
1517
|
+
const failure = { path: oldPath, source: "Files" };
|
|
1518
|
+
let listed: DirEntry[];
|
|
1515
1519
|
try {
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
//
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1520
|
+
listed = await getPlatform().listDirectory(newParent);
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
// A row was dropped on, so the folder exists: a failed listing says nothing about what is in it.
|
|
1523
|
+
notify.error(`Could not move ${oldPath}.`, {
|
|
1524
|
+
...failure,
|
|
1525
|
+
detail: `The folder could not be read to check for ${name}: ${errorMessage(error)}`,
|
|
1526
|
+
});
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
/* A move is the same filesystem rename a Rename is, so it replaces its destination in silence.
|
|
1530
|
+
Without regard to case, as `renameRefusal` compares: a case-insensitive disk takes one for the
|
|
1531
|
+
other. A dragged dotfile needs no refusal of its own, since only a host that lists dotfiles can
|
|
1532
|
+
show a row to drag. */
|
|
1533
|
+
const lower = name.toLowerCase();
|
|
1534
|
+
const occupant = listed.find((entry) => entry.name.toLowerCase() === lower);
|
|
1535
|
+
if (occupant !== undefined) {
|
|
1536
|
+
const where = newParent === "." ? "The project root" : `${newParent}/`;
|
|
1537
|
+
notify.error(`Could not move ${oldPath}.`, {
|
|
1538
|
+
...failure,
|
|
1539
|
+
detail: `${where} already has ${occupant.name}, and a move would replace it.`,
|
|
1540
|
+
});
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
try {
|
|
1544
|
+
const report = await commitRename(oldPath, newPath);
|
|
1540
1545
|
// Auto-expand target directory
|
|
1541
1546
|
if (newParent !== ".") {
|
|
1542
1547
|
requireProjectState().expanded.add(newParent);
|
|
1543
1548
|
}
|
|
1544
|
-
|
|
1545
|
-
reloadRewrittenTabs(report, newPath);
|
|
1546
1549
|
renderLeftPanel();
|
|
1547
1550
|
notifyMoveOutcome(`Moved to ${newPath}`, report, newPath);
|
|
1548
1551
|
} catch (error) {
|
|
1549
|
-
notify.error(`Could not move ${oldPath}.`, {
|
|
1550
|
-
detail: errorMessage(error),
|
|
1551
|
-
path: oldPath,
|
|
1552
|
-
source: "Files",
|
|
1553
|
-
});
|
|
1552
|
+
notify.error(`Could not move ${oldPath}.`, { ...failure, detail: errorMessage(error) });
|
|
1554
1553
|
}
|
|
1555
1554
|
}
|
|
1556
1555
|
|
|
@@ -2052,14 +2051,21 @@ async function createNewFile(dirPath: string, renderLeftPanel: () => void) {
|
|
|
2052
2051
|
* is typed rather than after it is confirmed — the refactor pass is about to rewrite every one of
|
|
2053
2052
|
* those references, and silently doing that much work was the previous behaviour.
|
|
2054
2053
|
*
|
|
2055
|
-
* @param
|
|
2054
|
+
* @param currentName The entry's name, which the field opens on.
|
|
2055
|
+
* @param path The entry being renamed.
|
|
2056
|
+
* @param taken The names in its folder, which the field refuses.
|
|
2057
|
+
* @returns The new name, or `null` when the dialog was dismissed.
|
|
2056
2058
|
*/
|
|
2057
|
-
async function showRenameFileDialog(
|
|
2059
|
+
async function showRenameFileDialog(
|
|
2060
|
+
currentName: string,
|
|
2061
|
+
path: string,
|
|
2062
|
+
taken: ReadonlySet<string>,
|
|
2063
|
+
): Promise<string | null> {
|
|
2058
2064
|
const message = await renamePromptMessage(path);
|
|
2059
2065
|
const promptOptions: PromptDialogOptions = {
|
|
2060
2066
|
confirmLabel: "Rename",
|
|
2061
2067
|
select: "stem",
|
|
2062
|
-
validate: (v) => (v
|
|
2068
|
+
validate: (v) => renameRefusal(path, v, taken),
|
|
2063
2069
|
value: currentName,
|
|
2064
2070
|
};
|
|
2065
2071
|
if (message !== undefined) {
|
|
@@ -2069,7 +2075,7 @@ async function showRenameFileDialog(currentName: string, path: string): Promise<
|
|
|
2069
2075
|
}
|
|
2070
2076
|
|
|
2071
2077
|
/** Build the status-bar message for a rename, summarising any reference/tag rewrites. */
|
|
2072
|
-
function renameStatus(newName: string, report: RenameResult): string {
|
|
2078
|
+
export function renameStatus(newName: string, report: RenameResult): string {
|
|
2073
2079
|
const refs = report.references;
|
|
2074
2080
|
const tagNote = report.tag ? `; tag → <${report.tag.to}> (${report.tag.refsUpdated})` : "";
|
|
2075
2081
|
if (refs && refs.refsUpdated > 0) {
|
|
@@ -2154,30 +2160,85 @@ export async function settleRename(from: string, to: string, report: RenameResul
|
|
|
2154
2160
|
if (toDir !== fromDir) {
|
|
2155
2161
|
await loadDirectory(toDir);
|
|
2156
2162
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2163
|
+
const state = requireProjectState();
|
|
2164
|
+
if (state.selectedPath === from) {
|
|
2165
|
+
state.selectedPath = to;
|
|
2166
|
+
} else if (state.selectedPath?.startsWith(`${from}/`)) {
|
|
2167
|
+
state.selectedPath = to + state.selectedPath.slice(from.length);
|
|
2159
2168
|
}
|
|
2160
2169
|
reloadRewrittenTabs(report, to);
|
|
2161
2170
|
}
|
|
2162
2171
|
|
|
2172
|
+
/**
|
|
2173
|
+
* Rename a file on the backend and catch the app up: the one door a rename goes through, whichever
|
|
2174
|
+
* surface asked for the name (the Files panel, the Media workspace).
|
|
2175
|
+
*
|
|
2176
|
+
* The tab is re-keyed rather than reopened, so whatever it holds (unsaved edits, a pending image
|
|
2177
|
+
* crop) stays with the file. Throws what the backend threw; what to say about it is the caller's.
|
|
2178
|
+
*
|
|
2179
|
+
* @param from - The path before the rename.
|
|
2180
|
+
* @param to - The path after it.
|
|
2181
|
+
* @returns What the refactor pass rewrote.
|
|
2182
|
+
*/
|
|
2183
|
+
export async function commitRename(from: string, to: string): Promise<RenameResult> {
|
|
2184
|
+
markLocalMutation(from, to);
|
|
2185
|
+
const report = await getPlatform().renameFile(from, to);
|
|
2186
|
+
// A copy of the keys, since a re-key edits the map being walked. `from` may be a folder.
|
|
2187
|
+
const openIds = [...workspace.tabs.keys()];
|
|
2188
|
+
for (const id of openIds) {
|
|
2189
|
+
if (id === from || id.startsWith(`${from}/`)) {
|
|
2190
|
+
const moved = to + id.slice(from.length);
|
|
2191
|
+
renameTab(id, moved, moved);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
await settleRename(from, to, report);
|
|
2195
|
+
return report;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
/**
|
|
2199
|
+
* The names beside the entry at `path`, for {@link renameRefusal} to judge a new name against.
|
|
2200
|
+
*
|
|
2201
|
+
* Not `listTakenNames`, which answers empty for a folder that may not exist yet. This one holds the
|
|
2202
|
+
* entry, so a failed listing says nothing about what a rename would overwrite, and it makes the
|
|
2203
|
+
* rename refuse rather than guess.
|
|
2204
|
+
*
|
|
2205
|
+
* @param path The entry about to be renamed.
|
|
2206
|
+
* @param source Names the flow in the Problem.
|
|
2207
|
+
* @returns The names, or `null` when the folder could not be read; the reason has been reported.
|
|
2208
|
+
*/
|
|
2209
|
+
export async function listRenameSiblings(
|
|
2210
|
+
path: string,
|
|
2211
|
+
source: string,
|
|
2212
|
+
): Promise<Set<string> | null> {
|
|
2213
|
+
try {
|
|
2214
|
+
const listed = await getPlatform().listDirectory(parentDirOf(path));
|
|
2215
|
+
return new Set(listed.map((entry) => entry.name));
|
|
2216
|
+
} catch (error) {
|
|
2217
|
+
notify.error(`Could not rename ${path.split("/").pop() ?? path}.`, {
|
|
2218
|
+
detail: `The folder could not be read to check the new name: ${errorMessage(error)}`,
|
|
2219
|
+
path,
|
|
2220
|
+
source,
|
|
2221
|
+
});
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2163
2226
|
async function renameFile(
|
|
2164
2227
|
entry: { name: string; path: string; type: string },
|
|
2165
2228
|
renderLeftPanel: () => void,
|
|
2166
2229
|
) {
|
|
2167
|
-
const
|
|
2230
|
+
const parentDirPath = parentDirOf(entry.path);
|
|
2231
|
+
const taken = await listRenameSiblings(entry.path, "Files");
|
|
2232
|
+
if (taken === null) {
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const newName = await showRenameFileDialog(entry.name, entry.path, taken);
|
|
2168
2236
|
if (!newName || newName === entry.name) {
|
|
2169
2237
|
return;
|
|
2170
2238
|
}
|
|
2171
|
-
const parentDirPath = parentDirOf(entry.path);
|
|
2172
2239
|
const newPath = parentDirPath === "." ? newName : `${parentDirPath}/${newName}`;
|
|
2173
|
-
markLocalMutation(entry.path, newPath);
|
|
2174
2240
|
try {
|
|
2175
|
-
const
|
|
2176
|
-
const report = await platform.renameFile(entry.path, newPath);
|
|
2177
|
-
if (workspace.tabs.has(entry.path)) {
|
|
2178
|
-
renameTab(entry.path, newPath, newPath);
|
|
2179
|
-
}
|
|
2180
|
-
await settleRename(entry.path, newPath, report);
|
|
2241
|
+
const report = await commitRename(entry.path, newPath);
|
|
2181
2242
|
renderLeftPanel();
|
|
2182
2243
|
notifyMoveOutcome(renameStatus(newName, report), report, newPath);
|
|
2183
2244
|
} catch (error) {
|
|
@@ -2446,6 +2507,8 @@ export function reloadCleanTab(path: string): void {
|
|
|
2446
2507
|
if (isCollabPath(path)) {
|
|
2447
2508
|
return;
|
|
2448
2509
|
}
|
|
2510
|
+
// An image's editor holds the file's bytes rather than a document, and reads them again itself.
|
|
2511
|
+
imageFileChanged(path);
|
|
2449
2512
|
for (const [, tab] of workspace.tabs.entries()) {
|
|
2450
2513
|
if (tab.documentPath === path && !tab.doc.dirty) {
|
|
2451
2514
|
void reloadFileInTab(path);
|
package/src/files/media-paths.ts
CHANGED
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
|
|
39
39
|
import { encodeProjectPath, normalizeProjectPath, PUBLIC_DIR } from "@jxsuite/schema/asset-paths";
|
|
40
40
|
import { contentMountFor, hostAssetDeclarations, projectLocales } from "../canvas/asset-refs";
|
|
41
|
-
import { withVersion } from "../canvas/asset-resolve";
|
|
42
|
-
import { versionOf } from "./asset-versions";
|
|
41
|
+
import { withRevision, withVersion } from "../canvas/asset-resolve";
|
|
42
|
+
import { localRevisionOf, versionOf } from "./asset-versions";
|
|
43
43
|
import { loopbackAssetSrc } from "../canvas/canvas-origin";
|
|
44
44
|
import { projectState } from "../store";
|
|
45
45
|
import type { AssetMount } from "@jxsuite/schema/asset-paths";
|
|
@@ -134,10 +134,18 @@ export function previewFileSrc(path: string): string {
|
|
|
134
134
|
host serves the FILE, at its own path, under the declared base — versioned when a listing
|
|
135
135
|
vouched for its bytes, so a host that recognizes the version can let the browser cache it. */
|
|
136
136
|
const { space, fileBaseUrl } = hostAssetDeclarations();
|
|
137
|
+
/* Studio's own repaint key rides in BOTH branches (§3.4). The site branch is the one that needs
|
|
138
|
+
it most: it carries no version at all, so after an in-place overwrite its URL would be
|
|
139
|
+
byte-identical and every thumbnail already decoded would keep the old pixels. */
|
|
140
|
+
const revision = localRevisionOf(normalized);
|
|
137
141
|
if (space === "repo") {
|
|
138
|
-
return
|
|
142
|
+
return withRevision(
|
|
143
|
+
withVersion(`${fileBaseUrl}${encodeProjectPath(normalized)}`, versionOf(normalized)),
|
|
144
|
+
revision,
|
|
145
|
+
);
|
|
139
146
|
}
|
|
140
147
|
/* Encoded per segment for the `src` only. `mediaSiteUrl` is also what an author's document is
|
|
141
148
|
given, and stays as typed; a `#` or `?` in a filename would cut this URL short. */
|
|
142
|
-
|
|
149
|
+
const encoded = encodeProjectPath(mediaSiteUrl(normalized).slice(1));
|
|
150
|
+
return withRevision(loopbackAssetSrc(`/${encoded}`), revision);
|
|
143
151
|
}
|