CI/CD
This commit is contained in:
152
ui/src/components/LayerStack.jsx
Normal file
152
ui/src/components/LayerStack.jsx
Normal file
@@ -0,0 +1,152 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user