drag-n-drop chain + dynamic params
This commit is contained in:
@@ -31,17 +31,93 @@ const Display: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
interface EffectParamsOption {
|
||||
type: "slider";
|
||||
min: number;
|
||||
max: number;
|
||||
default: number;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
interface EffectOption {
|
||||
function: string;
|
||||
params: { [key: string]: EffectParamsOption } | undefined;
|
||||
}
|
||||
|
||||
interface EffectActive {
|
||||
function: string;
|
||||
params: { [key: string]: any };
|
||||
}
|
||||
|
||||
interface State {
|
||||
initialized: number;
|
||||
current_effect: string;
|
||||
chain: EffectActive[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
effects: {
|
||||
available: string[];
|
||||
available: EffectOption[];
|
||||
};
|
||||
}
|
||||
|
||||
const ActiveEffect: FC<{
|
||||
availableEffects: EffectOption[];
|
||||
effect: EffectActive;
|
||||
key: string;
|
||||
onChange: (name: string, value: number) => void;
|
||||
}> = ({ availableEffects, effect, onChange, key }) => {
|
||||
const effectUsed = availableEffects.find(
|
||||
(e) => e.function === effect.function
|
||||
);
|
||||
|
||||
const children = (function () {
|
||||
if (
|
||||
effectUsed &&
|
||||
effectUsed.params !== undefined &&
|
||||
effectUsed.params !== null
|
||||
) {
|
||||
return Object.entries(effectUsed.params).map(
|
||||
([name, paramOptions], idx) => {
|
||||
if (paramOptions.type === "slider") {
|
||||
const currentValue = effect.params[name];
|
||||
const id = `${key}-input-${name}-${idx}`;
|
||||
return (
|
||||
<div>
|
||||
<div className="flex">
|
||||
<div className="text-sm">{paramOptions.min}</div>
|
||||
<input
|
||||
id={id}
|
||||
type="range"
|
||||
value={currentValue}
|
||||
min={paramOptions.min}
|
||||
max={paramOptions.max}
|
||||
onChange={(e) => onChange(name, parseFloat(e.target.value))}
|
||||
step={paramOptions.step || 1}
|
||||
/>
|
||||
<div className="text-sm">{paramOptions.max}</div>
|
||||
</div>
|
||||
<label className="text-sm text-gray-500" htmlFor={id}>
|
||||
{name}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<Display color="green">
|
||||
<p>{effect.function}</p>
|
||||
{children}
|
||||
</Display>
|
||||
);
|
||||
};
|
||||
|
||||
function App() {
|
||||
const [state, setState] = useState<State | undefined>(undefined);
|
||||
const [options, setOptions] = useState<Options | undefined>(undefined);
|
||||
@@ -101,30 +177,99 @@ function App() {
|
||||
}
|
||||
}, [socket]);
|
||||
|
||||
const onEffectButtonClick = (name: string) => {
|
||||
socket?.send(
|
||||
JSON.stringify({ type: "mutation", payload: { current_effect: name } })
|
||||
);
|
||||
const propagateState = (state: State) => {
|
||||
socket?.send(JSON.stringify({ type: "mutation", payload: state }));
|
||||
};
|
||||
|
||||
const handleAvailableDrop = (id: string) => {
|
||||
const [source, index, name] = id.split("-");
|
||||
if (source === "active" && state) {
|
||||
// remove from chain
|
||||
const stateCopy = JSON.parse(JSON.stringify(state)) as State;
|
||||
stateCopy.chain.splice(parseInt(index), 1);
|
||||
propagateState(stateCopy);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChainDrop = (id: string) => {
|
||||
const [source, index, name] = id.split("-");
|
||||
if (source === "available" && state) {
|
||||
const stateCopy = JSON.parse(JSON.stringify(state)) as State;
|
||||
const effect = options?.effects.available.filter(
|
||||
(e) => e.function === name
|
||||
)[0];
|
||||
if (effect) {
|
||||
stateCopy.chain.push({
|
||||
function: effect.function,
|
||||
params: {},
|
||||
});
|
||||
propagateState(stateCopy);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{options && state ? (
|
||||
<div className="font-mono flex justify-center flex-col">
|
||||
<Display color="gray">Current Effect {state.current_effect}</Display>
|
||||
{options.effects.available.map((effect) => {
|
||||
return (
|
||||
<Button
|
||||
key={effect}
|
||||
color="green"
|
||||
onClick={() => {
|
||||
onEffectButtonClick(effect);
|
||||
}}
|
||||
>
|
||||
{effect}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
<div className="flex flex-row">
|
||||
<div
|
||||
className="font-mono flex justify-start flex-col w-1/2"
|
||||
onDrop={(e) => {
|
||||
const id = e.dataTransfer.getData("application/json");
|
||||
handleAvailableDrop(id);
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{options.effects.available.map((effect, index) => {
|
||||
const key = `available-${index}-${effect.function}`;
|
||||
return (
|
||||
<div
|
||||
draggable
|
||||
key={key}
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.setData("application/json", key);
|
||||
}}
|
||||
>
|
||||
<Display color="gray">{effect.function}</Display>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className="flex justify-start flex-col border border-green-500 w-1/2"
|
||||
onDrop={(e) => {
|
||||
const id = e.dataTransfer.getData("application/json");
|
||||
handleChainDrop(id);
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{state.chain.map((effect, index) => {
|
||||
const key = `active-${index}-${effect.function}`;
|
||||
return (
|
||||
<div
|
||||
draggable
|
||||
key={key}
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.setData("application/json", key);
|
||||
}}
|
||||
>
|
||||
<ActiveEffect
|
||||
key={key}
|
||||
effect={effect}
|
||||
availableEffects={options.effects.available}
|
||||
onChange={(name, value) => {
|
||||
effect.params[name] = value;
|
||||
propagateState(state);
|
||||
}}
|
||||
></ActiveEffect>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="font-mono text-center text-8xl">OFFLINE</div>
|
||||
|
||||
Reference in New Issue
Block a user