mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
parent
a3f3cfadb3
commit
3bbc77049d
@ -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<unknown>();
|
||||
|
||||
useEffect(() => {
|
||||
if (screen.current) {
|
||||
@ -59,13 +59,7 @@ export const Apple2 = (props: Apple2Props) => {
|
||||
setCPU(cpu);
|
||||
apple2.reset();
|
||||
apple2.run();
|
||||
}).catch((e) => {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}).catch((e) => setError(e));
|
||||
}
|
||||
}, [props]);
|
||||
|
||||
|
@ -40,19 +40,11 @@ export interface DiskIIProps extends DiskIIData {
|
||||
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 [error, setError] = useState<unknown>();
|
||||
const [currentHash, setCurrentHash] = useState<string>();
|
||||
|
||||
const hash = useHash();
|
||||
|
||||
const handleError = (e: unknown) => {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const hashParts = getHashParts(hash);
|
||||
const newHash = hashParts[number];
|
||||
@ -61,11 +53,11 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
|
||||
if (hashPart !== currentHash) {
|
||||
if (hashPart.match(/^https?:/)) {
|
||||
loadHttpFile(disk2, number, hashPart)
|
||||
.catch((e) => handleError(e));
|
||||
.catch((e) => setError(e));
|
||||
} else {
|
||||
const filename = `/json/disks/${hashPart}.json`;
|
||||
loadJSON(disk2, number, filename)
|
||||
.catch((e) => handleError(e));
|
||||
.catch((e) => setError(e));
|
||||
}
|
||||
setCurrentHash(hashPart);
|
||||
}
|
||||
|
@ -1,18 +1,28 @@
|
||||
import { h, Fragment } from 'preact';
|
||||
import { h } from 'preact';
|
||||
import { useCallback } from 'preact/hooks';
|
||||
import { Modal, ModalContent, ModalFooter } from './Modal';
|
||||
|
||||
export interface ErrorProps {
|
||||
error: string | undefined;
|
||||
error: unknown | undefined;
|
||||
setError: (error: string | undefined) => void;
|
||||
}
|
||||
|
||||
export const ErrorModal = ({ error, setError } : ErrorProps) => {
|
||||
export const ErrorModal = ({ error, setError }: ErrorProps) => {
|
||||
const onClose = useCallback(() => setError(undefined), [setError]);
|
||||
let errorStr = null;
|
||||
if (error) {
|
||||
if (error instanceof Error) {
|
||||
errorStr = error.message;
|
||||
} else if (typeof error === 'string') {
|
||||
errorStr = error;
|
||||
} else {
|
||||
errorStr = 'Unknown Error';
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (errorStr) {
|
||||
return (
|
||||
<>
|
||||
{ error && (
|
||||
<Modal
|
||||
title="Error"
|
||||
icon="triangle-exclamation"
|
||||
@ -20,15 +30,16 @@ export const ErrorModal = ({ error, setError } : ErrorProps) => {
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalContent>
|
||||
<div style={{ width: 320, fontSize: '1.1em', padding: '5px 11px'}}>
|
||||
{error}
|
||||
<div style={{ width: 320, fontSize: '1.1em', padding: '5px 11px' }}>
|
||||
{errorStr}
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<button onClick={onClose}>OK</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@ -52,7 +52,7 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
|
||||
const [category, setCategory] = useState<string>();
|
||||
const [handles, setHandles] = useState<FileSystemFileHandleLike[]>();
|
||||
const [filename, setFilename] = useState<string>();
|
||||
const [error, setError] = useState<string>();
|
||||
const [error, setError] = useState<unknown>();
|
||||
const hash = useHash();
|
||||
|
||||
const doCancel = useCallback(() => onClose(true), [onClose]);
|
||||
@ -72,11 +72,7 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
|
||||
await loadJSON(disk2, number, filename);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
setError(e);
|
||||
} finally {
|
||||
setHashParts(hashParts);
|
||||
setBusy(false);
|
||||
|
@ -19,10 +19,10 @@ const WOZ_INTEGRITY_CHECK = 0x0a0d0aff;
|
||||
* @returns ASCII string
|
||||
*/
|
||||
function stringFromBytes(data: DataView, start: number, end: number): string {
|
||||
return String.fromCharCode.apply(
|
||||
null,
|
||||
new Uint8Array(data.buffer.slice(data.byteOffset + start, data.byteOffset + end))
|
||||
) as string;
|
||||
const byteArray = new Uint8Array(
|
||||
data.buffer.slice(data.byteOffset + start, data.byteOffset + end)
|
||||
);
|
||||
return String.fromCharCode(...byteArray);
|
||||
}
|
||||
|
||||
export class InfoChunk {
|
||||
|
@ -622,7 +622,7 @@ function loadDisk(drive: DriveNumber, disk: JSONDisk) {
|
||||
*/
|
||||
|
||||
function updateLocalStorage() {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
const diskIndex = JSON.parse(window.localStorage.getItem('diskIndex') || '{}') as LocalDiskIndex;
|
||||
const names = Object.keys(diskIndex);
|
||||
|
||||
const cat: DiskDescriptor[] = disk_categories['Local Saves'] = [];
|
||||
@ -654,12 +654,12 @@ type LocalDiskIndex = {
|
||||
};
|
||||
|
||||
function saveLocalStorage(drive: DriveNumber, name: string) {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
const diskIndex = JSON.parse(window.localStorage.getItem('diskIndex') || '{}') as LocalDiskIndex;
|
||||
|
||||
const json = _disk2.getJSON(drive);
|
||||
diskIndex[name] = json;
|
||||
|
||||
window.localStorage.diskIndex = JSON.stringify(diskIndex);
|
||||
window.localStorage.setItem('diskIndex', JSON.stringify(diskIndex));
|
||||
|
||||
driveLights.label(drive, name);
|
||||
driveLights.dirty(drive, false);
|
||||
@ -667,17 +667,17 @@ function saveLocalStorage(drive: DriveNumber, name: string) {
|
||||
}
|
||||
|
||||
function deleteLocalStorage(name: string) {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
const diskIndex = JSON.parse(window.localStorage.getItem('diskIndex') || '{}') as LocalDiskIndex;
|
||||
if (diskIndex[name]) {
|
||||
delete diskIndex[name];
|
||||
openAlert('Deleted');
|
||||
}
|
||||
window.localStorage.diskIndex = JSON.stringify(diskIndex);
|
||||
window.localStorage.setItem('diskIndex', JSON.stringify(diskIndex));
|
||||
updateLocalStorage();
|
||||
}
|
||||
|
||||
function loadLocalStorage(drive: DriveNumber, name: string) {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
const diskIndex = JSON.parse(window.localStorage.getItem('diskIndex') || '{}') as LocalDiskIndex;
|
||||
if (diskIndex[name]) {
|
||||
_disk2.setJSON(drive, diskIndex[name]);
|
||||
driveLights.label(drive, name);
|
||||
@ -887,11 +887,12 @@ function onLoaded(apple2: Apple2, disk2: DiskII, massStorage: MassStorage, print
|
||||
keyboard.setFunction('F3', () => io.keyDown(0x1b)); // Escape
|
||||
keyboard.setFunction('F4', optionsModal.openModal);
|
||||
keyboard.setFunction('F6', () => {
|
||||
window.localStorage.state = base64_json_stringify(_apple2.getState());
|
||||
window.localStorage.setItem('state', base64_json_stringify(_apple2.getState()));
|
||||
});
|
||||
keyboard.setFunction('F9', () => {
|
||||
if (window.localStorage.state) {
|
||||
_apple2.setState(base64_json_parse(window.localStorage.state as string) as Apple2State);
|
||||
const localState = window.localStorage.getItem('state');
|
||||
if (localState) {
|
||||
_apple2.setState(base64_json_parse(localState) as Apple2State);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -9,7 +9,17 @@ describe('ErrorModal', () => {
|
||||
it('renders when there is an error', () => {
|
||||
const setError = jest.fn();
|
||||
render(
|
||||
<ErrorModal error="My Error" setError={setError} />
|
||||
<ErrorModal error={new Error('My Error')} setError={setError} />
|
||||
);
|
||||
expect(screen.queryByRole('banner')).toBeVisible();
|
||||
expect(screen.queryByRole('banner')).toHaveTextContent('Error');
|
||||
expect(screen.queryByText('My Error')).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders when there is an error string', () => {
|
||||
const setError = jest.fn();
|
||||
render(
|
||||
<ErrorModal error={'My Error'} setError={setError} />
|
||||
);
|
||||
expect(screen.queryByRole('banner')).toBeVisible();
|
||||
expect(screen.queryByRole('banner')).toHaveTextContent('Error');
|
||||
@ -18,11 +28,20 @@ describe('ErrorModal', () => {
|
||||
|
||||
it('does not render when there is not an error', () => {
|
||||
const setError = jest.fn();
|
||||
render(
|
||||
const { container } = render(
|
||||
<ErrorModal error={undefined} setError={setError} />
|
||||
);
|
||||
expect(screen.queryByRole('banner')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('My Error')).not.toBeInTheDocument();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('handles an invalid error', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation();
|
||||
const setError = jest.fn();
|
||||
render(
|
||||
<ErrorModal error={{foo: 'My Error'}} setError={setError} />
|
||||
);
|
||||
expect(screen.queryByText('Unknown Error')).toBeVisible();
|
||||
expect(console.error).toHaveBeenCalledWith({foo: 'My Error'});
|
||||
});
|
||||
|
||||
it('calls setError when close is clicked', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user