Multiselection and container assets: create container from selection, template instances
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
91ee2feadd
commit
0b78382bcc
26 files changed
+897
-114
No files matched your search
+34
-4
@@ -6,11 +6,23 @@ import { deserializeProject, serializeProject } from '../core/store/serialize';
|
||||
|
||||
export type Selection =
|
||||
| { kind: 'asset'; assetId: string }
|
||||
| { kind: 'object'; sceneId: string; objectId: string }
|
||||
/**
|
||||
* One or more objects of a scene. `objectId` is the primary one (the last picked – the
|
||||
* inspector shows it); `objectIds` holds the whole selection, primary included.
|
||||
*/
|
||||
| { kind: 'object'; sceneId: string; objectId: string; objectIds: string[] }
|
||||
/** A node inside a behavior's graph. */
|
||||
| { kind: 'node'; assetId: string; nodeId: string }
|
||||
| null;
|
||||
|
||||
/** Builds an object selection; the last id is the primary one unless given explicitly. */
|
||||
export function objectSelection(sceneId: string, objectIds: readonly string[], primary?: string): Selection {
|
||||
const ids = [...new Set(objectIds)];
|
||||
if (ids.length === 0) return null;
|
||||
const objectId = primary && ids.includes(primary) ? primary : ids[ids.length - 1]!;
|
||||
return { kind: 'object', sceneId, objectId, objectIds: ids };
|
||||
}
|
||||
|
||||
export type EditorMode = 'edit' | 'play';
|
||||
export type SaveStatus = 'saved' | 'dirty' | 'saving' | 'error' | 'conflict';
|
||||
|
||||
@@ -169,19 +181,35 @@ export class EditorState {
|
||||
// --- selection -----------------------------------------------------------------------
|
||||
|
||||
select(selection: Selection): void {
|
||||
// Tolerate the short form `{ kind: 'object', sceneId, objectId }` (tests, older callers).
|
||||
if (selection?.kind === 'object' && !selection.objectIds) selection = objectSelection(selection.sceneId, [selection.objectId]);
|
||||
if (sameSelection(this.selection, selection)) return;
|
||||
this.selection = selection;
|
||||
this.emit('selection');
|
||||
}
|
||||
|
||||
/** Drops a selection whose target no longer exists (after delete/undo). */
|
||||
/** Ctrl/Shift-click: adds the object to the selection or removes it again. */
|
||||
toggleObject(sceneId: string, objectId: string): void {
|
||||
const s = this.selection;
|
||||
const current = s?.kind === 'object' && s.sceneId === sceneId ? s.objectIds : [];
|
||||
const next = current.includes(objectId) ? current.filter((id) => id !== objectId) : [...current, objectId];
|
||||
this.select(objectSelection(sceneId, next, current.includes(objectId) ? undefined : objectId));
|
||||
}
|
||||
|
||||
/** Ids of the selected scene objects (empty unless objects are selected). */
|
||||
get selectedObjectIds(): string[] {
|
||||
return this.selection?.kind === 'object' ? this.selection.objectIds : [];
|
||||
}
|
||||
|
||||
/** Drops selected targets that no longer exist (after delete/undo). */
|
||||
private validateSelection(): void {
|
||||
const s = this.selection;
|
||||
if (!s) return;
|
||||
if (s.kind === 'asset' && !this.project.assets.some((a) => a.id === s.assetId)) this.select(null);
|
||||
if (s.kind === 'object') {
|
||||
const scene = findScene(this.project, s.sceneId);
|
||||
if (!scene || !scene.objects.some((o) => o.id === s.objectId)) this.select(null);
|
||||
const alive = scene ? s.objectIds.filter((id) => scene.objects.some((o) => o.id === id)) : [];
|
||||
if (alive.length !== s.objectIds.length) this.select(objectSelection(s.sceneId, alive, s.objectId));
|
||||
}
|
||||
if (s.kind === 'node') {
|
||||
const asset = this.project.assets.find((a) => a.id === s.assetId);
|
||||
@@ -225,7 +253,9 @@ function sameSelection(a: Selection, b: Selection): boolean {
|
||||
if (a === b) return true;
|
||||
if (!a || !b || a.kind !== b.kind) return false;
|
||||
if (a.kind === 'asset' && b.kind === 'asset') return a.assetId === b.assetId;
|
||||
if (a.kind === 'object' && b.kind === 'object') return a.sceneId === b.sceneId && a.objectId === b.objectId;
|
||||
if (a.kind === 'object' && b.kind === 'object') {
|
||||
return a.sceneId === b.sceneId && a.objectId === b.objectId && a.objectIds.length === b.objectIds.length && a.objectIds.every((id, i) => id === b.objectIds[i]);
|
||||
}
|
||||
if (a.kind === 'node' && b.kind === 'node') return a.assetId === b.assetId && a.nodeId === b.nodeId;
|
||||
return false;
|
||||
}
|
||||
Reference in new issue
Block a user