Add side drawer to display feature guide
This commit is contained in:
parent
1d8413cc7d
commit
9dcfad555d
@ -1,3 +1,4 @@
|
||||
import React from "react";
|
||||
import "../styles/globals.css";
|
||||
import "../styles/editor.css";
|
||||
import "../styles/mosaic.scss";
|
||||
@ -5,17 +6,24 @@ import "@blueprintjs/core/lib/css/blueprint.css";
|
||||
import "@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
import "react-mosaic-component/react-mosaic-component.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import { DarkModeProvider } from "../ui/providers/dark-mode-provider";
|
||||
import { ErrorBoundaryProvider } from "../ui/providers/error-boundary-provider";
|
||||
import { Providers } from "../ui/providers";
|
||||
import { NextPage } from "next";
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<ErrorBoundaryProvider>
|
||||
<DarkModeProvider>
|
||||
<Component {...pageProps} />
|
||||
</DarkModeProvider>
|
||||
</ErrorBoundaryProvider>
|
||||
);
|
||||
/** Type for pages that use a custom layout */
|
||||
export type NextPageWithLayout = NextPage & {
|
||||
getLayout?: (page: React.ReactNode) => React.ReactNode;
|
||||
};
|
||||
|
||||
/** AppProps type but extended for custom layouts */
|
||||
type AppPropsWithLayout = AppProps & {
|
||||
Component: NextPageWithLayout;
|
||||
};
|
||||
|
||||
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
// Use the layout defined at the page level, if available
|
||||
const getLayout =
|
||||
Component.getLayout ?? ((page) => <Providers>{page}</Providers>);
|
||||
return getLayout(<Component {...pageProps} />);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import { NextPageWithLayout } from "./_app";
|
||||
import Head from "next/head";
|
||||
import logoImg from "../ui/assets/logo.png";
|
||||
import Image from "next/image";
|
||||
@ -8,6 +8,7 @@ import Link from "next/link";
|
||||
import { useDarkMode } from "../ui/providers/dark-mode-provider";
|
||||
import LANGUAGES from "./languages.json";
|
||||
import { GitHubIcon } from "../ui/custom-icons";
|
||||
import { Providers } from "../ui/providers";
|
||||
|
||||
const REPO_URL = "https://github.com/nilaymaj/esolang-park";
|
||||
const WIKI_URL = REPO_URL + "/wiki";
|
||||
@ -50,7 +51,7 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
const Index: NextPage = () => {
|
||||
const Index: NextPageWithLayout = () => {
|
||||
const DarkMode = useDarkMode();
|
||||
const backgroundColor = DarkMode.isDark ? Colors.DARK_GRAY3 : Colors.WHITE;
|
||||
|
||||
@ -113,4 +114,9 @@ const Index: NextPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// Feature guide should not be shown on the home page
|
||||
Index.getLayout = function getLayout(page: React.ReactNode) {
|
||||
return <Providers omitFeatureGuide={true}>{page}</Providers>;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
BIN
ui/assets/guide-breakpoints.png
Normal file
BIN
ui/assets/guide-breakpoints.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
ui/assets/guide-exec-controls.png
Normal file
BIN
ui/assets/guide-exec-controls.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
BIN
ui/assets/guide-info-btn.png
Normal file
BIN
ui/assets/guide-info-btn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
BIN
ui/assets/guide-syntax-check.png
Normal file
BIN
ui/assets/guide-syntax-check.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
179
ui/features-guide.tsx
Normal file
179
ui/features-guide.tsx
Normal file
@ -0,0 +1,179 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Text, Classes, Drawer, TextProps, Divider } from "@blueprintjs/core";
|
||||
import breakpointsImg from "./assets/guide-breakpoints.png";
|
||||
import execControlsImg from "./assets/guide-exec-controls.png";
|
||||
import infoBtnImg from "./assets/guide-info-btn.png";
|
||||
import syntaxCheckImg from "./assets/guide-syntax-check.png";
|
||||
|
||||
const BigText = (props: TextProps & { children: React.ReactNode }) => {
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<Text className={Classes.RUNNING_TEXT} {...rest}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const SideBySideSection = (props: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
image: JSX.Element;
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
<h3>{props.title}</h3>
|
||||
</Text>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<div style={{ flexShrink: 0, width: "30%", marginRight: 20 }}>
|
||||
<div style={{ borderRadius: 10, overflowY: "hidden" }}>
|
||||
{props.image}
|
||||
</div>
|
||||
</div>
|
||||
<BigText>{props.children}</BigText>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SyntaxCheckSection = () => {
|
||||
return (
|
||||
<SideBySideSection
|
||||
title="Live syntax checking"
|
||||
image={<Image src={syntaxCheckImg} alt="syntax checking" />}
|
||||
>
|
||||
<p>
|
||||
Esolang Park checks the syntax of your source code{" "}
|
||||
<b>while you're typing</b>. Any errors in the syntax of your program are
|
||||
marked in the editor with a (mostly) useful error message, without
|
||||
needing to run the program.
|
||||
</p>
|
||||
</SideBySideSection>
|
||||
);
|
||||
};
|
||||
|
||||
const BreakpointsSection = () => {
|
||||
return (
|
||||
<SideBySideSection
|
||||
title="Set breakpoints in your code"
|
||||
image={<Image src={breakpointsImg} alt="breakpoints" />}
|
||||
>
|
||||
<p>
|
||||
Esolang Park allows you to <b>set debugging breakpoints in your code</b>
|
||||
. When you run your program and execution reaches a line with
|
||||
breakpoint, the program will pause and you can inspect the state of the
|
||||
program.
|
||||
</p>
|
||||
<p>
|
||||
Click on the left of any line number to set a breakpoint on that line.
|
||||
Click on the red circle to remove the breakpoint.
|
||||
</p>
|
||||
</SideBySideSection>
|
||||
);
|
||||
};
|
||||
|
||||
const ExecControlsSection = () => {
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
<h3>Pause, inspect and step through execution</h3>
|
||||
</Text>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "60%",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
borderRadius: 10,
|
||||
overflowY: "hidden",
|
||||
}}
|
||||
>
|
||||
<Image src={execControlsImg} alt="execution controls" />
|
||||
</div>
|
||||
</div>
|
||||
<BigText>
|
||||
<p>
|
||||
When your run a program, the "Run code" button changes to the
|
||||
execution controls. Pause execution to{" "}
|
||||
<b>
|
||||
inspect the runtime state, start stepping through execution, change
|
||||
the execution interval or stop execution entirely
|
||||
</b>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
The execution interval dictates how fast your program should be run by
|
||||
Esolang Park. The smallest execution interval currently supported is
|
||||
5ms.
|
||||
</p>
|
||||
</BigText>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InfoSection = () => {
|
||||
return (
|
||||
<SideBySideSection
|
||||
title={""}
|
||||
image={<Image src={infoBtnImg} alt="info buttons" />}
|
||||
>
|
||||
<p>
|
||||
Click the document button to read a short introduction and view
|
||||
reference links for the esolang. This also contains{" "}
|
||||
<b>notes about the implementation of this esolang</b>, including any
|
||||
incompatibilities and quirks you should take care of while debugging
|
||||
your code.
|
||||
</p>
|
||||
<p>
|
||||
Click the question mark button to <b>view this guide</b> at any point.
|
||||
</p>
|
||||
</SideBySideSection>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const FeaturesGuide = (props: Props) => {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={props.isOpen}
|
||||
onClose={props.onClose}
|
||||
title="Esolang Park"
|
||||
style={{ overflowY: "auto" }}
|
||||
>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<BigText>
|
||||
<p>
|
||||
Esolang Park is an online <b>interpreter and debugger interface</b>{" "}
|
||||
for esoteric programming languages. Think Repl.it, but a simpler
|
||||
version for esoteric languages, with a visual debugger catered to
|
||||
each language, that runs in your browser.
|
||||
</p>
|
||||
<p>
|
||||
The goal of Esolang Park is to be a platform for esolang enthusiasts
|
||||
to test and debug their code more easily, as well as for other
|
||||
people to discover and play around with esoteric languages without
|
||||
leaving the browser.
|
||||
</p>
|
||||
<p>
|
||||
Esolang Park is <b>very early in development</b>, things are by no
|
||||
means optimal, and there are most certainly bugs hanging around in
|
||||
the source code. If you catch one, please create an issue on GitHub!
|
||||
</p>
|
||||
</BigText>
|
||||
<Divider style={{ margin: 20 }} />
|
||||
<SyntaxCheckSection />
|
||||
<Divider style={{ margin: 20 }} />
|
||||
<BreakpointsSection />
|
||||
<Divider style={{ margin: 20 }} />
|
||||
<ExecControlsSection />
|
||||
<Divider style={{ margin: 20 }} />
|
||||
<InfoSection />
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
@ -3,6 +3,7 @@ import logoImg from "./assets/logo.png";
|
||||
import { GitHubIcon } from "./custom-icons";
|
||||
import { useDarkMode } from "./providers/dark-mode-provider";
|
||||
import { Button, Card, Icon, Tag } from "@blueprintjs/core";
|
||||
import { useFeaturesGuide } from "./providers/features-guide-provider";
|
||||
|
||||
/** Link to the project's GitHub repository */
|
||||
const REPO_LINK = "https://github.com/nilaymaj/esolang-park";
|
||||
@ -19,6 +20,7 @@ type Props = {
|
||||
|
||||
export const Header = (props: Props) => {
|
||||
const DarkMode = useDarkMode();
|
||||
const featuresGuide = useFeaturesGuide();
|
||||
|
||||
const brandSection = (
|
||||
<div
|
||||
@ -51,10 +53,16 @@ export const Header = (props: Props) => {
|
||||
<div style={{ flex: 1, textAlign: "right", paddingRight: 8 }}>
|
||||
<a
|
||||
href={NOTES_LINK(props.langId)}
|
||||
title="View implementation notes for this esolang"
|
||||
title="View the notes for this esolang"
|
||||
>
|
||||
<Button minimal icon={<Icon icon="info-sign" />} />
|
||||
<Button minimal icon={<Icon icon="document" />} />
|
||||
</a>
|
||||
<Button
|
||||
minimal
|
||||
title="View the features guide"
|
||||
icon={<Icon icon="help" />}
|
||||
onClick={featuresGuide.show}
|
||||
/>
|
||||
<Button
|
||||
minimal
|
||||
title="Toggle between dark and light mode"
|
||||
|
37
ui/providers/features-guide-provider.tsx
Normal file
37
ui/providers/features-guide-provider.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { FeaturesGuide } from "../features-guide";
|
||||
|
||||
/** Local storage key that stores boolean for whether guide has been closed by user */
|
||||
const GUIDE_CLOSED_KEY = "guide-closed";
|
||||
|
||||
const FeaturesGuideContext = React.createContext<{
|
||||
show: () => void;
|
||||
}>({ show: () => {} });
|
||||
|
||||
/** Context provider for showing the features guide dialog */
|
||||
export const FeaturesGuideProvider = (props: { children: React.ReactNode }) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const hasClosedGuide = window.localStorage.getItem(GUIDE_CLOSED_KEY);
|
||||
if (hasClosedGuide == null) setIsOpen(true);
|
||||
});
|
||||
|
||||
return (
|
||||
<FeaturesGuideContext.Provider value={{ show: () => setIsOpen(true) }}>
|
||||
{props.children}
|
||||
{isOpen && (
|
||||
<FeaturesGuide
|
||||
isOpen={isOpen}
|
||||
onClose={() => {
|
||||
window.localStorage.setItem(GUIDE_CLOSED_KEY, "true");
|
||||
setIsOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</FeaturesGuideContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/** Utility hook to show the features guide */
|
||||
export const useFeaturesGuide = () => React.useContext(FeaturesGuideContext);
|
22
ui/providers/index.tsx
Normal file
22
ui/providers/index.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { DarkModeProvider } from "./dark-mode-provider";
|
||||
import { ErrorBoundaryProvider } from "./error-boundary-provider";
|
||||
import { FeaturesGuideProvider } from "./features-guide-provider";
|
||||
|
||||
type Props = {
|
||||
omitFeatureGuide?: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const Providers = (props: Props) => {
|
||||
return (
|
||||
<DarkModeProvider>
|
||||
<ErrorBoundaryProvider>
|
||||
{props.omitFeatureGuide ? (
|
||||
props.children
|
||||
) : (
|
||||
<FeaturesGuideProvider>{props.children}</FeaturesGuideProvider>
|
||||
)}
|
||||
</ErrorBoundaryProvider>
|
||||
</DarkModeProvider>
|
||||
);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user