mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-25 22:29:02 +00:00
[red-knot] Add run panel (#17002)
## Summary This PR adds a new secondary panel to the red knot playground that allows running the python code (current file) with [pyodide](https://pyodide.org/en/stable/index.html) (currently Python 3.12 only). ## Test Plan https://github.com/user-attachments/assets/7bda8ef7-19fb-4c2f-8e62-8e49a1416be1
This commit is contained in:
parent
338fed98a4
commit
43ca85a351
8 changed files with 414 additions and 22 deletions
|
|
@ -19,6 +19,7 @@
|
|||
"classnames": "^2.5.1",
|
||||
"lz-string": "^1.5.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"pyodide": "^0.27.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
|
|
@ -31,5 +32,8 @@
|
|||
"react": "$react",
|
||||
"react-dom": "$react-dom"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite-plugin-static-copy": "^2.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
Theme,
|
||||
VerticalResizeHandle,
|
||||
} from "shared";
|
||||
import { Diagnostic, Workspace } from "red_knot_wasm";
|
||||
import type { Diagnostic, Workspace } from "red_knot_wasm";
|
||||
import { Panel, PanelGroup } from "react-resizable-panels";
|
||||
import { Files } from "./Files";
|
||||
import SecondarySideBar from "./SecondarySideBar";
|
||||
|
|
@ -181,6 +181,7 @@ export default function Chrome({
|
|||
minSize={10}
|
||||
>
|
||||
<SecondaryPanel
|
||||
files={files}
|
||||
theme={theme}
|
||||
tool={secondaryTool}
|
||||
result={checkResult.secondary}
|
||||
|
|
@ -247,6 +248,13 @@ function useCheckResult(
|
|||
content: workspace.tokens(currentHandle),
|
||||
};
|
||||
break;
|
||||
|
||||
case "Run":
|
||||
secondary = {
|
||||
status: "ok",
|
||||
content: "",
|
||||
};
|
||||
break;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
secondary = {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import MonacoEditor from "@monaco-editor/react";
|
||||
import { Theme } from "shared";
|
||||
import { AstralButton, Theme } from "shared";
|
||||
import { ReadonlyFiles } from "../Playground";
|
||||
import { Suspense, use, useState } from "react";
|
||||
import { loadPyodide, PyodideInterface } from "pyodide";
|
||||
import classNames from "classnames";
|
||||
|
||||
export enum SecondaryTool {
|
||||
"AST" = "AST",
|
||||
"Tokens" = "Tokens",
|
||||
"Run" = "Run",
|
||||
}
|
||||
|
||||
export type SecondaryPanelResult =
|
||||
|
|
@ -12,6 +17,7 @@ export type SecondaryPanelResult =
|
|||
| { status: "error"; error: string };
|
||||
|
||||
export interface SecondaryPanelProps {
|
||||
files: ReadonlyFiles;
|
||||
tool: SecondaryTool;
|
||||
result: SecondaryPanelResult;
|
||||
theme: Theme;
|
||||
|
|
@ -20,23 +26,32 @@ export interface SecondaryPanelProps {
|
|||
export default function SecondaryPanel({
|
||||
tool,
|
||||
result,
|
||||
files,
|
||||
theme,
|
||||
}: SecondaryPanelProps) {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-grow">
|
||||
<Content tool={tool} result={result} theme={theme} />
|
||||
</div>
|
||||
<Content
|
||||
tool={tool}
|
||||
result={result}
|
||||
theme={theme}
|
||||
files={files}
|
||||
revision={files.revision}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Content({
|
||||
files,
|
||||
tool,
|
||||
result,
|
||||
theme,
|
||||
revision,
|
||||
}: {
|
||||
tool: SecondaryTool;
|
||||
files: ReadonlyFiles;
|
||||
revision: number;
|
||||
result: SecondaryPanelResult;
|
||||
theme: Theme;
|
||||
}) {
|
||||
|
|
@ -54,25 +69,124 @@ function Content({
|
|||
case "Tokens":
|
||||
language = "RustPythonTokens";
|
||||
break;
|
||||
|
||||
case "Run":
|
||||
return <Run theme={theme} files={files} key={`${revision}`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MonacoEditor
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 14,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
contextmenu: false,
|
||||
}}
|
||||
language={language}
|
||||
value={result.content}
|
||||
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
|
||||
/>
|
||||
<div className="flex-grow">
|
||||
<MonacoEditor
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 14,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
contextmenu: false,
|
||||
}}
|
||||
language={language}
|
||||
value={result.content}
|
||||
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "error":
|
||||
return <code className="whitespace-pre-wrap">{result.error}</code>;
|
||||
return (
|
||||
<div className="flex-grow">
|
||||
<code className="whitespace-pre-wrap">{result.error}</code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pyodidePromise: Promise<PyodideInterface> | null = null;
|
||||
|
||||
function Run({ files, theme }: { files: ReadonlyFiles; theme: Theme }) {
|
||||
if (pyodidePromise == null) {
|
||||
pyodidePromise = loadPyodide();
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div className="text-center">Loading</div>}>
|
||||
<RunWithPyiodide
|
||||
theme={theme}
|
||||
files={files}
|
||||
pyodidePromise={pyodidePromise}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function RunWithPyiodide({
|
||||
files,
|
||||
pyodidePromise,
|
||||
theme,
|
||||
}: {
|
||||
files: ReadonlyFiles;
|
||||
theme: Theme;
|
||||
pyodidePromise: Promise<PyodideInterface>;
|
||||
}) {
|
||||
const pyodide = use(pyodidePromise);
|
||||
|
||||
const [output, setOutput] = useState<string | null>(null);
|
||||
|
||||
if (output == null) {
|
||||
const handleRun = () => {
|
||||
let stdout = "";
|
||||
|
||||
pyodide.setStdout({
|
||||
batched(output) {
|
||||
stdout += output + "\n";
|
||||
},
|
||||
});
|
||||
|
||||
const main = files.selected == null ? "" : files.contents[files.selected];
|
||||
|
||||
for (const file of files.index) {
|
||||
pyodide.FS.writeFile(file.name, files.contents[file.id]);
|
||||
}
|
||||
|
||||
try {
|
||||
// Patch up reveal types
|
||||
pyodide.runPython(`
|
||||
import builtins
|
||||
builtins.reveal_type = print`);
|
||||
|
||||
pyodide.runPython(main);
|
||||
setOutput(stdout);
|
||||
} catch (e) {
|
||||
setOutput(`Failed to run Python script: ${e}`);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-auto flex-col justify-center items-center">
|
||||
<AstralButton
|
||||
type="button"
|
||||
className="flex-none leading-6 py-1.5 px-3 shadow-xs"
|
||||
onClick={handleRun}
|
||||
>
|
||||
<span
|
||||
className="inset-0 flex items-center justify-center"
|
||||
aria-hidden="false"
|
||||
>
|
||||
Run...
|
||||
</span>
|
||||
</AstralButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<pre
|
||||
className={classNames(
|
||||
"m-2",
|
||||
"text-sm",
|
||||
"whitespace-pre",
|
||||
theme === "dark" ? "text-white" : null,
|
||||
)}
|
||||
>
|
||||
{output}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { SecondaryTool } from "./SecondaryPanel";
|
|||
|
||||
interface Props {
|
||||
selected: SecondaryTool | null;
|
||||
|
||||
onSelected(tool: SecondaryTool): void;
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +27,14 @@ export default function SecondarySideBar({ selected, onSelected }: Props) {
|
|||
>
|
||||
<Icons.Token />
|
||||
</SideBarEntry>
|
||||
<SideBarEntry
|
||||
title="Run"
|
||||
position={"right"}
|
||||
selected={selected === SecondaryTool.Run}
|
||||
onClick={() => onSelected(SecondaryTool.Run)}
|
||||
>
|
||||
<Icons.Run />
|
||||
</SideBarEntry>
|
||||
</SideBar>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
import { ErrorMessage, Header, setupMonaco, useTheme } from "shared";
|
||||
import { FileHandle, Workspace } from "red_knot_wasm";
|
||||
import { persist, persistLocal, restore } from "./Editor/persist";
|
||||
import initRedKnot from "../red_knot_wasm";
|
||||
import { loader } from "@monaco-editor/react";
|
||||
import knotSchema from "../../../knot.schema.json";
|
||||
import Chrome, { formatError } from "./Editor/Chrome";
|
||||
|
|
@ -399,7 +398,8 @@ export interface InitializedPlayground {
|
|||
|
||||
// Run once during startup. Initializes monaco, loads the wasm file, and restores the previous editor state.
|
||||
async function startPlayground(): Promise<InitializedPlayground> {
|
||||
await initRedKnot();
|
||||
const red_knot = await import("../red_knot_wasm");
|
||||
await red_knot.default();
|
||||
const monaco = await loader.init();
|
||||
|
||||
setupMonaco(monaco, {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,31 @@
|
|||
import { defineConfig } from "vite";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
|
||||
const PYODIDE_EXCLUDE = [
|
||||
"!**/*.{md,html}",
|
||||
"!**/*.d.ts",
|
||||
"!**/*.whl",
|
||||
"!**/node_modules",
|
||||
];
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
plugins: [react(), tailwindcss(), viteStaticCopyPyodide()],
|
||||
optimizeDeps: { exclude: ["pyodide"] },
|
||||
});
|
||||
|
||||
export function viteStaticCopyPyodide() {
|
||||
const pyodideDir = dirname(fileURLToPath(import.meta.resolve("pyodide")));
|
||||
return viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: [join(pyodideDir, "*"), ...PYODIDE_EXCLUDE],
|
||||
dest: "assets",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
209
playground/package-lock.json
generated
209
playground/package-lock.json
generated
|
|
@ -38,12 +38,16 @@
|
|||
"classnames": "^2.5.1",
|
||||
"lz-string": "^1.5.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"pyodide": "^0.27.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"red_knot_wasm": "file:red_knot_wasm",
|
||||
"shared": "0.0.0",
|
||||
"smol-toml": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite-plugin-static-copy": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"knot/red_knot_wasm": {
|
||||
|
|
@ -1806,6 +1810,20 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
|
|
@ -2014,6 +2032,19 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-install": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.0.tgz",
|
||||
|
|
@ -2130,6 +2161,44 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
|
|
@ -3065,6 +3134,21 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
|
||||
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
|
|
@ -3522,6 +3606,19 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-boolean-object": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
||||
|
|
@ -3942,6 +4039,19 @@
|
|||
"json5": "lib/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
|
|
@ -4416,6 +4526,16 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
|
@ -4617,6 +4737,19 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-map": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz",
|
||||
"integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -4774,6 +4907,18 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pyodide": {
|
||||
"version": "0.27.4",
|
||||
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.27.4.tgz",
|
||||
"integrity": "sha512-2y3ySHCBmyzYDUlB939SaU3n7RxYQxwnGHgdakW/CPrNFX2L9fC+4nfJWQJH8a0ruQa8bBZSKCImMt/cq15RiQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
|
@ -4833,6 +4978,19 @@
|
|||
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/red_knot_wasm": {
|
||||
"resolved": "knot/red_knot_wasm",
|
||||
"link": true
|
||||
|
|
@ -5630,6 +5788,16 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
@ -5712,6 +5880,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-static-copy": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.3.0.tgz",
|
||||
"integrity": "sha512-LLKwhhHetGaCnWz4mas4qqjjguDka6/6b4+SeIohRroj8aCE7QTfiZECfPecslFQkWZ3HdQuq5kOPmWZjNYlKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.3",
|
||||
"fast-glob": "^3.2.11",
|
||||
"fs-extra": "^11.1.0",
|
||||
"p-map": "^7.0.3",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.0.0 || ^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wasm-pack": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.13.1.tgz",
|
||||
|
|
@ -5848,6 +6036,27 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,31 @@ export function File({
|
|||
);
|
||||
}
|
||||
|
||||
export function Run({
|
||||
width = 24,
|
||||
height = 24,
|
||||
}: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.00024 2V14.4805L12.9149 8.24024L4.00024 2ZM11.1812 8.24024L4.99524 12.5684V3.91209L11.1812 8.24024Z"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Settings() {
|
||||
return (
|
||||
<svg
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue