mirror of https://github.com/astral-sh/ruff
[playground] Add Reset button (#17236)
## Summary Add a *Reset* button to both Ruff's and Red Knot's playground that resets the playground to its initial state. Closes https://github.com/astral-sh/ruff/issues/17195 ## Test Plan https://github.com/user-attachments/assets/753cca19-155a-44b1-89ba-76744487a55d https://github.com/user-attachments/assets/7d19f04c-70f4-4d9e-b745-0486cb1d4993
This commit is contained in:
parent
4571c5f56f
commit
3dd6d0a422
|
|
@ -40,15 +40,15 @@ export interface Props {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
selectedFileName: string;
|
selectedFileName: string;
|
||||||
|
|
||||||
onFileAdded(workspace: Workspace, name: string): void;
|
onAddFile(workspace: Workspace, name: string): void;
|
||||||
|
|
||||||
onFileChanged(workspace: Workspace, content: string): void;
|
onChangeFile(workspace: Workspace, content: string): void;
|
||||||
|
|
||||||
onFileRenamed(workspace: Workspace, file: FileId, newName: string): void;
|
onRenameFile(workspace: Workspace, file: FileId, newName: string): void;
|
||||||
|
|
||||||
onFileRemoved(workspace: Workspace, file: FileId): void;
|
onRemoveFile(workspace: Workspace, file: FileId): void;
|
||||||
|
|
||||||
onFileSelected(id: FileId): void;
|
onSelectFile(id: FileId): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Chrome({
|
export default function Chrome({
|
||||||
|
|
@ -56,11 +56,11 @@ export default function Chrome({
|
||||||
selectedFileName,
|
selectedFileName,
|
||||||
workspacePromise,
|
workspacePromise,
|
||||||
theme,
|
theme,
|
||||||
onFileAdded,
|
onAddFile,
|
||||||
onFileRenamed,
|
onRenameFile,
|
||||||
onFileRemoved,
|
onRemoveFile,
|
||||||
onFileSelected,
|
onSelectFile,
|
||||||
onFileChanged,
|
onChangeFile,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const workspace = use(workspacePromise);
|
const workspace = use(workspacePromise);
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ export default function Chrome({
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const handleFileRenamed = (file: FileId, newName: string) => {
|
const handleFileRenamed = (file: FileId, newName: string) => {
|
||||||
onFileRenamed(workspace, file, newName);
|
onRenameFile(workspace, file, newName);
|
||||||
editorRef.current?.editor.focus();
|
editorRef.current?.editor.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -129,9 +129,9 @@ export default function Chrome({
|
||||||
?.dispose();
|
?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileRemoved(workspace, id);
|
onRemoveFile(workspace, id);
|
||||||
},
|
},
|
||||||
[workspace, files.index, onFileRemoved],
|
[workspace, files.index, onRemoveFile],
|
||||||
);
|
);
|
||||||
|
|
||||||
const checkResult = useCheckResult(files, workspace, secondaryTool);
|
const checkResult = useCheckResult(files, workspace, secondaryTool);
|
||||||
|
|
@ -144,9 +144,9 @@ export default function Chrome({
|
||||||
files={files.index}
|
files={files.index}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
selected={files.selected}
|
selected={files.selected}
|
||||||
onAdd={(name) => onFileAdded(workspace, name)}
|
onAdd={(name) => onAddFile(workspace, name)}
|
||||||
onRename={handleFileRenamed}
|
onRename={handleFileRenamed}
|
||||||
onSelected={onFileSelected}
|
onSelect={onSelectFile}
|
||||||
onRemove={handleRemoved}
|
onRemove={handleRemoved}
|
||||||
/>
|
/>
|
||||||
<PanelGroup direction="horizontal" autoSaveId="main">
|
<PanelGroup direction="horizontal" autoSaveId="main">
|
||||||
|
|
@ -167,8 +167,8 @@ export default function Chrome({
|
||||||
diagnostics={checkResult.diagnostics}
|
diagnostics={checkResult.diagnostics}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
onMount={handleEditorMount}
|
onMount={handleEditorMount}
|
||||||
onChange={(content) => onFileChanged(workspace, content)}
|
onChange={(content) => onChangeFile(workspace, content)}
|
||||||
onFileOpened={onFileSelected}
|
onOpenFile={onSelectFile}
|
||||||
/>
|
/>
|
||||||
{checkResult.error ? (
|
{checkResult.error ? (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ type Props = {
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
onChange(content: string): void;
|
onChange(content: string): void;
|
||||||
onMount(editor: IStandaloneCodeEditor, monaco: Monaco): void;
|
onMount(editor: IStandaloneCodeEditor, monaco: Monaco): void;
|
||||||
onFileOpened(file: FileId): void;
|
onOpenFile(file: FileId): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Editor({
|
export default function Editor({
|
||||||
|
|
@ -51,7 +51,7 @@ export default function Editor({
|
||||||
workspace,
|
workspace,
|
||||||
onChange,
|
onChange,
|
||||||
onMount,
|
onMount,
|
||||||
onFileOpened,
|
onOpenFile,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const serverRef = useRef<PlaygroundServer | null>(null);
|
const serverRef = useRef<PlaygroundServer | null>(null);
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ export default function Editor({
|
||||||
serverRef.current.update({
|
serverRef.current.update({
|
||||||
files,
|
files,
|
||||||
workspace,
|
workspace,
|
||||||
onFileOpened,
|
onOpenFile,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ export default function Editor({
|
||||||
const server = new PlaygroundServer(instance, {
|
const server = new PlaygroundServer(instance, {
|
||||||
workspace,
|
workspace,
|
||||||
files,
|
files,
|
||||||
onFileOpened,
|
onOpenFile,
|
||||||
});
|
});
|
||||||
|
|
||||||
server.updateDiagnostics(diagnostics);
|
server.updateDiagnostics(diagnostics);
|
||||||
|
|
@ -105,11 +105,12 @@ export default function Editor({
|
||||||
onMount(editor, instance);
|
onMount(editor, instance);
|
||||||
},
|
},
|
||||||
|
|
||||||
[files, onFileOpened, workspace, onMount, diagnostics],
|
[files, onOpenFile, workspace, onMount, diagnostics],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Moncao
|
<Moncao
|
||||||
|
key={files.playgroundRevision}
|
||||||
onMount={handleMount}
|
onMount={handleMount}
|
||||||
options={{
|
options={{
|
||||||
fixedOverflowWidgets: true,
|
fixedOverflowWidgets: true,
|
||||||
|
|
@ -133,7 +134,7 @@ export default function Editor({
|
||||||
interface PlaygroundServerProps {
|
interface PlaygroundServerProps {
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
files: ReadonlyFiles;
|
files: ReadonlyFiles;
|
||||||
onFileOpened: (file: FileId) => void;
|
onOpenFile: (file: FileId) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlaygroundServer
|
class PlaygroundServer
|
||||||
|
|
@ -333,7 +334,7 @@ class PlaygroundServer
|
||||||
if (files.selected !== fileId) {
|
if (files.selected !== fileId) {
|
||||||
source.setModel(model);
|
source.setModel(model);
|
||||||
|
|
||||||
this.props.onFileOpened(fileId);
|
this.props.onOpenFile(fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionOrPosition != null) {
|
if (selectionOrPosition != null) {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export interface Props {
|
||||||
|
|
||||||
onRemove(id: FileId): void;
|
onRemove(id: FileId): void;
|
||||||
|
|
||||||
onSelected(id: FileId): void;
|
onSelect(id: FileId): void;
|
||||||
|
|
||||||
onRename(id: FileId, newName: string): void;
|
onRename(id: FileId, newName: string): void;
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ export function Files({
|
||||||
onAdd,
|
onAdd,
|
||||||
onRemove,
|
onRemove,
|
||||||
onRename,
|
onRename,
|
||||||
onSelected,
|
onSelect,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
let index: number | null = null;
|
let index: number | null = null;
|
||||||
|
|
@ -54,7 +54,7 @@ export function Files({
|
||||||
<FileEntry
|
<FileEntry
|
||||||
selected={selected === id}
|
selected={selected === id}
|
||||||
name={name}
|
name={name}
|
||||||
onClicked={() => onSelected(id)}
|
onClicked={() => onSelect(id)}
|
||||||
onRenamed={(newName) => {
|
onRenamed={(newName) => {
|
||||||
if (!files.some(({ name }) => name === newName)) {
|
if (!files.some(({ name }) => name === newName)) {
|
||||||
onRename(id, newName);
|
onRename(id, newName);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ActionDispatch,
|
||||||
Suspense,
|
Suspense,
|
||||||
useCallback,
|
useCallback,
|
||||||
useDeferredValue,
|
useDeferredValue,
|
||||||
|
|
@ -22,6 +23,7 @@ export default function Playground() {
|
||||||
const [version, setVersion] = useState<string>("0.0.0");
|
const [version, setVersion] = useState<string>("0.0.0");
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const workspacePromiseRef = useRef<Promise<Workspace> | null>(null);
|
const workspacePromiseRef = useRef<Promise<Workspace> | null>(null);
|
||||||
|
const [workspace, setWorkspace] = useState<Workspace | null>(null);
|
||||||
|
|
||||||
let workspacePromise = workspacePromiseRef.current;
|
let workspacePromise = workspacePromiseRef.current;
|
||||||
if (workspacePromise == null) {
|
if (workspacePromise == null) {
|
||||||
|
|
@ -29,43 +31,14 @@ export default function Playground() {
|
||||||
(fetched) => {
|
(fetched) => {
|
||||||
setVersion(fetched.version);
|
setVersion(fetched.version);
|
||||||
const workspace = new Workspace("/", {});
|
const workspace = new Workspace("/", {});
|
||||||
|
restoreWorkspace(workspace, fetched.workspace, dispatchFiles, setError);
|
||||||
let hasSettings = false;
|
setWorkspace(workspace);
|
||||||
|
|
||||||
for (const [name, content] of Object.entries(fetched.workspace.files)) {
|
|
||||||
let handle = null;
|
|
||||||
if (name === SETTINGS_FILE_NAME) {
|
|
||||||
updateOptions(workspace, content, setError);
|
|
||||||
hasSettings = true;
|
|
||||||
} else {
|
|
||||||
handle = workspace.openFile(name, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchFiles({ type: "add", handle, content, name });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasSettings) {
|
|
||||||
updateOptions(workspace, null, setError);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchFiles({
|
|
||||||
type: "selectFileByName",
|
|
||||||
name: fetched.workspace.current,
|
|
||||||
});
|
|
||||||
|
|
||||||
return workspace;
|
return workspace;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [files, dispatchFiles] = useReducer(filesReducer, {
|
const [files, dispatchFiles] = useReducer(filesReducer, INIT_FILES_STATE);
|
||||||
index: [],
|
|
||||||
contents: Object.create(null),
|
|
||||||
handles: Object.create(null),
|
|
||||||
nextId: 0,
|
|
||||||
revision: 0,
|
|
||||||
selected: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileName = useMemo(() => {
|
const fileName = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -155,6 +128,29 @@ export default function Playground() {
|
||||||
dispatchFiles({ type: "selectFile", id: file });
|
dispatchFiles({ type: "selectFile", id: file });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
if (workspace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all open files
|
||||||
|
for (const file of files.index) {
|
||||||
|
const handle = files.handles[file.id];
|
||||||
|
|
||||||
|
if (handle != null) {
|
||||||
|
try {
|
||||||
|
workspace.closeFile(handle);
|
||||||
|
} catch (e) {
|
||||||
|
setError(formatError(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchFiles({ type: "reset" });
|
||||||
|
|
||||||
|
restoreWorkspace(workspace, DEFAULT_WORKSPACE, dispatchFiles, setError);
|
||||||
|
}, [files.handles, files.index, workspace]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col h-full bg-ayu-background dark:bg-ayu-background-dark">
|
<main className="flex flex-col h-full bg-ayu-background dark:bg-ayu-background-dark">
|
||||||
<Header
|
<Header
|
||||||
|
|
@ -164,6 +160,7 @@ export default function Playground() {
|
||||||
version={version}
|
version={version}
|
||||||
onChangeTheme={setTheme}
|
onChangeTheme={setTheme}
|
||||||
onShare={handleShare}
|
onShare={handleShare}
|
||||||
|
onReset={workspace == null ? undefined : handleReset}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Suspense fallback={<Loading />}>
|
<Suspense fallback={<Loading />}>
|
||||||
|
|
@ -172,11 +169,11 @@ export default function Playground() {
|
||||||
workspacePromise={workspacePromise}
|
workspacePromise={workspacePromise}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
selectedFileName={fileName}
|
selectedFileName={fileName}
|
||||||
onFileAdded={handleFileAdded}
|
onAddFile={handleFileAdded}
|
||||||
onFileRenamed={handleFileRenamed}
|
onRenameFile={handleFileRenamed}
|
||||||
onFileRemoved={handleFileRemoved}
|
onRemoveFile={handleFileRemoved}
|
||||||
onFileSelected={handleFileSelected}
|
onSelectFile={handleFileSelected}
|
||||||
onFileChanged={handleFileChanged}
|
onChangeFile={handleFileChanged}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{error ? (
|
{error ? (
|
||||||
|
|
@ -208,6 +205,14 @@ export const DEFAULT_SETTINGS = JSON.stringify(
|
||||||
4,
|
4,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const DEFAULT_WORKSPACE = {
|
||||||
|
files: {
|
||||||
|
"main.py": "import os",
|
||||||
|
"knot.json": DEFAULT_SETTINGS,
|
||||||
|
},
|
||||||
|
current: "main.py",
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persists the files to local storage. This is done deferred to avoid too frequent writes.
|
* Persists the files to local storage. This is done deferred to avoid too frequent writes.
|
||||||
*/
|
*/
|
||||||
|
|
@ -254,6 +259,13 @@ interface FilesState {
|
||||||
* The revision. Gets incremented every time files changes.
|
* The revision. Gets incremented every time files changes.
|
||||||
*/
|
*/
|
||||||
revision: number;
|
revision: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revision identifying this playground. Gets incremented every time the
|
||||||
|
* playground is reset.
|
||||||
|
*/
|
||||||
|
playgroundRevision: number;
|
||||||
|
|
||||||
nextId: FileId;
|
nextId: FileId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +288,18 @@ export type FileAction =
|
||||||
id: FileId;
|
id: FileId;
|
||||||
}
|
}
|
||||||
| { type: "selectFile"; id: FileId }
|
| { type: "selectFile"; id: FileId }
|
||||||
| { type: "selectFileByName"; name: string };
|
| { type: "selectFileByName"; name: string }
|
||||||
|
| { type: "reset" };
|
||||||
|
|
||||||
|
const INIT_FILES_STATE: ReadonlyFiles = {
|
||||||
|
index: [],
|
||||||
|
contents: Object.create(null),
|
||||||
|
handles: Object.create(null),
|
||||||
|
nextId: 0,
|
||||||
|
revision: 0,
|
||||||
|
selected: null,
|
||||||
|
playgroundRevision: 0,
|
||||||
|
};
|
||||||
|
|
||||||
function filesReducer(
|
function filesReducer(
|
||||||
state: Readonly<FilesState>,
|
state: Readonly<FilesState>,
|
||||||
|
|
@ -366,6 +389,14 @@ function filesReducer(
|
||||||
selected,
|
selected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "reset": {
|
||||||
|
return {
|
||||||
|
...INIT_FILES_STATE,
|
||||||
|
playgroundRevision: state.playgroundRevision + 1,
|
||||||
|
revision: state.revision + 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,13 +441,7 @@ async function startPlayground(): Promise<InitializedPlayground> {
|
||||||
|
|
||||||
const restored = await restore();
|
const restored = await restore();
|
||||||
|
|
||||||
const workspace = restored ?? {
|
const workspace = restored ?? DEFAULT_WORKSPACE;
|
||||||
files: {
|
|
||||||
"main.py": "import os",
|
|
||||||
"knot.json": DEFAULT_SETTINGS,
|
|
||||||
},
|
|
||||||
current: "main.py",
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: "0.0.0",
|
version: "0.0.0",
|
||||||
|
|
@ -457,3 +482,36 @@ function updateFile(
|
||||||
function Loading() {
|
function Loading() {
|
||||||
return <div className="align-middle text-center my-2">Loading...</div>;
|
return <div className="align-middle text-center my-2">Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restoreWorkspace(
|
||||||
|
workspace: Workspace,
|
||||||
|
state: {
|
||||||
|
files: { [name: string]: string };
|
||||||
|
current: string;
|
||||||
|
},
|
||||||
|
dispatchFiles: ActionDispatch<[FileAction]>,
|
||||||
|
setError: (error: string | null) => void,
|
||||||
|
) {
|
||||||
|
let hasSettings = false;
|
||||||
|
|
||||||
|
for (const [name, content] of Object.entries(state.files)) {
|
||||||
|
let handle = null;
|
||||||
|
if (name === SETTINGS_FILE_NAME) {
|
||||||
|
updateOptions(workspace, content, setError);
|
||||||
|
hasSettings = true;
|
||||||
|
} else {
|
||||||
|
handle = workspace.openFile(name, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchFiles({ type: "add", handle, content, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSettings) {
|
||||||
|
updateOptions(workspace, null, setError);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchFiles({
|
||||||
|
type: "selectFileByName",
|
||||||
|
name: state.current,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,15 @@ export default function Chrome() {
|
||||||
[pythonSource],
|
[pythonSource],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleResetClicked = useCallback(() => {
|
||||||
|
const pythonSource = DEFAULT_PYTHON_SOURCE;
|
||||||
|
const settings = stringify(Workspace.defaultSettings());
|
||||||
|
|
||||||
|
persistLocal({ pythonSource, settingsSource: settings });
|
||||||
|
setPythonSource(pythonSource);
|
||||||
|
setSettings(settings);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const source: Source | null = useMemo(() => {
|
const source: Source | null = useMemo(() => {
|
||||||
if (pythonSource == null || settings == null) {
|
if (pythonSource == null || settings == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -82,6 +91,7 @@ export default function Chrome() {
|
||||||
version={ruffVersion}
|
version={ruffVersion}
|
||||||
onChangeTheme={setTheme}
|
onChangeTheme={setTheme}
|
||||||
onShare={handleShare}
|
onShare={handleShare}
|
||||||
|
onReset={handleResetClicked}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex grow">
|
<div className="flex grow">
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import ThemeButton from "./ThemeButton";
|
||||||
import ShareButton from "./ShareButton";
|
import ShareButton from "./ShareButton";
|
||||||
import { Theme } from "./theme";
|
import { Theme } from "./theme";
|
||||||
import VersionTag from "./VersionTag";
|
import VersionTag from "./VersionTag";
|
||||||
|
import AstralButton from "./AstralButton";
|
||||||
|
|
||||||
export default function Header({
|
export default function Header({
|
||||||
edit,
|
edit,
|
||||||
|
|
@ -11,6 +12,7 @@ export default function Header({
|
||||||
logo,
|
logo,
|
||||||
version,
|
version,
|
||||||
onChangeTheme,
|
onChangeTheme,
|
||||||
|
onReset,
|
||||||
onShare,
|
onShare,
|
||||||
}: {
|
}: {
|
||||||
edit: number | null;
|
edit: number | null;
|
||||||
|
|
@ -18,43 +20,42 @@ export default function Header({
|
||||||
logo: "ruff" | "astral";
|
logo: "ruff" | "astral";
|
||||||
version: string | null;
|
version: string | null;
|
||||||
onChangeTheme: (theme: Theme) => void;
|
onChangeTheme: (theme: Theme) => void;
|
||||||
onShare?: () => void;
|
onReset?(): void;
|
||||||
|
onShare: () => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className="
|
||||||
"w-full",
|
w-full
|
||||||
"flex",
|
flex
|
||||||
"justify-between",
|
justify-between
|
||||||
"pl-5",
|
antialiased
|
||||||
"sm:pl-1",
|
border-b
|
||||||
"pr-4",
|
border-gray-200
|
||||||
"lg:pr-6",
|
dark:border-b-radiate
|
||||||
"z-10",
|
dark:bg-galaxy
|
||||||
"top-0",
|
"
|
||||||
"left-0",
|
|
||||||
"-mb-px",
|
|
||||||
"antialiased",
|
|
||||||
"border-b",
|
|
||||||
"border-gray-200",
|
|
||||||
"dark:border-b-radiate",
|
|
||||||
"dark:bg-galaxy",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div className="py-4 pl-2">
|
<div className="py-4 pl-2">
|
||||||
<Logo name={logo} className="fill-galaxy dark:fill-radiate" />
|
<Logo name={logo} className="fill-galaxy dark:fill-radiate" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center min-w-0">
|
<div className="flex items-center min-w-0 gap-4 mx-2">
|
||||||
{version ? (
|
{version ? (
|
||||||
<div className="hidden sm:flex items-center">
|
<div className="hidden sm:flex">
|
||||||
<VersionTag>v{version}</VersionTag>
|
<VersionTag>v{version}</VersionTag>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<Divider />
|
<Divider />
|
||||||
<RepoButton />
|
<RepoButton />
|
||||||
<Divider />
|
<Divider />
|
||||||
<ShareButton key={edit} onShare={onShare} />
|
<div className="max-sm:hidden flex">
|
||||||
|
<ResetButton onClicked={onReset} />
|
||||||
|
</div>
|
||||||
|
<div className="max-sm:hidden flex">
|
||||||
|
<ShareButton key={edit} onShare={onShare} />
|
||||||
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<ThemeButton theme={theme} onChange={onChangeTheme} />
|
<ThemeButton theme={theme} onChange={onChangeTheme} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,7 +64,16 @@ export default function Header({
|
||||||
|
|
||||||
function Divider() {
|
function Divider() {
|
||||||
return (
|
return (
|
||||||
<div className="hidden sm:block mx-6 lg:mx-4 w-px h-8 bg-gray-200 dark:bg-gray-700" />
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"max-sm:hidden",
|
||||||
|
"visible",
|
||||||
|
"w-px",
|
||||||
|
"h-8",
|
||||||
|
"bg-gray-200",
|
||||||
|
"dark:bg-gray-700",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,3 +131,16 @@ function Logo({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ResetButton({ onClicked }: { onClicked?: () => void }) {
|
||||||
|
return (
|
||||||
|
<AstralButton
|
||||||
|
type="button"
|
||||||
|
className="relative flex-none leading-6 py-1.5 px-3 shadow-xs disabled:opacity-50"
|
||||||
|
disabled={onClicked == null}
|
||||||
|
onClick={onClicked}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</AstralButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import AstralButton from "./AstralButton";
|
import AstralButton from "./AstralButton";
|
||||||
|
|
||||||
export default function ShareButton({ onShare }: { onShare?: () => void }) {
|
export default function ShareButton({ onShare }: { onShare: () => void }) {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -28,15 +28,11 @@ export default function ShareButton({ onShare }: { onShare?: () => void }) {
|
||||||
<AstralButton
|
<AstralButton
|
||||||
type="button"
|
type="button"
|
||||||
className="relative flex-none leading-6 py-1.5 px-3 shadow-xs disabled:opacity-50"
|
className="relative flex-none leading-6 py-1.5 px-3 shadow-xs disabled:opacity-50"
|
||||||
disabled={!onShare || copied}
|
disabled={copied}
|
||||||
onClick={
|
onClick={() => {
|
||||||
onShare
|
setCopied(true);
|
||||||
? () => {
|
onShare();
|
||||||
setCopied(true);
|
}}
|
||||||
onShare();
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="absolute inset-0 flex items-center justify-center"
|
className="absolute inset-0 flex items-center justify-center"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function ThemeButton({
|
||||||
return (
|
return (
|
||||||
<AstralButton
|
<AstralButton
|
||||||
type="button"
|
type="button"
|
||||||
className="ml-4 sm:ml-0 dark:shadow-copied"
|
className="sm:ml-0 dark:shadow-copied"
|
||||||
onClick={() => onChange(theme === "light" ? "dark" : "light")}
|
onClick={() => onChange(theme === "light" ? "dark" : "light")}
|
||||||
>
|
>
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue