Refactor simulation and viewer architecture
This commit is contained in:
180
apps/viewer/src/viewerHistoryManager.ts
Normal file
180
apps/viewer/src/viewerHistoryManager.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import * as THREE from "three";
|
||||
import { createHistoryWindowState, refreshHistoryWindow } from "./viewerHistory";
|
||||
import type { HistoryWindowState, Selectable, WorldState } from "./viewerTypes";
|
||||
|
||||
export function openHistoryWindow(
|
||||
historyWindows: HistoryWindowState[],
|
||||
historyLayerEl: HTMLDivElement,
|
||||
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(document, target, historyWindows.length, nextCounter);
|
||||
historyWindows.push(windowState);
|
||||
historyLayerEl.append(windowState.root);
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
const [removed] = historyWindows.splice(index, 1);
|
||||
removed.root.remove();
|
||||
if (historyWindowDragId === id) {
|
||||
return {
|
||||
historyWindowDragId: undefined,
|
||||
historyWindowDragPointerId: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
historyWindowDragId,
|
||||
historyWindowDragPointerId,
|
||||
};
|
||||
}
|
||||
|
||||
export function bringHistoryWindowToFront(windowState: HistoryWindowState, nextZIndex: number) {
|
||||
windowState.root.style.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,
|
||||
};
|
||||
}
|
||||
|
||||
const bounds = windowState.root.getBoundingClientRect();
|
||||
historyWindowDragOffset.set(clientX - bounds.left, clientY - bounds.top);
|
||||
windowState.root.setPointerCapture?.(pointerId);
|
||||
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;
|
||||
}
|
||||
|
||||
const width = windowState.root.offsetWidth;
|
||||
const height = windowState.root.offsetHeight;
|
||||
const left = THREE.MathUtils.clamp(clientX - historyWindowDragOffset.x, 20, window.innerWidth - width - 20);
|
||||
const top = THREE.MathUtils.clamp(clientY - historyWindowDragOffset.y, 20, window.innerHeight - height - 20);
|
||||
windowState.root.style.left = `${left}px`;
|
||||
windowState.root.style.top = `${top}px`;
|
||||
}
|
||||
|
||||
export function endHistoryWindowDrag(
|
||||
historyWindows: HistoryWindowState[],
|
||||
historyWindowDragId: string | undefined,
|
||||
historyWindowDragPointerId: number | undefined,
|
||||
pointerId: number,
|
||||
) {
|
||||
if (historyWindowDragPointerId !== pointerId || !historyWindowDragId) {
|
||||
return {
|
||||
historyWindowDragId,
|
||||
historyWindowDragPointerId,
|
||||
};
|
||||
}
|
||||
|
||||
const windowState = historyWindows.find((candidate) => candidate.id === historyWindowDragId);
|
||||
windowState?.root.releasePointerCapture?.(pointerId);
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user