145 lines
4.6 KiB
JavaScript
145 lines
4.6 KiB
JavaScript
import { GripVertical, Trash2 } from "lucide-react";
|
|
|
|
import { postJson } from "../api/controlApi";
|
|
import { ParameterField } from "./ParameterField";
|
|
|
|
export function LayerCard({
|
|
layer,
|
|
index,
|
|
shaders,
|
|
expanded,
|
|
isDragging,
|
|
isDropTarget,
|
|
onToggleExpanded,
|
|
onDragStart,
|
|
onDragEnd,
|
|
onDragOver,
|
|
onDrop,
|
|
onRemove,
|
|
onLayerParameterChange,
|
|
}) {
|
|
const selectedShader = shaders.find((shader) => shader.id === layer.shaderId);
|
|
|
|
return (
|
|
<div
|
|
className={`layer-card${expanded ? " layer-card--expanded" : ""}${isDragging ? " layer-card--dragging" : ""}${isDropTarget ? " layer-card--drop-target" : ""}`}
|
|
onDragOver={(event) => {
|
|
event.preventDefault();
|
|
onDragOver(layer.id);
|
|
}}
|
|
onDrop={(event) => {
|
|
event.preventDefault();
|
|
onDrop(event, layer.id, index);
|
|
}}
|
|
>
|
|
<div className="layer-card__header">
|
|
<div className="layer-card__meta">
|
|
<button
|
|
type="button"
|
|
className="layer-card__drag-handle"
|
|
title="Drag to reorder"
|
|
aria-label="Drag to reorder"
|
|
draggable
|
|
onDragStart={(event) => {
|
|
event.dataTransfer.effectAllowed = "move";
|
|
event.dataTransfer.setData("text/plain", layer.id);
|
|
event.stopPropagation();
|
|
onDragStart(layer.id);
|
|
}}
|
|
onDragEnd={(event) => {
|
|
event.stopPropagation();
|
|
onDragEnd();
|
|
}}
|
|
>
|
|
<GripVertical size={16} strokeWidth={1.75} />
|
|
</button>
|
|
<span className="layer-card__index">{index + 1}</span>
|
|
<button type="button" className="layer-card__title" onClick={() => onToggleExpanded(layer.id)}>
|
|
{layer.shaderName}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="layer-card__actions">
|
|
<label className="toggle toggle--compact">
|
|
<input
|
|
type="checkbox"
|
|
checked={Boolean(layer.bypass)}
|
|
onChange={(event) =>
|
|
postJson("/api/layers/set-bypass", {
|
|
layerId: layer.id,
|
|
bypass: event.target.checked,
|
|
})
|
|
}
|
|
/>
|
|
<span>Bypass</span>
|
|
</label>
|
|
|
|
<button type="button" onClick={() => onToggleExpanded(layer.id)}>
|
|
{expanded ? "Hide" : "Controls"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="icon-button"
|
|
title="Remove layer"
|
|
aria-label="Remove layer"
|
|
onClick={() => onRemove(layer.id)}
|
|
>
|
|
<Trash2 size={16} strokeWidth={1.75} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{expanded ? (
|
|
<div className="layer-card__body">
|
|
{layer.temporal?.enabled ? (
|
|
<div className="layer-card__field">
|
|
<label>Temporal</label>
|
|
<div className="muted">
|
|
{layer.temporal.historySource} history, requested {layer.temporal.requestedHistoryLength} frame{layer.temporal.requestedHistoryLength === 1 ? "" : "s"}, using {layer.temporal.effectiveHistoryLength}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="layer-card__field">
|
|
<label>Temporal</label>
|
|
<div className="muted">Stateless shader</div>
|
|
</div>
|
|
)}
|
|
|
|
{selectedShader?.description ? (
|
|
<div className="shader-description">
|
|
<div className="shader-description__meta">{selectedShader.category || "Shader"}</div>
|
|
<p>{selectedShader.description}</p>
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="layer-card__subheader">
|
|
<h3>Parameters</h3>
|
|
<button
|
|
type="button"
|
|
disabled={layer.parameters.length === 0}
|
|
onClick={() => postJson("/api/layers/reset-parameters", { layerId: layer.id })}
|
|
>
|
|
Reset
|
|
</button>
|
|
</div>
|
|
|
|
{layer.parameters.length > 0 ? (
|
|
<div className="parameter-grid">
|
|
{layer.parameters.map((parameter) => (
|
|
<ParameterField
|
|
key={`${layer.id}:${parameter.id}`}
|
|
layer={layer}
|
|
parameter={parameter}
|
|
onParameterChange={(parameterId, value) => onLayerParameterChange(layer.id, parameterId, value)}
|
|
/>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="muted">This shader does not expose any user parameters.</p>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|