2022-06-01 00:41:24 +00:00
|
|
|
import { h, Fragment, JSX } from 'preact';
|
2022-05-28 17:52:48 +00:00
|
|
|
import { useCallback, useState } from 'preact/hooks';
|
|
|
|
import { DiskDescriptor, DriveNumber, NibbleFormat, NIBBLE_FORMATS } from '../formats/types';
|
2022-05-10 13:52:06 +00:00
|
|
|
import { Modal, ModalContent, ModalFooter } from './Modal';
|
2022-06-05 17:57:04 +00:00
|
|
|
import { loadLocalNibbleFile, loadJSON, getHashParts, setHashParts } from './util/files';
|
2022-05-10 13:52:06 +00:00
|
|
|
import DiskII from '../cards/disk2';
|
2022-06-01 00:41:24 +00:00
|
|
|
import { ErrorModal } from './ErrorModal';
|
2022-05-10 13:52:06 +00:00
|
|
|
|
|
|
|
import index from 'json/disks/index.json';
|
2022-05-31 15:38:40 +00:00
|
|
|
import { noAwait } from './util/promises';
|
2022-06-01 00:41:24 +00:00
|
|
|
import { useHash } from './hooks/useHash';
|
2022-06-03 17:12:30 +00:00
|
|
|
import { FileChooser, FilePickerAcceptType } from './FileChooser';
|
2022-05-28 17:52:48 +00:00
|
|
|
|
2022-06-03 22:30:39 +00:00
|
|
|
import styles from './css/FileModal.module.css';
|
|
|
|
|
2022-05-28 17:52:48 +00:00
|
|
|
const DISK_TYPES: FilePickerAcceptType[] = [
|
|
|
|
{
|
|
|
|
description: 'Disk Images',
|
|
|
|
accept: { 'application/octet-stream': NIBBLE_FORMATS.map(x => '.' + x) },
|
|
|
|
}
|
|
|
|
];
|
2022-05-10 13:52:06 +00:00
|
|
|
|
|
|
|
const categories = index.reduce<Record<string, DiskDescriptor[]>>(
|
|
|
|
(
|
|
|
|
acc: Record<string, DiskDescriptor[]>,
|
|
|
|
disk: DiskDescriptor
|
|
|
|
) => {
|
|
|
|
const category = disk.category || 'Misc';
|
|
|
|
acc[category] = [disk, ...(acc[category] || [])];
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
const categoryNames = Object.keys(categories).sort();
|
|
|
|
|
|
|
|
export type NibbleFileCallback = (
|
|
|
|
name: string,
|
|
|
|
fmt: NibbleFormat,
|
|
|
|
rawData: ArrayBuffer
|
|
|
|
) => boolean;
|
|
|
|
|
|
|
|
interface FileModalProps {
|
|
|
|
isOpen: boolean;
|
2022-06-12 16:42:01 +00:00
|
|
|
disk2: DiskII;
|
2022-05-10 13:52:06 +00:00
|
|
|
number: DriveNumber;
|
|
|
|
onClose: (closeBox?: boolean) => void;
|
|
|
|
}
|
|
|
|
|
2022-05-28 17:52:48 +00:00
|
|
|
export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) => {
|
2022-05-10 13:52:06 +00:00
|
|
|
const [busy, setBusy] = useState<boolean>(false);
|
|
|
|
const [empty, setEmpty] = useState<boolean>(true);
|
|
|
|
const [category, setCategory] = useState<string>();
|
2022-06-03 17:12:30 +00:00
|
|
|
const [handles, setHandles] = useState<FileSystemFileHandle[]>();
|
2022-05-10 13:52:06 +00:00
|
|
|
const [filename, setFilename] = useState<string>();
|
2022-06-01 13:28:05 +00:00
|
|
|
const [error, setError] = useState<unknown>();
|
2022-06-01 00:41:24 +00:00
|
|
|
const hash = useHash();
|
2022-05-10 13:52:06 +00:00
|
|
|
|
2022-05-29 20:48:51 +00:00
|
|
|
const doCancel = useCallback(() => onClose(true), [onClose]);
|
2022-05-10 13:52:06 +00:00
|
|
|
|
2022-05-28 17:52:48 +00:00
|
|
|
const doOpen = useCallback(async () => {
|
2022-06-01 00:41:24 +00:00
|
|
|
const hashParts = getHashParts(hash);
|
|
|
|
setBusy(true);
|
2022-05-10 13:52:06 +00:00
|
|
|
|
2022-06-01 00:41:24 +00:00
|
|
|
try {
|
2022-06-12 16:42:01 +00:00
|
|
|
if (handles?.length === 1) {
|
2022-06-01 00:41:24 +00:00
|
|
|
hashParts[number] = '';
|
2022-06-05 17:57:04 +00:00
|
|
|
await loadLocalNibbleFile(disk2, number, await handles[0].getFile());
|
2022-06-01 00:41:24 +00:00
|
|
|
}
|
2022-06-12 16:42:01 +00:00
|
|
|
if (filename) {
|
2022-06-01 00:41:24 +00:00
|
|
|
const name = filename.match(/\/([^/]+).json$/) || ['', ''];
|
|
|
|
hashParts[number] = name[1];
|
|
|
|
await loadJSON(disk2, number, filename);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2022-06-01 13:28:05 +00:00
|
|
|
setError(e);
|
2022-06-01 00:41:24 +00:00
|
|
|
} finally {
|
|
|
|
setHashParts(hashParts);
|
|
|
|
setBusy(false);
|
|
|
|
onClose();
|
2022-05-10 13:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
setHashParts(hashParts);
|
2022-06-01 00:41:24 +00:00
|
|
|
}, [disk2, filename, number, onClose, handles, hash]);
|
2022-05-10 13:52:06 +00:00
|
|
|
|
2022-06-03 17:12:30 +00:00
|
|
|
const onChange = useCallback((handles: FileSystemFileHandle[]) => {
|
2022-05-28 17:52:48 +00:00
|
|
|
setEmpty(handles.length === 0);
|
|
|
|
setHandles(handles);
|
|
|
|
}, []);
|
2022-05-10 13:52:06 +00:00
|
|
|
|
|
|
|
const doSelectCategory = useCallback(
|
|
|
|
(event: JSX.TargetedMouseEvent<HTMLSelectElement>) =>
|
|
|
|
setCategory(event.currentTarget.value)
|
|
|
|
, []
|
|
|
|
);
|
|
|
|
|
|
|
|
const doSelectFilename = useCallback(
|
|
|
|
(event: JSX.TargetedMouseEvent<HTMLSelectElement>) => {
|
|
|
|
setEmpty(!event.currentTarget.value);
|
|
|
|
setFilename(event.currentTarget.value);
|
|
|
|
}, []
|
|
|
|
);
|
|
|
|
|
|
|
|
const disks = category ? categories[category] : [];
|
|
|
|
|
|
|
|
return (
|
2022-06-01 00:41:24 +00:00
|
|
|
<>
|
2022-06-04 21:31:57 +00:00
|
|
|
<Modal title="Open File" isOpen={isOpen} onClose={onClose}>
|
2022-06-01 00:41:24 +00:00
|
|
|
<ModalContent>
|
2022-06-03 22:30:39 +00:00
|
|
|
<div className={styles.loadModal}>
|
2022-06-01 00:41:24 +00:00
|
|
|
<select multiple onChange={doSelectCategory}>
|
|
|
|
{categoryNames.map((name) => (
|
|
|
|
<option key={name}>{name}</option>
|
|
|
|
))}
|
|
|
|
</select>
|
|
|
|
<select multiple onChange={doSelectFilename}>
|
|
|
|
{disks.map((disk) => (
|
|
|
|
<option key={disk.filename} value={disk.filename}>
|
|
|
|
{disk.name}
|
|
|
|
{disk.disk ? ` - ${disk.disk}` : ''}
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
<FileChooser onChange={onChange} accept={DISK_TYPES} />
|
|
|
|
</ModalContent>
|
|
|
|
<ModalFooter>
|
|
|
|
<button onClick={doCancel}>Cancel</button>
|
|
|
|
<button onClick={noAwait(doOpen)} disabled={busy || empty}>Open</button>
|
|
|
|
</ModalFooter>
|
|
|
|
</Modal>
|
|
|
|
<ErrorModal error={error} setError={setError} />
|
|
|
|
</>
|
2022-05-10 13:52:06 +00:00
|
|
|
);
|
|
|
|
};
|