153 lines
4.5 KiB
JavaScript
153 lines
4.5 KiB
JavaScript
import { postJson } from "../api/controlApi";
|
|
import { LayerCard } from "./LayerCard";
|
|
|
|
function moveItem(array, fromIndex, toIndex) {
|
|
if (fromIndex < 0 || fromIndex >= array.length || toIndex < 0 || toIndex >= array.length) {
|
|
return array;
|
|
}
|
|
|
|
const copy = [...array];
|
|
const [item] = copy.splice(fromIndex, 1);
|
|
copy.splice(toIndex, 0, item);
|
|
return copy;
|
|
}
|
|
|
|
export function LayerStack({
|
|
dragLayerId,
|
|
dropTargetLayerId,
|
|
expandedLayerIds,
|
|
layers,
|
|
pendingShaderId,
|
|
setAppState,
|
|
setDragLayerId,
|
|
setDropTargetLayerId,
|
|
setExpandedLayerIds,
|
|
setPendingShaderId,
|
|
shaders,
|
|
}) {
|
|
const expandedSet = new Set(expandedLayerIds);
|
|
|
|
function updateLayerParameterOptimistically(layerId, parameterId, value) {
|
|
return postJson("/api/layers/update-parameter", {
|
|
layerId,
|
|
parameterId,
|
|
value,
|
|
});
|
|
}
|
|
|
|
function toggleExpanded(layerId) {
|
|
setExpandedLayerIds((current) =>
|
|
current.includes(layerId) ? current.filter((id) => id !== layerId) : [...current, layerId],
|
|
);
|
|
}
|
|
|
|
function removeLayer(layerId) {
|
|
setExpandedLayerIds((current) => current.filter((id) => id !== layerId));
|
|
postJson("/api/layers/remove", { layerId });
|
|
}
|
|
|
|
function handleDrop(event, targetLayerId, targetIndex) {
|
|
const sourceLayerId = event.dataTransfer.getData("text/plain") || dragLayerId;
|
|
if (!sourceLayerId || sourceLayerId === targetLayerId) {
|
|
setDragLayerId(null);
|
|
setDropTargetLayerId(null);
|
|
return;
|
|
}
|
|
|
|
setAppState((current) => {
|
|
if (!current?.layers) {
|
|
return current;
|
|
}
|
|
|
|
const sourceIndex = current.layers.findIndex((layer) => layer.id === sourceLayerId);
|
|
const destinationIndex = current.layers.findIndex((layer) => layer.id === targetLayerId);
|
|
if (sourceIndex < 0 || destinationIndex < 0 || sourceIndex === destinationIndex) {
|
|
return current;
|
|
}
|
|
|
|
return {
|
|
...current,
|
|
layers: moveItem(current.layers, sourceIndex, destinationIndex),
|
|
};
|
|
});
|
|
|
|
postJson("/api/layers/reorder", {
|
|
layerId: sourceLayerId,
|
|
targetIndex,
|
|
});
|
|
setDragLayerId(null);
|
|
setDropTargetLayerId(null);
|
|
}
|
|
|
|
return (
|
|
<section className="panel">
|
|
<div className="panel__header">
|
|
<h2>Layers</h2>
|
|
<p className="muted">Drag layers to reorder them. Each layer processes the output of the one above it.</p>
|
|
</div>
|
|
|
|
<div className="layer-stack">
|
|
{layers.map((layer, index) => (
|
|
<LayerCard
|
|
key={layer.id}
|
|
layer={layer}
|
|
index={index}
|
|
shaders={shaders}
|
|
expanded={expandedSet.has(layer.id)}
|
|
isDragging={dragLayerId === layer.id}
|
|
isDropTarget={dropTargetLayerId === layer.id}
|
|
onToggleExpanded={toggleExpanded}
|
|
onDragStart={setDragLayerId}
|
|
onDragEnd={() => {
|
|
setDragLayerId(null);
|
|
setDropTargetLayerId(null);
|
|
}}
|
|
onDragOver={setDropTargetLayerId}
|
|
onDrop={handleDrop}
|
|
onRemove={removeLayer}
|
|
onLayerParameterChange={updateLayerParameterOptimistically}
|
|
/>
|
|
))}
|
|
|
|
<div className="layer-card layer-card--add">
|
|
<div className="layer-card__header">
|
|
<div className="layer-card__meta">
|
|
<span className="layer-card__index">+</span>
|
|
<div className="layer-card__title layer-card__title--static">Add Layer</div>
|
|
</div>
|
|
<div className="layer-card__actions">
|
|
<button
|
|
type="button"
|
|
disabled={!pendingShaderId}
|
|
onClick={() => {
|
|
if (pendingShaderId) {
|
|
postJson("/api/layers/add", { shaderId: pendingShaderId });
|
|
}
|
|
}}
|
|
>
|
|
Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="layer-card__body">
|
|
<div className="layer-card__field">
|
|
<label htmlFor="add-layer-select">Shader</label>
|
|
<select
|
|
id="add-layer-select"
|
|
value={pendingShaderId}
|
|
onChange={(event) => setPendingShaderId(event.target.value)}
|
|
>
|
|
{shaders.map((shader) => (
|
|
<option key={shader.id} value={shader.id}>
|
|
{shader.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|