More feedback, modal key hook

This commit is contained in:
Will Scullin 2022-05-09 19:32:17 -07:00
parent 9657cc57df
commit 3a6517bb3e
No known key found for this signature in database
GPG Key ID: 26DCD1042C6638CD
4 changed files with 91 additions and 76 deletions

View File

@ -32,27 +32,27 @@ import { Restorable, rom } from './types';
import { processGamepad } from './ui/gamepad';
export interface Apple2Options {
characterRom: string
enhanced: boolean
e: boolean
gl: boolean
rom: string
canvas: HTMLCanvasElement
tick: () => void
characterRom: string;
enhanced: boolean;
e: boolean;
gl: boolean;
rom: string;
canvas: HTMLCanvasElement;
tick: () => void;
}
export interface Stats {
cycles: number
frames: number
renderedFrames: number
cycles: number;
frames: number;
renderedFrames: number;
}
interface State {
cpu: CpuState
vm: VideoModesState
io: Apple2IOState
mmu?: MMUState
ram?: RAMState[]
cpu: CpuState;
vm: VideoModesState;
io: Apple2IOState;
mmu?: MMUState;
ram?: RAMState[];
}
export class Apple2 implements Restorable<State>, DebuggerContainer {

View File

@ -1,5 +1,6 @@
import { h, FunctionalComponent } from 'preact';
import { useCallback } from 'preact/hooks';
import { useHotKey } from './hooks/useHotKey';
/**
* Temporary JS styling while I figure out how I really want
@ -100,6 +101,31 @@ export const ModalFooter: FunctionalComponent = ({ children }) => {
);
};
/**
* ModalCloseButton component properties
*/
interface ModalCloseButtonProp {
onClose: (closeBox?: boolean) => void;
}
/**
* Renders a close button and registers a global Escape key
* hook to trigger it.
*
* @param onClose Close callback
* @returns ModalClose component
*/
export const ModalCloseButton = ({ onClose }: ModalCloseButtonProp) => {
const doClose = useCallback(() => onClose(true), [onClose]);
useHotKey('Escape', doClose);
return (
<button onClick={doClose} title="Close">
{'\u2715'}
</button>
);
};
/**
* ModalHeader component properties
*/
@ -113,19 +139,13 @@ export interface ModalHeaderProps {
*
* @param onClose Close callback
* @param title Modal title
* @returns
* @returns ModalHeader component
*/
export const ModalHeader = ({ onClose, title }: ModalHeaderProps) => {
const doClose = useCallback(() => onClose?.(true), [onClose]);
return (
<div style={modalHeaderStyle}>
<span style={modalTitleStyle}>{title}</span>
{onClose && (
<button onClick={doClose} title="Close">
{'\u2715'}
</button>
)}
{onClose && <ModalCloseButton onClose={onClose} />}
</div>
);
};

View File

@ -1,7 +1,6 @@
import { includes } from 'js/types';
import { initGamepad } from 'js/ui/gamepad';
import {
DISK_FORMATS,
DriveNumber,
JSONDisk,
NIBBLE_FORMATS
@ -46,24 +45,23 @@ export const loadLocalFile = (
fileReader.onload = function () {
const result = this.result as ArrayBuffer;
const parts = file.name.split('.');
const ext = parts.pop()!.toLowerCase();
const ext = parts.pop()?.toLowerCase() || '[none]';
const name = parts.join('.');
if (includes(DISK_FORMATS, ext)) {
if (includes(NIBBLE_FORMATS, ext)) {
if (result.byteLength >= 800 * 1024) {
reject(`Unable to load ${name}`);
} else {
if (
includes(NIBBLE_FORMATS, ext) &&
disk2?.setBinary(number, name, ext, result)
) {
initGamepad();
initGamepad();
if (disk2.setBinary(number, name, ext, result)) {
resolve(true);
} else {
reject(`Unable to load ${name}`);
}
}
} else {
reject(`Extension ${ext} not recognized.`);
}
resolve(true);
};
fileReader.readAsArrayBuffer(file);
});
@ -81,15 +79,14 @@ export const loadLocalFile = (
*/
export const loadJSON = async (disk2: DiskII, number: DriveNumber, url: string) => {
const response = await fetch(url);
if (response.ok) {
const data: JSONDisk = await response.json();
if (includes(NIBBLE_FORMATS, data.type)) {
disk2.setDisk(number, data);
}
initGamepad(data.gamepad);
} else {
throw new Error('Error loading: ' + response.statusText);
if (!response.ok) {
throw new Error(`Error loading: ${response.statusText}`);
}
const data: JSONDisk = await response.json();
if (includes(NIBBLE_FORMATS, data.type)) {
disk2.setDisk(number, data);
}
initGamepad(data.gamepad);
};
/**
@ -109,40 +106,37 @@ export const loadHttpFile = async (
) => {
if (url.endsWith('.json')) {
return loadJSON(disk2, number, url);
} else {
const response = await fetch(url);
if (response.ok) {
const reader = response.body!.getReader();
let received = 0;
const chunks: Uint8Array[] = [];
let result = await reader.read();
while (!result.done) {
chunks.push(result.value);
received += result.value.length;
result = await reader.read();
}
const data = new Uint8Array(received);
let offset = 0;
for (const chunk of chunks) {
data.set(chunk, offset);
offset += chunk.length;
}
const urlParts = url!.split('/');
const file = urlParts.pop()!;
const fileParts = file.split('.');
const ext = fileParts.pop()!.toLowerCase();
const name = decodeURIComponent(fileParts.join('.'));
if (includes(NIBBLE_FORMATS, ext)) {
disk2.setBinary(number, name, ext, data);
initGamepad();
} else {
throw new Error(`Extension ${ext} not recognized.`);
}
} else {
throw new Error('Error loading: ' + response.statusText);
}
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error loading: ${response.statusText}`);
}
const reader = response.body!.getReader();
let received = 0;
const chunks: Uint8Array[] = [];
let result = await reader.read();
while (!result.done) {
chunks.push(result.value);
received += result.value.length;
result = await reader.read();
}
const data = new Uint8Array(received);
let offset = 0;
for (const chunk of chunks) {
data.set(chunk, offset);
offset += chunk.length;
}
const urlParts = url.split('/');
const file = urlParts.pop()!;
const fileParts = file.split('.');
const ext = fileParts.pop()?.toLowerCase() || '[none]';
const name = decodeURIComponent(fileParts.join('.'));
if (!includes(NIBBLE_FORMATS, ext)) {
throw new Error(`Extension ${ext} not recognized.`);
}
disk2.setBinary(number, name, ext, data);
initGamepad();
};

View File

@ -221,6 +221,7 @@ export type KeyFunction = (key: KeyboardEvent) => void;
* @returns ASCII character
*/
export const mapKeyEvent = (evt: KeyboardEvent, caps: boolean) => {
// TODO(whscullin): Find replacement for deprecated keycode
const code = evt.keyCode;
let key: byte = 0xff;
@ -233,7 +234,7 @@ export const mapKeyEvent = (evt: KeyboardEvent, caps: boolean) => {
key -= 0x20;
}
} else {
debug('Unhandled key = ' + toHex(code));
debug(`Unhandled key = ${toHex(code)}`);
}
return key;