Adds right-click context menu for canvas with full operation support: **Clipboard Operations:** - Cut/Copy/Paste with selection mask support - Browser clipboard API integration for external images - Internal clipboard buffer for canvas selections - Toast notifications for user feedback **Selection Operations:** - Select All - creates full canvas selection with proper mask - Deselect - clears active selection - Selection state properly integrated with canvas operations **Layer Operations:** - New Layer - creates layer with history support - Duplicate Layer - clones active layer - Merge Down - merges layer with one below **Transform Operations:** - Rotate 90° CW - rotates active layer clockwise - Flip Horizontal - mirrors layer horizontally - Flip Vertical - mirrors layer vertically - All transforms preserve image quality and support undo/redo **Edit Operations:** - Undo/Redo - integrated with history system - Disabled states for unavailable operations - Context-aware menu items **New Files Created:** - lib/clipboard-operations.ts - Cut/copy/paste implementation - lib/canvas-operations.ts - Rotate/flip canvas functions **Modified Files:** - components/canvas/canvas-with-tools.tsx - Context menu integration - store/selection-store.ts - Added selectAll() method - core/commands/index.ts - Export all command types **Technical Improvements:** - Proper Selection type structure with mask/bounds - History command integration for all operations - Lazy-loaded operations for performance - Toast feedback for all user actions - Full TypeScript type safety All operations work with undo/redo and maintain app state consistency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
111 lines
2.5 KiB
TypeScript
111 lines
2.5 KiB
TypeScript
import { create } from 'zustand';
|
|
import type {
|
|
Selection,
|
|
SelectionType,
|
|
SelectionMode,
|
|
SelectionState,
|
|
} from '@/types/selection';
|
|
|
|
interface SelectionStore extends SelectionState {
|
|
setActiveSelection: (selection: Selection | null) => void;
|
|
setSelectionType: (type: SelectionType) => void;
|
|
setSelectionMode: (mode: SelectionMode) => void;
|
|
setFeather: (feather: number) => void;
|
|
setTolerance: (tolerance: number) => void;
|
|
setMarching: (isMarching: boolean) => void;
|
|
clearSelection: () => void;
|
|
selectAll: () => void;
|
|
invertSelection: () => void;
|
|
}
|
|
|
|
export const useSelectionStore = create<SelectionStore>((set) => ({
|
|
activeSelection: null,
|
|
selectionType: 'rectangular',
|
|
selectionMode: 'new',
|
|
feather: 0,
|
|
tolerance: 32,
|
|
isMarching: true,
|
|
|
|
setActiveSelection: (selection) =>
|
|
set({
|
|
activeSelection: selection,
|
|
}),
|
|
|
|
setSelectionType: (type) =>
|
|
set({
|
|
selectionType: type,
|
|
}),
|
|
|
|
setSelectionMode: (mode) =>
|
|
set({
|
|
selectionMode: mode,
|
|
}),
|
|
|
|
setFeather: (feather) =>
|
|
set({
|
|
feather: Math.max(0, Math.min(250, feather)),
|
|
}),
|
|
|
|
setTolerance: (tolerance) =>
|
|
set({
|
|
tolerance: Math.max(0, Math.min(255, tolerance)),
|
|
}),
|
|
|
|
setMarching: (isMarching) =>
|
|
set({
|
|
isMarching,
|
|
}),
|
|
|
|
clearSelection: () =>
|
|
set({
|
|
activeSelection: null,
|
|
}),
|
|
|
|
selectAll: () =>
|
|
set(() => {
|
|
const { useCanvasStore, useLayerStore } = require('@/store');
|
|
const { width, height } = useCanvasStore.getState();
|
|
const { getActiveLayer } = useLayerStore.getState();
|
|
const activeLayer = getActiveLayer();
|
|
|
|
if (!activeLayer) return {};
|
|
|
|
// Create a mask that covers the entire canvas
|
|
const maskData = new Uint8Array(width * height).fill(255);
|
|
|
|
return {
|
|
activeSelection: {
|
|
id: `selection-${Date.now()}`,
|
|
layerId: activeLayer.id,
|
|
mask: {
|
|
width,
|
|
height,
|
|
data: maskData,
|
|
bounds: {
|
|
x: 0,
|
|
y: 0,
|
|
width,
|
|
height,
|
|
},
|
|
},
|
|
inverted: false,
|
|
feather: 0,
|
|
createdAt: Date.now(),
|
|
},
|
|
};
|
|
}),
|
|
|
|
invertSelection: () =>
|
|
set((state) => {
|
|
if (state.activeSelection) {
|
|
return {
|
|
activeSelection: {
|
|
...state.activeSelection,
|
|
inverted: !state.activeSelection.inverted,
|
|
},
|
|
};
|
|
}
|
|
return state;
|
|
}),
|
|
}));
|