Add error dialog, fix dynamic hash updates

This commit is contained in:
Will Scullin 2022-05-29 08:49:13 -07:00
parent 3e91101b3c
commit 218133cde0
No known key found for this signature in database
GPG Key ID: 26DCD1042C6638CD
6 changed files with 67 additions and 44 deletions

View File

@ -41,7 +41,7 @@ export const Apple2 = (props: Apple2Props) => {
const [apple2, setApple2] = useState<Apple2Impl>();
const [io, setIO] = useState<Apple2IO>();
const [cpu, setCPU] = useState<CPU6502>();
const [error, setError] =useState<string>();
const [error, setError] = useState<string>();
useEffect(() => {
if (screen.current) {

View File

@ -5,6 +5,7 @@ import Disk2 from '../cards/disk2';
import { FileModal } from './FileModal';
import { loadJSON, loadHttpFile, getHashParts } from './util/files';
import { ErrorModal } from './ErrorModal';
import { useHash } from './hooks/useHash';
/**
* Storage structure for Disk II state returned via callbacks.
@ -40,21 +41,28 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
const label = side ? `${name} - ${side}` : name;
const [modalOpen, setModalOpen] = useState(false);
const [error, setError] = useState<string>();
const [currentHash, setCurrentHash] = useState<string>();
const hash = useHash();
useEffect(() => {
const hashParts = getHashParts();
if (disk2 && hashParts && hashParts[number]) {
const hashPart = decodeURIComponent(hashParts[number]);
if (hashPart.match(/^https?:/)) {
loadHttpFile(disk2, number, hashPart)
.catch((e) => setError(e.message));
} else {
const filename = `/json/disks/${hashPart}.json`;
loadJSON(disk2, number, filename)
.catch((e) => setError(e.message));
const hashParts = getHashParts(hash);
const newHash = hashParts[number];
if (disk2 && newHash) {
const hashPart = decodeURIComponent(newHash);
if (hashPart !== currentHash) {
if (hashPart.match(/^https?:/)) {
loadHttpFile(disk2, number, hashPart)
.catch((e) => setError(e.message));
} else {
const filename = `/json/disks/${hashPart}.json`;
loadJSON(disk2, number, filename)
.catch((e) => setError(e.message));
setCurrentHash(hashPart);
}
}
}
}, [disk2, number]);
}, [currentHash, disk2, hash, number]);
const doClose = useCallback(() => {
setModalOpen(false);

View File

@ -27,7 +27,7 @@ export interface FileChooserProps {
control?: typeof controlDefault;
}
const hasPicker: boolean = !!window.showOpenFilePicker;
const hasPicker = !!window.showOpenFilePicker;
const controlDefault = hasPicker ? 'picker' : 'input';
interface InputFileChooserProps {

View File

@ -9,6 +9,7 @@ import { ErrorModal } from './ErrorModal';
import index from 'json/disks/index.json';
import { FileChooser, FilePickerAcceptType, FileSystemFileHandleLike } from './FileChooser';
import { noAwait } from './util/promises';
import { useHash } from './hooks/useHash';
const DISK_TYPES: FilePickerAcceptType[] = [
{
@ -52,43 +53,38 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
const [handles, setHandles] = useState<FileSystemFileHandleLike[]>();
const [filename, setFilename] = useState<string>();
const [error, setError] = useState<string>();
const hash = useHash();
const doCancel = useCallback(() => onClose(true), [onClose]);
const doOpen = useCallback(async () => {
const hashParts = getHashParts();
const hashParts = getHashParts(hash);
setBusy(true);
if (disk2 && handles && handles.length === 1) {
hashParts[number] = '';
setBusy(true);
try {
try {
if (disk2 && handles?.length === 1) {
hashParts[number] = '';
await loadLocalFile(disk2, number, await handles[0].getFile());
} catch (e: unknown) {
if (e instanceof Error) {
setError(e.message);
} else {
console.error(e);
}
} finally {
setBusy(false);
onClose();
}
}
if (disk2 && filename) {
const name = filename.match(/\/([^/]+).json$/) || ['', ''];
hashParts[number] = name[1];
setBusy(true);
loadJSON(disk2, number, filename)
.catch((e: Error) => setError(e.message))
.finally(() => {
setBusy(false);
onClose();
});
if (disk2 && filename) {
const name = filename.match(/\/([^/]+).json$/) || ['', ''];
hashParts[number] = name[1];
await loadJSON(disk2, number, filename);
}
} catch (e: unknown) {
if (e instanceof Error) {
setError(e.message);
} else {
console.error(e);
}
} finally {
setHashParts(hashParts);
setBusy(false);
onClose();
}
setHashParts(hashParts);
}, [disk2, filename, number, onClose, handles]);
}, [disk2, filename, number, onClose, handles, hash]);
const onChange = useCallback((handles: FileSystemFileHandleLike[]) => {
setEmpty(handles.length === 0);

View File

@ -0,0 +1,19 @@
import { useEffect, useState } from 'preact/hooks';
export const useHash = () => {
const [hash, setHash] = useState(window.location.pathname);
const popstateListener = () => {
const hash = window.location.hash;
setHash(hash);
};
useEffect(() => {
window.addEventListener('popstate', popstateListener);
return () => {
window.removeEventListener('popstate', popstateListener);
};
}, []);
return hash;
};

View File

@ -12,8 +12,8 @@ import DiskII from 'js/cards/disk2';
*
* @returns an padded array for 1 based indexing
*/
export const getHashParts = () => {
const parts = window.location.hash.match(/^#([^|]*)\|?(.*)$/) || ['', '', ''];
export const getHashParts = (hash: string) => {
const parts = hash.match(/^#([^|]*)\|?(.*)$/) || ['', '', ''];
return ['', parts[1], parts[2]];
};
@ -60,7 +60,7 @@ export const loadLocalFile = (
}
}
} else {
reject(`Extension ${ext} not recognized.`);
reject(`Extension "${ext}" not recognized.`);
}
};
fileReader.readAsArrayBuffer(file);
@ -139,7 +139,7 @@ export const loadHttpFile = async (
const ext = fileParts.pop()?.toLowerCase() || '[none]';
const name = decodeURIComponent(fileParts.join('.'));
if (!includes(NIBBLE_FORMATS, ext)) {
throw new Error(`Extension ${ext} not recognized.`);
throw new Error(`Extension "${ext}" not recognized.`);
}
disk2.setBinary(number, name, ext, data);
initGamepad();