Add side drawer to display feature guide

This commit is contained in:
Nilay Majorwar 2022-02-22 02:17:55 +05:30
parent 1d8413cc7d
commit 9dcfad555d
10 changed files with 274 additions and 14 deletions

View File

@ -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;

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

179
ui/features-guide.tsx Normal file
View 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>
);
};

View File

@ -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"

View 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
View 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>
);
};