From d747a5e8dc585c31679d6593a325cba365e4fada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Sat, 19 Nov 2022 21:13:28 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20moved=20emukit=20out=20into=20a=20separ?= =?UTF-8?q?ate=20lib=20=F0=9F=8F=8B=EF=B8=8F=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontends/web/index.ts | 4 +- frontends/web/package.json | 11 +- frontends/web/react/app.css | 20 - frontends/web/react/app.tsx | 738 ------------------ .../button-container/button-container.css | 12 - .../button-container/button-container.tsx | 18 - .../button-increment/button-increment.css | 15 - .../button-increment/button-increment.tsx | 85 -- .../button-switch/button-switch.tsx | 33 - .../web/react/components/button/button.css | 123 --- .../web/react/components/button/button.tsx | 95 --- .../web/react/components/canvas/canvas.css | 0 .../web/react/components/canvas/canvas.tsx | 74 -- .../web/react/components/display/display.css | 116 --- .../web/react/components/display/display.tsx | 306 -------- .../web/react/components/display/minimise.svg | 1 - .../web/react/components/footer/footer.css | 24 - .../web/react/components/footer/footer.tsx | 28 - frontends/web/react/components/help/help.css | 53 -- frontends/web/react/components/help/help.tsx | 142 ---- frontends/web/react/components/index.ts | 23 - frontends/web/react/components/info/info.css | 30 - frontends/web/react/components/info/info.tsx | 26 - .../keyboard-chip8/keyboard-chip8.css | 51 -- .../keyboard-chip8/keyboard-chip8.tsx | 99 --- .../components/keyboard-gb/keyboard-gb.css | 257 ------ .../components/keyboard-gb/keyboard-gb.tsx | 332 -------- frontends/web/react/components/link/link.css | 15 - frontends/web/react/components/link/link.tsx | 28 - .../web/react/components/modal/close.svg | 1 - .../web/react/components/modal/modal.css | 149 ---- .../web/react/components/modal/modal.tsx | 114 --- .../web/react/components/overlay/overlay.css | 44 -- .../web/react/components/overlay/overlay.tsx | 69 -- .../react/components/overlay/sunglasses.png | Bin 4330 -> 0 bytes frontends/web/react/components/pair/pair.css | 0 frontends/web/react/components/pair/pair.tsx | 37 - .../components/panel-split/panel-split.css | 30 - .../components/panel-split/panel-split.tsx | 27 - .../react/components/panel-tab/panel-tab.css | 65 -- .../react/components/panel-tab/panel-tab.tsx | 47 -- .../react/components/paragraph/paragraph.css | 5 - .../react/components/paragraph/paragraph.tsx | 20 - .../components/registers-gb/registers-gb.css | 32 - .../components/registers-gb/registers-gb.tsx | 79 -- .../web/react/components/section/section.css | 13 - .../web/react/components/section/section.tsx | 31 - .../web/react/components/tiles/tiles.css | 14 - .../web/react/components/tiles/tiles.tsx | 98 --- .../web/react/components/title/title.css | 10 - .../web/react/components/title/title.tsx | 38 - .../web/react/components/toast/toast.css | 57 -- .../web/react/components/toast/toast.tsx | 36 - frontends/web/react/structs.ts | 275 ------- frontends/web/react/util.ts | 3 - frontends/web/{ => ts}/gb.ts | 8 +- frontends/web/ts/index.ts | 3 + frontends/web/{ => ts}/palettes.ts | 0 frontends/web/{ => ts}/util.ts | 0 59 files changed, 15 insertions(+), 4049 deletions(-) delete mode 100644 frontends/web/react/app.css delete mode 100644 frontends/web/react/app.tsx delete mode 100644 frontends/web/react/components/button-container/button-container.css delete mode 100644 frontends/web/react/components/button-container/button-container.tsx delete mode 100644 frontends/web/react/components/button-increment/button-increment.css delete mode 100644 frontends/web/react/components/button-increment/button-increment.tsx delete mode 100644 frontends/web/react/components/button-switch/button-switch.tsx delete mode 100644 frontends/web/react/components/button/button.css delete mode 100644 frontends/web/react/components/button/button.tsx delete mode 100644 frontends/web/react/components/canvas/canvas.css delete mode 100644 frontends/web/react/components/canvas/canvas.tsx delete mode 100644 frontends/web/react/components/display/display.css delete mode 100644 frontends/web/react/components/display/display.tsx delete mode 100644 frontends/web/react/components/display/minimise.svg delete mode 100644 frontends/web/react/components/footer/footer.css delete mode 100644 frontends/web/react/components/footer/footer.tsx delete mode 100644 frontends/web/react/components/help/help.css delete mode 100644 frontends/web/react/components/help/help.tsx delete mode 100644 frontends/web/react/components/index.ts delete mode 100644 frontends/web/react/components/info/info.css delete mode 100644 frontends/web/react/components/info/info.tsx delete mode 100644 frontends/web/react/components/keyboard-chip8/keyboard-chip8.css delete mode 100644 frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx delete mode 100644 frontends/web/react/components/keyboard-gb/keyboard-gb.css delete mode 100644 frontends/web/react/components/keyboard-gb/keyboard-gb.tsx delete mode 100644 frontends/web/react/components/link/link.css delete mode 100644 frontends/web/react/components/link/link.tsx delete mode 100644 frontends/web/react/components/modal/close.svg delete mode 100644 frontends/web/react/components/modal/modal.css delete mode 100644 frontends/web/react/components/modal/modal.tsx delete mode 100644 frontends/web/react/components/overlay/overlay.css delete mode 100644 frontends/web/react/components/overlay/overlay.tsx delete mode 100644 frontends/web/react/components/overlay/sunglasses.png delete mode 100644 frontends/web/react/components/pair/pair.css delete mode 100644 frontends/web/react/components/pair/pair.tsx delete mode 100644 frontends/web/react/components/panel-split/panel-split.css delete mode 100644 frontends/web/react/components/panel-split/panel-split.tsx delete mode 100644 frontends/web/react/components/panel-tab/panel-tab.css delete mode 100644 frontends/web/react/components/panel-tab/panel-tab.tsx delete mode 100644 frontends/web/react/components/paragraph/paragraph.css delete mode 100644 frontends/web/react/components/paragraph/paragraph.tsx delete mode 100644 frontends/web/react/components/registers-gb/registers-gb.css delete mode 100644 frontends/web/react/components/registers-gb/registers-gb.tsx delete mode 100644 frontends/web/react/components/section/section.css delete mode 100644 frontends/web/react/components/section/section.tsx delete mode 100644 frontends/web/react/components/tiles/tiles.css delete mode 100644 frontends/web/react/components/tiles/tiles.tsx delete mode 100644 frontends/web/react/components/title/title.css delete mode 100644 frontends/web/react/components/title/title.tsx delete mode 100644 frontends/web/react/components/toast/toast.css delete mode 100644 frontends/web/react/components/toast/toast.tsx delete mode 100644 frontends/web/react/structs.ts delete mode 100644 frontends/web/react/util.ts rename frontends/web/{ => ts}/gb.ts (96%) create mode 100644 frontends/web/ts/index.ts rename frontends/web/{ => ts}/palettes.ts (100%) rename frontends/web/{ => ts}/util.ts (100%) diff --git a/frontends/web/index.ts b/frontends/web/index.ts index 25ac5e1c..75a1697c 100644 --- a/frontends/web/index.ts +++ b/frontends/web/index.ts @@ -1,5 +1,5 @@ -import { startApp } from "./react/app"; -import { GameboyEmulator } from "./gb"; +import { startApp } from "emukit"; +import { GameboyEmulator } from "./ts"; const BACKGROUNDS = [ "264653", diff --git a/frontends/web/package.json b/frontends/web/package.json index 8611640c..58ff1982 100644 --- a/frontends/web/package.json +++ b/frontends/web/package.json @@ -16,14 +16,15 @@ }, "source": "index.ts", "devDependencies": { - "@parcel/transformer-typescript-tsc": "^2.7.0", - "@types/react": "^18.0.21", - "@types/react-dom": "^18.0.6", - "parcel": "^2.7.0", + "@parcel/transformer-typescript-tsc": "^2.8.0", + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "emukit": "^0.1.1", + "parcel": "^2.8.0", "prettier": "^2.7.1", "process": "^0.11.10", "react": "^18.2.0", "react-dom": "^18.2.0", - "typescript": "^4.8.4" + "typescript": "^4.9.3" } } diff --git a/frontends/web/react/app.css b/frontends/web/react/app.css deleted file mode 100644 index f21e17fb..00000000 --- a/frontends/web/react/app.css +++ /dev/null @@ -1,20 +0,0 @@ -.app { - color: #ffffff; - font-family: VT323, Roboto, Open Sans, Arial, Helvetica, sans-serif; - margin: 0px auto 0px auto; - max-width: 1400px; -} - -.app h3 { - margin: 0px 0px 16px 0px; -} - -.app .display-container { - margin-top: 78px; -} - -@media only screen and (max-width: 1120px) { - .app .display-container { - margin-top: 14px; - } -} diff --git a/frontends/web/react/app.tsx b/frontends/web/react/app.tsx deleted file mode 100644 index e27c152a..00000000 --- a/frontends/web/react/app.tsx +++ /dev/null @@ -1,738 +0,0 @@ -import React, { FC, ReactNode, useEffect, useRef, useState } from "react"; -import ReactDOM from "react-dom/client"; - -declare const require: any; - -import { - Button, - ButtonContainer, - ButtonIncrement, - ButtonSwitch, - ClearHandler, - Display, - DrawHandler, - Footer, - Help, - Info, - KeyboardChip8, - KeyboardGB, - Link, - Modal, - Overlay, - Pair, - PanelSplit, - PanelTab, - Paragraph, - RegistersGB, - Section, - Tiles, - Title, - Toast -} from "./components"; -import { - Emulator, - Feature, - FREQUENCY_DELTA, - PixelFormat, - RomInfo -} from "./structs"; - -import "./app.css"; - -type EmulatorAppProps = { - emulator: Emulator; - fullscreen?: boolean; - debug?: boolean; - keyboard?: boolean; - palette?: string; - backgrounds?: string[]; -}; - -const isTouchDevice = () => { - return "ontouchstart" in window || navigator.maxTouchPoints > 0; -}; - -export const EmulatorApp: FC<EmulatorAppProps> = ({ - emulator, - fullscreen = false, - debug = false, - keyboard = false, - palette, - backgrounds = ["264653"] -}) => { - const [paused, setPaused] = useState(false); - const [fullscreenState, setFullscreenState] = useState(fullscreen); - const [backgroundIndex, setBackgroundIndex] = useState(0); - const [romInfo, setRomInfo] = useState<RomInfo>({}); - const [framerate, setFramerate] = useState(0); - const [paletteName, setPaletteName] = useState(emulator.palette); - 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); - const [toastVisible, setToastVisible] = useState(false); - const [keyboardVisible, setKeyboardVisible] = useState( - isTouchDevice() || keyboard - ); - const [infoVisible, setInfoVisible] = useState(true); - const [debugVisible, setDebugVisible] = useState(debug); - - const toastCounterRef = useRef(0); - const frameRef = useRef<boolean>(false); - const errorRef = useRef<boolean>(false); - const modalCallbackRef = - useRef<(value: boolean | PromiseLike<boolean>) => void>(); - - useEffect(() => { - document.body.style.backgroundColor = `#${getBackground()}`; - }, [backgroundIndex]); - useEffect(() => { - switch (keyaction) { - case "Plus": - emulator.frequency += - emulator.frequencyDelta ?? FREQUENCY_DELTA; - setKeyaction(undefined); - break; - case "Minus": - emulator.frequency -= - emulator.frequencyDelta ?? FREQUENCY_DELTA; - setKeyaction(undefined); - break; - case "Escape": - setFullscreenState(false); - setKeyaction(undefined); - break; - case "Fullscreen": - setFullscreenState(!fullscreenState); - setKeyaction(undefined); - break; - case "Keyboard": - setKeyboardVisible(!keyboardVisible); - setKeyaction(undefined); - break; - case "Accelerate": - emulator.frequency *= 8; - break; - case "Slowdown": - emulator.frequency /= 8; - break; - } - }, [keyaction]); - useEffect(() => { - if (palette) { - emulator.palette = palette; - } - const onFullChange = (event: Event) => { - if ( - !document.fullscreenElement && - !(document as any).webkitFullscreenElement - ) { - setFullscreenState(false); - } - }; - const onKeyDown = (event: KeyboardEvent) => { - switch (event.key) { - case "+": - setKeyaction("Plus"); - event.stopPropagation(); - event.preventDefault(); - break; - case "-": - setKeyaction("Minus"); - event.stopPropagation(); - event.preventDefault(); - break; - case "Escape": - setKeyaction("Escape"); - event.stopPropagation(); - event.preventDefault(); - break; - } - if (event.ctrlKey === true) { - switch (event.key) { - case "f": - setKeyaction("Fullscreen"); - event.stopPropagation(); - event.preventDefault(); - break; - case "k": - setKeyaction("Keyboard"); - event.stopPropagation(); - event.preventDefault(); - break; - case "d": - setKeyaction("Accelerate"); - event.stopPropagation(); - event.preventDefault(); - break; - } - } - }; - const onKeyUp = (event: KeyboardEvent) => { - if (event.ctrlKey === true) { - switch (event.key) { - case "d": - setKeyaction("Slowdown"); - event.stopPropagation(); - event.preventDefault(); - break; - } - } - }; - const onBooted = () => { - setRomInfo(emulator.romInfo); - setPaused(false); - }; - const onMessage = ( - emulator: Emulator, - params: Record<string, any> = {} - ) => { - showToast(params.text, params.error, params.timeout); - }; - document.addEventListener("fullscreenchange", onFullChange); - document.addEventListener("webkitfullscreenchange", onFullChange); - document.addEventListener("keydown", onKeyDown); - document.addEventListener("keyup", onKeyUp); - emulator.bind("booted", onBooted); - emulator.bind("message", onMessage); - return () => { - document.removeEventListener("fullscreenchange", onFullChange); - document.removeEventListener( - "webkitfullscreenchange", - onFullChange - ); - document.removeEventListener("keydown", onKeyDown); - document.removeEventListener("keyup", onKeyUp); - emulator.unbind("booted", onBooted); - emulator.unbind("message", onMessage); - }; - }, []); - - const getPauseText = () => (paused ? "Resume" : "Pause"); - const getPauseIcon = () => - paused ? require("../res/play.svg") : require("../res/pause.svg"); - const getBackground = () => backgrounds[backgroundIndex]; - - const showModal = async ( - title = "Alert", - text?: string, - contents?: ReactNode - ): Promise<boolean> => { - 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); - setToastVisible(true); - toastCounterRef.current++; - const counter = toastCounterRef.current; - await new Promise((resolve) => { - setTimeout(() => { - if (counter !== toastCounterRef.current) return; - setToastVisible(false); - resolve(true); - }, timeout); - }); - }; - const hasFeature = (feature: Feature) => { - return emulator.features.includes(feature); - }; - - const onFile = async (file: File) => { - const fileExtension = file.name.split(".").pop() ?? ""; - if (!emulator.romExts.includes(fileExtension)) { - showToast( - `This is probably not a ${emulator.device.text} ROM file!`, - true - ); - return; - } - - const arrayBuffer = await file.arrayBuffer(); - const romData = new Uint8Array(arrayBuffer); - - emulator.boot({ engine: null, romName: file.name, romData: romData }); - - showToast(`Loaded ${file.name} ROM successfully!`); - }; - const onModalConfirm = () => { - if (modalCallbackRef.current) { - modalCallbackRef.current(true); - modalCallbackRef.current = undefined; - } - setModalVisible(false); - }; - const onModalCancel = () => { - if (modalCallbackRef.current) { - modalCallbackRef.current(false); - modalCallbackRef.current = undefined; - } - setModalVisible(false); - }; - const onToastCancel = () => { - setToastVisible(false); - }; - const onPauseClick = () => { - emulator.toggleRunning(); - setPaused(!paused); - }; - const onResetClick = () => { - emulator.reset(); - }; - const onBenchmarkClick = async () => { - if (!emulator.benchmark) return; - const result = await showModal( - "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(); - await showToast( - `Took ${delta.toFixed( - 2 - )} seconds to run ${count} ticks (${frequency_mhz.toFixed( - 2 - )} Mhz)!`, - undefined, - 7500 - ); - }; - const onFullscreenClick = () => { - setFullscreenState(!fullscreenState); - }; - const onKeyboardClick = () => { - setKeyboardVisible(!keyboardVisible); - }; - const onInformationClick = () => { - setInfoVisible(!infoVisible); - }; - const onHelpClick = () => { - showHelp(); - }; - const onDebugClick = () => { - setDebugVisible(!debugVisible); - }; - const onThemeClick = () => { - setBackgroundIndex((backgroundIndex + 1) % backgrounds.length); - }; - const onPaletteClick = () => { - const palette = emulator.changePalette?.(); - setPaletteName(palette); - }; - const onUploadFile = async (file: File) => { - const arrayBuffer = await file.arrayBuffer(); - const romData = new Uint8Array(arrayBuffer); - emulator.boot({ engine: null, romName: file.name, romData: romData }); - showToast(`Loaded ${file.name} ROM successfully!`); - }; - const onEngineChange = (engine: string) => { - emulator.boot({ engine: engine.toLowerCase() }); - showToast( - `${emulator.device.text} running in engine "${engine}" from now on!` - ); - }; - const onFrequencyChange = (value: number) => { - emulator.frequency = value * 1000 * 1000; - }; - const onFrequencyReady = (handler: (value: number) => void) => { - emulator.bind("frequency", (emulator: Emulator, frequency: number) => { - handler(frequency / 1000000); - }); - }; - const onMinimize = () => { - setFullscreenState(!fullscreenState); - }; - const onKeyDown = (key: string) => { - emulator.keyPress(key); - }; - const onKeyUp = (key: string) => { - emulator.keyLift(key); - }; - const onGamepad = (id: string, isValid: boolean, connected = true) => { - if (connected) { - if (isValid) showToast(`ðŸ•¹ï¸ Gamepad connect ${id}`); - else showToast(`😥 Unsupported gamepad connect ${id}`, true); - } else if (isValid) { - showToast(`ðŸ•¹ï¸ Gamepad disconnected ${id}`, true); - } - }; - const onDrawHandler = (handler: DrawHandler) => { - if (frameRef.current) return; - frameRef.current = true; - emulator.bind("frame", () => { - handler(emulator.imageBuffer, PixelFormat.RGB); - setFramerate(emulator.framerate); - }); - }; - const onClearHandler = (handler: ClearHandler) => { - if (errorRef.current) return; - errorRef.current = true; - emulator.bind("error", async () => { - await handler(undefined, require("../res/storm.png"), 0.2); - }); - }; - - return ( - <div className="app"> - <Overlay text={"Drag to load ROM"} onFile={onFile} /> - <Modal - title={modalTitle} - text={modalText} - contents={modalContents} - visible={modalVisible} - onConfirm={onModalConfirm} - onCancel={onModalCancel} - /> - <Toast - text={toastText} - error={toastError} - visible={toastVisible} - onCancel={onToastCancel} - /> - <Footer color={getBackground()}> - Built with â¤ï¸ by{" "} - <Link href="https://joao.me" target="_blank"> - João Magalhães - </Link> - </Footer> - <PanelSplit - left={ - <div className="display-container"> - <Display - fullscreen={fullscreenState} - onDrawHandler={onDrawHandler} - onClearHandler={onClearHandler} - onMinimize={onMinimize} - /> - </div> - } - > - <Section visible={keyboardVisible} separatorBottom={true}> - {hasFeature(Feature.KeyboardChip8) && ( - <KeyboardChip8 - onKeyDown={onKeyDown} - onKeyUp={onKeyUp} - /> - )} - {hasFeature(Feature.KeyboardGB) && ( - <KeyboardGB - fullscreen={fullscreenState} - onKeyDown={onKeyDown} - onKeyUp={onKeyUp} - onGamepad={onGamepad} - /> - )} - </Section> - <Title - text={emulator.name} - version={emulator.version?.text} - versionUrl={emulator.version?.url} - iconSrc={require("../res/thunder.png")} - ></Title> - <Section> - <Paragraph> - This is a{" "} - {emulator.device.url ? ( - <Link href={emulator.device.url} target="_blank"> - {emulator.device.text} - </Link> - ) : ( - emulator.device.text - )}{" "} - emulator built using the{" "} - <Link href="https://www.rust-lang.org" target="_blank"> - Rust Programming Language - </Link>{" "} - and is running inside this browser with the help of{" "} - <Link href="https://webassembly.org/" target="_blank"> - WebAssembly - </Link> - . - </Paragraph> - {emulator.repository && ( - <Paragraph> - You can check the source code of it at{" "} - {emulator.repository.url ? ( - <Link - href={emulator.repository.url} - target="_blank" - > - {emulator.repository.text} - </Link> - ) : ( - <>{emulator.repository.text}</> - )} - . - </Paragraph> - )} - <Paragraph> - TIP: Drag and Drop ROM files to the Browser to load the - ROM. - </Paragraph> - </Section> - {debugVisible && ( - <Section> - <div - style={{ - display: "inline-block", - verticalAlign: "top", - marginRight: 32, - width: 256 - }} - > - <h3>VRAM Tiles</h3> - <Tiles - getTile={(index) => emulator.getTile(index)} - tileCount={384} - width={"100%"} - contentBox={false} - /> - </div> - <div - style={{ - display: "inline-block", - verticalAlign: "top" - }} - > - <h3>Registers</h3> - <RegistersGB - getRegisters={() => emulator.registers} - /> - </div> - </Section> - )} - {infoVisible && ( - <Section> - <PanelTab - tabs={[ - <Info> - <Pair - key="button-engine" - name={"Engine"} - valueNode={ - <ButtonSwitch - options={emulator.engines.map( - (e) => e.toUpperCase() - )} - size={"large"} - style={["simple"]} - onChange={onEngineChange} - /> - } - /> - <Pair - key="rom" - name={"ROM"} - value={romInfo.name ?? "-"} - /> - <Pair - key="rom-size" - name={"ROM Size"} - value={ - romInfo.size - ? `${new Intl.NumberFormat().format( - romInfo.size - )} bytes` - : "-" - } - /> - <Pair - key="button-frequency" - name={"CPU Frequency"} - valueNode={ - <ButtonIncrement - value={ - emulator.frequency / - 1000 / - 1000 - } - delta={ - (emulator.frequencyDelta ?? - FREQUENCY_DELTA) / - 1000 / - 1000 - } - min={0} - suffix={"MHz"} - decimalPlaces={2} - onChange={onFrequencyChange} - onReady={onFrequencyReady} - /> - } - /> - <Pair - key="rom-type" - name={"ROM Type"} - value={ - romInfo.extra?.romType - ? `${romInfo.extra?.romType}` - : "-" - } - /> - <Pair - key="framerate" - name={"Framerate"} - value={`${framerate} fps`} - /> - </Info>, - <Info> - <Pair - key="palette" - name={"Palette"} - value={paletteName} - /> - </Info> - ]} - tabNames={["General", "Detailed"]} - selectors={true} - /> - </Section> - )} - <Section> - <ButtonContainer> - <Button - text={getPauseText()} - image={getPauseIcon()} - imageAlt="pause" - enabled={paused} - style={["simple", "border", "padded"]} - onClick={onPauseClick} - /> - <Button - text={"Reset"} - image={require("../res/reset.svg")} - imageAlt="reset" - style={["simple", "border", "padded"]} - onClick={onResetClick} - /> - {hasFeature(Feature.Benchmark) && ( - <Button - text={"Benchmark"} - image={require("../res/bolt.svg")} - imageAlt="benchmark" - style={["simple", "border", "padded"]} - onClick={onBenchmarkClick} - /> - )} - <Button - text={"Fullscreen"} - image={require("../res/maximise.svg")} - imageAlt="maximise" - style={["simple", "border", "padded"]} - onClick={onFullscreenClick} - /> - {hasFeature(Feature.Keyboard) && ( - <Button - text={"Keyboard"} - image={require("../res/dialpad.svg")} - imageAlt="keyboard" - enabled={keyboardVisible} - style={["simple", "border", "padded"]} - onClick={onKeyboardClick} - /> - )} - <Button - text={"Information"} - image={require("../res/info.svg")} - imageAlt="information" - enabled={infoVisible} - 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"} - image={require("../res/bug.svg")} - imageAlt="debug" - enabled={debugVisible} - style={["simple", "border", "padded"]} - onClick={onDebugClick} - /> - )} - <Button - text={"Theme"} - image={require("../res/marker.svg")} - imageAlt="theme" - style={["simple", "border", "padded"]} - onClick={onThemeClick} - /> - {hasFeature(Feature.Palettes) && ( - <Button - text={"Palette"} - image={require("../res/brightness.svg")} - imageAlt="palette" - style={["simple", "border", "padded"]} - onClick={onPaletteClick} - /> - )} - <Button - text={"Load ROM"} - image={require("../res/upload.svg")} - imageAlt="upload" - file={true} - accept={".gb"} - style={["simple", "border", "padded"]} - onFile={onUploadFile} - /> - </ButtonContainer> - </Section> - </PanelSplit> - </div> - ); -}; - -export const startApp = ( - element: string, - { - emulator, - fullscreen = false, - debug = false, - keyboard = false, - palette, - backgrounds = [] - }: { - emulator: Emulator; - fullscreen?: boolean; - debug?: boolean; - keyboard?: boolean; - palette?: string; - backgrounds: string[]; - } -) => { - const elementRef = document.getElementById(element); - if (!elementRef) return; - - const root = ReactDOM.createRoot(elementRef); - root.render( - <EmulatorApp - emulator={emulator} - fullscreen={fullscreen} - debug={debug} - keyboard={keyboard} - palette={palette} - backgrounds={backgrounds} - /> - ); -}; - -export default EmulatorApp; diff --git a/frontends/web/react/components/button-container/button-container.css b/frontends/web/react/components/button-container/button-container.css deleted file mode 100644 index 1d1df8f7..00000000 --- a/frontends/web/react/components/button-container/button-container.css +++ /dev/null @@ -1,12 +0,0 @@ -.button-container { - margin-bottom: -12px; -} - -.button-container > * { - margin-bottom: 12px; - margin-right: 8px; -} - -.button-container > *:last-child { - margin-right: 0px; -} diff --git a/frontends/web/react/components/button-container/button-container.tsx b/frontends/web/react/components/button-container/button-container.tsx deleted file mode 100644 index b11fc1f1..00000000 --- a/frontends/web/react/components/button-container/button-container.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { FC, ReactNode } from "react"; - -import "./button-container.css"; - -type ButtonContainerProps = { - children: ReactNode; - style?: string[]; -}; - -export const ButtonContainer: FC<ButtonContainerProps> = ({ - children, - style = [] -}) => { - const classes = () => ["button-container", ...style].join(" "); - return <div className={classes()}>{children}</div>; -}; - -export default ButtonContainer; diff --git a/frontends/web/react/components/button-increment/button-increment.css b/frontends/web/react/components/button-increment/button-increment.css deleted file mode 100644 index 57f94577..00000000 --- a/frontends/web/react/components/button-increment/button-increment.css +++ /dev/null @@ -1,15 +0,0 @@ -.button-increment { - display: inline-block; -} - -.button-increment > .value { - margin: 0px 8px 0px 8px; -} - -.button-increment > .prefix { - margin-left: 8px; -} - -.button-increment > .suffix { - margin-right: 8px; -} diff --git a/frontends/web/react/components/button-increment/button-increment.tsx b/frontends/web/react/components/button-increment/button-increment.tsx deleted file mode 100644 index f8060938..00000000 --- a/frontends/web/react/components/button-increment/button-increment.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { FC, useEffect, useState } from "react"; -import Button from "../button/button"; - -import "./button-increment.css"; - -type ButtonIncrementProps = { - value: number; - delta?: number; - min?: number; - max?: number; - prefix?: string; - suffix?: string; - decimalPlaces?: number; - size?: string; - style?: string[]; - onClick?: () => void; - onBeforeChange?: (value: number) => boolean; - onChange?: (value: number) => void; - onReady?: (setValue: (value: number) => void) => void; -}; - -export const ButtonIncrement: FC<ButtonIncrementProps> = ({ - value, - delta = 1, - min, - max, - prefix, - suffix, - decimalPlaces, - size = "medium", - style = ["simple", "border"], - onClick, - onBeforeChange, - onChange, - onReady -}) => { - const [valueState, setValue] = useState(value); - const classes = () => ["button-increment", size, ...style].join(" "); - useEffect(() => { - onReady && onReady((value) => setValue(value)); - }, []); - const _onMinusClick = () => { - let valueNew = valueState - delta; - if (onBeforeChange) { - if (!onBeforeChange(valueNew)) return; - } - if (min !== undefined) valueNew = Math.max(min, valueNew); - if (valueNew === valueState) return; - setValue(valueNew); - if (onChange) onChange(valueNew); - }; - const _onPlusClick = () => { - let valueNew = valueState + delta; - if (onBeforeChange) { - if (!onBeforeChange(valueNew)) return; - } - if (max !== undefined) valueNew = Math.min(max, valueNew); - if (valueNew === valueState) return; - setValue(valueNew); - if (onChange) onChange(valueNew); - }; - return ( - <span className={classes()} onClick={onClick}> - <Button - text={"-"} - size={size} - style={["simple"]} - onClick={_onMinusClick} - /> - {prefix && <span className="prefix">{prefix}</span>} - <span className="value"> - {decimalPlaces ? valueState.toFixed(decimalPlaces) : valueState} - </span> - {suffix && <span className="suffix">{suffix}</span>} - <Button - text={"+"} - size={size} - style={["simple"]} - onClick={_onPlusClick} - /> - </span> - ); -}; - -export default ButtonIncrement; diff --git a/frontends/web/react/components/button-switch/button-switch.tsx b/frontends/web/react/components/button-switch/button-switch.tsx deleted file mode 100644 index 54c5bd27..00000000 --- a/frontends/web/react/components/button-switch/button-switch.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { FC, useState } from "react"; -import Button from "../button/button"; - -type ButtonSwitchProps = { - options: string[]; - size?: string; - style?: string[]; - onClick?: () => void; - onChange?: (value: string, index: number) => void; -}; - -export const ButtonSwitch: FC<ButtonSwitchProps> = ({ - options, - size = "small", - style = ["simple", "border"], - onClick, - onChange -}) => { - const [index, setIndex] = useState(0); - const text = () => options[index]; - const _onClick = () => { - const indexNew = (index + 1) % options.length; - const option = options[indexNew]; - setIndex(indexNew); - if (onClick) onClick(); - if (onChange) onChange(option, indexNew); - }; - return ( - <Button text={text()} size={size} style={style} onClick={_onClick} /> - ); -}; - -export default ButtonSwitch; diff --git a/frontends/web/react/components/button/button.css b/frontends/web/react/components/button/button.css deleted file mode 100644 index 910de45c..00000000 --- a/frontends/web/react/components/button/button.css +++ /dev/null @@ -1,123 +0,0 @@ -.button { - cursor: pointer; - display: inline-flex; - transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: background-color 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - vertical-align: middle; -} - -.button.small { - font-size: 16px; - line-height: 24px; -} - -.button.simple { - border-radius: 96px 96px 96px 96px; - -o-border-radius: 96px 96px 96px 96px; - -ms-border-radius: 96px 96px 96px 96px; - -moz-border-radius: 96px 96px 96px 96px; - -khtml-border-radius: 96px 96px 96px 96px; - -webkit-border-radius: 96px 96px 96px 96px; - padding: 0px 8px 0px 8px; - -webkit-tap-highlight-color: transparent; - user-select: none; - -o-user-select: none; - -ms-user-select: none; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; -} - -.button.simple.border { - border: 1px solid #ffffff; -} - -.button.simple.padded { - padding: 0px 10px 0px 10px; -} - -.button.simple.padded-large { - padding: 4px 14px 4px 14px; -} - -.button.simple.rounded { - padding: 6px 6px 6px 6px; -} - -.button.simple.enabled { - background-color: #50cb93; -} - -.button.simple.file { - position: relative; -} - -.button.simple:focus, -.button.simple:hover { - background-color: #50cb93; -} - -.button.simple.red:focus, -.button.simple.red:hover { - background-color: #e63946; -} - -.button.simple:active { - background-color: #2a9d8f; -} - -.button.simple.red:active { - background-color: #bf2a37; -} - -.button.simple > img { - margin-right: 6px; - vertical-align: middle; - width: 13px; -} - -.button.simple.medium > img { - width: 20px; -} - -.button.simple.large > img { - width: 28px; -} - -.button.simple.very-large > img { - width: 38px; -} - -.button.simple > span { - display: inline-block; - vertical-align: middle; -} - -.button.simple.no-text > img { - margin-right: 0px; - margin-top: 0px; -} - -.button.simple.file > input[type="file"] { - cursor: pointer; - height: 100%; - left: 0px; - opacity: 0; - -o-opacity: 0; - -ms-opacity: 0; - -moz-opacity: 0; - -khtml-opacity: 0; - -webkit-opacity: 0; - position: absolute; - top: 0px; - vertical-align: top; - width: 100%; -} - -.button.simple.file > input[type="file"]::-webkit-file-upload-button { - cursor: pointer; -} diff --git a/frontends/web/react/components/button/button.tsx b/frontends/web/react/components/button/button.tsx deleted file mode 100644 index 5c5a734b..00000000 --- a/frontends/web/react/components/button/button.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { ChangeEvent, FC, useRef } from "react"; - -import "./button.css"; - -type ButtonProps = { - text: string; - image?: string; - imageAlt?: string; - enabled?: boolean; - focusable?: boolean; - file?: boolean; - accept?: string; - size?: string; - style?: string[]; - onClick?: () => void; - onFile?: (file: File) => void; -}; - -export const Button: FC<ButtonProps> = ({ - text, - image, - imageAlt, - enabled = false, - focusable = true, - file = false, - accept = ".txt", - size = "small", - style = ["simple", "border"], - onClick, - onFile -}) => { - const classes = () => - [ - "button", - size, - enabled ? "enabled" : "", - file ? "file" : "", - ...style - ].join(" "); - const fileRef = useRef<HTMLInputElement>(null); - const onFileChange = (event: ChangeEvent<HTMLInputElement>) => { - if (!event.target.files) return; - if (event.target.files.length === 0) return; - const file = event.target.files[0]; - onFile && onFile(file); - event.target.value = ""; - }; - const onMouseDown = (event: React.MouseEvent) => { - event.stopPropagation(); - event.preventDefault(); - }; - const onMouseUp = (event: React.MouseEvent) => { - event.stopPropagation(); - event.preventDefault(); - }; - const onKeyPress = (event: React.KeyboardEvent) => { - if (event.key !== "Enter") return; - if (file) fileRef.current?.click(); - onClick && onClick(); - }; - const renderSimple = () => ( - <span - className={classes()} - onClick={onClick} - onKeyPress={onKeyPress} - tabIndex={focusable ? 0 : undefined} - > - {text} - </span> - ); - const renderComplex = () => ( - <span - className={classes()} - onClick={onClick} - onMouseDown={onMouseDown} - onMouseUp={onMouseUp} - onKeyPress={onKeyPress} - tabIndex={focusable ? 0 : undefined} - > - {image && <img src={image} alt={imageAlt ?? text ?? "button"} />} - {file && ( - <input - ref={fileRef} - type="file" - accept={accept} - onChange={onFileChange} - /> - )} - <span>{text}</span> - </span> - ); - return image ? renderComplex() : renderSimple(); -}; - -export default Button; diff --git a/frontends/web/react/components/canvas/canvas.css b/frontends/web/react/components/canvas/canvas.css deleted file mode 100644 index e69de29b..00000000 diff --git a/frontends/web/react/components/canvas/canvas.tsx b/frontends/web/react/components/canvas/canvas.tsx deleted file mode 100644 index 5cad1d5c..00000000 --- a/frontends/web/react/components/canvas/canvas.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { FC, useEffect, useRef } from "react"; - -import "./canvas.css"; - -export type CanvasStructure = { - canvas: HTMLCanvasElement; - canvasContext: CanvasRenderingContext2D; - canvasImage: ImageData; - canvasBuffer: DataView; -}; - -type CanvasProps = { - width: number; - height: number; - scaledWidth?: number | string; - scale?: number; - style?: string[]; - onCanvas?: (structure: CanvasStructure) => void; -}; - -export const Canvas: FC<CanvasProps> = ({ - width, - height, - scaledWidth, - scale = 1, - style = [], - onCanvas -}) => { - const classes = () => ["canvas", ...style].join(" "); - const canvasRef = useRef<HTMLCanvasElement>(null); - useEffect(() => { - if (canvasRef.current) { - const structure = initCanvas( - width, - height, - scale, - canvasRef.current - ); - onCanvas && onCanvas(structure); - } - }, [canvasRef]); - return ( - <canvas - ref={canvasRef} - className={classes()} - style={{ width: scaledWidth ?? width * scale }} - width={width} - height={height} - /> - ); -}; - -const initCanvas = ( - width: number, - height: number, - scale: number, - canvas: HTMLCanvasElement, - smoothing = false -): CanvasStructure => { - const canvasContext = canvas.getContext("2d")!; - canvasContext.imageSmoothingEnabled = smoothing; - - const canvasImage = canvasContext.createImageData(width, height); - const canvasBuffer = new DataView(canvasImage.data.buffer); - - return { - canvas: canvas, - canvasContext: canvasContext, - canvasImage: canvasImage, - canvasBuffer: canvasBuffer - }; -}; - -export default Canvas; diff --git a/frontends/web/react/components/display/display.css b/frontends/web/react/components/display/display.css deleted file mode 100644 index 8fde45f8..00000000 --- a/frontends/web/react/components/display/display.css +++ /dev/null @@ -1,116 +0,0 @@ -.display { - max-width: 100%; -} - -.display.fullscreen { - align-items: center; - background-color: #2d2d2d; - display: flex; - height: 100%; - justify-content: center; - left: 0px; - position: fixed; - top: 0px; - width: 100%; - z-index: 6; -} - -.display > .magnify-button { - cursor: pointer; - display: inline-block; - transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: transform 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); -} - -.display > .magnify-button:hover { - transform: scale(1.3, 1.3); - -o-transform: scale(1.3, 1.3); - -ms-transform: scale(1.3, 1.3); - -moz-transform: scale(1.3, 1.3); - -khtml-transform: scale(1.3, 1.3); - -webkit-transform: scale(1.3, 1.3); -} - -.display > .magnify-button:active { - transform: scale(1.0, 1.0); - -o-transform: scale(1.0, 1.0); - -ms-transform: scale(1.0, 1.0); - -moz-transform: scale(1.0, 1.0); - -khtml-transform: scale(1.0, 1.0); - -webkit-transform: scale(1.0, 1.0); -} - -.display > .display-minimize { - bottom: 22px; - display: none; - position: absolute; - right: 22px; - user-select: none; - -o-user-select: none; - -ms-user-select: none; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; -} - -@media only screen and (max-width: 1120px) { - .display > .display-minimize { - bottom: initial; - top: 22px; - } -} - -.display > .display-minimize > img { - height: 32px; - width: 32px; -} - -.display.fullscreen > .display-minimize { - display: block; -} - -.display > .display-frame { - background-color: #1b1a17; - border: 2px solid #50cb93; - box-sizing: content-box; - -o-box-sizing: content-box; - -ms-box-sizing: content-box; - -moz-box-sizing: content-box; - -khtml-box-sizing: content-box; - -webkit-box-sizing: content-box; - font-size: 0px; - padding: 8px 8px 8px 8px; - transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: width 0.35s cubic-bezier(0.075, 0.82, 0.165, 1), height 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); -} - -.display.fullscreen > .display-frame { - border: none; - box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24); - -o-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24); - -ms-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24); - -moz-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24); - -khtml-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24); - -webkit-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.24); - margin: 0px 0px 0px 0px; - max-width: unset; - padding: 0px 0px 0px 0px; -} - -.display > .display-frame > .display-canvas { - outline: none; - -o-outline: none; - -ms-outline: none; - -moz-outline: none; - -khtml-outline: none; - -webkit-outline: none; - width: 100%; -} diff --git a/frontends/web/react/components/display/display.tsx b/frontends/web/react/components/display/display.tsx deleted file mode 100644 index 4180efb4..00000000 --- a/frontends/web/react/components/display/display.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import React, { FC, useState, useRef, useEffect } from "react"; -import { PixelFormat } from "../../structs"; - -import "./display.css"; - -const PIXEL_UNSET_COLOR = 0x1b1a17ff; - -declare const require: any; - -/** - * Function that handles a draw operation into a - * certain drawing context. - */ -export type DrawHandler = (pixels: Uint8Array, format: PixelFormat) => void; - -export type ClearHandler = ( - color?: number, - image?: string, - imageScale?: number -) => Promise<void>; - -type DisplayOptions = { - width: number; - height: number; - logicWidth: number; - logicHeight: number; - scale?: number; -}; - -type DisplayProps = { - options?: DisplayOptions; - size?: string; - fullscreen?: boolean; - nativeFullscreen?: boolean; - style?: string[]; - onDrawHandler?: (caller: DrawHandler) => void; - onClearHandler?: (caller: ClearHandler) => void; - onMinimize?: () => void; -}; - -type CanvasContents = { - canvas: HTMLCanvasElement; - canvasCtx: CanvasRenderingContext2D; - canvasBuffer: HTMLCanvasElement; - canvasBufferCtx: CanvasRenderingContext2D; - imageData: ImageData; - videoBuffer: DataView; -}; - -export const Display: FC<DisplayProps> = ({ - options = { width: 320, height: 288, logicWidth: 160, logicHeight: 144 }, - size = "small", - fullscreen = false, - nativeFullscreen = true, - style = [], - onDrawHandler, - onClearHandler, - onMinimize -}) => { - options = { - ...options, - ...{ width: 320, height: 288, logicWidth: 160, logicHeight: 144 } - }; - if (!options.scale) { - options.scale = window.devicePixelRatio ? window.devicePixelRatio : 1; - } - - const classes = () => - ["display", fullscreen ? "fullscreen" : null, size, ...style].join(" "); - - const [width, setWidth] = useState<number>(); - const [height, setHeight] = useState<number>(); - const canvasRef = useRef<HTMLCanvasElement>(null); - const canvasContentsRef = useRef<CanvasContents>(); - const resizeRef = useRef(() => { - const [fullWidth, fullHeight] = crop(options.width / options.height); - setWidth(fullWidth); - setHeight(fullHeight); - }); - - useEffect(() => { - if (canvasRef.current) { - canvasContentsRef.current = initCanvas( - options.logicWidth, - options.logicHeight, - canvasRef.current - ); - } - }, [canvasRef, options.scale]); - - useEffect(() => { - if (fullscreen) { - canvasRef.current?.focus(); - resizeRef.current(); - document.getElementsByTagName("body")[0].style.overflow = "hidden"; - window.addEventListener("resize", resizeRef.current); - - // requests the browser to go fullscreen using the - // body of the document as the entry HTML element - if (nativeFullscreen && document.body.requestFullscreen) { - document.body.requestFullscreen().catch(() => {}); - } else if ( - nativeFullscreen && - (document.body as any).webkitRequestFullscreen - ) { - (document.body as any).webkitRequestFullscreen(); - } - } else { - setWidth(undefined); - setHeight(undefined); - document - .getElementsByTagName("body")[0] - .style.removeProperty("overflow"); - window.removeEventListener("resize", resizeRef.current); - - // restores the window mode, returning from the - // fullscreen browser - if (nativeFullscreen && document.exitFullscreen) { - document.exitFullscreen().catch(() => {}); - } else if ( - nativeFullscreen && - (document as any).webkitExitFullscreen - ) { - (document as any).webkitExitFullscreen(); - } - } - return () => { - window.removeEventListener("resize", resizeRef.current); - }; - }, [fullscreen]); - - if (onDrawHandler) { - onDrawHandler((pixels, format) => { - if (!canvasContentsRef.current) return; - updateCanvas(canvasContentsRef.current, pixels, format); - }); - } - - if (onClearHandler) { - onClearHandler(async (color, image, imageScale) => { - if (!canvasContentsRef.current) return; - await clearCanvas(canvasContentsRef.current, color, { - image: image, - imageScale: imageScale - }); - }); - } - - return ( - <div className={classes()}> - <span - className="magnify-button display-minimize" - onClick={onMinimize} - > - <img - className="large" - src={require("./minimise.svg")} - alt="minimise" - /> - </span> - <div - className="display-frame" - style={{ width: width ?? options.width, height: height }} - > - <canvas - ref={canvasRef} - tabIndex={-1} - className="display-canvas" - width={options.width * options.scale} - height={options.height * options.scale} - ></canvas> - </div> - </div> - ); -}; - -const initCanvas = ( - width: number, - height: number, - canvas: HTMLCanvasElement -): CanvasContents => { - // initializes the off-screen canvas that is going to be - // used in the drawing process, this is used essentially for - // performance reasons as it provides a way to draw pixels - // in the original size instead of the target one - const canvasBuffer = document.createElement("canvas"); - canvasBuffer.width = width; - canvasBuffer.height = height; - const canvasBufferCtx = canvasBuffer.getContext("2d")!; - const imageData = canvasBufferCtx.createImageData( - canvasBuffer.width, - canvasBuffer.height - ); - const videoBuffer = new DataView(imageData.data.buffer); - - // initializes the visual canvas (where data is going to be written) - // with, resetting the transform vector to the identity and re-calculating - // the scale of the drawing properly - const canvasCtx = canvas.getContext("2d")!; - canvasCtx.setTransform(1, 0, 0, 1, 0, 0); - canvasCtx.scale( - canvas.width / canvasBuffer.width, - canvas.height / canvasBuffer.height - ); - canvasCtx.imageSmoothingEnabled = false; - - return { - canvas: canvas, - canvasCtx: canvasCtx, - canvasBuffer: canvasBuffer, - canvasBufferCtx: canvasBufferCtx, - imageData: imageData, - videoBuffer: videoBuffer - }; -}; - -const updateCanvas = ( - canvasContents: CanvasContents, - pixels: Uint8Array, - format: PixelFormat = PixelFormat.RGB -) => { - let offset = 0; - for (let index = 0; index < pixels.length; index += format) { - const color = - (pixels[index] << 24) | - (pixels[index + 1] << 16) | - (pixels[index + 2] << 8) | - (format == PixelFormat.RGBA ? pixels[index + 3] : 0xff); - canvasContents.videoBuffer.setUint32(offset, color); - offset += PixelFormat.RGBA; - } - canvasContents.canvasBufferCtx.putImageData(canvasContents.imageData, 0, 0); - canvasContents.canvasCtx.drawImage(canvasContents.canvasBuffer, 0, 0); -}; - -const clearCanvas = async ( - canvasContents: CanvasContents, - color = PIXEL_UNSET_COLOR, - { - image = null, - imageScale = 1 - }: { image?: string | null; imageScale?: number } = {} -) => { - // uses the "clear" color to fill a rectangle with the complete - // size of the canvas contents - canvasContents.canvasCtx.fillStyle = `#${color.toString(16).toUpperCase()}`; - canvasContents.canvasCtx.fillRect( - 0, - 0, - canvasContents.canvas.width, - canvasContents.canvas.height - ); - - // in case an image was requested then uses that to load - // an image at the center of the screen properly scaled - if (image) { - await drawImageCanvas(canvasContents, image, imageScale); - } -}; - -const drawImageCanvas = async ( - canvasContents: CanvasContents, - image: string, - imageScale = 1.0 -) => { - const img = await new Promise<HTMLImageElement>((resolve) => { - const img = new Image(); - img.onload = () => { - resolve(img); - }; - img.src = image; - }); - const [imgWidth, imgHeight] = [ - img.width * imageScale * window.devicePixelRatio, - img.height * imageScale * window.devicePixelRatio - ]; - const [x0, y0] = [ - canvasContents.canvas.width / 2 - imgWidth / 2, - canvasContents.canvas.height / 2 - imgHeight / 2 - ]; - canvasContents.canvasCtx.setTransform(1, 0, 0, 1, 0, 0); - try { - canvasContents.canvasCtx.drawImage(img, x0, y0, imgWidth, imgHeight); - } finally { - canvasContents.canvasCtx.scale( - canvasContents.canvas.width / canvasContents.canvasBuffer.width, - canvasContents.canvas.height / canvasContents.canvasBuffer.height - ); - } -}; - -const crop = (ratio: number): [number, number] => { - // calculates the window ratio as this is fundamental to - // determine the proper way to crop the fullscreen - const windowRatio = window.innerWidth / window.innerHeight; - - // in case the window is wider (more horizontal than the base ratio) - // this means that we must crop horizontally - if (windowRatio > ratio) { - return [window.innerWidth * (ratio / windowRatio), window.innerHeight]; - } else { - return [window.innerWidth, window.innerHeight * (windowRatio / ratio)]; - } -}; - -export default Display; diff --git a/frontends/web/react/components/display/minimise.svg b/frontends/web/react/components/display/minimise.svg deleted file mode 100644 index 3d41ee13..00000000 --- a/frontends/web/react/components/display/minimise.svg +++ /dev/null @@ -1 +0,0 @@ -<svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" viewBox="0 0 24 24" aria-labelledby="minimiseIconTitle" stroke="#ffffff" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#ffffff"> <title id="minimiseIconTitle">Minimise View</title> <polyline points="8 3 8 8 3 8"/> <polyline points="21 8 16 8 16 3"/> <polyline points="3 16 8 16 8 21"/> <polyline points="16 21 16 16 21 16"/> </svg> \ No newline at end of file diff --git a/frontends/web/react/components/footer/footer.css b/frontends/web/react/components/footer/footer.css deleted file mode 100644 index 2958e0d4..00000000 --- a/frontends/web/react/components/footer/footer.css +++ /dev/null @@ -1,24 +0,0 @@ -.footer > .footer-background { - bottom: 0px; - filter: blur(1.0rem); - -o-filter: blur(1.0rem); - -ms-filter: blur(1.0rem); - -moz-filter: blur(1.0rem); - -khtml-filter: blur(1.0rem); - -webkit-filter: blur(1.0rem); - height: 40px; - left: 0px; - position: fixed; - width: 100%; -} - -.footer > .footer-contents { - bottom: 0px; - height: 40px; - left: 0px; - line-height: 40px; - padding: 0px 0px 0px 0px; - position: fixed; - text-align: center; - width: 100%; -} diff --git a/frontends/web/react/components/footer/footer.tsx b/frontends/web/react/components/footer/footer.tsx deleted file mode 100644 index e33c01e5..00000000 --- a/frontends/web/react/components/footer/footer.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { FC, ReactNode } from "react"; - -import "./footer.css"; - -type FooterProps = { - children: ReactNode; - color?: string; - style?: string[]; -}; - -export const Footer: FC<FooterProps> = ({ - children, - color = "ffffff", - style = [] -}) => { - const classes = () => ["footer", ...style].join(" "); - return ( - <div className={classes()}> - <div - className="footer-background" - style={{ backgroundColor: `#${color}f2` }} - ></div> - <div className="footer-contents">{children}</div> - </div> - ); -}; - -export default Footer; diff --git a/frontends/web/react/components/help/help.css b/frontends/web/react/components/help/help.css deleted file mode 100644 index 9cfbd320..00000000 --- a/frontends/web/react/components/help/help.css +++ /dev/null @@ -1,53 +0,0 @@ -.help { - display: flex; - flex-direction: column; - overflow: auto; -} - -.keyboard-help { - font-size: 18px; - line-height: 22px; - list-style: none; - margin: 0px 0px 0px 0px; - padding-left: 0px; -} - -.keyboard-help > li { - margin: 12px 0px 12px 0px; -} - -.keyboard-help .key-container { - display: inline-block; - margin-right: 18px; - min-width: 86px; - text-align: right; -} - -.keyboard-help .key { - border: 2px solid #ffffff; - border-radius: 6px 6px 6px 6px; - -o-border-radius: 6px 6px 6px 6px; - -ms-border-radius: 6px 6px 6px 6px; - -moz-border-radius: 6px 6px 6px 6px; - -khtml-border-radius: 6px 6px 6px 6px; - -webkit-border-radius: 6px 6px 6px 6px; - display: inline-block; - font-size: 16px; - margin-right: 8px; - min-width: 34px; - padding: 1px 8px 1px 8px; - text-align: center; -} - -.keyboard-help .key:last-child { - margin-right: 0px; -} - -.faqs-help > h3 { - margin: 0px 0px 6px 0px; -} - -.faqs-help > p { - line-height: 20px; - margin: 0px 0px 22px 0px; -} diff --git a/frontends/web/react/components/help/help.tsx b/frontends/web/react/components/help/help.tsx deleted file mode 100644 index 1fc70a63..00000000 --- a/frontends/web/react/components/help/help.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, { FC } from "react"; -import Link from "../link/link"; -import PanelTab from "../panel-tab/panel-tab"; - -import "./help.css"; - -type HelpProps = { - style?: string[]; -}; - -export const Help: FC<HelpProps> = ({ style = [] }) => { - const classes = () => ["help", ...style].join(" "); - return ( - <div className={classes()}> - <PanelTab - tabs={[keyboardHelp(), faqsHelp()]} - tabNames={["Keyboard", "FAQs"]} - /> - </div> - ); -}; - -const keyboardHelp = () => ( - <ul className="keyboard-help"> - <li> - <span className="key-container"> - <span className="key">Enter</span> - </span> - Start button - </li> - <li> - <span className="key-container"> - <span className="key">Space</span> - </span> - Select button - </li> - <li> - <span className="key-container"> - <span className="key">A</span> - </span> - A button - </li> - <li> - <span className="key-container"> - <span className="key">S</span> - </span> - B button - </li> - <li> - <span className="key-container"> - <span className="key">â†</span> - <span className="key">→</span> - </span> - Horizontal control - </li> - <li> - <span className="key-container"> - <span className="key">↑</span> - <span className="key">↓</span> - </span> - Vertical control - </li> - <li> - <span className="key-container"> - <span className="key">Escape</span> - </span> - Exit fullscreen - </li> - <li> - <span className="key-container"> - <span className="key">Ctrl + D</span> - </span> - Turbo speed - </li> - <li> - <span className="key-container"> - <span className="key">Ctrl + F</span> - </span> - Toggle fullscreen - </li> - <li> - <span className="key-container"> - <span className="key">Ctrl + K</span> - </span> - Toggle on-screen keyboard - </li> - </ul> -); - -const faqsHelp = () => ( - <div className="faqs-help"> - <h3>Does it play all Game Boy games?</h3> - <p> - Not really, but it plays the coolest ones. Now seriously it should - play around 90% of the Game Boy games. - </p> - <h3>Why there's no sound?</h3> - <p>It's under development, I'm hopping to have it before Christmas.</p> - <h3>Can I use my Xbox One game pad?</h3> - <p> - Yes, just plug it in and press a button. - <br /> - BTW: This uses the{" "} - <Link - href="https://developer.mozilla.org/docs/Web/API/Gamepad_API/Using_the_Gamepad_API" - target="_blank" - > - Web Gamepad API - </Link>{" "} - 🕹ï¸. - </p> - <h3>Will it ever play Game Boy Color games?</h3> - <p>Eventually...</p> - <h3>I've found a bug, where can I report it?</h3> - <p> - Use the{" "} - <Link - href="https://github.com/joamag/boytacean/issues" - target="_blank" - > - GitHub issue tracker - </Link> - . - </p> - <h3>What's WebAssembly?</h3> - <p> - The coolest thing that happened to the Web in the latest years, - check the{" "} - <Link - href="https://en.wikipedia.org/wiki/WebAssembly" - target="_blank" - > - Wikipedia page - </Link>{" "} - for more info. - </p> - <h3>Why another Game Boy emulator?</h3> - <p>Because it's cool, and a challenging problem to solve.</p> - </div> -); - -export default Help; diff --git a/frontends/web/react/components/index.ts b/frontends/web/react/components/index.ts deleted file mode 100644 index 4c0acbae..00000000 --- a/frontends/web/react/components/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export * from "./button/button"; -export * from "./button-container/button-container"; -export * from "./button-increment/button-increment"; -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"; -export * from "./link/link"; -export * from "./modal/modal"; -export * from "./overlay/overlay"; -export * from "./pair/pair"; -export * from "./panel-split/panel-split"; -export * from "./panel-tab/panel-tab"; -export * from "./paragraph/paragraph"; -export * from "./registers-gb/registers-gb"; -export * from "./section/section"; -export * from "./tiles/tiles"; -export * from "./title/title"; -export * from "./toast/toast"; diff --git a/frontends/web/react/components/info/info.css b/frontends/web/react/components/info/info.css deleted file mode 100644 index 936980db..00000000 --- a/frontends/web/react/components/info/info.css +++ /dev/null @@ -1,30 +0,0 @@ -.info { - font-size: 24px; - margin: 0px 0px 0px 0px; - vertical-align: top; -} - -.info > dt { - clear: both; - float: left; - margin-top: 12px; -} - -.info > dt:first-of-type { - margin-top: 0px; -} - -.info > dd { - float: right; - margin-top: 12px; -} - -.info > dd:first-of-type { - margin-top: 0px; -} - -.info::after { - clear: both; - content: ''; - display: block; -} diff --git a/frontends/web/react/components/info/info.tsx b/frontends/web/react/components/info/info.tsx deleted file mode 100644 index a899b91c..00000000 --- a/frontends/web/react/components/info/info.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { FC, ReactNode } from "react"; - -import "./info.css"; - -type InfoProps = { - children: ReactNode; - style?: string[]; -}; - -/** - * Builds a new info component with the provided pairs components - * setting the style in accordance with the provided list of strings. - * - * An info component is responsible for the management of multiple - * key to "value" pairs. - * - * @param options The multiple options that are going to be used - * to build the info pairs. - * @returns The info component with the associated pairs. - */ -export const Info: FC<InfoProps> = ({ children, style = [] }) => { - const classes = () => ["info", ...style].join(" "); - return <dl className={classes()}>{children}</dl>; -}; - -export default Info; diff --git a/frontends/web/react/components/keyboard-chip8/keyboard-chip8.css b/frontends/web/react/components/keyboard-chip8/keyboard-chip8.css deleted file mode 100644 index a9552b23..00000000 --- a/frontends/web/react/components/keyboard-chip8/keyboard-chip8.css +++ /dev/null @@ -1,51 +0,0 @@ -.keyboard-chip8 { - font-size: 0px; - text-align: center; - touch-callout: none !important; - -o-touch-callout: none !important; - -ms-touch-callout: none !important; - -moz-touch-callout: none !important; - -khtml-touch-callout: none !important; - -webkit-touch-callout: none !important; - user-select: none; - -o-user-select: none; - -ms-user-select: none; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; -} - -.keyboard-chip8 > .keyboard-line { - margin-bottom: 12px; -} - -.keyboard-chip8 > .keyboard-line:last-child { - margin-bottom: 0px; -} - -.keyboard-chip8 .key { - border: 2px solid #ffffff; - border-radius: 5px 5px 5px 5px; - -o-border-radius: 5px 5px 5px 5px; - -ms-border-radius: 5px 5px 5px 5px; - -moz-border-radius: 5px 5px 5px 5px; - -khtml-border-radius: 5px 5px 5px 5px; - -webkit-border-radius: 5px 5px 5px 5px; - cursor: pointer; - display: inline-block; - font-size: 38px; - height: 48px; - line-height: 46px; - margin-right: 14px; - min-width: 48px; - padding: 0px 6px 0px 6px; - text-align: center; -} - -.keyboard-chip8 .key:last-child { - margin-right: 0px; -} - -.keyboard-chip8 .key.pressed { - background-color: #50cb93; -} diff --git a/frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx b/frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx deleted file mode 100644 index 2208c1c1..00000000 --- a/frontends/web/react/components/keyboard-chip8/keyboard-chip8.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { FC, useState } from "react"; - -import "./keyboard-chip8.css"; - -type KeyboardChip8Props = { - focusable?: boolean; - style?: string[]; - onKeyDown?: (key: string) => void; - onKeyUp?: (key: string) => void; -}; - -export const KeyboardChip8: FC<KeyboardChip8Props> = ({ - focusable = true, - style = [], - onKeyDown, - onKeyUp -}) => { - const classes = () => ["keyboard", "keyboard-chip8", ...style].join(" "); - const renderKey = (key: string, styles: string[] = []) => { - const [pressed, setPressed] = useState(false); - const classes = ["key", pressed ? "pressed" : "", ...styles].join(" "); - return ( - <span - className={classes} - key={key} - tabIndex={focusable ? 0 : undefined} - onKeyDown={(event) => { - if (event.key !== "Enter") return; - setPressed(true); - onKeyDown && onKeyDown(key); - event.stopPropagation(); - event.preventDefault(); - }} - onKeyUp={(event) => { - if (event.key !== "Enter") return; - setPressed(false); - onKeyUp && onKeyUp(key); - event.stopPropagation(); - event.preventDefault(); - }} - onBlur={(event) => { - setPressed(false); - onKeyUp && onKeyUp(key); - }} - onMouseDown={(event) => { - setPressed(true); - onKeyDown && onKeyDown(key); - event.stopPropagation(); - event.preventDefault(); - }} - onMouseUp={(event) => { - setPressed(false); - onKeyUp && onKeyUp(key); - event.stopPropagation(); - event.preventDefault(); - }} - onMouseLeave={(event) => { - if (!pressed) return; - setPressed(false); - onKeyUp && onKeyUp(key); - event.stopPropagation(); - event.preventDefault(); - }} - onTouchStart={(event) => { - setPressed(true); - onKeyDown && onKeyDown(key); - event.stopPropagation(); - event.preventDefault(); - }} - onTouchEnd={(event) => { - setPressed(false); - onKeyUp && onKeyUp(key); - event.stopPropagation(); - event.preventDefault(); - }} - > - {key} - </span> - ); - }; - return ( - <div className={classes()}> - <div className="keyboard-line"> - {["1", "2", "3", "4"].map((k) => renderKey(k))} - </div> - <div className="keyboard-line"> - {["Q", "W", "E", "R"].map((k) => renderKey(k))} - </div> - <div className="keyboard-line"> - {["A", "S", "D", "F"].map((k) => renderKey(k))} - </div> - <div className="keyboard-line"> - {["Z", "X", "C", "V"].map((k) => renderKey(k))} - </div> - </div> - ); -}; - -export default KeyboardChip8; diff --git a/frontends/web/react/components/keyboard-gb/keyboard-gb.css b/frontends/web/react/components/keyboard-gb/keyboard-gb.css deleted file mode 100644 index 29ea8df0..00000000 --- a/frontends/web/react/components/keyboard-gb/keyboard-gb.css +++ /dev/null @@ -1,257 +0,0 @@ -.keyboard-container.fullscreen { - bottom: 30px; - left: 0px; - pointer-events: none; - position: fixed; - width: 100%; - z-index: 10; -} - -@media only screen and (max-width: 1120px) { - .keyboard-container.fullscreen { - bottom: 0px; - } -} - -.keyboard-gb { - font-size: 0px; - -webkit-tap-highlight-color: transparent; - text-align: center; - touch-callout: none !important; - -o-touch-callout: none !important; - -ms-touch-callout: none !important; - -moz-touch-callout: none !important; - -khtml-touch-callout: none !important; - -webkit-touch-callout: none !important; - user-select: none; - -o-user-select: none; - -ms-user-select: none; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; -} - -.keyboard-gb.fullscreen { - background: rgba(0, 0, 0, 0.3); - border-radius: 24px 24px 24px 24px; - -o-border-radius: 24px 24px 24px 24px; - -ms-border-radius: 24px 24px 24px 24px; - -moz-border-radius: 24px 24px 24px 24px; - -khtml-border-radius: 24px 24px 24px 24px; - -webkit-border-radius: 24px 24px 24px 24px; - margin: 0px auto 0px auto; - max-width: 600px; - padding: 18px 0px 18px 0px; - pointer-events: initial; -} - -@media only screen and (max-width: 600px) { - .keyboard-gb.fullscreen { - border-radius: 0px 0px 0px 0px; - -o-border-radius: 0px 0px 0px 0px; - -ms-border-radius: 0px 0px 0px 0px; - -moz-border-radius: 0px 0px 0px 0px; - -khtml-border-radius: 0px 0px 0px 0px; - -webkit-border-radius: 0px 0px 0px 0px; - padding: 12px 0px 12px 0px; - } -} - -.keyboard-gb > .keyboard-line { - border-radius: 24px 24px 24px 24px; - -o-border-radius: 24px 24px 24px 24px; - -ms-border-radius: 24px 24px 24px 24px; - -moz-border-radius: 24px 24px 24px 24px; - -khtml-border-radius: 24px 24px 24px 24px; - -webkit-border-radius: 24px 24px 24px 24px; - margin-bottom: 12px; -} - -.keyboard-gb > .keyboard-line:last-child { - margin-bottom: 0px; -} - -.keyboard-gb .key { - border: 2px solid #ffffff; - border-radius: 5px 5px 5px 5px; - -o-border-radius: 5px 5px 5px 5px; - -ms-border-radius: 5px 5px 5px 5px; - -moz-border-radius: 5px 5px 5px 5px; - -khtml-border-radius: 5px 5px 5px 5px; - -webkit-border-radius: 5px 5px 5px 5px; - cursor: pointer; - display: inline-block; - font-size: 38px; - height: 48px; - line-height: 46px; - margin-right: 14px; - min-width: 48px; - padding: 0px 8px 0px 8px; - text-align: center; -} - -.keyboard-gb .key:last-child { - margin-right: 0px; -} - -.keyboard-gb .key.pressed { - background-color: #50cb93; -} - -.keyboard-gb > .dpad { - float: left; - text-align: center; - width: 172px; -} - -@media only screen and (max-width: 600px) { - .keyboard-gb > .dpad { - width: 162px; - } -} - -.keyboard-gb > .dpad > .dpad-top { - margin-bottom: -3px; -} - -.keyboard-gb > .dpad > .dpad-bottom { - margin-top: -3px; -} - -.keyboard-gb > .dpad .key { - border: none; - font-size: 24px; - padding: 0px 0px 0px 0px; -} - -.keyboard-gb > .dpad .key.pressed { - background-color: #50cb93; -} - -.keyboard-gb > .dpad .key.up { - border-bottom: 3px solid transparent; - border-left: 3px solid #ffffff; - border-radius: 6px 6px 0px 0px; - -o-border-radius: 6px 6px 0px 0px; - -ms-border-radius: 6px 6px 0px 0px; - -moz-border-radius: 6px 6px 0px 0px; - -khtml-border-radius: 6px 6px 0px 0px; - -webkit-border-radius: 6px 6px 0px 0px; - border-right: 3px solid #ffffff; - border-top: 3px solid #ffffff; -} - -.keyboard-gb > .dpad .key.left { - border-bottom: 3px solid #ffffff; - border-left: 3px solid #ffffff; - border-radius: 6px 0px 0px 6px; - -o-border-radius: 6px 0px 0px 6px; - -ms-border-radius: 6px 0px 0px 6px; - -moz-border-radius: 6px 0px 0px 6px; - -khtml-border-radius: 6px 0px 0px 6px; - -webkit-border-radius: 6px 0px 0px 6px; - border-right: 3px solid transparent; - border-top: 3px solid #ffffff; - margin-right: 44px; -} - -.keyboard-gb > .dpad .key.right { - border-bottom: 3px solid #ffffff; - border-left: 3px solid transparent; - border-radius: 0px 6px 6px 0px; - -o-border-radius: 0px 6px 6px 0px; - -ms-border-radius: 0px 6px 6px 0px; - -moz-border-radius: 0px 6px 6px 0px; - -khtml-border-radius: 0px 6px 6px 0px; - -webkit-border-radius: 0px 6px 6px 0px; - border-right: 3px solid #ffffff; - border-top: 3px solid #ffffff; - margin-left: -3px; - margin-right: 0px; -} - -.keyboard-gb > .dpad .key.center { - border-radius: 0px 0px 0px 0px; - -o-border-radius: 0px 0px 0px 0px; - -ms-border-radius: 0px 0px 0px 0px; - -moz-border-radius: 0px 0px 0px 0px; - -khtml-border-radius: 0px 0px 0px 0px; - -webkit-border-radius: 0px 0px 0px 0px; - margin-right: 0px; - pointer-events: none; -} - -.keyboard-gb > .dpad .key.down { - border-bottom: 3px solid #ffffff; - border-left: 3px solid #ffffff; - border-radius: 0px 0px 6px 6px; - -o-border-radius: 0px 0px 6px 6px; - -ms-border-radius: 0px 0px 6px 6px; - -moz-border-radius: 0px 0px 6px 6px; - -khtml-border-radius: 0px 0px 6px 6px; - -webkit-border-radius: 0px 0px 6px 6px; - border-right: 3px solid #ffffff; - border-top: 3px solid transparent; -} - -.keyboard-gb > .action { - float: right; - margin-right: 18px; -} - -@media only screen and (max-width: 600px) { - .keyboard-gb > .action { - margin-right: 8px; - } -} - -.keyboard-gb > .action > .key { - border-radius: 32px 32px 32px 32px; - -o-border-radius: 32px 32px 32px 32px; - -ms-border-radius: 32px 32px 32px 32px; - -moz-border-radius: 32px 32px 32px 32px; - -khtml-border-radius: 32px 32px 32px 32px; - -webkit-border-radius: 32px 32px 32px 32px; - border-width: 3px; - font-size: 30px; - font-weight: 900; - height: 58px; - line-height: 52px; - width: 58px; -} - -.keyboard-gb > .action > .key.a { - position: relative; - right: 0px; - top: 10px; -} - -.keyboard-gb > .action > .key.b { - position: relative; - right: 2px; - top: 50px; -} - -.keyboard-gb > .options { - margin-top: 32px; -} - -@media only screen and (max-width: 600px) { - .keyboard-gb > .options { - margin-top: 22px; - } -} - -.keyboard-gb > .options > .key { - font-size: 14px; - font-weight: 900; - height: 30px; - line-height: 26px; - margin-left: 16px; - margin-right: 16px; - min-width: 100px; -} - -.keyboard-gb > .break { - clear: both; -} diff --git a/frontends/web/react/components/keyboard-gb/keyboard-gb.tsx b/frontends/web/react/components/keyboard-gb/keyboard-gb.tsx deleted file mode 100644 index 282cb400..00000000 --- a/frontends/web/react/components/keyboard-gb/keyboard-gb.tsx +++ /dev/null @@ -1,332 +0,0 @@ -import React, { FC, useEffect, useRef, useState } from "react"; -import { isAndroid } from "../../util"; - -import "./keyboard-gb.css"; - -const KEYS: Record<string, string> = { - ArrowUp: "ArrowUp", - ArrowDown: "ArrowDown", - ArrowLeft: "ArrowLeft", - ArrowRight: "ArrowRight", - Enter: "Start", - " ": "Select", - a: "A", - s: "B" -}; - -const KEYS_XBOX: Record<number, string> = { - 12: "ArrowUp", - 102: "ArrowUp", - 13: "ArrowDown", - 103: "ArrowDown", - 14: "ArrowLeft", - 100: "ArrowLeft", - 15: "ArrowRight", - 101: "ArrowRight", - 9: "Start", - 8: "Select", - 1: "A", - 0: "B" -}; - -const PREVENT_KEYS: Record<string, boolean> = { - ArrowUp: true, - ArrowDown: true, - ArrowLeft: true, - ArrowRight: true, - " ": true -}; - -declare const require: any; - -enum Gamepad { - Unknown = 1, - Xbox, - Playstation, - Switch -} - -type KeyboardGBProps = { - focusable?: boolean; - fullscreen?: boolean; - physical?: boolean; - selectedKeys?: string[]; - style?: string[]; - onKeyDown?: (key: string) => void; - onKeyUp?: (key: string) => void; - onGamepad?: (id: string, isValid: boolean, connected?: boolean) => void; -}; - -/** - * The sequence of game pads that are considered - * supported by the current implementation. - */ -const SUPPORTED_PADS = [Gamepad.Xbox]; - -export const KeyboardGB: FC<KeyboardGBProps> = ({ - focusable = true, - fullscreen = false, - physical = true, - selectedKeys = [], - style = [], - onKeyDown, - onKeyUp, - onGamepad -}) => { - const containerClasses = () => - ["keyboard-container", fullscreen ? "fullscreen" : ""].join(" "); - const recordRef = - useRef<Record<string, React.Dispatch<React.SetStateAction<boolean>>>>(); - const classes = () => - [ - "keyboard", - "keyboard-gb", - fullscreen ? "fullscreen" : "", - ...style - ].join(" "); - useEffect(() => { - if (!physical) return; - const getGamepadType = (gamepad: globalThis.Gamepad): Gamepad => { - let gamepadType = Gamepad.Unknown; - const isXbox = gamepad.id.includes("Xbox"); - if (isXbox) gamepadType = Gamepad.Xbox; - return gamepadType; - }; - const _onKeyDown = (event: KeyboardEvent) => { - const keyCode = KEYS[event.key]; - const isPrevent = PREVENT_KEYS[event.key] ?? false; - if (isPrevent) event.preventDefault(); - if (keyCode !== undefined) { - const records = recordRef.current ?? {}; - const setter = records[keyCode]; - setter(true); - onKeyDown && onKeyDown(keyCode); - return; - } - }; - const _onKeyUp = (event: KeyboardEvent) => { - const keyCode = KEYS[event.key]; - const isPrevent = PREVENT_KEYS[event.key] ?? false; - if (isPrevent) event.preventDefault(); - if (keyCode !== undefined) { - const records = recordRef.current ?? {}; - const setter = records[keyCode]; - setter(false); - onKeyUp && onKeyUp(keyCode); - return; - } - }; - const onGamepadConnected = (event: GamepadEvent) => { - const gamepad = event.gamepad; - let gamepadType = getGamepadType(gamepad); - const isValid = SUPPORTED_PADS.includes(gamepadType); - - onGamepad && onGamepad(gamepad.id, isValid); - - let keySolver: Record<number, string>; - switch (gamepadType) { - case Gamepad.Xbox: - keySolver = KEYS_XBOX; - break; - } - - const buttonStates: Record<number, boolean> = {}; - - const updateStatus = () => { - const _gamepad = navigator.getGamepads()[gamepad.index]; - if (!_gamepad) return; - - handleButton(100, _gamepad.axes[0] < -0.5); - handleButton(101, _gamepad.axes[0] > 0.5); - handleButton(102, _gamepad.axes[1] < -0.5); - handleButton(103, _gamepad.axes[1] > 0.5); - - _gamepad.buttons.forEach((button, index) => { - const pressed = button.pressed; - handleButton(index, pressed); - }); - - requestAnimationFrame(updateStatus); - }; - - const handleButton = (index: number, pressed: boolean) => { - const keyCode = keySolver[index]; - if (keyCode === undefined) return; - const state = buttonStates[index] ?? false; - const keyDown = pressed && !state; - const keyUp = !pressed && state; - - if (keyDown) { - const records = recordRef.current ?? {}; - const setter = records[keyCode]; - setter(true); - onKeyDown && onKeyDown(keyCode); - } - - if (keyUp) { - const records = recordRef.current ?? {}; - const setter = records[keyCode]; - setter(false); - onKeyUp && onKeyUp(keyCode); - } - - buttonStates[index] = pressed; - }; - - requestAnimationFrame(updateStatus); - }; - const onGamepadDisconnected = (event: GamepadEvent) => { - const gamepad = event.gamepad; - let gamepadType = getGamepadType(gamepad); - const isValid = SUPPORTED_PADS.includes(gamepadType); - - onGamepad && onGamepad(gamepad.id, isValid, false); - }; - document.addEventListener("keydown", _onKeyDown); - document.addEventListener("keyup", _onKeyUp); - window.addEventListener("gamepadconnected", onGamepadConnected); - window.addEventListener("gamepaddisconnected", onGamepadDisconnected); - return () => { - document.removeEventListener("keydown", _onKeyDown); - document.removeEventListener("keyup", _onKeyUp); - window.removeEventListener("gamepadconnected", onGamepadConnected); - window.removeEventListener( - "gamepadconnected", - onGamepadDisconnected - ); - }; - }, []); - const renderKey = ( - key: string, - keyName?: string, - selected = false, - styles: string[] = [] - ) => { - const [pressed, setPressed] = useState(selected); - const classes = ["key", pressed ? "pressed" : "", ...styles].join(" "); - const records = recordRef.current ?? {}; - records[keyName ?? key ?? "undefined"] = setPressed; - recordRef.current = records; - return ( - <span - className={classes} - key={keyName ?? key} - tabIndex={focusable ? 0 : undefined} - onKeyDown={(event) => { - if (event.key !== "Enter") return; - setPressed(true); - onKeyDown && onKeyDown(keyName ?? key); - event.stopPropagation(); - event.preventDefault(); - }} - onKeyUp={(event) => { - if (event.key !== "Enter") return; - setPressed(false); - onKeyUp && onKeyUp(keyName ?? key); - event.stopPropagation(); - event.preventDefault(); - }} - onBlur={(event) => { - setPressed(false); - onKeyUp && onKeyUp(key); - }} - onMouseDown={(event) => { - setPressed(true); - onKeyDown && onKeyDown(keyName ?? key); - event.stopPropagation(); - event.preventDefault(); - }} - onMouseUp={(event) => { - setPressed(false); - onKeyUp && onKeyUp(keyName ?? key); - event.stopPropagation(); - event.preventDefault(); - }} - onMouseLeave={(event) => { - if (!pressed) return; - setPressed(false); - onKeyUp && onKeyUp(keyName ?? key); - event.stopPropagation(); - event.preventDefault(); - }} - onTouchStart={(event) => { - setPressed(true); - onKeyDown && onKeyDown(keyName ?? key); - event.stopPropagation(); - event.preventDefault(); - }} - onTouchEnd={(event) => { - setPressed(false); - onKeyUp && onKeyUp(keyName ?? key); - event.stopPropagation(); - event.preventDefault(); - }} - > - {key} - </span> - ); - }; - return ( - <div className={containerClasses()}> - <div - className={classes()} - onTouchStart={(e) => e.preventDefault()} - onTouchEnd={(e) => e.preventDefault()} - > - <div className="dpad"> - <div className="dpad-top"> - {renderKey( - isAndroid() ? "â–²" : "â–²", - "ArrowUp", - selectedKeys.includes("ArrowUp"), - ["up"] - )} - </div> - <div> - {renderKey( - isAndroid() ? "â—€" : "â—„", - "ArrowLeft", - selectedKeys.includes("ArrowLeft"), - ["left"] - )} - {renderKey( - isAndroid() ? "â–¶" : "â–º", - "ArrowRight", - selectedKeys.includes("ArrowRight"), - ["right"] - )} - </div> - <div className="dpad-bottom"> - {renderKey( - isAndroid() ? "â–¼" : "â–¼", - "ArrowDown", - selectedKeys.includes("ArrowDown"), - ["down"] - )} - </div> - </div> - <div className="action"> - {renderKey("B", "B", selectedKeys.includes("B"), ["b"])} - {renderKey("A", "A", selectedKeys.includes("A"), ["a"])} - </div> - <div className="break"></div> - <div className="options"> - {renderKey( - "SELECT", - "Select", - selectedKeys.includes("Select"), - ["select"] - )} - {renderKey( - "START", - "Start", - selectedKeys.includes("Start"), - ["start"] - )} - </div> - </div> - </div> - ); -}; - -export default KeyboardGB; diff --git a/frontends/web/react/components/link/link.css b/frontends/web/react/components/link/link.css deleted file mode 100644 index 30a552d4..00000000 --- a/frontends/web/react/components/link/link.css +++ /dev/null @@ -1,15 +0,0 @@ -.link { - border-bottom: 2px dotted #ffffff; - color: #ffffff; - text-decoration: none; - transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: border 0.25s cubic-bezier(0.075, 0.82, 0.165, 1); -} - -.link:hover { - border-bottom: 2px solid #ffffff; -} diff --git a/frontends/web/react/components/link/link.tsx b/frontends/web/react/components/link/link.tsx deleted file mode 100644 index ee5b134b..00000000 --- a/frontends/web/react/components/link/link.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { ReactNode, FC } from "react"; - -import "./link.css"; - -type LinkProps = { - children?: ReactNode; - text?: string; - href?: string; - target?: string; - style?: string[]; -}; - -export const Link: FC<LinkProps> = ({ - children, - text, - href, - target, - style = [] -}) => { - const classes = () => ["link", ...style].join(" "); - return ( - <a className={classes()} href={href} target={target}> - {children ?? text} - </a> - ); -}; - -export default Link; diff --git a/frontends/web/react/components/modal/close.svg b/frontends/web/react/components/modal/close.svg deleted file mode 100644 index aeac9982..00000000 --- a/frontends/web/react/components/modal/close.svg +++ /dev/null @@ -1 +0,0 @@ -<svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" viewBox="0 0 24 24" aria-labelledby="closeIconTitle" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#ffffff"> <title id="closeIconTitle">Close</title> <path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"/> </svg> \ No newline at end of file diff --git a/frontends/web/react/components/modal/modal.css b/frontends/web/react/components/modal/modal.css deleted file mode 100644 index 0c0ea90e..00000000 --- a/frontends/web/react/components/modal/modal.css +++ /dev/null @@ -1,149 +0,0 @@ -.modal { - align-items: center; - background-color: rgba(20, 20, 20, 0.95); - display: flex; - height: 100%; - justify-content: center; - left: 0px; - opacity: 0; - -o-opacity: 0; - -ms-opacity: 0; - -moz-opacity: 0; - -khtml-opacity: 0; - -webkit-opacity: 0; - padding: 12px 12px 12px 12px; - pointer-events: none; - position: fixed; - text-align: center; - top: 0px; - transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: opacity 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); - width: 100%; - z-index: 10; -} - -.modal.visible { - opacity: 1.0; - -o-opacity: 1.0; - -ms-opacity: 1.0; - -moz-opacity: 1.0; - -khtml-opacity: 1.0; - -webkit-opacity: 1.0; - pointer-events: initial; - transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); -} - -.modal > .modal-window { - background-color: #264653; - border-radius: 6px 6px 6px 6px; - -o-border-radius: 6px 6px 6px 6px; - -ms-border-radius: 6px 6px 6px 6px; - -moz-border-radius: 6px 6px 6px 6px; - -khtml-border-radius: 6px 6px 6px 6px; - -webkit-border-radius: 6px 6px 6px 6px; - box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); - -o-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); - -ms-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); - -moz-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); - -khtml-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); - -webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); - display: flex; - flex-direction: column; - margin-top: 30px; - max-height: 100%; - max-width: 100%; - outline: none; - -o-outline: none; - -ms-outline: none; - -moz-outline: none; - -khtml-outline: none; - -webkit-outline: none; - overflow-y: auto; - padding: 24px 24px 24px 24px; - text-align: left; - transform: scale(0.96); - -o-transform: scale(0.96); - -ms-transform: scale(0.96); - -moz-transform: scale(0.96); - -khtml-transform: scale(0.96); - -webkit-transform: scale(0.96); - transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: transform 0.3s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - width: 480px; -} - -.modal.visible > .modal-window { - margin-top: 0px; - pointer-events: all; - transform: scale(1); - -o-transform: scale(1); - -ms-transform: scale(1); - -moz-transform: scale(1); - -khtml-transform: scale(1); - -webkit-transform: scale(1); - transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), margin-top 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); -} - -.modal > .modal-window > .modal-top-buttons { - position: absolute; - right: 14px; - top: 14px; -} - -.modal > .modal-window > .modal-title { - font-size: 32px; - margin-top: 0px; - text-align: left; -} - -.modal > .modal-window > .modal-contents { - display: flex; - flex-direction: column; - overflow: auto; -} - -.modal > .modal-window > .modal-text { - font-size: 20px; - line-height: 22px; - overflow: auto; -} - -.modal > .modal-window > .modal-buttons { - font-size: 22px; - margin-top: 24px; - text-align: center; - user-select: none; - -o-user-select: none; - -ms-user-select: none; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; -} - -.modal > .modal-window > .modal-buttons > .button.simple { - display: inline-block; - margin-right: 12px; - min-width: 120px; -} - -.modal > .modal-window > .modal-buttons > .button.simple:last-child { - margin-right: 0px; -} diff --git a/frontends/web/react/components/modal/modal.tsx b/frontends/web/react/components/modal/modal.tsx deleted file mode 100644 index 2f2573b9..00000000 --- a/frontends/web/react/components/modal/modal.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { FC, ReactNode, useEffect, useRef } from "react"; -import Button from "../button/button"; - -import "./modal.css"; - -declare const require: any; - -type ModalProps = { - title?: string; - text?: string; - contents?: ReactNode; - visible?: boolean; - buttons?: boolean; - overlayClose?: boolean; - style?: string[]; - onConfirm?: () => void; - onCancel?: () => void; -}; - -export const Modal: FC<ModalProps> = ({ - title = "Alert", - text, - contents, - visible = false, - buttons, - overlayClose = true, - style = [], - onConfirm, - onCancel -}) => { - 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) => { - if (event.key === "Escape") { - onCancel && onCancel(); - } - }; - document.addEventListener("keydown", onKeyDown); - return () => { - document.removeEventListener("keydown", onKeyDown); - }; - }, []); - useEffect(() => { - if (visible) { - modalRef.current?.focus(); - } - }, [visible]); - const getTextHtml = (separator = /\n/g) => - text - ? { - __html: text.replace(separator, "<br/>") - } - : undefined; - const onWindowClick = ( - event: React.MouseEvent<HTMLDivElement, MouseEvent> - ) => { - if (!overlayClose) return; - event.stopPropagation(); - }; - return ( - <div className={classes()} onClick={onCancel}> - <div - ref={modalRef} - className="modal-window" - onClick={onWindowClick} - tabIndex={-1} - > - <div className="modal-top-buttons"> - <Button - text={""} - size={"medium"} - style={["simple", "rounded", "no-text"]} - image={require("./close.svg")} - imageAlt="close" - onClick={onCancel} - /> - </div> - <h2 className="modal-title">{title}</h2> - {contents ? ( - <div className="modal-contents">{contents}</div> - ) : ( - <p - className="modal-text" - dangerouslySetInnerHTML={getTextHtml()} - ></p> - )} - {buttons && ( - <div className="modal-buttons"> - <Button - text={"Cancel"} - size={"medium"} - style={["simple", "red", "border", "padded-large"]} - onClick={onCancel} - /> - <Button - text={"Confirm"} - size={"medium"} - style={["simple", "border", "padded-large"]} - onClick={onConfirm} - /> - </div> - )} - </div> - </div> - ); -}; - -export default Modal; diff --git a/frontends/web/react/components/overlay/overlay.css b/frontends/web/react/components/overlay/overlay.css deleted file mode 100644 index 14ad1c9b..00000000 --- a/frontends/web/react/components/overlay/overlay.css +++ /dev/null @@ -1,44 +0,0 @@ -.overlay { - align-items: center; - background-color: rgba(80, 203, 147, 0.95); - display: flex; - font-size: 48px; - height: 100%; - justify-content: center; - left: 0px; - opacity: 0.0; - -o-opacity: 0.0; - -ms-opacity: 0.0; - -moz-opacity: 0.0; - -khtml-opacity: 0.0; - -webkit-opacity: 0.0; - pointer-events: none; - position: fixed; - text-align: center; - top: 0px; - transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: opacity 0.4s cubic-bezier(0.075, 0.82, 0.165, 1); - width: 100%; - z-index: 10; -} - -.overlay.visible { - opacity: 1.0; - -o-opacity: 1.0; - -ms-opacity: 1.0; - -moz-opacity: 1.0; - -khtml-opacity: 1.0; - -webkit-opacity: 1.0; -} - -.overlay .overlay-image { - margin-top: 16px; -} - -.overlay .overlay-image > img { - width: 64px; -} diff --git a/frontends/web/react/components/overlay/overlay.tsx b/frontends/web/react/components/overlay/overlay.tsx deleted file mode 100644 index 201d6d4b..00000000 --- a/frontends/web/react/components/overlay/overlay.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { FC, useEffect, useState } from "react"; - -import "./overlay.css"; - -declare const require: any; - -type OverlayProps = { - text?: string; - style?: string[]; - onFile?: (file: File) => void; -}; - -export const Overlay: FC<OverlayProps> = ({ text, style = [], onFile }) => { - const [visible, setVisible] = useState(false); - const classes = () => - ["overlay", visible ? "visible" : "", ...style].join(" "); - useEffect(() => { - const onDrop = async (event: DragEvent) => { - if (!event.dataTransfer?.items) return; - if (event.dataTransfer?.items[0].type) return; - - setVisible(false); - - const file = event.dataTransfer?.files[0]; - onFile && onFile(file); - - event.preventDefault(); - event.stopPropagation(); - }; - const onDragOver = async (event: DragEvent) => { - if (!event.dataTransfer?.items) return; - if (event.dataTransfer?.items[0].type) return; - setVisible(true); - event.preventDefault(); - }; - const onDragEnter = async (event: DragEvent) => { - if (!event.dataTransfer?.items) return; - if (event.dataTransfer?.items[0].type) return; - setVisible(true); - }; - const onDragLeave = async (event: DragEvent) => { - if (!event.dataTransfer?.items) return; - if (event.dataTransfer?.items[0].type) return; - setVisible(false); - }; - document.addEventListener("drop", onDrop); - document.addEventListener("dragover", onDragOver); - document.addEventListener("dragenter", onDragEnter); - document.addEventListener("dragleave", onDragLeave); - return () => { - document.removeEventListener("drop", onDrop); - document.removeEventListener("dragover", onDragOver); - document.removeEventListener("dragenter", onDragEnter); - document.removeEventListener("dragleave", onDragLeave); - }; - }, []); - return ( - <div className={classes()}> - <div className="overlay-container"> - {text && <div className="overlay-text">{text}</div>} - <div className="overlay-image"> - <img alt="sunglasses" src={require("./sunglasses.png")} /> - </div> - </div> - </div> - ); -}; - -export default Overlay; diff --git a/frontends/web/react/components/overlay/sunglasses.png b/frontends/web/react/components/overlay/sunglasses.png deleted file mode 100644 index 98b0bba649db0699a170016e18bc4b7a9ce3b70c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4330 zcmcgvc~BEs9&S(vkBW$}xPSt~v51PuGF);b3W$JmD##g?BOHMM3gHfpJIJAA5h2PE zkSv0NARq+f2r3Zd5CORjNHP!wBqkw1BuQ_(TeVX&>#popIe&D$*YCZmuJ3r?@7Jjg zc2=<M^4kFbfLWikZ~_2vXethD{SF$IqhEglfL+zr7RSy+Pkfk&xt>3p(8iuN){X0( zBEYJRJlnRdd{U`v)Pcdyhh>$0FLvd)<}t&w=0?|n@G}VD;4YaA<$2`{nF$q#5}R9F z2g<xi$1RT`Y;~+QC9eUpO2KiXR7G73ScH&IDXL+V_O0^;vu^km0~Xhe)r(!F9ZHfs zogUa{USK}l+1Yt-@Z9~`t5x{yPWNtJ#LzYS-eS{!ZnuP_Z6?;zVr8Kf<9#5Tq`0q8 zAFe$MbHvw6DjFCN4h$z(%t#Z*J@Emew~o#}BMzFVS!BXUyM;?1w>jK(M6IW0z(#%g z4n6+VYtF>lIkUZ)33Ne-F{hdx+p0P52r7Y&X)_hxg()droH6&Fnu+yNt;xMoF%}jN z&%`AJ1O%je**p5>Bo)suEP4!%?IP&w=RDB}iix~jF_q7(Of7Esd9lUCD|X2n{?dQv z8(+Qs#cAqAZG2-tv<f^7UF@&5y{w!RkFe-{yj$k%i`_sHME+&~P=*FE0N4ru2xtZg zvkUmz)`pHZd35ZSam8R*PfJqcqa5*Ye2TDC-&AjP$1rIznLgfHtJtID2@i2X^&6z~ zhmg-NUG@0Rb$nY#w%d>55(q47)=jY|Emp8cIPi0UYdngTni5c}kjCtuoXTiYoN^dr zP~OuM>I;R}Xjs2m7f)Y<H5ech%yq5tFGqy0;vQS%)SXMEa}543i2o#JLyWYAx>j#~ ze`;xo=v+mRwAaJ2=~=6$^hY8>>_jrE<Thn#&q1HQJZ*Ikuu;M_<e9j~tLfdwlo97K z#&koBN+nXXbiMUutGcx}!jw36q<KcqSf`+O_uqy_hq2)niI*PmC26?f_Hbn55wFda zIOU7N1buO}N6Vdc>^9ZBn|<Jn-#XfuTW*T=7j7aPRS0a@;;{IMtDvl$YGdTC8bL{x z{;!HEL$Ys3(cgOG7k1h(SvHxHtxZjFE{)!1e;yWO$ri2|TkSJr!as9RdK(_enoGHB zHB^vPP}L~@edcp!{fV5CJa|K(N-LJxBupqsuyyL~=b+CEM|f~;j$&6@{Ku>(v4^`< zoAibSlqbxahU^7GiANF)C(WLsO>Mhkx+L%mW;`tnwb2JN%2#d-$wtz0``p7F9s93% zd~3~<AkLz^)Mr0hdu7ed#v}$j;8V3fagOc4AN_ecZX%g^?>0On$g2Gn>AhWBhO3tt zVz-szpge3F{L0k$5yjCd9mA%oTFeU~vA~bV=H(d(BQ36%m`J#F&$Q552&kAtwtAoj z+*>}~zD0#5j*d6H7}kgtPcmI>UKnjR$HUa~aNufbhc)%_bUv@DL2WoVYzF|WJQ^5? z<%}u=Kv5Fp;Q!@L|0)T-9s!9%2Q1Cju%klMXN=wu#zh3Mxs6yqKU}Ih<rcCBa4-xN z`&y9v>Z8Bd=`*_<awNq|%%PjKC<C++3B0Hk?;LcrEb~PBmHTu3hDAoQy`lV%;pRr^ zBRO~jM;~*|2YpIlY@KHMy-qx?rNlk0v%8Yg8g$7>UZI8-)$H{lHXUrL4=uNBX=O5Z zVxW<zaoYp$Ud*6nH%>;q_PI!}#a-1>t?Pm_I3x3{svgvYHuQ#juYggkOju)?;Kw+& z_=TAEk6l)HD4MUMt2Ah=9Za&NESoOf4}%w6OuQ7_^OGCW6|?kE0!U-rpsovH03egO zQN+K!gP(c&(%QdCAJt^jJrkl;Vwezae<rU+jSI5h&z$3bvTIV#yzgAv$vS+y5G~3A z1-|Hs2Ay$%oP<7gS(vvYPE;6s!BBtau;<p)Q-x-x{>%NR^g$FJG+!L6Rvi~4z(@ax zG_KUO2{cQjvo)?U>d1qMA~jeNCjZsGp2PV!(V|x)xxmQ)(cK;Nr8t(IK8{`7!9Mj2 zSnJ$I3m1|9^oJDzGO=eee04fB?0dBc4n2$Upd2(A7sS-gbv}t)xY<Wpm=In}#M##H z7rvurNgxJ20Jk}nzZ3_RMQ>6rSaHF&J^ZF-hiv+2^6a5w;iq#=G&2l2UB29Dg}v9) zwcT^kA`eV!p!8pR6W3Wv+?{s_Xc4y`Uby^evN@*qv@&qAghGKYHL{4l+<v3={02~C z)mTbq@R`q^PAxJGA}1AT_LS!u_U-Qq<dU(-Y!xMef&F4ej09ON5A152i>8lfLTI9W z&U#`$tlsRy`OWrN_k)C;A^7n^3?vprd!{yJt($O^UL?$gv|zFH+il^~cRm9(g@AgH zqr3*Wpaf-9PGWKBHaZc5x(gR=Zj<7=Gg4w6zL=x5#{LipRJgSq4W;l_o)JMy>g9Ga zWom(%WFgxdbYn~Hpcf8Yu6l00>FsRxK?d|duCq73Z!EiyHgo)Bm)1sllK&7T${W2{ zO^5Sd)7FK~cJJIx8cQQy5up}C(=xd3R(6k@bK2EHY?z+xON?#(8m2<=M%`6L)CHBH zQoW8_)-c)PB4R?IM>ChWpq*{beB3#e@V=5#DC`Mqt_ecb@F%gaeJ(efIaV>HHRq7w zOUx*LO#=i5%ZhPRgxl!hUffRzf4;@pGZol!X^Iz2ze`QN*@sN*OAPPc=0*0^mB_gl z3xoWD5Zm^xi8xO#KPT=a_r_4Fs&WES`1+<ip9s8dQJHsqh*d>&l-jsJR~Bo9Bnz)W z73MplTn4-;@pKKpr)pf=&ZxB~b$=PkH59)fq8Ipbao|UNF@)3a<ZP&NLQMoRKgNOK zmX81M$t8{N!{DJWAHoEnyC$4^;qjp!dvU&T7YwFzv7PjvT<bOR;599Zm<l=GVF!oW zQdd$_Ek}Vr<&msm#xtTSPAC?H%s#6GrzO#;xHvr;Z`w)AQnO}&?cS=@A;`ji7&8lw z<#Nz-X$srrfcvNC=Npyriz8>a;PP^7!%DN}RH}62d*aizr!7|@i-b0uw>e}~*MD3s zLR3@7ers<4m2m3W_0B+zNO1pxk|Cvx+4g%leepr%O2iL(vFoI<8Wp8@Bx+jVHk8+Y zH1GmdPD+Xw>s>zVvzSFR*2%=r#khdeFZa6^SKqSayHdP0QKunJIq#yq1B1FIocDr` zNOU@5XR!7nAMBX)VUIki9y{ukZql!+a&h6L={yV6O(qW)uS}!jedDx(>IBjhds94g zWWDav2`XnyCl{n3t6tVS`4)0AGX10=!tKEqq+6D83#IU?ySoS6g=<`>I0poZ-gW$7 zKp@?pAyFS{l?3cZDVdVp(^f{D&g9uThH#!LNwMEad(eL(kTvptrIRJ~F{Y5qpQpKj zX~A27I5WhPPlq$QFm`(2GhGd8Hm-&&Ew<(HznW=^FfD<;oE5j!>ko?`9Y1NleNz$? zA^#y=zOuEU9_Ampd`JCcDVc<QnlCq8E4yzvJT{F_^5LP;D)RCN2VB~DNJENghSw{9 zRoT}J`;Z$b?>8=Ej4FLXalRjkQD^c(<<QY53G4aVkKg3+Bt1h6wgW4dlP<4%6KAU{ z%BZ*(<kd9W{_&m@`^yeu;*6JKqs=ei!E1yM(x}8bW@QELt#OpOPUDsh_7Y!W<4bE_ zY9r5&LWT6og0{A4)YzM#^<t9o%l&jVrRhR3yJ_@cXh`UR$T3)lZe)b{n}*Bt_4`OX zn5Bs*M%Vp>N{{R0=f6Yov<Fq?tlShq&_Tyj2A)UMbMi-43FEz2;Pvt{oNEZ%-vb3f axM#E1gr=bQ3>UHoU~Oq<QF;9QkADZ#7ywQH diff --git a/frontends/web/react/components/pair/pair.css b/frontends/web/react/components/pair/pair.css deleted file mode 100644 index e69de29b..00000000 diff --git a/frontends/web/react/components/pair/pair.tsx b/frontends/web/react/components/pair/pair.tsx deleted file mode 100644 index 9e032e85..00000000 --- a/frontends/web/react/components/pair/pair.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { FC, ReactNode } from "react"; - -import "./pair.css"; - -type PairProps = { - name: string; - value?: string; - valueNode?: ReactNode; - style?: string[]; - onNameClick?: () => void; - onValueClick?: () => void; -}; - -export const Pair: FC<PairProps> = ({ - name, - value, - valueNode, - style = [], - onNameClick, - onValueClick -}) => { - const classes = () => ["pair", ...style].join(" "); - const _onNameClick = () => (onNameClick ? onNameClick() : undefined); - const _onValueClick = () => (onValueClick ? onValueClick() : undefined); - return ( - <> - <dt className={classes()} onClick={_onNameClick}> - {name} - </dt> - <dd className={classes()} onClick={_onValueClick}> - {valueNode ?? value ?? ""} - </dd> - </> - ); -}; - -export default Pair; diff --git a/frontends/web/react/components/panel-split/panel-split.css b/frontends/web/react/components/panel-split/panel-split.css deleted file mode 100644 index 8b927d8a..00000000 --- a/frontends/web/react/components/panel-split/panel-split.css +++ /dev/null @@ -1,30 +0,0 @@ -.panel-split { - display: flex; -} - -@media only screen and (max-width: 1120px) { - .panel-split { - flex-direction: column; - } -} - -.panel-split > .side-left { - display: flex; - flex: 1 0; - justify-content: center; - text-align: center; -} - -.panel-split > .side-right { - flex: 0; - max-width: 100%; - min-width: 580px; - padding: 0px 24px 0px 24px; -} - -@media only screen and (max-width: 1120px) { - .panel-split > .side-right { - min-width: unset; - padding: 0px 0px 0px 0px; - } -} diff --git a/frontends/web/react/components/panel-split/panel-split.tsx b/frontends/web/react/components/panel-split/panel-split.tsx deleted file mode 100644 index 10020f2f..00000000 --- a/frontends/web/react/components/panel-split/panel-split.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { FC, ReactNode } from "react"; - -import "./panel-split.css"; - -type PanelSplitProps = { - children?: ReactNode; - left?: ReactNode; - right?: ReactNode; - style?: string[]; -}; - -export const PanelSplit: FC<PanelSplitProps> = ({ - children, - left, - right, - style = [] -}) => { - const classes = () => ["panel-split", ...style].join(" "); - return ( - <div className={classes()}> - <div className="side-left">{left}</div> - <div className="side-right">{children ?? right}</div> - </div> - ); -}; - -export default PanelSplit; diff --git a/frontends/web/react/components/panel-tab/panel-tab.css b/frontends/web/react/components/panel-tab/panel-tab.css deleted file mode 100644 index 98460c1f..00000000 --- a/frontends/web/react/components/panel-tab/panel-tab.css +++ /dev/null @@ -1,65 +0,0 @@ -.panel-tab { - display: flex; - flex-direction: column; - overflow: auto; -} - -.panel-tab > .tab-selectors { - line-height: 30px; - margin-bottom: 26px; -} - -.panel-tab > .tab-selectors > .tab-selector { - border-bottom: 2px solid transparent; - cursor: pointer; - display: inline-block; - font-size: 22px; - margin-right: 18px; - opacity: 0.8; - -o-opacity: 0.8; - -ms-opacity: 0.8; - -moz-opacity: 0.8; - -khtml-opacity: 0.8; - -webkit-opacity: 0.8; - transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: opacity 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); - user-select: none; - -o-user-select: none; - -ms-user-select: none; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; -} - -.panel-tab > .tab-selectors > .tab-selector:last-child { - margin-right: 0px; -} - -.panel-tab > .tab-selectors > .tab-selector:hover { - opacity: 1.0; - -o-opacity: 1.0; - -ms-opacity: 1.0; - -moz-opacity: 1.0; - -khtml-opacity: 1.0; - -webkit-opacity: 1.0; -} - -.panel-tab > .tab-selectors > .tab-selector.selected { - border-bottom: 2px solid #ffffff; - opacity: 1.0; - -o-opacity: 1.0; - -ms-opacity: 1.0; - -moz-opacity: 1.0; - -khtml-opacity: 1.0; - -webkit-opacity: 1.0; -} - -.panel-tab > .tab-container { - display: flex; - flex-direction: column; - overflow: auto; -} diff --git a/frontends/web/react/components/panel-tab/panel-tab.tsx b/frontends/web/react/components/panel-tab/panel-tab.tsx deleted file mode 100644 index 43fef5e6..00000000 --- a/frontends/web/react/components/panel-tab/panel-tab.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { FC, ReactNode, useState } from "react"; - -import "./panel-tab.css"; - -type PanelTabProps = { - tabs: ReactNode[]; - tabNames: string[]; - tabIndex?: number; - selectors?: boolean; - style?: string[]; -}; - -export const PanelTab: FC<PanelTabProps> = ({ - tabs, - tabNames, - tabIndex = 0, - selectors = true, - style = [] -}) => { - const classes = () => ["panel-tab", ...style].join(" "); - const [tabIndexState, setTabIndexState] = useState(tabIndex); - return ( - <div className={classes()}> - {selectors && ( - <div className="tab-selectors"> - {tabNames.map((tabName, tabIndex) => { - const classes = [ - "tab-selector", - tabIndex === tabIndexState ? "selected" : "" - ].join(" "); - return ( - <span - className={classes} - onClick={() => setTabIndexState(tabIndex)} - > - {tabName} - </span> - ); - })} - </div> - )} - <div className="tab-container">{tabs[tabIndexState]}</div> - </div> - ); -}; - -export default PanelTab; diff --git a/frontends/web/react/components/paragraph/paragraph.css b/frontends/web/react/components/paragraph/paragraph.css deleted file mode 100644 index 4d6c5b87..00000000 --- a/frontends/web/react/components/paragraph/paragraph.css +++ /dev/null @@ -1,5 +0,0 @@ -.paragraph { - font-size: 18px; - line-height: 24px; - margin: 12px 0px 12px 0px; -} diff --git a/frontends/web/react/components/paragraph/paragraph.tsx b/frontends/web/react/components/paragraph/paragraph.tsx deleted file mode 100644 index 6d1efdb5..00000000 --- a/frontends/web/react/components/paragraph/paragraph.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { ReactNode, FC } from "react"; - -import "./paragraph.css"; - -type ParagraphProps = { - children?: ReactNode; - text?: string; - style?: string[]; -}; - -export const Paragraph: FC<ParagraphProps> = ({ - children, - text, - style = [] -}) => { - const classes = () => ["paragraph", ...style].join(" "); - return <p className={classes()}>{children ?? text}</p>; -}; - -export default Paragraph; diff --git a/frontends/web/react/components/registers-gb/registers-gb.css b/frontends/web/react/components/registers-gb/registers-gb.css deleted file mode 100644 index 0801939b..00000000 --- a/frontends/web/react/components/registers-gb/registers-gb.css +++ /dev/null @@ -1,32 +0,0 @@ -.registers-gb > .section { - display: inline-block; - margin-right: 32px; - vertical-align: top; -} - -.registers-gb > .section:last-child { - margin-right: 0px; -} - -.registers-gb > .section > h4 { - font-size: 22px; - margin: 0px 0px 8px 0px; -} - -.registers-gb > .section > .register { - font-size: 0px; - line-height: 22px; -} - -.registers-gb > .section > .register > .register-key { - display: inline-block; - font-size: 20px; - width: 40px; -} - -.registers-gb > .section > .register > .register-value { - display: inline-block; - font-size: 20px; - text-align: right; - width: 66px; -} diff --git a/frontends/web/react/components/registers-gb/registers-gb.tsx b/frontends/web/react/components/registers-gb/registers-gb.tsx deleted file mode 100644 index 988ba20e..00000000 --- a/frontends/web/react/components/registers-gb/registers-gb.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { FC, useEffect, useRef, useState } from "react"; - -import "./registers-gb.css"; - -type RegistersGBProps = { - getRegisters: () => Record<string, string | number>; - interval?: number; - style?: string[]; -}; - -export const RegistersGB: FC<RegistersGBProps> = ({ - getRegisters, - interval = 50, - style = [] -}) => { - const classes = () => ["registers-gb", ...style].join(" "); - const [registers, setRegisters] = useState<Record<string, string | number>>( - {} - ); - const intervalsRef = useRef<number>(); - useEffect(() => { - const updateRegisters = () => { - const registers = getRegisters(); - setRegisters(registers); - }; - setInterval(() => updateRegisters(), interval); - updateRegisters(); - return () => { - if (intervalsRef.current) { - clearInterval(intervalsRef.current); - } - }; - }, []); - const renderRegister = ( - key: string, - value?: number, - size = 2, - styles: string[] = [] - ) => { - const classes = ["register", ...styles].join(" "); - const valueS = - value?.toString(16).toUpperCase().padStart(size, "0") ?? value; - return ( - <div className={classes}> - <span className="register-key">{key}</span> - <span className="register-value"> - {valueS ? `0x${valueS}` : "-"} - </span> - </div> - ); - }; - return ( - <div className={classes()}> - <div className="section"> - <h4>CPU</h4> - {renderRegister("PC", registers.pc as number, 4)} - {renderRegister("SP", registers.sp as number, 4)} - {renderRegister("A", registers.a as number)} - {renderRegister("B", registers.b as number)} - {renderRegister("C", registers.c as number)} - {renderRegister("D", registers.d as number)} - {renderRegister("E", registers.e as number)} - {renderRegister("H", registers.h as number)} - {renderRegister("L", registers.l as number)} - </div> - <div className="section"> - <h4>PPU</h4> - {renderRegister("SCY", registers.scy as number)} - {renderRegister("SCX", registers.scx as number)} - {renderRegister("WY", registers.wy as number)} - {renderRegister("WX", registers.wx as number)} - {renderRegister("LY", registers.ly as number)} - {renderRegister("LYC", registers.lyc as number)} - </div> - </div> - ); -}; - -export default RegistersGB; diff --git a/frontends/web/react/components/section/section.css b/frontends/web/react/components/section/section.css deleted file mode 100644 index afe6f3de..00000000 --- a/frontends/web/react/components/section/section.css +++ /dev/null @@ -1,13 +0,0 @@ -.section { - display: none; -} - -.section.visible { - display: block; -} - -.section > .separator { - background: #ffffff; - height: 2px; - margin: 22px 0px 22px 0px; -} diff --git a/frontends/web/react/components/section/section.tsx b/frontends/web/react/components/section/section.tsx deleted file mode 100644 index 4958a17b..00000000 --- a/frontends/web/react/components/section/section.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { FC, ReactNode } from "react"; - -import "./section.css"; - -type SectionProps = { - children: ReactNode; - visible?: boolean; - separator?: boolean; - separatorBottom?: boolean; - style?: string[]; -}; - -export const Section: FC<SectionProps> = ({ - children, - visible = true, - separator = true, - separatorBottom = false, - style = [] -}) => { - const classes = () => - ["section", visible ? "visible" : "", ...style].join(" "); - return ( - <div className={classes()}> - {separator && <div className="separator"></div>} - <div className="section-contents">{children}</div> - {separatorBottom && <div className="separator"></div>} - </div> - ); -}; - -export default Section; diff --git a/frontends/web/react/components/tiles/tiles.css b/frontends/web/react/components/tiles/tiles.css deleted file mode 100644 index 030a9575..00000000 --- a/frontends/web/react/components/tiles/tiles.css +++ /dev/null @@ -1,14 +0,0 @@ -.tiles > .canvas { - background-color: #1b1a17; - border: 2px solid #50cb93; - padding: 8px 8px 8px 8px; -} - -.tiles > .canvas.content-box { - box-sizing: content-box; - -o-box-sizing: content-box; - -ms-box-sizing: content-box; - -moz-box-sizing: content-box; - -khtml-box-sizing: content-box; - -webkit-box-sizing: content-box; -} diff --git a/frontends/web/react/components/tiles/tiles.tsx b/frontends/web/react/components/tiles/tiles.tsx deleted file mode 100644 index c1b6f7b5..00000000 --- a/frontends/web/react/components/tiles/tiles.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { FC, useEffect, useRef } from "react"; -import { PixelFormat } from "../../structs"; -import Canvas, { CanvasStructure } from "../canvas/canvas"; - -import "./tiles.css"; - -type TilesProps = { - getTile: (index: number) => Uint8Array; - tileCount: number; - width?: number | string; - contentBox?: boolean; - interval?: number; - style?: string[]; -}; - -export const Tiles: FC<TilesProps> = ({ - getTile, - tileCount, - width, - contentBox = true, - interval = 1000, - style = [] -}) => { - const classes = () => - ["tiles", contentBox ? "content-box" : "", ...style].join(" "); - const intervalsRef = useRef<number>(); - useEffect(() => { - return () => { - if (intervalsRef.current) { - clearInterval(intervalsRef.current); - } - }; - }, []); - const onCanvas = (structure: CanvasStructure) => { - const drawTiles = () => { - for (let index = 0; index < 384; index++) { - const pixels = getTile(index); - drawTile(index, pixels, structure); - } - }; - drawTiles(); - intervalsRef.current = setInterval(() => drawTiles(), interval); - }; - return ( - <div className={classes()}> - <Canvas - width={128} - height={192} - scale={2} - scaledWidth={width} - onCanvas={onCanvas} - /> - </div> - ); -}; - -/** - * Draws the tile at the given index to the proper vertical - * offset in the given context and buffer. - * - * @param index The index of the sprite to be drawn. - * @param pixels Buffer of pixels that contains the RGB data - * that is going to be drawn. - * @param structure The canvas context to which the tile is - * growing to be drawn. - * @param format The pixel format of the sprite. - */ -const drawTile = ( - index: number, - pixels: Uint8Array, - structure: CanvasStructure, - format: PixelFormat = PixelFormat.RGB -) => { - const line = Math.floor(index / 16); - const column = index % 16; - let offset = - (line * structure.canvas.width * 8 + column * 8) * PixelFormat.RGBA; - let counter = 0; - for (let i = 0; i < pixels.length; i += format) { - const color = - (pixels[i] << 24) | - (pixels[i + 1] << 16) | - (pixels[i + 2] << 8) | - (format === PixelFormat.RGBA ? pixels[i + 3] : 0xff); - structure.canvasBuffer.setUint32(offset, color); - - counter++; - if (counter === 8) { - counter = 0; - offset += (structure.canvas.width - 7) * PixelFormat.RGBA; - } else { - offset += PixelFormat.RGBA; - } - } - structure.canvasContext.putImageData(structure.canvasImage, 0, 0); -}; - -export default Tiles; diff --git a/frontends/web/react/components/title/title.css b/frontends/web/react/components/title/title.css deleted file mode 100644 index 7989a40d..00000000 --- a/frontends/web/react/components/title/title.css +++ /dev/null @@ -1,10 +0,0 @@ -.title > .link, -.title > .label { - margin-left: 14px; -} - -.title > .icon { - margin-left: 14px; - vertical-align: middle; - width: 32px; -} diff --git a/frontends/web/react/components/title/title.tsx b/frontends/web/react/components/title/title.tsx deleted file mode 100644 index ac312ce7..00000000 --- a/frontends/web/react/components/title/title.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { FC } from "react"; -import { Link } from "../link/link"; - -import "./title.css"; - -type TitleProps = { - text: string; - version?: string; - versionUrl?: string; - iconSrc?: string; - style?: string[]; -}; - -export const Title: FC<TitleProps> = ({ - text, - version, - versionUrl, - iconSrc, - style = [] -}) => { - const classes = () => ["title", ...style].join(" "); - return ( - <h1 className={classes()}> - {text} - {version && - (versionUrl ? ( - <Link href={versionUrl} target="_blank"> - {version} - </Link> - ) : ( - <span className="label">{version}</span> - ))} - {iconSrc && <img className="icon" src={iconSrc} alt="icon" />} - </h1> - ); -}; - -export default Title; diff --git a/frontends/web/react/components/toast/toast.css b/frontends/web/react/components/toast/toast.css deleted file mode 100644 index 8bf52332..00000000 --- a/frontends/web/react/components/toast/toast.css +++ /dev/null @@ -1,57 +0,0 @@ -.toast { - background-color: black; - height: 0px; - left: 0px; - padding: 0px 24px 0px 24px; - pointer-events: none; - position: fixed; - text-align: center; - top: 0px; - width: 100%; - z-index: 8; -} - -.toast > .toast-text { - background-color: #2a9d8f; - border-radius: 4px 4px 4px 4px; - -o-border-radius: 4px 4px 4px 4px; - -ms-border-radius: 4px 4px 4px 4px; - -moz-border-radius: 4px 4px 4px 4px; - -khtml-border-radius: 4px 4px 4px 4px; - -webkit-border-radius: 4px 4px 4px 4px; - cursor: pointer; - display: inline-block; - font-size: 20px; - line-height: 22px; - opacity: 0.0; - -o-opacity: 0.0; - -ms-opacity: 0.0; - -moz-opacity: 0.0; - -khtml-opacity: 0.0; - -webkit-opacity: 0.0; - padding: 12px 18px 12px 18px; - position: relative; - top: -46px; - transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -o-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -ms-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -moz-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -khtml-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - -webkit-transition: top 0.5s cubic-bezier(0.075, 0.82, 0.165, 1), opacity 0.35s cubic-bezier(0.075, 0.82, 0.165, 1); - width: fit-content; -} - -.toast.error > .toast-text { - background-color: #e63946; -} - -.toast.visible > .toast-text { - opacity: 1.0; - -o-opacity: 1.0; - -ms-opacity: 1.0; - -moz-opacity: 1.0; - -khtml-opacity: 1.0; - -webkit-opacity: 1.0; - pointer-events: all; - top: 24px; -} diff --git a/frontends/web/react/components/toast/toast.tsx b/frontends/web/react/components/toast/toast.tsx deleted file mode 100644 index 3c1f3831..00000000 --- a/frontends/web/react/components/toast/toast.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { FC } from "react"; - -import "./toast.css"; - -type ToastProps = { - text?: string; - error?: boolean; - visible?: boolean; - style?: string[]; - onCancel?: () => void; -}; - -export const Toast: FC<ToastProps> = ({ - text = "", - error = false, - visible = false, - style = [], - onCancel -}) => { - const classes = () => - [ - "toast", - error ? "error" : "", - visible ? "visible" : "", - ...style - ].join(" "); - return ( - <div className={classes()}> - <div className="toast-text" onClick={onCancel}> - {text} - </div> - </div> - ); -}; - -export default Toast; diff --git a/frontends/web/react/structs.ts b/frontends/web/react/structs.ts deleted file mode 100644 index d2719b88..00000000 --- a/frontends/web/react/structs.ts +++ /dev/null @@ -1,275 +0,0 @@ -export const FREQUENCY_DELTA = 100000; - -export type Callback<T> = (owner: T, params?: any) => void; - -export type RomInfo = { - name?: string; - data?: Uint8Array; - size?: number; - extra?: Record<string, string | undefined>; -}; - -export type BenchmarkResult = { - delta: number; - count: number; - cycles: number; - frequency_mhz: number; -}; - -export type Entry = { - text: string; - url?: string; -}; - -/** - * Enumeration to be used to describe the set of - * features that a certain emulator supports, this - * is going to condition its runtime execution. - */ -export enum Feature { - Debug = 1, - Palettes, - Benchmark, - Keyboard, - KeyboardChip8, - KeyboardGB -} - -/** - * Enumeration that describes the multiple pixel - * formats and the associated size in bytes. - */ -export enum PixelFormat { - RGB = 3, - RGBA = 4 -} - -export interface ObservableI { - bind(event: string, callback: Callback<this>): void; - unbind(event: string, callback: Callback<this>): void; - trigger(event: string): void; -} - -/** - * Top level interface that declares the main abstract - * interface of an emulator structured entity. - * Should allow typical hardware operations to be performed. - */ -export interface Emulator extends ObservableI { - /** - * The descriptive name of the emulator. - */ - get name(): string; - - /** - * The information on the hardware that is being emulated - * by the emulator (eg: Super Nintendo), can contain a URL - * that describes the device that is being emulated by - * the emulator (eg: Wikipedia link). - */ - get device(): Entry; - - /** - * A semantic version string for the current version - * of the emulator, can include a URL pointing to a - * changelog or equivalent document. - * - * @see {@link https://semver.org} - */ - get version(): Entry | undefined; - - /** - * Information about the source code repository where - * the emulator source code is being stored. - */ - get repository(): Entry | undefined; - - /** - * The features available and compatible with the emulator, - * these values will influence the associated GUIs. - */ - get features(): Feature[]; - - /** - * The complete set of engine names that can be used - * in the re-boot operation. - */ - get engines(): string[]; - - /** - * The name of the current execution engine being used - * by the emulator. - */ - get engine(): string | null; - - /** - * The complete set of file extensions that this emulator - * supports. - */ - get romExts(): string[]; - - /** - * The pixel format of the emulator's display - * image buffer (eg: RGB). - */ - get pixelFormat(): PixelFormat; - - /** - * Gets the complete image buffer as a sequence of - * bytes that respects the current pixel format from - * `getPixelFormat()`. This method returns an in memory - * pointer to the heap and not a copy. - */ - get imageBuffer(): Uint8Array; - - /** - * Gets information about the ROM that is currently - * loaded in the emulator, using a structure containing - * the information about the ROM that is currently - * loaded in the emulator. - */ - get romInfo(): RomInfo; - - /** - * The current CPU frequency (logic) of the emulator, - * should impact other elements of the emulator. - */ - get frequency(): number; - set frequency(value: number); - - /** - * The recommended frequency delta in hertz for scale up - * and scale down operations in the CPU frequency. - */ - get frequencyDelta(): number | null; - - /** - * The current logic framerate of the running emulator. - */ - get framerate(): number; - - /** - * A dictionary that contains the register names associated - * with their value either as strings or numbers. - */ - get registers(): Record<string, string | number>; - - /** - * The palette as a string name that is currently - * set in the emulator for display. - */ - get palette(): string | undefined; - set palette(value: string | undefined); - - /** - * Obtains the pixel buffer for the VRAM tile at the given - * index. - * - * @param index The index of the tile to obtain pixel buffer. - * @returns The pixel buffer of the tile at the given index. - */ - getTile(index: number): Uint8Array; - - /** - * Boot (or reboots) the emulator according to the provided - * set of options. - * - * @param options The options that are going to be used for - * the booting operation of the emulator. - */ - boot(options: any): void; - - /** - * Toggle the running state of the emulator between paused - * and running, prevents consumers from the need to access - * the current running state of the emulator to implement - * a logic toggle. - */ - toggleRunning(): void; - pause(): void; - resume(): void; - - /** - * Resets the emulator machine to the start state and - * re-loads the ROM that is currently set in the emulator. - */ - reset(): void; - - keyPress(key: string): void; - - keyLift(key: string): void; - - /** - * Changes the palette of the emulator to the "next" one, - * the order in which the palette is chosen is defined by - * the concrete emulator implementation. - * - * @returns The name of the palette that has been selected. - */ - changePalette?: { (): string }; - - /** - * Runs a benchmark operation in the emulator, effectively - * measuring the performance of it. - * - * @param count The number of benchmark iterations to be - * run, increasing this value will make the benchmark take - * more time to be executed. - * @returns The result metrics from the benchmark run. - */ - benchmark?: { (count?: number): BenchmarkResult }; -} - -/** - * Abstract class that implements the basic functionality - * part of the definition of the Observer pattern. - * - * @see {@link https://en.wikipedia.org/wiki/Observer_pattern} - */ -export class Observable { - private events: Record<string, [Callback<this>]> = {}; - - bind(event: string, callback: Callback<this>) { - const callbacks = this.events[event] ?? []; - if (callbacks.includes(callback)) return; - callbacks.push(callback); - this.events[event] = callbacks; - } - - unbind(event: string, callback: Callback<this>) { - const callbacks = this.events[event] ?? []; - if (!callbacks.includes(callback)) return; - const index = callbacks.indexOf(callback); - callbacks.splice(index, 1); - this.events[event] = callbacks; - } - - trigger(event: string, params?: any) { - const callbacks = this.events[event] ?? []; - callbacks.forEach((c) => c(this, params)); - } -} - -export class EmulatorBase extends Observable { - get version(): Entry | undefined { - return undefined; - } - - get repository(): Entry | undefined { - return undefined; - } - - get features(): Feature[] { - return []; - } - - get frequencyDelta(): number | null { - return FREQUENCY_DELTA; - } - - get palette(): string | undefined { - return undefined; - } - - set palette(value: string | undefined) {} -} diff --git a/frontends/web/react/util.ts b/frontends/web/react/util.ts deleted file mode 100644 index ba258354..00000000 --- a/frontends/web/react/util.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const isAndroid = () => { - return navigator.userAgent.match(/Android/i); -}; diff --git a/frontends/web/gb.ts b/frontends/web/ts/gb.ts similarity index 96% rename from frontends/web/gb.ts rename to frontends/web/ts/gb.ts index d3103b8a..5ddc731f 100644 --- a/frontends/web/gb.ts +++ b/frontends/web/ts/gb.ts @@ -5,7 +5,7 @@ import { Feature, PixelFormat, RomInfo -} from "./react/structs"; +} from "emukit"; import { PALETTES, PALETTES_MAP } from "./palettes"; import { @@ -14,8 +14,8 @@ import { GameBoy, PadKey, PpuMode -} from "./lib/boytacean.js"; -import info from "./package.json"; +} from "../lib/boytacean.js"; +import info from "../package.json"; import { base64ToBuffer, bufferToBase64 } from "./util"; declare const require: any; @@ -45,7 +45,7 @@ const KEYS_NAME: Record<string, number> = { B: PadKey.B }; -const ROM_PATH = require("../../res/roms/pocket.gb"); +const ROM_PATH = require("../../../res/roms/pocket.gb"); /** * Top level class that controls the emulator behaviour diff --git a/frontends/web/ts/index.ts b/frontends/web/ts/index.ts new file mode 100644 index 00000000..f9401867 --- /dev/null +++ b/frontends/web/ts/index.ts @@ -0,0 +1,3 @@ +export * from "./gb"; +export * from "./palettes"; +export * from "./util"; diff --git a/frontends/web/palettes.ts b/frontends/web/ts/palettes.ts similarity index 100% rename from frontends/web/palettes.ts rename to frontends/web/ts/palettes.ts diff --git a/frontends/web/util.ts b/frontends/web/ts/util.ts similarity index 100% rename from frontends/web/util.ts rename to frontends/web/ts/util.ts -- GitLab