feat(phase-13): implement layer groups/folders system

Add comprehensive layer groups system for organizing layers hierarchically.

Features:
- Create layer groups (folders)
- Add layers to groups
- Remove layers from groups
- Toggle group collapsed/expanded state
- Get all layers in a group
- Group properties:
  * groupId: Parent group ID (null if not in group)
  * isGroup: Whether layer is a group
  * collapsed: Whether group is collapsed
- Groups are special layers with isGroup=true
- Groups have no canvas (width/height = 0)
- Groups can contain multiple layers
- Layers track their parent group via groupId

Changes:
- Updated types/layer.ts with group properties:
  * groupId: string | null
  * isGroup: boolean
  * collapsed: boolean
- Updated store/layer-store.ts:
  * createLayer initializes group properties
  * createGroup() - Create new group
  * addToGroup() - Add layer to group
  * removeFromGroup() - Remove from group
  * toggleGroupCollapsed() - Toggle collapsed
  * getGroupLayers() - Get group's layers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-21 20:29:20 +01:00
parent bd6fd22522
commit ad87b86c0f
2 changed files with 74 additions and 0 deletions

View File

@@ -40,6 +40,16 @@ interface LayerStore {
invertMask: (id: string) => void;
/** Apply mask (merge and remove) */
applyMask: (id: string) => void;
/** Create a layer group */
createGroup: (name: string) => Layer;
/** Add layer to group */
addToGroup: (layerId: string, groupId: string) => void;
/** Remove layer from group */
removeFromGroup: (layerId: string) => void;
/** Toggle group collapsed state */
toggleGroupCollapsed: (groupId: string) => void;
/** Get layers in a group */
getGroupLayers: (groupId: string) => Layer[];
}
export const useLayerStore = create<LayerStore>((set, get) => ({
@@ -62,6 +72,9 @@ export const useLayerStore = create<LayerStore>((set, get) => ({
x: params.x ?? 0,
y: params.y ?? 0,
mask: null,
groupId: null,
isGroup: false,
collapsed: false,
createdAt: now,
updatedAt: now,
};
@@ -350,4 +363,59 @@ export const useLayerStore = create<LayerStore>((set, get) => ({
// Remove mask
get().removeMask(id);
},
createGroup: (name) => {
const now = Date.now();
const group: Layer = {
id: uuidv4(),
name: name || `Group ${get().layers.filter(l => l.isGroup).length + 1}`,
canvas: null,
visible: true,
opacity: 1,
blendMode: 'normal',
order: get().layers.length,
locked: false,
width: 0,
height: 0,
x: 0,
y: 0,
mask: null,
groupId: null,
isGroup: true,
collapsed: false,
createdAt: now,
updatedAt: now,
};
set((state) => ({
layers: [...state.layers, group],
activeLayerId: group.id,
}));
return group;
},
addToGroup: (layerId, groupId) => {
const layer = get().getLayer(layerId);
const group = get().getLayer(groupId);
if (!layer || !group || !group.isGroup) return;
get().updateLayer(layerId, { groupId });
},
removeFromGroup: (layerId) => {
get().updateLayer(layerId, { groupId: null });
},
toggleGroupCollapsed: (groupId) => {
const group = get().getLayer(groupId);
if (!group || !group.isGroup) return;
get().updateLayer(groupId, { collapsed: !group.collapsed });
},
getGroupLayers: (groupId) => {
return get().layers.filter(l => l.groupId === groupId);
},
}));

View File

@@ -59,6 +59,12 @@ export interface Layer {
y: number;
/** Layer mask for non-destructive editing */
mask: LayerMask | null;
/** Parent group ID (null if not in a group) */
groupId: string | null;
/** Whether this layer is a group */
isGroup: boolean;
/** Whether group is collapsed (only relevant if isGroup=true) */
collapsed: boolean;
/** Timestamp of creation */
createdAt: number;
/** Timestamp of last modification */