@bardway/trpg-media 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/NOTICE +10 -0
- package/NOTICE.md +1 -0
- package/README.md +23 -2
- package/dist/AudioByteCache.d.ts +8 -0
- package/dist/DisplayAudioController.d.ts +2 -0
- package/dist/MediaPreloader.d.ts +12 -0
- package/dist/RuntimeLogger.d.ts +4 -0
- package/dist/audio/HtmlAudioSessionBackend.d.ts +36 -0
- package/dist/audio/SessionAudioBackend.d.ts +19 -0
- package/dist/audio/SessionAudioEngine.d.ts +93 -0
- package/dist/audio/SessionAudioPlaybackPolicy.d.ts +8 -0
- package/dist/audio.d.ts +5 -0
- package/dist/audio.js +625 -0
- package/dist/audio.js.map +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +493 -0
- package/dist/index.js.map +7 -0
- package/dist/sessionDisplayPreload.d.ts +15 -0
- package/dist/sessionDisplayResourceRetry.d.ts +11 -0
- package/dist/tuning/SessionTuningMigration.d.ts +9 -0
- package/dist/tuning/SessionTuningResolver.d.ts +12 -0
- package/dist/tuning/SessionTuningSchema.d.ts +27 -0
- package/dist/tuning/SessionTuningStore.d.ts +31 -0
- package/dist/tuning/SessionTuningTypes.d.ts +50 -0
- package/dist/tuning.d.ts +5 -0
- package/dist/tuning.js +292 -0
- package/dist/tuning.js.map +7 -0
- package/dist/video-poster.d.ts +77 -0
- package/dist/video.js +259 -0
- package/dist/video.js.map +7 -0
- package/package.json +49 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/sessionDisplayResourceRetry.ts", "../src/sessionDisplayPreload.ts", "../src/DisplayAudioController.ts", "../src/tuning/SessionTuningTypes.ts", "../src/tuning/SessionTuningResolver.ts", "../src/tuning/SessionTuningSchema.ts", "../src/tuning/SessionTuningMigration.ts", "../src/tuning/SessionTuningStore.ts", "../src/MediaPreloader.ts", "../src/AudioByteCache.ts"],
|
|
4
|
+
"sourcesContent": ["export const SESSION_DISPLAY_RESOURCE_RETRY_COUNT = 3;\nexport const SESSION_DISPLAY_RESOURCE_RETRY_DELAY_MS = 500;\n\n/** A caller may cancel its wait without cancelling a retained media resource. */\nexport const waitForSessionMediaReady = (\n ready: Promise<boolean> | null,\n signal: AbortSignal,\n): Promise<boolean> => {\n if (!ready || signal.aborted) return Promise.resolve(false);\n return new Promise((resolve) => {\n const finish = (result: boolean) => {\n signal.removeEventListener('abort', abort);\n resolve(result);\n };\n const abort = () => finish(false);\n signal.addEventListener('abort', abort, { once: true });\n void ready.then(finish, () => finish(false));\n if (signal.aborted) abort();\n });\n};\n\ninterface SessionDisplayResourceRetryOptions {\n readonly signal?: AbortSignal;\n readonly retryCount?: number;\n readonly retryDelayMs?: number;\n}\n\nconst createAbortError = (): DOMException =>\n new DOMException('Session display resource load was aborted', 'AbortError');\n\nconst waitForRetry = (delayMs: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(createAbortError());\n return;\n }\n const timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', handleAbort);\n resolve();\n }, delayMs);\n const handleAbort = () => {\n clearTimeout(timeoutId);\n reject(createAbortError());\n };\n signal?.addEventListener('abort', handleAbort, { once: true });\n });\n\nexport const retrySessionDisplayResource = async <T>(\n operation: () => Promise<T>,\n options: SessionDisplayResourceRetryOptions = {},\n): Promise<T> => {\n const retryCount = Math.max(0, options.retryCount ?? SESSION_DISPLAY_RESOURCE_RETRY_COUNT);\n const retryDelayMs = Math.max(0, options.retryDelayMs ?? SESSION_DISPLAY_RESOURCE_RETRY_DELAY_MS);\n let attempt = 0;\n while (true) {\n if (options.signal?.aborted) throw createAbortError();\n try {\n return await operation();\n } catch (error) {\n if (options.signal?.aborted) throw createAbortError();\n if (attempt >= retryCount) throw error;\n attempt += 1;\n await waitForRetry(retryDelayMs, options.signal);\n }\n }\n};\n", "import type { RuntimeLogger } from './RuntimeLogger.js';\n\nexport const normalizeBattlePreloadUrls = (urls: Array<string | null | undefined>): string[] => {\n const normalized = new Set<string>();\n urls.forEach((url) => {\n const trimmed = url?.trim();\n if (trimmed) {\n normalized.add(trimmed);\n }\n });\n return [...normalized];\n};\n\nconst registerImagePreloadLink = (url: string): void => {\n if (typeof document === 'undefined') {\n return;\n }\n const existing = [...document.head.querySelectorAll<HTMLLinkElement>('link[rel=\"preload\"]')].some(\n (link) => link.as === 'image' && link.href === new URL(url, window.location.href).href,\n );\n if (existing) {\n return;\n }\n\n const link = document.createElement('link');\n link.rel = 'preload';\n link.as = 'image';\n link.crossOrigin = 'anonymous';\n link.href = url;\n link.dataset.owner = 'battle-display-preload';\n document.head.appendChild(link);\n};\n\nconst loadImage = async (url: string): Promise<void> => {\n await new Promise<void>((resolve, reject) => {\n const image = new Image();\n image.decoding = 'async';\n image.loading = 'eager';\n image.crossOrigin = 'anonymous';\n image.onload = () => resolve();\n image.onerror = () => reject(new Error(`\u56FE\u7247\u9884\u52A0\u8F7D\u5931\u8D25: ${url}`));\n image.src = url;\n\n if (typeof image.decode === 'function') {\n void image\n .decode()\n .then(resolve)\n .catch(() => undefined);\n }\n if (image.complete) {\n resolve();\n }\n });\n};\n\ntype BattleDisplayPreloadEntry = {\n status: 'loading' | 'loaded' | 'failed';\n promise?: Promise<boolean>;\n};\n\nexport type BattleDisplayPreloadState = Map<string, BattleDisplayPreloadEntry>;\n\nexport const createBattleDisplayPreloadState = (): BattleDisplayPreloadState => new Map();\n\nexport const preloadBattleDisplayImages = async (\n urls: Array<string | null | undefined>,\n state: BattleDisplayPreloadState,\n options: { wait?: boolean; retryFailed?: boolean; scope: string; logger: RuntimeLogger },\n): Promise<boolean> => {\n const tasks = normalizeBattlePreloadUrls(urls).map((url): Promise<boolean> => {\n const existing = state.get(url);\n if (existing?.status === 'loaded') return Promise.resolve(true);\n if (existing?.status === 'loading' && existing.promise) return existing.promise;\n if (existing?.status === 'failed' && !options.retryFailed) return Promise.resolve(false);\n\n registerImagePreloadLink(url);\n const promise = loadImage(url)\n .then(() => {\n state.set(url, { status: 'loaded' });\n return true;\n })\n .catch((err: unknown) => {\n state.set(url, { status: 'failed' });\n options.logger.warn('[BattleDisplay] \u8D44\u6E90\u9884\u52A0\u8F7D\u5931\u8D25', { scope: options.scope, url, err });\n return false;\n });\n state.set(url, { status: 'loading', promise });\n return promise;\n });\n if (tasks.length === 0) return true;\n return (await Promise.all(tasks)).every(Boolean);\n};\n", "export type DisplayAudioState = 'initializing' | 'ready' | 'blocked' | 'failed';\n\nexport const classifyDisplayAudioFailure = (error: unknown): DisplayAudioState => {\n if (error instanceof DOMException && error.name === 'NotAllowedError') return 'blocked';\n return 'failed';\n};\n", "export type SessionDisplayScene = string;\n\nexport const SESSION_TUNING_SCHEMA_VERSION = 11 as const;\n\nexport type SessionTuningCategory = 'layout' | 'visual' | 'motion' | 'audio';\nexport type SessionTuningPrimitive = string | number | boolean | null;\nexport type SessionTuningValue =\n | SessionTuningPrimitive\n | readonly SessionTuningValue[]\n | { readonly [key: string]: SessionTuningValue };\n\nexport type SessionTuningDocument = {\n schemaVersion: typeof SESSION_TUNING_SCHEMA_VERSION;\n values: Record<string, SessionTuningValue>;\n};\n\nexport interface SessionTuningBundle {\n layout: SessionTuningDocument;\n visual: SessionTuningDocument;\n motion: SessionTuningDocument;\n audio: SessionTuningDocument;\n}\n\nexport type SessionTuningPath = `${SessionTuningCategory}.${string}`;\n\nexport type SessionTunableType =\n | 'number'\n | 'integer'\n | 'boolean'\n | 'color'\n | 'enum'\n | 'vec2'\n | 'rect'\n | 'range'\n | 'easing'\n | 'asset'\n | 'audioCue';\n\nexport interface TunableDescriptor {\n id: string;\n path: SessionTuningPath;\n label: string;\n description: string;\n category: SessionTuningCategory;\n group: string;\n scene?: SessionDisplayScene;\n slotId?: string;\n type: SessionTunableType;\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n options?: readonly string[];\n previewId?: string;\n updateMode?: 'live' | 'localRebuild' | 'sceneRebuild';\n readOnly?: boolean;\n}\n\nexport interface SessionTuningChange {\n path: SessionTuningPath;\n before: SessionTuningValue | undefined;\n after: SessionTuningValue | undefined;\n}\n\nexport interface SessionTuningSnapshot {\n readonly formal: SessionTuningBundle;\n readonly workspaceSaved: SessionTuningBundle;\n readonly working: SessionTuningBundle;\n readonly formalDirtyPaths: readonly SessionTuningPath[];\n readonly workspaceDirtyPaths: readonly SessionTuningPath[];\n}\n", "import type {\n SessionTuningBundle,\n SessionTuningCategory,\n SessionTuningPath,\n SessionTuningValue,\n} from './SessionTuningTypes.js';\n\nconst clone = <T>(value: T): T => structuredClone(value);\n\nconst isRecord = (value: unknown): value is { readonly [key: string]: SessionTuningValue } =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst resolvePath = (\n bundle: SessionTuningBundle,\n path: SessionTuningPath,\n): SessionTuningValue | undefined => {\n const [category, ...parts] = path.split('.') as [SessionTuningCategory, ...string[]];\n let value: SessionTuningValue | undefined = bundle[category].values;\n for (const part of parts) {\n if (!isRecord(value)) return undefined;\n value = value[part];\n }\n return value;\n};\n\nexport interface SessionTuningResolver {\n get(path: SessionTuningPath): SessionTuningValue | undefined;\n getRect(path: SessionTuningPath): { x: number; y: number; width: number; height: number } | null;\n snapshot(): SessionTuningBundle;\n}\n\nexport const createSessionTuningResolver = (\n bundle: SessionTuningBundle,\n): SessionTuningResolver => ({\n get: (path) => clone(resolvePath(bundle, path)),\n getRect: (path) => {\n const value = resolvePath(bundle, path);\n if (!isRecord(value)) return null;\n const { x, y, width, height } = value;\n if (![x, y, width, height].every((item) => typeof item === 'number' && Number.isFinite(item)))\n return null;\n return { x: x as number, y: y as number, width: width as number, height: height as number };\n },\n snapshot: () => clone(bundle),\n});\n", "import { z } from 'zod';\nimport {\n SESSION_TUNING_SCHEMA_VERSION,\n type SessionTuningBundle,\n type SessionTuningDocument,\n type SessionTuningValue,\n} from './SessionTuningTypes.js';\n\nconst hexColor = /^#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$/;\n\nconst valueSchema: z.ZodType<SessionTuningValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number().finite(),\n z.boolean(),\n z.null(),\n z.array(valueSchema),\n z.record(z.string(), valueSchema),\n ]),\n);\n\nexport const sessionTuningDocumentSchema = z.object({\n schemaVersion: z.literal(SESSION_TUNING_SCHEMA_VERSION),\n values: z.record(z.string(), valueSchema),\n});\n\nexport const sessionTuningBundleSchema = z.object({\n layout: sessionTuningDocumentSchema,\n visual: sessionTuningDocumentSchema,\n motion: sessionTuningDocumentSchema,\n audio: sessionTuningDocumentSchema,\n});\n\nexport const validateTuningColor = (value: unknown): value is string =>\n typeof value === 'string' && hexColor.test(value);\n\nexport const parseSessionTuningDocument = (value: unknown): SessionTuningDocument =>\n sessionTuningDocumentSchema.parse(value);\n\nexport const parseSessionTuningBundle = (value: unknown): SessionTuningBundle =>\n sessionTuningBundleSchema.parse(value);\n", "import { parseSessionTuningDocument } from './SessionTuningSchema.js';\nimport { SESSION_TUNING_SCHEMA_VERSION, type SessionTuningDocument } from './SessionTuningTypes.js';\n\nexport interface SessionTuningMigrationResult {\n document: SessionTuningDocument;\n fromVersion: typeof SESSION_TUNING_SCHEMA_VERSION;\n removedPaths: readonly [];\n}\n\n/** Validates the current tuning contract. Legacy tuning documents are rejected. */\nexport const migrateSessionTuningDocumentWithResult = (\n input: unknown,\n): SessionTuningMigrationResult => {\n if (\n !input ||\n typeof input !== 'object' ||\n Array.isArray(input) ||\n (input as { schemaVersion?: unknown }).schemaVersion !== SESSION_TUNING_SCHEMA_VERSION\n ) {\n throw new Error(`\u4E0D\u652F\u6301\u7248\u672C: Visual Lab \u4EC5\u652F\u6301 tuning v${SESSION_TUNING_SCHEMA_VERSION}`);\n }\n const document = parseSessionTuningDocument(input);\n return {\n document,\n fromVersion: SESSION_TUNING_SCHEMA_VERSION,\n removedPaths: [],\n };\n};\n\nexport const migrateSessionTuningDocument = (input: unknown): SessionTuningDocument =>\n migrateSessionTuningDocumentWithResult(input).document;\n", "import {\n createSessionTuningResolver,\n type SessionTuningResolver,\n} from './SessionTuningResolver.js';\nimport { parseSessionTuningBundle } from './SessionTuningSchema.js';\n\nimport type {\n SessionTuningBundle,\n SessionTuningChange,\n SessionTuningPath,\n SessionTuningSnapshot,\n SessionTuningValue,\n} from './SessionTuningTypes.js';\n\nconst clone = <T>(value: T): T => structuredClone(value);\n\nconst hasOwn = (value: object, key: PropertyKey): boolean =>\n Object.prototype.hasOwnProperty.call(value, key);\n\nconst isTuningRecord = (value: SessionTuningValue): value is Record<string, SessionTuningValue> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst mergeTuningValue = (\n formal: SessionTuningValue,\n workspace: SessionTuningValue,\n): SessionTuningValue => {\n if (!isTuningRecord(formal) || !isTuningRecord(workspace)) return clone(workspace);\n const merged: Record<string, SessionTuningValue> = {};\n for (const [key, formalValue] of Object.entries(formal)) {\n merged[key] = hasOwn(workspace, key)\n ? mergeTuningValue(formalValue, workspace[key]!)\n : clone(formalValue);\n }\n return merged;\n};\n\nconst mergeWorkspaceWithFormal = (\n formal: SessionTuningBundle,\n workspace: SessionTuningBundle,\n): SessionTuningBundle => ({\n layout: {\n schemaVersion: formal.layout.schemaVersion,\n values: mergeTuningValue(formal.layout.values, workspace.layout.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n visual: {\n schemaVersion: formal.visual.schemaVersion,\n values: mergeTuningValue(formal.visual.values, workspace.visual.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n motion: {\n schemaVersion: formal.motion.schemaVersion,\n values: mergeTuningValue(formal.motion.values, workspace.motion.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n audio: {\n schemaVersion: formal.audio.schemaVersion,\n values: mergeTuningValue(formal.audio.values, workspace.audio.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n});\n\nconst tuningValuesEqual = (\n left: SessionTuningValue | undefined,\n right: SessionTuningValue | undefined,\n): boolean => JSON.stringify(left) === JSON.stringify(right);\n\nconst setPath = (\n bundle: SessionTuningBundle,\n path: SessionTuningPath,\n value: SessionTuningValue | undefined,\n): void => {\n const [category, ...parts] = path.split('.') as [keyof SessionTuningBundle, ...string[]];\n if (\n !['layout', 'visual', 'motion', 'audio'].includes(category) ||\n parts.length === 0 ||\n parts.some((part) => !part || ['__proto__', 'prototype', 'constructor'].includes(part))\n )\n throw new Error('Invalid tuning path');\n let target = bundle[category].values as Record<string, SessionTuningValue>;\n for (const part of parts.slice(0, -1)) {\n const child = target[part];\n if (typeof child !== 'object' || child === null || Array.isArray(child)) target[part] = {};\n target = target[part] as Record<string, SessionTuningValue>;\n }\n const last = parts.at(-1);\n if (!last) return;\n if (value === undefined) delete target[last];\n else target[last] = clone(value);\n};\n\nconst rebaseTuningBundle = (\n previousFormal: SessionTuningBundle,\n value: SessionTuningBundle,\n nextFormal: SessionTuningBundle,\n editablePaths: readonly SessionTuningPath[],\n): SessionTuningBundle => {\n const normalizedValue = mergeWorkspaceWithFormal(previousFormal, value);\n const previousFormalResolver = createSessionTuningResolver(previousFormal);\n const valueResolver = createSessionTuningResolver(normalizedValue);\n const nextFormalResolver = createSessionTuningResolver(nextFormal);\n const rebased = clone(nextFormal);\n\n for (const path of editablePaths) {\n const previousFormalValue = previousFormalResolver.get(path);\n const nextFormalValue = nextFormalResolver.get(path);\n if (!tuningValuesEqual(previousFormalValue, nextFormalValue)) continue;\n setPath(rebased, path, valueResolver.get(path));\n }\n return rebased;\n};\n\nexport class SessionTuningStore {\n private formal: SessionTuningBundle;\n private workspaceSaved: SessionTuningBundle;\n private working: SessionTuningBundle;\n private readonly undoStack: SessionTuningChange[][] = [];\n private readonly redoStack: SessionTuningChange[][] = [];\n private readonly listeners = new Set<() => void>();\n\n constructor(\n defaults: SessionTuningBundle,\n private readonly editablePaths: readonly SessionTuningPath[] = flatten(defaults),\n ) {\n this.formal = clone(defaults);\n this.workspaceSaved = clone(defaults);\n this.working = clone(defaults);\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n getResolver(): SessionTuningResolver {\n return createSessionTuningResolver(this.working);\n }\n getFormalResolver(): SessionTuningResolver {\n return createSessionTuningResolver(this.formal);\n }\n getWorkspaceSavedResolver(): SessionTuningResolver {\n return createSessionTuningResolver(this.workspaceSaved);\n }\n getSnapshot(): SessionTuningSnapshot {\n return {\n formal: clone(this.formal),\n workspaceSaved: clone(this.workspaceSaved),\n working: clone(this.working),\n formalDirtyPaths: this.diff().map((change) => change.path),\n workspaceDirtyPaths: this.workspaceDiff().map((change) => change.path),\n };\n }\n patch(changes: readonly SessionTuningChange[], record = true): void {\n if (changes.length === 0) return;\n const next = clone(this.working);\n for (const change of changes) setPath(next, change.path, change.after);\n this.working = parseSessionTuningBundle(next);\n if (record) {\n this.undoStack.push(changes.map(clone));\n this.redoStack.length = 0;\n }\n this.emit();\n }\n set(path: SessionTuningPath, value: SessionTuningValue): void {\n this.patch([{ path, before: this.getResolver().get(path), after: value }]);\n }\n resetPath(path: SessionTuningPath): void {\n this.patch([\n {\n path,\n before: this.getResolver().get(path),\n after: this.getWorkspaceSavedResolver().get(path),\n },\n ]);\n }\n resetAll(): void {\n this.working = clone(this.workspaceSaved);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n saveWorkspace(): void {\n this.workspaceSaved = clone(this.working);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n markApplied(paths: readonly SessionTuningPath[]): void {\n if (paths.length === 0) return;\n const resolver = this.getResolver();\n for (const path of paths) setPath(this.formal, path, resolver.get(path));\n // An undo after writing source defaults would only change the workspace and\n // would no longer describe the formal configuration on disk.\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n loadWorkspace(value: SessionTuningBundle, formalBaseline?: SessionTuningBundle): void {\n const workspace = formalBaseline\n ? rebaseTuningBundle(formalBaseline, value, this.formal, this.editablePaths)\n : clone(this.formal);\n this.working = clone(workspace);\n this.workspaceSaved = clone(workspace);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n rebaseFormal(nextFormal: SessionTuningBundle): void {\n if (JSON.stringify(this.formal) === JSON.stringify(nextFormal)) return;\n const previousFormal = this.formal;\n this.workspaceSaved = rebaseTuningBundle(\n previousFormal,\n this.workspaceSaved,\n nextFormal,\n this.editablePaths,\n );\n this.working = rebaseTuningBundle(previousFormal, this.working, nextFormal, this.editablePaths);\n this.formal = clone(nextFormal);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n undo(): void {\n const changes = this.undoStack.pop();\n if (!changes) return;\n for (const change of changes) setPath(this.working, change.path, change.before);\n this.redoStack.push(changes);\n this.emit();\n }\n redo(): void {\n const changes = this.redoStack.pop();\n if (!changes) return;\n for (const change of changes) setPath(this.working, change.path, change.after);\n this.undoStack.push(changes);\n this.emit();\n }\n diff(): SessionTuningChange[] {\n return this.diffFrom(this.formal);\n }\n workspaceDiff(): SessionTuningChange[] {\n return this.diffFrom(this.workspaceSaved);\n }\n private diffFrom(baseline: SessionTuningBundle): SessionTuningChange[] {\n const paths = new Set([...flatten(baseline), ...flatten(this.working)]);\n const saved = createSessionTuningResolver(baseline);\n const working = this.getResolver();\n const changes: SessionTuningChange[] = [];\n for (const path of paths) {\n const before = saved.get(path);\n const after = working.get(path);\n if (JSON.stringify(before) !== JSON.stringify(after)) changes.push({ path, before, after });\n }\n return changes;\n }\n private emit(): void {\n for (const listener of this.listeners) listener();\n }\n}\n\nconst flatten = (bundle: SessionTuningBundle): SessionTuningPath[] => {\n const paths: SessionTuningPath[] = [];\n const visit = (value: SessionTuningValue, prefix: string): void => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n paths.push(prefix as SessionTuningPath);\n return;\n }\n for (const [key, child] of Object.entries(value)) visit(child, `${prefix}.${key}`);\n };\n for (const [category, document] of Object.entries(bundle)) visit(document.values, category);\n return paths;\n};\n", "import { retrySessionDisplayResource } from './sessionDisplayResourceRetry.js';\nimport type { AudioByteCache } from './AudioByteCache.js';\nexport interface SessionMediaPreloadResult {\n readonly failedUrls: readonly string[];\n}\nconst normalizeUrls = (urls: Iterable<string | null | undefined>) => [\n ...new Set([...urls].map((url) => url?.trim()).filter((url): url is string => Boolean(url))),\n];\nexport const preloadSessionAudio = async (\n urls: readonly string[],\n options: {\n readonly signal: AbortSignal;\n readonly onProgress: (completed: number, total: number) => void;\n readonly concurrency?: number;\n readonly cache: AudioByteCache;\n readonly retryCount?: number;\n readonly retryDelayMs?: number;\n },\n): Promise<SessionMediaPreloadResult> => {\n const queue = normalizeUrls(urls);\n const failedUrls: string[] = [];\n let cursor = 0;\n let completed = 0;\n const worker = async (): Promise<void> => {\n while (!options.signal.aborted) {\n const index = cursor;\n cursor += 1;\n const url = queue[index];\n if (!url) return;\n try {\n await retrySessionDisplayResource(() => options.cache.load(url), {\n signal: options.signal,\n retryCount: options.retryCount,\n retryDelayMs: options.retryDelayMs,\n });\n } catch {\n if (!options.signal.aborted) failedUrls.push(url);\n } finally {\n completed += 1;\n options.onProgress(completed, queue.length);\n }\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(Math.max(1, options.concurrency ?? 6), queue.length) }, worker),\n );\n return { failedUrls };\n};\n", "/** Instance ownership allows a host to share a preload cache without sharing accounts or sessions. */\nexport class AudioByteCache {\n private readonly records = new Map<\n string,\n { controller: AbortController; promise: Promise<ArrayBuffer> }\n >();\n constructor(private readonly fetchAudio: typeof fetch = fetch) {}\n\n async load(url: string): Promise<ArrayBuffer> {\n let record = this.records.get(url);\n if (!record) {\n const controller = new AbortController();\n const promise = this.fetchAudio(url, { signal: controller.signal }).then(async (response) => {\n if (!response.ok) throw new Error(`Audio request failed: ${response.status}`);\n const bytes = await response.arrayBuffer();\n if (controller.signal.aborted) throw new DOMException('Audio load cancelled', 'AbortError');\n return bytes;\n });\n record = { controller, promise };\n this.records.set(url, record);\n const owner = record;\n void promise.catch(() => {\n if (this.records.get(url) === owner) this.records.delete(url);\n });\n }\n return (await record.promise).slice(0);\n }\n\n clear(): void {\n for (const record of this.records.values()) record.controller.abort();\n this.records.clear();\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAAO,IAAM,uCAAuC;AAC7C,IAAM,0CAA0C;AAGhD,IAAM,2BAA2B,CACtC,OACA,WACqB;AACrB,MAAI,CAAC,SAAS,OAAO,QAAS,QAAO,QAAQ,QAAQ,KAAK;AAC1D,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,SAAS,CAAC,WAAoB;AAClC,aAAO,oBAAoB,SAAS,KAAK;AACzC,cAAQ,MAAM;AAAA,IAChB;AACA,UAAM,QAAQ,MAAM,OAAO,KAAK;AAChC,WAAO,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AACtD,SAAK,MAAM,KAAK,QAAQ,MAAM,OAAO,KAAK,CAAC;AAC3C,QAAI,OAAO,QAAS,OAAM;AAAA,EAC5B,CAAC;AACH;AAQA,IAAM,mBAAmB,MACvB,IAAI,aAAa,6CAA6C,YAAY;AAE5E,IAAM,eAAe,CAAC,SAAiB,WACrC,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC/B,MAAI,QAAQ,SAAS;AACnB,WAAO,iBAAiB,CAAC;AACzB;AAAA,EACF;AACA,QAAM,YAAY,WAAW,MAAM;AACjC,YAAQ,oBAAoB,SAAS,WAAW;AAChD,YAAQ;AAAA,EACV,GAAG,OAAO;AACV,QAAM,cAAc,MAAM;AACxB,iBAAa,SAAS;AACtB,WAAO,iBAAiB,CAAC;AAAA,EAC3B;AACA,UAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,KAAK,CAAC;AAC/D,CAAC;AAEI,IAAM,8BAA8B,OACzC,WACA,UAA8C,CAAC,MAChC;AACf,QAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,cAAc,oCAAoC;AACzF,QAAM,eAAe,KAAK,IAAI,GAAG,QAAQ,gBAAgB,uCAAuC;AAChG,MAAI,UAAU;AACd,SAAO,MAAM;AACX,QAAI,QAAQ,QAAQ,QAAS,OAAM,iBAAiB;AACpD,QAAI;AACF,aAAO,MAAM,UAAU;AAAA,IACzB,SAAS,OAAO;AACd,UAAI,QAAQ,QAAQ,QAAS,OAAM,iBAAiB;AACpD,UAAI,WAAW,WAAY,OAAM;AACjC,iBAAW;AACX,YAAM,aAAa,cAAc,QAAQ,MAAM;AAAA,IACjD;AAAA,EACF;AACF;;;AC/DO,IAAM,6BAA6B,CAAC,SAAqD;AAC9F,QAAM,aAAa,oBAAI,IAAY;AACnC,OAAK,QAAQ,CAAC,QAAQ;AACpB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,SAAS;AACX,iBAAW,IAAI,OAAO;AAAA,IACxB;AAAA,EACF,CAAC;AACD,SAAO,CAAC,GAAG,UAAU;AACvB;AAEA,IAAM,2BAA2B,CAAC,QAAsB;AACtD,MAAI,OAAO,aAAa,aAAa;AACnC;AAAA,EACF;AACA,QAAM,WAAW,CAAC,GAAG,SAAS,KAAK,iBAAkC,qBAAqB,CAAC,EAAE;AAAA,IAC3F,CAACA,UAASA,MAAK,OAAO,WAAWA,MAAK,SAAS,IAAI,IAAI,KAAK,OAAO,SAAS,IAAI,EAAE;AAAA,EACpF;AACA,MAAI,UAAU;AACZ;AAAA,EACF;AAEA,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,OAAK,MAAM;AACX,OAAK,KAAK;AACV,OAAK,cAAc;AACnB,OAAK,OAAO;AACZ,OAAK,QAAQ,QAAQ;AACrB,WAAS,KAAK,YAAY,IAAI;AAChC;AAEA,IAAM,YAAY,OAAO,QAA+B;AACtD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,QAAQ,IAAI,MAAM;AACxB,UAAM,WAAW;AACjB,UAAM,UAAU;AAChB,UAAM,cAAc;AACpB,UAAM,SAAS,MAAM,QAAQ;AAC7B,UAAM,UAAU,MAAM,OAAO,IAAI,MAAM,+CAAY,GAAG,EAAE,CAAC;AACzD,UAAM,MAAM;AAEZ,QAAI,OAAO,MAAM,WAAW,YAAY;AACtC,WAAK,MACF,OAAO,EACP,KAAK,OAAO,EACZ,MAAM,MAAM,MAAS;AAAA,IAC1B;AACA,QAAI,MAAM,UAAU;AAClB,cAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACH;AASO,IAAM,kCAAkC,MAAiC,oBAAI,IAAI;AAEjF,IAAM,6BAA6B,OACxC,MACA,OACA,YACqB;AACrB,QAAM,QAAQ,2BAA2B,IAAI,EAAE,IAAI,CAAC,QAA0B;AAC5E,UAAM,WAAW,MAAM,IAAI,GAAG;AAC9B,QAAI,UAAU,WAAW,SAAU,QAAO,QAAQ,QAAQ,IAAI;AAC9D,QAAI,UAAU,WAAW,aAAa,SAAS,QAAS,QAAO,SAAS;AACxE,QAAI,UAAU,WAAW,YAAY,CAAC,QAAQ,YAAa,QAAO,QAAQ,QAAQ,KAAK;AAEvF,6BAAyB,GAAG;AAC5B,UAAM,UAAU,UAAU,GAAG,EAC1B,KAAK,MAAM;AACV,YAAM,IAAI,KAAK,EAAE,QAAQ,SAAS,CAAC;AACnC,aAAO;AAAA,IACT,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,YAAM,IAAI,KAAK,EAAE,QAAQ,SAAS,CAAC;AACnC,cAAQ,OAAO,KAAK,8DAA2B,EAAE,OAAO,QAAQ,OAAO,KAAK,IAAI,CAAC;AACjF,aAAO;AAAA,IACT,CAAC;AACH,UAAM,IAAI,KAAK,EAAE,QAAQ,WAAW,QAAQ,CAAC;AAC7C,WAAO;AAAA,EACT,CAAC;AACD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAQ,MAAM,QAAQ,IAAI,KAAK,GAAG,MAAM,OAAO;AACjD;;;ACzFO,IAAM,8BAA8B,CAAC,UAAsC;AAChF,MAAI,iBAAiB,gBAAgB,MAAM,SAAS,kBAAmB,QAAO;AAC9E,SAAO;AACT;;;ACHO,IAAM,gCAAgC;;;ACK7C,IAAM,QAAQ,CAAI,UAAgB,gBAAgB,KAAK;AAEvD,IAAM,WAAW,CAAC,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAErE,IAAM,cAAc,CAClB,QACA,SACmC;AACnC,QAAM,CAAC,UAAU,GAAG,KAAK,IAAI,KAAK,MAAM,GAAG;AAC3C,MAAI,QAAwC,OAAO,QAAQ,EAAE;AAC7D,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,YAAQ,MAAM,IAAI;AAAA,EACpB;AACA,SAAO;AACT;AAQO,IAAM,8BAA8B,CACzC,YAC2B;AAAA,EAC3B,KAAK,CAAC,SAAS,MAAM,YAAY,QAAQ,IAAI,CAAC;AAAA,EAC9C,SAAS,CAAC,SAAS;AACjB,UAAM,QAAQ,YAAY,QAAQ,IAAI;AACtC,QAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,UAAM,EAAE,GAAG,GAAG,OAAO,OAAO,IAAI;AAChC,QAAI,CAAC,CAAC,GAAG,GAAG,OAAO,MAAM,EAAE,MAAM,CAAC,SAAS,OAAO,SAAS,YAAY,OAAO,SAAS,IAAI,CAAC;AAC1F,aAAO;AACT,WAAO,EAAE,GAAgB,GAAgB,OAAwB,OAAyB;AAAA,EAC5F;AAAA,EACA,UAAU,MAAM,MAAM,MAAM;AAC9B;;;AC5CA,SAAS,SAAS;AAQlB,IAAM,WAAW;AAEjB,IAAM,cAA6C,EAAE;AAAA,EAAK,MACxD,EAAE,MAAM;AAAA,IACN,EAAE,OAAO;AAAA,IACT,EAAE,OAAO,EAAE,OAAO;AAAA,IAClB,EAAE,QAAQ;AAAA,IACV,EAAE,KAAK;AAAA,IACP,EAAE,MAAM,WAAW;AAAA,IACnB,EAAE,OAAO,EAAE,OAAO,GAAG,WAAW;AAAA,EAClC,CAAC;AACH;AAEO,IAAM,8BAA8B,EAAE,OAAO;AAAA,EAClD,eAAe,EAAE,QAAQ,6BAA6B;AAAA,EACtD,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,WAAW;AAC1C,CAAC;AAEM,IAAM,4BAA4B,EAAE,OAAO;AAAA,EAChD,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AACT,CAAC;AAEM,IAAM,sBAAsB,CAAC,UAClC,OAAO,UAAU,YAAY,SAAS,KAAK,KAAK;AAE3C,IAAM,6BAA6B,CAAC,UACzC,4BAA4B,MAAM,KAAK;AAElC,IAAM,2BAA2B,CAAC,UACvC,0BAA0B,MAAM,KAAK;;;AC9BhC,IAAM,yCAAyC,CACpD,UACiC;AACjC,MACE,CAAC,SACD,OAAO,UAAU,YACjB,MAAM,QAAQ,KAAK,KAClB,MAAsC,kBAAkB,+BACzD;AACA,UAAM,IAAI,MAAM,yEAAiC,6BAA6B,EAAE;AAAA,EAClF;AACA,QAAMC,YAAW,2BAA2B,KAAK;AACjD,SAAO;AAAA,IACL,UAAAA;AAAA,IACA,aAAa;AAAA,IACb,cAAc,CAAC;AAAA,EACjB;AACF;AAEO,IAAM,+BAA+B,CAAC,UAC3C,uCAAuC,KAAK,EAAE;;;AChBhD,IAAMC,SAAQ,CAAI,UAAgB,gBAAgB,KAAK;AAEvD,IAAM,SAAS,CAAC,OAAe,QAC7B,OAAO,UAAU,eAAe,KAAK,OAAO,GAAG;AAEjD,IAAM,iBAAiB,CAAC,UACtB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAErE,IAAM,mBAAmB,CACvB,QACA,cACuB;AACvB,MAAI,CAAC,eAAe,MAAM,KAAK,CAAC,eAAe,SAAS,EAAG,QAAOA,OAAM,SAAS;AACjF,QAAM,SAA6C,CAAC;AACpD,aAAW,CAAC,KAAK,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,WAAO,GAAG,IAAI,OAAO,WAAW,GAAG,IAC/B,iBAAiB,aAAa,UAAU,GAAG,CAAE,IAC7CA,OAAM,WAAW;AAAA,EACvB;AACA,SAAO;AACT;AAEA,IAAM,2BAA2B,CAC/B,QACA,eACyB;AAAA,EACzB,QAAQ;AAAA,IACN,eAAe,OAAO,OAAO;AAAA,IAC7B,QAAQ,iBAAiB,OAAO,OAAO,QAAQ,UAAU,OAAO,MAAM;AAAA,EAIxE;AAAA,EACA,QAAQ;AAAA,IACN,eAAe,OAAO,OAAO;AAAA,IAC7B,QAAQ,iBAAiB,OAAO,OAAO,QAAQ,UAAU,OAAO,MAAM;AAAA,EAIxE;AAAA,EACA,QAAQ;AAAA,IACN,eAAe,OAAO,OAAO;AAAA,IAC7B,QAAQ,iBAAiB,OAAO,OAAO,QAAQ,UAAU,OAAO,MAAM;AAAA,EAIxE;AAAA,EACA,OAAO;AAAA,IACL,eAAe,OAAO,MAAM;AAAA,IAC5B,QAAQ,iBAAiB,OAAO,MAAM,QAAQ,UAAU,MAAM,MAAM;AAAA,EAItE;AACF;AAEA,IAAM,oBAAoB,CACxB,MACA,UACY,KAAK,UAAU,IAAI,MAAM,KAAK,UAAU,KAAK;AAE3D,IAAM,UAAU,CACd,QACA,MACA,UACS;AACT,QAAM,CAAC,UAAU,GAAG,KAAK,IAAI,KAAK,MAAM,GAAG;AAC3C,MACE,CAAC,CAAC,UAAU,UAAU,UAAU,OAAO,EAAE,SAAS,QAAQ,KAC1D,MAAM,WAAW,KACjB,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,aAAa,aAAa,EAAE,SAAS,IAAI,CAAC;AAEtF,UAAM,IAAI,MAAM,qBAAqB;AACvC,MAAI,SAAS,OAAO,QAAQ,EAAE;AAC9B,aAAW,QAAQ,MAAM,MAAM,GAAG,EAAE,GAAG;AACrC,UAAM,QAAQ,OAAO,IAAI;AACzB,QAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,EAAG,QAAO,IAAI,IAAI,CAAC;AACzF,aAAS,OAAO,IAAI;AAAA,EACtB;AACA,QAAM,OAAO,MAAM,GAAG,EAAE;AACxB,MAAI,CAAC,KAAM;AACX,MAAI,UAAU,OAAW,QAAO,OAAO,IAAI;AAAA,MACtC,QAAO,IAAI,IAAIA,OAAM,KAAK;AACjC;AAEA,IAAM,qBAAqB,CACzB,gBACA,OACA,YACA,kBACwB;AACxB,QAAM,kBAAkB,yBAAyB,gBAAgB,KAAK;AACtE,QAAM,yBAAyB,4BAA4B,cAAc;AACzE,QAAM,gBAAgB,4BAA4B,eAAe;AACjE,QAAM,qBAAqB,4BAA4B,UAAU;AACjE,QAAM,UAAUA,OAAM,UAAU;AAEhC,aAAW,QAAQ,eAAe;AAChC,UAAM,sBAAsB,uBAAuB,IAAI,IAAI;AAC3D,UAAM,kBAAkB,mBAAmB,IAAI,IAAI;AACnD,QAAI,CAAC,kBAAkB,qBAAqB,eAAe,EAAG;AAC9D,YAAQ,SAAS,MAAM,cAAc,IAAI,IAAI,CAAC;AAAA,EAChD;AACA,SAAO;AACT;AAEO,IAAM,qBAAN,MAAyB;AAAA,EAQ9B,YACE,UACiB,gBAA8C,QAAQ,QAAQ,GAC/E;AADiB;AAEjB,SAAK,SAASA,OAAM,QAAQ;AAC5B,SAAK,iBAAiBA,OAAM,QAAQ;AACpC,SAAK,UAAUA,OAAM,QAAQ;AAAA,EAC/B;AAAA,EALmB;AAAA,EATX;AAAA,EACA;AAAA,EACA;AAAA,EACS,YAAqC,CAAC;AAAA,EACtC,YAAqC,CAAC;AAAA,EACtC,YAAY,oBAAI,IAAgB;AAAA,EAWjD,UAAU,UAAkC;AAC1C,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,EAC7C;AAAA,EACA,cAAqC;AACnC,WAAO,4BAA4B,KAAK,OAAO;AAAA,EACjD;AAAA,EACA,oBAA2C;AACzC,WAAO,4BAA4B,KAAK,MAAM;AAAA,EAChD;AAAA,EACA,4BAAmD;AACjD,WAAO,4BAA4B,KAAK,cAAc;AAAA,EACxD;AAAA,EACA,cAAqC;AACnC,WAAO;AAAA,MACL,QAAQA,OAAM,KAAK,MAAM;AAAA,MACzB,gBAAgBA,OAAM,KAAK,cAAc;AAAA,MACzC,SAASA,OAAM,KAAK,OAAO;AAAA,MAC3B,kBAAkB,KAAK,KAAK,EAAE,IAAI,CAAC,WAAW,OAAO,IAAI;AAAA,MACzD,qBAAqB,KAAK,cAAc,EAAE,IAAI,CAAC,WAAW,OAAO,IAAI;AAAA,IACvE;AAAA,EACF;AAAA,EACA,MAAM,SAAyC,SAAS,MAAY;AAClE,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,OAAOA,OAAM,KAAK,OAAO;AAC/B,eAAW,UAAU,QAAS,SAAQ,MAAM,OAAO,MAAM,OAAO,KAAK;AACrE,SAAK,UAAU,yBAAyB,IAAI;AAC5C,QAAI,QAAQ;AACV,WAAK,UAAU,KAAK,QAAQ,IAAIA,MAAK,CAAC;AACtC,WAAK,UAAU,SAAS;AAAA,IAC1B;AACA,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,IAAI,MAAyB,OAAiC;AAC5D,SAAK,MAAM,CAAC,EAAE,MAAM,QAAQ,KAAK,YAAY,EAAE,IAAI,IAAI,GAAG,OAAO,MAAM,CAAC,CAAC;AAAA,EAC3E;AAAA,EACA,UAAU,MAA+B;AACvC,SAAK,MAAM;AAAA,MACT;AAAA,QACE;AAAA,QACA,QAAQ,KAAK,YAAY,EAAE,IAAI,IAAI;AAAA,QACnC,OAAO,KAAK,0BAA0B,EAAE,IAAI,IAAI;AAAA,MAClD;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EACA,WAAiB;AACf,SAAK,UAAUA,OAAM,KAAK,cAAc;AACxC,SAAK,UAAU,SAAS;AACxB,SAAK,UAAU,SAAS;AACxB,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,gBAAsB;AACpB,SAAK,iBAAiBA,OAAM,KAAK,OAAO;AACxC,SAAK,UAAU,SAAS;AACxB,SAAK,UAAU,SAAS;AACxB,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,YAAY,OAA2C;AACrD,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,WAAW,KAAK,YAAY;AAClC,eAAW,QAAQ,MAAO,SAAQ,KAAK,QAAQ,MAAM,SAAS,IAAI,IAAI,CAAC;AAGvE,SAAK,UAAU,SAAS;AACxB,SAAK,UAAU,SAAS;AACxB,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,cAAc,OAA4B,gBAA4C;AACpF,UAAM,YAAY,iBACd,mBAAmB,gBAAgB,OAAO,KAAK,QAAQ,KAAK,aAAa,IACzEA,OAAM,KAAK,MAAM;AACrB,SAAK,UAAUA,OAAM,SAAS;AAC9B,SAAK,iBAAiBA,OAAM,SAAS;AACrC,SAAK,UAAU,SAAS;AACxB,SAAK,UAAU,SAAS;AACxB,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,aAAa,YAAuC;AAClD,QAAI,KAAK,UAAU,KAAK,MAAM,MAAM,KAAK,UAAU,UAAU,EAAG;AAChE,UAAM,iBAAiB,KAAK;AAC5B,SAAK,iBAAiB;AAAA,MACpB;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA,KAAK;AAAA,IACP;AACA,SAAK,UAAU,mBAAmB,gBAAgB,KAAK,SAAS,YAAY,KAAK,aAAa;AAC9F,SAAK,SAASA,OAAM,UAAU;AAC9B,SAAK,UAAU,SAAS;AACxB,SAAK,UAAU,SAAS;AACxB,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,OAAa;AACX,UAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAI,CAAC,QAAS;AACd,eAAW,UAAU,QAAS,SAAQ,KAAK,SAAS,OAAO,MAAM,OAAO,MAAM;AAC9E,SAAK,UAAU,KAAK,OAAO;AAC3B,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,OAAa;AACX,UAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAI,CAAC,QAAS;AACd,eAAW,UAAU,QAAS,SAAQ,KAAK,SAAS,OAAO,MAAM,OAAO,KAAK;AAC7E,SAAK,UAAU,KAAK,OAAO;AAC3B,SAAK,KAAK;AAAA,EACZ;AAAA,EACA,OAA8B;AAC5B,WAAO,KAAK,SAAS,KAAK,MAAM;AAAA,EAClC;AAAA,EACA,gBAAuC;AACrC,WAAO,KAAK,SAAS,KAAK,cAAc;AAAA,EAC1C;AAAA,EACQ,SAAS,UAAsD;AACrE,UAAM,QAAQ,oBAAI,IAAI,CAAC,GAAG,QAAQ,QAAQ,GAAG,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC;AACtE,UAAM,QAAQ,4BAA4B,QAAQ;AAClD,UAAM,UAAU,KAAK,YAAY;AACjC,UAAM,UAAiC,CAAC;AACxC,eAAW,QAAQ,OAAO;AACxB,YAAM,SAAS,MAAM,IAAI,IAAI;AAC7B,YAAM,QAAQ,QAAQ,IAAI,IAAI;AAC9B,UAAI,KAAK,UAAU,MAAM,MAAM,KAAK,UAAU,KAAK,EAAG,SAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,CAAC;AAAA,IAC5F;AACA,WAAO;AAAA,EACT;AAAA,EACQ,OAAa;AACnB,eAAW,YAAY,KAAK,UAAW,UAAS;AAAA,EAClD;AACF;AAEA,IAAM,UAAU,CAAC,WAAqD;AACpE,QAAM,QAA6B,CAAC;AACpC,QAAM,QAAQ,CAAC,OAA2B,WAAyB;AACjE,QAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG;AACvE,YAAM,KAAK,MAA2B;AACtC;AAAA,IACF;AACA,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,EAAG,OAAM,OAAO,GAAG,MAAM,IAAI,GAAG,EAAE;AAAA,EACnF;AACA,aAAW,CAAC,UAAUC,SAAQ,KAAK,OAAO,QAAQ,MAAM,EAAG,OAAMA,UAAS,QAAQ,QAAQ;AAC1F,SAAO;AACT;;;AChRA,IAAM,gBAAgB,CAAC,SAA8C;AAAA,EACnE,GAAG,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,EAAE,OAAO,CAAC,QAAuB,QAAQ,GAAG,CAAC,CAAC;AAC7F;AACO,IAAM,sBAAsB,OACjC,MACA,YAQuC;AACvC,QAAM,QAAQ,cAAc,IAAI;AAChC,QAAM,aAAuB,CAAC;AAC9B,MAAI,SAAS;AACb,MAAI,YAAY;AAChB,QAAM,SAAS,YAA2B;AACxC,WAAO,CAAC,QAAQ,OAAO,SAAS;AAC9B,YAAM,QAAQ;AACd,gBAAU;AACV,YAAM,MAAM,MAAM,KAAK;AACvB,UAAI,CAAC,IAAK;AACV,UAAI;AACF,cAAM,4BAA4B,MAAM,QAAQ,MAAM,KAAK,GAAG,GAAG;AAAA,UAC/D,QAAQ,QAAQ;AAAA,UAChB,YAAY,QAAQ;AAAA,UACpB,cAAc,QAAQ;AAAA,QACxB,CAAC;AAAA,MACH,QAAQ;AACN,YAAI,CAAC,QAAQ,OAAO,QAAS,YAAW,KAAK,GAAG;AAAA,MAClD,UAAE;AACA,qBAAa;AACb,gBAAQ,WAAW,WAAW,MAAM,MAAM;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,QAAM,QAAQ;AAAA,IACZ,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,KAAK,IAAI,GAAG,QAAQ,eAAe,CAAC,GAAG,MAAM,MAAM,EAAE,GAAG,MAAM;AAAA,EAC9F;AACA,SAAO,EAAE,WAAW;AACtB;;;AC9CO,IAAM,iBAAN,MAAqB;AAAA,EAK1B,YAA6B,aAA2B,OAAO;AAAlC;AAAA,EAAmC;AAAA,EAAnC;AAAA,EAJZ,UAAU,oBAAI,IAG7B;AAAA,EAGF,MAAM,KAAK,KAAmC;AAC5C,QAAI,SAAS,KAAK,QAAQ,IAAI,GAAG;AACjC,QAAI,CAAC,QAAQ;AACX,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU,KAAK,WAAW,KAAK,EAAE,QAAQ,WAAW,OAAO,CAAC,EAAE,KAAK,OAAO,aAAa;AAC3F,YAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,yBAAyB,SAAS,MAAM,EAAE;AAC5E,cAAM,QAAQ,MAAM,SAAS,YAAY;AACzC,YAAI,WAAW,OAAO,QAAS,OAAM,IAAI,aAAa,wBAAwB,YAAY;AAC1F,eAAO;AAAA,MACT,CAAC;AACD,eAAS,EAAE,YAAY,QAAQ;AAC/B,WAAK,QAAQ,IAAI,KAAK,MAAM;AAC5B,YAAM,QAAQ;AACd,WAAK,QAAQ,MAAM,MAAM;AACvB,YAAI,KAAK,QAAQ,IAAI,GAAG,MAAM,MAAO,MAAK,QAAQ,OAAO,GAAG;AAAA,MAC9D,CAAC;AAAA,IACH;AACA,YAAQ,MAAM,OAAO,SAAS,MAAM,CAAC;AAAA,EACvC;AAAA,EAEA,QAAc;AACZ,eAAW,UAAU,KAAK,QAAQ,OAAO,EAAG,QAAO,WAAW,MAAM;AACpE,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;",
|
|
6
|
+
"names": ["link", "document", "clone", "document"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RuntimeLogger } from './RuntimeLogger.js';
|
|
2
|
+
export declare const normalizeBattlePreloadUrls: (urls: Array<string | null | undefined>) => string[];
|
|
3
|
+
type BattleDisplayPreloadEntry = {
|
|
4
|
+
status: 'loading' | 'loaded' | 'failed';
|
|
5
|
+
promise?: Promise<boolean>;
|
|
6
|
+
};
|
|
7
|
+
export type BattleDisplayPreloadState = Map<string, BattleDisplayPreloadEntry>;
|
|
8
|
+
export declare const createBattleDisplayPreloadState: () => BattleDisplayPreloadState;
|
|
9
|
+
export declare const preloadBattleDisplayImages: (urls: Array<string | null | undefined>, state: BattleDisplayPreloadState, options: {
|
|
10
|
+
wait?: boolean;
|
|
11
|
+
retryFailed?: boolean;
|
|
12
|
+
scope: string;
|
|
13
|
+
logger: RuntimeLogger;
|
|
14
|
+
}) => Promise<boolean>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const SESSION_DISPLAY_RESOURCE_RETRY_COUNT = 3;
|
|
2
|
+
export declare const SESSION_DISPLAY_RESOURCE_RETRY_DELAY_MS = 500;
|
|
3
|
+
/** A caller may cancel its wait without cancelling a retained media resource. */
|
|
4
|
+
export declare const waitForSessionMediaReady: (ready: Promise<boolean> | null, signal: AbortSignal) => Promise<boolean>;
|
|
5
|
+
interface SessionDisplayResourceRetryOptions {
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
readonly retryCount?: number;
|
|
8
|
+
readonly retryDelayMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const retrySessionDisplayResource: <T>(operation: () => Promise<T>, options?: SessionDisplayResourceRetryOptions) => Promise<T>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SESSION_TUNING_SCHEMA_VERSION, type SessionTuningDocument } from './SessionTuningTypes.js';
|
|
2
|
+
export interface SessionTuningMigrationResult {
|
|
3
|
+
document: SessionTuningDocument;
|
|
4
|
+
fromVersion: typeof SESSION_TUNING_SCHEMA_VERSION;
|
|
5
|
+
removedPaths: readonly [];
|
|
6
|
+
}
|
|
7
|
+
/** Validates the current tuning contract. Legacy tuning documents are rejected. */
|
|
8
|
+
export declare const migrateSessionTuningDocumentWithResult: (input: unknown) => SessionTuningMigrationResult;
|
|
9
|
+
export declare const migrateSessionTuningDocument: (input: unknown) => SessionTuningDocument;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SessionTuningBundle, SessionTuningPath, SessionTuningValue } from './SessionTuningTypes.js';
|
|
2
|
+
export interface SessionTuningResolver {
|
|
3
|
+
get(path: SessionTuningPath): SessionTuningValue | undefined;
|
|
4
|
+
getRect(path: SessionTuningPath): {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
} | null;
|
|
10
|
+
snapshot(): SessionTuningBundle;
|
|
11
|
+
}
|
|
12
|
+
export declare const createSessionTuningResolver: (bundle: SessionTuningBundle) => SessionTuningResolver;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type SessionTuningBundle, type SessionTuningDocument, type SessionTuningValue } from './SessionTuningTypes.js';
|
|
3
|
+
export declare const sessionTuningDocumentSchema: z.ZodObject<{
|
|
4
|
+
schemaVersion: z.ZodLiteral<11>;
|
|
5
|
+
values: z.ZodRecord<z.ZodString, z.ZodType<SessionTuningValue, unknown, z.core.$ZodTypeInternals<SessionTuningValue, unknown>>>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export declare const sessionTuningBundleSchema: z.ZodObject<{
|
|
8
|
+
layout: z.ZodObject<{
|
|
9
|
+
schemaVersion: z.ZodLiteral<11>;
|
|
10
|
+
values: z.ZodRecord<z.ZodString, z.ZodType<SessionTuningValue, unknown, z.core.$ZodTypeInternals<SessionTuningValue, unknown>>>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
visual: z.ZodObject<{
|
|
13
|
+
schemaVersion: z.ZodLiteral<11>;
|
|
14
|
+
values: z.ZodRecord<z.ZodString, z.ZodType<SessionTuningValue, unknown, z.core.$ZodTypeInternals<SessionTuningValue, unknown>>>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
motion: z.ZodObject<{
|
|
17
|
+
schemaVersion: z.ZodLiteral<11>;
|
|
18
|
+
values: z.ZodRecord<z.ZodString, z.ZodType<SessionTuningValue, unknown, z.core.$ZodTypeInternals<SessionTuningValue, unknown>>>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
audio: z.ZodObject<{
|
|
21
|
+
schemaVersion: z.ZodLiteral<11>;
|
|
22
|
+
values: z.ZodRecord<z.ZodString, z.ZodType<SessionTuningValue, unknown, z.core.$ZodTypeInternals<SessionTuningValue, unknown>>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export declare const validateTuningColor: (value: unknown) => value is string;
|
|
26
|
+
export declare const parseSessionTuningDocument: (value: unknown) => SessionTuningDocument;
|
|
27
|
+
export declare const parseSessionTuningBundle: (value: unknown) => SessionTuningBundle;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type SessionTuningResolver } from './SessionTuningResolver.js';
|
|
2
|
+
import type { SessionTuningBundle, SessionTuningChange, SessionTuningPath, SessionTuningSnapshot, SessionTuningValue } from './SessionTuningTypes.js';
|
|
3
|
+
export declare class SessionTuningStore {
|
|
4
|
+
private readonly editablePaths;
|
|
5
|
+
private formal;
|
|
6
|
+
private workspaceSaved;
|
|
7
|
+
private working;
|
|
8
|
+
private readonly undoStack;
|
|
9
|
+
private readonly redoStack;
|
|
10
|
+
private readonly listeners;
|
|
11
|
+
constructor(defaults: SessionTuningBundle, editablePaths?: readonly SessionTuningPath[]);
|
|
12
|
+
subscribe(listener: () => void): () => void;
|
|
13
|
+
getResolver(): SessionTuningResolver;
|
|
14
|
+
getFormalResolver(): SessionTuningResolver;
|
|
15
|
+
getWorkspaceSavedResolver(): SessionTuningResolver;
|
|
16
|
+
getSnapshot(): SessionTuningSnapshot;
|
|
17
|
+
patch(changes: readonly SessionTuningChange[], record?: boolean): void;
|
|
18
|
+
set(path: SessionTuningPath, value: SessionTuningValue): void;
|
|
19
|
+
resetPath(path: SessionTuningPath): void;
|
|
20
|
+
resetAll(): void;
|
|
21
|
+
saveWorkspace(): void;
|
|
22
|
+
markApplied(paths: readonly SessionTuningPath[]): void;
|
|
23
|
+
loadWorkspace(value: SessionTuningBundle, formalBaseline?: SessionTuningBundle): void;
|
|
24
|
+
rebaseFormal(nextFormal: SessionTuningBundle): void;
|
|
25
|
+
undo(): void;
|
|
26
|
+
redo(): void;
|
|
27
|
+
diff(): SessionTuningChange[];
|
|
28
|
+
workspaceDiff(): SessionTuningChange[];
|
|
29
|
+
private diffFrom;
|
|
30
|
+
private emit;
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type SessionDisplayScene = string;
|
|
2
|
+
export declare const SESSION_TUNING_SCHEMA_VERSION: 11;
|
|
3
|
+
export type SessionTuningCategory = 'layout' | 'visual' | 'motion' | 'audio';
|
|
4
|
+
export type SessionTuningPrimitive = string | number | boolean | null;
|
|
5
|
+
export type SessionTuningValue = SessionTuningPrimitive | readonly SessionTuningValue[] | {
|
|
6
|
+
readonly [key: string]: SessionTuningValue;
|
|
7
|
+
};
|
|
8
|
+
export type SessionTuningDocument = {
|
|
9
|
+
schemaVersion: typeof SESSION_TUNING_SCHEMA_VERSION;
|
|
10
|
+
values: Record<string, SessionTuningValue>;
|
|
11
|
+
};
|
|
12
|
+
export interface SessionTuningBundle {
|
|
13
|
+
layout: SessionTuningDocument;
|
|
14
|
+
visual: SessionTuningDocument;
|
|
15
|
+
motion: SessionTuningDocument;
|
|
16
|
+
audio: SessionTuningDocument;
|
|
17
|
+
}
|
|
18
|
+
export type SessionTuningPath = `${SessionTuningCategory}.${string}`;
|
|
19
|
+
export type SessionTunableType = 'number' | 'integer' | 'boolean' | 'color' | 'enum' | 'vec2' | 'rect' | 'range' | 'easing' | 'asset' | 'audioCue';
|
|
20
|
+
export interface TunableDescriptor {
|
|
21
|
+
id: string;
|
|
22
|
+
path: SessionTuningPath;
|
|
23
|
+
label: string;
|
|
24
|
+
description: string;
|
|
25
|
+
category: SessionTuningCategory;
|
|
26
|
+
group: string;
|
|
27
|
+
scene?: SessionDisplayScene;
|
|
28
|
+
slotId?: string;
|
|
29
|
+
type: SessionTunableType;
|
|
30
|
+
min?: number;
|
|
31
|
+
max?: number;
|
|
32
|
+
step?: number;
|
|
33
|
+
unit?: string;
|
|
34
|
+
options?: readonly string[];
|
|
35
|
+
previewId?: string;
|
|
36
|
+
updateMode?: 'live' | 'localRebuild' | 'sceneRebuild';
|
|
37
|
+
readOnly?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface SessionTuningChange {
|
|
40
|
+
path: SessionTuningPath;
|
|
41
|
+
before: SessionTuningValue | undefined;
|
|
42
|
+
after: SessionTuningValue | undefined;
|
|
43
|
+
}
|
|
44
|
+
export interface SessionTuningSnapshot {
|
|
45
|
+
readonly formal: SessionTuningBundle;
|
|
46
|
+
readonly workspaceSaved: SessionTuningBundle;
|
|
47
|
+
readonly working: SessionTuningBundle;
|
|
48
|
+
readonly formalDirtyPaths: readonly SessionTuningPath[];
|
|
49
|
+
readonly workspaceDirtyPaths: readonly SessionTuningPath[];
|
|
50
|
+
}
|
package/dist/tuning.d.ts
ADDED
package/dist/tuning.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// src/tuning/SessionTuningTypes.ts
|
|
2
|
+
var SESSION_TUNING_SCHEMA_VERSION = 11;
|
|
3
|
+
|
|
4
|
+
// src/tuning/SessionTuningResolver.ts
|
|
5
|
+
var clone = (value) => structuredClone(value);
|
|
6
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
var resolvePath = (bundle, path) => {
|
|
8
|
+
const [category, ...parts] = path.split(".");
|
|
9
|
+
let value = bundle[category].values;
|
|
10
|
+
for (const part of parts) {
|
|
11
|
+
if (!isRecord(value)) return void 0;
|
|
12
|
+
value = value[part];
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
var createSessionTuningResolver = (bundle) => ({
|
|
17
|
+
get: (path) => clone(resolvePath(bundle, path)),
|
|
18
|
+
getRect: (path) => {
|
|
19
|
+
const value = resolvePath(bundle, path);
|
|
20
|
+
if (!isRecord(value)) return null;
|
|
21
|
+
const { x, y, width, height } = value;
|
|
22
|
+
if (![x, y, width, height].every((item) => typeof item === "number" && Number.isFinite(item)))
|
|
23
|
+
return null;
|
|
24
|
+
return { x, y, width, height };
|
|
25
|
+
},
|
|
26
|
+
snapshot: () => clone(bundle)
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// src/tuning/SessionTuningSchema.ts
|
|
30
|
+
import { z } from "zod";
|
|
31
|
+
var hexColor = /^#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$/;
|
|
32
|
+
var valueSchema = z.lazy(
|
|
33
|
+
() => z.union([
|
|
34
|
+
z.string(),
|
|
35
|
+
z.number().finite(),
|
|
36
|
+
z.boolean(),
|
|
37
|
+
z.null(),
|
|
38
|
+
z.array(valueSchema),
|
|
39
|
+
z.record(z.string(), valueSchema)
|
|
40
|
+
])
|
|
41
|
+
);
|
|
42
|
+
var sessionTuningDocumentSchema = z.object({
|
|
43
|
+
schemaVersion: z.literal(SESSION_TUNING_SCHEMA_VERSION),
|
|
44
|
+
values: z.record(z.string(), valueSchema)
|
|
45
|
+
});
|
|
46
|
+
var sessionTuningBundleSchema = z.object({
|
|
47
|
+
layout: sessionTuningDocumentSchema,
|
|
48
|
+
visual: sessionTuningDocumentSchema,
|
|
49
|
+
motion: sessionTuningDocumentSchema,
|
|
50
|
+
audio: sessionTuningDocumentSchema
|
|
51
|
+
});
|
|
52
|
+
var validateTuningColor = (value) => typeof value === "string" && hexColor.test(value);
|
|
53
|
+
var parseSessionTuningDocument = (value) => sessionTuningDocumentSchema.parse(value);
|
|
54
|
+
var parseSessionTuningBundle = (value) => sessionTuningBundleSchema.parse(value);
|
|
55
|
+
|
|
56
|
+
// src/tuning/SessionTuningMigration.ts
|
|
57
|
+
var migrateSessionTuningDocumentWithResult = (input) => {
|
|
58
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || input.schemaVersion !== SESSION_TUNING_SCHEMA_VERSION) {
|
|
59
|
+
throw new Error(`\u4E0D\u652F\u6301\u7248\u672C: Visual Lab \u4EC5\u652F\u6301 tuning v${SESSION_TUNING_SCHEMA_VERSION}`);
|
|
60
|
+
}
|
|
61
|
+
const document = parseSessionTuningDocument(input);
|
|
62
|
+
return {
|
|
63
|
+
document,
|
|
64
|
+
fromVersion: SESSION_TUNING_SCHEMA_VERSION,
|
|
65
|
+
removedPaths: []
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
var migrateSessionTuningDocument = (input) => migrateSessionTuningDocumentWithResult(input).document;
|
|
69
|
+
|
|
70
|
+
// src/tuning/SessionTuningStore.ts
|
|
71
|
+
var clone2 = (value) => structuredClone(value);
|
|
72
|
+
var hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
73
|
+
var isTuningRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74
|
+
var mergeTuningValue = (formal, workspace) => {
|
|
75
|
+
if (!isTuningRecord(formal) || !isTuningRecord(workspace)) return clone2(workspace);
|
|
76
|
+
const merged = {};
|
|
77
|
+
for (const [key, formalValue] of Object.entries(formal)) {
|
|
78
|
+
merged[key] = hasOwn(workspace, key) ? mergeTuningValue(formalValue, workspace[key]) : clone2(formalValue);
|
|
79
|
+
}
|
|
80
|
+
return merged;
|
|
81
|
+
};
|
|
82
|
+
var mergeWorkspaceWithFormal = (formal, workspace) => ({
|
|
83
|
+
layout: {
|
|
84
|
+
schemaVersion: formal.layout.schemaVersion,
|
|
85
|
+
values: mergeTuningValue(formal.layout.values, workspace.layout.values)
|
|
86
|
+
},
|
|
87
|
+
visual: {
|
|
88
|
+
schemaVersion: formal.visual.schemaVersion,
|
|
89
|
+
values: mergeTuningValue(formal.visual.values, workspace.visual.values)
|
|
90
|
+
},
|
|
91
|
+
motion: {
|
|
92
|
+
schemaVersion: formal.motion.schemaVersion,
|
|
93
|
+
values: mergeTuningValue(formal.motion.values, workspace.motion.values)
|
|
94
|
+
},
|
|
95
|
+
audio: {
|
|
96
|
+
schemaVersion: formal.audio.schemaVersion,
|
|
97
|
+
values: mergeTuningValue(formal.audio.values, workspace.audio.values)
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
var tuningValuesEqual = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
101
|
+
var setPath = (bundle, path, value) => {
|
|
102
|
+
const [category, ...parts] = path.split(".");
|
|
103
|
+
if (!["layout", "visual", "motion", "audio"].includes(category) || parts.length === 0 || parts.some((part) => !part || ["__proto__", "prototype", "constructor"].includes(part)))
|
|
104
|
+
throw new Error("Invalid tuning path");
|
|
105
|
+
let target = bundle[category].values;
|
|
106
|
+
for (const part of parts.slice(0, -1)) {
|
|
107
|
+
const child = target[part];
|
|
108
|
+
if (typeof child !== "object" || child === null || Array.isArray(child)) target[part] = {};
|
|
109
|
+
target = target[part];
|
|
110
|
+
}
|
|
111
|
+
const last = parts.at(-1);
|
|
112
|
+
if (!last) return;
|
|
113
|
+
if (value === void 0) delete target[last];
|
|
114
|
+
else target[last] = clone2(value);
|
|
115
|
+
};
|
|
116
|
+
var rebaseTuningBundle = (previousFormal, value, nextFormal, editablePaths) => {
|
|
117
|
+
const normalizedValue = mergeWorkspaceWithFormal(previousFormal, value);
|
|
118
|
+
const previousFormalResolver = createSessionTuningResolver(previousFormal);
|
|
119
|
+
const valueResolver = createSessionTuningResolver(normalizedValue);
|
|
120
|
+
const nextFormalResolver = createSessionTuningResolver(nextFormal);
|
|
121
|
+
const rebased = clone2(nextFormal);
|
|
122
|
+
for (const path of editablePaths) {
|
|
123
|
+
const previousFormalValue = previousFormalResolver.get(path);
|
|
124
|
+
const nextFormalValue = nextFormalResolver.get(path);
|
|
125
|
+
if (!tuningValuesEqual(previousFormalValue, nextFormalValue)) continue;
|
|
126
|
+
setPath(rebased, path, valueResolver.get(path));
|
|
127
|
+
}
|
|
128
|
+
return rebased;
|
|
129
|
+
};
|
|
130
|
+
var SessionTuningStore = class {
|
|
131
|
+
constructor(defaults, editablePaths = flatten(defaults)) {
|
|
132
|
+
this.editablePaths = editablePaths;
|
|
133
|
+
this.formal = clone2(defaults);
|
|
134
|
+
this.workspaceSaved = clone2(defaults);
|
|
135
|
+
this.working = clone2(defaults);
|
|
136
|
+
}
|
|
137
|
+
editablePaths;
|
|
138
|
+
formal;
|
|
139
|
+
workspaceSaved;
|
|
140
|
+
working;
|
|
141
|
+
undoStack = [];
|
|
142
|
+
redoStack = [];
|
|
143
|
+
listeners = /* @__PURE__ */ new Set();
|
|
144
|
+
subscribe(listener) {
|
|
145
|
+
this.listeners.add(listener);
|
|
146
|
+
return () => this.listeners.delete(listener);
|
|
147
|
+
}
|
|
148
|
+
getResolver() {
|
|
149
|
+
return createSessionTuningResolver(this.working);
|
|
150
|
+
}
|
|
151
|
+
getFormalResolver() {
|
|
152
|
+
return createSessionTuningResolver(this.formal);
|
|
153
|
+
}
|
|
154
|
+
getWorkspaceSavedResolver() {
|
|
155
|
+
return createSessionTuningResolver(this.workspaceSaved);
|
|
156
|
+
}
|
|
157
|
+
getSnapshot() {
|
|
158
|
+
return {
|
|
159
|
+
formal: clone2(this.formal),
|
|
160
|
+
workspaceSaved: clone2(this.workspaceSaved),
|
|
161
|
+
working: clone2(this.working),
|
|
162
|
+
formalDirtyPaths: this.diff().map((change) => change.path),
|
|
163
|
+
workspaceDirtyPaths: this.workspaceDiff().map((change) => change.path)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
patch(changes, record = true) {
|
|
167
|
+
if (changes.length === 0) return;
|
|
168
|
+
const next = clone2(this.working);
|
|
169
|
+
for (const change of changes) setPath(next, change.path, change.after);
|
|
170
|
+
this.working = parseSessionTuningBundle(next);
|
|
171
|
+
if (record) {
|
|
172
|
+
this.undoStack.push(changes.map(clone2));
|
|
173
|
+
this.redoStack.length = 0;
|
|
174
|
+
}
|
|
175
|
+
this.emit();
|
|
176
|
+
}
|
|
177
|
+
set(path, value) {
|
|
178
|
+
this.patch([{ path, before: this.getResolver().get(path), after: value }]);
|
|
179
|
+
}
|
|
180
|
+
resetPath(path) {
|
|
181
|
+
this.patch([
|
|
182
|
+
{
|
|
183
|
+
path,
|
|
184
|
+
before: this.getResolver().get(path),
|
|
185
|
+
after: this.getWorkspaceSavedResolver().get(path)
|
|
186
|
+
}
|
|
187
|
+
]);
|
|
188
|
+
}
|
|
189
|
+
resetAll() {
|
|
190
|
+
this.working = clone2(this.workspaceSaved);
|
|
191
|
+
this.undoStack.length = 0;
|
|
192
|
+
this.redoStack.length = 0;
|
|
193
|
+
this.emit();
|
|
194
|
+
}
|
|
195
|
+
saveWorkspace() {
|
|
196
|
+
this.workspaceSaved = clone2(this.working);
|
|
197
|
+
this.undoStack.length = 0;
|
|
198
|
+
this.redoStack.length = 0;
|
|
199
|
+
this.emit();
|
|
200
|
+
}
|
|
201
|
+
markApplied(paths) {
|
|
202
|
+
if (paths.length === 0) return;
|
|
203
|
+
const resolver = this.getResolver();
|
|
204
|
+
for (const path of paths) setPath(this.formal, path, resolver.get(path));
|
|
205
|
+
this.undoStack.length = 0;
|
|
206
|
+
this.redoStack.length = 0;
|
|
207
|
+
this.emit();
|
|
208
|
+
}
|
|
209
|
+
loadWorkspace(value, formalBaseline) {
|
|
210
|
+
const workspace = formalBaseline ? rebaseTuningBundle(formalBaseline, value, this.formal, this.editablePaths) : clone2(this.formal);
|
|
211
|
+
this.working = clone2(workspace);
|
|
212
|
+
this.workspaceSaved = clone2(workspace);
|
|
213
|
+
this.undoStack.length = 0;
|
|
214
|
+
this.redoStack.length = 0;
|
|
215
|
+
this.emit();
|
|
216
|
+
}
|
|
217
|
+
rebaseFormal(nextFormal) {
|
|
218
|
+
if (JSON.stringify(this.formal) === JSON.stringify(nextFormal)) return;
|
|
219
|
+
const previousFormal = this.formal;
|
|
220
|
+
this.workspaceSaved = rebaseTuningBundle(
|
|
221
|
+
previousFormal,
|
|
222
|
+
this.workspaceSaved,
|
|
223
|
+
nextFormal,
|
|
224
|
+
this.editablePaths
|
|
225
|
+
);
|
|
226
|
+
this.working = rebaseTuningBundle(previousFormal, this.working, nextFormal, this.editablePaths);
|
|
227
|
+
this.formal = clone2(nextFormal);
|
|
228
|
+
this.undoStack.length = 0;
|
|
229
|
+
this.redoStack.length = 0;
|
|
230
|
+
this.emit();
|
|
231
|
+
}
|
|
232
|
+
undo() {
|
|
233
|
+
const changes = this.undoStack.pop();
|
|
234
|
+
if (!changes) return;
|
|
235
|
+
for (const change of changes) setPath(this.working, change.path, change.before);
|
|
236
|
+
this.redoStack.push(changes);
|
|
237
|
+
this.emit();
|
|
238
|
+
}
|
|
239
|
+
redo() {
|
|
240
|
+
const changes = this.redoStack.pop();
|
|
241
|
+
if (!changes) return;
|
|
242
|
+
for (const change of changes) setPath(this.working, change.path, change.after);
|
|
243
|
+
this.undoStack.push(changes);
|
|
244
|
+
this.emit();
|
|
245
|
+
}
|
|
246
|
+
diff() {
|
|
247
|
+
return this.diffFrom(this.formal);
|
|
248
|
+
}
|
|
249
|
+
workspaceDiff() {
|
|
250
|
+
return this.diffFrom(this.workspaceSaved);
|
|
251
|
+
}
|
|
252
|
+
diffFrom(baseline) {
|
|
253
|
+
const paths = /* @__PURE__ */ new Set([...flatten(baseline), ...flatten(this.working)]);
|
|
254
|
+
const saved = createSessionTuningResolver(baseline);
|
|
255
|
+
const working = this.getResolver();
|
|
256
|
+
const changes = [];
|
|
257
|
+
for (const path of paths) {
|
|
258
|
+
const before = saved.get(path);
|
|
259
|
+
const after = working.get(path);
|
|
260
|
+
if (JSON.stringify(before) !== JSON.stringify(after)) changes.push({ path, before, after });
|
|
261
|
+
}
|
|
262
|
+
return changes;
|
|
263
|
+
}
|
|
264
|
+
emit() {
|
|
265
|
+
for (const listener of this.listeners) listener();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
var flatten = (bundle) => {
|
|
269
|
+
const paths = [];
|
|
270
|
+
const visit = (value, prefix) => {
|
|
271
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
272
|
+
paths.push(prefix);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
for (const [key, child] of Object.entries(value)) visit(child, `${prefix}.${key}`);
|
|
276
|
+
};
|
|
277
|
+
for (const [category, document] of Object.entries(bundle)) visit(document.values, category);
|
|
278
|
+
return paths;
|
|
279
|
+
};
|
|
280
|
+
export {
|
|
281
|
+
SESSION_TUNING_SCHEMA_VERSION,
|
|
282
|
+
SessionTuningStore,
|
|
283
|
+
createSessionTuningResolver,
|
|
284
|
+
migrateSessionTuningDocument,
|
|
285
|
+
migrateSessionTuningDocumentWithResult,
|
|
286
|
+
parseSessionTuningBundle,
|
|
287
|
+
parseSessionTuningDocument,
|
|
288
|
+
sessionTuningBundleSchema,
|
|
289
|
+
sessionTuningDocumentSchema,
|
|
290
|
+
validateTuningColor
|
|
291
|
+
};
|
|
292
|
+
//# sourceMappingURL=tuning.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/tuning/SessionTuningTypes.ts", "../src/tuning/SessionTuningResolver.ts", "../src/tuning/SessionTuningSchema.ts", "../src/tuning/SessionTuningMigration.ts", "../src/tuning/SessionTuningStore.ts"],
|
|
4
|
+
"sourcesContent": ["export type SessionDisplayScene = string;\n\nexport const SESSION_TUNING_SCHEMA_VERSION = 11 as const;\n\nexport type SessionTuningCategory = 'layout' | 'visual' | 'motion' | 'audio';\nexport type SessionTuningPrimitive = string | number | boolean | null;\nexport type SessionTuningValue =\n | SessionTuningPrimitive\n | readonly SessionTuningValue[]\n | { readonly [key: string]: SessionTuningValue };\n\nexport type SessionTuningDocument = {\n schemaVersion: typeof SESSION_TUNING_SCHEMA_VERSION;\n values: Record<string, SessionTuningValue>;\n};\n\nexport interface SessionTuningBundle {\n layout: SessionTuningDocument;\n visual: SessionTuningDocument;\n motion: SessionTuningDocument;\n audio: SessionTuningDocument;\n}\n\nexport type SessionTuningPath = `${SessionTuningCategory}.${string}`;\n\nexport type SessionTunableType =\n | 'number'\n | 'integer'\n | 'boolean'\n | 'color'\n | 'enum'\n | 'vec2'\n | 'rect'\n | 'range'\n | 'easing'\n | 'asset'\n | 'audioCue';\n\nexport interface TunableDescriptor {\n id: string;\n path: SessionTuningPath;\n label: string;\n description: string;\n category: SessionTuningCategory;\n group: string;\n scene?: SessionDisplayScene;\n slotId?: string;\n type: SessionTunableType;\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n options?: readonly string[];\n previewId?: string;\n updateMode?: 'live' | 'localRebuild' | 'sceneRebuild';\n readOnly?: boolean;\n}\n\nexport interface SessionTuningChange {\n path: SessionTuningPath;\n before: SessionTuningValue | undefined;\n after: SessionTuningValue | undefined;\n}\n\nexport interface SessionTuningSnapshot {\n readonly formal: SessionTuningBundle;\n readonly workspaceSaved: SessionTuningBundle;\n readonly working: SessionTuningBundle;\n readonly formalDirtyPaths: readonly SessionTuningPath[];\n readonly workspaceDirtyPaths: readonly SessionTuningPath[];\n}\n", "import type {\n SessionTuningBundle,\n SessionTuningCategory,\n SessionTuningPath,\n SessionTuningValue,\n} from './SessionTuningTypes.js';\n\nconst clone = <T>(value: T): T => structuredClone(value);\n\nconst isRecord = (value: unknown): value is { readonly [key: string]: SessionTuningValue } =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst resolvePath = (\n bundle: SessionTuningBundle,\n path: SessionTuningPath,\n): SessionTuningValue | undefined => {\n const [category, ...parts] = path.split('.') as [SessionTuningCategory, ...string[]];\n let value: SessionTuningValue | undefined = bundle[category].values;\n for (const part of parts) {\n if (!isRecord(value)) return undefined;\n value = value[part];\n }\n return value;\n};\n\nexport interface SessionTuningResolver {\n get(path: SessionTuningPath): SessionTuningValue | undefined;\n getRect(path: SessionTuningPath): { x: number; y: number; width: number; height: number } | null;\n snapshot(): SessionTuningBundle;\n}\n\nexport const createSessionTuningResolver = (\n bundle: SessionTuningBundle,\n): SessionTuningResolver => ({\n get: (path) => clone(resolvePath(bundle, path)),\n getRect: (path) => {\n const value = resolvePath(bundle, path);\n if (!isRecord(value)) return null;\n const { x, y, width, height } = value;\n if (![x, y, width, height].every((item) => typeof item === 'number' && Number.isFinite(item)))\n return null;\n return { x: x as number, y: y as number, width: width as number, height: height as number };\n },\n snapshot: () => clone(bundle),\n});\n", "import { z } from 'zod';\nimport {\n SESSION_TUNING_SCHEMA_VERSION,\n type SessionTuningBundle,\n type SessionTuningDocument,\n type SessionTuningValue,\n} from './SessionTuningTypes.js';\n\nconst hexColor = /^#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$/;\n\nconst valueSchema: z.ZodType<SessionTuningValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number().finite(),\n z.boolean(),\n z.null(),\n z.array(valueSchema),\n z.record(z.string(), valueSchema),\n ]),\n);\n\nexport const sessionTuningDocumentSchema = z.object({\n schemaVersion: z.literal(SESSION_TUNING_SCHEMA_VERSION),\n values: z.record(z.string(), valueSchema),\n});\n\nexport const sessionTuningBundleSchema = z.object({\n layout: sessionTuningDocumentSchema,\n visual: sessionTuningDocumentSchema,\n motion: sessionTuningDocumentSchema,\n audio: sessionTuningDocumentSchema,\n});\n\nexport const validateTuningColor = (value: unknown): value is string =>\n typeof value === 'string' && hexColor.test(value);\n\nexport const parseSessionTuningDocument = (value: unknown): SessionTuningDocument =>\n sessionTuningDocumentSchema.parse(value);\n\nexport const parseSessionTuningBundle = (value: unknown): SessionTuningBundle =>\n sessionTuningBundleSchema.parse(value);\n", "import { parseSessionTuningDocument } from './SessionTuningSchema.js';\nimport { SESSION_TUNING_SCHEMA_VERSION, type SessionTuningDocument } from './SessionTuningTypes.js';\n\nexport interface SessionTuningMigrationResult {\n document: SessionTuningDocument;\n fromVersion: typeof SESSION_TUNING_SCHEMA_VERSION;\n removedPaths: readonly [];\n}\n\n/** Validates the current tuning contract. Legacy tuning documents are rejected. */\nexport const migrateSessionTuningDocumentWithResult = (\n input: unknown,\n): SessionTuningMigrationResult => {\n if (\n !input ||\n typeof input !== 'object' ||\n Array.isArray(input) ||\n (input as { schemaVersion?: unknown }).schemaVersion !== SESSION_TUNING_SCHEMA_VERSION\n ) {\n throw new Error(`\u4E0D\u652F\u6301\u7248\u672C: Visual Lab \u4EC5\u652F\u6301 tuning v${SESSION_TUNING_SCHEMA_VERSION}`);\n }\n const document = parseSessionTuningDocument(input);\n return {\n document,\n fromVersion: SESSION_TUNING_SCHEMA_VERSION,\n removedPaths: [],\n };\n};\n\nexport const migrateSessionTuningDocument = (input: unknown): SessionTuningDocument =>\n migrateSessionTuningDocumentWithResult(input).document;\n", "import {\n createSessionTuningResolver,\n type SessionTuningResolver,\n} from './SessionTuningResolver.js';\nimport { parseSessionTuningBundle } from './SessionTuningSchema.js';\n\nimport type {\n SessionTuningBundle,\n SessionTuningChange,\n SessionTuningPath,\n SessionTuningSnapshot,\n SessionTuningValue,\n} from './SessionTuningTypes.js';\n\nconst clone = <T>(value: T): T => structuredClone(value);\n\nconst hasOwn = (value: object, key: PropertyKey): boolean =>\n Object.prototype.hasOwnProperty.call(value, key);\n\nconst isTuningRecord = (value: SessionTuningValue): value is Record<string, SessionTuningValue> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst mergeTuningValue = (\n formal: SessionTuningValue,\n workspace: SessionTuningValue,\n): SessionTuningValue => {\n if (!isTuningRecord(formal) || !isTuningRecord(workspace)) return clone(workspace);\n const merged: Record<string, SessionTuningValue> = {};\n for (const [key, formalValue] of Object.entries(formal)) {\n merged[key] = hasOwn(workspace, key)\n ? mergeTuningValue(formalValue, workspace[key]!)\n : clone(formalValue);\n }\n return merged;\n};\n\nconst mergeWorkspaceWithFormal = (\n formal: SessionTuningBundle,\n workspace: SessionTuningBundle,\n): SessionTuningBundle => ({\n layout: {\n schemaVersion: formal.layout.schemaVersion,\n values: mergeTuningValue(formal.layout.values, workspace.layout.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n visual: {\n schemaVersion: formal.visual.schemaVersion,\n values: mergeTuningValue(formal.visual.values, workspace.visual.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n motion: {\n schemaVersion: formal.motion.schemaVersion,\n values: mergeTuningValue(formal.motion.values, workspace.motion.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n audio: {\n schemaVersion: formal.audio.schemaVersion,\n values: mergeTuningValue(formal.audio.values, workspace.audio.values) as Record<\n string,\n SessionTuningValue\n >,\n },\n});\n\nconst tuningValuesEqual = (\n left: SessionTuningValue | undefined,\n right: SessionTuningValue | undefined,\n): boolean => JSON.stringify(left) === JSON.stringify(right);\n\nconst setPath = (\n bundle: SessionTuningBundle,\n path: SessionTuningPath,\n value: SessionTuningValue | undefined,\n): void => {\n const [category, ...parts] = path.split('.') as [keyof SessionTuningBundle, ...string[]];\n if (\n !['layout', 'visual', 'motion', 'audio'].includes(category) ||\n parts.length === 0 ||\n parts.some((part) => !part || ['__proto__', 'prototype', 'constructor'].includes(part))\n )\n throw new Error('Invalid tuning path');\n let target = bundle[category].values as Record<string, SessionTuningValue>;\n for (const part of parts.slice(0, -1)) {\n const child = target[part];\n if (typeof child !== 'object' || child === null || Array.isArray(child)) target[part] = {};\n target = target[part] as Record<string, SessionTuningValue>;\n }\n const last = parts.at(-1);\n if (!last) return;\n if (value === undefined) delete target[last];\n else target[last] = clone(value);\n};\n\nconst rebaseTuningBundle = (\n previousFormal: SessionTuningBundle,\n value: SessionTuningBundle,\n nextFormal: SessionTuningBundle,\n editablePaths: readonly SessionTuningPath[],\n): SessionTuningBundle => {\n const normalizedValue = mergeWorkspaceWithFormal(previousFormal, value);\n const previousFormalResolver = createSessionTuningResolver(previousFormal);\n const valueResolver = createSessionTuningResolver(normalizedValue);\n const nextFormalResolver = createSessionTuningResolver(nextFormal);\n const rebased = clone(nextFormal);\n\n for (const path of editablePaths) {\n const previousFormalValue = previousFormalResolver.get(path);\n const nextFormalValue = nextFormalResolver.get(path);\n if (!tuningValuesEqual(previousFormalValue, nextFormalValue)) continue;\n setPath(rebased, path, valueResolver.get(path));\n }\n return rebased;\n};\n\nexport class SessionTuningStore {\n private formal: SessionTuningBundle;\n private workspaceSaved: SessionTuningBundle;\n private working: SessionTuningBundle;\n private readonly undoStack: SessionTuningChange[][] = [];\n private readonly redoStack: SessionTuningChange[][] = [];\n private readonly listeners = new Set<() => void>();\n\n constructor(\n defaults: SessionTuningBundle,\n private readonly editablePaths: readonly SessionTuningPath[] = flatten(defaults),\n ) {\n this.formal = clone(defaults);\n this.workspaceSaved = clone(defaults);\n this.working = clone(defaults);\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n getResolver(): SessionTuningResolver {\n return createSessionTuningResolver(this.working);\n }\n getFormalResolver(): SessionTuningResolver {\n return createSessionTuningResolver(this.formal);\n }\n getWorkspaceSavedResolver(): SessionTuningResolver {\n return createSessionTuningResolver(this.workspaceSaved);\n }\n getSnapshot(): SessionTuningSnapshot {\n return {\n formal: clone(this.formal),\n workspaceSaved: clone(this.workspaceSaved),\n working: clone(this.working),\n formalDirtyPaths: this.diff().map((change) => change.path),\n workspaceDirtyPaths: this.workspaceDiff().map((change) => change.path),\n };\n }\n patch(changes: readonly SessionTuningChange[], record = true): void {\n if (changes.length === 0) return;\n const next = clone(this.working);\n for (const change of changes) setPath(next, change.path, change.after);\n this.working = parseSessionTuningBundle(next);\n if (record) {\n this.undoStack.push(changes.map(clone));\n this.redoStack.length = 0;\n }\n this.emit();\n }\n set(path: SessionTuningPath, value: SessionTuningValue): void {\n this.patch([{ path, before: this.getResolver().get(path), after: value }]);\n }\n resetPath(path: SessionTuningPath): void {\n this.patch([\n {\n path,\n before: this.getResolver().get(path),\n after: this.getWorkspaceSavedResolver().get(path),\n },\n ]);\n }\n resetAll(): void {\n this.working = clone(this.workspaceSaved);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n saveWorkspace(): void {\n this.workspaceSaved = clone(this.working);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n markApplied(paths: readonly SessionTuningPath[]): void {\n if (paths.length === 0) return;\n const resolver = this.getResolver();\n for (const path of paths) setPath(this.formal, path, resolver.get(path));\n // An undo after writing source defaults would only change the workspace and\n // would no longer describe the formal configuration on disk.\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n loadWorkspace(value: SessionTuningBundle, formalBaseline?: SessionTuningBundle): void {\n const workspace = formalBaseline\n ? rebaseTuningBundle(formalBaseline, value, this.formal, this.editablePaths)\n : clone(this.formal);\n this.working = clone(workspace);\n this.workspaceSaved = clone(workspace);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n rebaseFormal(nextFormal: SessionTuningBundle): void {\n if (JSON.stringify(this.formal) === JSON.stringify(nextFormal)) return;\n const previousFormal = this.formal;\n this.workspaceSaved = rebaseTuningBundle(\n previousFormal,\n this.workspaceSaved,\n nextFormal,\n this.editablePaths,\n );\n this.working = rebaseTuningBundle(previousFormal, this.working, nextFormal, this.editablePaths);\n this.formal = clone(nextFormal);\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.emit();\n }\n undo(): void {\n const changes = this.undoStack.pop();\n if (!changes) return;\n for (const change of changes) setPath(this.working, change.path, change.before);\n this.redoStack.push(changes);\n this.emit();\n }\n redo(): void {\n const changes = this.redoStack.pop();\n if (!changes) return;\n for (const change of changes) setPath(this.working, change.path, change.after);\n this.undoStack.push(changes);\n this.emit();\n }\n diff(): SessionTuningChange[] {\n return this.diffFrom(this.formal);\n }\n workspaceDiff(): SessionTuningChange[] {\n return this.diffFrom(this.workspaceSaved);\n }\n private diffFrom(baseline: SessionTuningBundle): SessionTuningChange[] {\n const paths = new Set([...flatten(baseline), ...flatten(this.working)]);\n const saved = createSessionTuningResolver(baseline);\n const working = this.getResolver();\n const changes: SessionTuningChange[] = [];\n for (const path of paths) {\n const before = saved.get(path);\n const after = working.get(path);\n if (JSON.stringify(before) !== JSON.stringify(after)) changes.push({ path, before, after });\n }\n return changes;\n }\n private emit(): void {\n for (const listener of this.listeners) listener();\n }\n}\n\nconst flatten = (bundle: SessionTuningBundle): SessionTuningPath[] => {\n const paths: SessionTuningPath[] = [];\n const visit = (value: SessionTuningValue, prefix: string): void => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n paths.push(prefix as SessionTuningPath);\n return;\n }\n for (const [key, child] of Object.entries(value)) visit(child, `${prefix}.${key}`);\n };\n for (const [category, document] of Object.entries(bundle)) visit(document.values, category);\n return paths;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["clone"]
|
|
7
|
+
}
|