feat: Introduce new LayoutSystem and spatial workspace architecture design document, refactor document card and workspace to use it, and simplify pointer event handling.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
export interface LayoutItem {
|
||||
import { constrainDimensions } from './utils/layoutUtils';
|
||||
|
||||
export interface LayoutCardState {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -6,79 +8,108 @@ export interface LayoutItem {
|
||||
rotation: number;
|
||||
width: number;
|
||||
height: number;
|
||||
ref: HTMLElement;
|
||||
}
|
||||
|
||||
export class LayoutCard implements LayoutCardState {
|
||||
id: string;
|
||||
x: number = 0;
|
||||
y: number = 0;
|
||||
z: number = 0;
|
||||
rotation: number = 0;
|
||||
width: number = 0;
|
||||
height: number = 0;
|
||||
ref: HTMLElement | null = null;
|
||||
|
||||
constructor(id: string, initialData: Partial<LayoutCardState> = {}, ref: HTMLElement | null = null) {
|
||||
this.id = id;
|
||||
Object.assign(this, initialData);
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
setRef(ref: HTMLElement | null) {
|
||||
this.ref = ref;
|
||||
this.applyTransform();
|
||||
}
|
||||
|
||||
update(changes: Partial<LayoutCardState>) {
|
||||
Object.assign(this, changes);
|
||||
this.applyTransform();
|
||||
}
|
||||
|
||||
private applyTransform() {
|
||||
if (this.ref) {
|
||||
this.ref.style.transform =
|
||||
`translate3d(${this.x}px, ${this.y}px, 0) rotate(${this.rotation}deg)`;
|
||||
this.ref.style.zIndex = String(this.z);
|
||||
this.ref.style.width = `${this.width}px`;
|
||||
this.ref.style.height = `${this.height}px`;
|
||||
}
|
||||
}
|
||||
|
||||
toSnapshot(): LayoutCardState {
|
||||
return {
|
||||
id: this.id,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
z: this.z,
|
||||
rotation: this.rotation,
|
||||
width: this.width,
|
||||
height: this.height
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class LayoutStore {
|
||||
items = new Map<string, LayoutItem>();
|
||||
items = new Map<string, LayoutCard>();
|
||||
zCounter = 100;
|
||||
|
||||
register(id: string, ref: HTMLElement, initialData: Partial<LayoutItem>) {
|
||||
const existing = this.items.get(id);
|
||||
this.items.set(id, {
|
||||
id,
|
||||
ref,
|
||||
x: existing?.x ?? 0,
|
||||
y: existing?.y ?? 0,
|
||||
z: existing?.z ?? 0,
|
||||
rotation: existing?.rotation ?? 0,
|
||||
width: 200,
|
||||
height: 200,
|
||||
...initialData
|
||||
});
|
||||
}
|
||||
|
||||
initialize(id: string, ref: HTMLElement, config: {
|
||||
x?: number;
|
||||
y?: number;
|
||||
rotation?: number;
|
||||
z: number;
|
||||
initialize(id: string, ref: HTMLElement | null, config: {
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
if (this.items.has(id)) {
|
||||
// Update ref if it changed
|
||||
const item = this.items.get(id)!;
|
||||
if (item.ref !== ref) {
|
||||
item.ref = ref;
|
||||
this.update(id, {}); // Re-apply styles
|
||||
}
|
||||
return;
|
||||
let card = this.items.get(id);
|
||||
|
||||
const { width, height } = constrainDimensions(
|
||||
config.width,
|
||||
config.height,
|
||||
280
|
||||
);
|
||||
|
||||
if (!card) {
|
||||
// Apply defaults if not provided
|
||||
const x = Math.random() * 500;
|
||||
const y = Math.random() * 500;
|
||||
const rotation = Math.random() * 10 - 5;
|
||||
|
||||
card = new LayoutCard(id, {
|
||||
x,
|
||||
y,
|
||||
rotation,
|
||||
width,
|
||||
height
|
||||
}, ref);
|
||||
this.items.set(id, card);
|
||||
} else {
|
||||
card.update({ width, height });
|
||||
}
|
||||
|
||||
// Apply defaults if not provided
|
||||
const x = config.x ?? Math.random() * 500;
|
||||
const y = config.y ?? Math.random() * 500;
|
||||
const rotation = config.rotation ?? (Math.random() * 10 - 5);
|
||||
// Always update ref and ensure transform is applied
|
||||
if (card.ref !== ref) {
|
||||
card.setRef(ref);
|
||||
}
|
||||
|
||||
this.register(id, ref, {
|
||||
...config,
|
||||
x,
|
||||
y,
|
||||
rotation
|
||||
});
|
||||
|
||||
// Apply immediately
|
||||
this.update(id, {});
|
||||
return card;
|
||||
}
|
||||
|
||||
unregister(id: string) {
|
||||
this.items.delete(id);
|
||||
}
|
||||
|
||||
// Fast Update: Updates internal state AND applies CSS transform immediately
|
||||
update(id: string, updates: Partial<LayoutItem>) {
|
||||
const item = this.items.get(id);
|
||||
if (!item) return;
|
||||
|
||||
Object.assign(item, updates);
|
||||
if (updates.z) this.zCounter = Math.max(this.zCounter, updates.z);
|
||||
|
||||
// Direct DOM manipulation (The "Engine" part)
|
||||
if (item.ref) {
|
||||
item.ref.style.transform =
|
||||
`translate3d(${item.x}px, ${item.y}px, 0) rotate(${item.rotation}deg)`;
|
||||
item.ref.style.zIndex = String(item.z);
|
||||
update(id: string, updates: Partial<LayoutCardState>) {
|
||||
const card = this.items.get(id);
|
||||
if (card) {
|
||||
if (updates.z) this.zCounter = Math.max(this.zCounter, updates.z);
|
||||
card.update(updates);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +118,6 @@ export class LayoutStore {
|
||||
}
|
||||
|
||||
getSnapshot() {
|
||||
// Return serializable data for persistence
|
||||
return Array.from(this.items.values()).map(({ ref: _ref, ...data }) => data);
|
||||
return Array.from(this.items.values()).map(card => card.toSnapshot());
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton or Context-provided instance
|
||||
export const globalLayout = new LayoutStore();
|
||||
|
||||
Reference in New Issue
Block a user