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.
- package/README.md +5 -0
- package/dist-embed/VERSION.json +5 -0
- package/dist-embed/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
- package/dist-embed/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/dist-embed/assets/basis-C64VHDVD.js +1 -0
- package/dist-embed/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/dist-embed/assets/createViewer2d-D12Wy3VF.js +1 -0
- package/dist-embed/assets/createViewerLite-CP-WHGLg.js +707 -0
- package/dist-embed/assets/creator-gl-BF2CQzeI.wasm +0 -0
- package/dist-embed/assets/creator-gl.js-B_0GrQRe.symbols +5379 -0
- package/dist-embed/assets/creator-ui-BX5Fvnbt.js +1 -0
- package/dist-embed/assets/creator-ui-CzKHLH2Q.wasm +0 -0
- package/dist-embed/assets/creator2d-BtQ-1_B0.wasm +0 -0
- package/dist-embed/assets/draco-BiISTFcR.js +118 -0
- package/dist-embed/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/dist-embed/assets/host-CWKB1WDd.js +162 -0
- package/dist-embed/assets/index-CIK8PnUA.js +2 -0
- package/dist-embed/assets/lit-DUE9rhY9.filamat +0 -0
- package/dist-embed/assets/loadCreatorUiNode-UAdhjRTd.js +1 -0
- package/dist-embed/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
- package/dist-embed/assets/particles-DxYDmI1O.filamat +0 -0
- package/dist-embed/assets/particles-quad-DwYo9yCK.filamat +0 -0
- package/dist-embed/assets/shadow-C9xJpqlY.filamat +0 -0
- package/dist-embed/assets/uberarchive-uXjRYa9p.bin +0 -0
- package/dist-embed/assets/unlit-pe4tFY5b.filamat +0 -0
- package/dist-embed/assets/unlit-transparent-D9OyaOBR.filamat +0 -0
- package/dist-embed/assets/video-eedvhMvS.filamat +0 -0
- package/dist-embed/assets/worker-Caf-yYEI.js +2 -0
- package/dist-embed/embed.html +48 -0
- package/dist-embed/embed.js +187 -0
- package/embed.html +48 -0
- package/package.json +35 -0
- package/src/assets/banner.png +0 -0
- package/src/assets/image2.png +0 -0
- package/src/assets/landing.mp4 +0 -0
- package/src/assets/neutral_ibl128.ktx +0 -0
- package/src/assets/neutral_ibl256.ktx +0 -0
- package/src/assets/output.mp4 +0 -0
- package/src/assets/output2.mp4 +0 -0
- package/src/assets/texture.png +0 -0
- package/src/assets/vue.svg +1 -0
- package/src/canvas/createCanvasHost.ts +2 -0
- package/src/createViewer.ts +103 -0
- package/src/embed.ts +515 -0
- package/src/host.ts +428 -0
- package/src/lite-test.ts +160 -0
- package/src/main.ts +77 -0
- package/src/sourcemapError.ts +2 -0
- package/src/ui/CuiProps.gen.ts +103 -0
- package/src/ui/UIScreen.ts +486 -0
- package/src/ui/createBareUI.ts +32 -0
- package/src/ui/createMeasureSystem.ts +220 -0
- package/src/ui/createNodeElement.ts +77 -0
- package/src/ui/createPagerSystem.ts +345 -0
- package/src/ui/createTouchSystem.ts +2 -0
- package/src/ui/createUI.ts +806 -0
- package/src/ui/creator-ui.js +14 -0
- package/src/ui/creator-ui.wasm +0 -0
- package/src/ui/defaultStyle.css +176 -0
- package/src/ui/layers.ts +9 -0
- package/src/ui/mountCanvasUI.ts +213 -0
- package/src/ui/nativeViewSystem.ts +91 -0
- package/src/ui/presentPlayer.ts +156 -0
- package/src/ui/props-apply.ts +403 -0
- package/src/ui/router.ts +2 -0
- package/src/ui/safeAreaSpec.ts +3 -0
- package/src/ui/shells.ts +37 -0
- package/src/ui/toasts.ts +2 -0
- package/src/ui/transitions.ts +250 -0
- package/src/ui/ui.d.ts +78 -0
- package/src/ui/vListSystem.ts +212 -0
- package/src/ui/viewScale.ts +2 -0
- package/src/utils/animate.ts +2 -0
- package/src/utils/createUtils.ts +2 -0
- package/src/utils/fetch.ts +3 -0
- package/src/utils/fonts.ts +2 -0
- package/src/utils/globals.ts +2 -0
- package/src/utils/imageNode.ts +260 -0
- package/src/utils/openFilePicker.ts +2 -0
- package/src/utils/svg.ts +2 -0
- package/src/utils/videoNode.ts +2 -0
- package/src/utils/ws.ts +2 -0
- package/src/viewer/createViewer.ts +993 -0
- package/src/viewer/creator-gl.js +14 -0
- package/src/viewer/creator-gl.js.symbols +5379 -0
- package/src/viewer/creator-gl.wasm +0 -0
- package/src/viewer/lit.filamat +0 -0
- package/src/viewer/mediaplayer.ts +3 -0
- package/src/viewer/particles-quad.filamat +0 -0
- package/src/viewer/particles.filamat +0 -0
- package/src/viewer/physics.ts +140 -0
- package/src/viewer/shadow.filamat +0 -0
- package/src/viewer/uberarchive.bin +0 -0
- package/src/viewer/unlit-transparent.filamat +0 -0
- package/src/viewer/unlit.filamat +0 -0
- package/src/viewer/validateGlb.ts +262 -0
- package/src/viewer/video.filamat +0 -0
- package/src/viewer/wasmCrashSymbolizer.ts +133 -0
- package/src/viewer2d/createViewer2d.ts +438 -0
- package/src/viewer2d/creator2d.js +14 -0
- package/src/viewer2d/creator2d.wasm +0 -0
- package/src/viewer2d/sprite2d.ts +80 -0
- 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
|
+
}
|
package/src/utils/svg.ts
ADDED
package/src/utils/ws.ts
ADDED