@fmsim/board 2.0.0-beta.99 → 2.0.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/custom-elements.json +2692 -2085
- package/dist/src/board-i18n.d.ts +37 -0
- package/dist/src/board-i18n.js +121 -0
- package/dist/src/board-i18n.js.map +1 -0
- package/dist/src/config/flow-replay.d.ts +14 -0
- package/dist/src/config/flow-replay.js +20 -0
- package/dist/src/config/flow-replay.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/layers/mcs-mouse-event-handler.js +21 -4
- package/dist/src/layers/mcs-mouse-event-handler.js.map +1 -1
- package/dist/src/layers/movement-layer.d.ts +5 -2
- package/dist/src/layers/movement-layer.js +19 -2
- package/dist/src/layers/movement-layer.js.map +1 -1
- package/dist/src/modeller/bulk-create-dialog.d.ts +12 -1
- package/dist/src/modeller/bulk-create-dialog.js +67 -3
- package/dist/src/modeller/bulk-create-dialog.js.map +1 -1
- package/dist/src/modeller/bulk-create.d.ts +6 -0
- package/dist/src/modeller/bulk-create.js +3 -1
- package/dist/src/modeller/bulk-create.js.map +1 -1
- package/dist/src/modeller/component-toolbar/component-toolbar.d.ts +6 -0
- package/dist/src/modeller/component-toolbar/component-toolbar.js +18 -2
- package/dist/src/modeller/component-toolbar/component-toolbar.js.map +1 -1
- package/dist/src/modeller/edit-toolbar.js +10 -1
- package/dist/src/modeller/edit-toolbar.js.map +1 -1
- package/dist/src/modeller/property-sidebar/effects/effects.js +3 -2
- package/dist/src/modeller/property-sidebar/effects/effects.js.map +1 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.d.ts +9 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js +34 -4
- package/dist/src/modeller/property-sidebar/inspector/inspector-controller.js.map +1 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.d.ts +2 -0
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js +23 -4
- package/dist/src/modeller/property-sidebar/inspector/inspector-group-view.js.map +1 -1
- package/dist/src/modeller/property-sidebar/inspector/inspector.js +28 -5
- package/dist/src/modeller/property-sidebar/inspector/inspector.js.map +1 -1
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js +12 -3
- package/dist/src/modeller/property-sidebar/shapes/ox-input-entity-id.js.map +1 -1
- package/dist/src/modeller/property-sidebar/shapes/shapes.js +40 -24
- package/dist/src/modeller/property-sidebar/shapes/shapes.js.map +1 -1
- package/dist/src/modeller/property-sidebar/specifics/parent-object.d.ts +29 -0
- package/dist/src/modeller/property-sidebar/specifics/parent-object.js +74 -0
- package/dist/src/modeller/property-sidebar/specifics/parent-object.js.map +1 -0
- package/dist/src/modeller/property-sidebar/specifics/specifics.d.ts +4 -2
- package/dist/src/modeller/property-sidebar/specifics/specifics.js +13 -31
- package/dist/src/modeller/property-sidebar/specifics/specifics.js.map +1 -1
- package/dist/src/ox-board-modeller.js +9 -2
- package/dist/src/ox-board-modeller.js.map +1 -1
- package/dist/src/ox-board-viewer.js +16 -6
- package/dist/src/ox-board-viewer.js.map +1 -1
- package/dist/src/ox-editor-board-selector.js +4 -2
- package/dist/src/ox-editor-board-selector.js.map +1 -1
- package/dist/src/ox-playback-controls.d.ts +9 -0
- package/dist/src/ox-playback-controls.js +24 -0
- package/dist/src/ox-playback-controls.js.map +1 -1
- package/dist/src/scene-monitor-renderer.d.ts +10 -0
- package/dist/src/scene-monitor-renderer.js +226 -0
- package/dist/src/scene-monitor-renderer.js.map +1 -0
- package/dist/src/selector/ox-board-selector.js +2 -1
- package/dist/src/selector/ox-board-selector.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ox-playback-controls.js","sourceRoot":"","sources":["../../src/ox-playback-controls.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/iconbutton/icon-button.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\ntype PlaybackState = 'idle' | 'playing' | 'paused' | 'stopped'\n\n/**\n * Window-length presets shown in the range dropdown. Values are durations\n * in milliseconds; `'Range'` is the sentinel for \"user picks both bounds\n * directly\". The fixed presets cap at 2H — same as the explicit Range\n * mode max gap — so any preset switch produces a window already inside\n * the allowed envelope.\n */\nconst RANGE_DURATIONS: Record<string, number> = {\n '10m': 10 * 60 * 1000,\n '30m': 30 * 60 * 1000,\n '1H': 60 * 60 * 1000,\n '2H': 2 * 60 * 60 * 1000\n}\nconst ONE_HOUR = RANGE_DURATIONS['1H']\nconst MAX_GAP_MS = RANGE_DURATIONS['2H'] // 2H upper bound on (to − from)\nconst MIN_GAP_MS = RANGE_DURATIONS['10m'] // 10m lower bound (prevents reversal / zero-window)\n\ntype RangeKey = '10m' | '30m' | '1H' | '2H' | 'Range'\n\n/**\n * 플레이백 컨트롤 오버레이 바 (2행 구조)\n *\n * 1행 (config): Range 선택 (10m / 30m / 1H / 2H / Range) + ←/→ shift\n * + FROM/TO datetime 입력 (date 캘린더 + time 키인) + Apply 버튼\n * + 배속 (×1 ×2 ×4)\n *\n * - Fixed presets: TO 가 anchor (←/→ 로 shift), FROM = TO − range.\n * 입력 disabled. 초기 TO = 현재시간.\n * - Range mode: 양쪽 직접 입력. 2H cap, 10m min, future clamp,\n * 마지막 편집한 쪽이 anchor.\n * - 상단 변경은 PENDING tier 만 갱신 — Apply 누르기 전에는 하단\n * 슬라이더/엔진에 영향 없음.\n *\n * 2행 (control): play / pause / 시크바 (적용된 range 만큼 span) /\n * 현재 재생 시각 / 닫기.\n *\n * @fires playback-start - 플레이백 시작 요청 { fromTime, toTime, speed }\n * toTime 은 window END — 엔진이 chunked fetch 의 상한으로 사용.\n * @fires playback-pause - 일시정지 요청\n * @fires playback-resume - 재개 요청\n * @fires playback-stop - 중지 요청\n * @fires playback-seek - 드래그 릴리즈 시 시크 요청 { toTime }\n * @fires playback-seek-start - 드래그 첫 input 시 즉시 발행 (엔진 타이머·애니메이션 정지 신호, detail 없음)\n * @fires playback-seek-preview - 드래그 중 throttle (leading+trailing, 200ms) 된 스크러빙 요청 { toTime }\n * @fires playback-speed - 배속 변경 요청 { speed }\n */\n@customElement('ox-playback-controls')\nexport class PlaybackControls extends LitElement {\n static styles = css`\n :host {\n display: flex;\n justify-content: center;\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 1001;\n pointer-events: none;\n }\n\n .control-bar {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 16px;\n margin: 12px;\n max-width: 560px;\n width: 100%;\n background: rgba(0, 0, 0, 0.75);\n backdrop-filter: blur(8px);\n border-radius: 12px;\n color: #fff;\n pointer-events: auto;\n font-size: 13px;\n box-sizing: border-box;\n }\n\n /* 1행: 시점 설정 + 배속 */\n .row-time {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n md-icon-button {\n --md-icon-button-icon-color: #fff;\n --md-icon-button-hover-icon-color: #fff;\n --md-icon-button-pressed-icon-color: #fff;\n --md-icon-button-focus-icon-color: #fff;\n --md-icon-button-state-layer-color: rgba(255, 255, 255, 0.2);\n }\n\n md-icon-button:hover {\n background: rgba(255, 255, 255, 0.15);\n border-radius: 50%;\n }\n\n /*\n * shift 버튼 (←/→) — Material Web 의 md-icon-button 은 shadow DOM\n * 내 ripple/focus-ring/touch-target 영역이 visible 32px 컨테이너\n * 너머로 hit area 를 확장해서 인접 버튼끼리 호버 영역이 겹친다.\n * (사용자 보고: > 의 hover 가 < 의 오른쪽 영역까지 침범) plain\n * <button> 으로 교체해 hit area 를 정확히 32×32px 로 한정.\n */\n .shift-btn {\n width: 22px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n flex-shrink: 0;\n }\n\n /*\n * shift 버튼 묶음 — 두 chevron 이 시각적으로 한 쌍으로 보이도록\n * 사이 여백을 완전히 제거 (row-time 의 6px gap 전체를 negative\n * margin 으로 상쇄). 양쪽 인접 요소와의 간격은 row gap 으로 유지.\n */\n .shift-btn + .shift-btn {\n margin-left: -6px;\n }\n\n .shift-btn md-icon {\n font-size: 16px;\n width: 16px;\n height: 16px;\n }\n\n .shift-btn:hover:not(:disabled) {\n color: #ffb766;\n }\n\n .shift-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n .date-input {\n background: rgba(255, 255, 255, 0.15);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 6px;\n color: #fff;\n font-size: 13px;\n padding: 4px 8px;\n outline: none;\n color-scheme: dark;\n width: 130px;\n }\n\n .date-input:focus {\n border-color: rgba(255, 255, 255, 0.6);\n }\n\n .hour-select,\n .range-select {\n background: rgba(255, 255, 255, 0.15);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 6px;\n color: #fff;\n font-size: 13px;\n padding: 4px 6px;\n outline: none;\n color-scheme: dark;\n cursor: pointer;\n }\n\n /*\n * Windows + Chrome: select 의 color/background 가 native popup option 에\n * 일관되게 적용 안 됨 (option 텍스트는 white 상속, popup 배경은 OS 기본 흰색\n * → invisible). option 요소에 명시적으로 dark 컬러 박아 모든 OS 에서 가독.\n */\n .hour-select option,\n .range-select option {\n background-color: #1a1d22;\n color: #fff;\n }\n\n .hour-select:focus,\n .range-select:focus {\n border-color: rgba(255, 255, 255, 0.6);\n }\n\n .range-select {\n min-width: 64px;\n }\n\n .composite-datetime {\n display: inline-flex;\n align-items: center;\n background: rgba(255, 255, 255, 0.15);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 6px;\n color: #fff;\n font-size: 12px;\n padding: 4px 6px;\n box-sizing: border-box;\n transition: border-color 0.2s, opacity 0.2s;\n }\n\n .composite-datetime:focus-within {\n border-color: rgba(255, 255, 255, 0.6);\n }\n\n .composite-datetime.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .inner-date {\n background: transparent;\n border: none;\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n outline: none;\n color-scheme: dark;\n padding: 0;\n cursor: text;\n }\n\n .inner-date::-webkit-calendar-picker-indicator,\n .inner-date::-webkit-inner-spin-button,\n .inner-date::-webkit-clear-button {\n display: none;\n -webkit-appearance: none;\n }\n\n .inner-time {\n background: transparent;\n border: none;\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n outline: none;\n width: 16px;\n text-align: center;\n padding: 0;\n }\n\n .inner-time::placeholder {\n color: rgba(255, 255, 255, 0.3);\n }\n\n .date-input:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .apply-btn {\n padding: 4px 12px;\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 4px;\n background: transparent;\n color: #fff;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n }\n\n .apply-btn.dirty {\n background: rgba(255, 165, 0, 0.4);\n border-color: rgba(255, 165, 0, 0.8);\n }\n\n .apply-btn:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.2);\n }\n\n .apply-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n .time-label {\n font-size: 12px;\n opacity: 0.7;\n white-space: nowrap;\n }\n\n .spacer {\n flex: 1;\n }\n\n .speed-group {\n display: flex;\n gap: 2px;\n }\n\n .speed-btn {\n padding: 4px 8px;\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 4px;\n background: transparent;\n color: #fff;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n }\n\n .speed-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n }\n\n .speed-btn.active {\n background: rgba(255, 255, 255, 0.3);\n border-color: #fff;\n }\n\n /* 2행: 재생 제어 + 시크바 */\n .row-seek {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .row-seek md-icon-button {\n --md-icon-button-icon-size: 20px;\n --md-icon-button-container-width: 36px;\n --md-icon-button-container-height: 36px;\n }\n\n .row-seek md-icon-button.primary {\n --md-icon-button-icon-size: 24px;\n --md-icon-button-container-width: 40px;\n --md-icon-button-container-height: 40px;\n }\n\n .row-seek md-icon {\n font-variation-settings: 'FILL' 1;\n }\n\n .timeline {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n }\n\n .seek-track {\n position: relative;\n width: 100%;\n height: 12px;\n }\n\n .seek-rail {\n position: absolute;\n top: 4px;\n left: 0;\n right: 0;\n height: 4px;\n background: rgba(255, 255, 255, 0.15);\n border-radius: 2px;\n }\n\n .buffer-bar {\n position: absolute;\n top: 4px;\n height: 4px;\n background: rgba(255, 255, 255, 0.4);\n border-radius: 2px;\n pointer-events: none;\n z-index: 1;\n }\n\n .seek-bar {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 12px;\n -webkit-appearance: none;\n appearance: none;\n background: transparent;\n outline: none;\n cursor: pointer;\n z-index: 2;\n margin: 0;\n }\n\n .seek-bar::-webkit-slider-runnable-track {\n height: 12px;\n background: transparent;\n }\n\n .seek-bar::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #fff;\n cursor: pointer;\n }\n\n .seek-labels {\n display: flex;\n justify-content: space-between;\n font-size: 11px;\n opacity: 0.8;\n }\n\n .current-time {\n font-size: 12px;\n opacity: 0.9;\n white-space: nowrap;\n min-width: 60px;\n text-align: center;\n }\n `\n\n @property({ type: String }) playbackState: PlaybackState = 'idle'\n @property({ type: Number }) speed: number = 1\n @property({ type: String }) currentTime: string = ''\n @property({ type: Array }) bufferedRanges: { from: number; to: number }[] = []\n\n /** 외부에서 전달받은 초기 시간 범위 (from만 사용, to는 from+1h로 자동 계산) */\n @property({ type: Object }) timeRange: { from: Date; to: Date } = {\n from: new Date(Date.now() - ONE_HOUR),\n to: new Date()\n }\n\n /**\n * Two-tier state model:\n *\n * PENDING (`_pendingRange`, `_pendingFrom`, `_pendingTo`)\n * The upper bar's draft. User edits these via the range dropdown,\n * ←/→ shift, or direct date/time inputs (Range mode only). The\n * lower seekbar is NOT driven by these — they only become live\n * when the user clicks \"적용\".\n *\n * APPLIED (`_appliedFrom`, `_appliedTo`)\n * The window the seekbar visualizes and the engine fetches data\n * for. Updated only by `_onApply()`, which mirrors pending values\n * here and dispatches a `playback-start` to refresh the engine\n * with the new window (when playing/paused).\n *\n * Splitting these prevents mid-edit churn: typing into the time input\n * doesn't refetch on every keystroke, and the slider doesn't jiggle\n * while the user picks a date.\n */\n @state() private _pendingRange: RangeKey = '1H'\n @state() private _pendingFrom: Date = new Date(Date.now() - ONE_HOUR)\n @state() private _pendingTo: Date = new Date()\n /**\n * Which side did the user touch most recently in Range mode? Anchors\n * the auto-adjust when the resulting gap violates min/max — the\n * unedited side is what gets pushed to a legal value, so the user's\n * latest intent is preserved.\n */\n @state() private _lastEdited: 'from' | 'to' = 'to'\n\n @state() private _appliedFrom: Date = new Date(Date.now() - ONE_HOUR)\n @state() private _appliedTo: Date = new Date()\n\n @state() declare private _seekValue: number\n @state() declare private _currentPlayTime: Date | null\n @state() private _seeking = false\n @state() private _seekPreviewTime: Date | null = null\n\n private _speeds = [1, 2, 4]\n /** 드래그 중 playback-seek-preview throttle 타이머 (leading+trailing) */\n private _previewThrottle?: number\n /** 드래그 중 가장 최근 seekbar 위치 시각 */\n private _latestPreviewTime: Date | null = null\n /** 가장 최근에 실제 emit 한 preview 시각 (dedupe 용) */\n private _lastEmittedPreviewTime: Date | null = null\n\n connectedCallback() {\n super.connectedCallback()\n // Initialize both pending and applied from the incoming timeRange.\n // Default range = 1H (matches the previous fixed-window behavior),\n // so the seekbar covers a 1H window from the start.\n const initFrom = new Date(this.timeRange.from)\n const initTo = new Date(this.timeRange.to)\n this._appliedFrom = initFrom\n this._appliedTo = initTo\n this._pendingFrom = new Date(initFrom)\n this._pendingTo = new Date(initTo)\n this._pendingRange = this._inferRangeKey(initTo.getTime() - initFrom.getTime())\n this._seekValue = 0\n this._currentPlayTime = null\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._cancelPreviewThrottle()\n }\n\n updated(changes: Map<string, any>) {\n if (changes.has('timeRange') && this.timeRange) {\n // Resync both tiers from the externally-supplied window.\n this._appliedFrom = new Date(this.timeRange.from)\n this._appliedTo = new Date(this.timeRange.to)\n this._pendingFrom = new Date(this._appliedFrom)\n this._pendingTo = new Date(this._appliedTo)\n this._pendingRange = this._inferRangeKey(this._appliedTo.getTime() - this._appliedFrom.getTime())\n }\n // 엔진 프레임(currentTime 프로프 변경)이 유일한 시간 소스.\n // drag 중에는 _seeking 가 true 이므로 엔진 업데이트 무시 (스크러빙 중 UI 튐 방지).\n if (changes.has('currentTime') && this.currentTime && !this._seeking) {\n this._syncFromEngine()\n }\n }\n\n /**\n * Map a millisecond gap to the matching preset key, or `'Range'` if\n * the gap doesn't match any preset (custom window already in effect).\n */\n private _inferRangeKey(gapMs: number): RangeKey {\n for (const k of ['10m', '30m', '1H', '2H'] as const) {\n if (RANGE_DURATIONS[k] === gapMs) return k\n }\n return 'Range'\n }\n\n render() {\n const isPlaying = this.playbackState === 'playing'\n const isPaused = this.playbackState === 'paused'\n const isActive = isPlaying || isPaused\n\n const isRangeMode = this._pendingRange === 'Range'\n const isDirty =\n this._pendingFrom.getTime() !== this._appliedFrom.getTime() ||\n this._pendingTo.getTime() !== this._appliedTo.getTime()\n\n return html`\n <div class=\"control-bar\">\n <!-- 1행 (config): Range 선택 + ←/→ shift + datetime 입력 + Apply -->\n <div class=\"row-time\">\n <select class=\"range-select\" .value=${this._pendingRange} @change=${this._onRangeChange}>\n ${(['10m', '30m', '1H', '2H', 'Range'] as RangeKey[]).map(\n k => html`<option value=${k} ?selected=${this._pendingRange === k}>${k}</option>`\n )}\n </select>\n\n <button\n class=\"shift-btn\"\n @click=${this._shiftPending(-1)}\n ?disabled=${isRangeMode}\n title=\"이전 ${this._pendingRange}\"\n >\n <md-icon>chevron_left</md-icon>\n </button>\n <button\n class=\"shift-btn\"\n @click=${this._shiftPending(1)}\n ?disabled=${isRangeMode}\n title=\"다음 ${this._pendingRange}\"\n >\n <md-icon>chevron_right</md-icon>\n </button>\n\n <!-- FROM composite datetime -->\n <div class=\"composite-datetime ${!isRangeMode ? 'disabled' : ''}\">\n <input\n type=\"date\"\n class=\"inner-date\"\n .value=${this._toDateString(this._pendingFrom)}\n ?disabled=${!isRangeMode}\n @change=${(e: Event) => this._onCompositeChange('from', 'date', e)}\n />\n \n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingFrom.getHours())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('from', 'h', e)}\n @input=${(e: Event) => this._onTimeInput('h', e)}\n @change=${(e: Event) => this._onCompositeChange('from', 'h', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingFrom.getMinutes())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('from', 'm', e)}\n @input=${(e: Event) => this._onTimeInput('m', e)}\n @change=${(e: Event) => this._onCompositeChange('from', 'm', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingFrom.getSeconds())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('from', 's', e)}\n @input=${(e: Event) => this._onTimeInput('s', e)}\n @change=${(e: Event) => this._onCompositeChange('from', 's', e)}\n />\n </div>\n\n <span class=\"time-label\">~</span>\n\n <!-- TO composite datetime -->\n <div class=\"composite-datetime ${!isRangeMode ? 'disabled' : ''}\">\n <input\n type=\"date\"\n class=\"inner-date\"\n .value=${this._toDateString(this._pendingTo)}\n ?disabled=${!isRangeMode}\n @change=${(e: Event) => this._onCompositeChange('to', 'date', e)}\n />\n \n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingTo.getHours())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('to', 'h', e)}\n @input=${(e: Event) => this._onTimeInput('h', e)}\n @change=${(e: Event) => this._onCompositeChange('to', 'h', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingTo.getMinutes())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('to', 'm', e)}\n @input=${(e: Event) => this._onTimeInput('m', e)}\n @change=${(e: Event) => this._onCompositeChange('to', 'm', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingTo.getSeconds())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('to', 's', e)}\n @input=${(e: Event) => this._onTimeInput('s', e)}\n @change=${(e: Event) => this._onCompositeChange('to', 's', e)}\n />\n </div>\n\n <button class=\"apply-btn ${isDirty ? 'dirty' : ''}\" ?disabled=${!isDirty} @click=${this._onApply}>\n 적용\n </button>\n\n <span class=\"spacer\"></span>\n </div>\n\n <!-- 2행: 재생 제어 + 시크바 + 배속 + 닫기 -->\n <div class=\"row-seek\">\n <md-icon-button class=\"primary\" @click=${isActive ? this._onTogglePlayPause : this._onStart}>\n <md-icon>${isPlaying ? 'pause' : 'play_arrow'}</md-icon>\n </md-icon-button>\n\n <div class=\"timeline\">\n <div class=\"seek-track\">\n <div class=\"seek-rail\"></div>\n ${this._renderBufferBars()}\n <input\n type=\"range\"\n class=\"seek-bar\"\n min=\"0\"\n max=\"1000\"\n .value=${String(this._seekValue)}\n @input=${this._onSeekInput}\n @change=${this._onSeekChange}\n />\n </div>\n <div class=\"seek-labels\">\n <span>${this._formatTime(this._appliedFrom)}</span>\n <span>${this._formatTime(this._appliedTo)}</span>\n </div>\n </div>\n\n <span class=\"current-time\">\n ${this._seeking && this._seekPreviewTime\n ? this._formatTime(this._seekPreviewTime)\n : this._currentPlayTime ? this._formatTime(this._currentPlayTime) : '--:--:--'}\n </span>\n\n <div class=\"speed-group\">\n ${this._speeds.map(\n s => html`\n <button class=\"speed-btn ${this.speed === s ? 'active' : ''}\" @click=${() => this._onSpeedChange(s)}>\n ×${s}\n </button>\n `\n )}\n </div>\n\n <md-icon-button @click=${this._onStop}>\n <md-icon>close</md-icon>\n </md-icon-button>\n </div>\n </div>\n `\n }\n\n private _formatTime(date: Date): string {\n return date.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })\n }\n\n private _toDateString(date: Date): string {\n const pad = (n: number) => String(n).padStart(2, '0')\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n }\n\n /** \"HH:MM:SS\" — what `<input type=\"time\" step=\"1\">` reads/writes. */\n private _toTimeString(date: Date): string {\n const pad = (n: number) => String(n).padStart(2, '0')\n return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n }\n\n /**\n * \"YYYY-MM-DDTHH:MM:SS\" — what `<input type=\"datetime-local\" step=\"1\">`\n * reads/writes. Local time (no Z suffix), matches the input element's\n * own convention.\n */\n private _toDatetimeLocalString(date: Date): string {\n return `${this._toDateString(date)}T${this._toTimeString(date)}`\n }\n\n /**\n * 엔진 프레임(currentTime 프로프) 을 유일한 시간 소스로 사용하여\n * _currentPlayTime 및 _seekValue 동기화.\n */\n private _syncFromEngine() {\n if (!this.currentTime || this._seeking) return\n const currentMs = new Date(this.currentTime).getTime()\n if (!Number.isFinite(currentMs)) return\n\n this._currentPlayTime = new Date(currentMs)\n\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const range = to - from\n if (range <= 0) return\n this._seekValue = Math.max(0, Math.min(1000, Math.round(((currentMs - from) / range) * 1000)))\n }\n\n // ── Pending-tier handlers (upper config bar) ──────────────────────\n\n private _onRangeChange(e: Event) {\n const newKey = (e.target as HTMLSelectElement).value as RangeKey\n this._pendingRange = newKey\n if (newKey === 'Range') {\n // Switching to Range: keep current pending bounds, just unlock the\n // inputs. Auto-adjust will kick in when the user edits one side.\n return\n }\n // Fixed preset: end stays where it is (clamped to now), start = end − range.\n const dur = RANGE_DURATIONS[newKey]\n const now = Date.now()\n const newTo = Math.min(this._pendingTo.getTime(), now)\n this._pendingTo = new Date(newTo)\n this._pendingFrom = new Date(newTo - dur)\n this._lastEdited = 'to'\n }\n\n private _shiftPending(direction: -1 | 1) {\n return () => {\n // Only meaningful in Fixed mode — Range mode disables the buttons.\n if (this._pendingRange === 'Range') return\n const dur = RANGE_DURATIONS[this._pendingRange]\n const now = Date.now()\n let newToMs = this._pendingTo.getTime() + direction * dur\n newToMs = Math.min(newToMs, now) // future clamp on forward shift\n this._pendingTo = new Date(newToMs)\n this._pendingFrom = new Date(newToMs - dur)\n this._lastEdited = 'to'\n }\n }\n\n private _pad(n: number): string {\n return String(n).padStart(2, '0')\n }\n\n private _onTimeKeydown(side: 'from' | 'to', part: 'h' | 'm' | 's', e: KeyboardEvent) {\n const el = e.target as HTMLInputElement\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault()\n const current = side === 'from' ? this._pendingFrom : this._pendingTo\n const next = new Date(current)\n \n let num = parseInt(el.value, 10)\n if (isNaN(num)) num = 0\n \n const step = e.key === 'ArrowUp' ? 1 : -1\n num += step\n \n const max = part === 'h' ? 23 : 59\n if (num < 0) num = max\n if (num > max) num = 0\n\n if (part === 'h') next.setHours(num)\n if (part === 'm') next.setMinutes(num)\n if (part === 's') next.setSeconds(num)\n\n this._writePendingSide(side, next)\n return\n }\n\n if (\n !/^[0-9]$/.test(e.key) &&\n !['Backspace', 'Tab', 'ArrowLeft', 'ArrowRight', 'Delete', 'Enter'].includes(e.key) &&\n !e.ctrlKey && !e.metaKey\n ) {\n e.preventDefault()\n }\n }\n\n private _onTimeInput(part: 'h' | 'm' | 's', e: Event) {\n const el = e.target as HTMLInputElement\n let val = el.value.replace(/[^0-9]/g, '')\n if (val.length > 0) {\n let num = parseInt(val, 10)\n const max = part === 'h' ? 23 : 59\n if (num > max) {\n val = String(max)\n }\n }\n el.value = val\n }\n\n private _onCompositeChange(side: 'from' | 'to', part: 'date' | 'h' | 'm' | 's', e: Event) {\n const el = e.target as HTMLInputElement\n const current = side === 'from' ? this._pendingFrom : this._pendingTo\n const next = new Date(current)\n\n if (part === 'date') {\n const value = el.value\n if (!value) return\n const [y, m, d] = value.split('-').map(Number)\n next.setFullYear(y, m - 1, d)\n } else {\n let num = parseInt(el.value, 10)\n if (isNaN(num)) num = 0\n if (part === 'h') next.setHours(Math.max(0, Math.min(23, num)))\n if (part === 'm') next.setMinutes(Math.max(0, Math.min(59, num)))\n if (part === 's') next.setSeconds(Math.max(0, Math.min(59, num)))\n // 패딩 강제 적용하여 UI 보정\n el.value = this._pad(num)\n }\n\n if (Number.isNaN(next.getTime())) return\n this._writePendingSide(side, next)\n }\n\n /**\n * Commit a side's new value, then enforce the gap envelope.\n *\n * Rules (per spec):\n * - `to` clamped to now (no future).\n * - `to − from ≥ 10m` (no zero/negative window).\n * - `to − from ≤ 2h`.\n * - The side just edited (`side`) is the anchor; the OTHER side\n * gets pushed to satisfy the envelope.\n * - If anchoring `from` would push `to` past now, fall back to\n * anchoring `to = now` and shrink `from`.\n */\n private _writePendingSide(side: 'from' | 'to', next: Date) {\n this._lastEdited = side\n const now = Date.now()\n\n if (side === 'to') {\n const toMs = Math.min(next.getTime(), now)\n this._pendingTo = new Date(toMs)\n const fromMs = this._pendingFrom.getTime()\n const gap = toMs - fromMs\n if (gap < MIN_GAP_MS) this._pendingFrom = new Date(toMs - MIN_GAP_MS)\n else if (gap > MAX_GAP_MS) this._pendingFrom = new Date(toMs - MAX_GAP_MS)\n } else {\n const fromMs = next.getTime()\n this._pendingFrom = new Date(fromMs)\n const toMs = this._pendingTo.getTime()\n const gap = toMs - fromMs\n if (gap < MIN_GAP_MS) {\n let adj = fromMs + MIN_GAP_MS\n if (adj > now) {\n // Pushing `to` would land in the future — re-anchor on `to=now`\n // and shrink `from` instead.\n adj = now\n this._pendingFrom = new Date(adj - MIN_GAP_MS)\n }\n this._pendingTo = new Date(adj)\n } else if (gap > MAX_GAP_MS) {\n let adj = fromMs + MAX_GAP_MS\n if (adj > now) {\n adj = now\n this._pendingFrom = new Date(adj - MAX_GAP_MS)\n }\n this._pendingTo = new Date(adj)\n }\n }\n }\n\n /**\n * \"적용\" — copy pending → applied, reset seek to window start, and\n * (re)start the engine so it refetches data for the new window.\n *\n * Behavior by prior state:\n * - playing: dispatch `playback-start` → engine restarts in new window.\n * - paused : same — engine refetches; we leave the user in 'playing'\n * state because the alternative (carry \"paused\" through a window\n * change) ends up showing stale buffered data while the new fetch\n * completes, which reads as a stuck UI.\n * - idle : window updated silently; no engine activity until user\n * presses play.\n */\n private _onApply() {\n this._appliedFrom = new Date(this._pendingFrom)\n this._appliedTo = new Date(this._pendingTo)\n this._seekValue = 0\n this._currentPlayTime = new Date(this._appliedFrom)\n this._seekPreviewTime = null\n\n if (this.playbackState === 'playing' || this.playbackState === 'paused') {\n this._onStart()\n }\n }\n\n private _onStart() {\n // 현재 seekbar 위치에서 재생 시작 (_seekValue 가 유일한 슬라이더 위치 소스)\n const ratio = this._seekValue / 1000\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const fromTime = new Date(from + (to - from) * ratio)\n const toTime = new Date(to)\n\n this._currentPlayTime = fromTime\n this.playbackState = 'playing'\n\n // toTime is the window END (= _appliedTo), NOT a hardcoded\n // fromTime + 1h. The engine uses it as the chunked-fetch upper\n // bound — without it, variable ranges (10m / 30m / Range mode)\n // would silently overshoot the user-selected window.\n this.dispatchEvent(\n new CustomEvent('playback-start', {\n detail: { fromTime, toTime, speed: this.speed },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onTogglePlayPause() {\n if (this.playbackState === 'playing') {\n this.playbackState = 'paused'\n this.dispatchEvent(new CustomEvent('playback-pause', { bubbles: true, composed: true }))\n } else {\n this.playbackState = 'playing'\n this.dispatchEvent(new CustomEvent('playback-resume', { bubbles: true, composed: true }))\n }\n }\n\n private _onStop() {\n this._cancelPreviewThrottle()\n this._currentPlayTime = null\n this._seekValue = 0\n this.playbackState = 'idle'\n this.dispatchEvent(new CustomEvent('playback-stop', { bubbles: true, composed: true }))\n }\n\n private _onSeekInput(e: Event) {\n // 드래그 시작(첫 input) 탐지\n const justStartedDrag = !this._seeking\n this._seeking = true\n this._seekValue = Number((e.target as HTMLInputElement).value)\n\n // 드래그 중 preview 시간 계산 + UI 표시\n const ratio = this._seekValue / 1000\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const previewTime = new Date(from + (to - from) * ratio)\n this._seekPreviewTime = previewTime\n this._latestPreviewTime = previewTime\n\n if (justStartedDrag) {\n // 즉시 이벤트 — 재생 타이머 정지\n this.dispatchEvent(\n new CustomEvent('playback-seek-start', {\n bubbles: true,\n composed: true\n })\n )\n }\n\n // throttle: leading-edge 에서 즉시 emit, 이후 200ms 내 input 은 latest 만 갱신.\n // throttle 만료 시점에 latest 가 마지막 emit 과 다르면 trailing emit.\n if (this._previewThrottle === undefined) {\n // leading emit\n this._emitPreview()\n // trailing 예약\n this._previewThrottle = window.setTimeout(() => {\n this._previewThrottle = undefined\n // 드래그가 끝났거나 (_seeking=false) 새 throttle 이 이미 있으면 스킵\n if (!this._seeking) return\n // latest 가 마지막 emit 과 같으면 중복 skip\n const latest = this._latestPreviewTime\n if (latest && latest.getTime() !== this._lastEmittedPreviewTime?.getTime()) {\n this._emitPreview()\n }\n }, PlaybackControls.PREVIEW_THROTTLE_MS)\n }\n // else: 이미 throttle 진행 중 → _latestPreviewTime 만 업데이트됨 (위에서 처리됨)\n }\n\n /** latest 위치 시각으로 playback-seek-preview 를 발행 (dedupe). */\n private _emitPreview() {\n const time = this._latestPreviewTime\n if (!time) return\n if (this._lastEmittedPreviewTime?.getTime() === time.getTime()) return\n this._lastEmittedPreviewTime = time\n this.dispatchEvent(\n new CustomEvent('playback-seek-preview', {\n detail: { toTime: time },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onSeekChange(e: Event) {\n this._seeking = false\n this._cancelPreviewThrottle()\n\n const ratio = Number((e.target as HTMLInputElement).value) / 1000\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const targetTime = new Date(from + (to - from) * ratio)\n\n this._seekPreviewTime = null\n this._currentPlayTime = targetTime\n\n this.dispatchEvent(\n new CustomEvent('playback-seek', {\n detail: { toTime: targetTime },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private static readonly PREVIEW_THROTTLE_MS = 200\n\n private _cancelPreviewThrottle() {\n if (this._previewThrottle != null) {\n clearTimeout(this._previewThrottle)\n this._previewThrottle = undefined\n }\n this._latestPreviewTime = null\n this._lastEmittedPreviewTime = null\n }\n\n private _renderBufferBars() {\n if (!this.bufferedRanges?.length) return ''\n\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const range = to - from\n if (range <= 0) return ''\n\n return this.bufferedRanges.map(r => {\n const left = Math.max(0, (r.from - from) / range) * 100\n const right = Math.min(1, (r.to - from) / range) * 100\n const width = right - left\n return html`<div class=\"buffer-bar\" style=\"left:${left}%;width:${width}%\"></div>`\n })\n }\n\n private _onSpeedChange(speed: number) {\n this.speed = speed\n\n this.dispatchEvent(\n new CustomEvent('playback-speed', {\n detail: { speed },\n bubbles: true,\n composed: true\n })\n )\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ox-playback-controls.js","sourceRoot":"","sources":["../../src/ox-playback-controls.ts"],"names":[],"mappings":";;AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,yCAAyC,CAAA;AAEhD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAIlE;;;;;;GAMG;AACH,MAAM,eAAe,GAA2B;IAC9C,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACrB,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACrB,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACpB,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;CACzB,CAAA;AACD,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;AACtC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA,CAAC,gCAAgC;AACzE,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA,CAAC,oDAAoD;AAI9F;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEI,IAAM,gBAAgB,wBAAtB,MAAM,gBAAiB,SAAQ,UAAU;IAAzC;;QAgXuB,kBAAa,GAAkB,MAAM,CAAA;QACrC,UAAK,GAAW,CAAC,CAAA;QACjB,gBAAW,GAAW,EAAE,CAAA;QACzB,mBAAc,GAAmC,EAAE,CAAA;QAE9E,wDAAwD;QAC5B,cAAS,GAA6B;YAChE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC;YACrC,EAAE,EAAE,IAAI,IAAI,EAAE;SACf,CAAA;QAED;;;;;;;;;;;;;;;;;;WAkBG;QACc,kBAAa,GAAa,IAAI,CAAA;QAC9B,iBAAY,GAAS,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAA;QACpD,eAAU,GAAS,IAAI,IAAI,EAAE,CAAA;QAC9C;;;;;WAKG;QACc,gBAAW,GAAkB,IAAI,CAAA;QAEjC,iBAAY,GAAS,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAA;QACpD,eAAU,GAAS,IAAI,IAAI,EAAE,CAAA;QAI7B,aAAQ,GAAG,KAAK,CAAA;QAChB,qBAAgB,GAAgB,IAAI,CAAA;QAE7C,YAAO,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QAG3B,gCAAgC;QACxB,uBAAkB,GAAgB,IAAI,CAAA;QAC9C,6CAA6C;QACrC,4BAAuB,GAAgB,IAAI,CAAA;IAgoBrD,CAAC;IA9nBC,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,mEAAmE;QACnE,mEAAmE;QACnE,oDAAoD;QACpD,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QAC9C,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;QAC1C,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAA;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtC,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/E,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;QACnB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;IAC9B,CAAC;IAED,oBAAoB;QAClB,KAAK,CAAC,oBAAoB,EAAE,CAAA;QAC5B,IAAI,CAAC,sBAAsB,EAAE,CAAA;IAC/B,CAAC;IAED,OAAO,CAAC,OAAyB;QAC/B,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/C,yDAAyD;YACzD,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YACjD,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;YAC7C,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;YAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YAC3C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAA;QACnG,CAAC;QACD,yCAAyC;QACzC,4DAA4D;QAC5D,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrE,IAAI,CAAC,eAAe,EAAE,CAAA;QACxB,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,cAAc,CAAC,KAAa;QAClC,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAU,EAAE,CAAC;YACpD,IAAI,eAAe,CAAC,CAAC,CAAC,KAAK,KAAK;gBAAE,OAAO,CAAC,CAAA;QAC5C,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,MAAM;QACJ,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,KAAK,SAAS,CAAA;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,KAAK,QAAQ,CAAA;QAChD,MAAM,QAAQ,GAAG,SAAS,IAAI,QAAQ,CAAA;QAEtC,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,KAAK,OAAO,CAAA;QAClD,MAAM,OAAO,GACX,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;YAC3D,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;QAEzD,OAAO,IAAI,CAAA;;;;gDAIiC,IAAI,CAAC,aAAa,YAAY,IAAI,CAAC,cAAc;cAClF,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAgB,CAAC,GAAG,CACvD,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA,iBAAiB,CAAC,cAAc,IAAI,CAAC,aAAa,KAAK,CAAC,IAAI,CAAC,WAAW,CAClF;;;;;qBAKQ,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;wBACnB,WAAW;wBACX,IAAI,CAAC,aAAa;;;;;;qBAMrB,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;wBAClB,WAAW;wBACX,IAAI,CAAC,aAAa;;;;;;6CAMG,IAAI,CAAC,kBAAkB,EAAE;;;;;2CAK3B,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;;;;uBAIlD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC;0BAClC,CAAC,WAAW;wBACd,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;;;;;;;uBAOzD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;0BACpC,CAAC,WAAW;yBACb,CAAC,CAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;uBAC3D,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;wBACtC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;;;;;;uBAMtD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;0BACtC,CAAC,WAAW;yBACb,CAAC,CAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;uBAC3D,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;wBACtC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;;;;;;uBAMtD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;0BACtC,CAAC,WAAW;yBACb,CAAC,CAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;uBAC3D,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;wBACtC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;;;;;;;2CAOlC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;;;;uBAIlD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC;0BAChC,CAAC,WAAW;wBACd,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;;;;;;;uBAOvD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;0BAClC,CAAC,WAAW;yBACb,CAAC,CAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;uBACzD,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;wBACtC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;;;;;;uBAMpD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;0BACpC,CAAC,WAAW;yBACb,CAAC,CAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;uBACzD,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;wBACtC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;;;;;;uBAMpD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;0BACpC,CAAC,WAAW;yBACb,CAAC,CAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;uBACzD,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;wBACtC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;;;;qCAItC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,OAAO,WAAW,IAAI,CAAC,QAAQ;;;;;;;;;mDASvD,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ;uBAC9E,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY;;;;;;gBAMzC,IAAI,CAAC,iBAAiB,EAAE;;;;;;yBAMf,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;yBACvB,IAAI,CAAC,YAAY;0BAChB,IAAI,CAAC,aAAa;;;;sBAItB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC;sBACnC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;;;;;cAKzC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,gBAAgB;YACtC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,gBAAgB,CAAC;YACzC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,UAAU;;;;cAI9E,IAAI,CAAC,OAAO,CAAC,GAAG,CAChB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA;2CACoB,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;qBAC9F,CAAC;;eAEP,CACF;;;mCAGsB,IAAI,CAAC,OAAO;;;;;KAK1C,CAAA;IACH,CAAC;IAEO,WAAW,CAAC,IAAU;QAC5B,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;IACnH,CAAC;IAEO,aAAa,CAAC,IAAU;QAC9B,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QACrD,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAA;IACnF,CAAC;IAED,qEAAqE;IAC7D,aAAa,CAAC,IAAU;QAC9B,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QACrD,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,CAAA;IACtF,CAAC;IAED;;;;OAIG;IACK,sBAAsB,CAAC,IAAU;QACvC,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAA;IAClE,CAAC;IAED;;;OAGG;IACK,eAAe;QACrB,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAC9C,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAA;QACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAM;QAEvC,IAAI,CAAC,gBAAgB,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAA;QAE3C,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAA;QACxC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,EAAE,GAAG,IAAI,CAAA;QACvB,IAAI,KAAK,IAAI,CAAC;YAAE,OAAM;QACtB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IAChG,CAAC;IAED,qEAAqE;IAE7D,cAAc,CAAC,CAAQ;QAC7B,MAAM,MAAM,GAAI,CAAC,CAAC,MAA4B,CAAC,KAAiB,CAAA;QAChE,IAAI,CAAC,aAAa,GAAG,MAAM,CAAA;QAC3B,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,mEAAmE;YACnE,iEAAiE;YACjE,OAAM;QACR,CAAC;QACD,6EAA6E;QAC7E,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAA;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAA;QACtD,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAA;QACjC,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,CAAA;QACzC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;IACzB,CAAC;IAEO,aAAa,CAAC,SAAiB;QACrC,OAAO,GAAG,EAAE;YACV,mEAAmE;YACnE,IAAI,IAAI,CAAC,aAAa,KAAK,OAAO;gBAAE,OAAM;YAC1C,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACtB,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,SAAS,GAAG,GAAG,CAAA;YACzD,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA,CAAC,gCAAgC;YACjE,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAA;YACnC,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,CAAA;YAC3C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACzB,CAAC,CAAA;IACH,CAAC;IAED;;;;;;;OAOG;IACK,kBAAkB;QACxB,OAAO,GAAG,EAAE;YACV,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACtB,MAAM,IAAI,GACR,IAAI,CAAC,aAAa,KAAK,OAAO;gBAC5B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;gBACtE,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YAEzC,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;YAC/B,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,CAAA;YACxC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACzB,CAAC,CAAA;IACH,CAAC;IAEO,IAAI,CAAC,CAAS;QACpB,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACnC,CAAC;IAEO,cAAc,CAAC,IAAmB,EAAE,IAAqB,EAAE,CAAgB;QACjF,MAAM,EAAE,GAAG,CAAC,CAAC,MAA0B,CAAA;QACvC,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;YACjD,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,MAAM,OAAO,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAA;YACrE,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAA;YAE9B,IAAI,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YAChC,IAAI,KAAK,CAAC,GAAG,CAAC;gBAAE,GAAG,GAAG,CAAC,CAAA;YAEvB,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACzC,GAAG,IAAI,IAAI,CAAA;YAEX,MAAM,GAAG,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAClC,IAAI,GAAG,GAAG,CAAC;gBAAE,GAAG,GAAG,GAAG,CAAA;YACtB,IAAI,GAAG,GAAG,GAAG;gBAAE,GAAG,GAAG,CAAC,CAAA;YAEtB,IAAI,IAAI,KAAK,GAAG;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;YACpC,IAAI,IAAI,KAAK,GAAG;gBAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;YACtC,IAAI,IAAI,KAAK,GAAG;gBAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;YAEtC,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,IACE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;YACtB,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC;YACnF,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,EACxB,CAAC;YACD,CAAC,CAAC,cAAc,EAAE,CAAA;QACpB,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,IAAqB,EAAE,CAAQ;QAClD,MAAM,EAAE,GAAG,CAAC,CAAC,MAA0B,CAAA;QACvC,IAAI,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACzC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;YAC3B,MAAM,GAAG,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAClC,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;gBACd,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;YACnB,CAAC;QACH,CAAC;QACD,EAAE,CAAC,KAAK,GAAG,GAAG,CAAA;IAChB,CAAC;IAEO,kBAAkB,CAAC,IAAmB,EAAE,IAA8B,EAAE,CAAQ;QACtF,MAAM,EAAE,GAAG,CAAC,CAAC,MAA0B,CAAA;QACvC,MAAM,OAAO,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAA;QACrE,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAA;QAE9B,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAA;YACtB,IAAI,CAAC,KAAK;gBAAE,OAAM;YAClB,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC9C,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QAC/B,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YAChC,IAAI,KAAK,CAAC,GAAG,CAAC;gBAAE,GAAG,GAAG,CAAC,CAAA;YACvB,IAAI,IAAI,KAAK,GAAG;gBAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;YAC/D,IAAI,IAAI,KAAK,GAAG;gBAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;YACjE,IAAI,IAAI,KAAK,GAAG;gBAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;YACjE,mBAAmB;YACnB,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC3B,CAAC;QAED,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAAE,OAAM;QACxC,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACpC,CAAC;IAED;;;;;;;;;;;OAWG;IACK,iBAAiB,CAAC,IAAmB,EAAE,IAAU;QACvD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEtB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAA;YAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAA;YAChC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAA;YAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,MAAM,CAAA;YACzB,IAAI,GAAG,GAAG,UAAU;gBAAE,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,CAAA;iBAChE,IAAI,GAAG,GAAG,UAAU;gBAAE,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,CAAA;QAC5E,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;YAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,CAAA;YACpC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;YACtC,MAAM,GAAG,GAAG,IAAI,GAAG,MAAM,CAAA;YACzB,IAAI,GAAG,GAAG,UAAU,EAAE,CAAC;gBACrB,IAAI,GAAG,GAAG,MAAM,GAAG,UAAU,CAAA;gBAC7B,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;oBACd,gEAAgE;oBAChE,6BAA6B;oBAC7B,GAAG,GAAG,GAAG,CAAA;oBACT,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,CAAA;gBAChD,CAAC;gBACD,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;YACjC,CAAC;iBAAM,IAAI,GAAG,GAAG,UAAU,EAAE,CAAC;gBAC5B,IAAI,GAAG,GAAG,MAAM,GAAG,UAAU,CAAA;gBAC7B,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;oBACd,GAAG,GAAG,GAAG,CAAA;oBACT,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,CAAA;gBAChD,CAAC;gBACD,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;OAYG;IACK,QAAQ;QACd,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC3C,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;QACnB,IAAI,CAAC,gBAAgB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QACnD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAE5B,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,QAAQ,EAAE,CAAC;YACxE,IAAI,CAAC,QAAQ,EAAE,CAAA;QACjB,CAAC;IACH,CAAC;IAEO,QAAQ;QACd,sDAAsD;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAA;QACxC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAA;QAE3B,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAA;QAChC,IAAI,CAAC,aAAa,GAAG,SAAS,CAAA;QAE9B,2DAA2D;QAC3D,+DAA+D;QAC/D,+DAA+D;QAC/D,qDAAqD;QACrD,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,gBAAgB,EAAE;YAChC,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;YAC/C,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAA;IACH,CAAC;IAEO,kBAAkB;QACxB,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACrC,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAA;YAC7B,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,gBAAgB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC1F,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,aAAa,GAAG,SAAS,CAAA;YAC9B,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC3F,CAAC;IACH,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,sBAAsB,EAAE,CAAA;QAC7B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;QACnB,IAAI,CAAC,aAAa,GAAG,MAAM,CAAA;QAC3B,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACzF,CAAC;IAEO,YAAY,CAAC,CAAQ;QAC3B,qBAAqB;QACrB,MAAM,eAAe,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAA;QACtC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,MAAM,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,CAAA;QAE9D,8BAA8B;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAA;QACxC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,CAAA;QACxD,IAAI,CAAC,gBAAgB,GAAG,WAAW,CAAA;QACnC,IAAI,CAAC,kBAAkB,GAAG,WAAW,CAAA;QAErC,IAAI,eAAe,EAAE,CAAC;YACpB,qBAAqB;YACrB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,qBAAqB,EAAE;gBACrC,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,IAAI;aACf,CAAC,CACH,CAAA;QACH,CAAC;QAED,qEAAqE;QACrE,yDAAyD;QACzD,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACxC,eAAe;YACf,IAAI,CAAC,YAAY,EAAE,CAAA;YACnB,cAAc;YACd,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;;gBAC7C,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAA;gBACjC,oDAAoD;gBACpD,IAAI,CAAC,IAAI,CAAC,QAAQ;oBAAE,OAAM;gBAC1B,kCAAkC;gBAClC,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAA;gBACtC,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,MAAK,MAAA,IAAI,CAAC,uBAAuB,0CAAE,OAAO,EAAE,CAAA,EAAE,CAAC;oBAC3E,IAAI,CAAC,YAAY,EAAE,CAAA;gBACrB,CAAC;YACH,CAAC,EAAE,kBAAgB,CAAC,mBAAmB,CAAC,CAAA;QAC1C,CAAC;QACD,gEAAgE;IAClE,CAAC;IAED,0DAA0D;IAClD,YAAY;;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAA;QACpC,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,IAAI,CAAA,MAAA,IAAI,CAAC,uBAAuB,0CAAE,OAAO,EAAE,MAAK,IAAI,CAAC,OAAO,EAAE;YAAE,OAAM;QACtE,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAA;QACnC,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,uBAAuB,EAAE;YACvC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;YACxB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,CAAQ;QAC5B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAA;QACrB,IAAI,CAAC,sBAAsB,EAAE,CAAA;QAE7B,MAAM,KAAK,GAAG,MAAM,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAA;QACxC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,CAAA;QAEvD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAC5B,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAA;QAElC,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,eAAe,EAAE;YAC/B,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE;YAC9B,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAA;IACH,CAAC;IAIO,sBAAsB;QAC5B,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,EAAE,CAAC;YAClC,YAAY,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;YACnC,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAA;QACnC,CAAC;QACD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAA;QAC9B,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAA;IACrC,CAAC;IAEO,iBAAiB;;QACvB,IAAI,CAAC,CAAA,MAAA,IAAI,CAAC,cAAc,0CAAE,MAAM,CAAA;YAAE,OAAO,EAAE,CAAA;QAE3C,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAA;QACxC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,KAAK,GAAG,EAAE,GAAG,IAAI,CAAA;QACvB,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO,EAAE,CAAA;QAEzB,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAA;YACvD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAA;YACtD,MAAM,KAAK,GAAG,KAAK,GAAG,IAAI,CAAA;YAC1B,OAAO,IAAI,CAAA,uCAAuC,IAAI,WAAW,KAAK,WAAW,CAAA;QACnF,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,cAAc,CAAC,KAAa;QAClC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAElB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,gBAAgB,EAAE;YAChC,MAAM,EAAE,EAAE,KAAK,EAAE;YACjB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAA;IACH,CAAC;;AAriCM,uBAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6WlB,AA7WY,CA6WZ;AAmpBuB,oCAAmB,GAAG,GAAG,AAAN,CAAM;AAjpBrB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;uDAAsC;AACrC;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;+CAAkB;AACjB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;qDAAyB;AACzB;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;wDAAoD;AAGlD;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;mDAG1B;AAqBgB;IAAhB,KAAK,EAAE;uDAAuC;AAC9B;IAAhB,KAAK,EAAE;sDAA6D;AACpD;IAAhB,KAAK,EAAE;oDAAsC;AAO7B;IAAhB,KAAK,EAAE;qDAA0C;AAEjC;IAAhB,KAAK,EAAE;sDAA6D;AACpD;IAAhB,KAAK,EAAE;oDAAsC;AAErB;IAAxB,KAAK,EAAE;oDAAmC;AAClB;IAAxB,KAAK,EAAE;0DAA8C;AACrC;IAAhB,KAAK,EAAE;kDAAyB;AAChB;IAAhB,KAAK,EAAE;0DAA6C;AA/Z1C,gBAAgB;IAD5B,aAAa,CAAC,sBAAsB,CAAC;GACzB,gBAAgB,CAuiC5B","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/iconbutton/icon-button.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\ntype PlaybackState = 'idle' | 'playing' | 'paused' | 'stopped'\n\n/**\n * Window-length presets shown in the range dropdown. Values are durations\n * in milliseconds; `'Range'` is the sentinel for \"user picks both bounds\n * directly\". The fixed presets cap at 2H — same as the explicit Range\n * mode max gap — so any preset switch produces a window already inside\n * the allowed envelope.\n */\nconst RANGE_DURATIONS: Record<string, number> = {\n '10m': 10 * 60 * 1000,\n '30m': 30 * 60 * 1000,\n '1H': 60 * 60 * 1000,\n '2H': 2 * 60 * 60 * 1000\n}\nconst ONE_HOUR = RANGE_DURATIONS['1H']\nconst MAX_GAP_MS = RANGE_DURATIONS['2H'] // 2H upper bound on (to − from)\nconst MIN_GAP_MS = RANGE_DURATIONS['10m'] // 10m lower bound (prevents reversal / zero-window)\n\ntype RangeKey = '10m' | '30m' | '1H' | '2H' | 'Range'\n\n/**\n * 플레이백 컨트롤 오버레이 바 (2행 구조)\n *\n * 1행 (config): Range 선택 (10m / 30m / 1H / 2H / Range) + ←/→ shift\n * + FROM/TO datetime 입력 (date 캘린더 + time 키인) + Apply 버튼\n * + 배속 (×1 ×2 ×4)\n *\n * - Fixed presets: TO 가 anchor (←/→ 로 shift), FROM = TO − range.\n * 입력 disabled. 초기 TO = 현재시간.\n * - Range mode: 양쪽 직접 입력. 2H cap, 10m min, future clamp,\n * 마지막 편집한 쪽이 anchor.\n * - 상단 변경은 PENDING tier 만 갱신 — Apply 누르기 전에는 하단\n * 슬라이더/엔진에 영향 없음.\n *\n * 2행 (control): play / pause / 시크바 (적용된 range 만큼 span) /\n * 현재 재생 시각 / 닫기.\n *\n * @fires playback-start - 플레이백 시작 요청 { fromTime, toTime, speed }\n * toTime 은 window END — 엔진이 chunked fetch 의 상한으로 사용.\n * @fires playback-pause - 일시정지 요청\n * @fires playback-resume - 재개 요청\n * @fires playback-stop - 중지 요청\n * @fires playback-seek - 드래그 릴리즈 시 시크 요청 { toTime }\n * @fires playback-seek-start - 드래그 첫 input 시 즉시 발행 (엔진 타이머·애니메이션 정지 신호, detail 없음)\n * @fires playback-seek-preview - 드래그 중 throttle (leading+trailing, 200ms) 된 스크러빙 요청 { toTime }\n * @fires playback-speed - 배속 변경 요청 { speed }\n */\n@customElement('ox-playback-controls')\nexport class PlaybackControls extends LitElement {\n static styles = css`\n :host {\n display: flex;\n justify-content: center;\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 1001;\n pointer-events: none;\n }\n\n .control-bar {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 16px;\n margin: 12px;\n max-width: 560px;\n width: 100%;\n background: rgba(0, 0, 0, 0.75);\n backdrop-filter: blur(8px);\n border-radius: 12px;\n color: #fff;\n pointer-events: auto;\n font-size: 13px;\n box-sizing: border-box;\n }\n\n /* 1행: 시점 설정 + 배속 */\n .row-time {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n md-icon-button {\n --md-icon-button-icon-color: #fff;\n --md-icon-button-hover-icon-color: #fff;\n --md-icon-button-pressed-icon-color: #fff;\n --md-icon-button-focus-icon-color: #fff;\n --md-icon-button-state-layer-color: rgba(255, 255, 255, 0.2);\n }\n\n md-icon-button:hover {\n background: rgba(255, 255, 255, 0.15);\n border-radius: 50%;\n }\n\n /*\n * shift 버튼 (←/→) — Material Web 의 md-icon-button 은 shadow DOM\n * 내 ripple/focus-ring/touch-target 영역이 visible 32px 컨테이너\n * 너머로 hit area 를 확장해서 인접 버튼끼리 호버 영역이 겹친다.\n * (사용자 보고: > 의 hover 가 < 의 오른쪽 영역까지 침범) plain\n * <button> 으로 교체해 hit area 를 정확히 32×32px 로 한정.\n */\n .shift-btn {\n width: 22px;\n height: 28px;\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n flex-shrink: 0;\n }\n\n /*\n * shift 버튼 묶음 — 두 chevron 이 시각적으로 한 쌍으로 보이도록\n * 사이 여백을 완전히 제거 (row-time 의 6px gap 전체를 negative\n * margin 으로 상쇄). 양쪽 인접 요소와의 간격은 row gap 으로 유지.\n */\n .shift-btn + .shift-btn {\n margin-left: -6px;\n }\n\n .shift-btn md-icon {\n font-size: 16px;\n width: 16px;\n height: 16px;\n }\n\n .shift-btn:hover:not(:disabled) {\n color: #ffb766;\n }\n\n .shift-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n .date-input {\n background: rgba(255, 255, 255, 0.15);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 6px;\n color: #fff;\n font-size: 13px;\n padding: 4px 8px;\n outline: none;\n color-scheme: dark;\n width: 130px;\n }\n\n .date-input:focus {\n border-color: rgba(255, 255, 255, 0.6);\n }\n\n .hour-select,\n .range-select {\n background: rgba(255, 255, 255, 0.15);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 6px;\n color: #fff;\n font-size: 13px;\n padding: 4px 6px;\n outline: none;\n color-scheme: dark;\n cursor: pointer;\n }\n\n /*\n * Windows + Chrome: select 의 color/background 가 native popup option 에\n * 일관되게 적용 안 됨 (option 텍스트는 white 상속, popup 배경은 OS 기본 흰색\n * → invisible). option 요소에 명시적으로 dark 컬러 박아 모든 OS 에서 가독.\n */\n .hour-select option,\n .range-select option {\n background-color: #1a1d22;\n color: #fff;\n }\n\n .hour-select:focus,\n .range-select:focus {\n border-color: rgba(255, 255, 255, 0.6);\n }\n\n .range-select {\n min-width: 64px;\n }\n\n .composite-datetime {\n display: inline-flex;\n align-items: center;\n background: rgba(255, 255, 255, 0.15);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 6px;\n color: #fff;\n font-size: 12px;\n padding: 4px 6px;\n box-sizing: border-box;\n transition: border-color 0.2s, opacity 0.2s;\n }\n\n .composite-datetime:focus-within {\n border-color: rgba(255, 255, 255, 0.6);\n }\n\n .composite-datetime.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .inner-date {\n background: transparent;\n border: none;\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n outline: none;\n color-scheme: dark;\n padding: 0;\n cursor: text;\n }\n\n .inner-date::-webkit-calendar-picker-indicator,\n .inner-date::-webkit-inner-spin-button,\n .inner-date::-webkit-clear-button {\n display: none;\n -webkit-appearance: none;\n }\n\n .inner-time {\n background: transparent;\n border: none;\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n outline: none;\n width: 16px;\n text-align: center;\n padding: 0;\n }\n\n .inner-time::placeholder {\n color: rgba(255, 255, 255, 0.3);\n }\n\n .date-input:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .apply-btn {\n padding: 4px 12px;\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 4px;\n background: transparent;\n color: #fff;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n }\n\n .apply-btn.dirty {\n background: rgba(255, 165, 0, 0.4);\n border-color: rgba(255, 165, 0, 0.8);\n }\n\n .apply-btn:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.2);\n }\n\n .apply-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n .time-label {\n font-size: 12px;\n opacity: 0.7;\n white-space: nowrap;\n }\n\n .spacer {\n flex: 1;\n }\n\n .speed-group {\n display: flex;\n gap: 2px;\n }\n\n .speed-btn {\n padding: 4px 8px;\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 4px;\n background: transparent;\n color: #fff;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n }\n\n .speed-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n }\n\n .speed-btn.active {\n background: rgba(255, 255, 255, 0.3);\n border-color: #fff;\n }\n\n /* 2행: 재생 제어 + 시크바 */\n .row-seek {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .row-seek md-icon-button {\n --md-icon-button-icon-size: 20px;\n --md-icon-button-container-width: 36px;\n --md-icon-button-container-height: 36px;\n }\n\n .row-seek md-icon-button.primary {\n --md-icon-button-icon-size: 24px;\n --md-icon-button-container-width: 40px;\n --md-icon-button-container-height: 40px;\n }\n\n .row-seek md-icon {\n font-variation-settings: 'FILL' 1;\n }\n\n .timeline {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n }\n\n .seek-track {\n position: relative;\n width: 100%;\n height: 12px;\n }\n\n .seek-rail {\n position: absolute;\n top: 4px;\n left: 0;\n right: 0;\n height: 4px;\n background: rgba(255, 255, 255, 0.15);\n border-radius: 2px;\n }\n\n .buffer-bar {\n position: absolute;\n top: 4px;\n height: 4px;\n background: rgba(255, 255, 255, 0.4);\n border-radius: 2px;\n pointer-events: none;\n z-index: 1;\n }\n\n .seek-bar {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 12px;\n -webkit-appearance: none;\n appearance: none;\n background: transparent;\n outline: none;\n cursor: pointer;\n z-index: 2;\n margin: 0;\n }\n\n .seek-bar::-webkit-slider-runnable-track {\n height: 12px;\n background: transparent;\n }\n\n .seek-bar::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #fff;\n cursor: pointer;\n }\n\n .seek-labels {\n display: flex;\n justify-content: space-between;\n font-size: 11px;\n opacity: 0.8;\n }\n\n .current-time {\n font-size: 12px;\n opacity: 0.9;\n white-space: nowrap;\n min-width: 60px;\n text-align: center;\n }\n `\n\n @property({ type: String }) playbackState: PlaybackState = 'idle'\n @property({ type: Number }) speed: number = 1\n @property({ type: String }) currentTime: string = ''\n @property({ type: Array }) bufferedRanges: { from: number; to: number }[] = []\n\n /** 외부에서 전달받은 초기 시간 범위 (from만 사용, to는 from+1h로 자동 계산) */\n @property({ type: Object }) timeRange: { from: Date; to: Date } = {\n from: new Date(Date.now() - ONE_HOUR),\n to: new Date()\n }\n\n /**\n * Two-tier state model:\n *\n * PENDING (`_pendingRange`, `_pendingFrom`, `_pendingTo`)\n * The upper bar's draft. User edits these via the range dropdown,\n * ←/→ shift, or direct date/time inputs (Range mode only). The\n * lower seekbar is NOT driven by these — they only become live\n * when the user clicks \"적용\".\n *\n * APPLIED (`_appliedFrom`, `_appliedTo`)\n * The window the seekbar visualizes and the engine fetches data\n * for. Updated only by `_onApply()`, which mirrors pending values\n * here and dispatches a `playback-start` to refresh the engine\n * with the new window (when playing/paused).\n *\n * Splitting these prevents mid-edit churn: typing into the time input\n * doesn't refetch on every keystroke, and the slider doesn't jiggle\n * while the user picks a date.\n */\n @state() private _pendingRange: RangeKey = '1H'\n @state() private _pendingFrom: Date = new Date(Date.now() - ONE_HOUR)\n @state() private _pendingTo: Date = new Date()\n /**\n * Which side did the user touch most recently in Range mode? Anchors\n * the auto-adjust when the resulting gap violates min/max — the\n * unedited side is what gets pushed to a legal value, so the user's\n * latest intent is preserved.\n */\n @state() private _lastEdited: 'from' | 'to' = 'to'\n\n @state() private _appliedFrom: Date = new Date(Date.now() - ONE_HOUR)\n @state() private _appliedTo: Date = new Date()\n\n @state() declare private _seekValue: number\n @state() declare private _currentPlayTime: Date | null\n @state() private _seeking = false\n @state() private _seekPreviewTime: Date | null = null\n\n private _speeds = [1, 2, 4]\n /** 드래그 중 playback-seek-preview throttle 타이머 (leading+trailing) */\n private _previewThrottle?: number\n /** 드래그 중 가장 최근 seekbar 위치 시각 */\n private _latestPreviewTime: Date | null = null\n /** 가장 최근에 실제 emit 한 preview 시각 (dedupe 용) */\n private _lastEmittedPreviewTime: Date | null = null\n\n connectedCallback() {\n super.connectedCallback()\n // Initialize both pending and applied from the incoming timeRange.\n // Default range = 1H (matches the previous fixed-window behavior),\n // so the seekbar covers a 1H window from the start.\n const initFrom = new Date(this.timeRange.from)\n const initTo = new Date(this.timeRange.to)\n this._appliedFrom = initFrom\n this._appliedTo = initTo\n this._pendingFrom = new Date(initFrom)\n this._pendingTo = new Date(initTo)\n this._pendingRange = this._inferRangeKey(initTo.getTime() - initFrom.getTime())\n this._seekValue = 0\n this._currentPlayTime = null\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._cancelPreviewThrottle()\n }\n\n updated(changes: Map<string, any>) {\n if (changes.has('timeRange') && this.timeRange) {\n // Resync both tiers from the externally-supplied window.\n this._appliedFrom = new Date(this.timeRange.from)\n this._appliedTo = new Date(this.timeRange.to)\n this._pendingFrom = new Date(this._appliedFrom)\n this._pendingTo = new Date(this._appliedTo)\n this._pendingRange = this._inferRangeKey(this._appliedTo.getTime() - this._appliedFrom.getTime())\n }\n // 엔진 프레임(currentTime 프로프 변경)이 유일한 시간 소스.\n // drag 중에는 _seeking 가 true 이므로 엔진 업데이트 무시 (스크러빙 중 UI 튐 방지).\n if (changes.has('currentTime') && this.currentTime && !this._seeking) {\n this._syncFromEngine()\n }\n }\n\n /**\n * Map a millisecond gap to the matching preset key, or `'Range'` if\n * the gap doesn't match any preset (custom window already in effect).\n */\n private _inferRangeKey(gapMs: number): RangeKey {\n for (const k of ['10m', '30m', '1H', '2H'] as const) {\n if (RANGE_DURATIONS[k] === gapMs) return k\n }\n return 'Range'\n }\n\n render() {\n const isPlaying = this.playbackState === 'playing'\n const isPaused = this.playbackState === 'paused'\n const isActive = isPlaying || isPaused\n\n const isRangeMode = this._pendingRange === 'Range'\n const isDirty =\n this._pendingFrom.getTime() !== this._appliedFrom.getTime() ||\n this._pendingTo.getTime() !== this._appliedTo.getTime()\n\n return html`\n <div class=\"control-bar\">\n <!-- 1행 (config): Range 선택 + ←/→ shift + datetime 입력 + Apply -->\n <div class=\"row-time\">\n <select class=\"range-select\" .value=${this._pendingRange} @change=${this._onRangeChange}>\n ${(['10m', '30m', '1H', '2H', 'Range'] as RangeKey[]).map(\n k => html`<option value=${k} ?selected=${this._pendingRange === k}>${k}</option>`\n )}\n </select>\n\n <button\n class=\"shift-btn\"\n @click=${this._shiftPending(-1)}\n ?disabled=${isRangeMode}\n title=\"이전 ${this._pendingRange}\"\n >\n <md-icon>chevron_left</md-icon>\n </button>\n <button\n class=\"shift-btn\"\n @click=${this._shiftPending(1)}\n ?disabled=${isRangeMode}\n title=\"다음 ${this._pendingRange}\"\n >\n <md-icon>chevron_right</md-icon>\n </button>\n <!-- 현재 시간으로 점프 — window 길이는 유지한 채 TO 를 now 로 옮긴다.\n Range 모드에서도 의미가 있어(간격 보존) ←/→ 와 달리 비활성화하지 않는다. -->\n <button class=\"shift-btn\" @click=${this._shiftPendingToNow()} title=\"현재 시간으로 이동\">\n <md-icon>last_page</md-icon>\n </button>\n\n <!-- FROM composite datetime -->\n <div class=\"composite-datetime ${!isRangeMode ? 'disabled' : ''}\">\n <input\n type=\"date\"\n class=\"inner-date\"\n .value=${this._toDateString(this._pendingFrom)}\n ?disabled=${!isRangeMode}\n @change=${(e: Event) => this._onCompositeChange('from', 'date', e)}\n />\n \n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingFrom.getHours())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('from', 'h', e)}\n @input=${(e: Event) => this._onTimeInput('h', e)}\n @change=${(e: Event) => this._onCompositeChange('from', 'h', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingFrom.getMinutes())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('from', 'm', e)}\n @input=${(e: Event) => this._onTimeInput('m', e)}\n @change=${(e: Event) => this._onCompositeChange('from', 'm', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingFrom.getSeconds())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('from', 's', e)}\n @input=${(e: Event) => this._onTimeInput('s', e)}\n @change=${(e: Event) => this._onCompositeChange('from', 's', e)}\n />\n </div>\n\n <span class=\"time-label\">~</span>\n\n <!-- TO composite datetime -->\n <div class=\"composite-datetime ${!isRangeMode ? 'disabled' : ''}\">\n <input\n type=\"date\"\n class=\"inner-date\"\n .value=${this._toDateString(this._pendingTo)}\n ?disabled=${!isRangeMode}\n @change=${(e: Event) => this._onCompositeChange('to', 'date', e)}\n />\n \n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingTo.getHours())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('to', 'h', e)}\n @input=${(e: Event) => this._onTimeInput('h', e)}\n @change=${(e: Event) => this._onCompositeChange('to', 'h', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingTo.getMinutes())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('to', 'm', e)}\n @input=${(e: Event) => this._onTimeInput('m', e)}\n @change=${(e: Event) => this._onCompositeChange('to', 'm', e)}\n />:\n <input\n type=\"text\"\n class=\"inner-time\"\n maxlength=\"2\"\n .value=${this._pad(this._pendingTo.getSeconds())}\n ?disabled=${!isRangeMode}\n @keydown=${(e: KeyboardEvent) => this._onTimeKeydown('to', 's', e)}\n @input=${(e: Event) => this._onTimeInput('s', e)}\n @change=${(e: Event) => this._onCompositeChange('to', 's', e)}\n />\n </div>\n\n <button class=\"apply-btn ${isDirty ? 'dirty' : ''}\" ?disabled=${!isDirty} @click=${this._onApply}>\n 적용\n </button>\n\n <span class=\"spacer\"></span>\n </div>\n\n <!-- 2행: 재생 제어 + 시크바 + 배속 + 닫기 -->\n <div class=\"row-seek\">\n <md-icon-button class=\"primary\" @click=${isActive ? this._onTogglePlayPause : this._onStart}>\n <md-icon>${isPlaying ? 'pause' : 'play_arrow'}</md-icon>\n </md-icon-button>\n\n <div class=\"timeline\">\n <div class=\"seek-track\">\n <div class=\"seek-rail\"></div>\n ${this._renderBufferBars()}\n <input\n type=\"range\"\n class=\"seek-bar\"\n min=\"0\"\n max=\"1000\"\n .value=${String(this._seekValue)}\n @input=${this._onSeekInput}\n @change=${this._onSeekChange}\n />\n </div>\n <div class=\"seek-labels\">\n <span>${this._formatTime(this._appliedFrom)}</span>\n <span>${this._formatTime(this._appliedTo)}</span>\n </div>\n </div>\n\n <span class=\"current-time\">\n ${this._seeking && this._seekPreviewTime\n ? this._formatTime(this._seekPreviewTime)\n : this._currentPlayTime ? this._formatTime(this._currentPlayTime) : '--:--:--'}\n </span>\n\n <div class=\"speed-group\">\n ${this._speeds.map(\n s => html`\n <button class=\"speed-btn ${this.speed === s ? 'active' : ''}\" @click=${() => this._onSpeedChange(s)}>\n ×${s}\n </button>\n `\n )}\n </div>\n\n <md-icon-button @click=${this._onStop}>\n <md-icon>close</md-icon>\n </md-icon-button>\n </div>\n </div>\n `\n }\n\n private _formatTime(date: Date): string {\n return date.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })\n }\n\n private _toDateString(date: Date): string {\n const pad = (n: number) => String(n).padStart(2, '0')\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n }\n\n /** \"HH:MM:SS\" — what `<input type=\"time\" step=\"1\">` reads/writes. */\n private _toTimeString(date: Date): string {\n const pad = (n: number) => String(n).padStart(2, '0')\n return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n }\n\n /**\n * \"YYYY-MM-DDTHH:MM:SS\" — what `<input type=\"datetime-local\" step=\"1\">`\n * reads/writes. Local time (no Z suffix), matches the input element's\n * own convention.\n */\n private _toDatetimeLocalString(date: Date): string {\n return `${this._toDateString(date)}T${this._toTimeString(date)}`\n }\n\n /**\n * 엔진 프레임(currentTime 프로프) 을 유일한 시간 소스로 사용하여\n * _currentPlayTime 및 _seekValue 동기화.\n */\n private _syncFromEngine() {\n if (!this.currentTime || this._seeking) return\n const currentMs = new Date(this.currentTime).getTime()\n if (!Number.isFinite(currentMs)) return\n\n this._currentPlayTime = new Date(currentMs)\n\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const range = to - from\n if (range <= 0) return\n this._seekValue = Math.max(0, Math.min(1000, Math.round(((currentMs - from) / range) * 1000)))\n }\n\n // ── Pending-tier handlers (upper config bar) ──────────────────────\n\n private _onRangeChange(e: Event) {\n const newKey = (e.target as HTMLSelectElement).value as RangeKey\n this._pendingRange = newKey\n if (newKey === 'Range') {\n // Switching to Range: keep current pending bounds, just unlock the\n // inputs. Auto-adjust will kick in when the user edits one side.\n return\n }\n // Fixed preset: end stays where it is (clamped to now), start = end − range.\n const dur = RANGE_DURATIONS[newKey]\n const now = Date.now()\n const newTo = Math.min(this._pendingTo.getTime(), now)\n this._pendingTo = new Date(newTo)\n this._pendingFrom = new Date(newTo - dur)\n this._lastEdited = 'to'\n }\n\n private _shiftPending(direction: -1 | 1) {\n return () => {\n // Only meaningful in Fixed mode — Range mode disables the buttons.\n if (this._pendingRange === 'Range') return\n const dur = RANGE_DURATIONS[this._pendingRange]\n const now = Date.now()\n let newToMs = this._pendingTo.getTime() + direction * dur\n newToMs = Math.min(newToMs, now) // future clamp on forward shift\n this._pendingTo = new Date(newToMs)\n this._pendingFrom = new Date(newToMs - dur)\n this._lastEdited = 'to'\n }\n }\n\n /**\n * TO(기준시간)를 현재 시각으로 옮기고 FROM 도 같은 간격만큼 함께 시프트한다\n * (= window 길이 보존, 지금 시점을 창의 끝으로).\n *\n * Fixed preset 은 range 가 곧 window 길이(불변식 FROM = TO − dur)라 preset 값을 쓰고,\n * Range 모드는 사용자가 정한 현재 간격(TO − FROM)을 그대로 보존한다. 그래서 ←/→ 와\n * 달리 Range 모드에서도 동작한다.\n */\n private _shiftPendingToNow() {\n return () => {\n const now = Date.now()\n const span =\n this._pendingRange === 'Range'\n ? Math.max(1, this._pendingTo.getTime() - this._pendingFrom.getTime())\n : RANGE_DURATIONS[this._pendingRange]\n\n this._pendingTo = new Date(now)\n this._pendingFrom = new Date(now - span)\n this._lastEdited = 'to'\n }\n }\n\n private _pad(n: number): string {\n return String(n).padStart(2, '0')\n }\n\n private _onTimeKeydown(side: 'from' | 'to', part: 'h' | 'm' | 's', e: KeyboardEvent) {\n const el = e.target as HTMLInputElement\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault()\n const current = side === 'from' ? this._pendingFrom : this._pendingTo\n const next = new Date(current)\n \n let num = parseInt(el.value, 10)\n if (isNaN(num)) num = 0\n \n const step = e.key === 'ArrowUp' ? 1 : -1\n num += step\n \n const max = part === 'h' ? 23 : 59\n if (num < 0) num = max\n if (num > max) num = 0\n\n if (part === 'h') next.setHours(num)\n if (part === 'm') next.setMinutes(num)\n if (part === 's') next.setSeconds(num)\n\n this._writePendingSide(side, next)\n return\n }\n\n if (\n !/^[0-9]$/.test(e.key) &&\n !['Backspace', 'Tab', 'ArrowLeft', 'ArrowRight', 'Delete', 'Enter'].includes(e.key) &&\n !e.ctrlKey && !e.metaKey\n ) {\n e.preventDefault()\n }\n }\n\n private _onTimeInput(part: 'h' | 'm' | 's', e: Event) {\n const el = e.target as HTMLInputElement\n let val = el.value.replace(/[^0-9]/g, '')\n if (val.length > 0) {\n let num = parseInt(val, 10)\n const max = part === 'h' ? 23 : 59\n if (num > max) {\n val = String(max)\n }\n }\n el.value = val\n }\n\n private _onCompositeChange(side: 'from' | 'to', part: 'date' | 'h' | 'm' | 's', e: Event) {\n const el = e.target as HTMLInputElement\n const current = side === 'from' ? this._pendingFrom : this._pendingTo\n const next = new Date(current)\n\n if (part === 'date') {\n const value = el.value\n if (!value) return\n const [y, m, d] = value.split('-').map(Number)\n next.setFullYear(y, m - 1, d)\n } else {\n let num = parseInt(el.value, 10)\n if (isNaN(num)) num = 0\n if (part === 'h') next.setHours(Math.max(0, Math.min(23, num)))\n if (part === 'm') next.setMinutes(Math.max(0, Math.min(59, num)))\n if (part === 's') next.setSeconds(Math.max(0, Math.min(59, num)))\n // 패딩 강제 적용하여 UI 보정\n el.value = this._pad(num)\n }\n\n if (Number.isNaN(next.getTime())) return\n this._writePendingSide(side, next)\n }\n\n /**\n * Commit a side's new value, then enforce the gap envelope.\n *\n * Rules (per spec):\n * - `to` clamped to now (no future).\n * - `to − from ≥ 10m` (no zero/negative window).\n * - `to − from ≤ 2h`.\n * - The side just edited (`side`) is the anchor; the OTHER side\n * gets pushed to satisfy the envelope.\n * - If anchoring `from` would push `to` past now, fall back to\n * anchoring `to = now` and shrink `from`.\n */\n private _writePendingSide(side: 'from' | 'to', next: Date) {\n this._lastEdited = side\n const now = Date.now()\n\n if (side === 'to') {\n const toMs = Math.min(next.getTime(), now)\n this._pendingTo = new Date(toMs)\n const fromMs = this._pendingFrom.getTime()\n const gap = toMs - fromMs\n if (gap < MIN_GAP_MS) this._pendingFrom = new Date(toMs - MIN_GAP_MS)\n else if (gap > MAX_GAP_MS) this._pendingFrom = new Date(toMs - MAX_GAP_MS)\n } else {\n const fromMs = next.getTime()\n this._pendingFrom = new Date(fromMs)\n const toMs = this._pendingTo.getTime()\n const gap = toMs - fromMs\n if (gap < MIN_GAP_MS) {\n let adj = fromMs + MIN_GAP_MS\n if (adj > now) {\n // Pushing `to` would land in the future — re-anchor on `to=now`\n // and shrink `from` instead.\n adj = now\n this._pendingFrom = new Date(adj - MIN_GAP_MS)\n }\n this._pendingTo = new Date(adj)\n } else if (gap > MAX_GAP_MS) {\n let adj = fromMs + MAX_GAP_MS\n if (adj > now) {\n adj = now\n this._pendingFrom = new Date(adj - MAX_GAP_MS)\n }\n this._pendingTo = new Date(adj)\n }\n }\n }\n\n /**\n * \"적용\" — copy pending → applied, reset seek to window start, and\n * (re)start the engine so it refetches data for the new window.\n *\n * Behavior by prior state:\n * - playing: dispatch `playback-start` → engine restarts in new window.\n * - paused : same — engine refetches; we leave the user in 'playing'\n * state because the alternative (carry \"paused\" through a window\n * change) ends up showing stale buffered data while the new fetch\n * completes, which reads as a stuck UI.\n * - idle : window updated silently; no engine activity until user\n * presses play.\n */\n private _onApply() {\n this._appliedFrom = new Date(this._pendingFrom)\n this._appliedTo = new Date(this._pendingTo)\n this._seekValue = 0\n this._currentPlayTime = new Date(this._appliedFrom)\n this._seekPreviewTime = null\n\n if (this.playbackState === 'playing' || this.playbackState === 'paused') {\n this._onStart()\n }\n }\n\n private _onStart() {\n // 현재 seekbar 위치에서 재생 시작 (_seekValue 가 유일한 슬라이더 위치 소스)\n const ratio = this._seekValue / 1000\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const fromTime = new Date(from + (to - from) * ratio)\n const toTime = new Date(to)\n\n this._currentPlayTime = fromTime\n this.playbackState = 'playing'\n\n // toTime is the window END (= _appliedTo), NOT a hardcoded\n // fromTime + 1h. The engine uses it as the chunked-fetch upper\n // bound — without it, variable ranges (10m / 30m / Range mode)\n // would silently overshoot the user-selected window.\n this.dispatchEvent(\n new CustomEvent('playback-start', {\n detail: { fromTime, toTime, speed: this.speed },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onTogglePlayPause() {\n if (this.playbackState === 'playing') {\n this.playbackState = 'paused'\n this.dispatchEvent(new CustomEvent('playback-pause', { bubbles: true, composed: true }))\n } else {\n this.playbackState = 'playing'\n this.dispatchEvent(new CustomEvent('playback-resume', { bubbles: true, composed: true }))\n }\n }\n\n private _onStop() {\n this._cancelPreviewThrottle()\n this._currentPlayTime = null\n this._seekValue = 0\n this.playbackState = 'idle'\n this.dispatchEvent(new CustomEvent('playback-stop', { bubbles: true, composed: true }))\n }\n\n private _onSeekInput(e: Event) {\n // 드래그 시작(첫 input) 탐지\n const justStartedDrag = !this._seeking\n this._seeking = true\n this._seekValue = Number((e.target as HTMLInputElement).value)\n\n // 드래그 중 preview 시간 계산 + UI 표시\n const ratio = this._seekValue / 1000\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const previewTime = new Date(from + (to - from) * ratio)\n this._seekPreviewTime = previewTime\n this._latestPreviewTime = previewTime\n\n if (justStartedDrag) {\n // 즉시 이벤트 — 재생 타이머 정지\n this.dispatchEvent(\n new CustomEvent('playback-seek-start', {\n bubbles: true,\n composed: true\n })\n )\n }\n\n // throttle: leading-edge 에서 즉시 emit, 이후 200ms 내 input 은 latest 만 갱신.\n // throttle 만료 시점에 latest 가 마지막 emit 과 다르면 trailing emit.\n if (this._previewThrottle === undefined) {\n // leading emit\n this._emitPreview()\n // trailing 예약\n this._previewThrottle = window.setTimeout(() => {\n this._previewThrottle = undefined\n // 드래그가 끝났거나 (_seeking=false) 새 throttle 이 이미 있으면 스킵\n if (!this._seeking) return\n // latest 가 마지막 emit 과 같으면 중복 skip\n const latest = this._latestPreviewTime\n if (latest && latest.getTime() !== this._lastEmittedPreviewTime?.getTime()) {\n this._emitPreview()\n }\n }, PlaybackControls.PREVIEW_THROTTLE_MS)\n }\n // else: 이미 throttle 진행 중 → _latestPreviewTime 만 업데이트됨 (위에서 처리됨)\n }\n\n /** latest 위치 시각으로 playback-seek-preview 를 발행 (dedupe). */\n private _emitPreview() {\n const time = this._latestPreviewTime\n if (!time) return\n if (this._lastEmittedPreviewTime?.getTime() === time.getTime()) return\n this._lastEmittedPreviewTime = time\n this.dispatchEvent(\n new CustomEvent('playback-seek-preview', {\n detail: { toTime: time },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onSeekChange(e: Event) {\n this._seeking = false\n this._cancelPreviewThrottle()\n\n const ratio = Number((e.target as HTMLInputElement).value) / 1000\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const targetTime = new Date(from + (to - from) * ratio)\n\n this._seekPreviewTime = null\n this._currentPlayTime = targetTime\n\n this.dispatchEvent(\n new CustomEvent('playback-seek', {\n detail: { toTime: targetTime },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private static readonly PREVIEW_THROTTLE_MS = 200\n\n private _cancelPreviewThrottle() {\n if (this._previewThrottle != null) {\n clearTimeout(this._previewThrottle)\n this._previewThrottle = undefined\n }\n this._latestPreviewTime = null\n this._lastEmittedPreviewTime = null\n }\n\n private _renderBufferBars() {\n if (!this.bufferedRanges?.length) return ''\n\n const from = this._appliedFrom.getTime()\n const to = this._appliedTo.getTime()\n const range = to - from\n if (range <= 0) return ''\n\n return this.bufferedRanges.map(r => {\n const left = Math.max(0, (r.from - from) / range) * 100\n const right = Math.min(1, (r.to - from) / range) * 100\n const width = right - left\n return html`<div class=\"buffer-bar\" style=\"left:${left}%;width:${width}%\"></div>`\n })\n }\n\n private _onSpeedChange(speed: number) {\n this.speed = speed\n\n this.dispatchEvent(\n new CustomEvent('playback-speed', {\n detail: { speed },\n bubbles: true,\n composed: true\n })\n )\n }\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LoadingOverlayRenderer, LoadingOverlayState, LoadingOverlayHost } from '@hatiolab/things-scene';
|
|
2
|
+
export declare class SceneMonitorRenderer implements LoadingOverlayRenderer {
|
|
3
|
+
private _card?;
|
|
4
|
+
private _titleEl?;
|
|
5
|
+
private _bodyEl?;
|
|
6
|
+
private _lagEl?;
|
|
7
|
+
build(container: HTMLElement, host: LoadingOverlayHost): void;
|
|
8
|
+
update(state: LoadingOverlayState): void;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SceneMonitorRenderer — fmsim 커스텀 로딩 오버레이 표현.
|
|
3
|
+
*
|
|
4
|
+
* things-scene 의 LoadingOverlay 렌더러 시임(LoadingOverlayRenderer)을 구현해,
|
|
5
|
+
* 로딩 단계(phaseHistory)를 Network / CPU 카테고리로 묶어 소요시간을 보여준다.
|
|
6
|
+
* 코어(수명주기·데이터·M키·자동표시·타이머)는 things-scene 이 그대로 담당하고,
|
|
7
|
+
* 이 클래스는 표현(DOM)만 담당한다 — 코어를 fork 하지 않는다.
|
|
8
|
+
*
|
|
9
|
+
* this._loadingOverlay.renderer = new SceneMonitorRenderer()
|
|
10
|
+
*/
|
|
11
|
+
/** 로딩 phase → 표시 라벨. */
|
|
12
|
+
const PHASE_LABEL = {
|
|
13
|
+
fetch: 'Fetching model',
|
|
14
|
+
assets: 'Loading assets',
|
|
15
|
+
data: 'Waiting for data',
|
|
16
|
+
parse: 'Parsing model',
|
|
17
|
+
compile: 'Building components',
|
|
18
|
+
render: 'Rendering',
|
|
19
|
+
init: 'Initializing'
|
|
20
|
+
};
|
|
21
|
+
/* 인라인 SVG 아이콘 — stroke=currentColor 라 감싸는 span 색(카테고리색)으로 틴팅된다. */
|
|
22
|
+
const ICON_NETWORK = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'
|
|
23
|
+
+ '<circle cx="12" cy="12" r="9"/><line x1="3" y1="12" x2="21" y2="12"/>'
|
|
24
|
+
+ '<path d="M12 3a15.3 15.3 0 0 1 4 9 15.3 15.3 0 0 1-4 9 15.3 15.3 0 0 1-4-9 15.3 15.3 0 0 1 4-9z"/></svg>';
|
|
25
|
+
/** CPU = microchip(칩) — 둥근 사각 + 내부 사각 + 8핀(원본 이미지 형태). */
|
|
26
|
+
const ICON_CPU = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'
|
|
27
|
+
+ '<rect x="4" y="4" width="16" height="16" rx="2" ry="2"/><rect x="9" y="9" width="6" height="6"/>'
|
|
28
|
+
+ '<line x1="9" y1="1" x2="9" y2="4"/><line x1="15" y1="1" x2="15" y2="4"/>'
|
|
29
|
+
+ '<line x1="9" y1="20" x2="9" y2="23"/><line x1="15" y1="20" x2="15" y2="23"/>'
|
|
30
|
+
+ '<line x1="20" y1="9" x2="23" y2="9"/><line x1="20" y1="14" x2="23" y2="14"/>'
|
|
31
|
+
+ '<line x1="1" y1="9" x2="4" y2="9"/><line x1="1" y1="14" x2="4" y2="14"/></svg>';
|
|
32
|
+
/** 카테고리 정의(표시 순서). Network=파랑, CPU=주황(아이콘도 카테고리색으로 틴팅). */
|
|
33
|
+
const CATEGORIES = [
|
|
34
|
+
{ key: 'network', label: 'Network', icon: ICON_NETWORK, color: '#4a9eff', phases: ['fetch', 'assets', 'data'] },
|
|
35
|
+
{ key: 'cpu', label: 'CPU', icon: ICON_CPU, color: '#ff9f40', phases: ['parse', 'compile', 'render', 'init'] }
|
|
36
|
+
];
|
|
37
|
+
function fmt(ms) {
|
|
38
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
39
|
+
}
|
|
40
|
+
const DIR_META = {
|
|
41
|
+
leading: { word: 'LEAD', arrow: '◂', color: '#3ddc84' },
|
|
42
|
+
lagging: { word: 'LAG', arrow: '▸', color: '#ff5a5a' },
|
|
43
|
+
ontrack: { word: 'SYNC', arrow: '≈', color: '#aab0b6' }
|
|
44
|
+
};
|
|
45
|
+
const LAG_LEVEL_COLORS = { warn: '#ff4040', critical: '#ff1f1f' };
|
|
46
|
+
const MIN_LAG_RADIUS_MS = 1000;
|
|
47
|
+
function fmtLag(ms) {
|
|
48
|
+
const a = Math.abs(ms);
|
|
49
|
+
return a < 1000 ? `${Math.round(a)}ms` : `${(a / 1000).toFixed(1)}s`;
|
|
50
|
+
}
|
|
51
|
+
/** 정확값(ms, 천단위 구분) — 반올림 손실 없이 그대로. */
|
|
52
|
+
function fmtMs(ms) {
|
|
53
|
+
return `${Math.round(ms).toLocaleString()}ms`;
|
|
54
|
+
}
|
|
55
|
+
/** 부호 있는 정확한 차이(drift) — +뒤처짐 / −앞섬 / ±동일. 부호는 반올림 ms 기준. */
|
|
56
|
+
function fmtDrift(ms) {
|
|
57
|
+
const r = Math.round(ms);
|
|
58
|
+
const sign = r > 0 ? '+' : r < 0 ? '−' : '±';
|
|
59
|
+
return `${sign}${Math.abs(r).toLocaleString()}ms`;
|
|
60
|
+
}
|
|
61
|
+
/** drift 부호(반올림 ms)로 삼분 — LAG(>0)/LEAD(<0)/SYNC(=0). deadzone 없음. */
|
|
62
|
+
function lagDirection(s) {
|
|
63
|
+
const d = Math.round(s.drift);
|
|
64
|
+
return d > 0 ? 'lagging' : d < 0 ? 'leading' : 'ontrack';
|
|
65
|
+
}
|
|
66
|
+
/** 방향색 — lagging 은 level(warn/critical)로 더 진한 적색, leading 녹색, ontrack 회색. */
|
|
67
|
+
function dirColor(direction, level) {
|
|
68
|
+
if (direction === 'lagging')
|
|
69
|
+
return LAG_LEVEL_COLORS[level] || DIR_META.lagging.color;
|
|
70
|
+
return DIR_META[direction].color;
|
|
71
|
+
}
|
|
72
|
+
function lagGauge(s) {
|
|
73
|
+
const { current, baseline, min, max } = s;
|
|
74
|
+
const clamp = (x, lo, hi) => (x < lo ? lo : x > hi ? hi : x);
|
|
75
|
+
const R = Math.max(baseline - min, max - baseline, MIN_LAG_RADIUS_MS);
|
|
76
|
+
const pos = (v) => 50 + 50 * clamp((v - baseline) / R, -1, 1);
|
|
77
|
+
return { curPct: pos(current), minPct: pos(min), maxPct: pos(max) };
|
|
78
|
+
}
|
|
79
|
+
function lagFill(pct) {
|
|
80
|
+
return pct >= 50
|
|
81
|
+
? `left:50%;width:${(pct - 50).toFixed(2)}%`
|
|
82
|
+
: `left:${pct.toFixed(2)}%;width:${(50 - pct).toFixed(2)}%`;
|
|
83
|
+
}
|
|
84
|
+
/** 방향 배지(pill) — 색·단어·화살표 + 정확한 차이(부호 있는 drift)로 Lag/Lead 를 즉시 구분. */
|
|
85
|
+
function dirPill(direction, drift, color) {
|
|
86
|
+
const m = DIR_META[direction];
|
|
87
|
+
const diff = fmtDrift(drift);
|
|
88
|
+
const label = direction === 'lagging' ? `${m.word} ${diff} ${m.arrow}` : `${m.arrow} ${m.word} ${diff}`;
|
|
89
|
+
return `<span style="display:inline-flex;align-items:center;padding:2px 9px;border-radius:10px;`
|
|
90
|
+
+ `background:${color}22;color:${color};border:1px solid ${color}66;`
|
|
91
|
+
+ `font-size:11px;font-weight:700;letter-spacing:0.3px;white-space:nowrap">${label}</span>`;
|
|
92
|
+
}
|
|
93
|
+
/** Data Lag 섹션 HTML — baseline 중심 게이지 + 방향 배지 + min/max 눈금 + cur 점. */
|
|
94
|
+
function lagSectionHtml(s) {
|
|
95
|
+
const direction = lagDirection(s);
|
|
96
|
+
const { curPct, minPct, maxPct } = lagGauge(s);
|
|
97
|
+
const color = dirColor(direction, s.level);
|
|
98
|
+
const tick = (p) => `<div style="position:absolute;left:${p}%;top:-1px;width:1px;height:8px;background:rgba(255,255,255,0.28);transform:translateX(-50%)"></div>`;
|
|
99
|
+
return `<div style="display:flex;align-items:center;gap:8px;padding:7px 0 4px;border-top:1px solid rgba(255,255,255,0.07);margin-top:5px">`
|
|
100
|
+
+ `<span style="color:rgba(232,234,237,0.6);font-size:12px;flex:1">Data Lag</span>`
|
|
101
|
+
+ `<span style="color:rgba(232,234,237,0.5);font-size:11px">cur ${fmtMs(s.current)}</span>`
|
|
102
|
+
+ dirPill(direction, s.drift, color)
|
|
103
|
+
+ `</div>`
|
|
104
|
+
+ `<div style="position:relative;height:6px;background:rgba(255,255,255,0.08);border-radius:3px;margin:3px 0 3px">`
|
|
105
|
+
+ `<div style="position:absolute;left:50%;top:-3px;width:1px;height:12px;background:rgba(255,255,255,0.45);transform:translateX(-50%)"></div>`
|
|
106
|
+
+ tick(minPct) + tick(maxPct)
|
|
107
|
+
+ `<div style="position:absolute;top:50%;height:3px;transform:translateY(-50%);border-radius:2px;background:${color};opacity:0.55;${lagFill(curPct)}"></div>`
|
|
108
|
+
+ `<div style="position:absolute;top:50%;width:9px;height:9px;border-radius:50%;background:${color};box-shadow:0 0 0 2px rgba(0,0,0,0.35);transform:translate(-50%,-50%);left:${curPct}%"></div>`
|
|
109
|
+
+ `</div>`
|
|
110
|
+
+ `<div style="display:flex;align-items:center;font-size:10px;color:rgba(232,234,237,0.4)">`
|
|
111
|
+
+ `<span style="flex:1">◂ ahead · min ${fmtLag(s.min)}</span>`
|
|
112
|
+
+ `<span style="flex:1;text-align:center;color:rgba(232,234,237,0.3)">base ${fmtLag(s.baseline)} · n ${s.sampleCount}</span>`
|
|
113
|
+
+ `<span style="flex:1;text-align:right">max ${fmtLag(s.max)} · behind ▸</span>`
|
|
114
|
+
+ `</div>`;
|
|
115
|
+
}
|
|
116
|
+
export class SceneMonitorRenderer {
|
|
117
|
+
build(container, host) {
|
|
118
|
+
const card = document.createElement('div');
|
|
119
|
+
card.style.cssText = [
|
|
120
|
+
'position:absolute',
|
|
121
|
+
'top:50%',
|
|
122
|
+
'left:50%',
|
|
123
|
+
'transform:translate(-50%,-50%)',
|
|
124
|
+
'display:flex',
|
|
125
|
+
'flex-direction:column',
|
|
126
|
+
'gap:6px',
|
|
127
|
+
'min-width:340px',
|
|
128
|
+
'max-width:460px',
|
|
129
|
+
'padding:14px 18px',
|
|
130
|
+
'background:#16191d',
|
|
131
|
+
'border:1px solid rgba(255,255,255,0.08)',
|
|
132
|
+
'border-radius:10px',
|
|
133
|
+
'box-shadow:0 8px 28px rgba(0,0,0,0.45)',
|
|
134
|
+
'pointer-events:auto',
|
|
135
|
+
'user-select:none',
|
|
136
|
+
'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
|
|
137
|
+
'color:#e8eaed'
|
|
138
|
+
].join(';');
|
|
139
|
+
// 헤더: "Scene Monitor" + X
|
|
140
|
+
const header = document.createElement('div');
|
|
141
|
+
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding-bottom:6px;border-bottom:1px solid rgba(255,255,255,0.07)';
|
|
142
|
+
const title = document.createElement('span');
|
|
143
|
+
title.style.cssText = 'font-weight:700;font-size:15px;color:#f1f3f5';
|
|
144
|
+
title.textContent = 'Scene Monitor';
|
|
145
|
+
this._titleEl = title;
|
|
146
|
+
header.appendChild(title);
|
|
147
|
+
const close = document.createElement('button');
|
|
148
|
+
close.textContent = '✕';
|
|
149
|
+
close.title = 'Close';
|
|
150
|
+
close.style.cssText = 'background:none;border:none;color:rgba(255,255,255,0.55);cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;border-radius:4px';
|
|
151
|
+
close.addEventListener('mouseenter', () => { close.style.color = 'rgba(255,255,255,0.95)'; });
|
|
152
|
+
close.addEventListener('mouseleave', () => { close.style.color = 'rgba(255,255,255,0.55)'; });
|
|
153
|
+
close.addEventListener('click', () => host.hide());
|
|
154
|
+
header.appendChild(close);
|
|
155
|
+
card.appendChild(header);
|
|
156
|
+
// 본문: 카테고리별 그룹(매 tick 재구성 — 소량이라 저렴)
|
|
157
|
+
const body = document.createElement('div');
|
|
158
|
+
body.style.cssText = 'display:flex;flex-direction:column;gap:2px;padding-top:4px';
|
|
159
|
+
this._bodyEl = body;
|
|
160
|
+
card.appendChild(body);
|
|
161
|
+
// 하부 Data Lag 섹션 — state.lag 있을 때만 채워짐(없으면 숨김).
|
|
162
|
+
const lag = document.createElement('div');
|
|
163
|
+
lag.style.display = 'none';
|
|
164
|
+
this._lagEl = lag;
|
|
165
|
+
card.appendChild(lag);
|
|
166
|
+
container.appendChild(card);
|
|
167
|
+
this._card = card;
|
|
168
|
+
}
|
|
169
|
+
update(state) {
|
|
170
|
+
var _a, _b;
|
|
171
|
+
if (this._titleEl) {
|
|
172
|
+
this._titleEl.textContent = state.isComplete ? 'Scene Monitor' : 'Scene Monitor';
|
|
173
|
+
}
|
|
174
|
+
if (!this._bodyEl)
|
|
175
|
+
return;
|
|
176
|
+
// phase → duration(ms). 완료 phase 는 duration, 진행 중은 경과.
|
|
177
|
+
const now = performance.now();
|
|
178
|
+
const durByPhase = new Map();
|
|
179
|
+
for (const r of state.phaseHistory) {
|
|
180
|
+
if (r.phase === 'complete')
|
|
181
|
+
continue;
|
|
182
|
+
const d = r.duration !== undefined ? r.duration : Math.max(0, now - r.timestamp);
|
|
183
|
+
durByPhase.set(r.phase, ((_a = durByPhase.get(r.phase)) !== null && _a !== void 0 ? _a : 0) + d);
|
|
184
|
+
}
|
|
185
|
+
let html = '';
|
|
186
|
+
for (const cat of CATEGORIES) {
|
|
187
|
+
const present = cat.phases.filter(p => durByPhase.has(p));
|
|
188
|
+
if (present.length === 0)
|
|
189
|
+
continue;
|
|
190
|
+
const total = present.reduce((s, p) => { var _a; return s + ((_a = durByPhase.get(p)) !== null && _a !== void 0 ? _a : 0); }, 0);
|
|
191
|
+
// 카테고리 헤더
|
|
192
|
+
html += `<div style="display:flex;align-items:center;gap:6px;padding:5px 0 2px">`
|
|
193
|
+
+ `<span style="display:inline-flex;align-items:center;color:${cat.color}">${cat.icon}</span>`
|
|
194
|
+
+ `<span style="flex:1;font-weight:600;font-size:13px;color:${cat.color}">${cat.label}</span>`
|
|
195
|
+
+ `<span style="font-weight:700;font-size:13px;color:${cat.color}">${fmt(total)}</span>`
|
|
196
|
+
+ `</div>`;
|
|
197
|
+
// 하위 단계
|
|
198
|
+
for (const p of present) {
|
|
199
|
+
const label = PHASE_LABEL[p] || p;
|
|
200
|
+
html += `<div style="display:flex;align-items:center;padding:1px 0 1px 22px">`
|
|
201
|
+
+ `<span style="flex:1;font-size:12.5px;color:rgba(232,234,237,0.82)">${label}</span>`
|
|
202
|
+
+ `<span style="font-size:12.5px;color:rgba(232,234,237,0.55)">${fmt((_b = durByPhase.get(p)) !== null && _b !== void 0 ? _b : 0)}</span>`
|
|
203
|
+
+ `</div>`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
this._bodyEl.innerHTML = html;
|
|
207
|
+
// 하부 Data Lag — 표본 있으면 표시, 없으면 숨김.
|
|
208
|
+
if (this._lagEl) {
|
|
209
|
+
if (state.lag) {
|
|
210
|
+
this._lagEl.style.display = '';
|
|
211
|
+
this._lagEl.innerHTML = lagSectionHtml(state.lag);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this._lagEl.style.display = 'none';
|
|
215
|
+
this._lagEl.innerHTML = '';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
destroy() {
|
|
220
|
+
this._card = undefined;
|
|
221
|
+
this._titleEl = undefined;
|
|
222
|
+
this._bodyEl = undefined;
|
|
223
|
+
this._lagEl = undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=scene-monitor-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-monitor-renderer.js","sourceRoot":"","sources":["../../src/scene-monitor-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * SceneMonitorRenderer — fmsim 커스텀 로딩 오버레이 표현.\n *\n * things-scene 의 LoadingOverlay 렌더러 시임(LoadingOverlayRenderer)을 구현해,\n * 로딩 단계(phaseHistory)를 Network / CPU 카테고리로 묶어 소요시간을 보여준다.\n * 코어(수명주기·데이터·M키·자동표시·타이머)는 things-scene 이 그대로 담당하고,\n * 이 클래스는 표현(DOM)만 담당한다 — 코어를 fork 하지 않는다.\n *\n * this._loadingOverlay.renderer = new SceneMonitorRenderer()\n */\n\nimport type { LoadingOverlayRenderer, LoadingOverlayState, LoadingOverlayHost, LagStats } from '@hatiolab/things-scene'\n\n/** 로딩 phase → 표시 라벨. */\nconst PHASE_LABEL: Record<string, string> = {\n fetch: 'Fetching model',\n assets: 'Loading assets',\n data: 'Waiting for data',\n parse: 'Parsing model',\n compile: 'Building components',\n render: 'Rendering',\n init: 'Initializing'\n}\n\ninterface Category {\n key: string\n label: string\n icon: string\n color: string\n phases: string[]\n}\n\n/* 인라인 SVG 아이콘 — stroke=currentColor 라 감싸는 span 색(카테고리색)으로 틴팅된다. */\nconst ICON_NETWORK =\n '<svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'\n + '<circle cx=\"12\" cy=\"12\" r=\"9\"/><line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"/>'\n + '<path d=\"M12 3a15.3 15.3 0 0 1 4 9 15.3 15.3 0 0 1-4 9 15.3 15.3 0 0 1-4-9 15.3 15.3 0 0 1 4-9z\"/></svg>'\n/** CPU = microchip(칩) — 둥근 사각 + 내부 사각 + 8핀(원본 이미지 형태). */\nconst ICON_CPU =\n '<svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'\n + '<rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\" ry=\"2\"/><rect x=\"9\" y=\"9\" width=\"6\" height=\"6\"/>'\n + '<line x1=\"9\" y1=\"1\" x2=\"9\" y2=\"4\"/><line x1=\"15\" y1=\"1\" x2=\"15\" y2=\"4\"/>'\n + '<line x1=\"9\" y1=\"20\" x2=\"9\" y2=\"23\"/><line x1=\"15\" y1=\"20\" x2=\"15\" y2=\"23\"/>'\n + '<line x1=\"20\" y1=\"9\" x2=\"23\" y2=\"9\"/><line x1=\"20\" y1=\"14\" x2=\"23\" y2=\"14\"/>'\n + '<line x1=\"1\" y1=\"9\" x2=\"4\" y2=\"9\"/><line x1=\"1\" y1=\"14\" x2=\"4\" y2=\"14\"/></svg>'\n\n/** 카테고리 정의(표시 순서). Network=파랑, CPU=주황(아이콘도 카테고리색으로 틴팅). */\nconst CATEGORIES: Category[] = [\n { key: 'network', label: 'Network', icon: ICON_NETWORK, color: '#4a9eff', phases: ['fetch', 'assets', 'data'] },\n { key: 'cpu', label: 'CPU', icon: ICON_CPU, color: '#ff9f40', phases: ['parse', 'compile', 'render', 'init'] }\n]\n\nfunction fmt(ms: number): string {\n return `${(ms / 1000).toFixed(1)}s`\n}\n\n// ── Data Lag 게이지(하부 — state.lag 있을 때만 표시) ──\n// baseline(초기 평균) 대비 현재 지연을 상태 + 부호 있는 정확한 차이로 표시한다:\n// LAG = 기준보다 지연(drift>0, 뒤처짐) → 적색(▸, level 로 진해짐)\n// LEAD = 기준보다 신선(drift<0, 따라잡음) → 녹색(◂)\n// SYNC = 기준과 동일(drift≈0, ±0ms) → 중립 회색(≈) ※ deadzone 없음, 반올림 0ms 일 때만\n// 배지 예: \"LAG +1,234ms ▸\". knob·채움·배지가 방향색을 공유.\ntype LagDirection = 'leading' | 'lagging' | 'ontrack'\nconst DIR_META: Record<LagDirection, { word: string; arrow: string; color: string }> = {\n leading: { word: 'LEAD', arrow: '◂', color: '#3ddc84' },\n lagging: { word: 'LAG', arrow: '▸', color: '#ff5a5a' },\n ontrack: { word: 'SYNC', arrow: '≈', color: '#aab0b6' }\n}\nconst LAG_LEVEL_COLORS: Record<string, string> = { warn: '#ff4040', critical: '#ff1f1f' }\nconst MIN_LAG_RADIUS_MS = 1000\n\nfunction fmtLag(ms: number): string {\n const a = Math.abs(ms)\n return a < 1000 ? `${Math.round(a)}ms` : `${(a / 1000).toFixed(1)}s`\n}\n\n/** 정확값(ms, 천단위 구분) — 반올림 손실 없이 그대로. */\nfunction fmtMs(ms: number): string {\n return `${Math.round(ms).toLocaleString()}ms`\n}\n\n/** 부호 있는 정확한 차이(drift) — +뒤처짐 / −앞섬 / ±동일. 부호는 반올림 ms 기준. */\nfunction fmtDrift(ms: number): string {\n const r = Math.round(ms)\n const sign = r > 0 ? '+' : r < 0 ? '−' : '±'\n return `${sign}${Math.abs(r).toLocaleString()}ms`\n}\n\n/** drift 부호(반올림 ms)로 삼분 — LAG(>0)/LEAD(<0)/SYNC(=0). deadzone 없음. */\nfunction lagDirection(s: LagStats): LagDirection {\n const d = Math.round(s.drift)\n return d > 0 ? 'lagging' : d < 0 ? 'leading' : 'ontrack'\n}\n\n/** 방향색 — lagging 은 level(warn/critical)로 더 진한 적색, leading 녹색, ontrack 회색. */\nfunction dirColor(direction: LagDirection, level: string): string {\n if (direction === 'lagging') return LAG_LEVEL_COLORS[level] || DIR_META.lagging.color\n return DIR_META[direction].color\n}\n\nfunction lagGauge(s: LagStats): { curPct: number; minPct: number; maxPct: number } {\n const { current, baseline, min, max } = s\n const clamp = (x: number, lo: number, hi: number) => (x < lo ? lo : x > hi ? hi : x)\n const R = Math.max(baseline - min, max - baseline, MIN_LAG_RADIUS_MS)\n const pos = (v: number) => 50 + 50 * clamp((v - baseline) / R, -1, 1)\n return { curPct: pos(current), minPct: pos(min), maxPct: pos(max) }\n}\n\nfunction lagFill(pct: number): string {\n return pct >= 50\n ? `left:50%;width:${(pct - 50).toFixed(2)}%`\n : `left:${pct.toFixed(2)}%;width:${(50 - pct).toFixed(2)}%`\n}\n\n/** 방향 배지(pill) — 색·단어·화살표 + 정확한 차이(부호 있는 drift)로 Lag/Lead 를 즉시 구분. */\nfunction dirPill(direction: LagDirection, drift: number, color: string): string {\n const m = DIR_META[direction]\n const diff = fmtDrift(drift)\n const label = direction === 'lagging' ? `${m.word} ${diff} ${m.arrow}` : `${m.arrow} ${m.word} ${diff}`\n return `<span style=\"display:inline-flex;align-items:center;padding:2px 9px;border-radius:10px;`\n + `background:${color}22;color:${color};border:1px solid ${color}66;`\n + `font-size:11px;font-weight:700;letter-spacing:0.3px;white-space:nowrap\">${label}</span>`\n}\n\n/** Data Lag 섹션 HTML — baseline 중심 게이지 + 방향 배지 + min/max 눈금 + cur 점. */\nfunction lagSectionHtml(s: LagStats): string {\n const direction = lagDirection(s)\n const { curPct, minPct, maxPct } = lagGauge(s)\n const color = dirColor(direction, s.level)\n const tick = (p: number) =>\n `<div style=\"position:absolute;left:${p}%;top:-1px;width:1px;height:8px;background:rgba(255,255,255,0.28);transform:translateX(-50%)\"></div>`\n return `<div style=\"display:flex;align-items:center;gap:8px;padding:7px 0 4px;border-top:1px solid rgba(255,255,255,0.07);margin-top:5px\">`\n + `<span style=\"color:rgba(232,234,237,0.6);font-size:12px;flex:1\">Data Lag</span>`\n + `<span style=\"color:rgba(232,234,237,0.5);font-size:11px\">cur ${fmtMs(s.current)}</span>`\n + dirPill(direction, s.drift, color)\n + `</div>`\n + `<div style=\"position:relative;height:6px;background:rgba(255,255,255,0.08);border-radius:3px;margin:3px 0 3px\">`\n + `<div style=\"position:absolute;left:50%;top:-3px;width:1px;height:12px;background:rgba(255,255,255,0.45);transform:translateX(-50%)\"></div>`\n + tick(minPct) + tick(maxPct)\n + `<div style=\"position:absolute;top:50%;height:3px;transform:translateY(-50%);border-radius:2px;background:${color};opacity:0.55;${lagFill(curPct)}\"></div>`\n + `<div style=\"position:absolute;top:50%;width:9px;height:9px;border-radius:50%;background:${color};box-shadow:0 0 0 2px rgba(0,0,0,0.35);transform:translate(-50%,-50%);left:${curPct}%\"></div>`\n + `</div>`\n + `<div style=\"display:flex;align-items:center;font-size:10px;color:rgba(232,234,237,0.4)\">`\n + `<span style=\"flex:1\">◂ ahead · min ${fmtLag(s.min)}</span>`\n + `<span style=\"flex:1;text-align:center;color:rgba(232,234,237,0.3)\">base ${fmtLag(s.baseline)} · n ${s.sampleCount}</span>`\n + `<span style=\"flex:1;text-align:right\">max ${fmtLag(s.max)} · behind ▸</span>`\n + `</div>`\n}\n\nexport class SceneMonitorRenderer implements LoadingOverlayRenderer {\n private _card?: HTMLElement\n private _titleEl?: HTMLElement\n private _bodyEl?: HTMLElement\n private _lagEl?: HTMLElement\n\n build(container: HTMLElement, host: LoadingOverlayHost): void {\n const card = document.createElement('div')\n card.style.cssText = [\n 'position:absolute',\n 'top:50%',\n 'left:50%',\n 'transform:translate(-50%,-50%)',\n 'display:flex',\n 'flex-direction:column',\n 'gap:6px',\n 'min-width:340px',\n 'max-width:460px',\n 'padding:14px 18px',\n 'background:#16191d',\n 'border:1px solid rgba(255,255,255,0.08)',\n 'border-radius:10px',\n 'box-shadow:0 8px 28px rgba(0,0,0,0.45)',\n 'pointer-events:auto',\n 'user-select:none',\n 'font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif',\n 'color:#e8eaed'\n ].join(';')\n\n // 헤더: \"Scene Monitor\" + X\n const header = document.createElement('div')\n header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding-bottom:6px;border-bottom:1px solid rgba(255,255,255,0.07)'\n\n const title = document.createElement('span')\n title.style.cssText = 'font-weight:700;font-size:15px;color:#f1f3f5'\n title.textContent = 'Scene Monitor'\n this._titleEl = title\n header.appendChild(title)\n\n const close = document.createElement('button')\n close.textContent = '✕'\n close.title = 'Close'\n close.style.cssText = 'background:none;border:none;color:rgba(255,255,255,0.55);cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;border-radius:4px'\n close.addEventListener('mouseenter', () => { close.style.color = 'rgba(255,255,255,0.95)' })\n close.addEventListener('mouseleave', () => { close.style.color = 'rgba(255,255,255,0.55)' })\n close.addEventListener('click', () => host.hide())\n header.appendChild(close)\n\n card.appendChild(header)\n\n // 본문: 카테고리별 그룹(매 tick 재구성 — 소량이라 저렴)\n const body = document.createElement('div')\n body.style.cssText = 'display:flex;flex-direction:column;gap:2px;padding-top:4px'\n this._bodyEl = body\n card.appendChild(body)\n\n // 하부 Data Lag 섹션 — state.lag 있을 때만 채워짐(없으면 숨김).\n const lag = document.createElement('div')\n lag.style.display = 'none'\n this._lagEl = lag\n card.appendChild(lag)\n\n container.appendChild(card)\n this._card = card\n }\n\n update(state: LoadingOverlayState): void {\n if (this._titleEl) {\n this._titleEl.textContent = state.isComplete ? 'Scene Monitor' : 'Scene Monitor'\n }\n if (!this._bodyEl) return\n\n // phase → duration(ms). 완료 phase 는 duration, 진행 중은 경과.\n const now = performance.now()\n const durByPhase = new Map<string, number>()\n for (const r of state.phaseHistory) {\n if (r.phase === 'complete') continue\n const d = r.duration !== undefined ? r.duration : Math.max(0, now - r.timestamp)\n durByPhase.set(r.phase, (durByPhase.get(r.phase) ?? 0) + d)\n }\n\n let html = ''\n for (const cat of CATEGORIES) {\n const present = cat.phases.filter(p => durByPhase.has(p))\n if (present.length === 0) continue\n\n const total = present.reduce((s, p) => s + (durByPhase.get(p) ?? 0), 0)\n\n // 카테고리 헤더\n html += `<div style=\"display:flex;align-items:center;gap:6px;padding:5px 0 2px\">`\n + `<span style=\"display:inline-flex;align-items:center;color:${cat.color}\">${cat.icon}</span>`\n + `<span style=\"flex:1;font-weight:600;font-size:13px;color:${cat.color}\">${cat.label}</span>`\n + `<span style=\"font-weight:700;font-size:13px;color:${cat.color}\">${fmt(total)}</span>`\n + `</div>`\n\n // 하위 단계\n for (const p of present) {\n const label = PHASE_LABEL[p] || p\n html += `<div style=\"display:flex;align-items:center;padding:1px 0 1px 22px\">`\n + `<span style=\"flex:1;font-size:12.5px;color:rgba(232,234,237,0.82)\">${label}</span>`\n + `<span style=\"font-size:12.5px;color:rgba(232,234,237,0.55)\">${fmt(durByPhase.get(p) ?? 0)}</span>`\n + `</div>`\n }\n }\n\n this._bodyEl.innerHTML = html\n\n // 하부 Data Lag — 표본 있으면 표시, 없으면 숨김.\n if (this._lagEl) {\n if (state.lag) {\n this._lagEl.style.display = ''\n this._lagEl.innerHTML = lagSectionHtml(state.lag)\n } else {\n this._lagEl.style.display = 'none'\n this._lagEl.innerHTML = ''\n }\n }\n }\n\n destroy(): void {\n this._card = undefined\n this._titleEl = undefined\n this._bodyEl = undefined\n this._lagEl = undefined\n }\n}\n"]}
|
|
@@ -6,6 +6,7 @@ import { i18next, localize } from '@operato/i18n';
|
|
|
6
6
|
import { ScrollbarStyles } from '@operato/styles';
|
|
7
7
|
import InfiniteScrollable from '@operato/utils/mixins/infinite-scrollable.js';
|
|
8
8
|
import { createBoard, fetchBoardList, fetchGroupList } from '@fmsim/api';
|
|
9
|
+
import { resolveBoardName } from '../board-i18n.js';
|
|
9
10
|
let BoardSelector = class BoardSelector extends InfiniteScrollable(localize(i18next)(LitElement)) {
|
|
10
11
|
static get styles() {
|
|
11
12
|
return [
|
|
@@ -144,7 +145,7 @@ let BoardSelector = class BoardSelector extends InfiniteScrollable(localize(i18n
|
|
|
144
145
|
${this.boards.map(board => html `
|
|
145
146
|
<div class="card" @click=${(e) => this.onClickSelect(board)} ?selected=${this.value === board.id}>
|
|
146
147
|
<img src=${board.thumbnail} />
|
|
147
|
-
<div class="name">${board
|
|
148
|
+
<div class="name">${resolveBoardName(board)}</div>
|
|
148
149
|
<div class="description">${board.description}</div>
|
|
149
150
|
</div>
|
|
150
151
|
`)}
|