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:
@@ -40,6 +40,16 @@ interface LayerStore {
|
|||||||
invertMask: (id: string) => void;
|
invertMask: (id: string) => void;
|
||||||
/** Apply mask (merge and remove) */
|
/** Apply mask (merge and remove) */
|
||||||
applyMask: (id: string) => void;
|
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) => ({
|
export const useLayerStore = create<LayerStore>((set, get) => ({
|
||||||
@@ -62,6 +72,9 @@ export const useLayerStore = create<LayerStore>((set, get) => ({
|
|||||||
x: params.x ?? 0,
|
x: params.x ?? 0,
|
||||||
y: params.y ?? 0,
|
y: params.y ?? 0,
|
||||||
mask: null,
|
mask: null,
|
||||||
|
groupId: null,
|
||||||
|
isGroup: false,
|
||||||
|
collapsed: false,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
};
|
};
|
||||||
@@ -350,4 +363,59 @@ export const useLayerStore = create<LayerStore>((set, get) => ({
|
|||||||
// Remove mask
|
// Remove mask
|
||||||
get().removeMask(id);
|
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);
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ export interface Layer {
|
|||||||
y: number;
|
y: number;
|
||||||
/** Layer mask for non-destructive editing */
|
/** Layer mask for non-destructive editing */
|
||||||
mask: LayerMask | null;
|
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 */
|
/** Timestamp of creation */
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
/** Timestamp of last modification */
|
/** Timestamp of last modification */
|
||||||
|
|||||||
Reference in New Issue
Block a user