import * as THREE from "three"; import { createHistoryWindowState, refreshHistoryWindow } from "./viewerHistory"; import type { HistoryWindowState } from "./viewerHudState"; import type { Selectable, WorldState } from "./viewerTypes"; export function openHistoryWindow( historyWindows: HistoryWindowState[], target: Selectable, nextCounter: number, bringToFront: (windowState: HistoryWindowState) => void, refreshWindows: () => void, ) { const existing = historyWindows.find((windowState) => JSON.stringify(windowState.target) === JSON.stringify(target)); if (existing) { bringToFront(existing); refreshWindows(); return nextCounter; } const windowState = createHistoryWindowState(target, historyWindows.length, nextCounter); historyWindows.push(windowState); bringToFront(windowState); refreshWindows(); return nextCounter; } export function refreshHistoryWindows( world: WorldState | undefined, historyWindows: HistoryWindowState[], renderRecentEvents: (entityKind: string, entityId: string) => string, destroyWindow: (id: string) => void, ) { if (!world) { return; } for (const windowState of [...historyWindows]) { if (!refreshHistoryWindow(world, windowState, renderRecentEvents)) { destroyWindow(windowState.id); } } } export function destroyHistoryWindow( historyWindows: HistoryWindowState[], historyWindowDragId: string | undefined, historyWindowDragPointerId: number | undefined, id: string, ) { const index = historyWindows.findIndex((windowState) => windowState.id === id); if (index < 0) { return { historyWindowDragId, historyWindowDragPointerId, }; } historyWindows.splice(index, 1); if (historyWindowDragId === id) { return { historyWindowDragId: undefined, historyWindowDragPointerId: undefined, }; } return { historyWindowDragId, historyWindowDragPointerId, }; } export function bringHistoryWindowToFront(windowState: HistoryWindowState, nextZIndex: number) { windowState.zIndex = nextZIndex; } export function beginHistoryWindowDrag( historyWindows: HistoryWindowState[], historyWindowDragOffset: THREE.Vector2, pointerId: number, windowId: string, clientX: number, clientY: number, ) { const windowState = historyWindows.find((candidate) => candidate.id === windowId); if (!windowState) { return { historyWindowDragId: undefined, historyWindowDragPointerId: undefined, }; } historyWindowDragOffset.set(clientX - windowState.x, clientY - windowState.y); return { historyWindowDragId: windowId, historyWindowDragPointerId: pointerId, }; } export function updateHistoryWindowDrag( historyWindows: HistoryWindowState[], historyWindowDragId: string | undefined, historyWindowDragPointerId: number | undefined, historyWindowDragOffset: THREE.Vector2, pointerId: number, clientX: number, clientY: number, ) { if (historyWindowDragPointerId !== pointerId || !historyWindowDragId) { return; } const windowState = historyWindows.find((candidate) => candidate.id === historyWindowDragId); if (!windowState) { return; } windowState.x = THREE.MathUtils.clamp(clientX - historyWindowDragOffset.x, 20, window.innerWidth - windowState.width - 20); windowState.y = THREE.MathUtils.clamp(clientY - historyWindowDragOffset.y, 20, window.innerHeight - windowState.height - 20); } export function endHistoryWindowDrag( _historyWindows: HistoryWindowState[], historyWindowDragId: string | undefined, historyWindowDragPointerId: number | undefined, pointerId: number, ) { if (historyWindowDragPointerId !== pointerId || !historyWindowDragId) { return { historyWindowDragId, historyWindowDragPointerId, }; } return { historyWindowDragId: undefined, historyWindowDragPointerId: undefined, }; } export async function copyTextToClipboard(text: string) { if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text); return; } catch { } } const textarea = document.createElement("textarea"); textarea.value = text; textarea.setAttribute("readonly", "true"); textarea.style.position = "fixed"; textarea.style.top = "0"; textarea.style.left = "0"; textarea.style.width = "1px"; textarea.style.height = "1px"; textarea.style.opacity = "0"; document.body.append(textarea); textarea.focus(); textarea.select(); try { const copied = document.execCommand("copy"); if (!copied) { throw new Error("execCommand copy failed"); } } finally { textarea.remove(); } }