mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Add error dialog, fix dynamic hash updates
This commit is contained in:
parent
3e91101b3c
commit
218133cde0
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
19
js/components/hooks/useHash.ts
Normal file
19
js/components/hooks/useHash.ts
Normal 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;
|
||||
};
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue
Block a user