fundus 0.13.0 → 0.15.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/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css +1 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.br +0 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{dNO-9VOT.js → -8MKoIIe.js} +1 -1
- package/build/client/_app/immutable/chunks/-8MKoIIe.js.br +0 -0
- package/build/client/_app/immutable/chunks/-8MKoIIe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BmOjTxFk.js +1 -0
- package/build/client/_app/immutable/chunks/BmOjTxFk.js.br +0 -0
- package/build/client/_app/immutable/chunks/BmOjTxFk.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D1wFzWdi.js +108 -0
- package/build/client/_app/immutable/chunks/D1wFzWdi.js.br +0 -0
- package/build/client/_app/immutable/chunks/D1wFzWdi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DY9QjkXW.js +1 -0
- package/build/client/_app/immutable/chunks/DY9QjkXW.js.br +0 -0
- package/build/client/_app/immutable/chunks/DY9QjkXW.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DmQb0SN8.js +2 -0
- package/build/client/_app/immutable/chunks/DmQb0SN8.js.br +0 -0
- package/build/client/_app/immutable/chunks/DmQb0SN8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/N1X6Qzcl.js +1 -0
- package/build/client/_app/immutable/chunks/N1X6Qzcl.js.br +0 -0
- package/build/client/_app/immutable/chunks/N1X6Qzcl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/VX-Uq_b9.js +1 -0
- package/build/client/_app/immutable/chunks/VX-Uq_b9.js.br +1 -0
- package/build/client/_app/immutable/chunks/VX-Uq_b9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Z8rcWjXZ.js +3 -0
- package/build/client/_app/immutable/chunks/Z8rcWjXZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/Z8rcWjXZ.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CJpgt8H5.js +2 -0
- package/build/client/_app/immutable/entry/app.CJpgt8H5.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CJpgt8H5.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DToPJitz.js +1 -0
- package/build/client/_app/immutable/entry/start.DToPJitz.js.br +1 -0
- package/build/client/_app/immutable/entry/start.DToPJitz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js +1 -0
- package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.D6OhgXcC.js +1 -0
- package/build/client/_app/immutable/nodes/1.D6OhgXcC.js.br +3 -0
- package/build/client/_app/immutable/nodes/1.D6OhgXcC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.sBDjyIQu.js +3 -0
- package/build/client/_app/immutable/nodes/2.sBDjyIQu.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.sBDjyIQu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DDt73kMC.js +1 -0
- package/build/client/_app/immutable/nodes/3.DDt73kMC.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DDt73kMC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.FsXXhVSP.js +2 -0
- package/build/client/_app/immutable/nodes/4.FsXXhVSP.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.FsXXhVSP.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{context.js-6YdbScn_.js → context.js-CVuTSPjH.js} +2 -2
- package/build/server/chunks/chunks/{context.js-6YdbScn_.js.map → context.js-CVuTSPjH.js.map} +1 -1
- package/build/server/chunks/chunks/{contract.js-BHQbpsFk.js → contract.js-itFN0Gon.js} +6 -2
- package/build/server/chunks/chunks/contract.js-itFN0Gon.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-C5HMgZGL.js → exports.js-VRV1_yEb.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-C5HMgZGL.js.map → exports.js-VRV1_yEb.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-dbT0ifQ9.js → http.js-ChQRRKfZ.js} +2 -2
- package/build/server/chunks/chunks/{http.js-dbT0ifQ9.js.map → http.js-ChQRRKfZ.js.map} +1 -1
- package/build/server/chunks/chunks/{internal.js-BnaiVrdk.js → internal.js-qXxDe7VN.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-BnaiVrdk.js.map → internal.js-qXxDe7VN.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{shared.js-NjzxopNt.js → shared.js-DnEOHbgT.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-NjzxopNt.js.map → shared.js-DnEOHbgT.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-ZvXGkEDJ.js → utils.js-lNH0hKSx.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-ZvXGkEDJ.js.map → utils.js-lNH0hKSx.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BaMGnbi8.js → _server.ts.js-z988EDp2.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BaMGnbi8.js.map → _server.ts.js-z988EDp2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-B6YhmqYd.js → _server.ts.js-C68ru6gS.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-B6YhmqYd.js.map → _server.ts.js-C68ru6gS.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-qtGGwjZ4.js → _server.ts.js-C0tk6RFk.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-qtGGwjZ4.js.map → _server.ts.js-C0tk6RFk.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-98vjovDS.js → _server.ts.js-I03oZuo8.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-98vjovDS.js.map → _server.ts.js-I03oZuo8.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CCzQEr--.js → _server.ts.js-BsQB8YDq.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CCzQEr--.js.map → _server.ts.js-BsQB8YDq.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ca7G_5IJ.js → _server.ts.js-CJuFE6Fb.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ca7G_5IJ.js.map → _server.ts.js-CJuFE6Fb.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DRw4ICSn.js → _server.ts.js-DuOZRRiA.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DRw4ICSn.js.map → _server.ts.js-DuOZRRiA.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-Ai46jTgw.js → _server.ts.js-DGzYpQbv.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-Ai46jTgw.js.map → _server.ts.js-DGzYpQbv.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-NQKYbtiU.js → _server.ts.js-B045QoCA.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-NQKYbtiU.js.map → _server.ts.js-B045QoCA.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-_vJ2Npqd.js → _server.ts.js-HUkVdz19.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-_vJ2Npqd.js.map → _server.ts.js-HUkVdz19.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DIn5srjK.js → _server.ts.js-ry509mom.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DIn5srjK.js.map → _server.ts.js-ry509mom.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-CqFLRTP-.js → _server.ts.js-DU0J7U25.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-CqFLRTP-.js.map → _server.ts.js-DU0J7U25.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BYn_v3VS.js → _server.ts.js-AZIV2PIn.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BYn_v3VS.js.map → _server.ts.js-AZIV2PIn.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-sxVzkX1S.js → _server.ts.js-CDL0hCfM.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-sxVzkX1S.js.map → _server.ts.js-CDL0hCfM.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-E3B6VyAs.js → _server.ts.js-B13c17fq.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-E3B6VyAs.js.map → _server.ts.js-B13c17fq.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-ClgS9ghS.js → _server.ts.js-BCxjyAGC.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-ClgS9ghS.js.map → _server.ts.js-BCxjyAGC.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BPUzWMNL.js → _server.ts.js-BveeMZc2.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BPUzWMNL.js.map → _server.ts.js-BveeMZc2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-_G6pURnx.js → _server.ts.js-BZjTd9a0.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-_G6pURnx.js.map → _server.ts.js-BZjTd9a0.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-as3GcjaI.js → _server.ts.js-BbTfPvC9.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-as3GcjaI.js.map → _server.ts.js-BbTfPvC9.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CR58wymO.js → _server.ts.js-8VPJMLBk.js} +3 -3
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CR58wymO.js.map → _server.ts.js-8VPJMLBk.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Do2LOf04.js → _server.ts.js-BqR2J2sB.js} +4 -4
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Do2LOf04.js.map → _server.ts.js-BqR2J2sB.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Ckp6_SaE.js → _server.ts.js-DbA28dCY.js} +4 -4
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Ckp6_SaE.js.map → _server.ts.js-DbA28dCY.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-CL1tg0CT.js → hooks.server.js-9PBc_3Jh.js} +3 -3
- package/build/server/chunks/entries/{hooks.server.js-CL1tg0CT.js.map → hooks.server.js-9PBc_3Jh.js.map} +1 -1
- package/build/server/chunks/{handler-Zr8T8kxt.js → handler-BHt7eOOX.js} +5 -5
- package/build/server/chunks/{handler-Zr8T8kxt.js.map → handler-BHt7eOOX.js.map} +1 -1
- package/build/server/chunks/{index.js-CylGYR9j.js → index.js-DcVaL-Ja.js} +5 -5
- package/build/server/chunks/{index.js-CylGYR9j.js.map → index.js-DcVaL-Ja.js.map} +1 -1
- package/build/server/chunks/{manifest.js-BLhgBmWN.js → manifest.js-DgjSU4wg.js} +29 -29
- package/build/server/chunks/{manifest.js-BLhgBmWN.js.map → manifest.js-DgjSU4wg.js.map} +1 -1
- package/build/server/chunks/nodes/{0.js-S7CogBDa.js → 0.js-BgTB-_Zt.js} +2 -2
- package/build/server/chunks/nodes/{0.js-S7CogBDa.js.map → 0.js-BgTB-_Zt.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-DNabcIPP.js +7 -0
- package/build/server/chunks/nodes/{1.js-CmtmxFOc.js.map → 1.js-DNabcIPP.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-CcVBvLSR.js +7 -0
- package/build/server/chunks/nodes/{2.js-LCLS1c6Z.js.map → 2.js-CcVBvLSR.js.map} +1 -1
- package/build/server/chunks/nodes/3.js-DreG5rig.js +7 -0
- package/build/server/chunks/nodes/{3.js-Bng5WtIG.js.map → 3.js-DreG5rig.js.map} +1 -1
- package/build/server/chunks/nodes/4.js-BE7ZG9vA.js +7 -0
- package/build/server/chunks/nodes/{4.js-BQHl-pD5.js.map → 4.js-BE7ZG9vA.js.map} +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/manifest-entry.d.ts +14 -5
- package/dist/runtime/components/ChromaKeyVideo.svelte +3 -0
- package/dist/runtime/components/Image.svelte +8 -6
- package/dist/runtime/components/Image.svelte.d.ts +2 -3
- package/dist/runtime/components/Slice.svelte +52 -22
- package/dist/runtime/components/Video.svelte +3 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/kind-handlers.d.ts +22 -10
- package/dist/runtime/kind-handlers.js +58 -21
- package/dist/runtime/logging.d.ts +34 -0
- package/dist/runtime/logging.js +27 -0
- package/dist/runtime/manifest.d.ts +16 -5
- package/dist/runtime/manifest.js +19 -8
- package/dist/runtime/preload-registry.d.ts +20 -1
- package/dist/runtime/preload-registry.js +23 -2
- package/dist/runtime/slice-source-lifecycle.d.ts +11 -2
- package/dist/runtime/slice-source-lifecycle.js +21 -2
- package/dist/server/codegen.js +5 -1
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/AssetSourceMenu.C5W2HI4L.css +0 -1
- package/build/client/_app/immutable/assets/AssetSourceMenu.C5W2HI4L.css.br +0 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.C5W2HI4L.css.gz +0 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js +0 -1
- package/build/client/_app/immutable/chunks/B9HdDE5n.js.br +0 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cpo53v_d.js +0 -1
- package/build/client/_app/immutable/chunks/Cpo53v_d.js.br +0 -3
- package/build/client/_app/immutable/chunks/Cpo53v_d.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Q50krHyn.js +0 -2
- package/build/client/_app/immutable/chunks/Q50krHyn.js.br +0 -0
- package/build/client/_app/immutable/chunks/Q50krHyn.js.gz +0 -0
- package/build/client/_app/immutable/chunks/SCZFzB6X.js +0 -108
- package/build/client/_app/immutable/chunks/SCZFzB6X.js.br +0 -0
- package/build/client/_app/immutable/chunks/SCZFzB6X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/YpiA5Xry.js +0 -3
- package/build/client/_app/immutable/chunks/YpiA5Xry.js.br +0 -0
- package/build/client/_app/immutable/chunks/YpiA5Xry.js.gz +0 -0
- package/build/client/_app/immutable/chunks/dNO-9VOT.js.br +0 -0
- package/build/client/_app/immutable/chunks/dNO-9VOT.js.gz +0 -0
- package/build/client/_app/immutable/chunks/hUfV2O0k.js +0 -1
- package/build/client/_app/immutable/chunks/hUfV2O0k.js.br +0 -0
- package/build/client/_app/immutable/chunks/hUfV2O0k.js.gz +0 -0
- package/build/client/_app/immutable/chunks/rkm4Gik6.js +0 -1
- package/build/client/_app/immutable/chunks/rkm4Gik6.js.br +0 -0
- package/build/client/_app/immutable/chunks/rkm4Gik6.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.z-Xkr99w.js +0 -2
- package/build/client/_app/immutable/entry/app.z-Xkr99w.js.br +0 -0
- package/build/client/_app/immutable/entry/app.z-Xkr99w.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CHlFBfLa.js +0 -1
- package/build/client/_app/immutable/entry/start.CHlFBfLa.js.br +0 -0
- package/build/client/_app/immutable/entry/start.CHlFBfLa.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DApz1CKw.js +0 -1
- package/build/client/_app/immutable/nodes/0.DApz1CKw.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DApz1CKw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.CsFr-PaL.js +0 -1
- package/build/client/_app/immutable/nodes/1.CsFr-PaL.js.br +0 -3
- package/build/client/_app/immutable/nodes/1.CsFr-PaL.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.ba45AZgx.js +0 -3
- package/build/client/_app/immutable/nodes/2.ba45AZgx.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.ba45AZgx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.P9WOEhGt.js +0 -1
- package/build/client/_app/immutable/nodes/3.P9WOEhGt.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.P9WOEhGt.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CXvQQa7K.js +0 -2
- package/build/client/_app/immutable/nodes/4.CXvQQa7K.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CXvQQa7K.js.gz +0 -0
- package/build/server/chunks/chunks/contract.js-BHQbpsFk.js.map +0 -1
- package/build/server/chunks/nodes/1.js-CmtmxFOc.js +0 -7
- package/build/server/chunks/nodes/2.js-LCLS1c6Z.js +0 -7
- package/build/server/chunks/nodes/3.js-Bng5WtIG.js +0 -7
- package/build/server/chunks/nodes/4.js-BQHl-pD5.js +0 -7
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Slice renderer using the entry-based API (slicing arrives inline on the
|
|
3
3
|
`SliceManifestEntry`). Owns the source lifecycle (load, decode, latest-wins
|
|
4
|
-
commit)
|
|
5
|
-
into a backing canvas. A ResizeObserver
|
|
6
|
-
`resize` and DPR changes (browser zoom,
|
|
7
|
-
too, and a reactive effect redraws when the
|
|
8
|
-
works live inside the editor.
|
|
4
|
+
commit). A one-slice snapshot renders as a regular image; sliced snapshots
|
|
5
|
+
draw each cell with one `drawImage` into a backing canvas. A ResizeObserver
|
|
6
|
+
triggers a redraw on resize; window `resize` and DPR changes (browser zoom,
|
|
7
|
+
moving between displays) redraw too, and a reactive effect redraws when the
|
|
8
|
+
entry changes — so it also works live inside the editor.
|
|
9
9
|
|
|
10
10
|
The backing-store ratio is the devicePixelRatio FLOORED to an integer and
|
|
11
11
|
capped at `maxDpr` (default 2), never the exact ratio. Cell edges are
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
upscale, where slice boundaries are plain content. Floor + cap
|
|
18
18
|
deliberately favor seam-freeness and backing memory over sharpness.
|
|
19
19
|
|
|
20
|
-
The measured element is the root div, not
|
|
21
|
-
canvas is enlarged by the overdraw's destination size and
|
|
22
|
-
(see `computeCanvasPaintLayout`), so the baked shadow/glow
|
|
23
|
-
the layout box while the root (and therefore the layout)
|
|
24
|
-
size.
|
|
20
|
+
The measured element is the root div, not its painted child: with overdraw
|
|
21
|
+
the canvas or image is enlarged by the overdraw's destination size and
|
|
22
|
+
offset negatively (see `computeCanvasPaintLayout`), so the baked shadow/glow
|
|
23
|
+
paints outside the layout box while the root (and therefore the layout)
|
|
24
|
+
keeps the box size.
|
|
25
25
|
-->
|
|
26
26
|
<script lang="ts">
|
|
27
27
|
import { onDestroy } from 'svelte';
|
|
@@ -32,7 +32,12 @@
|
|
|
32
32
|
type CanvasPaintLayout
|
|
33
33
|
} from '../geometry.js';
|
|
34
34
|
import { observeBorderBoxSize } from '../observe-border-box-size.js';
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
SliceSourceLifecycle,
|
|
37
|
+
isDetachedSliceSource,
|
|
38
|
+
type LoadedSliceSnapshot
|
|
39
|
+
} from '../slice-source-lifecycle.js';
|
|
40
|
+
import { logRender } from '../logging.js';
|
|
36
41
|
|
|
37
42
|
interface Props {
|
|
38
43
|
src: SliceManifestEntry;
|
|
@@ -57,6 +62,8 @@
|
|
|
57
62
|
sourceLifecycle.request(src);
|
|
58
63
|
});
|
|
59
64
|
|
|
65
|
+
$effect(() => logRender(src));
|
|
66
|
+
|
|
60
67
|
onDestroy(() => sourceLifecycle.dispose());
|
|
61
68
|
|
|
62
69
|
let root = $state<HTMLDivElement>();
|
|
@@ -131,12 +138,18 @@
|
|
|
131
138
|
|
|
132
139
|
function draw(current: LoadedSliceSnapshot | null, currentLayout: CanvasPaintLayout): void {
|
|
133
140
|
const element = canvas;
|
|
134
|
-
if (!element) return;
|
|
141
|
+
if (!element || current?.entry.slicing.mode === 'one') return;
|
|
135
142
|
if (!current || boxWidth === 0 || boxHeight === 0) {
|
|
136
143
|
element.width = 0;
|
|
137
144
|
element.height = 0;
|
|
138
145
|
return;
|
|
139
146
|
}
|
|
147
|
+
if (isDetachedSliceSource(current.image)) {
|
|
148
|
+
// A preload release closed the retained bitmap while this slice stayed
|
|
149
|
+
// mounted. Keep the last painted pixels and reload asynchronously.
|
|
150
|
+
sourceLifecycle.request(src);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
140
153
|
|
|
141
154
|
element.width = currentLayout.backingWidth;
|
|
142
155
|
element.height = currentLayout.backingHeight;
|
|
@@ -185,16 +198,27 @@
|
|
|
185
198
|
<div
|
|
186
199
|
bind:this={root}
|
|
187
200
|
class="fundus-slice {className}"
|
|
188
|
-
role={alt ? 'img' : undefined}
|
|
189
|
-
aria-label={alt || undefined}
|
|
201
|
+
role={snapshot?.entry.slicing.mode === 'one' ? undefined : alt ? 'img' : undefined}
|
|
202
|
+
aria-label={snapshot?.entry.slicing.mode === 'one' ? undefined : alt || undefined}
|
|
190
203
|
>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
{#if snapshot?.entry.slicing.mode === 'one'}
|
|
205
|
+
<img
|
|
206
|
+
src={snapshot.entry.src}
|
|
207
|
+
{alt}
|
|
208
|
+
style:left="{layout.cssLeft}px"
|
|
209
|
+
style:top="{layout.cssTop}px"
|
|
210
|
+
style:width="{layout.cssWidth}px"
|
|
211
|
+
style:height="{layout.cssHeight}px"
|
|
212
|
+
/>
|
|
213
|
+
{:else if snapshot}
|
|
214
|
+
<canvas
|
|
215
|
+
bind:this={canvas}
|
|
216
|
+
style:left="{layout.cssLeft}px"
|
|
217
|
+
style:top="{layout.cssTop}px"
|
|
218
|
+
style:width="{layout.cssWidth}px"
|
|
219
|
+
style:height="{layout.cssHeight}px"
|
|
220
|
+
></canvas>
|
|
221
|
+
{/if}
|
|
198
222
|
</div>
|
|
199
223
|
|
|
200
224
|
<style>
|
|
@@ -204,9 +228,15 @@
|
|
|
204
228
|
height: 100%;
|
|
205
229
|
}
|
|
206
230
|
|
|
207
|
-
canvas
|
|
231
|
+
canvas,
|
|
232
|
+
img {
|
|
208
233
|
position: absolute;
|
|
209
234
|
display: block;
|
|
210
235
|
pointer-events: none;
|
|
211
236
|
}
|
|
237
|
+
|
|
238
|
+
img {
|
|
239
|
+
max-width: none;
|
|
240
|
+
max-height: none;
|
|
241
|
+
}
|
|
212
242
|
</style>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import type { VideoManifestEntry } from '../../core/index.js';
|
|
9
9
|
import { resolveRetainedSource } from '../preload-registry.js';
|
|
10
|
+
import { logRender } from '../logging.js';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
src: VideoManifestEntry;
|
|
@@ -35,6 +36,8 @@
|
|
|
35
36
|
|
|
36
37
|
const source = $derived(resolveRetainedSource(src.src));
|
|
37
38
|
|
|
39
|
+
$effect(() => logRender(src));
|
|
40
|
+
|
|
38
41
|
$effect(() => {
|
|
39
42
|
const video = videoElement;
|
|
40
43
|
if (!video) return;
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -5,5 +5,8 @@ export { default as Video } from './components/Video.svelte';
|
|
|
5
5
|
export { default as ChromaKeyVideo } from './components/ChromaKeyVideo.svelte';
|
|
6
6
|
export { resolveRetainedSource } from './preload-registry.js';
|
|
7
7
|
export { audioSourceOf } from './kind-handlers.js';
|
|
8
|
+
export { setFundusLogger } from './logging.js';
|
|
9
|
+
export type { FundusLogger, RenderedSource } from './logging.js';
|
|
10
|
+
export type { SourceStatus } from './preload-registry.js';
|
|
8
11
|
export type { ImageManifestEntry, SliceManifestEntry, VideoManifestEntry, ChromaKeyVideoManifestEntry, AudioManifestEntry, ManifestEntry, ChromaKeyParameters, ImagePreloadOptions, SlicePreloadOptions, VideoPreloadOptions, ChromaKeyVideoPreloadOptions, AudioPreloadOptions, PreloadOptions } from '../core/index.js';
|
|
9
12
|
export { DEFAULT_CHROMA_KEY_PARAMETERS } from '../core/index.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -6,4 +6,5 @@ export { default as Video } from './components/Video.svelte';
|
|
|
6
6
|
export { default as ChromaKeyVideo } from './components/ChromaKeyVideo.svelte';
|
|
7
7
|
export { resolveRetainedSource } from './preload-registry.js';
|
|
8
8
|
export { audioSourceOf } from './kind-handlers.js';
|
|
9
|
+
export { setFundusLogger } from './logging.js';
|
|
9
10
|
export { DEFAULT_CHROMA_KEY_PARAMETERS } from '../core/index.js';
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The runtime part of the type-plugin contract: per-kind preload
|
|
3
3
|
* strategy and its inverse release strategy. Each first-party kind registers
|
|
4
|
-
* a handler that knows how its delivered file is loaded —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* a handler that knows how its delivered file is loaded — images fetch +
|
|
5
|
+
* optionally decode, slices fetch and retain a decoded form (an ImageBitmap
|
|
6
|
+
* for multi-cell slices, the decoded element for one-slices), media kinds
|
|
7
|
+
* (video/audio) fetch fully and mint an object URL — and how what it loaded
|
|
8
|
+
* is disposed (revoke the object URL, close the bitmap; images have nothing
|
|
9
|
+
* to dispose). An unknown kind falls back to a plain full fetch: no decode,
|
|
10
|
+
* no retention.
|
|
9
11
|
*
|
|
10
12
|
* Handlers are pure strategy: refcounting, cross-manifest dedup, and
|
|
11
13
|
* in-flight bookkeeping live in the module-wide registry
|
|
@@ -16,12 +18,20 @@ import type { AudioManifestEntry, PreloadOptions } from '../core/index.js';
|
|
|
16
18
|
export declare function audioSourceOf(entry: AudioManifestEntry): string;
|
|
17
19
|
/**
|
|
18
20
|
* What a handler's `preload` acquired and its `release` must dispose. For
|
|
19
|
-
* retained media kinds this is the minted object URL
|
|
20
|
-
* (image,
|
|
21
|
+
* retained media kinds this is the minted object URL, for slices the decoded
|
|
22
|
+
* bitmap; cache-backed kinds (image, unknown) hold no disposable resources.
|
|
21
23
|
*/
|
|
22
24
|
export interface KindHandlerResources {
|
|
23
25
|
/** Object URL minted over the fully fetched file (retained media kinds). */
|
|
24
26
|
objectUrl?: string;
|
|
27
|
+
/** Decoded pixels of a multi-cell slice source, pinned against cache eviction. */
|
|
28
|
+
imageBitmap?: ImageBitmap;
|
|
29
|
+
/**
|
|
30
|
+
* Decoded element of a one-slice source: the synchronous-commit token for
|
|
31
|
+
* `SliceSourceLifecycle`, whose decode also warms the (evictable) browser
|
|
32
|
+
* cache the mounted `<img>` paints from. Nothing to dispose.
|
|
33
|
+
*/
|
|
34
|
+
image?: HTMLImageElement;
|
|
25
35
|
}
|
|
26
36
|
/** How one asset kind preloads — and releases — its delivered file. */
|
|
27
37
|
export interface KindRuntimeHandler {
|
|
@@ -29,12 +39,14 @@ export interface KindRuntimeHandler {
|
|
|
29
39
|
* Fully load one source of this kind — the promise resolves only once the
|
|
30
40
|
* file is completely transferred (plus the kind's strategy: decoded /
|
|
31
41
|
* retained as an object URL), its body matches the build-time `byteSize`,
|
|
32
|
-
* and return the resources `release` disposes.
|
|
42
|
+
* and return the resources `release` disposes. `entry` is the manifest
|
|
43
|
+
* entry the source came from, for strategies keyed on entry metadata.
|
|
33
44
|
*/
|
|
34
|
-
preload(url: string, byteSize: number, options: PreloadOptions): Promise<KindHandlerResources>;
|
|
45
|
+
preload(url: string, byteSize: number, options: PreloadOptions, entry?: Record<string, unknown>): Promise<KindHandlerResources>;
|
|
35
46
|
/**
|
|
36
47
|
* The inverse of `preload`: dispose the resources it returned (revoke the
|
|
37
|
-
* object URL). Omitted = the kind acquires nothing
|
|
48
|
+
* object URL, close the bitmap). Omitted = the kind acquires nothing
|
|
49
|
+
* disposable.
|
|
38
50
|
*/
|
|
39
51
|
release?(resources: KindHandlerResources): void;
|
|
40
52
|
}
|
|
@@ -66,28 +66,65 @@ function verifyByteSize(url, expected, actual) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* The image
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* The image strategy: fetch the full file, then decode by default. Decoding
|
|
70
|
+
* goes via an HTMLImageElement where a DOM exists (the browser serves the
|
|
71
|
+
* bytes from cache); headless environments (Node tests, SSR) skip decoding.
|
|
72
|
+
* Nothing to release — the browser cache owns the bytes, and the mounted
|
|
73
|
+
* `<img>` decodes from it.
|
|
73
74
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
const imageHandler = {
|
|
76
|
+
async preload(url, byteSize, options) {
|
|
77
|
+
const response = await fetchCompletely(url);
|
|
78
|
+
// Read the body fully so the file is actually transferred (and
|
|
79
|
+
// cached), not just the response headers.
|
|
80
|
+
const bytes = await response.arrayBuffer();
|
|
81
|
+
verifyByteSize(url, byteSize, bytes.byteLength);
|
|
82
|
+
if (shouldDecode('image', options) && typeof globalThis.Image === 'function') {
|
|
83
|
+
const image = new globalThis.Image();
|
|
84
|
+
image.src = url;
|
|
85
|
+
await image.decode();
|
|
86
|
+
}
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The slice strategy: fetch the full file and retain a decoded form while
|
|
92
|
+
* held, chosen by the entry's slicing mode. Multi-cell slices (nine,
|
|
93
|
+
* three-*) draw through a canvas and retain the pixels as an ImageBitmap —
|
|
94
|
+
* unlike a throwaway decode into the browser's evictable image cache, the
|
|
95
|
+
* bitmap survives memory pressure and lets `SliceSourceLifecycle` commit
|
|
96
|
+
* synchronously, so a preloaded slice paints in its mount frame. One-slice
|
|
97
|
+
* sources render as a plain `<img>`, which cannot paint from a bitmap: they
|
|
98
|
+
* retain the decoded HTMLImageElement instead — the same synchronous commit,
|
|
99
|
+
* and its decode warms the (evictable) cache the mounted `<img>` paints
|
|
100
|
+
* from. Release closes the bitmap; a retained element has nothing to
|
|
101
|
+
* dispose. Slice sources are small (the insets cover every rendered size),
|
|
102
|
+
* so retention is cheap. Environments without `createImageBitmap` retain the
|
|
103
|
+
* decoded element for every mode.
|
|
104
|
+
*/
|
|
105
|
+
const sliceHandler = {
|
|
106
|
+
async preload(url, byteSize, options, entry) {
|
|
107
|
+
const response = await fetchCompletely(url);
|
|
108
|
+
const blob = await response.blob();
|
|
109
|
+
verifyByteSize(url, byteSize, blob.size);
|
|
110
|
+
if (!shouldDecode('slice', options))
|
|
87
111
|
return {};
|
|
112
|
+
const oneSlice = entry?.slicing?.mode === 'one';
|
|
113
|
+
if (!oneSlice && typeof globalThis.createImageBitmap === 'function') {
|
|
114
|
+
return { imageBitmap: await globalThis.createImageBitmap(blob) };
|
|
88
115
|
}
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
if (typeof globalThis.Image === 'function') {
|
|
117
|
+
const image = new globalThis.Image();
|
|
118
|
+
image.src = url;
|
|
119
|
+
await image.decode();
|
|
120
|
+
return { image };
|
|
121
|
+
}
|
|
122
|
+
return {};
|
|
123
|
+
},
|
|
124
|
+
release(resources) {
|
|
125
|
+
resources.imageBitmap?.close();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
91
128
|
/**
|
|
92
129
|
* The retained-media strategy (video, chroma-key-video, audio): fetch the
|
|
93
130
|
* full file and retain it as an object URL so later playback does not touch
|
|
@@ -118,8 +155,8 @@ const unknownKindHandler = {
|
|
|
118
155
|
}
|
|
119
156
|
};
|
|
120
157
|
const runtimeHandlerCatalogue = {
|
|
121
|
-
image:
|
|
122
|
-
slice:
|
|
158
|
+
image: imageHandler,
|
|
159
|
+
slice: sliceHandler,
|
|
123
160
|
video: retainedMediaHandler,
|
|
124
161
|
'chroma-key-video': retainedMediaHandler,
|
|
125
162
|
audio: retainedMediaHandler
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in render-time diagnostics: a host registers one logger and every Fundus
|
|
3
|
+
* render component reports, on mount and whenever its displayed asset changes,
|
|
4
|
+
* which sources it needs and whether each was preloaded. Purely additive and
|
|
5
|
+
* gated — with no logger registered this is a single null-check, no walk, no
|
|
6
|
+
* cost. It never participates in rendering: `logRender` swallows any error
|
|
7
|
+
* (a malformed entry, a throwing logger) so diagnostics can't break the view.
|
|
8
|
+
*/
|
|
9
|
+
import type { ManifestEntry } from '../core/index.js';
|
|
10
|
+
import { type SourceStatus } from './preload-registry.js';
|
|
11
|
+
/** One rendered source and its preload state at the moment of the render. */
|
|
12
|
+
export interface RenderedSource {
|
|
13
|
+
/** The asset id the source belongs to — an inlined mask/fallback carries its own. */
|
|
14
|
+
id: string;
|
|
15
|
+
kind: string;
|
|
16
|
+
url: string;
|
|
17
|
+
status: SourceStatus;
|
|
18
|
+
}
|
|
19
|
+
export interface FundusLogger {
|
|
20
|
+
/**
|
|
21
|
+
* Reported once per component render (mount and each in-place asset swap),
|
|
22
|
+
* listing every source the component displays. A single-file kind yields one
|
|
23
|
+
* source; a chroma-key-video yields its video plus the inlined mask/fallback.
|
|
24
|
+
* Sources are not deduped — a url shared by two of them appears twice.
|
|
25
|
+
*/
|
|
26
|
+
onRender(sources: RenderedSource[]): void;
|
|
27
|
+
}
|
|
28
|
+
/** Register (or clear, with `null`) the process-wide render logger. */
|
|
29
|
+
export declare function setFundusLogger(logger: FundusLogger | null): void;
|
|
30
|
+
/**
|
|
31
|
+
* Report the sources a component is rendering and their current preload state.
|
|
32
|
+
* A no-op without a registered logger; never throws.
|
|
33
|
+
*/
|
|
34
|
+
export declare function logRender(entry: ManifestEntry): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { collectEntrySources } from './manifest.js';
|
|
2
|
+
import { sourceStatus } from './preload-registry.js';
|
|
3
|
+
let activeLogger = null;
|
|
4
|
+
/** Register (or clear, with `null`) the process-wide render logger. */
|
|
5
|
+
export function setFundusLogger(logger) {
|
|
6
|
+
activeLogger = logger;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Report the sources a component is rendering and their current preload state.
|
|
10
|
+
* A no-op without a registered logger; never throws.
|
|
11
|
+
*/
|
|
12
|
+
export function logRender(entry) {
|
|
13
|
+
if (!activeLogger)
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
activeLogger.onRender(collectEntrySources(entry).map((source) => ({
|
|
17
|
+
id: source.id,
|
|
18
|
+
kind: source.kind,
|
|
19
|
+
url: source.url,
|
|
20
|
+
status: sourceStatus(source.url)
|
|
21
|
+
})));
|
|
22
|
+
}
|
|
23
|
+
catch (cause) {
|
|
24
|
+
// Diagnostics must never break rendering.
|
|
25
|
+
console.warn('fundus: render logging failed', cause);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -11,14 +11,24 @@
|
|
|
11
11
|
import type { ManifestEntry, PreloadOptions } from '../core/index.js';
|
|
12
12
|
/** One deliverable file of an entry: identity, kind, and expected byte length. */
|
|
13
13
|
export interface EntrySource {
|
|
14
|
+
/** The asset id the source belongs to — inlined reference sources carry their own. */
|
|
15
|
+
id: string;
|
|
14
16
|
url: string;
|
|
15
17
|
kind: string;
|
|
16
18
|
byteSize: number;
|
|
19
|
+
/**
|
|
20
|
+
* The (possibly inlined) entry the source came from. Kind strategies read
|
|
21
|
+
* per-entry metadata from it — the slice handler picks its retention form
|
|
22
|
+
* by slicing mode. Orchestration itself never looks inside.
|
|
23
|
+
*/
|
|
24
|
+
entry: Record<string, unknown>;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
|
-
* Collect every deliverable
|
|
20
|
-
* inlined in place of asset references: any nested object carrying
|
|
21
|
-
* a string `kind
|
|
27
|
+
* Collect every deliverable source of an entry, including the entries codegen
|
|
28
|
+
* inlined in place of asset references: a source is any nested object carrying
|
|
29
|
+
* a string `id`, `kind`, and `src` — i.e. an asset entry (every generated entry
|
|
30
|
+
* carries all three). Requiring `id` keeps `EntrySource.id` an honest `string`
|
|
31
|
+
* and never drops a real source, since codegen bakes an id into every entry.
|
|
22
32
|
* Generic by design: adding a kind does not change preload orchestration.
|
|
23
33
|
*/
|
|
24
34
|
export declare function collectEntrySources(entry: ManifestEntry): EntrySource[];
|
|
@@ -49,8 +59,9 @@ export declare class Manifest {
|
|
|
49
59
|
* Drop this manifest's holds on everything it preloaded. Assets shared
|
|
50
60
|
* with another preloaded manifest stay fully alive for that holder; assets
|
|
51
61
|
* nobody holds anymore are disposed via their kind's release strategy
|
|
52
|
-
* (retained media object URLs are revoked,
|
|
53
|
-
* of a fully released source fall back to its
|
|
62
|
+
* (retained media object URLs are revoked, slice bitmaps closed, freeing
|
|
63
|
+
* the memory). Consumers of a fully released source fall back to its
|
|
64
|
+
* network URL.
|
|
54
65
|
*
|
|
55
66
|
* Safe in every state: before or without `preload()` it is a no-op, and
|
|
56
67
|
* calling it while a `preload()` is still in flight drops the holds
|
package/dist/runtime/manifest.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { acquireSource, releaseSource } from './preload-registry.js';
|
|
2
2
|
/**
|
|
3
|
-
* Collect every deliverable
|
|
4
|
-
* inlined in place of asset references: any nested object carrying
|
|
5
|
-
* a string `kind
|
|
3
|
+
* Collect every deliverable source of an entry, including the entries codegen
|
|
4
|
+
* inlined in place of asset references: a source is any nested object carrying
|
|
5
|
+
* a string `id`, `kind`, and `src` — i.e. an asset entry (every generated entry
|
|
6
|
+
* carries all three). Requiring `id` keeps `EntrySource.id` an honest `string`
|
|
7
|
+
* and never drops a real source, since codegen bakes an id into every entry.
|
|
6
8
|
* Generic by design: adding a kind does not change preload orchestration.
|
|
7
9
|
*/
|
|
8
10
|
export function collectEntrySources(entry) {
|
|
@@ -18,11 +20,19 @@ export function collectEntrySources(entry) {
|
|
|
18
20
|
if (typeof value !== 'object' || value === null)
|
|
19
21
|
return;
|
|
20
22
|
const record = value;
|
|
21
|
-
if (typeof record.
|
|
23
|
+
if (typeof record.id === 'string' &&
|
|
24
|
+
typeof record.kind === 'string' &&
|
|
25
|
+
typeof record.src === 'string') {
|
|
22
26
|
if (!Number.isSafeInteger(record.byteSize) || record.byteSize < 0) {
|
|
23
27
|
throw new Error(`Manifest entry for "${record.src}" requires a non-negative safe integer "byteSize".`);
|
|
24
28
|
}
|
|
25
|
-
sources.push({
|
|
29
|
+
sources.push({
|
|
30
|
+
id: record.id,
|
|
31
|
+
url: record.src,
|
|
32
|
+
kind: record.kind,
|
|
33
|
+
byteSize: record.byteSize,
|
|
34
|
+
entry: record
|
|
35
|
+
});
|
|
26
36
|
}
|
|
27
37
|
for (const nested of Object.values(record))
|
|
28
38
|
visit(nested);
|
|
@@ -87,8 +97,9 @@ export class Manifest {
|
|
|
87
97
|
* Drop this manifest's holds on everything it preloaded. Assets shared
|
|
88
98
|
* with another preloaded manifest stay fully alive for that holder; assets
|
|
89
99
|
* nobody holds anymore are disposed via their kind's release strategy
|
|
90
|
-
* (retained media object URLs are revoked,
|
|
91
|
-
* of a fully released source fall back to its
|
|
100
|
+
* (retained media object URLs are revoked, slice bitmaps closed, freeing
|
|
101
|
+
* the memory). Consumers of a fully released source fall back to its
|
|
102
|
+
* network URL.
|
|
92
103
|
*
|
|
93
104
|
* Safe in every state: before or without `preload()` it is a no-op, and
|
|
94
105
|
* calling it while a `preload()` is still in flight drops the holds
|
|
@@ -121,7 +132,7 @@ export class Manifest {
|
|
|
121
132
|
const acquisitions = [];
|
|
122
133
|
for (const [url, source] of sourceByUrl) {
|
|
123
134
|
held.add(url);
|
|
124
|
-
acquisitions.push(acquireSource(url, source.kind, source.byteSize, options));
|
|
135
|
+
acquisitions.push(acquireSource(url, source.kind, source.byteSize, options, source.entry));
|
|
125
136
|
}
|
|
126
137
|
const outcomes = await Promise.allSettled(acquisitions);
|
|
127
138
|
const failure = outcomes.find((outcome) => outcome.status === 'rejected');
|
|
@@ -24,7 +24,7 @@ import type { PreloadOptions } from '../core/index.js';
|
|
|
24
24
|
* load clears the in-flight state so the URL stays retryable; the caller
|
|
25
25
|
* rolls back its own hold via `releaseSource`.
|
|
26
26
|
*/
|
|
27
|
-
export declare function acquireSource(url: string, kind: string, byteSize: number, options: PreloadOptions): Promise<void>;
|
|
27
|
+
export declare function acquireSource(url: string, kind: string, byteSize: number, options: PreloadOptions, sourceEntry?: Record<string, unknown>): Promise<void>;
|
|
28
28
|
/**
|
|
29
29
|
* Drop one hold on `url`. At zero holds the kind handler's release strategy
|
|
30
30
|
* runs (object URL revoked) and the entry is cleared — unless the load is
|
|
@@ -38,3 +38,22 @@ export declare function releaseSource(url: string): void;
|
|
|
38
38
|
* consumer falls back to the network URL and simply streams.
|
|
39
39
|
*/
|
|
40
40
|
export declare function resolveRetainedSource(url: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* The retained decoded source of a slice URL while some manifest holds it —
|
|
43
|
+
* the pinned bitmap of a multi-cell slice, or the decoded element of a
|
|
44
|
+
* one-slice — or `null` when not (or no longer) preloaded; the consumer then
|
|
45
|
+
* falls back to an async image load. Never returns a closed bitmap: closing
|
|
46
|
+
* happens only when the entry is dropped from the registry.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveRetainedSliceSource(url: string): ImageBitmap | HTMLImageElement | null;
|
|
49
|
+
/**
|
|
50
|
+
* A source's preload state, for render-time diagnostics:
|
|
51
|
+
* - `loaded` — fully preloaded and held (every kind resolves non-null
|
|
52
|
+
* `resources`, so this holds for image/slice as well as retained media);
|
|
53
|
+
* - `pending` — a preload is in flight;
|
|
54
|
+
* - `absent` — never preloaded, released, or a prior preload failed. `absent`
|
|
55
|
+
* means "not currently loaded", not "not needed" — a component showing an
|
|
56
|
+
* `absent` source fetches it fresh on render.
|
|
57
|
+
*/
|
|
58
|
+
export type SourceStatus = 'loaded' | 'pending' | 'absent';
|
|
59
|
+
export declare function sourceStatus(url: string): SourceStatus;
|
|
@@ -7,7 +7,7 @@ const registryByUrl = new Map();
|
|
|
7
7
|
* load clears the in-flight state so the URL stays retryable; the caller
|
|
8
8
|
* rolls back its own hold via `releaseSource`.
|
|
9
9
|
*/
|
|
10
|
-
export function acquireSource(url, kind, byteSize, options) {
|
|
10
|
+
export function acquireSource(url, kind, byteSize, options, sourceEntry) {
|
|
11
11
|
let entry = registryByUrl.get(url);
|
|
12
12
|
if (!entry) {
|
|
13
13
|
entry = { kind, byteSize, referenceCount: 0, pending: null, resources: null };
|
|
@@ -23,7 +23,7 @@ export function acquireSource(url, kind, byteSize, options) {
|
|
|
23
23
|
if (!pending) {
|
|
24
24
|
const acquired = entry;
|
|
25
25
|
pending = runtimeHandlerFor(kind)
|
|
26
|
-
.preload(url, byteSize, options)
|
|
26
|
+
.preload(url, byteSize, options, sourceEntry)
|
|
27
27
|
.then((resources) => {
|
|
28
28
|
acquired.pending = null;
|
|
29
29
|
if (acquired.referenceCount === 0) {
|
|
@@ -72,3 +72,24 @@ export function releaseSource(url) {
|
|
|
72
72
|
export function resolveRetainedSource(url) {
|
|
73
73
|
return registryByUrl.get(url)?.resources?.objectUrl ?? url;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The retained decoded source of a slice URL while some manifest holds it —
|
|
77
|
+
* the pinned bitmap of a multi-cell slice, or the decoded element of a
|
|
78
|
+
* one-slice — or `null` when not (or no longer) preloaded; the consumer then
|
|
79
|
+
* falls back to an async image load. Never returns a closed bitmap: closing
|
|
80
|
+
* happens only when the entry is dropped from the registry.
|
|
81
|
+
*/
|
|
82
|
+
export function resolveRetainedSliceSource(url) {
|
|
83
|
+
const resources = registryByUrl.get(url)?.resources;
|
|
84
|
+
return resources?.imageBitmap ?? resources?.image ?? null;
|
|
85
|
+
}
|
|
86
|
+
export function sourceStatus(url) {
|
|
87
|
+
const entry = registryByUrl.get(url);
|
|
88
|
+
if (!entry)
|
|
89
|
+
return 'absent';
|
|
90
|
+
if (entry.resources !== null)
|
|
91
|
+
return 'loaded';
|
|
92
|
+
if (entry.pending !== null)
|
|
93
|
+
return 'pending';
|
|
94
|
+
return 'absent';
|
|
95
|
+
}
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import type { SliceManifestEntry } from '../core/index.js';
|
|
2
|
+
/** What a committed slice draws from: a decoded element or a retained bitmap. */
|
|
3
|
+
export type SliceRenderSource = HTMLImageElement | ImageBitmap;
|
|
2
4
|
export interface LoadedSliceSnapshot {
|
|
3
5
|
entry: SliceManifestEntry;
|
|
4
|
-
image:
|
|
6
|
+
image: SliceRenderSource;
|
|
5
7
|
}
|
|
6
8
|
export type SliceImageLoader = (url: string) => Promise<HTMLImageElement>;
|
|
9
|
+
export type RetainedSliceSourceResolver = (url: string) => SliceRenderSource | null;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a committed source can no longer be drawn: a preload-retained
|
|
12
|
+
* bitmap is closed when its last hold is released, and drawing a closed
|
|
13
|
+
* bitmap throws. A detached source is dropped and reloaded asynchronously.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isDetachedSliceSource(image: SliceRenderSource): boolean;
|
|
7
16
|
export declare function loadSliceImage(url: string): Promise<HTMLImageElement>;
|
|
8
17
|
/** Coordinates requested slice entries with the last image-ready snapshot. */
|
|
9
18
|
export declare class SliceSourceLifecycle {
|
|
10
19
|
#private;
|
|
11
|
-
constructor(commit: (snapshot: LoadedSliceSnapshot) => void, loadImage?: SliceImageLoader);
|
|
20
|
+
constructor(commit: (snapshot: LoadedSliceSnapshot) => void, loadImage?: SliceImageLoader, resolveRetained?: RetainedSliceSourceResolver);
|
|
12
21
|
request(entry: SliceManifestEntry): void;
|
|
13
22
|
dispose(): void;
|
|
14
23
|
}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { resolveRetainedSliceSource } from './preload-registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a committed source can no longer be drawn: a preload-retained
|
|
4
|
+
* bitmap is closed when its last hold is released, and drawing a closed
|
|
5
|
+
* bitmap throws. A detached source is dropped and reloaded asynchronously.
|
|
6
|
+
*/
|
|
7
|
+
export function isDetachedSliceSource(image) {
|
|
8
|
+
return typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap && image.width === 0;
|
|
9
|
+
}
|
|
1
10
|
export function loadSliceImage(url) {
|
|
2
11
|
const image = new Image();
|
|
3
12
|
image.decoding = 'async';
|
|
@@ -7,24 +16,34 @@ export function loadSliceImage(url) {
|
|
|
7
16
|
/** Coordinates requested slice entries with the last image-ready snapshot. */
|
|
8
17
|
export class SliceSourceLifecycle {
|
|
9
18
|
#loadImage;
|
|
19
|
+
#resolveRetained;
|
|
10
20
|
#commit;
|
|
11
21
|
#requestedEntry = null;
|
|
12
22
|
#displayedSnapshot = null;
|
|
13
23
|
#inFlight = new Map();
|
|
14
24
|
#disposed = false;
|
|
15
|
-
constructor(commit, loadImage = loadSliceImage) {
|
|
25
|
+
constructor(commit, loadImage = loadSliceImage, resolveRetained = resolveRetainedSliceSource) {
|
|
16
26
|
this.#commit = commit;
|
|
17
27
|
this.#loadImage = loadImage;
|
|
28
|
+
this.#resolveRetained = resolveRetained;
|
|
18
29
|
}
|
|
19
30
|
request(entry) {
|
|
20
31
|
if (this.#disposed)
|
|
21
32
|
return;
|
|
22
33
|
this.#requestedEntry = entry;
|
|
23
34
|
const displayed = this.#displayedSnapshot;
|
|
24
|
-
if (displayed?.entry.src === entry.src) {
|
|
35
|
+
if (displayed?.entry.src === entry.src && !isDetachedSliceSource(displayed.image)) {
|
|
25
36
|
this.#commitSnapshot({ entry, image: displayed.image });
|
|
26
37
|
return;
|
|
27
38
|
}
|
|
39
|
+
// A preload-retained source (the pinned bitmap of a multi-cell slice,
|
|
40
|
+
// the decoded element of a one-slice) commits synchronously — the slice
|
|
41
|
+
// paints in its mount frame instead of one decode roundtrip later.
|
|
42
|
+
const retained = this.#resolveRetained(entry.src);
|
|
43
|
+
if (retained) {
|
|
44
|
+
this.#commitSnapshot({ entry, image: retained });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
28
47
|
if (this.#inFlight.has(entry.src))
|
|
29
48
|
return;
|
|
30
49
|
let promise;
|
package/dist/server/codegen.js
CHANGED
|
@@ -43,7 +43,7 @@ export function manifestModuleFileName(manifestName) {
|
|
|
43
43
|
*/
|
|
44
44
|
export function manifestEntryForAsset(record, proxy, config, registry = serverPluginsByType) {
|
|
45
45
|
const plugin = serverPluginForType(record.type, registry);
|
|
46
|
-
|
|
46
|
+
const entry = plugin.buildManifestEntry({
|
|
47
47
|
assetId: proxy.assetId,
|
|
48
48
|
parameters: plugin.descriptor.validateParameters(record.parameters),
|
|
49
49
|
src: `${config.proxyBasePath}${proxy.relativePath
|
|
@@ -52,6 +52,10 @@ export function manifestEntryForAsset(record, proxy, config, registry = serverPl
|
|
|
52
52
|
.join('/')}`,
|
|
53
53
|
proxy
|
|
54
54
|
});
|
|
55
|
+
// The asset id is the SSOT key codegen already owns — bake it into the
|
|
56
|
+
// entry centrally so every kind carries it (and a new plugin can't forget
|
|
57
|
+
// it), rather than repeating it in each plugin's builder.
|
|
58
|
+
return { id: proxy.assetId, ...entry };
|
|
55
59
|
}
|
|
56
60
|
/**
|
|
57
61
|
* Replace every `{ $asset }` marker in a built entry with the referenced
|