mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:56 +00:00
playground: Persist source and panel (#6071)
This commit is contained in:
parent
c8ee357613
commit
1fdadee59c
5 changed files with 97 additions and 32 deletions
|
@ -10,7 +10,7 @@ import init, { Diagnostic, Workspace } from "../pkg";
|
|||
import { ErrorMessage } from "./ErrorMessage";
|
||||
import Header from "./Header";
|
||||
import { useTheme } from "./theme";
|
||||
import { persist, restore, stringify } from "./settings";
|
||||
import { persist, persistLocal, restore, stringify } from "./settings";
|
||||
import SettingsEditor from "./SettingsEditor";
|
||||
import SourceEditor from "./SourceEditor";
|
||||
import { Panel, PanelGroup } from "react-resizable-panels";
|
||||
|
@ -50,17 +50,45 @@ export default function Editor() {
|
|||
});
|
||||
|
||||
const [tab, setTab] = useState<Tab>("Source");
|
||||
const [theme, setTheme] = useTheme();
|
||||
const [secondaryTool, setSecondaryTool] = useState<SecondaryTool | null>(
|
||||
null,
|
||||
() => {
|
||||
const secondaryValue = new URLSearchParams(location.search).get(
|
||||
"secondary",
|
||||
);
|
||||
if (secondaryValue == null) {
|
||||
return null;
|
||||
} else {
|
||||
return parseSecondaryTool(secondaryValue);
|
||||
}
|
||||
},
|
||||
);
|
||||
const [theme, setTheme] = useTheme();
|
||||
|
||||
const initialized = ruffVersion != null;
|
||||
|
||||
// Ideally this would be retrieved right from the URl... but routing without a proper
|
||||
// router is hard (there's no location changed event) and pulling in a router
|
||||
// feels overkill.
|
||||
const handleSecondaryToolSelected = (tool: SecondaryTool | null) => {
|
||||
if (tool === secondaryTool) {
|
||||
tool = null;
|
||||
}
|
||||
|
||||
const url = new URL(location.href);
|
||||
|
||||
if (tool == null) {
|
||||
url.searchParams.delete("secondary");
|
||||
} else {
|
||||
url.searchParams.set("secondary", tool);
|
||||
}
|
||||
|
||||
history.replaceState(null, "", url);
|
||||
|
||||
setSecondaryTool(tool);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
init().then(() => {
|
||||
setRuffVersion(Workspace.version());
|
||||
|
||||
const [settingsSource, pythonSource] = restore() ?? [
|
||||
stringify(Workspace.defaultSettings()),
|
||||
DEFAULT_PYTHON_SOURCE,
|
||||
|
@ -71,6 +99,7 @@ export default function Editor() {
|
|||
revision: 0,
|
||||
settingsSource,
|
||||
});
|
||||
setRuffVersion(Workspace.version());
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
@ -141,6 +170,12 @@ export default function Editor() {
|
|||
}
|
||||
}, [initialized, deferredSource, secondaryTool]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialized) {
|
||||
persistLocal(source);
|
||||
}
|
||||
}, [initialized, source]);
|
||||
|
||||
const handleShare = useMemo(() => {
|
||||
if (!initialized) {
|
||||
return undefined;
|
||||
|
@ -215,13 +250,7 @@ export default function Editor() {
|
|||
)}
|
||||
<SecondarySideBar
|
||||
selected={secondaryTool}
|
||||
onSelected={(tool) => {
|
||||
if (secondaryTool === tool) {
|
||||
setSecondaryTool(null);
|
||||
} else {
|
||||
setSecondaryTool(tool);
|
||||
}
|
||||
}}
|
||||
onSelected={handleSecondaryToolSelected}
|
||||
/>
|
||||
</PanelGroup>
|
||||
) : null}
|
||||
|
@ -241,3 +270,11 @@ export default function Editor() {
|
|||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function parseSecondaryTool(tool: string): SecondaryTool | null {
|
||||
if (Object.hasOwn(SecondaryTool, tool)) {
|
||||
return tool as any;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import Editor from "@monaco-editor/react";
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export type SecondaryTool = "Format" | "AST" | "Tokens" | "FIR";
|
||||
export enum SecondaryTool {
|
||||
"Format" = "Format",
|
||||
"AST" = "AST",
|
||||
"Tokens" = "Tokens",
|
||||
"FIR" = "FIR",
|
||||
}
|
||||
|
||||
export type SecondaryPanelResult =
|
||||
| null
|
||||
|
|
|
@ -15,32 +15,32 @@ export default function SecondarySideBar({
|
|||
<SideBar position="right">
|
||||
<SideBarEntry
|
||||
title="Format (alpha)"
|
||||
selected={selected === "Format"}
|
||||
onClick={() => onSelected("Format")}
|
||||
selected={selected === SecondaryTool.Format}
|
||||
onClick={() => onSelected(SecondaryTool.Format)}
|
||||
>
|
||||
<FormatIcon />
|
||||
</SideBarEntry>
|
||||
|
||||
<SideBarEntry
|
||||
title="AST"
|
||||
selected={selected === "AST"}
|
||||
onClick={() => onSelected("AST")}
|
||||
selected={selected === SecondaryTool.AST}
|
||||
onClick={() => onSelected(SecondaryTool.AST)}
|
||||
>
|
||||
<StructureIcon />
|
||||
</SideBarEntry>
|
||||
|
||||
<SideBarEntry
|
||||
title="Tokens"
|
||||
selected={selected === "Tokens"}
|
||||
onClick={() => onSelected("Tokens")}
|
||||
selected={selected === SecondaryTool.Tokens}
|
||||
onClick={() => onSelected(SecondaryTool.Tokens)}
|
||||
>
|
||||
<TokensIcon />
|
||||
</SideBarEntry>
|
||||
|
||||
<SideBarEntry
|
||||
title="Formatter IR"
|
||||
selected={selected === "FIR"}
|
||||
onClick={() => onSelected("FIR")}
|
||||
selected={selected === SecondaryTool.FIR}
|
||||
onClick={() => onSelected(SecondaryTool.FIR)}
|
||||
>
|
||||
FIR
|
||||
</SideBarEntry>
|
||||
|
|
|
@ -39,10 +39,33 @@ export function restore(): [string, string] | null {
|
|||
window.location.hash.slice(1),
|
||||
);
|
||||
|
||||
if (value != null) {
|
||||
if (value == null) {
|
||||
return restoreLocal();
|
||||
} else {
|
||||
const [settingsSource, pythonSource] = value.split("$$$");
|
||||
return [settingsSource.replaceAll("$$$$$$", "$$$"), pythonSource];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function restoreLocal(): [string, string] | null {
|
||||
const source = localStorage.getItem("source");
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
} else {
|
||||
return JSON.parse(source);
|
||||
}
|
||||
}
|
||||
|
||||
export function persistLocal({
|
||||
settingsSource,
|
||||
pythonSource,
|
||||
}: {
|
||||
settingsSource: string;
|
||||
pythonSource: string;
|
||||
}) {
|
||||
localStorage.setItem(
|
||||
"source",
|
||||
JSON.stringify([settingsSource, pythonSource]),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ html,
|
|||
@font-face {
|
||||
font-family: "Alliance Text";
|
||||
src:
|
||||
url("../public/fonts/Alliance-TextRegular.woff2") format("woff2"),
|
||||
url("../public/fonts/Alliance-TextRegular.woff") format("woff");
|
||||
url("../fonts/Alliance-TextRegular.woff2") format("woff2"),
|
||||
url("../fonts/Alliance-TextRegular.woff") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
@ -39,8 +39,8 @@ html,
|
|||
@font-face {
|
||||
font-family: "Alliance Text";
|
||||
src:
|
||||
url("../public/fonts/Alliance-TextMedium.woff2") format("woff2"),
|
||||
url("../public/fonts/Alliance-TextMedium.woff") format("woff");
|
||||
url("../fonts/Alliance-TextMedium.woff2") format("woff2"),
|
||||
url("../fonts/Alliance-TextMedium.woff") format("woff");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
@ -49,8 +49,8 @@ html,
|
|||
@font-face {
|
||||
font-family: "Alliance Platt";
|
||||
src:
|
||||
url("../public/fonts/Alliance-PlattMedium.woff2") format("woff2"),
|
||||
url("../public/fonts/Alliance-PlattMedium.woff") format("woff");
|
||||
url("../fonts/Alliance-PlattMedium.woff2") format("woff2"),
|
||||
url("../fonts/Alliance-PlattMedium.woff") format("woff");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
@ -59,8 +59,8 @@ html,
|
|||
@font-face {
|
||||
font-family: "Alliance Platt";
|
||||
src:
|
||||
url("../public/fonts/Alliance-PlattRegular.woff2") format("woff2"),
|
||||
url("../public/fonts/Alliance-PlattRegular.woff") format("woff");
|
||||
url("../fonts/Alliance-PlattRegular.woff2") format("woff2"),
|
||||
url("../fonts/Alliance-PlattRegular.woff") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue