playground: Merge `Editor` state variables (#5831)

<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR removes state variables that can be derived, merges related variables into a single state, and generally avoids `null` states. 

## Test Plan

I clicked through the playground locally
<!-- How was it tested? -->
This commit is contained in:
Micha Reiser 2023-07-18 08:08:24 +02:00 committed by GitHub
parent 9ddf40455d
commit ef58287c16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 74 deletions

View File

@ -4,7 +4,7 @@ In-browser playground for Ruff. Available [https://play.ruff.rs/](https://play.r
## Getting started ## Getting started
- To build the WASM module, run `wasm-pack build ../crates/ruff_wasm --target web --out-dir ../../playground/src/pkg` - To build the WASM module, run `npm run build:wasm`
from the `./playground` directory. from the `./playground` directory.
- Install TypeScript dependencies with: `npm install`. - Install TypeScript dependencies with: `npm install`.
- Start the development server with: `npm run dev`. - Start the development server with: `npm run dev`.

View File

@ -4,6 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build:wasm": "wasm-pack build ../crates/ruff_wasm --target web --out-dir ../../playground/src/pkg",
"build": "tsc && vite build", "build": "tsc && vite build",
"check": "npm run lint && npm run tsc", "check": "npm run lint && npm run tsc",
"dev": "vite", "dev": "vite",

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { DEFAULT_PYTHON_SOURCE } from "../constants"; import { DEFAULT_PYTHON_SOURCE } from "../constants";
import init, { import init, {
check, check,
@ -16,90 +16,98 @@ import MonacoThemes from "./MonacoThemes";
type Tab = "Source" | "Settings"; type Tab = "Source" | "Settings";
interface Source {
pythonSource: string;
settingsSource: string;
revision: number;
}
interface CheckResult {
diagnostics: Diagnostic[];
error: string | null;
}
export default function Editor() { export default function Editor() {
const [initialized, setInitialized] = useState<boolean>(false); const [ruffVersion, setRuffVersion] = useState<string | null>(null);
const [version, setVersion] = useState<string | null>(null); const [checkResult, setCheckResult] = useState<CheckResult>({
diagnostics: [],
error: null,
});
const [source, setSource] = useState<Source>({
pythonSource: "",
settingsSource: "",
revision: 0,
});
const [tab, setTab] = useState<Tab>("Source"); const [tab, setTab] = useState<Tab>("Source");
const [edit, setEdit] = useState<number>(0);
const [settingsSource, setSettingsSource] = useState<string | null>(null);
const [pythonSource, setPythonSource] = useState<string | null>(null);
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([]);
const [error, setError] = useState<string | null>(null);
const [theme, setTheme] = useTheme(); const [theme, setTheme] = useTheme();
const initialized = ruffVersion != null;
useEffect(() => { useEffect(() => {
init().then(() => setInitialized(true)); init().then(() => {
setRuffVersion(currentVersion());
const [settingsSource, pythonSource] = restore() ?? [
stringify(defaultSettings()),
DEFAULT_PYTHON_SOURCE,
];
setSource({
pythonSource,
revision: 0,
settingsSource,
});
});
}, []); }, []);
useEffect(() => {
if (!initialized || settingsSource == null || pythonSource == null) {
return;
}
let config: any;
let diagnostics: Diagnostic[];
try {
config = JSON.parse(settingsSource);
} catch (e) {
setDiagnostics([]);
setError((e as Error).message);
return;
}
try {
diagnostics = check(pythonSource, config);
} catch (e) {
setError(e as string);
return;
}
setError(null);
setDiagnostics(diagnostics);
}, [initialized, settingsSource, pythonSource]);
useEffect(() => { useEffect(() => {
if (!initialized) { if (!initialized) {
return; return;
} }
if (settingsSource == null || pythonSource == null) { const { settingsSource, pythonSource } = source;
const payload = restore();
if (payload) {
const [settingsSource, pythonSource] = payload;
setSettingsSource(settingsSource);
setPythonSource(pythonSource);
} else {
setSettingsSource(stringify(defaultSettings()));
setPythonSource(DEFAULT_PYTHON_SOURCE);
}
}
}, [initialized, settingsSource, pythonSource]);
useEffect(() => { try {
const config = JSON.parse(settingsSource);
const diagnostics = check(pythonSource, config);
setCheckResult({
diagnostics,
error: null,
});
} catch (e) {
setCheckResult({
diagnostics: [],
error: (e as Error).message,
});
}
}, [initialized, source]);
const handleShare = useMemo(() => {
if (!initialized) { if (!initialized) {
return; return undefined;
} }
setVersion(currentVersion()); return () => {
}, [initialized]); persist(source.settingsSource, source.pythonSource);
};
const handleShare = useCallback(() => { }, [source, initialized]);
if (!initialized || settingsSource == null || pythonSource == null) {
return;
}
persist(settingsSource, pythonSource);
}, [initialized, settingsSource, pythonSource]);
const handlePythonSourceChange = useCallback((pythonSource: string) => { const handlePythonSourceChange = useCallback((pythonSource: string) => {
setEdit((edit) => edit + 1); setSource((state) => ({
setPythonSource(pythonSource); ...state,
pythonSource,
revision: state.revision + 1,
}));
}, []); }, []);
const handleSettingsSourceChange = useCallback((settingsSource: string) => { const handleSettingsSourceChange = useCallback((settingsSource: string) => {
setEdit((edit) => edit + 1); setSource((state) => ({
setSettingsSource(settingsSource); ...state,
settingsSource,
revision: state.revision + 1,
}));
}, []); }, []);
return ( return (
@ -109,37 +117,37 @@ export default function Editor() {
} }
> >
<Header <Header
edit={edit} edit={source.revision}
tab={tab} tab={tab}
theme={theme} theme={theme}
version={version} version={ruffVersion}
onChangeTab={setTab} onChangeTab={setTab}
onChangeTheme={setTheme} onChangeTheme={setTheme}
onShare={initialized ? handleShare : undefined} onShare={handleShare}
/> />
<MonacoThemes /> <MonacoThemes />
<div className={"mt-12 relative flex-auto"}> <div className={"mt-12 relative flex-auto"}>
{initialized && settingsSource != null && pythonSource != null ? ( {initialized ? (
<> <>
<SourceEditor <SourceEditor
visible={tab === "Source"} visible={tab === "Source"}
source={pythonSource} source={source.pythonSource}
theme={theme} theme={theme}
diagnostics={diagnostics} diagnostics={checkResult.diagnostics}
onChange={handlePythonSourceChange} onChange={handlePythonSourceChange}
/> />
<SettingsEditor <SettingsEditor
visible={tab === "Settings"} visible={tab === "Settings"}
source={settingsSource} source={source.settingsSource}
theme={theme} theme={theme}
onChange={handleSettingsSourceChange} onChange={handleSettingsSourceChange}
/> />
</> </>
) : null} ) : null}
</div> </div>
{error && tab === "Source" ? ( {checkResult.error && tab === "Source" ? (
<div <div
style={{ style={{
position: "fixed", position: "fixed",
@ -148,7 +156,7 @@ export default function Editor() {
bottom: "10%", bottom: "10%",
}} }}
> >
<ErrorMessage>{error}</ErrorMessage> <ErrorMessage>{checkResult.error}</ErrorMessage>
</div> </div>
) : null} ) : null}
</main> </main>