From 98006e85d38369bd75855df4215f8dc60717bc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Fri, 18 Nov 2022 10:38:34 +0000 Subject: [PATCH] feat: added help panel --- CHANGELOG.md | 1 + frontends/web/react/app.tsx | 30 ++++++++++--- frontends/web/react/components/help/help.css | 19 ++++++++ frontends/web/react/components/help/help.tsx | 44 +++++++++++++++++++ frontends/web/react/components/index.ts | 1 + .../web/react/components/modal/modal.tsx | 33 +++++++++----- frontends/web/res/help.svg | 1 + 7 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 frontends/web/react/components/help/help.css create mode 100644 frontends/web/react/components/help/help.tsx create mode 100644 frontends/web/res/help.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 877f3a71..0ccb5a5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Support for Ctrl+D (Speedup) and Ctrl+K (Keyboard toggle) shortcuts +* Help panel ### Changed diff --git a/frontends/web/react/app.tsx b/frontends/web/react/app.tsx index 67ee850a..f8f08e9f 100644 --- a/frontends/web/react/app.tsx +++ b/frontends/web/react/app.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useRef, useState } from "react"; +import React, { FC, ReactNode, useEffect, useRef, useState } from "react"; import ReactDOM from "react-dom/client"; declare const require: any; @@ -12,6 +12,7 @@ import { Display, DrawHandler, Footer, + Help, Info, KeyboardChip8, KeyboardGB, @@ -66,6 +67,7 @@ export const EmulatorApp: FC<EmulatorAppProps> = ({ const [keyaction, setKeyaction] = useState<string>(); const [modalTitle, setModalTitle] = useState<string>(); const [modalText, setModalText] = useState<string>(); + const [modalContents, setModalContents] = useState<ReactNode>(); const [modalVisible, setModalVisible] = useState(false); const [toastText, setToastText] = useState<string>(); const [toastError, setToastError] = useState(false); @@ -213,17 +215,22 @@ export const EmulatorApp: FC<EmulatorAppProps> = ({ const getBackground = () => backgrounds[backgroundIndex]; const showModal = async ( - text: string, - title = "Alert" + title = "Alert", + text?: string, + contents?: ReactNode ): Promise<boolean> => { - setModalText(text); setModalTitle(title); + setModalText(text); + setModalContents(contents); setModalVisible(true); const result = (await new Promise((resolve) => { modalCallbackRef.current = resolve; })) as boolean; return result; }; + const showHelp = async (title = "Help") => { + await showModal(title, undefined, <Help />); + }; const showToast = async (text: string, error = false, timeout = 3500) => { setToastText(text); setToastError(error); @@ -286,8 +293,8 @@ export const EmulatorApp: FC<EmulatorAppProps> = ({ const onBenchmarkClick = async () => { if (!emulator.benchmark) return; const result = await showModal( - "Are you sure you want to start a benchmark?\nThe benchmark is considered an expensive operation!", - "Confirm" + "Confirm", + "Are you sure you want to start a benchmark?\nThe benchmark is considered an expensive operation!" ); if (!result) return; const { delta, count, frequency_mhz } = emulator.benchmark(); @@ -310,6 +317,9 @@ export const EmulatorApp: FC<EmulatorAppProps> = ({ const onInformationClick = () => { setInfoVisible(!infoVisible); }; + const onHelpClick = () => { + showHelp(); + }; const onDebugClick = () => { setDebugVisible(!debugVisible); }; @@ -378,6 +388,7 @@ export const EmulatorApp: FC<EmulatorAppProps> = ({ <Modal title={modalTitle} text={modalText} + contents={modalContents} visible={modalVisible} onConfirm={onModalConfirm} onCancel={onModalCancel} @@ -621,6 +632,13 @@ export const EmulatorApp: FC<EmulatorAppProps> = ({ style={["simple", "border", "padded"]} onClick={onInformationClick} /> + <Button + text={"Help"} + image={require("../res/help.svg")} + imageAlt="help" + style={["simple", "border", "padded"]} + onClick={onHelpClick} + /> {hasFeature(Feature.Debug) && ( <Button text={"Debug"} diff --git a/frontends/web/react/components/help/help.css b/frontends/web/react/components/help/help.css new file mode 100644 index 00000000..33a52b36 --- /dev/null +++ b/frontends/web/react/components/help/help.css @@ -0,0 +1,19 @@ +.help > ul { + padding-left: 0px; + font-size: 18px; + line-height: 22px; + list-style: none; + margin: 0px 0px 0px 0px; +} + +.help > ul > li { + margin: 10px 0px 4px 0px; +} + +.help > ul .key { + border: 1px solid #ffffff; + display: inline-block; + padding: 0px 6px 0px 6px; + border-radius: 6px; + font-size: 16px; +} diff --git a/frontends/web/react/components/help/help.tsx b/frontends/web/react/components/help/help.tsx new file mode 100644 index 00000000..f7511cf9 --- /dev/null +++ b/frontends/web/react/components/help/help.tsx @@ -0,0 +1,44 @@ +import React, { FC } from "react"; + +import "./help.css"; + +type HelpProps = { + style?: string[]; +}; + +export const Help: FC<HelpProps> = ({ style = [] }) => { + const classes = () => ["help", ...style].join(" "); + return ( + <div className={classes()}> + <ul> + <li> + <span className="key">Enter</span> - Start + </li> + <li> + <span className="key">Space</span> - Select + </li> + <li> + <span className="key">A</span> - A + </li> + <li> + <span className="key">S</span> - B + </li> + <li> + <span className="key">Escape</span> - Exit fullscreen + </li> + <li> + <span className="key">Ctrl + D</span> - Turbo speed + </li> + <li> + <span className="key">Ctrl + F</span> - Toggle fullscreen + </li> + <li> + <span className="key">Ctrl + K</span> - Toggle on-screen + keyboard + </li> + </ul> + </div> + ); +}; + +export default Help; diff --git a/frontends/web/react/components/index.ts b/frontends/web/react/components/index.ts index a2980dab..693c7490 100644 --- a/frontends/web/react/components/index.ts +++ b/frontends/web/react/components/index.ts @@ -5,6 +5,7 @@ export * from "./button-switch/button-switch"; export * from "./canvas/canvas"; export * from "./display/display"; export * from "./footer/footer"; +export * from "./help/help"; export * from "./info/info"; export * from "./keyboard-chip8/keyboard-chip8"; export * from "./keyboard-gb/keyboard-gb"; diff --git a/frontends/web/react/components/modal/modal.tsx b/frontends/web/react/components/modal/modal.tsx index 7740e413..e9004167 100644 --- a/frontends/web/react/components/modal/modal.tsx +++ b/frontends/web/react/components/modal/modal.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useRef } from "react"; +import React, { FC, ReactNode, useEffect, useRef } from "react"; import Button from "../button/button"; import "./modal.css"; @@ -8,6 +8,7 @@ declare const require: any; type ModalProps = { title?: string; text?: string; + contents?: ReactNode; visible?: boolean; buttons?: boolean; overlayClose?: boolean; @@ -18,9 +19,10 @@ type ModalProps = { export const Modal: FC<ModalProps> = ({ title = "Alert", - text = "Do you confirm the following operation?", + text, + contents, visible = false, - buttons = true, + buttons, overlayClose = true, style = [], onConfirm, @@ -28,6 +30,10 @@ export const Modal: FC<ModalProps> = ({ }) => { const classes = () => ["modal", visible ? "visible" : "", ...style].join(" "); + text = + text ?? + (contents ? undefined : "Do you confirm the following operation?"); + buttons = buttons ?? (contents ? false : true); const modalRef = useRef<HTMLDivElement>(null); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { @@ -45,9 +51,12 @@ export const Modal: FC<ModalProps> = ({ modalRef.current?.focus(); } }, [visible]); - const getTextHtml = (separator = /\n/g) => ({ - __html: text.replace(separator, "<br/>") - }); + const getTextHtml = (separator = /\n/g) => + text + ? { + __html: text.replace(separator, "<br/>") + } + : undefined; const onWindowClick = ( event: React.MouseEvent<HTMLDivElement, MouseEvent> ) => { @@ -73,10 +82,14 @@ export const Modal: FC<ModalProps> = ({ /> </div> <h2 className="modal-title">{title}</h2> - <p - className="modal-text" - dangerouslySetInnerHTML={getTextHtml()} - ></p> + {contents ? ( + contents + ) : ( + <p + className="modal-text" + dangerouslySetInnerHTML={getTextHtml()} + ></p> + )} {buttons && ( <div className="modal-buttons"> <Button diff --git a/frontends/web/res/help.svg b/frontends/web/res/help.svg new file mode 100644 index 00000000..d450cbaa --- /dev/null +++ b/frontends/web/res/help.svg @@ -0,0 +1 @@ +<svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" viewBox="0 0 24 24" aria-labelledby="helpIconTitle" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#ffffff"> <title id="helpIconTitle">Help</title> <path d="M12 14C12 12 13.576002 11.6652983 14.1186858 11.1239516 14.663127 10.5808518 15 9.82976635 15 9 15 7.34314575 13.6568542 6 12 6 11.1040834 6 10.2998929 6.39272604 9.75018919 7.01541737 9.49601109 7.30334431 9.29624369 7.64043912 9.16697781 8.01061095"/> <line x1="12" y1="17" x2="12" y2="17"/> <circle cx="12" cy="12" r="10"/> </svg> \ No newline at end of file -- GitLab