From f58e9242abf2c05b0fc3069887f1ee94830b72d8 Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Sun, 7 Jul 2024 14:24:01 -0700 Subject: [PATCH] Add Preact cassette UI (#225) --- .eslintrc.json | 70 +++++++--------------------------- js/components/App.tsx | 1 + js/components/Cassette.tsx | 70 ++++++++++++++++++++++++++++++++++ js/components/ControlStrip.tsx | 2 + package-lock.json | 39 +++++++++++++++++++ package.json | 1 + 6 files changed, 127 insertions(+), 56 deletions(-) create mode 100644 js/components/Cassette.tsx diff --git a/.eslintrc.json b/.eslintrc.json index a2a7b1e..c426746 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,9 +1,7 @@ { // Global "root": true, - "plugins": [ - "prettier" - ], + "plugins": ["prettier"], "parser": "@typescript-eslint/parser", "extends": [ "eslint:recommended", @@ -12,38 +10,22 @@ ], "rules": { "prettier/prettier": "error", - "linebreak-style": [ - "error", - "unix" - ], - "eqeqeq": [ - "error", - "smart" - ], - "prefer-const": [ - "error" - ], + "linebreak-style": ["error", "unix"], + "eqeqeq": ["error", "smart"], + "prefer-const": ["error"], "no-var": "error", "no-use-before-define": "off", "no-console": [ "error", { - "allow": [ - "info", - "warn", - "error" - ] + "allow": ["info", "warn", "error"] } ], // Jest configuration "jest/expect-expect": [ "error", { - "assertFunctionNames": [ - "expect*", - "checkImageData", - "testCode" - ] + "assertFunctionNames": ["expect*", "checkImageData", "testCode"] } ] }, @@ -58,13 +40,8 @@ // // TypeScript/TSX-specific configuration { - "files": [ - "*.ts", - "*.tsx" - ], - "plugins": [ - "@typescript-eslint/eslint-plugin" - ], + "files": ["*.ts", "*.tsx"], + "plugins": ["@typescript-eslint/eslint-plugin"], "extends": [ "plugin:react/recommended", "plugin:react-hooks/recommended", @@ -106,9 +83,6 @@ ], // no redeclaration of classes, members or variables "no-redeclare": "off", - "@typescript-eslint/no-redeclare": [ - "error" - ], // allow empty interface definitions and empty extends "@typescript-eslint/no-empty-interface": "off", // allow explicit type declaration @@ -132,9 +106,7 @@ }, // UI elements { - "files": [ - "js/ui/**.ts" - ], + "files": ["js/ui/**.ts"], "rules": { // allow non-null assertions since these classes reference the DOM "@typescript-eslint/no-non-null-assertion": "off" @@ -142,11 +114,7 @@ }, // JS Node configuration { - "files": [ - "bin/*", - "babel.config.js", - "webpack.config.js" - ], + "files": ["bin/*", "babel.config.js", "webpack.config.js"], "rules": { "no-console": 0 }, @@ -158,9 +126,7 @@ }, // Test configuration { - "files": [ - "test/**/*" - ], + "files": ["test/**/*"], "env": { "jest": true, "jasmine": true, @@ -172,28 +138,20 @@ }, // Entry point configuration { - "files": [ - "js/entry2.ts", - "js/entry2e.ts", - "jest.config.js" - ], + "files": ["js/entry2.ts", "js/entry2e.ts", "jest.config.js"], "env": { "commonjs": true } }, // Worker configuration { - "files": [ - "workers/*" - ], + "files": ["workers/*"], "parserOptions": { "project": "workers/tsconfig.json" } } ], - "ignorePatterns": [ - "coverage/**/*" - ], + "ignorePatterns": ["coverage/**/*"], "settings": { "react": { "pragma": "h", diff --git a/js/components/App.tsx b/js/components/App.tsx index 038fc42..faa1a6b 100644 --- a/js/components/App.tsx +++ b/js/components/App.tsx @@ -10,6 +10,7 @@ import { defaultSystem, systemTypes } from './util/systems'; import styles from './css/App.module.scss'; import componentStyles from './css/Components.module.scss'; +import 'bootstrap-icons/font/bootstrap-icons.css'; /** * Top level application component, provides the parameters diff --git a/js/components/Cassette.tsx b/js/components/Cassette.tsx new file mode 100644 index 0000000..3056e0e --- /dev/null +++ b/js/components/Cassette.tsx @@ -0,0 +1,70 @@ +import { h, Fragment } from 'preact'; +import { useMemo, useState } from 'preact/hooks'; +import styles from './css/ControlButton.module.scss'; +import cs from 'classnames'; + +import Apple2IO from 'js/apple2io'; +import Tape, { TAPE_TYPES } from 'js/ui/tape'; +import { debug } from 'js/util'; +import { noAwait } from './util/promises'; + +import { FileChooser } from './FileChooser'; +import { Modal, ModalContent } from './Modal'; + +const CASSETTE_TYPES: FilePickerAcceptType[] = [ + { + description: 'Audio Files', + accept: { + 'application/octet-stream': TAPE_TYPES.map((x) => '.' + x), + }, + }, +]; + +export interface CassetteParams { + io: Apple2IO | undefined; +} + +export const Cassette = ({ io }: CassetteParams) => { + const tape = useMemo(() => (io ? new Tape(io) : null), [io]); + const [active, setActive] = useState(false); + const [isOpen, setIsOpen] = useState(false); + + const onClose = () => setIsOpen(false); + const onChange = (handles: FileSystemFileHandle[]) => { + if (tape && handles.length > 0) { + const load = async () => { + debug('Loading Cassette'); + const file = await handles[0].getFile(); + tape.doLoadLocalTape(file, () => debug('Cassette Loaded')); + }; + noAwait(load)(); + onClose(); + setActive(true); + } + }; + + return ( + <> + + + + + + + + ); +}; diff --git a/js/components/ControlStrip.tsx b/js/components/ControlStrip.tsx index ed5cdf4..fd6242c 100644 --- a/js/components/ControlStrip.tsx +++ b/js/components/ControlStrip.tsx @@ -8,6 +8,7 @@ import { OptionsModal } from './OptionsModal'; import { OptionsContext } from './OptionsContext'; import { Printer } from './Printer'; import { ControlButton } from './ControlButton'; +import { Cassette } from './Cassette'; import { Apple2 as Apple2Impl } from '../apple2'; import { JoyStick } from '../ui/joystick'; import { Screen, SCREEN_FULL_PAGE } from '../ui/screen'; @@ -92,6 +93,7 @@ export const ControlStrip = ({ /> +