lecodes-viewer 0.19.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.
Files changed (103) hide show
  1. package/README.md +5 -0
  2. package/dist-embed/VERSION.json +5 -0
  3. package/dist-embed/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
  4. package/dist-embed/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  5. package/dist-embed/assets/basis-C64VHDVD.js +1 -0
  6. package/dist-embed/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  7. package/dist-embed/assets/createViewer2d-D12Wy3VF.js +1 -0
  8. package/dist-embed/assets/createViewerLite-CP-WHGLg.js +707 -0
  9. package/dist-embed/assets/creator-gl-BF2CQzeI.wasm +0 -0
  10. package/dist-embed/assets/creator-gl.js-B_0GrQRe.symbols +5379 -0
  11. package/dist-embed/assets/creator-ui-BX5Fvnbt.js +1 -0
  12. package/dist-embed/assets/creator-ui-CzKHLH2Q.wasm +0 -0
  13. package/dist-embed/assets/creator2d-BtQ-1_B0.wasm +0 -0
  14. package/dist-embed/assets/draco-BiISTFcR.js +118 -0
  15. package/dist-embed/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  16. package/dist-embed/assets/host-CWKB1WDd.js +162 -0
  17. package/dist-embed/assets/index-CIK8PnUA.js +2 -0
  18. package/dist-embed/assets/lit-DUE9rhY9.filamat +0 -0
  19. package/dist-embed/assets/loadCreatorUiNode-UAdhjRTd.js +1 -0
  20. package/dist-embed/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
  21. package/dist-embed/assets/particles-DxYDmI1O.filamat +0 -0
  22. package/dist-embed/assets/particles-quad-DwYo9yCK.filamat +0 -0
  23. package/dist-embed/assets/shadow-C9xJpqlY.filamat +0 -0
  24. package/dist-embed/assets/uberarchive-uXjRYa9p.bin +0 -0
  25. package/dist-embed/assets/unlit-pe4tFY5b.filamat +0 -0
  26. package/dist-embed/assets/unlit-transparent-D9OyaOBR.filamat +0 -0
  27. package/dist-embed/assets/video-eedvhMvS.filamat +0 -0
  28. package/dist-embed/assets/worker-Caf-yYEI.js +2 -0
  29. package/dist-embed/embed.html +48 -0
  30. package/dist-embed/embed.js +187 -0
  31. package/embed.html +48 -0
  32. package/package.json +35 -0
  33. package/src/assets/banner.png +0 -0
  34. package/src/assets/image2.png +0 -0
  35. package/src/assets/landing.mp4 +0 -0
  36. package/src/assets/neutral_ibl128.ktx +0 -0
  37. package/src/assets/neutral_ibl256.ktx +0 -0
  38. package/src/assets/output.mp4 +0 -0
  39. package/src/assets/output2.mp4 +0 -0
  40. package/src/assets/texture.png +0 -0
  41. package/src/assets/vue.svg +1 -0
  42. package/src/canvas/createCanvasHost.ts +2 -0
  43. package/src/createViewer.ts +103 -0
  44. package/src/embed.ts +515 -0
  45. package/src/host.ts +428 -0
  46. package/src/lite-test.ts +160 -0
  47. package/src/main.ts +77 -0
  48. package/src/sourcemapError.ts +2 -0
  49. package/src/ui/CuiProps.gen.ts +103 -0
  50. package/src/ui/UIScreen.ts +486 -0
  51. package/src/ui/createBareUI.ts +32 -0
  52. package/src/ui/createMeasureSystem.ts +220 -0
  53. package/src/ui/createNodeElement.ts +77 -0
  54. package/src/ui/createPagerSystem.ts +345 -0
  55. package/src/ui/createTouchSystem.ts +2 -0
  56. package/src/ui/createUI.ts +806 -0
  57. package/src/ui/creator-ui.js +14 -0
  58. package/src/ui/creator-ui.wasm +0 -0
  59. package/src/ui/defaultStyle.css +176 -0
  60. package/src/ui/layers.ts +9 -0
  61. package/src/ui/mountCanvasUI.ts +213 -0
  62. package/src/ui/nativeViewSystem.ts +91 -0
  63. package/src/ui/presentPlayer.ts +156 -0
  64. package/src/ui/props-apply.ts +403 -0
  65. package/src/ui/router.ts +2 -0
  66. package/src/ui/safeAreaSpec.ts +3 -0
  67. package/src/ui/shells.ts +37 -0
  68. package/src/ui/toasts.ts +2 -0
  69. package/src/ui/transitions.ts +250 -0
  70. package/src/ui/ui.d.ts +78 -0
  71. package/src/ui/vListSystem.ts +212 -0
  72. package/src/ui/viewScale.ts +2 -0
  73. package/src/utils/animate.ts +2 -0
  74. package/src/utils/createUtils.ts +2 -0
  75. package/src/utils/fetch.ts +3 -0
  76. package/src/utils/fonts.ts +2 -0
  77. package/src/utils/globals.ts +2 -0
  78. package/src/utils/imageNode.ts +260 -0
  79. package/src/utils/openFilePicker.ts +2 -0
  80. package/src/utils/svg.ts +2 -0
  81. package/src/utils/videoNode.ts +2 -0
  82. package/src/utils/ws.ts +2 -0
  83. package/src/viewer/createViewer.ts +993 -0
  84. package/src/viewer/creator-gl.js +14 -0
  85. package/src/viewer/creator-gl.js.symbols +5379 -0
  86. package/src/viewer/creator-gl.wasm +0 -0
  87. package/src/viewer/lit.filamat +0 -0
  88. package/src/viewer/mediaplayer.ts +3 -0
  89. package/src/viewer/particles-quad.filamat +0 -0
  90. package/src/viewer/particles.filamat +0 -0
  91. package/src/viewer/physics.ts +140 -0
  92. package/src/viewer/shadow.filamat +0 -0
  93. package/src/viewer/uberarchive.bin +0 -0
  94. package/src/viewer/unlit-transparent.filamat +0 -0
  95. package/src/viewer/unlit.filamat +0 -0
  96. package/src/viewer/validateGlb.ts +262 -0
  97. package/src/viewer/video.filamat +0 -0
  98. package/src/viewer/wasmCrashSymbolizer.ts +133 -0
  99. package/src/viewer2d/createViewer2d.ts +438 -0
  100. package/src/viewer2d/creator2d.js +14 -0
  101. package/src/viewer2d/creator2d.wasm +0 -0
  102. package/src/viewer2d/sprite2d.ts +80 -0
  103. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,260 @@
1
+ import { getUrl } from "./fetch"
2
+ import {
3
+ ImageSrc,
4
+ isSvgSrc,
5
+ svgSrcTint,
6
+ convertSvgToDataUri,
7
+ applySvgMask,
8
+ clearSvgMask,
9
+ extractViewBox,
10
+ } from "./svg"
11
+
12
+ export type ImageHost = {
13
+ nodeMap: Map<number, HTMLElement>
14
+ weakMap: WeakMap<HTMLElement, number>
15
+ module: any
16
+ recalc(): void
17
+ recalcImmediately(): void
18
+ /** Resolve a baked `_creatorCanvas` surface (UIImage(canvas)) to a data-uri for the <img>. */
19
+ canvasHost?: { surfaceToDataUrl(surfaceId: number): string | null }
20
+ }
21
+
22
+ // Replaces the element's tag in place, preserving layout state and re-pointing the maps.
23
+ // No-op (returns the same element) when the tag already matches.
24
+ export function swapElementTag(host: ImageHost, el: HTMLElement, tag: "img" | "div"): HTMLElement {
25
+ if (el.tagName.toLowerCase() === tag) return el
26
+
27
+ const next = document.createElement(tag)
28
+ next.setAttribute("style", el.getAttribute("style") ?? "")
29
+ for (const attr of ["data-measured", "data-using-padding", "data-using-scroll", "data-viewbox"]) {
30
+ if (el.hasAttribute(attr)) {
31
+ const v = el.getAttribute(attr)!
32
+ next.setAttribute(attr, v)
33
+ }
34
+ }
35
+
36
+ const id = host.weakMap.get(el)!
37
+ host.weakMap.delete(el)
38
+ host.weakMap.set(next, id)
39
+ host.nodeMap.set(id, next)
40
+
41
+ el.replaceWith(next)
42
+ return next
43
+ }
44
+
45
+ // node-based entry (creation path) — unchanged call sites
46
+ export function configureImage(host: ImageHost, node: any, el: HTMLElement): void {
47
+ // A sourceRect stashed on the node (set before mount, while _id == 0) is applied here: the
48
+ // element was already created as a crop <div> by createNodeElement, so just paint + crop it.
49
+ const rect = node._sourceRect as [number, number, number, number] | undefined
50
+ if (rect) {
51
+ configureImageSourceRect(el, node._src as ImageSrc, rect)
52
+ return
53
+ }
54
+ // Effective tint: an explicit style-level tintColor wins; otherwise the SvgSource's own tintColor.
55
+ const tint = (node._style?.tintColor as string | undefined) ?? svgSrcTint(node._src as ImageSrc)
56
+ configureImageSrc(host, el, node._src as ImageSrc, tint)
57
+ }
58
+
59
+ // Runtime: image node's src changed (via updateNode). Re-derives the masked/unmasked
60
+ // category from the NEW src + the tint currently reflected in the DOM element, swaps the
61
+ // tag if the category flips, and reconfigures. No node object needed.
62
+ export function updateImageSrc(host: ImageHost, current: HTMLElement, src: ImageSrc): void {
63
+ // The effective tint: the NEW source's own tintColor takes priority (swapping between two
64
+ // source-tinted SvgSources), else the tint already on the element — a masked <div> carries it as
65
+ // backgroundColor (from a style tint or a previous source tint), a plain <img> has none.
66
+ const currentTint = current instanceof HTMLDivElement ? current.style.backgroundColor : ""
67
+ const tint = svgSrcTint(src) ?? currentTint
68
+
69
+ const wantsDiv = isSvgSrc(src) && !!tint
70
+ const el = swapElementTag(host, current, wantsDiv ? "div" : "img")
71
+ configureImageSrc(host, el, src, tint)
72
+ host.recalc()
73
+ }
74
+
75
+ // core: works from raw src + tint, no node
76
+ function configureImageSrc(host: ImageHost, el: HTMLElement, src: ImageSrc, tint: string | undefined): void {
77
+ // Forget any prior canvas binding; the canvasSurface branch below re-sets it. Keeps
78
+ // refreshCanvasSurface from repainting a node whose src has since moved off the canvas.
79
+ ;(el as any)._canvasSurface = undefined
80
+
81
+ if (el instanceof HTMLImageElement) {
82
+ clearSvgMask(el)
83
+ el.referrerPolicy = "no-referrer"
84
+ el.crossOrigin = "anonymous"
85
+ el.addEventListener("load", () => {
86
+ if (!el.hasAttribute("data-measured")) return
87
+ host.module._markNodeDirty(host.weakMap.get(el)!)
88
+ host.recalcImmediately()
89
+ }, { once: true })
90
+
91
+ if (isSvgSrc(src)) {
92
+ el.src = convertSvgToDataUri(src.svg)
93
+ const vb = extractViewBox(src.svg)
94
+ if (vb) el.setAttribute("data-viewbox", vb)
95
+ } else if (typeof src === "object" && "canvasSurface" in src) {
96
+ // UIImage(canvas): a baked _creatorCanvas surface → a data-uri the <img> can load. Remember
97
+ // the surface id so a later canvas.update() (refreshCanvasSurface) can repaint this node.
98
+ const surfaceId = (src as { canvasSurface: number }).canvasSurface
99
+ ;(el as any)._canvasSurface = surfaceId
100
+ el.removeAttribute("data-viewbox")
101
+ el.src = host.canvasHost?.surfaceToDataUrl(surfaceId) ?? ""
102
+ } else {
103
+ el.removeAttribute("data-viewbox")
104
+ el.src = typeof src === "object" ? getUrl(src._id) : src
105
+ }
106
+ return
107
+ }
108
+
109
+ if (isSvgSrc(src) && tint) {
110
+ applySvgMask(el, src.svg, tint)
111
+ if (el.hasAttribute("data-measured")) {
112
+ host.module._markNodeDirty(host.weakMap.get(el)!)
113
+ host.recalcImmediately()
114
+ }
115
+ }
116
+ }
117
+
118
+ // Render only a sub-rectangle (x, y, w, h, in texture pixels) of an image's source texture —
119
+ // the atlas / spritesheet primitive. Mirrors tintSvgImage: swap the <img> for a <div> and
120
+ // paint the atlas as a background-image, then scale + offset it so the sub-rect fills the box.
121
+ // (We use background-image rather than object-view-box because it works in every browser.)
122
+ //
123
+ // The rect is stashed on the element so measure can read w/h as the intrinsic size (one
124
+ // frame, not the whole atlas). The natural atlas size is needed for the offset/scale math and
125
+ // is read from the loaded <img>, or probed if the atlas hasn't loaded yet.
126
+ export function setImageSourceRect(host: ImageHost, nodeId: number, x: number, y: number, w: number, h: number): void {
127
+ const el = host.nodeMap.get(nodeId)
128
+ if (!el) return
129
+
130
+ const prev = (el as any)._sourceRect as [number, number, number, number] | undefined
131
+ ;(el as any)._sourceRect = [x, y, w, h]
132
+
133
+ if (el instanceof HTMLImageElement) {
134
+ // First crop on this node (rect set after mount): swap <img> → background-image <div>.
135
+ // The old <img> may already hold the decoded natural size — pass it to skip the probe.
136
+ const div = swapElementTag(host, el, "div")
137
+ ;(div as any)._sourceRect = [x, y, w, h] // swapElementTag copies attributes, not fields
138
+ paintCropDiv(div, el.src, el)
139
+ } else {
140
+ // Subsequent crop (animation hot path): the crop <div> is already set up, just restyle.
141
+ applySourceRectCrop(el)
142
+ }
143
+
144
+ // The frame w/h are the intrinsic size. First rect, or any w/h change → intrinsic size
145
+ // changed → relayout. A position-only change (x/y differ, w/h same) is the animation hot
146
+ // path: draw-only, no recalc.
147
+ if (!prev || prev[2] !== w || prev[3] !== h) {
148
+ host.module._markNodeDirty(nodeId)
149
+ host.recalc()
150
+ }
151
+ }
152
+
153
+ // Mount path: a sourceRect was set before the node was attached (setSourceRect while _id == 0)
154
+ // and stashed on the node. createNodeElement already made the element a <div>, so there is no
155
+ // <img> to swap — just paint + crop. No relayout here: this runs during tree construction, the
156
+ // element's _sourceRect is in place before the initial layout pass measures it.
157
+ export function configureImageSourceRect(el: HTMLElement, src: ImageSrc, rect: [number, number, number, number]): void {
158
+ ;(el as any)._sourceRect = rect
159
+ const url = typeof src === "object" ? getUrl((src as any)._id) : src
160
+ paintCropDiv(el, url, null)
161
+ }
162
+
163
+ // Paint the atlas as the element's background-image and crop it. The natural atlas size is
164
+ // needed for the offset/scale math: read it from `loadedImg` when it is already decoded,
165
+ // otherwise probe the url. The background is painted only once the size is known and the crop
166
+ // is applied in the same synchronous block — so the first paint is already cropped, never a
167
+ // flash of the full uncropped atlas. The frame size (and thus layout) is known without the
168
+ // bitmap, so only the visual crop waits. The probe closes over `el`, which is stable after
169
+ // this point (no further tag swap), and reads the live _sourceRect so a crop changed before
170
+ // the probe resolves still lands correctly.
171
+ function paintCropDiv(el: HTMLElement, url: string, loadedImg: HTMLImageElement | null): void {
172
+ el.style.backgroundRepeat = "no-repeat"
173
+
174
+ if (loadedImg && loadedImg.naturalWidth > 0) {
175
+ ;(el as any)._atlasSize = [loadedImg.naturalWidth, loadedImg.naturalHeight]
176
+ el.style.backgroundImage = `url("${url}")`
177
+ applySourceRectCrop(el)
178
+ return
179
+ }
180
+ const probe = new Image()
181
+ probe.crossOrigin = "anonymous"
182
+ probe.referrerPolicy = "no-referrer"
183
+ probe.addEventListener("load", () => {
184
+ ;(el as any)._atlasSize = [probe.naturalWidth, probe.naturalHeight]
185
+ el.style.backgroundImage = `url("${url}")`
186
+ applySourceRectCrop(el)
187
+ }, { once: true })
188
+ probe.src = url
189
+ }
190
+
191
+ // Scale the atlas so the sub-rect exactly fills the box, then offset so the sub-rect's
192
+ // top-left aligns to the box's top-left. Both are expressed as percentages so they are
193
+ // independent of the (layout-driven) box size — set once, no recompute on resize.
194
+ // background-size % is relative to the box: NW/w*100% makes one frame == box width.
195
+ // background-position % aligns the p% point of image to the p% point of box, which works
196
+ // out to x/(NW-w)*100% for the frame's left edge (full-span → 0).
197
+ function applySourceRectCrop(el: HTMLElement): void {
198
+ const rect = (el as any)._sourceRect as [number, number, number, number] | undefined
199
+ const atlas = (el as any)._atlasSize as [number, number] | undefined
200
+ if (!rect || !atlas) return // natural atlas size not known yet (probe still pending)
201
+
202
+ const [x, y, w, h] = rect
203
+ const [NW, NH] = atlas
204
+
205
+ el.style.backgroundSize = `${NW / w * 100}% ${NH / h * 100}%`
206
+ el.style.backgroundPosition =
207
+ `${NW === w ? 0 : x / (NW - w) * 100}% ${NH === h ? 0 : y / (NH - h) * 100}%`
208
+ }
209
+
210
+ // Canvas.update() → repaint every UIImage(canvas) node bound to this baked surface with the
211
+ // re-rasterized pixels. Draw-only, no relayout: the surface keeps its id and size across an
212
+ // update (a resize goes through a full `src` re-assign instead), so only the <img> bitmap
213
+ // changes. A canvasSurface src is never svg/tinted, so the node is always a plain <img>.
214
+ export function refreshCanvasSurface(host: ImageHost, surfaceId: number): void {
215
+ const url = host.canvasHost?.surfaceToDataUrl(surfaceId)
216
+ if (!url) return
217
+ for (const el of host.nodeMap.values()) {
218
+ if ((el as any)._canvasSurface === surfaceId && el instanceof HTMLImageElement) el.src = url
219
+ }
220
+ }
221
+
222
+ export function tintSvgImage(host: ImageHost, nodeId: number, tint: string): void {
223
+ const current = host.nodeMap.get(nodeId)
224
+ if (!current || !(current instanceof HTMLImageElement)) return
225
+ const dataUri = current.src // already data:image/svg+xml;base64,...
226
+ const div = swapElementTag(host, current, "div")
227
+ const s = div.style as any
228
+ s.webkitMaskImage = s.maskImage = `url("${dataUri}")`
229
+ s.webkitMaskRepeat = s.maskRepeat = "no-repeat"
230
+ s.webkitMaskPosition = s.maskPosition = "center"
231
+ if (!s.maskSize) s.webkitMaskSize = s.maskSize = "contain"
232
+ div.style.backgroundColor = tint
233
+ // data-viewbox already copied by swapElementTag — measure stays correct
234
+ host.recalc()
235
+ }
236
+
237
+ // tintColor reset (a PRESENCE prop: no tint = the image's original colors). Reverses
238
+ // tintSvgImage: the svg data-uri lives in the mask — swap the masked <div> back to a plain
239
+ // <img> loading it, and drop the mask + tint fill. A node tinted while still an <img>
240
+ // (non-svg src) only carried a backgroundColor, so clearing that is the whole reset.
241
+ export function untintSvgImage(host: ImageHost, nodeId: number): void {
242
+ const el = host.nodeMap.get(nodeId)
243
+ if (!el) return
244
+ const s = el.style as any
245
+ const mask: string = s.maskImage || s.webkitMaskImage || ""
246
+ if (el instanceof HTMLDivElement && mask) {
247
+ const src = /url\("(.*)"\)/.exec(mask)?.[1]
248
+ const img = swapElementTag(host, el, "img") as HTMLImageElement
249
+ const is = img.style as any
250
+ is.webkitMaskImage = is.maskImage = ""
251
+ is.webkitMaskRepeat = is.maskRepeat = ""
252
+ is.webkitMaskPosition = is.maskPosition = ""
253
+ is.webkitMaskSize = is.maskSize = ""
254
+ img.style.backgroundColor = ""
255
+ if (src) img.src = src
256
+ host.recalc()
257
+ } else {
258
+ el.style.backgroundColor = ""
259
+ }
260
+ }
@@ -0,0 +1,2 @@
1
+ // Moved to viewer-lite; re-export shim.
2
+ export * from "lecodes-viewer-lite/src/utils/openFilePicker"
@@ -0,0 +1,2 @@
1
+ // Moved to viewer-lite; re-export shim.
2
+ export * from "lecodes-viewer-lite/src/utils/svg"
@@ -0,0 +1,2 @@
1
+ // Moved to viewer-lite; re-export shim.
2
+ export * from "lecodes-viewer-lite/src/utils/videoNode"
@@ -0,0 +1,2 @@
1
+ // Moved to viewer-lite; re-export shim.
2
+ export * from "lecodes-viewer-lite/src/utils/ws"