extract run logic to a custom hook

This commit is contained in:
Vyacheslav Matyukhin 2022-07-06 01:41:06 +04:00
parent 87bb752c92
commit 7d3366389b
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C

View File

@ -86,7 +86,6 @@ const schema = yup.object({}).shape({
diagramStart: yup.number().required().positive().integer().default(0).min(0), diagramStart: yup.number().required().positive().integer().default(0).min(0),
diagramStop: yup.number().required().positive().integer().default(10).min(0), diagramStop: yup.number().required().positive().integer().default(10).min(0),
diagramCount: yup.number().required().positive().integer().default(20).min(2), diagramCount: yup.number().required().positive().integer().default(20).min(2),
autoplay: yup.boolean().required(),
}); });
type FormFields = yup.InferType<typeof schema>; type FormFields = yup.InferType<typeof schema>;
@ -306,38 +305,78 @@ const InputVariablesSettings: React.FC<{
); );
}; };
const PlayControls: React.FC<{ const RunControls: React.FC<{
autoplay: boolean; autorunMode: boolean;
playing: boolean; isRunning: boolean;
stale: boolean; isStale: boolean;
onAutoplayChange: (value: boolean) => void; onAutorunModeChange: (value: boolean) => void;
play: () => void; run: () => void;
}> = ({ autoplay, playing, stale, onAutoplayChange, play }) => { }> = ({ autorunMode, isRunning, isStale, onAutorunModeChange, run }) => {
const CurrentPlayIcon = playing ? RefreshIcon : PlayIcon; const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon;
return ( return (
<div className="flex space-x-1 items-center"> <div className="flex space-x-1 items-center">
{autoplay ? null : ( {autorunMode ? null : (
<button onClick={play}> <button onClick={run}>
<CurrentPlayIcon <CurrentPlayIcon
className={clsx( className={clsx(
"w-8 h-8", "w-8 h-8",
playing && "animate-spin", isRunning && "animate-spin",
stale ? "text-indigo-500" : "text-gray-400" isStale ? "text-indigo-500" : "text-gray-400"
)} )}
/> />
</button> </button>
)} )}
<Toggle <Toggle
texts={["Autoplay", "Paused"]} texts={["Autorun", "Paused"]}
icons={[CheckCircleIcon, PauseIcon]} icons={[CheckCircleIcon, PauseIcon]}
status={autoplay} status={autorunMode}
onChange={onAutoplayChange} onChange={onAutorunModeChange}
/> />
</div> </div>
); );
}; };
const useRunnerState = (code: string) => {
const [autorunMode, setAutorunMode] = useState(true);
const [renderedCode, setRenderedCode] = useState(code); // used in manual run mode only
const [isRunning, setIsRunning] = useState(false); // used in manual run mode only
// This part is tricky and fragile; we need to re-render first to make sure that the icon is spinning,
// and only then evaluate the squiggle code (which freezes the UI).
// Also note that `useEffect` execution order matters here.
// Hopefully it'll all go away after we make squiggle code evaluation async.
useEffect(() => {
if (renderedCode === code && isRunning) {
// It's not possible to put this after `setRenderedCode(code)` below because React would apply
// `setIsRunning` and `setRenderedCode` together and spinning icon will disappear immediately.
setIsRunning(false);
}
}, [renderedCode, code, isRunning]);
useEffect(() => {
if (!autorunMode && isRunning) {
setRenderedCode(code); // TODO - force run even if code hasn't changed
}
}, [autorunMode, code, isRunning]);
const run = () => {
// The rest will be handled by useEffects above, but we need to update the spinner first.
setIsRunning(true);
};
return {
run,
renderedCode: autorunMode ? code : renderedCode,
isRunning,
autorunMode,
setAutorunMode: (newValue: boolean) => {
if (!newValue) setRenderedCode(code);
setAutorunMode(newValue);
},
};
};
export const SquigglePlayground: FC<PlaygroundProps> = ({ export const SquigglePlayground: FC<PlaygroundProps> = ({
defaultCode = "", defaultCode = "",
height = 500, height = 500,
@ -356,15 +395,10 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
defaultValue: defaultCode, defaultValue: defaultCode,
onChange: onCodeChange, onChange: onCodeChange,
}); });
const [renderedCode, setRenderedCode] = useState(""); // used only if autoplay is false
const [imports, setImports] = useState({}); const [imports, setImports] = useState({});
const { const { register, control } = useForm({
register,
control,
setValue: setFormValue,
} = useForm({
resolver: yupResolver(schema), resolver: yupResolver(schema),
defaultValues: { defaultValues: {
sampleCount: 1000, sampleCount: 1000,
@ -381,7 +415,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
diagramStart: 0, diagramStart: 0,
diagramStop: 10, diagramStop: 10,
diagramCount: 20, diagramCount: 20,
autoplay: true,
}, },
}); });
const vars = useWatch({ const vars = useWatch({
@ -400,38 +433,12 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
[vars.sampleCount, vars.xyPointLength] [vars.sampleCount, vars.xyPointLength]
); );
const [playing, setPlaying] = useState(false); // used in manual play mode only const { run, autorunMode, setAutorunMode, isRunning, renderedCode } =
useRunnerState(code);
// This part is tricky and fragile; we need to re-render first to make sure that the icon is spinning,
// and only then evaluate the squiggle code (which freezes the UI).
// Also note that `useEffect` execution order matters here.
// Hopefully it'll all go away after we make squiggle code evaluation async.
useEffect(() => {
if (renderedCode === code && playing) {
// It's not possible to put this after `setRenderedCode(code)` below because React would apply
// `setPlaying` and `setRenderedCode` together and spinning icon will disappear immediately.
setPlaying(false);
}
}, [renderedCode, code, playing]);
useEffect(() => {
if (!vars.autoplay && playing) {
setRenderedCode(code); // TODO - force play even if code hasn't changed
}
}, [vars.autoplay, code, playing]);
const play = () => {
setPlaying(true);
};
const manualPlay = () => {
if (vars.autoplay) return; // should we allow reruns even in autoplay mode?
setRenderedCode(code); // TODO - force play even if code hasn't changed
};
const squiggleChart = ( const squiggleChart = (
<SquiggleChart <SquiggleChart
code={vars.autoplay ? code : renderedCode} code={renderedCode}
environment={env} environment={env}
diagramStart={Number(vars.diagramStart)} diagramStart={Number(vars.diagramStart)}
diagramStop={Number(vars.diagramStop)} diagramStop={Number(vars.diagramStop)}
@ -450,9 +457,9 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
const firstTab = vars.showEditor ? ( const firstTab = vars.showEditor ? (
<div className="border border-slate-200"> <div className="border border-slate-200">
<CodeEditor <CodeEditor
value={code ?? ""} value={code}
onChange={setCode} onChange={setCode}
onSubmit={play} onSubmit={run}
oneLine={false} oneLine={false}
showGutter={true} showGutter={true}
height={height - 1} height={height - 1}
@ -503,15 +510,12 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
<StyledTab name="View Settings" icon={ChartSquareBarIcon} /> <StyledTab name="View Settings" icon={ChartSquareBarIcon} />
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} /> <StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
</StyledTab.List> </StyledTab.List>
<PlayControls <RunControls
autoplay={vars.autoplay || false} autorunMode={autorunMode}
stale={!vars.autoplay && renderedCode !== code} isStale={renderedCode !== code}
play={play} run={run}
playing={playing} isRunning={isRunning}
onAutoplayChange={(newValue) => { onAutorunModeChange={setAutorunMode}
if (!newValue) setRenderedCode(code);
setFormValue("autoplay", newValue);
}}
/> />
</div> </div>
{vars.showEditor ? withEditor : withoutEditor} {vars.showEditor ? withEditor : withoutEditor}