Merge branch 'main' into claude/add-eye-detection-feature-69XOl

This commit is contained in:
outis1one
2026-01-25 20:01:22 -05:00
committed by GitHub
10 changed files with 380 additions and 48 deletions
+2 -1
View File
@@ -11,8 +11,9 @@ const AdvancedTools = ({
isProcessing,
setIsProcessing,
setError,
activeToolMode,
setActiveToolMode,
}) => {
const [activeToolMode, setActiveToolMode] = useState(null);
const [colorTolerance, setColorTolerance] = useState(30);
const handleRemoveBackground = async () => {
+63
View File
@@ -46,3 +46,66 @@
.clear-selection-btn:hover {
background-color: #dd4444;
}
/* Zoom controls */
.zoom-controls {
position: absolute;
bottom: 10px;
right: 10px;
display: flex;
align-items: center;
gap: 4px;
background-color: rgba(0, 0, 0, 0.8);
padding: 6px 10px;
border-radius: 4px;
z-index: 10;
}
.zoom-controls button {
width: 28px;
height: 28px;
padding: 0;
font-size: 18px;
font-weight: bold;
background-color: #444;
color: white;
border: 1px solid #666;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.zoom-controls button:hover {
background-color: #555;
}
.zoom-level {
color: white;
font-size: 12px;
min-width: 45px;
text-align: center;
}
/* Tool mode indicator */
.tool-mode-indicator {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 120, 255, 0.9);
color: white;
padding: 10px 20px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
z-index: 10;
white-space: nowrap;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
+55 -1
View File
@@ -61,11 +61,36 @@ const ImageCanvas = forwardRef(({
handleResize();
window.addEventListener('resize', handleResize);
// Mouse wheel zoom
const handleWheel = (opt) => {
const e = opt.e;
e.preventDefault();
e.stopPropagation();
const delta = e.deltaY;
let newZoom = canvas.getZoom();
newZoom *= 0.999 ** delta;
// Clamp zoom between 0.1x and 10x
if (newZoom > 10) newZoom = 10;
if (newZoom < 0.1) newZoom = 0.1;
// Zoom to point under cursor
const pointer = canvas.getPointer(e, true);
canvas.zoomToPoint({ x: pointer.x, y: pointer.y }, newZoom);
setCurrentZoom(newZoom);
onZoomChange?.(newZoom);
};
canvas.on('mouse:wheel', handleWheel);
return () => {
window.removeEventListener('resize', handleResize);
canvas.off('mouse:wheel', handleWheel);
canvas.dispose();
};
}, []);
}, [onZoomChange]);
// Center and scale image
const centerImage = (canvas, img, zoomFactor) => {
@@ -601,6 +626,35 @@ const ImageCanvas = forwardRef(({
}
};
const handleZoomIn = () => {
if (!fabricCanvasRef.current) return;
const canvas = fabricCanvasRef.current;
let newZoom = canvas.getZoom() * 1.2;
if (newZoom > 10) newZoom = 10;
canvas.setZoom(newZoom);
setCurrentZoom(newZoom);
onZoomChange?.(newZoom);
};
const handleZoomOut = () => {
if (!fabricCanvasRef.current) return;
const canvas = fabricCanvasRef.current;
let newZoom = canvas.getZoom() / 1.2;
if (newZoom < 0.1) newZoom = 0.1;
canvas.setZoom(newZoom);
setCurrentZoom(newZoom);
onZoomChange?.(newZoom);
};
const handleZoomReset = () => {
if (!fabricCanvasRef.current) return;
const canvas = fabricCanvasRef.current;
canvas.setZoom(1);
canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
setCurrentZoom(1);
onZoomChange?.(1);
};
return (
<div className="canvas-container">
<canvas ref={canvasRef} />
+54
View File
@@ -11,6 +11,7 @@ const Layers = ({
onLayerVisibilityChange,
onFlatten,
isProcessing,
onError,
}) => {
const [draggedLayer, setDraggedLayer] = useState(null);
@@ -69,6 +70,56 @@ const Layers = ({
loadLayers();
};
const handleNewLayer = async () => {
if (!projectId) return;
try {
// Create a new empty transparent layer
const newLayer = {
id: `layer-${Date.now()}`,
name: `Layer ${layers.length + 1}`,
visible: true,
thumbnail: null,
};
setLayers([...layers, newLayer]);
setActiveLayer(newLayer.id);
} catch (err) {
onError?.(`Failed to create layer: ${err.message}`);
}
};
const handleDeleteLayer = async () => {
if (!projectId || activeLayer === 'background') return;
try {
const updatedLayers = layers.filter((l) => l.id !== activeLayer);
setLayers(updatedLayers);
setActiveLayer(updatedLayers.length > 0 ? updatedLayers[updatedLayers.length - 1].id : 'background');
} catch (err) {
onError?.(`Failed to delete layer: ${err.message}`);
}
};
const handleDuplicateLayer = async () => {
if (!projectId || activeLayer === 'background') return;
try {
const layerToDuplicate = layers.find((l) => l.id === activeLayer);
if (!layerToDuplicate) return;
const newLayer = {
...layerToDuplicate,
id: `layer-${Date.now()}`,
name: `${layerToDuplicate.name} copy`,
};
const activeIndex = layers.findIndex((l) => l.id === activeLayer);
const updatedLayers = [...layers];
updatedLayers.splice(activeIndex + 1, 0, newLayer);
setLayers(updatedLayers);
setActiveLayer(newLayer.id);
} catch (err) {
onError?.(`Failed to duplicate layer: ${err.message}`);
}
};
return (
<div className="layers-panel">
<div className="layers-header">
@@ -142,6 +193,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || !projectId}
title="Add new empty layer"
onClick={handleNewLayer}
>
+ New Layer
</button>
@@ -149,6 +201,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Delete selected layer"
onClick={handleDeleteLayer}
>
Delete
</button>
@@ -156,6 +209,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Duplicate selected layer"
onClick={handleDuplicateLayer}
>
Duplicate
</button>