From 1fa177041178a25d36f908eda152ae0dbd3679a8 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sun, 4 Aug 2024 11:40:06 +0200 Subject: [PATCH] Red Knot Playground --- playground/index.html | 2 +- playground/package.json | 4 +- playground/red_knot/index.html | 39 +++ playground/src/red_knot.tsx | 11 + playground/src/red_knot/Chrome.tsx | 250 ++++++++++++++++++ playground/src/red_knot/Editor.tsx | 80 ++++++ playground/src/red_knot/Files.tsx | 178 +++++++++++++ playground/src/red_knot/PythonEditor.tsx | 81 ++++++ playground/src/{Editor => red_knot}/index.tsx | 0 playground/src/{main.tsx => ruff.tsx} | 2 +- playground/src/{Editor => ruff}/Chrome.tsx | 10 +- playground/src/{Editor => ruff}/Editor.tsx | 8 +- .../src/{Editor => ruff}/PrimarySideBar.tsx | 4 +- .../src/{Editor => ruff}/SecondaryPanel.tsx | 2 +- .../src/{Editor => ruff}/SecondarySideBar.tsx | 4 +- .../src/{Editor => ruff}/SettingsEditor.tsx | 2 +- .../src/{Editor => ruff}/SourceEditor.tsx | 4 +- playground/src/ruff/index.tsx | 3 + playground/src/{Editor => ruff}/settings.ts | 16 +- .../src/{Editor => shared}/AstralButton.tsx | 0 .../src/{Editor => shared}/ErrorMessage.tsx | 0 playground/src/{Editor => shared}/Header.tsx | 0 playground/src/{Editor => shared}/Icons.tsx | 60 +++++ .../src/{Editor => shared}/RepoButton.tsx | 0 .../src/{Editor => shared}/ResizeHandle.tsx | 0 .../src/{Editor => shared}/ShareButton.tsx | 0 playground/src/{Editor => shared}/SideBar.tsx | 2 +- .../src/{Editor => shared}/ThemeButton.tsx | 0 .../src/{Editor => shared}/VersionTag.tsx | 0 playground/src/{Editor => shared}/api.ts | 0 .../src/{Editor => shared}/setupMonaco.tsx | 0 playground/src/{Editor => shared}/theme.ts | 0 playground/src/{Editor => shared}/utils.ts | 0 playground/vite.config.ts | 8 + 34 files changed, 744 insertions(+), 26 deletions(-) create mode 100644 playground/red_knot/index.html create mode 100644 playground/src/red_knot.tsx create mode 100644 playground/src/red_knot/Chrome.tsx create mode 100644 playground/src/red_knot/Editor.tsx create mode 100644 playground/src/red_knot/Files.tsx create mode 100644 playground/src/red_knot/PythonEditor.tsx rename playground/src/{Editor => red_knot}/index.tsx (100%) rename playground/src/{main.tsx => ruff.tsx} (85%) rename playground/src/{Editor => ruff}/Chrome.tsx (94%) rename playground/src/{Editor => ruff}/Editor.tsx (96%) rename playground/src/{Editor => ruff}/PrimarySideBar.tsx (85%) rename playground/src/{Editor => ruff}/SecondaryPanel.tsx (97%) rename playground/src/{Editor => ruff}/SecondarySideBar.tsx (94%) rename playground/src/{Editor => ruff}/SettingsEditor.tsx (96%) rename playground/src/{Editor => ruff}/SourceEditor.tsx (97%) create mode 100644 playground/src/ruff/index.tsx rename playground/src/{Editor => ruff}/settings.ts (87%) rename playground/src/{Editor => shared}/AstralButton.tsx (100%) rename playground/src/{Editor => shared}/ErrorMessage.tsx (100%) rename playground/src/{Editor => shared}/Header.tsx (100%) rename playground/src/{Editor => shared}/Icons.tsx (84%) rename playground/src/{Editor => shared}/RepoButton.tsx (100%) rename playground/src/{Editor => shared}/ResizeHandle.tsx (100%) rename playground/src/{Editor => shared}/ShareButton.tsx (100%) rename playground/src/{Editor => shared}/SideBar.tsx (95%) rename playground/src/{Editor => shared}/ThemeButton.tsx (100%) rename playground/src/{Editor => shared}/VersionTag.tsx (100%) rename playground/src/{Editor => shared}/api.ts (100%) rename playground/src/{Editor => shared}/setupMonaco.tsx (100%) rename playground/src/{Editor => shared}/theme.ts (100%) rename playground/src/{Editor => shared}/utils.ts (100%) diff --git a/playground/index.html b/playground/index.html index 6a61a3cc99df46..5431fbc7335e1b 100644 --- a/playground/index.html +++ b/playground/index.html @@ -34,6 +34,6 @@
- + diff --git a/playground/package.json b/playground/package.json index e70ff88a53f448..8f807f3b71f31f 100644 --- a/playground/package.json +++ b/playground/package.json @@ -4,11 +4,11 @@ "version": "0.0.0", "type": "module", "scripts": { - "build:wasm": "wasm-pack build ../crates/ruff_wasm --target web --out-dir ../../playground/src/pkg", + "build:wasm": "wasm-pack build ../crates/ruff_wasm --target web --out-dir ../../playground/src/ruff/ruff_wasm && wasm-pack build ../crates/red_knot_wasm --target web --out-dir ../../playground/src/red_knot/red_knot_wasm", "build": "tsc && vite build", "check": "npm run lint && npm run tsc", "dev": "vite", - "dev:wasm": "wasm-pack build ../crates/ruff_wasm --dev --target web --out-dir ../../playground/src/pkg", + "dev:wasm": "wasm-pack build ../crates/ruff_wasm --dev --target web --out-dir ../../playground/src/ruff/ruff_wasm && wasm-pack build ../crates/red_knot_wasm --dev --target web --out-dir ../../playground/src/red_knot/red_knot_wasm", "fmt": "prettier --cache -w .", "lint": "eslint --cache --ext .ts,.tsx src", "preview": "vite preview", diff --git a/playground/red_knot/index.html b/playground/red_knot/index.html new file mode 100644 index 00000000000000..e266e6a5e59161 --- /dev/null +++ b/playground/red_knot/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + Playground | Red Knot + + + + + + + + + + + + + +
+ + + diff --git a/playground/src/red_knot.tsx b/playground/src/red_knot.tsx new file mode 100644 index 00000000000000..8972894af94914 --- /dev/null +++ b/playground/src/red_knot.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; + +import Chrome from "./red_knot/Chrome"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/playground/src/red_knot/Chrome.tsx b/playground/src/red_knot/Chrome.tsx new file mode 100644 index 00000000000000..9c1b5dba6e9893 --- /dev/null +++ b/playground/src/red_knot/Chrome.tsx @@ -0,0 +1,250 @@ +import { useCallback, useRef, useState } from "react"; +import Header from "../shared/Header"; +import { useTheme } from "../shared/theme"; +import { default as Editor } from "./Editor"; +import initRedKnot, { + Workspace, + Settings, + TargetVersion, + FileHandle, +} from "./red_knot_wasm"; +import { loader } from "@monaco-editor/react"; +import { setupMonaco } from "../shared/setupMonaco"; +import { Panel, PanelGroup } from "react-resizable-panels"; +import { Files } from "./Files"; + +type CurrentFile = { + handle: FileHandle; + content: string; +}; + +export type FileIndex = { + [name: string]: FileHandle; +}; + +export default function Chrome() { + const initPromise = useRef>(null); + const [workspace, setWorkspace] = useState(null); + + const [files, setFiles] = useState({}); + + // The revision gets incremented everytime any persisted state changes. + const [revision, setRevision] = useState(0); + const [version, setVersion] = useState(""); + + const [currentFile, setCurrentFile] = useState(null); + + const [theme, setTheme] = useTheme(); + + const handleShare = useCallback(() => { + alert("TODO"); + }, []); + + if (initPromise.current == null) { + initPromise.current = startPlayground() + .then(({ version }) => { + const settings = new Settings(TargetVersion.Py312); + const workspace = new Workspace("/", settings); + setVersion(version); + setWorkspace(workspace); + + const content = "import os"; + const main = workspace.openFile("main.py", content); + + setFiles({ + "main.py": main, + }); + + setCurrentFile({ + handle: main, + content, + }); + + setRevision(1); + }) + .catch((error) => { + console.error("Failed to initialize playground.", error); + }); + } + + const handleSourceChanged = useCallback( + (source: string) => { + if ( + workspace == null || + currentFile == null || + source == currentFile.content + ) { + return; + } + + workspace.updateFile(currentFile.handle, source); + + setCurrentFile({ + ...currentFile, + content: source, + }); + setRevision((revision) => revision + 1); + }, + [workspace, currentFile], + ); + + const handleFileClicked = useCallback( + (file: FileHandle) => { + if (workspace == null) { + return; + } + + setCurrentFile({ + handle: file, + content: workspace.sourceText(file), + }); + }, + [workspace], + ); + + const handleFileAdded = useCallback( + (name: string) => { + if (workspace == null) { + return; + } + + const handle = workspace.openFile(name, ""); + setCurrentFile({ + handle, + content: "", + }); + + setFiles((files) => ({ ...files, [name]: handle })); + setRevision((revision) => revision + 1); + }, + [workspace], + ); + + const handleFileRemoved = useCallback( + (file: FileHandle) => { + if (workspace == null) { + return; + } + + const fileEntries = Object.entries(files); + const index = fileEntries.findIndex(([, value]) => value === file); + + if (index === -1) { + return; + } + + // Remove the file + fileEntries.splice(index, 1); + + if (currentFile?.handle === file) { + const newCurrentFile = + index > 0 ? fileEntries[index - 1] : fileEntries[index]; + + if (newCurrentFile == null) { + setCurrentFile(null); + } else { + const handle = newCurrentFile[1]; + setCurrentFile({ + handle, + content: workspace.sourceText(handle), + }); + } + } + + workspace.closeFile(file); + setFiles(Object.fromEntries(fileEntries)); + setRevision((revision) => revision + 1); + }, + [currentFile, workspace, files], + ); + + const handleFileRenamed = useCallback( + (file: FileHandle, newName: string) => { + if (workspace == null) { + return; + } + + const content = workspace.sourceText(file); + workspace.closeFile(file); + const newFile = workspace.openFile(newName, content); + + if (currentFile?.handle === file) { + setCurrentFile({ + content, + handle: newFile, + }); + } + + setFiles((files) => { + const entries = Object.entries(files); + const index = entries.findIndex(([, value]) => value === file); + + entries.splice(index, 1, [newName, newFile]); + + return Object.fromEntries(entries); + }); + + setRevision((revision) => (revision += 1)); + }, + [workspace, currentFile], + ); + + return ( +
+
+ +
+ + {workspace != null && currentFile != null ? ( + + + +
+ +
+
+ ) : null} +
+
+
+ ); +} + +// Run once during startup. Initializes monaco, loads the wasm file, and restores the previous editor state. +async function startPlayground(): Promise<{ + version: string; +}> { + await initRedKnot(); + const monaco = await loader.init(); + + setupMonaco(monaco); + + return { + version: "0.0.0", + }; +} diff --git a/playground/src/red_knot/Editor.tsx b/playground/src/red_knot/Editor.tsx new file mode 100644 index 00000000000000..5c5077f64f616e --- /dev/null +++ b/playground/src/red_knot/Editor.tsx @@ -0,0 +1,80 @@ +import { useDeferredValue, useMemo } from "react"; +import { Workspace, FileHandle } from "./red_knot_wasm"; +import { ErrorMessage } from "../shared/ErrorMessage"; + +import { Theme } from "../shared/theme"; +import PythonEditor from "./PythonEditor"; + +interface CheckResult { + diagnostics: string[]; + error: string | null; +} + +type Props = { + file: FileHandle; + content: string; + theme: Theme; + workspace: Workspace; + + onSourceChanged(source: string): void; +}; + +export default function Editor({ + content, + file, + workspace, + theme, + onSourceChanged, +}: Props) { + // TODO: figure out how to do deferred + const deferredContent = useDeferredValue(content); + + const checkResult: CheckResult = useMemo(() => { + try { + const diagnostics = workspace.checkFile(file); + // There's an implicit dependency on deferredContent by what's stored + // in the workspace + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _value = deferredContent; + + return { + diagnostics, + error: null, + }; + } catch (e) { + console.error(e); + return { + diagnostics: [], + error: (e as Error).message, + }; + } + }, [deferredContent, file, workspace]); + + const filePath = file.toString(); + const fileType = filePath.endsWith(".toml") ? "toml" : "py"; + + return ( + <> + + + {checkResult.error ? ( +
+ {checkResult.error} +
+ ) : null} + + ); +} diff --git a/playground/src/red_knot/Files.tsx b/playground/src/red_knot/Files.tsx new file mode 100644 index 00000000000000..f6095f8eb15ad0 --- /dev/null +++ b/playground/src/red_knot/Files.tsx @@ -0,0 +1,178 @@ +import { FileHandle } from "./red_knot_wasm"; +import { FileIndex } from "./Chrome"; +import { AddIcon, CloseIcon, PythonIcon } from "../shared/Icons"; +import classNames from "classnames"; +import { useMemo, useState } from "react"; +import { Theme } from "../shared/theme"; + +export interface Props { + // The file names + files: FileIndex; + theme: Theme; + selected: FileHandle | null; + + onAdd(name: string): void; + onRemove(id: FileHandle): void; + onSelected(id: FileHandle): void; + onRename(id: FileHandle, newName: string): void; +} + +export function Files({ + files, + selected, + theme, + onAdd, + onRemove, + onRename, + onSelected, +}: Props) { + const handleAdd = () => { + let index: number | null = null; + let fileName = "file.py"; + + while (files[fileName] != null) { + index = (index ?? 0) + 1; + fileName = `file${index}.py`; + } + + onAdd(fileName); + }; + + const lastFile = useMemo(() => { + return Object.keys(files).length === 1; + }, [files]); + + return ( +
    + {Object.entries(files).map(([name, file]) => ( + + onSelected(file)} + onRenamed={(newName) => { + if (files[newName] == null) { + onRename(file, newName); + } + }} + /> + + + + ))} + + + +
+ ); +} + +interface ListItemProps { + selected: boolean; + children: React.ReactNode; + theme: Theme; +} + +function ListItem({ children, selected, theme }: ListItemProps) { + const activeBorderColor = + theme === "light" ? "border-galaxy" : "border-radiate"; + + return ( +
  • + {children} +
  • + ); +} + +interface FileEntryProps { + selected: boolean; + name: string; + onClicked(): void; + onRenamed(name: string): void; +} +function FileEntry({ name, onClicked, onRenamed, selected }: FileEntryProps) { + const [newName, setNewName] = useState(null); + + if (!selected && newName != null) { + setNewName(null); + } + + const handleRenamed = (newName: string) => { + setNewName(null); + if (name !== newName) { + onRenamed(newName); + } + }; + + return ( + + ); +} diff --git a/playground/src/red_knot/PythonEditor.tsx b/playground/src/red_knot/PythonEditor.tsx new file mode 100644 index 00000000000000..657e3b495145db --- /dev/null +++ b/playground/src/red_knot/PythonEditor.tsx @@ -0,0 +1,81 @@ +/** + * Editor for the Python source code. + */ + +import Editor, { BeforeMount, Monaco } from "@monaco-editor/react"; +import { MarkerSeverity } from "monaco-editor"; +import { useCallback, useEffect, useRef } from "react"; +import { Theme } from "../shared/theme"; + +type Props = { + visible: boolean; + source: string; + diagnostics: string[]; + theme: Theme; + onChange(content: string): void; +}; + +export default function PythonEditor({ + visible, + source, + theme, + diagnostics, + onChange, +}: Props) { + const monacoRef = useRef(null); + const monaco = monacoRef.current; + + useEffect(() => { + const editor = monaco?.editor; + const model = editor?.getModels()[0]; + if (!editor || !model) { + return; + } + + editor.setModelMarkers( + model, + "owner", + diagnostics.map((diagnostic) => ({ + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 1, + message: diagnostic, + severity: MarkerSeverity.Error, + tags: [], + })), + ); + }, [diagnostics, monaco]); + + const handleChange = useCallback( + (value: string | undefined) => { + onChange(value ?? ""); + }, + [onChange], + ); + + const handleMount: BeforeMount = useCallback( + (instance) => (monacoRef.current = instance), + [], + ); + + return ( + + ); +} diff --git a/playground/src/Editor/index.tsx b/playground/src/red_knot/index.tsx similarity index 100% rename from playground/src/Editor/index.tsx rename to playground/src/red_knot/index.tsx diff --git a/playground/src/main.tsx b/playground/src/ruff.tsx similarity index 85% rename from playground/src/main.tsx rename to playground/src/ruff.tsx index fbe0181a4dabd1..dd4187ee7a2d7f 100644 --- a/playground/src/main.tsx +++ b/playground/src/ruff.tsx @@ -1,7 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; -import Chrome from "./Editor/Chrome"; +import Chrome from "./ruff/Chrome"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/playground/src/Editor/Chrome.tsx b/playground/src/ruff/Chrome.tsx similarity index 94% rename from playground/src/Editor/Chrome.tsx rename to playground/src/ruff/Chrome.tsx index b97a1b5f008c88..439f53e307422f 100644 --- a/playground/src/Editor/Chrome.tsx +++ b/playground/src/ruff/Chrome.tsx @@ -1,11 +1,11 @@ import { useCallback, useMemo, useRef, useState } from "react"; -import Header from "./Header"; +import Header from "../shared/Header"; import { persist, persistLocal, restore, stringify } from "./settings"; -import { useTheme } from "./theme"; +import { useTheme } from "../shared/theme"; import { default as Editor, Source } from "./Editor"; -import initRuff, { Workspace } from "../pkg/ruff_wasm"; +import initRuff, { Workspace } from "./ruff_wasm"; import { loader } from "@monaco-editor/react"; -import { setupMonaco } from "./setupMonaco"; +import { setupMonaco } from "../shared/setupMonaco"; import { DEFAULT_PYTHON_SOURCE } from "../constants"; export default function Chrome() { @@ -105,8 +105,6 @@ async function startPlayground(): Promise<{ await initRuff(); const monaco = await loader.init(); - console.log(monaco); - setupMonaco(monaco); const response = await restore(); diff --git a/playground/src/Editor/Editor.tsx b/playground/src/ruff/Editor.tsx similarity index 96% rename from playground/src/Editor/Editor.tsx rename to playground/src/ruff/Editor.tsx index 1320fb4b208bb6..363cbc0966c73b 100644 --- a/playground/src/Editor/Editor.tsx +++ b/playground/src/ruff/Editor.tsx @@ -1,9 +1,9 @@ import { useDeferredValue, useMemo, useState } from "react"; import { Panel, PanelGroup } from "react-resizable-panels"; -import { Diagnostic, Workspace } from "../pkg/ruff_wasm"; -import { ErrorMessage } from "./ErrorMessage"; +import { Diagnostic, Workspace } from "./ruff_wasm"; +import { ErrorMessage } from "../shared/ErrorMessage"; import PrimarySideBar from "./PrimarySideBar"; -import { HorizontalResizeHandle } from "./ResizeHandle"; +import { HorizontalResizeHandle } from "../shared/ResizeHandle"; import SecondaryPanel, { SecondaryPanelResult, SecondaryTool, @@ -11,7 +11,7 @@ import SecondaryPanel, { import SecondarySideBar from "./SecondarySideBar"; import SettingsEditor from "./SettingsEditor"; import SourceEditor from "./SourceEditor"; -import { Theme } from "./theme"; +import { Theme } from "../shared/theme"; type Tab = "Source" | "Settings"; diff --git a/playground/src/Editor/PrimarySideBar.tsx b/playground/src/ruff/PrimarySideBar.tsx similarity index 85% rename from playground/src/Editor/PrimarySideBar.tsx rename to playground/src/ruff/PrimarySideBar.tsx index de4db82b0b5320..3ed8b9edcbd0ff 100644 --- a/playground/src/Editor/PrimarySideBar.tsx +++ b/playground/src/ruff/PrimarySideBar.tsx @@ -1,5 +1,5 @@ -import { FileIcon, SettingsIcon } from "./Icons"; -import SideBar, { SideBarEntry } from "./SideBar"; +import { FileIcon, SettingsIcon } from "../shared/Icons"; +import SideBar, { SideBarEntry } from "../shared/SideBar"; type Tool = "Settings" | "Source"; diff --git a/playground/src/Editor/SecondaryPanel.tsx b/playground/src/ruff/SecondaryPanel.tsx similarity index 97% rename from playground/src/Editor/SecondaryPanel.tsx rename to playground/src/ruff/SecondaryPanel.tsx index 513e2ba7a8a7b8..eaf6690e0059bf 100644 --- a/playground/src/Editor/SecondaryPanel.tsx +++ b/playground/src/ruff/SecondaryPanel.tsx @@ -1,5 +1,5 @@ import Editor from "@monaco-editor/react"; -import { Theme } from "./theme"; +import { Theme } from "../shared/theme"; export enum SecondaryTool { "Format" = "Format", diff --git a/playground/src/Editor/SecondarySideBar.tsx b/playground/src/ruff/SecondarySideBar.tsx similarity index 94% rename from playground/src/Editor/SecondarySideBar.tsx rename to playground/src/ruff/SecondarySideBar.tsx index 2accc7b915e3f7..3d3064806381ff 100644 --- a/playground/src/Editor/SecondarySideBar.tsx +++ b/playground/src/ruff/SecondarySideBar.tsx @@ -1,11 +1,11 @@ -import SideBar, { SideBarEntry } from "./SideBar"; +import SideBar, { SideBarEntry } from "../shared/SideBar"; import { FormatIcon, FormatterIRIcon, StructureIcon, TokensIcon, CommentsIcon, -} from "./Icons"; +} from "../shared/Icons"; import { SecondaryTool } from "./SecondaryPanel"; interface RightSideBarProps { diff --git a/playground/src/Editor/SettingsEditor.tsx b/playground/src/ruff/SettingsEditor.tsx similarity index 96% rename from playground/src/Editor/SettingsEditor.tsx rename to playground/src/ruff/SettingsEditor.tsx index 318395a7154831..6d1eae28c768f5 100644 --- a/playground/src/Editor/SettingsEditor.tsx +++ b/playground/src/ruff/SettingsEditor.tsx @@ -5,7 +5,7 @@ import Editor, { useMonaco } from "@monaco-editor/react"; import { useCallback, useEffect } from "react"; import schema from "../../../ruff.schema.json"; -import { Theme } from "./theme"; +import { Theme } from "../shared/theme"; export default function SettingsEditor({ visible, diff --git a/playground/src/Editor/SourceEditor.tsx b/playground/src/ruff/SourceEditor.tsx similarity index 97% rename from playground/src/Editor/SourceEditor.tsx rename to playground/src/ruff/SourceEditor.tsx index c74946e59bec99..def17b535ace45 100644 --- a/playground/src/Editor/SourceEditor.tsx +++ b/playground/src/ruff/SourceEditor.tsx @@ -5,8 +5,8 @@ import Editor, { BeforeMount, Monaco } from "@monaco-editor/react"; import { MarkerSeverity, MarkerTag } from "monaco-editor"; import { useCallback, useEffect, useRef } from "react"; -import { Diagnostic } from "../pkg/ruff_wasm"; -import { Theme } from "./theme"; +import { Diagnostic } from "./ruff_wasm"; +import { Theme } from "../shared/theme"; export default function SourceEditor({ visible, diff --git a/playground/src/ruff/index.tsx b/playground/src/ruff/index.tsx new file mode 100644 index 00000000000000..32edbc014fd4c5 --- /dev/null +++ b/playground/src/ruff/index.tsx @@ -0,0 +1,3 @@ +import Editor from "./Editor"; + +export default Editor; diff --git a/playground/src/Editor/settings.ts b/playground/src/ruff/settings.ts similarity index 87% rename from playground/src/Editor/settings.ts rename to playground/src/ruff/settings.ts index 411399da5d92b7..a8738badecbb97 100644 --- a/playground/src/Editor/settings.ts +++ b/playground/src/ruff/settings.ts @@ -1,5 +1,5 @@ import lzstring from "lz-string"; -import { fetchPlayground, savePlayground } from "./api"; +import { fetchPlayground, savePlayground } from "../shared/api"; export type Settings = { [K: string]: any }; @@ -38,6 +38,7 @@ export async function restore(): Promise<[string, string] | null> { // Legacy URLs, stored as encoded strings in the hash, like: // https://play.ruff.rs/#eyJzZXR0aW5nc1NvdXJjZ... const hash = window.location.hash.slice(1); + if (hash) { const value = lzstring.decompressFromEncodedURIComponent( window.location.hash.slice(1), @@ -48,8 +49,17 @@ export async function restore(): Promise<[string, string] | null> { // URLs stored in the database, like: // https://play.ruff.rs/1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed - const id = window.location.pathname.slice(1); - if (id) { + let id = window.location.pathname.slice(1); + + if (id.startsWith("red_knot")) { + id = id.substring("red_knot".length); + } + + if (id.startsWith("/")) { + id = id.substring(1); + } + + if (id && id !== "red_knot") { const playground = await fetchPlayground(id); if (playground == null) { return null; diff --git a/playground/src/Editor/AstralButton.tsx b/playground/src/shared/AstralButton.tsx similarity index 100% rename from playground/src/Editor/AstralButton.tsx rename to playground/src/shared/AstralButton.tsx diff --git a/playground/src/Editor/ErrorMessage.tsx b/playground/src/shared/ErrorMessage.tsx similarity index 100% rename from playground/src/Editor/ErrorMessage.tsx rename to playground/src/shared/ErrorMessage.tsx diff --git a/playground/src/Editor/Header.tsx b/playground/src/shared/Header.tsx similarity index 100% rename from playground/src/Editor/Header.tsx rename to playground/src/shared/Header.tsx diff --git a/playground/src/Editor/Icons.tsx b/playground/src/shared/Icons.tsx similarity index 84% rename from playground/src/Editor/Icons.tsx rename to playground/src/shared/Icons.tsx index 6d14d02341c0a9..38ead1cce1f45e 100644 --- a/playground/src/Editor/Icons.tsx +++ b/playground/src/shared/Icons.tsx @@ -140,3 +140,63 @@ export function CommentsIcon() { ); } + +export function AddIcon() { + return ( + + + + ); +} + +export function CloseIcon() { + return ( + + + + ); +} + +// https://github.com/material-extensions/vscode-material-icon-theme/blob/main/icons/python.svg +// or use https://github.com/vscode-icons/vscode-icons/blob/master/icons/file_type_python.svg?short_path=677f216 +export function PythonIcon({ + height = 24, + width = 24, +}: { + height?: number; + width?: number; +}) { + return ( + + + + + ); +} diff --git a/playground/src/Editor/RepoButton.tsx b/playground/src/shared/RepoButton.tsx similarity index 100% rename from playground/src/Editor/RepoButton.tsx rename to playground/src/shared/RepoButton.tsx diff --git a/playground/src/Editor/ResizeHandle.tsx b/playground/src/shared/ResizeHandle.tsx similarity index 100% rename from playground/src/Editor/ResizeHandle.tsx rename to playground/src/shared/ResizeHandle.tsx diff --git a/playground/src/Editor/ShareButton.tsx b/playground/src/shared/ShareButton.tsx similarity index 100% rename from playground/src/Editor/ShareButton.tsx rename to playground/src/shared/ShareButton.tsx diff --git a/playground/src/Editor/SideBar.tsx b/playground/src/shared/SideBar.tsx similarity index 95% rename from playground/src/Editor/SideBar.tsx rename to playground/src/shared/SideBar.tsx index 4651c59da51f58..5f77938f8f4ae8 100644 --- a/playground/src/Editor/SideBar.tsx +++ b/playground/src/shared/SideBar.tsx @@ -10,7 +10,7 @@ export default function SideBar({ children, position }: SideBarProps) { return (
      diff --git a/playground/src/Editor/ThemeButton.tsx b/playground/src/shared/ThemeButton.tsx similarity index 100% rename from playground/src/Editor/ThemeButton.tsx rename to playground/src/shared/ThemeButton.tsx diff --git a/playground/src/Editor/VersionTag.tsx b/playground/src/shared/VersionTag.tsx similarity index 100% rename from playground/src/Editor/VersionTag.tsx rename to playground/src/shared/VersionTag.tsx diff --git a/playground/src/Editor/api.ts b/playground/src/shared/api.ts similarity index 100% rename from playground/src/Editor/api.ts rename to playground/src/shared/api.ts diff --git a/playground/src/Editor/setupMonaco.tsx b/playground/src/shared/setupMonaco.tsx similarity index 100% rename from playground/src/Editor/setupMonaco.tsx rename to playground/src/shared/setupMonaco.tsx diff --git a/playground/src/Editor/theme.ts b/playground/src/shared/theme.ts similarity index 100% rename from playground/src/Editor/theme.ts rename to playground/src/shared/theme.ts diff --git a/playground/src/Editor/utils.ts b/playground/src/shared/utils.ts similarity index 100% rename from playground/src/Editor/utils.ts rename to playground/src/shared/utils.ts diff --git a/playground/vite.config.ts b/playground/vite.config.ts index d366e8c8d7caf0..1118d3f94f2eb2 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -4,4 +4,12 @@ import react from "@vitejs/plugin-react-swc"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], + build: { + rollupOptions: { + input: { + main: "index.html", + red_knot: "red_knot.html", + }, + }, + }, });