From 26ddb1a16aa5bbafffbd65821fa3e985c34b7073 Mon Sep 17 00:00:00 2001 From: philer Date: Mon, 13 Dec 2021 02:13:19 +0100 Subject: [PATCH] Add help with keyboard controls & about page --- localization/de.js | 6 ++ localization/en.js | 6 ++ src/global.scss | 1 + src/localization.ts | 6 ++ src/ui/Help.module.scss | 69 ++++++++++++++++++++++ src/ui/Help.tsx | 101 ++++++++++++++++++++++++++++++++ src/ui/Icon.tsx | 4 ++ src/ui/LanguageHelp.module.scss | 2 +- src/ui/Tabs.module.scss | 27 +++++++++ src/ui/Tabs.tsx | 34 +++++++++++ src/ui/WorldControls.tsx | 2 +- src/ui/WorldPanel.module.scss | 23 ++++---- src/ui/WorldPanel.tsx | 39 ++++++++++-- 13 files changed, 299 insertions(+), 21 deletions(-) create mode 100644 src/ui/Help.module.scss create mode 100644 src/ui/Help.tsx create mode 100644 src/ui/Tabs.module.scss create mode 100644 src/ui/Tabs.tsx diff --git a/localization/de.js b/localization/de.js index c5d1d7d..f23f0c5 100644 --- a/localization/de.js +++ b/localization/de.js @@ -85,6 +85,12 @@ config({ turnAround: "umdrehen", }, }, + help: { + controls: "Steuerung", + about: "Info", + builtin: "Anweisung", + key: "Taste", + }, error: { browser_feature_not_available: "Der Browser ist veraltet und unterstützt diese Funktionalität nicht.", invalid_world_file: "Das ist keine valide *.kdw Datei.", diff --git a/localization/en.js b/localization/en.js index 82e06bf..00fcc47 100644 --- a/localization/en.js +++ b/localization/en.js @@ -88,6 +88,12 @@ config({ turnAround: "turnAround", }, }, + help: { + controls: "Controls", + about: "About", + builtin: "Command", + key: "Key", + }, error: { browser_feature_not_available: "Your browser does not support his feature. Consider switch to an up-to-date browser.", invalid_world_file: "This does not appear to be a valid *.kdw file.", diff --git a/src/global.scss b/src/global.scss index cd04a81..954a5ef 100644 --- a/src/global.scss +++ b/src/global.scss @@ -9,6 +9,7 @@ --font-family: Roboto, Open Sans, Ubuntu, Verdana, Arial, Helvetica, sans-serif; --font-family-mono: "FiraCode", Consolas, Monaco, monospace; --border-radius: 3px; + --accent-color: #0c8; } * { diff --git a/src/localization.ts b/src/localization.ts index 717db63..952c9ea 100644 --- a/src/localization.ts +++ b/src/localization.ts @@ -137,6 +137,12 @@ export type Translations = { turnAround: string } } + help: { + controls: string + about: string + builtin: string + key: string + } error: { browser_feature_not_available: string invalid_world_file: string diff --git a/src/ui/Help.module.scss b/src/ui/Help.module.scss new file mode 100644 index 0000000..3d6fcc3 --- /dev/null +++ b/src/ui/Help.module.scss @@ -0,0 +1,69 @@ +.root { + line-height: 1.5em; + + h3, h4 { + margin: 1.5em 0 1em; + &:first-child { + margin-top: 0; + } + } + p, ul, dl { + margin: 0 0 1em; + } + ul { + padding: 0 1.5em; + list-style-type: square; + } + dl { + display: grid; + grid-template-columns: auto auto; + gap: .5em 0; + align-items: center; + } + dd { + margin: 0; + } + dt { + display: flex; + gap: .5em; + margin: 0; + justify-content: center; + align-items: center; + + > span { + border: 2px solid #aaa; + border-radius: var(--border-radius); + height: 2em; + width: 2em; + display: flex; + justify-content: center; + align-items: center; + } + } + .dlTitle { + font-weight: bold; + } + a { + color: #8af; + &:hover, &:focus, &:active { + color: #68f; + } + } +} + +.content { + width: 20em; + max-height: calc(80vh - 5rem); + overflow-x: hidden; + overflow-y: auto; + padding: 1em 1em 0; + user-select: text; +} + +.urlLink { + font-family: var(--font-family-mono); + font-size: .75em; + // margin: .25em; + display: block; + line-height: 1.25em; +} diff --git a/src/ui/Help.tsx b/src/ui/Help.tsx new file mode 100644 index 0000000..6834fab --- /dev/null +++ b/src/ui/Help.tsx @@ -0,0 +1,101 @@ +import {h, JSX} from "preact" + +import {translate as t} from "../localization" +import {IconArrowDown, IconArrowLeft, IconArrowRight, IconArrowUp} from "./Icon" +import {Tab, Tabs} from "./Tabs" +import {keyMap} from "./WorldControls" + +import * as classes from "./Help.module.scss" + +const keyIcons: Record = { + ArrowUp: , + ArrowDown: , + ArrowLeft: , + ArrowRight: , +} + +const inverseKeyMap = Object.entries(keyMap).reduce((acc, [key, builtin]) => ({ + ...acc, + [builtin]: acc[builtin] ? [...acc[builtin], key] : [key], +}), {} as Record) + +export const Help = () => +
+ + + +
+
{t("help.builtin")}
+
{t("help.key")}
+ {Object.entries(inverseKeyMap).flatMap(([builtin, keys]) => [ +
+ + + {t(`language.builtins.${builtin}`)[0]} + + () + +
, +
+ {keys.flatMap((key, idx, {length}) => [ + {keyIcons[key] || key}, + idx < length - 1 && t("or"), + ])} +
, + ])} +
+
+ + +
+

+ Online Karol is inspired by + {" "}Robot karol. +

+

+ To follow the development, visit + {" "} + github.com/philer/karol + . + You can also download a release and host it at your + school with custom settings. +

+

+ If you find any bugs or you have a suggestion, + please report an + {" "}issue{" "} + or send an email to karol at philer dot org. +

+

+ You can also help translate online Karol into more languages! +

+ +

Acknowledgements

+
    +
  • + "botty" sprite theme: + https://opengameart.org/content/botty +
  • +
  • + "neoz7" sprite theme: + https://neoz7.deviantart.com/art/NEW-Iso-Tiles-Free-487740820 +
  • +
  • + "Fira Code" monospace font: + https://github.com/tonsky/FiraCode +
  • +
  • + "noisejs" library: + https://github.com/josephg/noisejs +
  • +
+ +
+
+ +
+
+ + +const Link = ({children}: {children: string}) => + {children} diff --git a/src/ui/Icon.tsx b/src/ui/Icon.tsx index 891575f..0979287 100644 --- a/src/ui/Icon.tsx +++ b/src/ui/Icon.tsx @@ -10,6 +10,8 @@ import { // see https://fontawesome.com/how-to-use/use-with-node-js#tree-shaking import { faArrowDown, + faArrowLeft, + faArrowRight, faArrowUp, faCheck, faCheckSquare, @@ -57,6 +59,8 @@ export type IconProps = IconParams & PartialRecord & { type IP = IconProps export const IconArrowDown = (ip: IP) => +export const IconArrowLeft = (ip: IP) => +export const IconArrowRight = (ip: IP) => export const IconArrowUp = (ip: IP) => export const IconCheck = (ip: IP) => export const IconCheckSquare = (ip: IP) => diff --git a/src/ui/LanguageHelp.module.scss b/src/ui/LanguageHelp.module.scss index e77e0a9..3215376 100644 --- a/src/ui/LanguageHelp.module.scss +++ b/src/ui/LanguageHelp.module.scss @@ -49,7 +49,7 @@ &:hover, &:focus, &:active { background-color: #444; - color: #0c8; + color: var(--accent-color); } } } diff --git a/src/ui/Tabs.module.scss b/src/ui/Tabs.module.scss new file mode 100644 index 0000000..096eb02 --- /dev/null +++ b/src/ui/Tabs.module.scss @@ -0,0 +1,27 @@ +.tabs { + // display: flex; + // justify-content: stretch; + display: grid; + grid-auto-columns: 1fr; + grid-auto-flow: column; + + > button { + flex: 1 1 auto; + padding: .5em 1em; + border: none; + border-bottom: 2px solid #666; + font-size: 1.2em; + text-align: center; + background-color: transparent; + + &:hover { + background-color: #fff2; + border-bottom-color: #888; + } + &:disabled { + background-color: #fff1; + color: white; + border-bottom-color: var(--accent-color); + } + } +} diff --git a/src/ui/Tabs.tsx b/src/ui/Tabs.tsx new file mode 100644 index 0000000..b1ec19c --- /dev/null +++ b/src/ui/Tabs.tsx @@ -0,0 +1,34 @@ +import {ComponentChild, Fragment, h, VNode} from "preact" +import {useState} from "preact/hooks" + +import * as classes from "./Tabs.module.scss" + +export type TabProps = { + children: ComponentChild, + title: string, +} + +export const Tab = ({children}: TabProps) => <>{children} + +export type TabsProps = { + children: VNode | VNode[] +} + +export const Tabs = ({children}: TabsProps) => { + const childArray = Array.isArray(children) ? children : [children] + const [selected, setSelected] = useState(0) + return ( + <> + + {childArray[selected]} + + ) +} diff --git a/src/ui/WorldControls.tsx b/src/ui/WorldControls.tsx index 1e47905..bdce5f2 100644 --- a/src/ui/WorldControls.tsx +++ b/src/ui/WorldControls.tsx @@ -12,7 +12,7 @@ import {Tooltip} from "./Tooltip" import * as classes from "./WorldControls.module.scss" -const keyMap: Record = { +export const keyMap: Readonly> = { ArrowUp: "step", ArrowDown: "stepBackwards", ArrowLeft: "turnLeft", diff --git a/src/ui/WorldPanel.module.scss b/src/ui/WorldPanel.module.scss index 0f0cfe4..0f8ee10 100644 --- a/src/ui/WorldPanel.module.scss +++ b/src/ui/WorldPanel.module.scss @@ -60,17 +60,15 @@ place-self: start end; padding: 1em; } -.tools .separator { - margin: .5em .33em; - border: 1px solid white; - border-right-color: #ddd; +.toolsPopover { + margin-top: .5em; } - .settings { - margin-top: .5em; padding: 1em 1em 0; h3 { margin: 0 0 1rem; + font-size: 1.5em; + font-weight: normal; } fieldset { border: none; @@ -103,10 +101,9 @@ } .numberInput { --base-color: #666; - --highlight-color: #0c8; &:hover { > input[type="number"], > button { - border-color: var(--highlight-color); + border-color: var(--accent-color); } } > button { @@ -126,7 +123,7 @@ &:hover, &:focus, &:active { background-color: #666a; - color: var(--highlight-color); + color: var(--accent-color); } &:first-of-type { order: 1; @@ -156,14 +153,14 @@ &:hover, &:focus, &:active { outline: none; - border-color: var(--highlight-color); + border-color: var(--accent-color); & ~ button { - border-color: var(--highlight-color); + border-color: var(--accent-color); } } &:focus { font-weight: bold; - color: var(--highlight-color); + color: var(--accent-color); } } } @@ -177,7 +174,7 @@ color: #aaa; } &:hover { - color: #0c8; + color: var(--accent-color); } } > input { diff --git a/src/ui/WorldPanel.tsx b/src/ui/WorldPanel.tsx index 0dd5bf8..2977290 100644 --- a/src/ui/WorldPanel.tsx +++ b/src/ui/WorldPanel.tsx @@ -4,10 +4,11 @@ import {useContext, useEffect, useRef, useState} from "preact/hooks" import * as graphics from "../graphics" import {translate as t} from "../localization" import {checkKdwFormat, World} from "../simulation/world" -import {clamp, defaultPreventer} from "../util" +import {clamp, clsx, defaultPreventer} from "../util" import {readFile, saveTextAs} from "../util/files" import type {ChangeEvent} from "../util/types" -import {IconCheckSquare, IconCog, IconMinus, IconPlus, IconRobot, IconSquare} from "./Icon" +import {Help} from "./Help" +import {IconCheckSquare, IconCog, IconMinus, IconPlus, IconQuestion, IconRobot, IconSquare} from "./Icon" import {Logging, LogOutput} from "./Logging" import {Popover} from "./Popover" import {WorldControls} from "./WorldControls" @@ -29,7 +30,10 @@ export const WorldPanel = ({onChange, isSimulationRunning}: WorldPanelProps) => height: 5, }) const [isSettingsVisible, setIsSettingsVisible] = useState(false) - const toolsToggleRef = useRef(null) + const settingsToggleRef = useRef(null) + const [isHelpVisible, setIsHelpVisible] = useState(false) + const helpToggleRef = useRef(null) + const [world, setWorld] = useState(new World(width, length, height)) const [showFlat, setShowFlat] = useState(false) const [showPlayer, setShowPlayer] = useState(true) @@ -78,15 +82,19 @@ export const WorldPanel = ({onChange, isSimulationRunning}: WorldPanelProps) =>

{t("world.world")}