+
+ {/* Advanced tool mode indicator */}
+ {advancedToolMode && (
+
+ {advancedToolMode === 'smart-select' && 'Click on an object to select it'}
+ {advancedToolMode === 'color-select' && 'Click on a color to select similar pixels'}
+ {advancedToolMode === 'object-remove' && 'Click on an object to remove it'}
+
+ )}
+
+ {currentSelection && !advancedToolMode && (
<>
Click selection to move/resize/rotate
diff --git a/frontend/src/components/Layers.jsx b/frontend/src/components/Layers.jsx
index 22b50e2..f43770c 100644
--- a/frontend/src/components/Layers.jsx
+++ b/frontend/src/components/Layers.jsx
@@ -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 (
@@ -142,6 +193,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || !projectId}
title="Add new empty layer"
+ onClick={handleNewLayer}
>
+ New Layer
@@ -149,6 +201,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Delete selected layer"
+ onClick={handleDeleteLayer}
>
Delete
@@ -156,6 +209,7 @@ const Layers = ({
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Duplicate selected layer"
+ onClick={handleDuplicateLayer}
>
Duplicate
diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js
index cea392d..164910d 100644
--- a/frontend/src/utils/api.js
+++ b/frontend/src/utils/api.js
@@ -171,20 +171,22 @@ export const toolsApi = {
},
// Smart select object at point
+ // Returns { polygon, bbox, mask_base64 }
smartSelect: async (projectId, x, y) => {
const formData = new FormData();
formData.append('project_id', projectId);
formData.append('point_x', x);
formData.append('point_y', y);
+ formData.append('return_format', 'json');
const response = await api.post('/tools/smart-select', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
- responseType: 'blob',
});
return response.data;
},
// Select by color
+ // Returns { polygon, bbox, mask_base64, color, tolerance }
colorSelect: async (projectId, r, g, b, tolerance = 30) => {
const formData = new FormData();
formData.append('project_id', projectId);
@@ -192,10 +194,10 @@ export const toolsApi = {
formData.append('color_g', g);
formData.append('color_b', b);
formData.append('tolerance', tolerance);
+ formData.append('return_format', 'json');
const response = await api.post('/tools/color-select', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
- responseType: 'blob',
});
return response.data;
},