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 {
|
interface State {
|
||||||
initialized: number;
|
initialized: number;
|
||||||
current_effect: string;
|
chain: EffectActive[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
effects: {
|
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() {
|
function App() {
|
||||||
const [state, setState] = useState<State | undefined>(undefined);
|
const [state, setState] = useState<State | undefined>(undefined);
|
||||||
const [options, setOptions] = useState<Options | undefined>(undefined);
|
const [options, setOptions] = useState<Options | undefined>(undefined);
|
||||||
@@ -101,30 +177,99 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
const onEffectButtonClick = (name: string) => {
|
const propagateState = (state: State) => {
|
||||||
socket?.send(
|
socket?.send(JSON.stringify({ type: "mutation", payload: state }));
|
||||||
JSON.stringify({ type: "mutation", payload: { current_effect: name } })
|
};
|
||||||
);
|
|
||||||
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{options && state ? (
|
{options && state ? (
|
||||||
<div className="font-mono flex justify-center flex-col">
|
<div className="flex flex-row">
|
||||||
<Display color="gray">Current Effect {state.current_effect}</Display>
|
<div
|
||||||
{options.effects.available.map((effect) => {
|
className="font-mono flex justify-start flex-col w-1/2"
|
||||||
return (
|
onDrop={(e) => {
|
||||||
<Button
|
const id = e.dataTransfer.getData("application/json");
|
||||||
key={effect}
|
handleAvailableDrop(id);
|
||||||
color="green"
|
}}
|
||||||
onClick={() => {
|
onDragOver={(e) => {
|
||||||
onEffectButtonClick(effect);
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{effect}
|
{options.effects.available.map((effect, index) => {
|
||||||
</Button>
|
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>
|
||||||
) : (
|
) : (
|
||||||
<div className="font-mono text-center text-8xl">OFFLINE</div>
|
<div className="font-mono text-center text-8xl">OFFLINE</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user