Files
PaintPlus/frontend/src/components/Layers.jsx
T
Claude 909bb41f8a Add advanced editing features: layers, background removal, smart selection
Backend:
- Add /tools router with background removal, smart select, color select
- Add rembg dependency for AI background removal
- Add layer management API (list, flatten)
- Fix transparency preservation in blend_patch (veil collapse fix)
- Preserve alpha channel when reverting/resetting images

Frontend:
- Add AdvancedTools panel with background removal, smart select, color select
- Add Layers panel with drag-to-reorder, visibility toggle, flatten
- Add toolsApi for new backend endpoints
- Make right panel scrollable for additional controls

This adds "Photoshop light" capabilities:
- Remove background and create layer
- Smart object selection (click to select)
- Color selection with tolerance
- Layer system with compositing
2026-01-25 15:20:11 +00:00

168 lines
4.6 KiB
React

import React, { useState, useEffect } from 'react';
import { toolsApi } from '../utils/api';
import './Layers.css';
const Layers = ({
projectId,
layers,
setLayers,
activeLayer,
setActiveLayer,
onLayerVisibilityChange,
onFlatten,
isProcessing,
}) => {
const [draggedLayer, setDraggedLayer] = useState(null);
// Load layers on mount and when projectId changes
useEffect(() => {
if (projectId) {
loadLayers();
}
}, [projectId]);
const loadLayers = async () => {
if (!projectId) return;
try {
const result = await toolsApi.listLayers(projectId);
setLayers(result.layers || []);
} catch (err) {
console.error('Failed to load layers:', err);
}
};
const handleDragStart = (e, index) => {
setDraggedLayer(index);
e.dataTransfer.effectAllowed = 'move';
};
const handleDragOver = (e, index) => {
e.preventDefault();
if (draggedLayer === null || draggedLayer === index) return;
// Reorder layers
const newLayers = [...layers];
const [removed] = newLayers.splice(draggedLayer, 1);
newLayers.splice(index, 0, removed);
setLayers(newLayers);
setDraggedLayer(index);
};
const handleDragEnd = () => {
setDraggedLayer(null);
};
const toggleVisibility = (layerId) => {
const layer = layers.find((l) => l.id === layerId);
if (layer) {
layer.visible = !layer.visible;
setLayers([...layers]);
onLayerVisibilityChange?.(layerId, layer.visible);
}
};
const handleFlatten = async () => {
if (!projectId || layers.length === 0) return;
const visibleLayers = layers.filter((l) => l.visible !== false);
const layerOrder = visibleLayers.map((l) => l.id);
await onFlatten(layerOrder);
loadLayers();
};
return (
<div className="layers-panel">
<div className="layers-header">
<h3>Layers</h3>
{layers.length > 0 && (
<button
className="flatten-btn"
onClick={handleFlatten}
disabled={isProcessing}
title="Merge all visible layers"
>
Flatten
</button>
)}
</div>
<div className="layers-list">
{/* Background layer (always present) */}
<div
className={`layer-item ${activeLayer === 'background' ? 'active' : ''}`}
onClick={() => setActiveLayer('background')}
>
<span className="layer-visibility">
<input type="checkbox" checked disabled />
</span>
<span className="layer-preview background-preview"></span>
<span className="layer-name">Background</span>
<span className="layer-lock">🔒</span>
</div>
{/* Dynamic layers */}
{layers.map((layer, index) => (
<div
key={layer.id}
className={`layer-item ${activeLayer === layer.id ? 'active' : ''} ${
draggedLayer === index ? 'dragging' : ''
}`}
draggable
onDragStart={(e) => handleDragStart(e, index)}
onDragOver={(e) => handleDragOver(e, index)}
onDragEnd={handleDragEnd}
onClick={() => setActiveLayer(layer.id)}
>
<span className="layer-visibility">
<input
type="checkbox"
checked={layer.visible !== false}
onChange={() => toggleVisibility(layer.id)}
onClick={(e) => e.stopPropagation()}
/>
</span>
<span className="layer-preview">
{layer.thumbnail && (
<img src={layer.thumbnail} alt={layer.name} />
)}
</span>
<span className="layer-name">{layer.name}</span>
<span className="layer-drag-handle">⋮⋮</span>
</div>
))}
{layers.length === 0 && (
<div className="no-layers-hint">
Use "Remove Background" to create layers
</div>
)}
</div>
<div className="layers-actions">
<button
className="layer-action-btn"
disabled={isProcessing || !projectId}
title="Add new empty layer"
>
+ New Layer
</button>
<button
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Delete selected layer"
>
Delete
</button>
<button
className="layer-action-btn"
disabled={isProcessing || activeLayer === 'background'}
title="Duplicate selected layer"
>
Duplicate
</button>
</div>
</div>
);
};
export default Layers;