2979d06f1c
Align control/presentation with presentation screen rect and darkness z-order; sync window titles and session window cleanup. Pack Win/Mac/Linux with npmRebuild disabled and release-native-prep for classic-level and sharp. Co-authored-by: Cursor <cursoragent@cursor.com>
229 lines
7.5 KiB
TypeScript
229 lines
7.5 KiB
TypeScript
import {
|
|
clampMaterialsLayout,
|
|
DEFAULT_MATERIALS_OVERLAY_LAYOUT,
|
|
defaultLegendLayoutForMaterial,
|
|
type MaterialId,
|
|
type MaterialsOverlayEvent,
|
|
type MaterialsOverlayLayout,
|
|
type MaterialsOverlayState,
|
|
type MaterialsZoomTool,
|
|
zoomMaterialsLayoutAt,
|
|
} from '../../shared/types';
|
|
|
|
function emptyState(): MaterialsOverlayState {
|
|
return {
|
|
revision: 1,
|
|
activeMaterialIds: [],
|
|
layouts: {},
|
|
legendLayouts: {},
|
|
focusMaterialId: null,
|
|
zoomTool: null,
|
|
};
|
|
}
|
|
|
|
function initialLayout(rotationDeg?: number): MaterialsOverlayLayout {
|
|
return clampMaterialsLayout({
|
|
...DEFAULT_MATERIALS_OVERLAY_LAYOUT,
|
|
rotationDeg: rotationDeg ?? 0,
|
|
});
|
|
}
|
|
|
|
function layoutFor(state: MaterialsOverlayState, materialId: MaterialId): MaterialsOverlayLayout {
|
|
return state.layouts[materialId] ?? { ...DEFAULT_MATERIALS_OVERLAY_LAYOUT };
|
|
}
|
|
|
|
function legendLayoutFor(state: MaterialsOverlayState, materialId: MaterialId): MaterialsOverlayLayout {
|
|
return state.legendLayouts[materialId] ?? defaultLegendLayoutForMaterial();
|
|
}
|
|
|
|
export class MaterialsOverlayStore {
|
|
private state: MaterialsOverlayState = emptyState();
|
|
|
|
getState(): MaterialsOverlayState {
|
|
return this.state;
|
|
}
|
|
|
|
clear(): MaterialsOverlayState {
|
|
if (this.state.activeMaterialIds.length === 0 && this.state.zoomTool === null) {
|
|
return this.state;
|
|
}
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialIds: [],
|
|
layouts: {},
|
|
legendLayouts: {},
|
|
focusMaterialId: null,
|
|
zoomTool: null,
|
|
};
|
|
return this.state;
|
|
}
|
|
|
|
dispatch(event: MaterialsOverlayEvent): MaterialsOverlayState {
|
|
switch (event.kind) {
|
|
case 'hide':
|
|
return this.clear();
|
|
case 'show': {
|
|
if (this.state.activeMaterialIds.includes(event.materialId)) {
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
focusMaterialId: event.materialId,
|
|
};
|
|
return this.state;
|
|
}
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialIds: [...this.state.activeMaterialIds, event.materialId],
|
|
layouts: {
|
|
...this.state.layouts,
|
|
[event.materialId]: initialLayout(event.rotationDeg),
|
|
},
|
|
legendLayouts: {
|
|
...this.state.legendLayouts,
|
|
[event.materialId]: defaultLegendLayoutForMaterial(),
|
|
},
|
|
focusMaterialId: event.materialId,
|
|
zoomTool: this.state.zoomTool,
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'toggle': {
|
|
if (this.state.activeMaterialIds.includes(event.materialId)) {
|
|
const activeMaterialIds = this.state.activeMaterialIds.filter((id) => id !== event.materialId);
|
|
const layouts = { ...this.state.layouts };
|
|
const legendLayouts = { ...this.state.legendLayouts };
|
|
delete layouts[event.materialId];
|
|
delete legendLayouts[event.materialId];
|
|
const focusMaterialId =
|
|
this.state.focusMaterialId === event.materialId
|
|
? (activeMaterialIds[activeMaterialIds.length - 1] ?? null)
|
|
: this.state.focusMaterialId;
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialIds,
|
|
layouts,
|
|
legendLayouts,
|
|
focusMaterialId,
|
|
zoomTool: activeMaterialIds.length === 0 ? null : this.state.zoomTool,
|
|
};
|
|
return this.state;
|
|
}
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialIds: [...this.state.activeMaterialIds, event.materialId],
|
|
layouts: {
|
|
...this.state.layouts,
|
|
[event.materialId]: initialLayout(event.rotationDeg),
|
|
},
|
|
legendLayouts: {
|
|
...this.state.legendLayouts,
|
|
[event.materialId]: defaultLegendLayoutForMaterial(),
|
|
},
|
|
focusMaterialId: event.materialId,
|
|
zoomTool: this.state.zoomTool,
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'layout.set': {
|
|
if (!this.state.activeMaterialIds.includes(event.materialId)) return this.state;
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
focusMaterialId: event.materialId,
|
|
layouts: {
|
|
...this.state.layouts,
|
|
[event.materialId]: clampMaterialsLayout({
|
|
...layoutFor(this.state, event.materialId),
|
|
...event.layout,
|
|
}),
|
|
},
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'legendLayout.set': {
|
|
if (!this.state.activeMaterialIds.includes(event.materialId)) return this.state;
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
focusMaterialId: event.materialId,
|
|
legendLayouts: {
|
|
...this.state.legendLayouts,
|
|
[event.materialId]: clampMaterialsLayout({
|
|
...legendLayoutFor(this.state, event.materialId),
|
|
...event.layout,
|
|
rotationDeg: 0,
|
|
}),
|
|
},
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'zoomTool.set': {
|
|
const tool: MaterialsZoomTool = event.tool;
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
zoomTool: tool,
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'zoomAt': {
|
|
if (this.state.activeMaterialIds.length === 0 || !this.state.zoomTool) return this.state;
|
|
const targetId =
|
|
(event.materialId && this.state.activeMaterialIds.includes(event.materialId)
|
|
? event.materialId
|
|
: null) ??
|
|
this.state.focusMaterialId ??
|
|
this.state.activeMaterialIds[this.state.activeMaterialIds.length - 1] ??
|
|
null;
|
|
if (!targetId) return this.state;
|
|
const factor = this.state.zoomTool === 'zoomIn' ? 1.25 : 1 / 1.25;
|
|
const layout: MaterialsOverlayLayout = zoomMaterialsLayoutAt(
|
|
layoutFor(this.state, targetId),
|
|
event.nx,
|
|
event.ny,
|
|
factor,
|
|
);
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
focusMaterialId: targetId,
|
|
layouts: {
|
|
...this.state.layouts,
|
|
[targetId]: layout,
|
|
},
|
|
};
|
|
return this.state;
|
|
}
|
|
default:
|
|
return this.state;
|
|
}
|
|
}
|
|
|
|
ensureMaterialStillExists(materialIds: ReadonlySet<MaterialId>): MaterialsOverlayState {
|
|
const activeMaterialIds = this.state.activeMaterialIds.filter((id) => materialIds.has(id));
|
|
if (activeMaterialIds.length === this.state.activeMaterialIds.length) return this.state;
|
|
if (activeMaterialIds.length === 0) return this.clear();
|
|
const layouts: Record<string, MaterialsOverlayLayout> = {};
|
|
const legendLayouts: Record<string, MaterialsOverlayLayout> = {};
|
|
for (const id of activeMaterialIds) {
|
|
const layout = this.state.layouts[id];
|
|
if (layout) layouts[id] = layout;
|
|
const legend = this.state.legendLayouts[id];
|
|
if (legend) legendLayouts[id] = legend;
|
|
}
|
|
const focusMaterialId =
|
|
this.state.focusMaterialId && activeMaterialIds.includes(this.state.focusMaterialId)
|
|
? this.state.focusMaterialId
|
|
: (activeMaterialIds[activeMaterialIds.length - 1] ?? null);
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialIds,
|
|
layouts,
|
|
legendLayouts,
|
|
focusMaterialId,
|
|
zoomTool: this.state.zoomTool,
|
|
};
|
|
return this.state;
|
|
}
|
|
}
|