Skip to content

Commit

Permalink
Add developer menu (#1029)
Browse files Browse the repository at this point in the history
  • Loading branch information
georgefst authored Aug 16, 2023
2 parents fc0439c + aac4a7b commit 9e2e873
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 40 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"deep-equal": "^2.2.2",
"fp-ts": "^2.16.1",
"history": "^5.3.0",
"re-resizable": "^6.9.9",
"react": "^18.2.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 84 additions & 12 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { Suspense, lazy, useEffect, useState } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { CookiesProvider, useCookies } from "react-cookie";
import { CookieSetOptions } from "universal-cookie";
import { v4 as uuidv4 } from "uuid";
import { WrenchScrewdriverIcon } from "@heroicons/react/24/outline";
import { Resizable } from "re-resizable";

import "@/index.css";

import { ChooseSession, Edit, NoMatch } from "@/components";
import { DevOptions } from "@/components/Edit";

const ReactQueryDevtoolsProduction = lazy(() =>
// This ensures that we don't unnecessarily load the tools in production.
// https://tanstack.com/query/v4/docs/react/devtools#devtools-in-production
const ReactQueryDevtoolsPanel = lazy(() =>
import("@tanstack/react-query-devtools/build/lib/index.prod.js").then(
(d) => ({
default: d.ReactQueryDevtools,
default: d.ReactQueryDevtoolsPanel,
})
)
);
Expand All @@ -33,7 +37,16 @@ const idCookieOptions = (path: string): CookieSetOptions => {

const App = (): JSX.Element => {
const [cookies, setCookie] = useCookies(["id"]);
const [showDevtools, setShowDevtools] = useState(false);
const [enableDevtools, setEnableDevtools] = useState(import.meta.env.DEV);
const [devtoolsOpen, setDevtoolsOpen] = useState(false);

const devToolsMinHeight = 250;
const devToolsMaxHeight = 500;
const [devOpts, setDevOpts] = useState<DevOptions>({
showIDs: false,
inlineLabels: false,
alwaysShowLabels: true,
});

useEffect(() => {
if (!cookies.id) {
Expand All @@ -48,31 +61,90 @@ const App = (): JSX.Element => {
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.toggleDevtools = () => setShowDevtools((old) => !old);
window.toggleDevtools =
// This comment forces a line break to limit the scope of `@ts-ignore`.
() => setEnableDevtools((old) => !old);
}, []);

return (
<CookiesProvider>
<BrowserRouter>
<QueryClientProvider client={queryClient}>
{enableDevtools && (
<Suspense fallback={null}>
<button
className="absolute right-0 z-50 p-4"
onClick={() => setDevtoolsOpen((old) => !old)}
>
<WrenchScrewdriverIcon className="h-10 fill-grey-primary"></WrenchScrewdriverIcon>
</button>
{devtoolsOpen && (
<Resizable
enable={{ bottom: true }}
defaultSize={{ height: devToolsMinHeight, width: "100%" }}
className="fixed grid grid-cols-[minmax(0,2fr)_1fr]"
minHeight={devToolsMinHeight}
maxHeight={devToolsMaxHeight}
>
<ReactQueryDevtoolsPanel
style={{ height: "inherit", maxHeight: devToolsMaxHeight }}
setIsOpen={setDevtoolsOpen}
onDragStart={(_) => {}}
/>
<DevMenu opts={devOpts} set={setDevOpts} />
</Resizable>
)}
</Suspense>
)}
<Routes>
<Route path="/" element={<Navigate to="/sessions" />} />
<Route path="/sessions">
<Route index element={<ChooseSession />} />
<Route path=":sessionId" element={<Edit />} />
<Route path=":sessionId" element={<Edit {...devOpts} />} />
</Route>
<Route path="*" element={<NoMatch />} />
</Routes>
<ReactQueryDevtools initialIsOpen position="top-left" />
{showDevtools && (
<Suspense fallback={null}>
<ReactQueryDevtoolsProduction position="top-left" />
</Suspense>
)}
</QueryClientProvider>
</BrowserRouter>
</CookiesProvider>
);
};

const DevMenu = (p: { opts: DevOptions; set: (opts: DevOptions) => void }) => (
<div className="bg-blue-primary pl-1 text-white-primary">
<div>
<input
type="checkbox"
id="showIDs"
checked={p.opts.showIDs}
onChange={(e) => p.set({ ...p.opts, showIDs: e.target.checked })}
className="mr-1"
/>
<label htmlFor="showIDs">show node IDs</label>
</div>
<div>
<input
type="checkbox"
id="alwaysShowLabels"
checked={p.opts.alwaysShowLabels}
onChange={(e) =>
p.set({ ...p.opts, alwaysShowLabels: e.target.checked })
}
className="mr-1"
/>
<label htmlFor="alwaysShowLabels">always show labels</label>
</div>
<div>
<input
type="checkbox"
id="inlineLabels"
checked={p.opts.inlineLabels}
onChange={(e) => p.set({ ...p.opts, inlineLabels: e.target.checked })}
className="mr-1"
/>
<label htmlFor="inlineLabels">inline labels</label>
</div>
</div>
);

export default App;
41 changes: 27 additions & 14 deletions src/components/Edit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ import { Mode } from "../Toolbar";
// hardcoded values (for now)
const initialLevel: Level = "Expert";

const Edit = (): JSX.Element => {
export type DevOptions = {
showIDs: boolean;
inlineLabels: boolean;
alwaysShowLabels: boolean;
};

const Edit = (devOpts: DevOptions): JSX.Element => {
const params = useParams();
const sessionId = params["sessionId"];

Expand Down Expand Up @@ -88,10 +94,20 @@ const Edit = (): JSX.Element => {
}

// At this point, we have successfully received an initial program.
return <AppProg initialProg={queryRes.data} {...{ sessionId }}></AppProg>;
return (
<AppProg
initialProg={queryRes.data}
{...{ sessionId }}
devOpts={devOpts}
></AppProg>
);
};

const AppProg = (p: { sessionId: string; initialProg: Prog }): JSX.Element => {
const AppProg = (p: {
sessionId: string;
initialProg: Prog;
devOpts: DevOptions;
}): JSX.Element => {
const [prog, setProg0] = useState<Prog>(p.initialProg);
const [selection, setSelection0] = useState<Selection | undefined>(
prog.selection
Expand Down Expand Up @@ -140,6 +156,7 @@ const AppProg = (p: { sessionId: string; initialProg: Prog }): JSX.Element => {
setProg={setProg}
undoAvailable={prog.undoAvailable}
redoAvailable={prog.redoAvailable}
devOpts={p.devOpts}
/>
);
};
Expand Down Expand Up @@ -199,8 +216,9 @@ const AppNoError = ({
setProg: (p: Prog) => void;
undoAvailable: boolean;
redoAvailable: boolean;
devOpts: DevOptions;
}): JSX.Element => {
const initialMode = "tree 1";
const initialMode = "tree";
const [mode, setMode] = useState<Mode>(initialMode);
const [level, setLevel] = useState<Level>(initialLevel);
const toggleLevel = (): void => {
Expand Down Expand Up @@ -269,16 +287,9 @@ const AppNoError = ({
.sort((a, b) => cmpName(a.name, b.name))
.map((d) => d.name.baseName);

const treeProps = (() => {
switch (mode) {
case "text":
return defaultTreeReactFlowProps;
case "tree 1":
return defaultTreeReactFlowProps;
case "tree 2":
return inlineTreeReactFlowProps;
}
})();
const treeProps = p.devOpts.inlineLabels
? inlineTreeReactFlowProps
: defaultTreeReactFlowProps;

return (
<div className="grid h-[100dvh] grid-cols-[auto_20rem]">
Expand All @@ -288,6 +299,8 @@ const AppNoError = ({
scrollToDefRef={scrollToDefRef}
scrollToTypeDefRef={scrollToTypeDefRef}
{...treeProps}
showIDs={p.devOpts.showIDs}
alwaysShowLabels={p.devOpts.alwaysShowLabels}
{...(selection && { selection })}
onNodeClick={(_e, sel) => sel && setSelection(sel)}
defs={p.module.defs}
Expand Down
11 changes: 4 additions & 7 deletions src/components/Toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type ToolbarProps = {
undoAvailable: boolean;
onClickUndo: MouseEventHandler<HTMLButtonElement>;
};
export type Mode = "text" | "tree 1" | "tree 2";
export type Mode = "text" | "tree";

const iconClasses = "stroke-[2] p-1";
const heavyIconClasses = "w-7 stroke-[3] p-1";
Expand All @@ -32,19 +32,16 @@ const modeSvg = (m: Mode) => {
switch (m) {
case "text":
return <CodeBracketIcon className={iconClasses} />;
case "tree 1":
case "tree 2":
case "tree":
return <ShareIcon className={classNames(iconClasses, "rotate-90")} />;
}
};

const nextMode = (m: Mode): Mode => {
switch (m) {
case "text":
return "tree 1";
case "tree 1":
return "tree 2";
case "tree 2":
return "tree";
case "tree":
return "text";
}
};
Expand Down
3 changes: 3 additions & 0 deletions src/components/TreeReactFlow/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,18 +162,21 @@ export type PrimerNodeProps = {
flavor: NodeFlavorTextBody | NodeFlavorPrimBody | NodeFlavorNoBody;
contents: string;
hideLabel: boolean;
showIDs: boolean;
};

/** Properties for a simple node. */
export type PrimerSimpleNodeProps = {
nodeData: NodeData;
flavor: NodeFlavorNoBody;
showIDs: boolean;
};

/** Properties for a box node. */
export type PrimerBoxNodeProps = {
nodeData: NodeData;
flavor: NodeFlavorBoxBody;
showIDs: boolean;
};

/** Properties for the special definition name node. */
Expand Down
Loading

0 comments on commit 9e2e873

Please sign in to comment.