@virtualdisplay.io/client 4.0.0-alpha.1 → 4.0.0-alpha.2
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/CHANGELOG.md +6 -0
- package/dist/index.d.ts +0 -414
- package/dist/virtualdisplay.client.cjs.js +1 -2
- package/dist/virtualdisplay.client.es.js +798 -2
- package/dist/virtualdisplay.client.umd.js +1 -2
- package/package.json +1 -1
- package/dist/virtualdisplay.client.cjs.js.map +0 -1
- package/dist/virtualdisplay.client.es.js.map +0 -1
- package/dist/virtualdisplay.client.umd.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"virtualdisplay.client.umd.js","sources":["../src/client/error.ts","../src/client/iframe-builder.ts","../../logger/src/write.ts","../../logger/src/format.ts","../../logger/src/logger.ts","../../utils/src/date.ts","../src/utils/logger.ts","../src/client/ready-state-tracker.ts","../src/attributes/value.ts","../src/attributes/attribute.ts","../src/attributes/selections.ts","../../messaging/src/types.ts","../../messaging/src/guards.ts","../../messaging/src/queue.ts","../../messaging/src/messenger.ts","../src/attributes/attributes.ts","../src/camera/camera.ts","../src/camera/position.ts","../src/snapshot/photo.ts","../src/snapshot/snapshot.ts","../src/ui/viewer-config.ts","../src/client/client.ts"],"sourcesContent":["/**\n * Error codes for Virtualdisplay client errors\n */\nexport const ERROR_CODES = {\n NO_MAPPING: 'NO_MAPPING',\n ATTRIBUTE_NOT_FOUND: 'ATTRIBUTE_NOT_FOUND',\n VALUE_NOT_FOUND: 'VALUE_NOT_FOUND',\n PARENT_NOT_FOUND: 'PARENT_NOT_FOUND',\n INVALID_PARAMETER: 'INVALID_PARAMETER',\n SNAPSHOT_FAILED: 'SNAPSHOT_FAILED',\n} as const;\n\nexport type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];\n\n/**\n * Custom error class for Virtualdisplay client errors\n */\nexport class VirtualdisplayError extends Error {\n public readonly code: ErrorCode;\n\n constructor(message: string, code: ErrorCode) {\n super(message);\n this.name = 'VirtualdisplayError';\n this.code = code;\n\n // Maintain proper stack trace for where our error was thrown\n if (Error.captureStackTrace !== undefined) {\n Error.captureStackTrace(this, VirtualdisplayError);\n }\n }\n\n public static noMapping(): VirtualdisplayError {\n return new VirtualdisplayError(\n 'No attributes available. Waiting for server metadata.',\n ERROR_CODES.NO_MAPPING,\n );\n }\n\n public static attributeNotFound(attributeName: string): VirtualdisplayError {\n return new VirtualdisplayError(\n `Attribute \"${attributeName}\" not found in mapping`,\n ERROR_CODES.ATTRIBUTE_NOT_FOUND,\n );\n }\n\n public static valueNotFound(attributeName: string, valueName: string): VirtualdisplayError {\n return new VirtualdisplayError(\n `Value \"${valueName}\" not found for attribute \"${attributeName}\"`,\n ERROR_CODES.VALUE_NOT_FOUND,\n );\n }\n\n public static parentNotFound(selector: string): VirtualdisplayError {\n return new VirtualdisplayError(\n `Parent element not found: ${selector}`,\n ERROR_CODES.PARENT_NOT_FOUND,\n );\n }\n\n public static invalidParameter(reason: string): VirtualdisplayError {\n return new VirtualdisplayError(reason, ERROR_CODES.INVALID_PARAMETER);\n }\n\n public static snapshotFailed(filename: string, reason: string): VirtualdisplayError {\n return new VirtualdisplayError(\n `Snapshot failed for \"${filename}\": ${reason}`,\n ERROR_CODES.SNAPSHOT_FAILED,\n );\n }\n}\n","/// <reference types=\"vite/client\" />\n\nimport { VirtualdisplayError } from './error';\nimport type { ClientOptions } from './options';\n\n/**\n * Builds and configures the iframe for Virtualdisplay\n */\nexport class IframeBuilder {\n private readonly defaultServer = 'https://server.virtualdisplay.io';\n\n private readonly options: ClientOptions;\n\n constructor(options: ClientOptions) {\n this.options = options;\n }\n\n /**\n * Create iframe element with Virtualdisplay viewer\n */\n public create(): HTMLIFrameElement {\n const iframe = this.createIframe();\n this.attachToParent(iframe);\n return iframe;\n }\n\n private createIframe(): HTMLIFrameElement {\n const iframe = document.createElement('iframe');\n iframe.src = this.buildUrl();\n\n // Apply styles inline to avoid param reassign\n Object.assign(iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none',\n });\n\n // Apply attributes\n iframe.setAttribute('allow', 'xr-spatial-tracking; fullscreen');\n\n return iframe;\n }\n\n private attachToParent(iframe: HTMLIFrameElement): void {\n const parentElement = this.getParentElement();\n parentElement.appendChild(iframe);\n }\n\n private buildUrl(): string {\n const serverUrl = this.getServerUrl();\n const url = new URL(serverUrl);\n\n // Add required parameters\n url.searchParams.set('license', this.options.license);\n url.searchParams.set('model', this.options.model);\n\n // Add optional parameters\n if (this.options.debug === true) {\n url.searchParams.set('debug', 'true');\n }\n\n if (this.options.language !== undefined) {\n url.searchParams.set('language', this.options.language);\n }\n\n return url.toString();\n }\n\n /**\n * Get server URL from environment or use default\n */\n private getServerUrl(): string {\n // Check for environment variable (set during build)\n if (import.meta?.env?.VITE_VIRTUALDISPLAY_SERVER_HOST !== undefined) {\n return import.meta.env.VITE_VIRTUALDISPLAY_SERVER_HOST as string;\n }\n\n return this.defaultServer;\n }\n\n /**\n * Get parent element from selector or element\n */\n private getParentElement(): HTMLElement {\n if (typeof this.options.parent !== 'string') {\n return this.options.parent;\n }\n\n const element = document.querySelector(this.options.parent);\n if (element === null) {\n throw VirtualdisplayError.parentNotFound(this.options.parent);\n }\n\n return element as HTMLElement;\n }\n}\n","import type { LogRecord } from './types.js';\nimport { formatDev, formatProd } from './format.js';\n\n/**\n * Write log record to console.\n *\n * This is the only module that uses console.* directly.\n * All other code should use the Logger class instead.\n */\nexport function write(record: LogRecord, isDev: boolean): void {\n\tconst output = isDev ? formatDev(record) : formatProd(record);\n\n\t// Use appropriate console method based on log level\n\t// console.* is allowed here as this is the logging output module\n\tif (record.level === 'error') {\n\t\tconsole.error(output);\n\t} else if (record.level === 'warn') {\n\t\tconsole.warn(output);\n\t} else {\n\t\tconsole.log(output);\n\t}\n}\n","import type { LogRecord } from './types.js';\n\n/**\n * Format log record for development (human-readable).\n */\nexport function formatDev(record: LogRecord): string {\n\tconst time = new Date(record.timestamp).toLocaleTimeString('en-GB', { hour12: false });\n\tconst level = record.level.toUpperCase().padEnd(5);\n\tconst trace = record.trace_id?.slice(0, 8) ?? '--------';\n\tconst msg = record.msg;\n\n\t// Extract additional attributes (excluding known fields)\n\tconst knownFields = ['timestamp', 'level', 'msg', 'service', 'trace_id', 'span_id'];\n\tconst attrs = Object.entries(record)\n\t\t.filter(([key]) => !knownFields.includes(key))\n\t\t.map(([key, value]) => `${key}=${JSON.stringify(value)}`)\n\t\t.join(' ');\n\n\treturn `${time} ${level} [${trace}] ${msg}${attrs ? ' ' + attrs : ''}`;\n}\n\n/**\n * Format log record for production (JSON).\n */\nexport function formatProd(record: LogRecord): string {\n\treturn JSON.stringify(record);\n}\n","import type { LoggerConfig, LogLevel, LogRecord } from './types.js';\nimport { write } from './write.js';\nimport { toUTC, now } from '@virtualdisplay.io/utils';\n\n/**\n * Logger instance with optional trace correlation.\n */\nexport class Logger {\n\tconstructor(readonly config: LoggerConfig) {}\n\n\t/**\n\t * Log at specified level with optional attributes.\n\t */\n\tlog(level: LogLevel, msg: string, attrs: Record<string, unknown> = {}): void {\n\t\tconst traceContext = this.config.traceProvider?.() ?? {};\n\n\t\tconst record: LogRecord = {\n\t\t\ttimestamp: toUTC(now()),\n\t\t\tlevel,\n\t\t\tmsg,\n\t\t\tservice: this.config.service,\n\t\t\t...traceContext,\n\t\t\t...attrs\n\t\t};\n\n\t\twrite(record, this.config.isDev);\n\t}\n\n\t/**\n\t * Log at DEBUG level (development only).\n\t */\n\tdebug(msg: string, attrs?: Record<string, unknown>): void {\n\t\tif (this.config.isDev) {\n\t\t\tthis.log('debug', msg, attrs);\n\t\t}\n\t}\n\n\t/**\n\t * Log at INFO level.\n\t */\n\tinfo(msg: string, attrs?: Record<string, unknown>): void {\n\t\tthis.log('info', msg, attrs);\n\t}\n\n\t/**\n\t * Log at WARN level.\n\t */\n\twarn(msg: string, attrs?: Record<string, unknown>): void {\n\t\tthis.log('warn', msg, attrs);\n\t}\n\n\t/**\n\t * Log at ERROR level.\n\t */\n\terror(msg: string, attrs?: Record<string, unknown>): void {\n\t\tthis.log('error', msg, attrs);\n\t}\n\n\t/**\n\t * Create a child logger with additional default attributes.\n\t */\n\twith(attrs: Record<string, unknown>): Logger {\n\t\tconst childLogger = new Logger(this.config);\n\n\t\tchildLogger.log = ((\n\t\t\tlevel: LogLevel,\n\t\t\tmsg: string,\n\t\t\textraAttrs: Record<string, unknown> = {}\n\t\t): void => {\n\t\t\tthis.log(level, msg, { ...attrs, ...extraAttrs });\n\t\t}).bind(this);\n\n\t\treturn childLogger;\n\t}\n}\n\n/**\n * Create a new logger instance.\n *\n * @param service - Service name (e.g., \"portal\", \"admin\", \"viewer\")\n * @param options - Optional configuration overrides\n */\nexport function createLogger(\n\tservice: string,\n\toptions?: { isDev?: boolean; traceProvider?: LoggerConfig['traceProvider'] },\n): Logger {\n\tconst isDev =\n\t\toptions?.isDev !== undefined\n\t\t\t? options.isDev\n\t\t\t: typeof process !== 'undefined'\n\t\t\t\t? process.env.NODE_ENV !== 'production'\n\t\t\t\t: true;\n\n\treturn new Logger({ service, isDev, traceProvider: options?.traceProvider });\n}\n","/**\n * Date utilities for UTC consistency with backend.\n *\n * The backend stores all dates in UTC. This module provides utilities to:\n * - Send dates to API in UTC format\n * - Parse dates from API (which are always UTC)\n * - Get current time consistently\n */\n\n/**\n * Get current time as Date object.\n *\n * Browser's Date object is always in user's local timezone internally,\n * but can be converted to UTC for API calls.\n *\n * @returns Current date/time\n */\nexport function now(): Date {\n return new Date();\n}\n\n/**\n * Convert Date to UTC ISO 8601 string for API requests.\n *\n * @param date - Date to convert\n * @returns ISO 8601 string in UTC (e.g., \"2026-01-24T12:30:45.123Z\")\n *\n * @example\n * const date = new Date('2026-01-24T12:30:45');\n * toUTC(date); // \"2026-01-24T11:30:45.000Z\" (if in CET timezone)\n */\nexport function toUTC(date: Date): string {\n return date.toISOString();\n}\n\n/**\n * Parse UTC ISO 8601 string from API response to Date object.\n *\n * The Date object will be in user's local timezone, but represents\n * the same moment in time as the UTC string.\n *\n * @param utcString - ISO 8601 UTC string from API\n * @returns Date object in user's local timezone\n *\n * @example\n * const date = parseUTC('2026-01-24T12:00:00Z');\n * // If user is in CET (UTC+1):\n * // date.toString() => \"Fri Jan 24 2026 13:00:00 GMT+0100\"\n */\nexport function parseUTC(utcString: string): Date {\n return new Date(utcString);\n}\n\n/**\n * Format Date as YYYY-MM-DD in UTC for API date parameters.\n *\n * Use this when sending date-only values to the API (e.g., filters, ranges).\n *\n * @param date - Date to format\n * @returns Date string in UTC (YYYY-MM-DD)\n *\n * @example\n * const date = new Date('2026-01-24T23:30:00'); // CET timezone\n * formatForAPI(date); // \"2026-01-24\" (UTC date part)\n */\nexport function formatForAPI(date: Date): string {\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Get start of day in UTC (00:00:00.000Z).\n *\n * @param date - Date to get start of day for\n * @returns New Date at 00:00:00.000 UTC\n *\n * @example\n * const date = new Date('2026-01-24T15:30:00Z');\n * const start = startOfDayUTC(date);\n * start.toISOString(); // \"2026-01-24T00:00:00.000Z\"\n */\nexport function startOfDayUTC(date: Date): Date {\n const d = new Date(date);\n d.setUTCHours(0, 0, 0, 0);\n return d;\n}\n\n/**\n * Get end of day in UTC (23:59:59.999Z).\n *\n * @param date - Date to get end of day for\n * @returns New Date at 23:59:59.999 UTC\n *\n * @example\n * const date = new Date('2026-01-24T15:30:00Z');\n * const end = endOfDayUTC(date);\n * end.toISOString(); // \"2026-01-24T23:59:59.999Z\"\n */\nexport function endOfDayUTC(date: Date): Date {\n const d = new Date(date);\n d.setUTCHours(23, 59, 59, 999);\n return d;\n}\n\n/**\n * Add days to a date in UTC.\n *\n * @param date - Starting date\n * @param days - Number of days to add (can be negative)\n * @returns New Date with days added\n *\n * @example\n * const date = new Date('2026-01-24T12:00:00Z');\n * const tomorrow = addDaysUTC(date, 1);\n * tomorrow.toISOString(); // \"2026-01-25T12:00:00.000Z\"\n */\nexport function addDaysUTC(date: Date, days: number): Date {\n const d = new Date(date);\n d.setUTCDate(d.getUTCDate() + days);\n return d;\n}\n","import { createLogger, type Logger } from '@virtualdisplay.io/logger';\n\ntype Attrs = Record<string, unknown> | unknown;\n\nfunction normalise(attrs?: Attrs): Record<string, unknown> | undefined {\n\tif (attrs === undefined) return undefined;\n\tif (typeof attrs === 'object' && attrs !== null && !Array.isArray(attrs) && !(attrs instanceof Error)) {\n\t\treturn attrs as Record<string, unknown>;\n\t}\n\tif (attrs instanceof Error) {\n\t\treturn { error: attrs.message };\n\t}\n\treturn { value: attrs };\n}\n\nfunction createClientLogger(base: Logger) {\n\treturn {\n\t\tdebug: (msg: string, attrs?: Attrs) => base.debug(msg, normalise(attrs)),\n\t\tinfo: (msg: string, attrs?: Attrs) => base.info(msg, normalise(attrs)),\n\t\twarn: (msg: string, attrs?: Attrs) => base.warn(msg, normalise(attrs)),\n\t\terror: (msg: string, attrs?: Attrs) => base.error(msg, normalise(attrs)),\n\t};\n}\n\n/**\n * Client logger — starts in production mode (no debug output).\n * Call enableDebugMode() to enable debug logging.\n */\nlet debugMode = false;\n\nexport let logger = createClientLogger(createLogger('client', { isDev: false }));\n\nexport function enableDebugMode(): void {\n\tdebugMode = true;\n\tlogger = createClientLogger(createLogger('client', { isDev: true }));\n}\n\nexport function isDebugMode(): boolean {\n\treturn debugMode;\n}\n","import type { MutationResultMessage } from '@virtualdisplay.io/messaging';\nimport { logger } from '../utils/logger';\n\n/**\n * Tracks when the client is ready for use\n *\n * Ready is triggered by whichever arrives first:\n * - A mapping message from the server (server-driven flow)\n * - An initial mutation result (legacy/client-driven flow)\n */\nexport class ReadyStateTracker {\n private ready = false;\n\n private readonly onReady: (() => void) | undefined;\n\n constructor(onReady?: () => void) {\n this.onReady = onReady;\n }\n\n /**\n * Handle mapping received from server — marks client as ready\n */\n public handleMappingReceived(): void {\n this.markReady('Mapping received from server');\n }\n\n /**\n * Handle incoming mutation result message\n * When the first initial mutation result arrives, the client is considered ready\n */\n public handleMutationResult(message: MutationResultMessage): void {\n if (message.isInitial) {\n this.markReady('Initial mutations processed');\n }\n }\n\n public get isReady(): boolean {\n return this.ready;\n }\n\n public clear(): void {\n this.ready = false;\n }\n\n private markReady(reason: string): void {\n if (this.ready) {\n return;\n }\n\n this.ready = true;\n logger.info(`${reason} - client is now ready`);\n this.onReady?.();\n }\n}\n","/**\n * Pure domain entity representing a single attribute value\n * No side effects - only state\n */\nexport class AttributeValue {\n private isSelectedValue = false;\n\n constructor(\n public readonly value: string,\n public readonly attribute: string,\n ) {}\n\n public get isSelected(): boolean {\n return this.isSelectedValue;\n }\n\n public setSelected(selected: boolean): void {\n this.isSelectedValue = selected;\n }\n}\n","import { AttributeValue } from './value';\nimport { VirtualdisplayError } from '../client/error';\n\nexport interface AttributeOptions {\n readonly name: string;\n readonly values: string[];\n readonly onSelect: (name: string, value: string) => void;\n readonly defaultValue?: string;\n}\n\n/**\n * Represents a single attribute with its available values\n * Flat API - no source/target translation, works with server values directly\n */\nexport class Attribute {\n public readonly name: string;\n\n public readonly defaultValue: string | undefined;\n\n private readonly values: Map<string, AttributeValue>;\n\n private readonly onSelect: (name: string, value: string) => void;\n\n private onChangeCallback?: (attributeValue: AttributeValue) => void;\n\n constructor(options: AttributeOptions) {\n this.name = options.name;\n this.defaultValue = options.defaultValue;\n this.onSelect = options.onSelect;\n this.values = this.createValues(options.values);\n }\n\n /**\n * Select a value for this attribute\n * @throws {VirtualdisplayError} When value not found\n */\n public select(value: string): void {\n if (!this.values.has(value)) {\n throw VirtualdisplayError.valueNotFound(this.name, value);\n }\n\n this.onSelect(this.name, value);\n }\n\n public getValues(): AttributeValue[] {\n return Array.from(this.values.values());\n }\n\n public getValue(value: string): AttributeValue | undefined {\n return this.values.get(value);\n }\n\n /**\n * Register callback that fires when selection changes\n * Receives the newly selected AttributeValue\n */\n public onChange(callback: (selectedValue: AttributeValue) => void): void {\n this.onChangeCallback = callback;\n }\n\n /**\n * Update selection state from server mutation result\n * Direct value comparison - no source/target translation\n */\n public handleResult(serverValue: string): void {\n let selectedValue: AttributeValue | undefined;\n\n for (const attributeValue of this.values.values()) {\n const isMatch = attributeValue.value === serverValue;\n attributeValue.setSelected(isMatch);\n if (isMatch) {\n selectedValue = attributeValue;\n }\n }\n\n if (selectedValue !== undefined && this.onChangeCallback !== undefined) {\n this.onChangeCallback(selectedValue);\n }\n }\n\n private createValues(values: string[]): Map<string, AttributeValue> {\n const valueMap = new Map<string, AttributeValue>();\n\n for (const value of values) {\n const attributeValue = new AttributeValue(value, this.name);\n if (value === this.defaultValue) {\n attributeValue.setSelected(true);\n }\n valueMap.set(value, attributeValue);\n }\n\n return valueMap;\n }\n}\n","import { Attribute, type AttributeOptions } from './attribute';\nimport type { AttributeDefinition, MutationDto, MutationResult } from '@virtualdisplay.io/messaging';\n\n/**\n * Creates and tracks attribute instances from server definitions.\n * Handles mutation results by updating the corresponding attribute.\n */\nexport class AttributeRegistry {\n private readonly attributes = new Map<string, Attribute>();\n\n constructor(\n definitions: AttributeDefinition[],\n sendMutation: (mutations: MutationDto[], isInitial: boolean) => void,\n onMutationFailed?: (attribute: string, value: string) => void,\n ) {\n for (const definition of definitions) {\n const options = this.buildOptions(definition, sendMutation);\n this.attributes.set(definition.name, new Attribute(options));\n }\n this.onMutationFailed = onMutationFailed;\n }\n\n private readonly onMutationFailed: ((attribute: string, value: string) => void) | undefined;\n\n public get(name: string): Attribute | undefined {\n return this.attributes.get(name);\n }\n\n public defaults(): MutationDto[] {\n const mutations: MutationDto[] = [];\n\n for (const attribute of this.attributes.values()) {\n if (attribute.defaultValue !== undefined) {\n mutations.push({\n name: attribute.name,\n value: attribute.defaultValue,\n });\n }\n }\n\n return mutations;\n }\n\n public handleResults(results: MutationResult[]): void {\n for (const result of results) {\n if (!result.success) {\n this.onMutationFailed?.(result.name, result.value);\n continue;\n }\n\n this.get(result.name)?.handleResult(result.value);\n }\n }\n\n private buildOptions(\n definition: AttributeDefinition,\n sendMutation: (mutations: MutationDto[], isInitial: boolean) => void,\n ): AttributeOptions {\n const base = {\n name: definition.name,\n values: definition.values,\n onSelect: (name: string, value: string) => {\n sendMutation([{ name, value }], false);\n },\n };\n\n if (definition.defaultValue !== undefined) {\n return { ...base, defaultValue: definition.defaultValue };\n }\n\n return base;\n }\n}\n","/**\n * Virtualdisplay iframe messaging protocol\n *\n * Defines all message types exchanged between client (parent page)\n * and server (iframe viewer engine) via postMessage.\n */\n\n/**\n * Message type identifiers\n */\nexport const MESSAGE_TYPES = {\n READY: 'ready',\n MUTATION: 'mutation',\n STATE: 'state',\n CONFIG: 'config',\n CAMERA: 'camera',\n SNAPSHOT: 'snapshot',\n MAPPING: 'mapping',\n} as const;\n\n// ── Mutation protocol ──────────────────────────\n\n/**\n * Mutation DTO — selecting an attribute or material value\n */\nexport interface MutationDto {\n readonly name: string;\n readonly value: string;\n}\n\n/**\n * Result of processing a mutation\n */\nexport interface MutationResult {\n readonly name: string;\n readonly value: string;\n readonly success: boolean;\n readonly error?: string;\n}\n\n/**\n * Mutation message (client → server)\n */\nexport interface MutationMessage {\n readonly type: typeof MESSAGE_TYPES.MUTATION;\n readonly mutations: MutationDto[];\n readonly isInitial: boolean;\n}\n\n/**\n * Mutation result message (server → client)\n */\nexport interface MutationResultMessage {\n readonly type: typeof MESSAGE_TYPES.MUTATION;\n readonly results: MutationResult[];\n readonly isInitial: boolean;\n}\n\n// ── Config protocol ────────────────────────────\n\n/**\n * UI configuration options\n */\nexport interface UIConfig {\n readonly arEnabled?: boolean;\n readonly fullscreenEnabled?: boolean;\n readonly loadingIndicatorEnabled?: boolean;\n}\n\n/**\n * Camera configuration options\n */\nexport interface CameraConfig {\n readonly initialRotate?: number;\n readonly initialTilt?: number;\n readonly initialZoom?: number;\n readonly minZoom?: number;\n readonly maxZoom?: number;\n readonly minTilt?: number;\n readonly maxTilt?: number;\n}\n\n/**\n * Viewer configuration\n */\nexport interface ViewerConfig {\n readonly ui?: UIConfig;\n readonly camera?: CameraConfig;\n}\n\n/**\n * Configuration message (client → server)\n */\nexport interface ConfigMessage {\n readonly type: typeof MESSAGE_TYPES.CONFIG;\n readonly config: ViewerConfig;\n}\n\n// ── Camera protocol ────────────────────────────\n\n/**\n * Camera command (client → server)\n */\nexport interface CameraCommand {\n readonly action: 'rotate' | 'tilt' | 'zoom' | 'reset';\n readonly value?: number;\n}\n\n/**\n * Camera parameter (server → client)\n */\nexport interface CameraParameter {\n readonly type: 'rotation' | 'tilt' | 'zoom';\n readonly value: number;\n}\n\n/**\n * Camera command message (client → server)\n */\nexport interface CameraCommandMessage {\n readonly type: typeof MESSAGE_TYPES.CAMERA;\n readonly commands: CameraCommand[];\n}\n\n/**\n * Camera position message (server → client)\n */\nexport interface CameraPositionMessage {\n readonly type: typeof MESSAGE_TYPES.CAMERA;\n readonly transforms: CameraParameter[];\n}\n\n/**\n * Union of camera messages\n */\nexport type CameraMessage = CameraCommandMessage | CameraPositionMessage;\n\n// ── Snapshot protocol ──────────────────────────\n\n/**\n * Snapshot request (client → server)\n */\nexport interface SnapshotRequestMessage {\n readonly type: typeof MESSAGE_TYPES.SNAPSHOT;\n readonly filename: string;\n}\n\n/**\n * Snapshot result (server → client)\n *\n * Always sent in response to a request — even on failure.\n * On success: `data` contains the base64 data URL.\n * On failure: `error` describes what went wrong.\n */\nexport interface SnapshotResultMessage {\n readonly type: typeof MESSAGE_TYPES.SNAPSHOT;\n readonly filename: string;\n readonly data?: string;\n readonly error?: string;\n}\n\n// ── Mapping protocol ───────────────────────────\n\n/**\n * Attribute definition sent from server to client\n */\nexport interface AttributeDefinition {\n readonly name: string;\n readonly values: string[];\n readonly defaultValue?: string;\n}\n\n/**\n * Mapping message (server → client)\n */\nexport interface MappingMessage {\n readonly type: typeof MESSAGE_TYPES.MAPPING;\n readonly attributes: AttributeDefinition[];\n}\n\n// ── Ready protocol ────────────────────────────\n\n/**\n * Ready message (server → client)\n *\n * Sent by the server when its message listener is active.\n * The client uses this to know when the server can receive messages,\n * instead of relying on the iframe load event (which fires before\n * module scripts execute).\n */\nexport interface ReadyMessage {\n readonly type: typeof MESSAGE_TYPES.READY;\n}\n\n// ── Union ──────────────────────────────────────\n\n/**\n * Union of all protocol messages\n */\nexport type Message =\n | ReadyMessage\n | MutationMessage\n | MutationResultMessage\n | ConfigMessage\n | CameraCommandMessage\n | CameraPositionMessage\n | SnapshotRequestMessage\n | SnapshotResultMessage\n | MappingMessage;\n\n/**\n * Callback for handling a message\n */\nexport type MessageCallback = (message: Message) => void | Promise<void>;\n","import {\n MESSAGE_TYPES,\n type Message,\n type ReadyMessage,\n type MutationMessage,\n type MutationResultMessage,\n type SnapshotRequestMessage,\n type SnapshotResultMessage,\n type CameraCommandMessage,\n type CameraPositionMessage,\n type ConfigMessage,\n type MappingMessage,\n} from './types';\n\n/** All valid message type values */\nconst VALID_MESSAGE_TYPES = new Set<string>(Object.values(MESSAGE_TYPES));\n\n/**\n * Check if data looks like a valid protocol message\n * Filters out browser extension messages and non-objects\n */\nexport function isValidMessageData(data: unknown): data is Message {\n if (typeof data !== 'object' || data === null) {\n return false;\n }\n\n const obj = data as Record<string, unknown>;\n\n // Filter browser extension messages (they have a string `target` field)\n if (typeof obj.target === 'string') {\n return false;\n }\n\n return typeof obj.type === 'string' && VALID_MESSAGE_TYPES.has(obj.type);\n}\n\n// ── Type-specific guards ───────────────────────\n\nexport function isReadyMessage(data: unknown): data is ReadyMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.READY;\n}\n\nexport function isMutationMessage(data: unknown): data is MutationMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.MUTATION && Array.isArray(obj.mutations);\n}\n\nexport function isMutationResultMessage(data: unknown): data is MutationResultMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.MUTATION && Array.isArray(obj.results);\n}\n\nexport function isConfigMessage(data: unknown): data is ConfigMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.CONFIG && typeof obj.config === 'object';\n}\n\nexport function isSnapshotRequestMessage(data: unknown): data is SnapshotRequestMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.SNAPSHOT && typeof obj.filename === 'string' && !('data' in obj) && !('error' in obj);\n}\n\nexport function isSnapshotResultMessage(data: unknown): data is SnapshotResultMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.SNAPSHOT && ('data' in obj || 'error' in obj);\n}\n\nexport function isCameraCommandMessage(data: unknown): data is CameraCommandMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.CAMERA && Array.isArray(obj.commands);\n}\n\nexport function isCameraPositionMessage(data: unknown): data is CameraPositionMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.CAMERA && Array.isArray(obj.transforms);\n}\n\nexport function isMappingMessage(data: unknown): data is MappingMessage {\n const obj = asObject(data);\n return obj !== null && obj.type === MESSAGE_TYPES.MAPPING && Array.isArray(obj.attributes);\n}\n\nfunction asObject(data: unknown): Record<string, unknown> | null {\n if (typeof data !== 'object' || data === null) {\n return null;\n }\n return data as Record<string, unknown>;\n}\n","import type { Message } from './types';\n\n/**\n * Buffers messages until ready, then flushes to the processor\n *\n * Used by both client (outgoing queue) and server (incoming queue)\n * to handle messages before the other side is ready.\n */\nexport class MessageQueue {\n private readonly pending: Message[] = [];\n\n private isReady = false;\n\n constructor(private readonly processor: (message: Message) => void) {}\n\n /**\n * Add a message — processed immediately if ready, queued otherwise\n */\n public enqueue(message: Message): void {\n if (this.isReady) {\n this.processor(message);\n return;\n }\n\n this.pending.push(message);\n }\n\n /**\n * Mark as ready and flush all pending messages\n */\n public markReady(): void {\n if (this.isReady) {\n return;\n }\n\n this.isReady = true;\n\n if (this.pending.length === 0) {\n return;\n }\n\n const messages = [...this.pending];\n this.pending.length = 0;\n\n for (const message of messages) {\n this.processor(message);\n }\n }\n\n public get ready(): boolean {\n return this.isReady;\n }\n\n public get pendingCount(): number {\n return this.pending.length;\n }\n\n /**\n * Check if a message of a specific type is pending\n */\n public hasPending(type: string): boolean {\n return this.pending.some(msg => msg.type === type);\n }\n\n /**\n * Clear all pending messages\n */\n public clear(): void {\n this.pending.length = 0;\n }\n}\n","import { isValidMessageData } from './guards';\nimport { MessageQueue } from './queue';\nimport type { Message, MessageCallback } from './types';\n\n/**\n * Options for IframeMessenger\n */\nexport interface MessengerOptions {\n /**\n * Buffer incoming messages until markConsumerReady().\n * When false (default), incoming messages are processed immediately.\n */\n bufferIncoming?: boolean;\n\n /**\n * When buffering, messages where this returns false bypass the buffer\n * and are routed to handlers immediately. Used for urgent messages\n * like CONFIG that must be handled before the model loads.\n */\n shouldBuffer?: (message: Message) => boolean;\n}\n\n/**\n * Bidirectional iframe message port\n *\n * Handles sending and receiving messages via postMessage between\n * a parent page and an iframe. Both client and server use this —\n * the only difference is the target window.\n *\n * Features:\n * - Publisher queue: outgoing messages buffered until markReady()\n * - Consumer queue: incoming messages buffered until markConsumerReady() (opt-in)\n * - Handler registration: route incoming messages by type\n * - Echo prevention: tags outgoing messages to filter self-delivery\n * - Cleanup: removes listener on destroy\n *\n * @example Client (parent page — publisher queue only)\n * ```ts\n * const messenger = new IframeMessenger(() => iframe.contentWindow);\n * messenger.on('mutation', handleMutationResult);\n * messenger.on('mapping', handleMapping);\n * messenger.start();\n * // When iframe loads:\n * messenger.markReady();\n * messenger.send({ type: 'mutation', mutations, isInitial: true });\n * ```\n *\n * @example Server (inside iframe — both queues, CONFIG bypasses consumer)\n * ```ts\n * const messenger = new IframeMessenger(\n * () => window.parent !== window ? window.parent : null,\n * { bufferIncoming: true, shouldBuffer: (msg) => msg.type !== 'config' },\n * );\n * messenger.on('mutation', processMutation);\n * messenger.on('config', applyConfig);\n * messenger.start();\n * messenger.markReady();\n * // When model loads:\n * messenger.markConsumerReady();\n * ```\n */\nexport class IframeMessenger {\n private readonly handlers = new Map<string, MessageCallback>();\n\n private readonly publisher: MessageQueue;\n\n private readonly consumer: MessageQueue;\n\n private readonly shouldBuffer: ((message: Message) => boolean) | null;\n\n private listener: ((event: MessageEvent) => void) | null = null;\n\n private readonly sourceId: string;\n\n constructor(\n private readonly getTarget: () => Window | null,\n options?: MessengerOptions,\n ) {\n this.sourceId = `vd-${Math.random().toString(36).slice(2, 8)}`;\n this.publisher = new MessageQueue((message) => this.post(message));\n this.consumer = new MessageQueue((message) => this.routeToHandler(message));\n this.shouldBuffer = options?.shouldBuffer ?? null;\n\n // By default, consumer processes immediately (no buffering)\n if (options?.bufferIncoming !== true) {\n this.consumer.markReady();\n }\n }\n\n /**\n * Register a handler for a specific message type\n */\n public on(type: string, handler: MessageCallback): void {\n this.handlers.set(type, handler);\n }\n\n /**\n * Remove a handler for a specific message type\n */\n public off(type: string): void {\n this.handlers.delete(type);\n }\n\n /**\n * Send a message to the target window\n * Queued until markReady() is called\n */\n public send(message: Message): void {\n this.publisher.enqueue(message);\n }\n\n /**\n * Enable sending and flush queued outgoing messages\n */\n public markReady(): void {\n this.publisher.markReady();\n }\n\n /**\n * Enable processing of buffered incoming messages\n * Only relevant when constructed with bufferIncoming: true\n */\n public markConsumerReady(): void {\n this.consumer.markReady();\n }\n\n /**\n * Check if an incoming message of a specific type is pending in the consumer buffer\n */\n public consumerHasPending(type: string): boolean {\n return this.consumer.hasPending(type);\n }\n\n /**\n * Start listening for incoming messages\n */\n public start(): void {\n if (this.listener !== null) {\n return;\n }\n\n this.listener = (event: MessageEvent): void => {\n const { data } = event;\n\n if (!isValidMessageData(data)) {\n return;\n }\n\n // Ignore our own messages (echo prevention in direct mode)\n if ((data as unknown as Record<string, unknown>)._source === this.sourceId) {\n return;\n }\n\n const message = data as Message;\n\n // Bypass buffer for messages that shouldn't be buffered\n if (this.shouldBuffer !== null && !this.shouldBuffer(message)) {\n this.routeToHandler(message);\n return;\n }\n\n // Consumer queue: processes immediately if ready, buffers otherwise\n this.consumer.enqueue(message);\n };\n\n window.addEventListener('message', this.listener);\n }\n\n /**\n * Stop listening and clean up all state\n */\n public destroy(): void {\n if (this.listener !== null) {\n window.removeEventListener('message', this.listener);\n this.listener = null;\n }\n\n this.publisher.clear();\n this.consumer.clear();\n this.handlers.clear();\n }\n\n /**\n * Whether the messenger is ready to send\n */\n public get ready(): boolean {\n return this.publisher.ready;\n }\n\n /**\n * Number of messages waiting in the outgoing queue\n */\n public get pendingCount(): number {\n return this.publisher.pendingCount;\n }\n\n private post(message: Message): void {\n const target = this.getTarget();\n\n if (target === null) {\n return;\n }\n\n target.postMessage({ ...message, _source: this.sourceId }, '*');\n }\n\n private routeToHandler(message: Message): void {\n const handler = this.handlers.get(message.type);\n\n if (handler === undefined) {\n return;\n }\n\n const result = handler(message);\n\n if (result instanceof Promise) {\n result.catch((error: unknown) => {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`Virtualdisplay: error handling ${message.type}: ${msg}`);\n });\n }\n }\n}\n","import type { Attribute } from './attribute';\nimport { AttributeRegistry } from './selections';\nimport type { AttributeValue } from './value';\nimport {\n MESSAGE_TYPES,\n type MappingMessage,\n type MessageCallback,\n type MutationDto,\n type MutationMessage,\n type MutationResultMessage,\n} from '@virtualdisplay.io/messaging';\nimport { logger } from '../utils/logger';\n\n/**\n * Public attribute API.\n * Receives mapping from server, provides query/select API,\n * and sends mutations via postMessage.\n *\n * select() can be called before the mapping arrives — mutations are sent\n * immediately and buffered by the messenger's publisher queue until the\n * iframe is ready. onChange() callbacks registered before the mapping are\n * replayed once the registry is created.\n */\nexport class Attributes {\n private registry: AttributeRegistry | undefined;\n\n private readonly pendingCallbacks: Array<{ name: string; callback: (selectedValue: AttributeValue) => void }> = [];\n\n constructor(\n private readonly send: MessageCallback,\n private readonly onMutationFailed?: (attribute: string, value: string) => void,\n ) {}\n\n /**\n * Handle incoming MAPPING message from server\n */\n public handleMappingReceived(message: MappingMessage): void {\n logger.debug('Initializing from server mapping', {\n attributeCount: message.attributes.length,\n });\n\n this.registry = new AttributeRegistry(\n message.attributes,\n (mutations, isInitial) => this.sendMutation(mutations, isInitial),\n this.onMutationFailed,\n );\n\n const defaults = this.registry.defaults();\n if (defaults.length > 0) {\n this.sendMutation(defaults, true);\n }\n\n this.replayPendingCallbacks();\n }\n\n public handleMutationResult(message: MutationResultMessage): void {\n this.registry?.handleResults(message.results);\n }\n\n /**\n * Select a value for an attribute - sends mutation to server.\n * Can be called before the mapping arrives — the mutation is sent immediately\n * and the messenger's publisher queue holds it until the iframe is ready.\n */\n public select(name: string, value: string): void {\n this.sendMutation([{ name, value }], false);\n }\n\n public getValues(name: string): AttributeValue[] {\n return this.registry?.get(name)?.getValues() ?? [];\n }\n\n public getValue(name: string, value: string): AttributeValue | undefined {\n return this.registry?.get(name)?.getValue(value);\n }\n\n /**\n * Register onChange callback for an attribute.\n * Buffers the callback if the mapping hasn't arrived yet.\n */\n public onChange(name: string, callback: (selectedValue: AttributeValue) => void): void {\n if (this.registry === undefined) {\n this.pendingCallbacks.push({ name, callback });\n return;\n }\n\n const attribute = this.registry.get(name);\n if (attribute === undefined) {\n logger.warn(`onChange: attribute \"${name}\" not found — callback will not fire`);\n return;\n }\n\n attribute.onChange(callback);\n }\n\n public getAttribute(name: string): Attribute | undefined {\n return this.registry?.get(name);\n }\n\n public get hasMapping(): boolean {\n return this.registry !== undefined;\n }\n\n private sendMutation(mutations: MutationDto[], isInitial: boolean): void {\n const message: MutationMessage = {\n type: MESSAGE_TYPES.MUTATION,\n mutations,\n isInitial,\n };\n\n void this.send(message);\n }\n\n private replayPendingCallbacks(): void {\n for (const { name, callback } of this.pendingCallbacks) {\n const attribute = this.registry?.get(name);\n if (attribute === undefined) {\n logger.warn(`onChange: attribute \"${name}\" not found — buffered callback will not fire`);\n }\n attribute?.onChange(callback);\n }\n this.pendingCallbacks.length = 0;\n }\n}\n","import { VirtualdisplayError } from '../client/error';\nimport { MESSAGE_TYPES, type CameraCommand, type CameraCommandMessage, type MessageCallback } from '@virtualdisplay.io/messaging';\n\n/**\n * Camera control API for positioning and zoom\n * Uses chaining pattern for building commands\n */\nexport class Camera {\n private commands: CameraCommand[] = [];\n\n constructor(\n private readonly send: MessageCallback,\n ) {}\n\n /**\n * Rotate camera horizontally\n * @param degrees - Absolute rotation in degrees (0 = front view)\n * @throws VirtualdisplayError if value is not a finite number\n */\n public rotate(degrees: number): this {\n this.validateFiniteNumber(degrees, 'rotate');\n this.commands.push({ action: 'rotate', value: degrees });\n return this;\n }\n\n /**\n * Tilt camera vertically\n * @param degrees - Absolute tilt in degrees (0 = top view, 90 = horizontal)\n * @throws VirtualdisplayError if value is not a finite number\n */\n public tilt(degrees: number): this {\n this.validateFiniteNumber(degrees, 'tilt');\n this.commands.push({ action: 'tilt', value: degrees });\n return this;\n }\n\n /**\n * Zoom camera\n * @param percentage - Zoom percentage (100 = normal, >100 = zoom in, <100 = zoom out)\n * @throws VirtualdisplayError if value is not a finite number\n */\n public zoom(percentage: number): this {\n this.validateFiniteNumber(percentage, 'zoom');\n this.commands.push({ action: 'zoom', value: percentage });\n return this;\n }\n\n /**\n * Execute accumulated camera commands\n * @throws VirtualdisplayError if no commands have been accumulated\n */\n public set(): void {\n if (this.commands.length === 0) {\n throw VirtualdisplayError.invalidParameter(\n 'No camera commands to execute. Call rotate(), tilt(), or zoom() before set().',\n );\n }\n\n const message: CameraCommandMessage = {\n type: MESSAGE_TYPES.CAMERA,\n commands: this.commands,\n };\n\n void this.send(message);\n\n this.commands = [];\n }\n\n private validateFiniteNumber(value: number, method: string): void {\n if (!Number.isFinite(value)) {\n throw VirtualdisplayError.invalidParameter(\n `${method}() requires a finite number, got ${String(value)}`,\n );\n }\n }\n\n /**\n * Reset camera to initial position\n * This is not chainable and executes immediately\n */\n public reset(): void {\n const message: CameraCommandMessage = {\n type: MESSAGE_TYPES.CAMERA,\n commands: [{ action: 'reset' }],\n };\n\n void this.send(message);\n }\n}\n","import type { CameraParameter, CameraPositionMessage } from '@virtualdisplay.io/messaging';\n\n/**\n * Camera position state object\n */\nexport interface CameraPositionState {\n rotation: number;\n tilt: number;\n zoom: number;\n}\n\n/**\n * Camera position state\n * Tracks current rotation, tilt, and zoom values from server updates\n */\nexport class CameraPosition {\n private readonly state: CameraPositionState;\n\n private readonly changeCallbacks: Array<(position: CameraPositionState) => void> = [];\n\n constructor() {\n this.state = {\n rotation: 0,\n tilt: 0,\n zoom: 0,\n };\n }\n\n /**\n * Get current rotation in degrees\n */\n public get rotation(): number {\n return this.state.rotation;\n }\n\n /**\n * Get current tilt in degrees\n */\n public get tilt(): number {\n return this.state.tilt;\n }\n\n /**\n * Get current zoom percentage\n */\n public get zoom(): number {\n return this.state.zoom;\n }\n\n /**\n * Subscribe to camera position changes\n */\n public onChange(callback: (position: CameraPositionState) => void): void {\n this.changeCallbacks.push(callback);\n }\n\n /**\n * Handle incoming camera position message from server\n */\n public handlePositionUpdate(message: CameraPositionMessage): void {\n this.update(message.transforms);\n }\n\n /**\n * Update camera position from server transforms\n */\n public update(transforms: CameraParameter[]): void {\n let changed = false;\n\n for (const transform of transforms) {\n switch (transform.type) {\n case 'rotation':\n this.state.rotation = transform.value;\n changed = true;\n break;\n case 'tilt':\n this.state.tilt = transform.value;\n changed = true;\n break;\n case 'zoom':\n this.state.zoom = transform.value;\n changed = true;\n break;\n default:\n // Unknown transform type, ignore\n break;\n }\n }\n\n if (changed) {\n const position: CameraPositionState = {\n rotation: this.rotation,\n tilt: this.tilt,\n zoom: this.zoom,\n };\n\n for (const callback of this.changeCallbacks) {\n callback(position);\n }\n }\n }\n}\n","import type { VirtualdisplayError } from '../client/error';\n\nexport interface PhotoData {\n readonly filename: string;\n readonly data: string;\n}\n\n/**\n * Represents a photo being captured\n * Pure domain object that manages callbacks for when the photo is developed\n */\nexport class Photo {\n private readonly developedCallbacks: Set<(photo: PhotoData) => void> = new Set();\n\n private readonly errorCallbacks: Set<(error: VirtualdisplayError) => void> = new Set();\n\n private developed = false;\n\n private failed = false;\n\n private photoData: PhotoData | null = null;\n\n constructor(\n private readonly filename: string,\n ) {}\n\n /**\n * Register a callback for when the photo is developed\n * If the photo is already developed, the callback is called immediately\n *\n * @param callback Function to call with photo data\n * @returns This Photo instance for chaining\n */\n public onDeveloped(callback: (photo: PhotoData) => void): this {\n if (this.developed && this.photoData !== null) {\n callback(this.photoData);\n return this;\n }\n this.developedCallbacks.add(callback);\n return this;\n }\n\n /**\n * Register a callback for when the photo capture fails\n *\n * @param callback Function to call with error\n * @returns This Photo instance for chaining\n */\n public onError(callback: (error: VirtualdisplayError) => void): this {\n this.errorCallbacks.add(callback);\n return this;\n }\n\n /**\n * Get the filename for this photo\n */\n public getFilename(): string {\n return this.filename;\n }\n\n /**\n * Mark the photo as developed and notify all callbacks\n * This method is called by Snapshot when the photo data arrives from the server\n */\n public develop(photoData: PhotoData): void {\n if (this.developed || this.failed) {\n return;\n }\n\n this.photoData = photoData;\n this.developed = true;\n\n this.developedCallbacks.forEach(callback => {\n callback(this.photoData!);\n });\n\n this.developedCallbacks.clear();\n this.errorCallbacks.clear();\n }\n\n /**\n * Mark the photo as failed and notify error callbacks\n */\n public fail(error: VirtualdisplayError): void {\n if (this.developed || this.failed) {\n return;\n }\n\n this.failed = true;\n\n this.errorCallbacks.forEach(callback => {\n callback(error);\n });\n\n this.developedCallbacks.clear();\n this.errorCallbacks.clear();\n }\n}\n","import { Photo } from './photo';\nimport { VirtualdisplayError, ERROR_CODES } from '../client/error';\nimport { MESSAGE_TYPES, type MessageCallback, type SnapshotResultMessage } from '@virtualdisplay.io/messaging';\nimport { logger } from '../utils/logger';\n\n/**\n * API for taking screenshots of the 3D model\n * Following the camera analogy: take() triggers capture, onDeveloped() gets result\n */\nexport class Snapshot {\n private readonly validExtensions = ['.png'];\n\n private readonly pendingPhotos = new Map<string, Photo>();\n\n constructor(\n private readonly send: MessageCallback,\n ) {}\n\n /**\n * Take a snapshot of the current 3D model view\n *\n * @param filename The filename for the snapshot (must include valid extension)\n * @returns Photo object for registering callbacks\n * @throws VirtualdisplayError if filename is invalid\n */\n public take(filename: string): Photo {\n this.validateFilename(filename);\n\n logger.debug('Taking snapshot', { filename });\n\n const photo = new Photo(filename);\n this.pendingPhotos.set(photo.getFilename(), photo);\n\n void this.send({\n type: MESSAGE_TYPES.SNAPSHOT,\n filename,\n });\n\n return photo;\n }\n\n /**\n * Handle incoming snapshot result from server\n * Matches the message to a pending photo by filename, then develops or fails it\n */\n public handleSnapshotResult(message: SnapshotResultMessage): void {\n const photo = this.pendingPhotos.get(message.filename);\n\n if (photo === undefined) {\n logger.warn('No pending photo found for filename', {\n filename: message.filename,\n pendingFilenames: Array.from(this.pendingPhotos.keys()),\n });\n return;\n }\n\n this.pendingPhotos.delete(message.filename);\n\n if (message.error !== undefined) {\n photo.fail(new VirtualdisplayError(message.error, ERROR_CODES.SNAPSHOT_FAILED));\n return;\n }\n\n if (message.data !== undefined) {\n photo.develop({ filename: message.filename, data: message.data });\n }\n }\n\n /**\n * Validate filename has a valid image extension\n */\n private validateFilename(filename: string): void {\n if (filename === '' || typeof filename !== 'string') {\n throw new VirtualdisplayError(\n 'Filename must be a non-empty string',\n ERROR_CODES.INVALID_PARAMETER,\n );\n }\n\n const hasValidExtension = this.validExtensions.some(\n ext => filename.toLowerCase().endsWith(ext),\n );\n\n if (!hasValidExtension) {\n throw new VirtualdisplayError(\n `Invalid filename: \"${filename}\". Filename must end with one of: ${this.validExtensions.join(', ')}`,\n ERROR_CODES.INVALID_PARAMETER,\n );\n }\n\n // Additional validation for path traversal attempts\n if (filename.includes('..') || filename.includes('/') || filename.includes('\\\\')) {\n throw new VirtualdisplayError(\n 'Filename cannot contain path separators or \"..\"',\n ERROR_CODES.INVALID_PARAMETER,\n );\n }\n }\n}\n","import type { ClientOptions } from '../client/options';\nimport {\n MESSAGE_TYPES,\n type CameraConfig,\n type ConfigMessage,\n type MessageCallback,\n type UIConfig,\n type ViewerConfig,\n} from '@virtualdisplay.io/messaging';\nimport { logger } from '../utils/logger';\n\n/**\n * Service responsible for managing viewer UI configuration\n * Sends CONFIG messages to control AR button, fullscreen button, and loading indicator\n */\nexport class VirtualdisplayViewerService {\n private readonly send: MessageCallback;\n\n constructor(send: MessageCallback) {\n this.send = send;\n }\n\n /**\n * Send initial configuration based on client options\n * Supports both individual properties and ui object\n */\n public sendInitialConfig(options: ClientOptions): void {\n const uiConfig = this.buildUIConfig(options);\n const viewerConfig = this.buildViewerConfig(uiConfig, options.camera);\n\n if (viewerConfig !== undefined) {\n this.sendConfig(viewerConfig);\n }\n }\n\n /**\n * Build UI configuration from client options\n *\n * Supports both legacy properties (options.arEnabled) and the newer ui object\n * for backward compatibility. Legacy properties take precedence when both exist.\n */\n private buildUIConfig(options: ClientOptions): UIConfig | undefined {\n const uiValues = this.extractUIValues(options);\n\n // Only send message when UI options are explicitly configured\n const hasUIConfig = Object.values(uiValues).some(value => value !== undefined);\n if (!hasUIConfig) {\n return undefined;\n }\n\n return {\n ...(uiValues.arEnabled !== undefined && { arEnabled: uiValues.arEnabled }),\n ...(uiValues.fullscreenEnabled !== undefined && {\n fullscreenEnabled: uiValues.fullscreenEnabled,\n }),\n ...(uiValues.loadingIndicatorEnabled !== undefined && {\n loadingIndicatorEnabled: uiValues.loadingIndicatorEnabled,\n }),\n };\n }\n\n /**\n * Extract UI values from options, with legacy properties taking precedence\n */\n private extractUIValues(options: ClientOptions): {\n arEnabled: boolean | undefined;\n fullscreenEnabled: boolean | undefined;\n loadingIndicatorEnabled: boolean | undefined;\n } {\n return {\n arEnabled: options.arEnabled ?? options.ui?.arEnabled,\n fullscreenEnabled: options.fullscreenEnabled ?? options.ui?.fullscreenEnabled,\n loadingIndicatorEnabled:\n options.loadingIndicatorEnabled ?? options.ui?.loadingIndicatorEnabled,\n };\n }\n\n /**\n * Build viewer configuration from UI config and camera config\n */\n private buildViewerConfig(\n uiConfig: UIConfig | undefined,\n cameraConfig: CameraConfig | undefined,\n ): ViewerConfig | undefined {\n if (uiConfig === undefined && cameraConfig === undefined) {\n return undefined;\n }\n\n return {\n ...(uiConfig !== undefined && { ui: uiConfig }),\n ...(cameraConfig !== undefined && { camera: cameraConfig }),\n };\n }\n\n /**\n * Update AR button visibility\n */\n public setArEnabled(enabled: boolean): void {\n logger.debug(`Setting AR button enabled: ${enabled}`);\n this.updateUIConfig({ arEnabled: enabled });\n }\n\n /**\n * Update fullscreen button visibility\n */\n public setFullscreenEnabled(enabled: boolean): void {\n logger.debug(`Setting fullscreen button enabled: ${enabled}`);\n this.updateUIConfig({ fullscreenEnabled: enabled });\n }\n\n /**\n * Update loading indicator visibility\n */\n public setLoadingIndicatorEnabled(enabled: boolean): void {\n logger.debug(`Setting loading indicator enabled: ${enabled}`);\n this.updateUIConfig({ loadingIndicatorEnabled: enabled });\n }\n\n /**\n * Update multiple UI settings at once\n */\n public updateUIConfig(config: Partial<UIConfig>): void {\n logger.debug('Updating UI configuration', config);\n\n if (Object.keys(config).length > 0) {\n this.sendConfig({ ui: config as UIConfig });\n }\n }\n\n /**\n * Hide all UI elements\n */\n public hideAllUI(): void {\n logger.debug('Hiding all UI elements');\n this.sendUIConfig({\n arEnabled: false,\n fullscreenEnabled: false,\n loadingIndicatorEnabled: false,\n });\n }\n\n /**\n * Show all UI elements\n */\n public showAllUI(): void {\n logger.debug('Showing all UI elements');\n this.sendUIConfig({\n arEnabled: true,\n fullscreenEnabled: true,\n loadingIndicatorEnabled: true,\n });\n }\n\n /**\n * Send UI configuration to server\n */\n private sendUIConfig(uiConfig: Partial<UIConfig>): void {\n this.sendConfig({ ui: uiConfig });\n }\n\n /**\n * Send full viewer configuration to server\n */\n private sendConfig(config: ViewerConfig): void {\n const message: ConfigMessage = {\n type: MESSAGE_TYPES.CONFIG,\n config,\n };\n\n logger.debug('Sending config', message);\n\n void this.send(message);\n }\n}\n","/* eslint-disable vd-custom/no-generic-filenames -- Main client class, \"client\" is the product name */\nimport { IframeBuilder } from './iframe-builder';\nimport type { ClientOptions } from './options';\nimport { ReadyStateTracker } from './ready-state-tracker';\nimport type { MutationFailedEvent } from './types';\nimport type { Attribute } from '../attributes/attribute';\nimport { Attributes } from '../attributes/attributes';\nimport type { AttributeValue } from '../attributes/value';\nimport { Camera, CameraPosition } from '../camera';\nimport {\n IframeMessenger,\n MESSAGE_TYPES,\n isReadyMessage,\n isMutationResultMessage,\n isSnapshotResultMessage,\n isCameraPositionMessage,\n isMappingMessage,\n} from '@virtualdisplay.io/messaging';\nimport { Snapshot } from '../snapshot';\nimport { VirtualdisplayViewerService } from '../ui/viewer-config';\nimport { logger, enableDebugMode } from '../utils/logger';\n\n/**\n * Main Virtualdisplay Client\n * Provides the public API for 3D product visualization\n *\n * Acts as the composition root: wires all services together with direct dependency injection\n */\nexport class VirtualdisplayClient {\n private readonly attributes: Attributes;\n\n private readonly readyStateTracker: ReadyStateTracker;\n\n private readonly messenger: IframeMessenger;\n\n private readonly viewerService: VirtualdisplayViewerService;\n\n private readonly cameraApi: Camera;\n\n private readonly cameraPositionApi: CameraPosition;\n\n private readonly snapshotApi: Snapshot;\n\n private readonly options: ClientOptions;\n\n private iframe: HTMLIFrameElement | undefined;\n\n private readyCallbacks: Array<(client: VirtualdisplayClient) => void> = [];\n\n private readonly mutationFailedCallbacks: Array<(event: MutationFailedEvent) => void> = [];\n\n constructor(options: ClientOptions) {\n this.options = options;\n\n this.initializeLogging();\n\n // 1. Create messenger for iframe communication\n this.messenger = new IframeMessenger(() =>\n this.iframe?.contentWindow ?? null,\n );\n\n // 2. Create domain services with send function\n const send = (message: Parameters<IframeMessenger['send']>[0]): void => {\n this.messenger.send(message);\n };\n\n this.attributes = new Attributes(send, (attribute, value) => {\n for (const callback of this.mutationFailedCallbacks) {\n callback({ attribute, value });\n }\n });\n\n this.readyStateTracker = new ReadyStateTracker(() => {\n const callbacks = this.readyCallbacks;\n this.readyCallbacks = [];\n for (const callback of callbacks) {\n callback(this);\n }\n });\n\n this.viewerService = new VirtualdisplayViewerService(send);\n this.cameraApi = new Camera(send);\n this.cameraPositionApi = new CameraPosition();\n this.snapshotApi = new Snapshot(send);\n\n // 3. Register incoming message handlers and start\n this.registerHandlers();\n this.messenger.start();\n\n // 4. Create iframe\n this.createAndConnectIframe();\n }\n\n private registerHandlers(): void {\n // READY: server's message listener is active — safe to flush our queue\n this.messenger.on(MESSAGE_TYPES.READY, (message) => {\n if (isReadyMessage(message)) {\n logger.info('Server ready — flushing publisher queue');\n this.messenger.markReady();\n this.viewerService.sendInitialConfig(this.options);\n }\n });\n\n this.messenger.on(MESSAGE_TYPES.MUTATION, (message) => {\n if (isMutationResultMessage(message)) {\n this.attributes.handleMutationResult(message);\n this.readyStateTracker.handleMutationResult(message);\n }\n });\n\n this.messenger.on(MESSAGE_TYPES.SNAPSHOT, (message) => {\n if (isSnapshotResultMessage(message)) {\n this.snapshotApi.handleSnapshotResult(message);\n }\n });\n\n this.messenger.on(MESSAGE_TYPES.CAMERA, (message) => {\n if (isCameraPositionMessage(message)) {\n this.cameraPositionApi.handlePositionUpdate(message);\n }\n });\n\n this.messenger.on(MESSAGE_TYPES.MAPPING, (message) => {\n if (isMappingMessage(message)) {\n this.attributes.handleMappingReceived(message);\n this.readyStateTracker.handleMappingReceived();\n }\n });\n }\n\n private initializeLogging(): void {\n if (this.options.debug === true) {\n enableDebugMode();\n }\n\n logger.info('Initializing Virtualdisplay client', {\n model: this.options.model,\n debug: this.options.debug,\n language: this.options.language,\n });\n }\n\n private createAndConnectIframe(): void {\n logger.debug('Creating iframe for Virtualdisplay viewer');\n\n const iframeBuilder = new IframeBuilder(this.options);\n this.iframe = iframeBuilder.create();\n logger.debug('Iframe created, waiting for server READY');\n }\n\n /**\n * Select a value for an attribute — sends mutation to server.\n * Can be called immediately; the messenger's publisher queue holds\n * the message until the iframe is ready.\n */\n public select(name: string, value: string): void {\n this.attributes.select(name, value);\n }\n\n /**\n * Get all available values for an attribute\n */\n public getValues(name: string): AttributeValue[] {\n return this.attributes.getValues(name);\n }\n\n /**\n * Get a specific value for an attribute\n */\n public getValue(name: string, value: string): AttributeValue | undefined {\n return this.attributes.getValue(name, value);\n }\n\n /**\n * Register callback that fires when an attribute's selection changes\n */\n public onChange(name: string, callback: (selectedValue: AttributeValue) => void): void {\n this.attributes.onChange(name, callback);\n }\n\n /**\n * Get the Attribute instance for advanced use cases\n */\n public getAttribute(name: string): Attribute | undefined {\n return this.attributes.getAttribute(name);\n }\n\n /**\n * Check if the client has received initial mutations and is ready\n */\n public get isReady(): boolean {\n return this.readyStateTracker.isReady;\n }\n\n /**\n * Register a callback to be called when the client is ready\n * If already ready, the callback is called immediately\n */\n public onReady(callback: (client: VirtualdisplayClient) => void): void {\n if (this.readyStateTracker.isReady) {\n callback(this);\n return;\n }\n\n this.readyCallbacks.push(callback);\n }\n\n /**\n * Register a callback for when a mutation fails on the server\n */\n public onMutationFailed(callback: (event: MutationFailedEvent) => void): void {\n this.mutationFailedCallbacks.push(callback);\n }\n\n /**\n * Get the viewer service for UI configuration\n */\n public get viewer(): VirtualdisplayViewerService {\n return this.viewerService;\n }\n\n /**\n * Get the camera control API\n */\n public get camera(): Camera {\n return this.cameraApi;\n }\n\n /**\n * Get the camera position tracking API\n */\n public get cameraPosition(): CameraPosition {\n return this.cameraPositionApi;\n }\n\n /**\n * Get the snapshot API for taking screenshots\n */\n public get snapshot(): Snapshot {\n return this.snapshotApi;\n }\n\n public destroy(): void {\n logger.info('Destroying Virtualdisplay client');\n\n this.readyStateTracker.clear();\n this.readyCallbacks = [];\n this.messenger.destroy();\n\n // Remove iframe\n if (this.iframe?.parentElement !== null && this.iframe?.parentElement !== undefined) {\n this.iframe.parentElement.removeChild(this.iframe);\n }\n this.iframe = undefined;\n\n logger.debug('Virtualdisplay client destroyed');\n }\n}\n"],"names":["ERROR_CODES","NO_MAPPING","ATTRIBUTE_NOT_FOUND","VALUE_NOT_FOUND","PARENT_NOT_FOUND","INVALID_PARAMETER","SNAPSHOT_FAILED","VirtualdisplayError","Error","code","constructor","message","super","this","name","captureStackTrace","noMapping","attributeNotFound","attributeName","valueNotFound","valueName","parentNotFound","selector","invalidParameter","reason","snapshotFailed","filename","IframeBuilder","defaultServer","options","create","iframe","createIframe","attachToParent","document","createElement","src","buildUrl","Object","assign","style","width","height","border","setAttribute","getParentElement","appendChild","serverUrl","getServerUrl","url","URL","searchParams","set","license","model","debug","language","toString","__vite_import_meta_env__","VITE_VIRTUALDISPLAY_SERVER_HOST","parent","element","querySelector","write","record","isDev","output","time","Date","timestamp","toLocaleTimeString","hour12","level","toUpperCase","padEnd","trace","trace_id","slice","msg","knownFields","attrs","entries","filter","key","includes","map","value","JSON","stringify","join","formatDev","formatProd","console","error","warn","log","Logger","config","traceContext","traceProvider","date","toISOString","service","info","childLogger","extraAttrs","bind","createLogger","process","env","NODE_ENV","normalise","Array","isArray","createClientLogger","base","debugMode","enableDebugMode","logger","ReadyStateTracker","ready","onReady","handleMappingReceived","markReady","handleMutationResult","isInitial","isReady","clear","AttributeValue","attribute","isSelectedValue","isSelected","setSelected","selected","Attribute","defaultValue","values","onSelect","onChangeCallback","createValues","select","has","getValues","from","getValue","get","onChange","callback","handleResult","serverValue","selectedValue","attributeValue","isMatch","valueMap","Map","AttributeRegistry","attributes","definitions","sendMutation","onMutationFailed","definition","buildOptions","defaults","mutations","push","handleResults","results","result","success","MESSAGE_TYPES","READY","MUTATION","STATE","CONFIG","CAMERA","SNAPSHOT","MAPPING","VALID_MESSAGE_TYPES","Set","asObject","data","MessageQueue","processor","pending","enqueue","length","messages","pendingCount","hasPending","type","some","IframeMessenger","getTarget","sourceId","Math","random","publisher","post","consumer","routeToHandler","shouldBuffer","bufferIncoming","handlers","listener","on","handler","off","delete","send","markConsumerReady","consumerHasPending","start","event","obj","target","isValidMessageData","_source","window","addEventListener","destroy","removeEventListener","postMessage","Promise","catch","String","Attributes","registry","pendingCallbacks","attributeCount","replayPendingCallbacks","getAttribute","hasMapping","Camera","commands","rotate","degrees","validateFiniteNumber","action","tilt","zoom","percentage","method","Number","isFinite","reset","CameraPosition","state","changeCallbacks","rotation","handlePositionUpdate","update","transforms","changed","transform","position","Photo","developedCallbacks","errorCallbacks","developed","failed","photoData","onDeveloped","add","onError","getFilename","develop","forEach","fail","Snapshot","validExtensions","pendingPhotos","take","validateFilename","photo","handleSnapshotResult","pendingFilenames","keys","ext","toLowerCase","endsWith","VirtualdisplayViewerService","sendInitialConfig","uiConfig","buildUIConfig","viewerConfig","buildViewerConfig","camera","sendConfig","uiValues","extractUIValues","arEnabled","fullscreenEnabled","loadingIndicatorEnabled","ui","cameraConfig","setArEnabled","enabled","updateUIConfig","setFullscreenEnabled","setLoadingIndicatorEnabled","hideAllUI","sendUIConfig","showAllUI","readyStateTracker","messenger","viewerService","cameraApi","cameraPositionApi","snapshotApi","readyCallbacks","mutationFailedCallbacks","initializeLogging","contentWindow","callbacks","registerHandlers","createAndConnectIframe","isReadyMessage","isMutationResultMessage","isSnapshotResultMessage","isCameraPositionMessage","isMappingMessage","iframeBuilder","viewer","cameraPosition","snapshot","parentElement","removeChild"],"mappings":"2PAGO,MAAMA,EAAc,CACzBC,WAAY,aACZC,oBAAqB,sBACrBC,gBAAiB,kBACjBC,iBAAkB,mBAClBC,kBAAmB,oBACnBC,gBAAiB,mBAQZ,MAAMC,UAA4BC,MACvBC,KAEhB,WAAAC,CAAYC,EAAiBF,GAC3BG,MAAMD,GACNE,KAAKC,KAAO,sBACZD,KAAKJ,KAAOA,OAGoB,IAA5BD,MAAMO,mBACRP,MAAMO,kBAAkBF,KAAMN,EAElC,CAEA,gBAAcS,GACZ,OAAO,IAAIT,EACT,wDACAP,EAAYC,WAEhB,CAEA,wBAAcgB,CAAkBC,GAC9B,OAAO,IAAIX,EACT,cAAcW,0BACdlB,EAAYE,oBAEhB,CAEA,oBAAciB,CAAcD,EAAuBE,GACjD,OAAO,IAAIb,EACT,UAAUa,+BAAuCF,KACjDlB,EAAYG,gBAEhB,CAEA,qBAAckB,CAAeC,GAC3B,OAAO,IAAIf,EACT,6BAA6Be,IAC7BtB,EAAYI,iBAEhB,CAEA,uBAAcmB,CAAiBC,GAC7B,OAAO,IAAIjB,EAAoBiB,EAAQxB,EAAYK,kBACrD,CAEA,qBAAcoB,CAAeC,EAAkBF,GAC7C,OAAO,IAAIjB,EACT,wBAAwBmB,OAAcF,IACtCxB,EAAYM,gBAEhB,aC5DK,MAAMqB,EACMC,cAAgB,mCAEhBC,QAEjB,WAAAnB,CAAYmB,GACVhB,KAAKgB,QAAUA,CACjB,CAKO,MAAAC,GACL,MAAMC,EAASlB,KAAKmB,eAEpB,OADAnB,KAAKoB,eAAeF,GACbA,CACT,CAEQ,YAAAC,GACN,MAAMD,EAASG,SAASC,cAAc,UAatC,OAZAJ,EAAOK,IAAMvB,KAAKwB,WAGlBC,OAAOC,OAAOR,EAAOS,MAAO,CAC1BC,MAAO,OACPC,OAAQ,OACRC,OAAQ,SAIVZ,EAAOa,aAAa,QAAS,mCAEtBb,CACT,CAEQ,cAAAE,CAAeF,GACClB,KAAKgC,mBACbC,YAAYf,EAC5B,CAEQ,QAAAM,GACN,MAAMU,EAAYlC,KAAKmC,eACjBC,EAAM,IAAIC,IAAIH,GAepB,OAZAE,EAAIE,aAAaC,IAAI,UAAWvC,KAAKgB,QAAQwB,SAC7CJ,EAAIE,aAAaC,IAAI,QAASvC,KAAKgB,QAAQyB,QAGhB,IAAvBzC,KAAKgB,QAAQ0B,OACfN,EAAIE,aAAaC,IAAI,QAAS,aAGF,IAA1BvC,KAAKgB,QAAQ2B,UACfP,EAAIE,aAAaC,IAAI,WAAYvC,KAAKgB,QAAQ2B,UAGzCP,EAAIQ,UACb,CAKQ,YAAAT,GAEN,QAA0D,IAAtDU,GAAkBC,gCAItB,OAAO9C,KAAKe,aACd,CAKQ,gBAAAiB,GACN,GAAmC,iBAAxBhC,KAAKgB,QAAQ+B,OACtB,OAAO/C,KAAKgB,QAAQ+B,OAGtB,MAAMC,EAAU3B,SAAS4B,cAAcjD,KAAKgB,QAAQ+B,QACpD,GAAgB,OAAZC,EACF,MAAMtD,EAAoBc,eAAeR,KAAKgB,QAAQ+B,QAGxD,OAAOC,CACT,ECrFK,SAASE,EAAMC,EAAmBC,GACxC,MAAMC,EAASD,ECLT,SAAmBD,GACzB,MAAMG,EAAO,IAAIC,KAAKJ,EAAOK,WAAWC,mBAAmB,QAAS,CAAEC,QAAQ,IACxEC,EAAQR,EAAOQ,MAAMC,cAAcC,OAAO,GAC1CC,EAAQX,EAAOY,UAAUC,MAAM,EAAG,IAAM,WACxCC,EAAMd,EAAOc,IAGbC,EAAc,CAAC,YAAa,QAAS,MAAO,UAAW,WAAY,WACnEC,EAAQ1C,OAAO2C,QAAQjB,GAC3BkB,OAAO,EAAEC,MAAUJ,EAAYK,SAASD,IACxCE,IAAI,EAAEF,EAAKG,KAAW,GAAGH,KAAOI,KAAKC,UAAUF,MAC/CG,KAAK,KAEP,MAAO,GAAGtB,KAAQK,MAAUG,MAAUG,IAAME,EAAQ,IAAMA,EAAQ,IACnE,CDTwBU,CAAU1B,GCc3B,SAAoBA,GAC1B,OAAOuB,KAAKC,UAAUxB,EACvB,CDhB4C2B,CAAW3B,GAIjC,UAAjBA,EAAOQ,MACVoB,QAAQC,MAAM3B,GACa,SAAjBF,EAAOQ,MACjBoB,QAAQE,KAAK5B,GAEb0B,QAAQG,IAAI7B,EAEd,CEdO,MAAM8B,EACZ,WAAAtF,CAAqBuF,GAAApF,KAAAoF,OAAAA,CAAuB,CAK5C,GAAAF,CAAIvB,EAAiBM,EAAaE,EAAiC,CAAA,GAClE,MAAMkB,EAAerF,KAAKoF,OAAOE,mBAAqB,CAAA,ECiBjD,IAAeC,EDNpBrC,EAT0B,CACzBM,WCcmB+B,MAbThC,KAcJgC,EAAKC,eDdX7B,QACAM,MACAwB,QAASzF,KAAKoF,OAAOK,WAClBJ,KACAlB,GAGUnE,KAAKoF,OAAOhC,MAC3B,CAKA,KAAAV,CAAMuB,EAAaE,GACdnE,KAAKoF,OAAOhC,OACfpD,KAAKkF,IAAI,QAASjB,EAAKE,EAEzB,CAKA,IAAAuB,CAAKzB,EAAaE,GACjBnE,KAAKkF,IAAI,OAAQjB,EAAKE,EACvB,CAKA,IAAAc,CAAKhB,EAAaE,GACjBnE,KAAKkF,IAAI,OAAQjB,EAAKE,EACvB,CAKA,KAAAa,CAAMf,EAAaE,GAClBnE,KAAKkF,IAAI,QAASjB,EAAKE,EACxB,CAKA,KAAKA,GACJ,MAAMwB,EAAc,IAAIR,EAAOnF,KAAKoF,QAUpC,OARAO,EAAYT,MACXvB,EACAM,EACA2B,EAAsC,MAEtC5F,KAAKkF,IAAIvB,EAAOM,EAAK,IAAKE,KAAUyB,GACrC,GAAGC,KAAK7F,MAED2F,CACR,EASM,SAASG,EACfL,EACAzE,GAEA,MAAMoC,OACc,IAAnBpC,GAASoC,MACNpC,EAAQoC,MACW,oBAAZ2C,SACmB,eAAzBA,QAAQC,IAAIC,SAGjB,OAAO,IAAId,EAAO,CAAEM,UAASrC,QAAOkC,cAAetE,GAASsE,eAC7D,CE1FA,SAASY,EAAU/B,GAClB,YAAIA,EACJ,MAAqB,iBAAVA,GAAgC,OAAVA,GAAmBgC,MAAMC,QAAQjC,IAAYA,aAAiBxE,MAG3FwE,aAAiBxE,MACb,CAAEqF,MAAOb,EAAMrE,SAEhB,CAAE2E,MAAON,GALRA,CAMT,CAEA,SAASkC,EAAmBC,GAC3B,MAAO,CACN5D,MAAO,CAACuB,EAAaE,IAAkBmC,EAAK5D,MAAMuB,EAAKiC,EAAU/B,IACjEuB,KAAM,CAACzB,EAAaE,IAAkBmC,EAAKZ,KAAKzB,EAAKiC,EAAU/B,IAC/Dc,KAAM,CAAChB,EAAaE,IAAkBmC,EAAKrB,KAAKhB,EAAKiC,EAAU/B,IAC/Da,MAAO,CAACf,EAAaE,IAAkBmC,EAAKtB,MAAMf,EAAKiC,EAAU/B,IAEnE,CAMA,IAAIoC,GAAY,EAIT,SAASC,IACfD,GAAY,EACZE,EAAAA,OAASJ,EAAmBP,EAAa,SAAU,CAAE1C,OAAO,IAC7D,CALWqD,EAAAA,OAASJ,EAAmBP,EAAa,SAAU,CAAE1C,OAAO,KCpBhE,MAAMsD,EACHC,OAAQ,EAECC,QAEjB,WAAA/G,CAAY+G,GACV5G,KAAK4G,QAAUA,CACjB,CAKO,qBAAAC,GACL7G,KAAK8G,UAAU,+BACjB,CAMO,oBAAAC,CAAqBjH,GACtBA,EAAQkH,WACVhH,KAAK8G,UAAU,8BAEnB,CAEA,WAAWG,GACT,OAAOjH,KAAK2G,KACd,CAEO,KAAAO,GACLlH,KAAK2G,OAAQ,CACf,CAEQ,SAAAG,CAAUnG,GACZX,KAAK2G,QAIT3G,KAAK2G,OAAQ,EACbF,EAAAA,OAAOf,KAAK,GAAG/E,2BACfX,KAAK4G,YACP,EChDK,MAAMO,EAGX,WAAAtH,CACkB4E,EACA2C,GADApH,KAAAyE,MAAAA,EACAzE,KAAAoH,UAAAA,CACf,CALKC,iBAAkB,EAO1B,cAAWC,GACT,OAAOtH,KAAKqH,eACd,CAEO,WAAAE,CAAYC,GACjBxH,KAAKqH,gBAAkBG,CACzB,ECJK,MAAMC,EACKxH,KAEAyH,aAECC,OAEAC,SAETC,iBAER,WAAAhI,CAAYmB,GACVhB,KAAKC,KAAOe,EAAQf,KACpBD,KAAK0H,aAAe1G,EAAQ0G,aAC5B1H,KAAK4H,SAAW5G,EAAQ4G,SACxB5H,KAAK2H,OAAS3H,KAAK8H,aAAa9G,EAAQ2G,OAC1C,CAMO,MAAAI,CAAOtD,GACZ,IAAKzE,KAAK2H,OAAOK,IAAIvD,GACnB,MAAM/E,EAAoBY,cAAcN,KAAKC,KAAMwE,GAGrDzE,KAAK4H,SAAS5H,KAAKC,KAAMwE,EAC3B,CAEO,SAAAwD,GACL,OAAO9B,MAAM+B,KAAKlI,KAAK2H,OAAOA,SAChC,CAEO,QAAAQ,CAAS1D,GACd,OAAOzE,KAAK2H,OAAOS,IAAI3D,EACzB,CAMO,QAAA4D,CAASC,GACdtI,KAAK6H,iBAAmBS,CAC1B,CAMO,YAAAC,CAAaC,GAClB,IAAIC,EAEJ,IAAA,MAAWC,KAAkB1I,KAAK2H,OAAOA,SAAU,CACjD,MAAMgB,EAAUD,EAAejE,QAAU+D,EACzCE,EAAenB,YAAYoB,GACvBA,IACFF,EAAgBC,EAEpB,MAEsB,IAAlBD,QAAyD,IAA1BzI,KAAK6H,kBACtC7H,KAAK6H,iBAAiBY,EAE1B,CAEQ,YAAAX,CAAaH,GACnB,MAAMiB,MAAeC,IAErB,IAAA,MAAWpE,KAASkD,EAAQ,CAC1B,MAAMe,EAAiB,IAAIvB,EAAe1C,EAAOzE,KAAKC,MAClDwE,IAAUzE,KAAK0H,cACjBgB,EAAenB,aAAY,GAE7BqB,EAASrG,IAAIkC,EAAOiE,EACtB,CAEA,OAAOE,CACT,ECrFK,MAAME,EACMC,eAAiBF,IAElC,WAAAhJ,CACEmJ,EACAC,EACAC,GAEA,IAAA,MAAWC,KAAcH,EAAa,CACpC,MAAMhI,EAAUhB,KAAKoJ,aAAaD,EAAYF,GAC9CjJ,KAAK+I,WAAWxG,IAAI4G,EAAWlJ,KAAM,IAAIwH,EAAUzG,GACrD,CACAhB,KAAKkJ,iBAAmBA,CAC1B,CAEiBA,iBAEV,GAAAd,CAAInI,GACT,OAAOD,KAAK+I,WAAWX,IAAInI,EAC7B,CAEO,QAAAoJ,GACL,MAAMC,EAA2B,GAEjC,IAAA,MAAWlC,KAAapH,KAAK+I,WAAWpB,cACP,IAA3BP,EAAUM,cACZ4B,EAAUC,KAAK,CACbtJ,KAAMmH,EAAUnH,KAChBwE,MAAO2C,EAAUM,eAKvB,OAAO4B,CACT,CAEO,aAAAE,CAAcC,GACnB,IAAA,MAAWC,KAAUD,EACdC,EAAOC,QAKZ3J,KAAKoI,IAAIsB,EAAOzJ,OAAOsI,aAAamB,EAAOjF,OAJzCzE,KAAKkJ,mBAAmBQ,EAAOzJ,KAAMyJ,EAAOjF,MAMlD,CAEQ,YAAA2E,CACND,EACAF,GAEA,MAAM3C,EAAO,CACXrG,KAAMkJ,EAAWlJ,KACjB0H,OAAQwB,EAAWxB,OACnBC,SAAU,CAAC3H,EAAcwE,KACvBwE,EAAa,CAAC,CAAEhJ,OAAMwE,WAAU,KAIpC,YAAgC,IAA5B0E,EAAWzB,aACN,IAAKpB,EAAMoB,aAAcyB,EAAWzB,cAGtCpB,CACT,EC7DK,MAAMsD,EAAgB,CAC3BC,MAAO,QACPC,SAAU,WACVC,MAAO,QACPC,OAAQ,SACRC,OAAQ,SACRC,SAAU,WACVC,QAAS,WCFLC,EAAsB,IAAIC,IAAY5I,OAAOkG,OAAOiC,IAoE1D,SAASU,EAASC,GAChB,MAAoB,iBAATA,GAA8B,OAATA,EACvB,KAEFA,CACT,CChFO,MAAMC,EAKX,WAAA3K,CAA6B4K,GAAAzK,KAAAyK,UAAAA,CAAwC,CAJpDC,QAAqB,GAE9BzD,SAAU,EAOX,OAAA0D,CAAQ7K,GACTE,KAAKiH,QACPjH,KAAKyK,UAAU3K,GAIjBE,KAAK0K,QAAQnB,KAAKzJ,EACpB,CAKO,SAAAgH,GACL,GAAI9G,KAAKiH,QACP,OAKF,GAFAjH,KAAKiH,SAAU,EAEa,IAAxBjH,KAAK0K,QAAQE,OACf,OAGF,MAAMC,EAAW,IAAI7K,KAAK0K,SAC1B1K,KAAK0K,QAAQE,OAAS,EAEtB,IAAA,MAAW9K,KAAW+K,EACpB7K,KAAKyK,UAAU3K,EAEnB,CAEA,SAAW6G,GACT,OAAO3G,KAAKiH,OACd,CAEA,gBAAW6D,GACT,OAAO9K,KAAK0K,QAAQE,MACtB,CAKO,UAAAG,CAAWC,GAChB,OAAOhL,KAAK0K,QAAQO,KAAKhH,GAAOA,EAAI+G,OAASA,EAC/C,CAKO,KAAA9D,GACLlH,KAAK0K,QAAQE,OAAS,CACxB,ECRK,MAAMM,EAaX,WAAArL,CACmBsL,EACjBnK,GADiBhB,KAAAmL,UAAAA,EAGjBnL,KAAKoL,SAAW,MAAMC,KAAKC,SAAS1I,SAAS,IAAIoB,MAAM,EAAG,KAC1DhE,KAAKuL,UAAY,IAAIf,EAAc1K,GAAYE,KAAKwL,KAAK1L,IACzDE,KAAKyL,SAAW,IAAIjB,EAAc1K,GAAYE,KAAK0L,eAAe5L,IAClEE,KAAK2L,aAAe3K,GAAS2K,cAAgB,MAGb,IAA5B3K,GAAS4K,gBACX5L,KAAKyL,SAAS3E,WAElB,CAzBiB+E,aAAehD,IAEf0C,UAEAE,SAEAE,aAETG,SAAmD,KAE1CV,SAoBV,EAAAW,CAAGf,EAAcgB,GACtBhM,KAAK6L,SAAStJ,IAAIyI,EAAMgB,EAC1B,CAKO,GAAAC,CAAIjB,GACThL,KAAK6L,SAASK,OAAOlB,EACvB,CAMO,IAAAmB,CAAKrM,GACVE,KAAKuL,UAAUZ,QAAQ7K,EACzB,CAKO,SAAAgH,GACL9G,KAAKuL,UAAUzE,WACjB,CAMO,iBAAAsF,GACLpM,KAAKyL,SAAS3E,WAChB,CAKO,kBAAAuF,CAAmBrB,GACxB,OAAOhL,KAAKyL,SAASV,WAAWC,EAClC,CAKO,KAAAsB,GACiB,OAAlBtM,KAAK8L,WAIT9L,KAAK8L,SAAYS,IACf,MAAMhC,KAAEA,GAASgC,EAEjB,IF3HC,SAA4BhC,GACjC,GAAoB,iBAATA,GAA8B,OAATA,EAC9B,OAAO,EAGT,MAAMiC,EAAMjC,EAGZ,MAA0B,iBAAfiC,EAAIC,QAIY,iBAAbD,EAAIxB,MAAqBZ,EAAoBpC,IAAIwE,EAAIxB,KACrE,CE8GW0B,CAAmBnC,GACtB,OAIF,GAAKA,EAA4CoC,UAAY3M,KAAKoL,SAChE,OAGF,MAAMtL,EAAUyK,EAGU,OAAtBvK,KAAK2L,cAA0B3L,KAAK2L,aAAa7L,GAMrDE,KAAKyL,SAASd,QAAQ7K,GALpBE,KAAK0L,eAAe5L,IAQxB8M,OAAOC,iBAAiB,UAAW7M,KAAK8L,UAC1C,CAKO,OAAAgB,GACiB,OAAlB9M,KAAK8L,WACPc,OAAOG,oBAAoB,UAAW/M,KAAK8L,UAC3C9L,KAAK8L,SAAW,MAGlB9L,KAAKuL,UAAUrE,QACflH,KAAKyL,SAASvE,QACdlH,KAAK6L,SAAS3E,OAChB,CAKA,SAAWP,GACT,OAAO3G,KAAKuL,UAAU5E,KACxB,CAKA,gBAAWmE,GACT,OAAO9K,KAAKuL,UAAUT,YACxB,CAEQ,IAAAU,CAAK1L,GACX,MAAM2M,EAASzM,KAAKmL,YAEL,OAAXsB,GAIJA,EAAOO,YAAY,IAAKlN,EAAS6M,QAAS3M,KAAKoL,UAAY,IAC7D,CAEQ,cAAAM,CAAe5L,GACrB,MAAMkM,EAAUhM,KAAK6L,SAASzD,IAAItI,EAAQkL,MAE1C,QAAgB,IAAZgB,EACF,OAGF,MAAMtC,EAASsC,EAAQlM,GAEnB4J,aAAkBuD,SACpBvD,EAAOwD,MAAOlI,IACZ,MAAMf,EAAMe,aAAiBrF,MAAQqF,EAAMlF,QAAUqN,OAAOnI,GAC5DD,QAAQC,MAAM,kCAAkClF,EAAQkL,SAAS/G,MAGvE,ECtMK,MAAMmJ,EAKX,WAAAvN,CACmBsM,EACAjD,GADAlJ,KAAAmM,KAAAA,EACAnM,KAAAkJ,iBAAAA,CAChB,CAPKmE,SAESC,iBAA+F,GAUzG,qBAAAzG,CAAsB/G,GAC3B2G,EAAAA,OAAO/D,MAAM,mCAAoC,CAC/C6K,eAAgBzN,EAAQiJ,WAAW6B,SAGrC5K,KAAKqN,SAAW,IAAIvE,EAClBhJ,EAAQiJ,WACR,CAACO,EAAWtC,IAAchH,KAAKiJ,aAAaK,EAAWtC,GACvDhH,KAAKkJ,kBAGP,MAAMG,EAAWrJ,KAAKqN,SAAShE,WAC3BA,EAASuB,OAAS,GACpB5K,KAAKiJ,aAAaI,GAAU,GAG9BrJ,KAAKwN,wBACP,CAEO,oBAAAzG,CAAqBjH,GAC1BE,KAAKqN,UAAU7D,cAAc1J,EAAQ2J,QACvC,CAOO,MAAA1B,CAAO9H,EAAcwE,GAC1BzE,KAAKiJ,aAAa,CAAC,CAAEhJ,OAAMwE,WAAU,EACvC,CAEO,SAAAwD,CAAUhI,GACf,OAAOD,KAAKqN,UAAUjF,IAAInI,IAAOgI,aAAe,EAClD,CAEO,QAAAE,CAASlI,EAAcwE,GAC5B,OAAOzE,KAAKqN,UAAUjF,IAAInI,IAAOkI,SAAS1D,EAC5C,CAMO,QAAA4D,CAASpI,EAAcqI,GAC5B,QAAsB,IAAlBtI,KAAKqN,SAEP,YADArN,KAAKsN,iBAAiB/D,KAAK,CAAEtJ,OAAMqI,aAIrC,MAAMlB,EAAYpH,KAAKqN,SAASjF,IAAInI,QAClB,IAAdmH,EAKJA,EAAUiB,SAASC,GAJjB7B,EAAAA,OAAOxB,KAAK,wBAAwBhF,wCAKxC,CAEO,YAAAwN,CAAaxN,GAClB,OAAOD,KAAKqN,UAAUjF,IAAInI,EAC5B,CAEA,cAAWyN,GACT,YAAyB,IAAlB1N,KAAKqN,QACd,CAEQ,YAAApE,CAAaK,EAA0BtC,GAC7C,MAAMlH,EAA2B,CAC/BkL,KAAMpB,EAAcE,SACpBR,YACAtC,aAGGhH,KAAKmM,KAAKrM,EACjB,CAEQ,sBAAA0N,GACN,IAAA,MAAWvN,KAAEA,EAAAqI,SAAMA,KAActI,KAAKsN,iBAAkB,CACtD,MAAMlG,EAAYpH,KAAKqN,UAAUjF,IAAInI,QACnB,IAAdmH,GACFX,EAAAA,OAAOxB,KAAK,wBAAwBhF,kDAEtCmH,GAAWiB,SAASC,EACtB,CACAtI,KAAKsN,iBAAiB1C,OAAS,CACjC,ECnHK,MAAM+C,EAGX,WAAA9N,CACmBsM,GAAAnM,KAAAmM,KAAAA,CAChB,CAJKyB,SAA4B,GAW7B,MAAAC,CAAOC,GAGZ,OAFA9N,KAAK+N,qBAAqBD,EAAS,UACnC9N,KAAK4N,SAASrE,KAAK,CAAEyE,OAAQ,SAAUvJ,MAAOqJ,IACvC9N,IACT,CAOO,IAAAiO,CAAKH,GAGV,OAFA9N,KAAK+N,qBAAqBD,EAAS,QACnC9N,KAAK4N,SAASrE,KAAK,CAAEyE,OAAQ,OAAQvJ,MAAOqJ,IACrC9N,IACT,CAOO,IAAAkO,CAAKC,GAGV,OAFAnO,KAAK+N,qBAAqBI,EAAY,QACtCnO,KAAK4N,SAASrE,KAAK,CAAEyE,OAAQ,OAAQvJ,MAAO0J,IACrCnO,IACT,CAMO,GAAAuC,GACL,GAA6B,IAAzBvC,KAAK4N,SAAShD,OAChB,MAAMlL,EAAoBgB,iBACxB,iFAIJ,MAAMZ,EAAgC,CACpCkL,KAAMpB,EAAcK,OACpB2D,SAAU5N,KAAK4N,UAGZ5N,KAAKmM,KAAKrM,GAEfE,KAAK4N,SAAW,EAClB,CAEQ,oBAAAG,CAAqBtJ,EAAe2J,GAC1C,IAAKC,OAAOC,SAAS7J,GACnB,MAAM/E,EAAoBgB,iBACxB,GAAG0N,qCAA0CjB,OAAO1I,KAG1D,CAMO,KAAA8J,GACL,MAAMzO,EAAgC,CACpCkL,KAAMpB,EAAcK,OACpB2D,SAAU,CAAC,CAAEI,OAAQ,WAGlBhO,KAAKmM,KAAKrM,EACjB,ECxEK,MAAM0O,EACMC,MAEAC,gBAAkE,GAEnF,WAAA7O,GACEG,KAAKyO,MAAQ,CACXE,SAAU,EACVV,KAAM,EACNC,KAAM,EAEV,CAKA,YAAWS,GACT,OAAO3O,KAAKyO,MAAME,QACpB,CAKA,QAAWV,GACT,OAAOjO,KAAKyO,MAAMR,IACpB,CAKA,QAAWC,GACT,OAAOlO,KAAKyO,MAAMP,IACpB,CAKO,QAAA7F,CAASC,GACdtI,KAAK0O,gBAAgBnF,KAAKjB,EAC5B,CAKO,oBAAAsG,CAAqB9O,GAC1BE,KAAK6O,OAAO/O,EAAQgP,WACtB,CAKO,MAAAD,CAAOC,GACZ,IAAIC,GAAU,EAEd,IAAA,MAAWC,KAAaF,EACtB,OAAQE,EAAUhE,MAChB,IAAK,WACHhL,KAAKyO,MAAME,SAAWK,EAAUvK,MAChCsK,GAAU,EACV,MACF,IAAK,OACH/O,KAAKyO,MAAMR,KAAOe,EAAUvK,MAC5BsK,GAAU,EACV,MACF,IAAK,OACH/O,KAAKyO,MAAMP,KAAOc,EAAUvK,MAC5BsK,GAAU,EAQhB,GAAIA,EAAS,CACX,MAAME,EAAgC,CACpCN,SAAU3O,KAAK2O,SACfV,KAAMjO,KAAKiO,KACXC,KAAMlO,KAAKkO,MAGb,IAAA,MAAW5F,KAAYtI,KAAK0O,gBAC1BpG,EAAS2G,EAEb,CACF,ECzFK,MAAMC,EAWX,WAAArP,CACmBgB,GAAAb,KAAAa,SAAAA,CAChB,CAZcsO,uBAA0D9E,IAE1D+E,mBAAgE/E,IAEzEgF,WAAY,EAEZC,QAAS,EAETC,UAA8B,KAa/B,WAAAC,CAAYlH,GACjB,OAAItI,KAAKqP,WAAgC,OAAnBrP,KAAKuP,WACzBjH,EAAStI,KAAKuP,WACPvP,OAETA,KAAKmP,mBAAmBM,IAAInH,GACrBtI,KACT,CAQO,OAAA0P,CAAQpH,GAEb,OADAtI,KAAKoP,eAAeK,IAAInH,GACjBtI,IACT,CAKO,WAAA2P,GACL,OAAO3P,KAAKa,QACd,CAMO,OAAA+O,CAAQL,GACTvP,KAAKqP,WAAarP,KAAKsP,SAI3BtP,KAAKuP,UAAYA,EACjBvP,KAAKqP,WAAY,EAEjBrP,KAAKmP,mBAAmBU,QAAQvH,IAC9BA,EAAStI,KAAKuP,aAGhBvP,KAAKmP,mBAAmBjI,QACxBlH,KAAKoP,eAAelI,QACtB,CAKO,IAAA4I,CAAK9K,GACNhF,KAAKqP,WAAarP,KAAKsP,SAI3BtP,KAAKsP,QAAS,EAEdtP,KAAKoP,eAAeS,QAAQvH,IAC1BA,EAAStD,KAGXhF,KAAKmP,mBAAmBjI,QACxBlH,KAAKoP,eAAelI,QACtB,ECvFK,MAAM6I,EAKX,WAAAlQ,CACmBsM,GAAAnM,KAAAmM,KAAAA,CAChB,CANc6D,gBAAkB,CAAC,QAEnBC,kBAAoBpH,IAa9B,IAAAqH,CAAKrP,GACVb,KAAKmQ,iBAAiBtP,GAEtB4F,EAAAA,OAAO/D,MAAM,kBAAmB,CAAE7B,aAElC,MAAMuP,EAAQ,IAAIlB,EAAMrO,GAQxB,OAPAb,KAAKiQ,cAAc1N,IAAI6N,EAAMT,cAAeS,GAEvCpQ,KAAKmM,KAAK,CACbnB,KAAMpB,EAAcM,SACpBrJ,aAGKuP,CACT,CAMO,oBAAAC,CAAqBvQ,GAC1B,MAAMsQ,EAAQpQ,KAAKiQ,cAAc7H,IAAItI,EAAQe,eAE/B,IAAVuP,GAQJpQ,KAAKiQ,cAAc/D,OAAOpM,EAAQe,eAEZ,IAAlBf,EAAQkF,WAKS,IAAjBlF,EAAQyK,MACV6F,EAAMR,QAAQ,CAAE/O,SAAUf,EAAQe,SAAU0J,KAAMzK,EAAQyK,OAL1D6F,EAAMN,KAAK,IAAIpQ,EAAoBI,EAAQkF,MAAO7F,EAAYM,mBAV9DgH,EAAAA,OAAOxB,KAAK,sCAAuC,CACjDpE,SAAUf,EAAQe,SAClByP,iBAAkBnK,MAAM+B,KAAKlI,KAAKiQ,cAAcM,SAetD,CAKQ,gBAAAJ,CAAiBtP,GACvB,GAAiB,KAAbA,GAAuC,iBAAbA,EAC5B,MAAM,IAAInB,EACR,sCACAP,EAAYK,mBAQhB,IAJ0BQ,KAAKgQ,gBAAgB/E,KAC7CuF,GAAO3P,EAAS4P,cAAcC,SAASF,IAIvC,MAAM,IAAI9Q,EACR,sBAAsBmB,sCAA6Cb,KAAKgQ,gBAAgBpL,KAAK,QAC7FzF,EAAYK,mBAKhB,GAAIqB,EAAS0D,SAAS,OAAS1D,EAAS0D,SAAS,MAAQ1D,EAAS0D,SAAS,MACzE,MAAM,IAAI7E,EACR,kDACAP,EAAYK,kBAGlB,EClFK,MAAMmR,EACMxE,KAEjB,WAAAtM,CAAYsM,GACVnM,KAAKmM,KAAOA,CACd,CAMO,iBAAAyE,CAAkB5P,GACvB,MAAM6P,EAAW7Q,KAAK8Q,cAAc9P,GAC9B+P,EAAe/Q,KAAKgR,kBAAkBH,EAAU7P,EAAQiQ,aAEzC,IAAjBF,GACF/Q,KAAKkR,WAAWH,EAEpB,CAQQ,aAAAD,CAAc9P,GACpB,MAAMmQ,EAAWnR,KAAKoR,gBAAgBpQ,GAItC,GADoBS,OAAOkG,OAAOwJ,GAAUlG,KAAKxG,QAAmB,IAAVA,GAK1D,MAAO,aACD0M,EAASE,WAA2B,CAAEA,UAAWF,EAASE,mBAC3B,IAA/BF,EAASG,mBAAmC,CAC9CA,kBAAmBH,EAASG,2BAEW,IAArCH,EAASI,yBAAyC,CACpDA,wBAAyBJ,EAASI,yBAGxC,CAKQ,eAAAH,CAAgBpQ,GAKtB,MAAO,CACLqQ,UAAWrQ,EAAQqQ,WAAarQ,EAAQwQ,IAAIH,UAC5CC,kBAAmBtQ,EAAQsQ,mBAAqBtQ,EAAQwQ,IAAIF,kBAC5DC,wBACEvQ,EAAQuQ,yBAA2BvQ,EAAQwQ,IAAID,wBAErD,CAKQ,iBAAAP,CACNH,EACAY,GAEA,QAAiB,IAAbZ,QAA2C,IAAjBY,EAI9B,MAAO,SACY,IAAbZ,GAA0B,CAAEW,GAAIX,WACf,IAAjBY,GAA8B,CAAER,OAAQQ,GAEhD,CAKO,YAAAC,CAAaC,GAClBlL,EAAAA,OAAO/D,MAAM,8BAA8BiP,KAC3C3R,KAAK4R,eAAe,CAAEP,UAAWM,GACnC,CAKO,oBAAAE,CAAqBF,GAC1BlL,EAAAA,OAAO/D,MAAM,sCAAsCiP,KACnD3R,KAAK4R,eAAe,CAAEN,kBAAmBK,GAC3C,CAKO,0BAAAG,CAA2BH,GAChClL,EAAAA,OAAO/D,MAAM,sCAAsCiP,KACnD3R,KAAK4R,eAAe,CAAEL,wBAAyBI,GACjD,CAKO,cAAAC,CAAexM,GACpBqB,SAAO/D,MAAM,4BAA6B0C,GAEtC3D,OAAO8O,KAAKnL,GAAQwF,OAAS,GAC/B5K,KAAKkR,WAAW,CAAEM,GAAIpM,GAE1B,CAKO,SAAA2M,GACLtL,EAAAA,OAAO/D,MAAM,0BACb1C,KAAKgS,aAAa,CAChBX,WAAW,EACXC,mBAAmB,EACnBC,yBAAyB,GAE7B,CAKO,SAAAU,GACLxL,EAAAA,OAAO/D,MAAM,2BACb1C,KAAKgS,aAAa,CAChBX,WAAW,EACXC,mBAAmB,EACnBC,yBAAyB,GAE7B,CAKQ,YAAAS,CAAanB,GACnB7Q,KAAKkR,WAAW,CAAEM,GAAIX,GACxB,CAKQ,UAAAK,CAAW9L,GACjB,MAAMtF,EAAyB,CAC7BkL,KAAMpB,EAAcI,OACpB5E,UAGFqB,SAAO/D,MAAM,iBAAkB5C,GAE1BE,KAAKmM,KAAKrM,EACjB,4GChJK,MACYiJ,WAEAmJ,kBAEAC,UAEAC,cAEAC,UAEAC,kBAEAC,YAEAvR,QAETE,OAEAsR,eAAgE,GAEvDC,wBAAuE,GAExF,WAAA5S,CAAYmB,GACVhB,KAAKgB,QAAUA,EAEfhB,KAAK0S,oBAGL1S,KAAKmS,UAAY,IAAIjH,EAAgB,IACnClL,KAAKkB,QAAQyR,eAAiB,MAIhC,MAAMxG,EAAQrM,IACZE,KAAKmS,UAAUhG,KAAKrM,IAGtBE,KAAK+I,WAAa,IAAIqE,EAAWjB,EAAM,CAAC/E,EAAW3C,KACjD,IAAA,MAAW6D,KAAYtI,KAAKyS,wBAC1BnK,EAAS,CAAElB,YAAW3C,YAI1BzE,KAAKkS,kBAAoB,IAAIxL,EAAkB,KAC7C,MAAMkM,EAAY5S,KAAKwS,eACvBxS,KAAKwS,eAAiB,GACtB,IAAA,MAAWlK,KAAYsK,EACrBtK,EAAStI,QAIbA,KAAKoS,cAAgB,IAAIzB,EAA4BxE,GACrDnM,KAAKqS,UAAY,IAAI1E,EAAOxB,GAC5BnM,KAAKsS,kBAAoB,IAAI9D,EAC7BxO,KAAKuS,YAAc,IAAIxC,EAAS5D,GAGhCnM,KAAK6S,mBACL7S,KAAKmS,UAAU7F,QAGftM,KAAK8S,wBACP,CAEQ,gBAAAD,GAEN7S,KAAKmS,UAAUpG,GAAGnC,EAAcC,MAAQ/J,KTzDrC,SAAwByK,GAC7B,MAAMiC,EAAMlC,EAASC,GACrB,OAAe,OAARiC,GAAgBA,EAAIxB,OAASpB,EAAcC,KACpD,ESuDUkJ,CAAejT,KACjB2G,EAAAA,OAAOf,KAAK,2CACZ1F,KAAKmS,UAAUrL,YACf9G,KAAKoS,cAAcxB,kBAAkB5Q,KAAKgB,YAI9ChB,KAAKmS,UAAUpG,GAAGnC,EAAcE,SAAWhK,KTvDxC,SAAiCyK,GACtC,MAAMiC,EAAMlC,EAASC,GACrB,OAAe,OAARiC,GAAgBA,EAAIxB,OAASpB,EAAcE,UAAY3D,MAAMC,QAAQoG,EAAI/C,QAClF,ESqDUuJ,CAAwBlT,KAC1BE,KAAK+I,WAAWhC,qBAAqBjH,GACrCE,KAAKkS,kBAAkBnL,qBAAqBjH,MAIhDE,KAAKmS,UAAUpG,GAAGnC,EAAcM,SAAWpK,KT/CxC,SAAiCyK,GACtC,MAAMiC,EAAMlC,EAASC,GACrB,OAAe,OAARiC,GAAgBA,EAAIxB,OAASpB,EAAcM,WAAa,SAAUsC,GAAO,UAAWA,EAC7F,ES6CUyG,CAAwBnT,IAC1BE,KAAKuS,YAAYlC,qBAAqBvQ,KAI1CE,KAAKmS,UAAUpG,GAAGnC,EAAcK,OAASnK,KT3CtC,SAAiCyK,GACtC,MAAMiC,EAAMlC,EAASC,GACrB,OAAe,OAARiC,GAAgBA,EAAIxB,OAASpB,EAAcK,QAAU9D,MAAMC,QAAQoG,EAAIsC,WAChF,ESyCUoE,CAAwBpT,IAC1BE,KAAKsS,kBAAkB1D,qBAAqB9O,KAIhDE,KAAKmS,UAAUpG,GAAGnC,EAAcO,QAAUrK,KT5CvC,SAA0ByK,GAC/B,MAAMiC,EAAMlC,EAASC,GACrB,OAAe,OAARiC,GAAgBA,EAAIxB,OAASpB,EAAcO,SAAWhE,MAAMC,QAAQoG,EAAIzD,WACjF,ES0CUoK,CAAiBrT,KACnBE,KAAK+I,WAAWlC,sBAAsB/G,GACtCE,KAAKkS,kBAAkBrL,0BAG7B,CAEQ,iBAAA6L,IACqB,IAAvB1S,KAAKgB,QAAQ0B,OACf8D,IAGFC,EAAAA,OAAOf,KAAK,qCAAsC,CAChDjD,MAAOzC,KAAKgB,QAAQyB,MACpBC,MAAO1C,KAAKgB,QAAQ0B,MACpBC,SAAU3C,KAAKgB,QAAQ2B,UAE3B,CAEQ,sBAAAmQ,GACNrM,EAAAA,OAAO/D,MAAM,6CAEb,MAAM0Q,EAAgB,IAAItS,EAAcd,KAAKgB,SAC7ChB,KAAKkB,OAASkS,EAAcnS,SAC5BwF,EAAAA,OAAO/D,MAAM,2CACf,CAOO,MAAAqF,CAAO9H,EAAcwE,GAC1BzE,KAAK+I,WAAWhB,OAAO9H,EAAMwE,EAC/B,CAKO,SAAAwD,CAAUhI,GACf,OAAOD,KAAK+I,WAAWd,UAAUhI,EACnC,CAKO,QAAAkI,CAASlI,EAAcwE,GAC5B,OAAOzE,KAAK+I,WAAWZ,SAASlI,EAAMwE,EACxC,CAKO,QAAA4D,CAASpI,EAAcqI,GAC5BtI,KAAK+I,WAAWV,SAASpI,EAAMqI,EACjC,CAKO,YAAAmF,CAAaxN,GAClB,OAAOD,KAAK+I,WAAW0E,aAAaxN,EACtC,CAKA,WAAWgH,GACT,OAAOjH,KAAKkS,kBAAkBjL,OAChC,CAMO,OAAAL,CAAQ0B,GACTtI,KAAKkS,kBAAkBjL,QACzBqB,EAAStI,MAIXA,KAAKwS,eAAejJ,KAAKjB,EAC3B,CAKO,gBAAAY,CAAiBZ,GACtBtI,KAAKyS,wBAAwBlJ,KAAKjB,EACpC,CAKA,UAAW+K,GACT,OAAOrT,KAAKoS,aACd,CAKA,UAAWnB,GACT,OAAOjR,KAAKqS,SACd,CAKA,kBAAWiB,GACT,OAAOtT,KAAKsS,iBACd,CAKA,YAAWiB,GACT,OAAOvT,KAAKuS,WACd,CAEO,OAAAzF,GACLrG,EAAAA,OAAOf,KAAK,oCAEZ1F,KAAKkS,kBAAkBhL,QACvBlH,KAAKwS,eAAiB,GACtBxS,KAAKmS,UAAUrF,UAGoB,OAA/B9M,KAAKkB,QAAQsS,oBAAyD,IAA/BxT,KAAKkB,QAAQsS,eACtDxT,KAAKkB,OAAOsS,cAAcC,YAAYzT,KAAKkB,QAE7ClB,KAAKkB,YAAS,EAEduF,EAAAA,OAAO/D,MAAM,kCACf,6Ff3NK,WACN,OAAO6D,CACR"}
|