From ad87b86c0fac0551cde00678cfb66221b1ad0e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Fri, 21 Nov 2025 20:29:20 +0100 Subject: [PATCH] feat(phase-13): implement layer groups/folders system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- store/layer-store.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++ types/layer.ts | 6 ++++ 2 files changed, 74 insertions(+) diff --git a/store/layer-store.ts b/store/layer-store.ts index 102fcd6..fbf3dd0 100644 --- a/store/layer-store.ts +++ b/store/layer-store.ts @@ -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((set, get) => ({ @@ -62,6 +72,9 @@ export const useLayerStore = create((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((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); + }, })); diff --git a/types/layer.ts b/types/layer.ts index 1f6cadc..e4915db 100644 --- a/types/layer.ts +++ b/types/layer.ts @@ -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 */