feat(viewer): add Vue-based HUD, ops strip, and history window

This commit is contained in:
2026-03-19 13:49:56 -04:00
parent 710addf1f5
commit 3ca568c05d
36 changed files with 2648 additions and 1017 deletions

View File

@@ -0,0 +1,3 @@
import { createPinia } from "pinia";
export const viewerPinia = createPinia();

View File

@@ -0,0 +1,108 @@
import { defineStore } from "pinia";
import type { Selectable } from "../../viewerTypes";
export type ViewerSelectionSource = "viewer" | "ui" | null;
export interface ViewerSelectionSummary {
id: string;
kind: Selectable["kind"];
label?: string | null;
}
export function selectionToEntityId(selection: Selectable): string {
switch (selection.kind) {
case "planet":
return `${selection.systemId}:${selection.planetIndex}`;
case "moon":
return `${selection.systemId}:${selection.planetIndex}:${selection.moonIndex}`;
default:
return selection.id;
}
}
export function entityIdToSelectable(
kind: Selectable["kind"] | null,
entityId: string | null,
): Selectable | null {
if (!kind || !entityId) {
return null;
}
if (kind === "planet") {
const [systemId, planetIndex] = entityId.split(":");
if (!systemId || planetIndex == null) {
return null;
}
return {
kind,
systemId,
planetIndex: Number(planetIndex),
};
}
if (kind === "moon") {
const [systemId, planetIndex, moonIndex] = entityId.split(":");
if (!systemId || planetIndex == null || moonIndex == null) {
return null;
}
return {
kind,
systemId,
planetIndex: Number(planetIndex),
moonIndex: Number(moonIndex),
};
}
return {
kind,
id: entityId,
} as Selectable;
}
export const useViewerSelectionStore = defineStore("viewerSelection", {
state: () => ({
selectedEntityId: null as string | null,
selectedEntityKind: null as Selectable["kind"] | null,
selectedEntityLabel: null as string | null,
hoveredEntityId: null as string | null,
inspectedEntityId: null as string | null,
selectionSource: null as ViewerSelectionSource,
}),
actions: {
selectEntity(id: string | null, source: ViewerSelectionSource = null) {
this.selectedEntityId = id;
this.selectionSource = source;
if (id == null) {
this.selectedEntityKind = null;
this.selectedEntityLabel = null;
}
},
selectSelection(selection: ViewerSelectionSummary | null, source: ViewerSelectionSource = null) {
if (!selection) {
this.clearSelection(source);
return;
}
this.selectedEntityId = selection.id;
this.selectedEntityKind = selection.kind;
this.selectedEntityLabel = selection.label ?? null;
this.selectionSource = source;
},
clearSelection(source: ViewerSelectionSource = null) {
this.selectedEntityId = null;
this.selectedEntityKind = null;
this.selectedEntityLabel = null;
this.selectionSource = source;
},
hoverEntity(id: string | null) {
this.hoveredEntityId = id;
},
inspectEntity(id: string | null) {
this.inspectedEntityId = id;
},
},
});
export type ViewerSelectionStore = ReturnType<typeof useViewerSelectionStore>;