import * as THREE from "three"; import { beginHistoryWindowDrag, bringHistoryWindowToFront, copyTextToClipboard, destroyHistoryWindow, endHistoryWindowDrag, openHistoryWindow, refreshHistoryWindows, updateHistoryWindowDrag, } from "./viewerHistoryManager"; import type { HistoryWindowState } from "./viewerHudState"; import type { Selectable, WorldState } from "./viewerTypes"; export interface ViewerHistoryWindowContext { historyWindows: HistoryWindowState[]; getWorld: () => WorldState | undefined; getHistoryWindowCounter: () => number; setHistoryWindowCounter: (value: number) => void; getHistoryWindowZCounter: () => number; setHistoryWindowZCounter: (value: number) => void; getHistoryWindowDragId: () => string | undefined; setHistoryWindowDragId: (value: string | undefined) => void; getHistoryWindowDragPointerId: () => number | undefined; setHistoryWindowDragPointerId: (value: number | undefined) => void; historyWindowDragOffset: THREE.Vector2; renderRecentEvents: (entityKind: string, entityId: string) => string; } export class ViewerHistoryWindowController { constructor(private readonly context: ViewerHistoryWindowContext) {} openHistoryWindow(target: Selectable) { const nextCounter = openHistoryWindow( this.context.historyWindows, target, this.context.getHistoryWindowCounter() + 1, (windowState) => this.bringHistoryWindowToFront(windowState), () => this.refreshHistoryWindows(), ); this.context.setHistoryWindowCounter(nextCounter); } refreshHistoryWindows() { refreshHistoryWindows( this.context.getWorld(), this.context.historyWindows, this.context.renderRecentEvents, (id) => this.destroyHistoryWindow(id), ); } readonly onHistoryLayerClick = (event: MouseEvent) => { const target = event.target; if (!(target instanceof HTMLElement)) { return; } const windowEl = target.closest("[data-history-window-id]"); const windowId = windowEl?.dataset.historyWindowId; if (!windowId) { return; } if (target.closest(".history-window-copy")) { void this.copyHistoryWindowContent(windowId); return; } if (target.closest(".history-window-close")) { this.destroyHistoryWindow(windowId); return; } const windowState = this.context.historyWindows.find((candidate) => candidate.id === windowId); if (windowState) { this.bringHistoryWindowToFront(windowState); } }; readonly onHistoryLayerPointerDown = (event: PointerEvent) => { const target = event.target; if (!(target instanceof HTMLElement)) { return; } const windowEl = target.closest("[data-history-window-id]"); const windowId = windowEl?.dataset.historyWindowId; if (!windowEl || !windowId) { return; } const windowState = this.context.historyWindows.find((candidate) => candidate.id === windowId); if (!windowState) { return; } this.bringHistoryWindowToFront(windowState); if (!target.closest(".history-window-header") || target.closest("button")) { return; } const nextState = beginHistoryWindowDrag( this.context.historyWindows, this.context.historyWindowDragOffset, event.pointerId, windowId, event.clientX, event.clientY, ); this.context.setHistoryWindowDragId(nextState.historyWindowDragId); this.context.setHistoryWindowDragPointerId(nextState.historyWindowDragPointerId); }; readonly onHistoryWindowPointerMove = (event: PointerEvent) => { updateHistoryWindowDrag( this.context.historyWindows, this.context.getHistoryWindowDragId(), this.context.getHistoryWindowDragPointerId(), this.context.historyWindowDragOffset, event.pointerId, event.clientX, event.clientY, ); }; readonly onHistoryWindowPointerUp = (event: PointerEvent) => { const nextState = endHistoryWindowDrag( this.context.historyWindows, this.context.getHistoryWindowDragId(), this.context.getHistoryWindowDragPointerId(), event.pointerId, ); this.context.setHistoryWindowDragId(nextState.historyWindowDragId); this.context.setHistoryWindowDragPointerId(nextState.historyWindowDragPointerId); }; private destroyHistoryWindow(id: string) { const nextState = destroyHistoryWindow( this.context.historyWindows, this.context.getHistoryWindowDragId(), this.context.getHistoryWindowDragPointerId(), id, ); this.context.setHistoryWindowDragId(nextState.historyWindowDragId); this.context.setHistoryWindowDragPointerId(nextState.historyWindowDragPointerId); } private async copyHistoryWindowContent(windowId: string) { const windowState = this.context.historyWindows.find((candidate) => candidate.id === windowId); if (!windowState?.text) { return; } try { await copyTextToClipboard(windowState.text); windowState.copyLabel = "Copied"; window.setTimeout(() => { windowState.copyLabel = "Copy"; }, 1200); } catch { windowState.copyLabel = "Failed"; window.setTimeout(() => { windowState.copyLabel = "Copy"; }, 1200); } } private bringHistoryWindowToFront(windowState: HistoryWindowState) { const nextZIndex = this.context.getHistoryWindowZCounter() + 1; this.context.setHistoryWindowZCounter(nextZIndex); bringHistoryWindowToFront(windowState, nextZIndex); } }