lecodes-viewer 0.19.1 → 0.20.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-embed/VERSION.json +3 -3
- package/dist-embed/assets/{createViewer2d-D12Wy3VF.js → createViewer2d-Dx-COLjO.js} +1 -1
- package/dist-embed/assets/createViewerLite-DkM-O9BQ.js +852 -0
- package/dist-embed/assets/host-Czj6Nu-U.js +168 -0
- package/dist-embed/assets/{index-CIK8PnUA.js → index-mhLEVWyK.js} +1 -1
- package/dist-embed/assets/{loadCreatorUiNode-UAdhjRTd.js → loadCreatorUiNode-D87bZHrH.js} +1 -1
- package/dist-embed/assets/mapViewImpl-B2JcES8l.js +810 -0
- package/dist-embed/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
- package/dist-embed/embed.js +8 -8
- package/package.json +7 -3
- package/src/host.ts +3 -0
- package/src/ui/creator-ui.wasm +0 -0
- package/src/ui/nativeViewSystem.ts +2 -91
- package/src/ui/presentPlayer.ts +2 -156
- package/src/ui/vListSystem.ts +211 -211
- package/src/viewer/creator-gl.wasm +0 -0
- package/src/viewer2d/creator2d.wasm +0 -0
- package/dist-embed/assets/createViewerLite-CP-WHGLg.js +0 -707
- package/dist-embed/assets/host-CWKB1WDd.js +0 -162
- 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_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 +0 -1
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lecodes-viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"main": "src/createViewer.ts",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"lecodes-renderer": "0.
|
|
6
|
+
"lecodes-renderer": "0.20.0",
|
|
7
7
|
"ogl": "^1.0.11",
|
|
8
|
-
"lecodes-viewer-lite": "0.
|
|
8
|
+
"lecodes-viewer-lite": "0.20.0"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "vite",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"type": "module",
|
|
26
26
|
"files": [
|
|
27
27
|
"src",
|
|
28
|
+
"!src/assets/*.mp4",
|
|
29
|
+
"!src/assets/*.png",
|
|
30
|
+
"!src/assets/*.svg",
|
|
31
|
+
"!src/assets/neutral_ibl256.ktx",
|
|
28
32
|
"dist-embed",
|
|
29
33
|
"embed.html",
|
|
30
34
|
"README.md"
|
package/src/host.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { createCanvasHost } from './canvas/createCanvasHost'
|
|
|
13
13
|
import { putBuffer, getBuffer } from './utils/fetch'
|
|
14
14
|
import { createNavHostBridge } from 'lecodes-viewer-lite/src/gl/core/navBridge'
|
|
15
15
|
import { createInputHost } from "lecodes-viewer-lite/src/utils/input"
|
|
16
|
+
import { registerWebPlugins } from "lecodes-viewer-lite/src/plugins"
|
|
16
17
|
import { createUtils } from './utils/createUtils'
|
|
17
18
|
import { ensureFontsLoaded, loadBootFonts, type BootFont } from './utils/fonts'
|
|
18
19
|
import type { SafeAreaSpec } from './ui/safeAreaSpec'
|
|
@@ -217,6 +218,8 @@ export const createHost = async (root: HTMLElement, options: HostOptions = {}) =
|
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
const _creatorUI = options.ui ? await options.ui(root, canvasHost) : undefined
|
|
221
|
+
// The web twins of the first-party NativeView plugins (camera, map) — shared with the lite host.
|
|
222
|
+
registerWebPlugins(_creatorUI)
|
|
220
223
|
const _creatorUtils = createUtils({ launchUrl: options.launchUrl, onOrientation: options.onOrientation })
|
|
221
224
|
_creatorUI?._setCreator(_creator)
|
|
222
225
|
// Before the ResizeObserver's initial fire, so the first applied layout already has the insets.
|
package/src/ui/creator-ui.wasm
CHANGED
|
File without changes
|
|
@@ -1,91 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
// host._creatorUI._registerView("map", (params, channel) => {
|
|
5
|
-
// const el = document.createElement("div")
|
|
6
|
-
// return { el, call: (method, args) => {...}, destroy: () => {...} }
|
|
7
|
-
// })
|
|
8
|
-
//
|
|
9
|
-
// One SDK NativeView instance = one live DOM instance (keyed by its _viewId), reused across
|
|
10
|
-
// embed/fullscreen/pager mounts — that reuse IS the promotion semantics. An unregistered
|
|
11
|
-
// name renders a labeled placeholder so a bundle stays inspectable on hosts without the plugin.
|
|
12
|
-
|
|
13
|
-
export type WebViewChannel = { emit(event: string, data?: any): void }
|
|
14
|
-
export type WebViewInstanceHandle = {
|
|
15
|
-
el: HTMLElement
|
|
16
|
-
call?(method: string, args: any[]): any | Promise<any>
|
|
17
|
-
destroy?(): void
|
|
18
|
-
}
|
|
19
|
-
export type WebViewFactory = (params: any, channel: WebViewChannel) => WebViewInstanceHandle
|
|
20
|
-
|
|
21
|
-
// The SDK-side NativeView element/descriptor surface the system needs (see sdk NativeView.ts).
|
|
22
|
-
type SdkNativeView = {
|
|
23
|
-
viewName: string
|
|
24
|
-
params: any
|
|
25
|
-
_viewId: number
|
|
26
|
-
_emitViewEvent(event: string, dataJson?: string): void
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const createNativeViewSystem = () => {
|
|
30
|
-
const factories = new Map<string, WebViewFactory>()
|
|
31
|
-
const instances = new Map<number, WebViewInstanceHandle>()
|
|
32
|
-
|
|
33
|
-
const registerView = (name: string, factory: WebViewFactory) => { factories.set(name, factory) }
|
|
34
|
-
const isViewSupported = (name: string) => factories.has(name)
|
|
35
|
-
|
|
36
|
-
const placeholder = (name: string): WebViewInstanceHandle => {
|
|
37
|
-
const el = document.createElement("div")
|
|
38
|
-
el.style.cssText = "display:flex;align-items:center;justify-content:center;background:#333;color:#999;font:12px sans-serif"
|
|
39
|
-
el.textContent = `NativeView("${name}") — no web factory registered`
|
|
40
|
-
return { el }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Resolve (or lazily create) the live instance for an SDK NativeView. */
|
|
44
|
-
const ensureInstance = (view: SdkNativeView): WebViewInstanceHandle => {
|
|
45
|
-
let instance = instances.get(view._viewId)
|
|
46
|
-
if (!instance) {
|
|
47
|
-
const factory = factories.get(view.viewName)
|
|
48
|
-
instance = factory
|
|
49
|
-
? factory(view.params, { emit: (event, data) => view._emitViewEvent(event, data === undefined ? undefined : JSON.stringify(data)) })
|
|
50
|
-
: placeholder(view.viewName)
|
|
51
|
-
instances.set(view._viewId, instance)
|
|
52
|
-
}
|
|
53
|
-
return instance
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** The `_creatorUI.viewCall` bridge — JSON in/out, promise-friendly. */
|
|
57
|
-
const viewCall = (viewId: number, method: string, args: string, onComplete: (result?: string) => void, onError: (err: string) => void) => {
|
|
58
|
-
const instance = instances.get(viewId)
|
|
59
|
-
if (!instance?.call) {
|
|
60
|
-
onError(`NativeView call "${method}": no live instance or no call handler (viewId ${viewId})`)
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
Promise.resolve(instance.call(method, JSON.parse(args))).then(
|
|
65
|
-
(result) => onComplete(result === undefined ? undefined : JSON.stringify(result)),
|
|
66
|
-
(e) => onError(e instanceof Error ? e.message : String(e)),
|
|
67
|
-
)
|
|
68
|
-
} catch (e) {
|
|
69
|
-
onError(e instanceof Error ? e.message : String(e))
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Mount the live instance into a layout cell (the "native" node's DOM element). A view lives
|
|
74
|
-
* in ONE place: mounting here steals it from wherever it was (promotion works the same way
|
|
75
|
-
* in reverse — openView steals it from the cell, close returns it via re-mount). */
|
|
76
|
-
const mountInto = (view: SdkNativeView, cell: HTMLElement) => {
|
|
77
|
-
const { el } = ensureInstance(view)
|
|
78
|
-
el.style.position = "absolute"
|
|
79
|
-
el.style.inset = "0"
|
|
80
|
-
cell.appendChild(el)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const destroy = () => {
|
|
84
|
-
for (const i of instances.values()) i.destroy?.()
|
|
85
|
-
instances.clear()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { registerView, isViewSupported, ensureInstance, viewCall, mountInto, destroy }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export type NativeViewSystem = ReturnType<typeof createNativeViewSystem>
|
|
1
|
+
// Moved to viewer-lite (the NativeView registry + channel are shared by both web hosts); re-export shim.
|
|
2
|
+
export * from "lecodes-viewer-lite/src/ui/nativeViewSystem"
|
package/src/ui/presentPlayer.ts
CHANGED
|
@@ -1,156 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// so a custom spec from the SDK needs no extra machinery.
|
|
4
|
-
//
|
|
5
|
-
// The static rule (mirrors the Android SurfaceView constraint so apps feel the same): a side
|
|
6
|
-
// marked `static` (the scene canvas) never animates — only the screen side moves. Screens are
|
|
7
|
-
// position:absolute so they always paint above the in-flow canvas, which makes the degradations
|
|
8
|
-
// coherent: entering a scene slides the old screen away to reveal it; leaving a scene slides the
|
|
9
|
-
// new screen in over it.
|
|
10
|
-
|
|
11
|
-
export type SpecTransform = { x?: number | string, y?: number | string, scale?: number, opacity?: number }
|
|
12
|
-
export type TransitionSpecLike = {
|
|
13
|
-
duration?: number
|
|
14
|
-
easing?: [number, number, number, number]
|
|
15
|
-
incoming?: { from?: SpecTransform }
|
|
16
|
-
outgoing?: { to?: SpecTransform }
|
|
17
|
-
dim?: number
|
|
18
|
-
onTop?: "incoming" | "outgoing"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const STACK_EASING: [number, number, number, number] = [0.2, 0, 0, 1]
|
|
22
|
-
|
|
23
|
-
const NAMED: Record<string, TransitionSpecLike> = {
|
|
24
|
-
"none": {},
|
|
25
|
-
"fade": { duration: 280, incoming: { from: { opacity: 0 } } },
|
|
26
|
-
"slide-from-right": { duration: 400, incoming: { from: { x: "100%" } }, outgoing: { to: { x: "-30%" } } },
|
|
27
|
-
"slide-from-left": { duration: 400, incoming: { from: { x: "-100%" } }, outgoing: { to: { x: "30%" } } },
|
|
28
|
-
"slide-from-top": { duration: 400, incoming: { from: { y: "-100%" } }, outgoing: { to: { y: "30%" } } },
|
|
29
|
-
"slide-from-bottom": { duration: 400, incoming: { from: { y: "100%" } }, outgoing: { to: { y: "-30%" } } },
|
|
30
|
-
"zoom": { duration: 400, easing: [0.33, 1.2, 0.62, 1], incoming: { from: { scale: 0.985 } } },
|
|
31
|
-
"zoom-in": { duration: 400, easing: [0.33, 1.2, 0.62, 1], incoming: { from: { scale: 1.08, opacity: 0 } }, onTop: "incoming" },
|
|
32
|
-
"zoom-out": { duration: 420, outgoing: { to: { scale: 0.92, opacity: 0 } }, onTop: "outgoing" },
|
|
33
|
-
"push": { duration: 350, easing: STACK_EASING, incoming: { from: { x: "100%" } }, outgoing: { to: { x: "-25%" } }, dim: 0.3, onTop: "incoming" },
|
|
34
|
-
"pop": { duration: 350, easing: STACK_EASING, incoming: { from: { x: "-25%" } }, outgoing: { to: { x: "100%" } }, dim: 0.3, onTop: "outgoing" },
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const toTransform = (t: SpecTransform): string => {
|
|
38
|
-
const parts: string[] = []
|
|
39
|
-
if (t.x !== undefined || t.y !== undefined) {
|
|
40
|
-
const px = (v: number | string | undefined) => v === undefined ? "0px" : typeof v === "number" ? v + "px" : v
|
|
41
|
-
parts.push(`translate(${px(t.x)}, ${px(t.y)})`)
|
|
42
|
-
}
|
|
43
|
-
if (t.scale !== undefined) parts.push(`scale(${t.scale})`)
|
|
44
|
-
return parts.length > 0 ? parts.join(" ") : "none"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const poseKeyframe = (t: SpecTransform): Keyframe => {
|
|
48
|
-
const kf: Keyframe = { transform: toTransform(t) }
|
|
49
|
-
if (t.opacity !== undefined) kf.opacity = t.opacity
|
|
50
|
-
return kf
|
|
51
|
-
}
|
|
52
|
-
const IDENTITY: Keyframe = { transform: "none", opacity: 1 }
|
|
53
|
-
|
|
54
|
-
// The exit pose for an outgoing side when the incoming one is static (a scene): mirror the
|
|
55
|
-
// incoming entry so the reveal keeps the transition's direction (push's 100%-from-right becomes
|
|
56
|
-
// a full slide-out to the left); pure opacity entries become a fade-out.
|
|
57
|
-
const mirrorExit = (from: SpecTransform): SpecTransform => {
|
|
58
|
-
const neg = (v: number | string | undefined) =>
|
|
59
|
-
v === undefined ? undefined : typeof v === "number" ? -v : v.startsWith("-") ? v.slice(1) : "-" + v
|
|
60
|
-
const out: SpecTransform = {}
|
|
61
|
-
if (from.x !== undefined) out.x = neg(from.x)
|
|
62
|
-
if (from.y !== undefined) out.y = neg(from.y)
|
|
63
|
-
if (from.scale !== undefined) out.scale = 2 - from.scale
|
|
64
|
-
if (from.opacity !== undefined || Object.keys(out).length === 0) out.opacity = 0
|
|
65
|
-
return out
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
type Side = { el: HTMLElement, static?: boolean }
|
|
69
|
-
|
|
70
|
-
export const createPresentPlayer = (container: HTMLElement) => {
|
|
71
|
-
let finishActive: (() => void) | null = null
|
|
72
|
-
|
|
73
|
-
/** Play `transition` between the mounted `incoming` and the still-mounted `outgoing`; calls
|
|
74
|
-
* `onDone` exactly once (after the animation, or immediately for none/unsupported) — the
|
|
75
|
-
* caller physically unmounts the outgoing side there. A new play() snaps the previous
|
|
76
|
-
* transition to its end first. */
|
|
77
|
-
const play = (opts: {
|
|
78
|
-
incoming?: Side
|
|
79
|
-
outgoing?: Side
|
|
80
|
-
transition: string | TransitionSpecLike | null | undefined
|
|
81
|
-
onDone: () => void
|
|
82
|
-
}) => {
|
|
83
|
-
finishActive?.()
|
|
84
|
-
|
|
85
|
-
const spec = typeof opts.transition === "string" ? NAMED[opts.transition] : opts.transition ?? undefined
|
|
86
|
-
const inSide = opts.incoming && !opts.incoming.static ? opts.incoming.el : null
|
|
87
|
-
const outSide = opts.outgoing && !opts.outgoing.static ? opts.outgoing.el : null
|
|
88
|
-
|
|
89
|
-
let inFrom = spec?.incoming?.from
|
|
90
|
-
let outTo = spec?.outgoing?.to
|
|
91
|
-
|
|
92
|
-
// Static-side degradations (see header). A poseless spec ("none") stays instant — the
|
|
93
|
-
// degradations only reshape transitions that actually wanted to move something.
|
|
94
|
-
if (opts.incoming?.static && outSide && (spec?.incoming || spec?.outgoing)) {
|
|
95
|
-
// The incoming scene can't animate: unless this is an exit-shaped transition (onTop:
|
|
96
|
-
// "outgoing" — pop/zoom-out already fully remove the old side), the outgoing screen leaves
|
|
97
|
-
// by the mirror of the entry it was supposed to be covered by.
|
|
98
|
-
if (spec?.onTop !== "outgoing") outTo = inFrom ? mirrorExit(inFrom) : (outTo ?? { opacity: 0 })
|
|
99
|
-
inFrom = undefined
|
|
100
|
-
}
|
|
101
|
-
if (opts.outgoing?.static && inSide && !inFrom && spec && Object.keys(spec).length > 0) {
|
|
102
|
-
inFrom = { opacity: 0 } // something must move; fade the screen in over the scene
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const hasWork = (inSide && inFrom) || (outSide && outTo)
|
|
106
|
-
if (!spec || !hasWork || typeof (inSide ?? outSide)?.animate !== "function") {
|
|
107
|
-
opts.onDone()
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const duration = spec.duration ?? 300
|
|
112
|
-
const easing = spec.easing ? `cubic-bezier(${spec.easing.join(",")})` : "cubic-bezier(0.25, 0.1, 0.25, 1)"
|
|
113
|
-
const animations: Animation[] = []
|
|
114
|
-
|
|
115
|
-
// z-order for the duration of the transition (screens only — the static canvas stays in
|
|
116
|
-
// flow at the bottom). Dim sits between the two.
|
|
117
|
-
const onTop = spec.onTop ?? "incoming"
|
|
118
|
-
const topEl = onTop === "incoming" ? inSide : outSide
|
|
119
|
-
const bottomEl = onTop === "incoming" ? outSide : inSide
|
|
120
|
-
if (topEl) topEl.style.zIndex = "3"
|
|
121
|
-
if (bottomEl) bottomEl.style.zIndex = "1"
|
|
122
|
-
|
|
123
|
-
let dimEl: HTMLElement | null = null
|
|
124
|
-
if (spec.dim && spec.dim > 0) {
|
|
125
|
-
dimEl = document.createElement("div")
|
|
126
|
-
dimEl.style.cssText = "position:absolute;inset:0;background:#000;pointer-events:none;z-index:2"
|
|
127
|
-
container.appendChild(dimEl)
|
|
128
|
-
// Entering (push): the under layer darkens; exiting (pop): it un-darkens.
|
|
129
|
-
const [a, b] = onTop === "incoming" ? [0, spec.dim] : [spec.dim, 0]
|
|
130
|
-
animations.push(dimEl.animate([{ opacity: a }, { opacity: b }], { duration, easing }))
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (inSide && inFrom) {
|
|
134
|
-
animations.push(inSide.animate([poseKeyframe(inFrom), IDENTITY], { duration, easing }))
|
|
135
|
-
}
|
|
136
|
-
if (outSide && outTo) {
|
|
137
|
-
animations.push(outSide.animate([IDENTITY, poseKeyframe(outTo)], { duration, easing, fill: "forwards" }))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
let done = false
|
|
141
|
-
const cleanup = () => {
|
|
142
|
-
if (done) return
|
|
143
|
-
done = true
|
|
144
|
-
finishActive = null
|
|
145
|
-
for (const a of animations) a.cancel() // drops fill:forwards so a detached-not-freed screen restores clean
|
|
146
|
-
if (topEl) topEl.style.zIndex = ""
|
|
147
|
-
if (bottomEl) bottomEl.style.zIndex = ""
|
|
148
|
-
dimEl?.remove()
|
|
149
|
-
opts.onDone()
|
|
150
|
-
}
|
|
151
|
-
finishActive = cleanup
|
|
152
|
-
Promise.all(animations.map(a => a.finished)).then(cleanup, cleanup)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return { play, finish: () => finishActive?.() }
|
|
156
|
-
}
|
|
1
|
+
// Moved to viewer-lite (both web hosts play the same destination transitions); re-export shim.
|
|
2
|
+
export * from "lecodes-viewer-lite/src/ui/presentPlayer"
|