apple2js/js/components/FileModal.tsx
Will Scullin 4a188a9a5c
Preact UI (#106)
First pass at a Preact UI, still short some major features but full proof of concept.
2022-05-10 06:52:06 -07:00

124 lines
4.0 KiB
TypeScript

import { h, JSX } from 'preact';
import { useCallback, useRef, useState } from 'preact/hooks';
import { DiskDescriptor, DriveNumber, NibbleFormat } from '../formats/types';
import { Modal, ModalContent, ModalFooter } from './Modal';
import { loadLocalFile, loadJSON, getHashParts, setHashParts } from './util/files';
import DiskII from '../cards/disk2';
import index from 'json/disks/index.json';
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;
disk2: DiskII | undefined;
number: DriveNumber;
onClose: (closeBox?: boolean) => void;
}
export const FileModal = ({ disk2, number, onClose, isOpen } : FileModalProps) => {
const inputRef = useRef<HTMLInputElement>(null);
const [busy, setBusy] = useState<boolean>(false);
const [empty, setEmpty] = useState<boolean>(true);
const [category, setCategory] = useState<string>();
const [filename, setFilename] = useState<string>();
const doCancel = useCallback(() => onClose(true), []);
const doOpen = useCallback(() => {
const hashParts = getHashParts();
if (disk2 && inputRef.current && inputRef.current.files?.length === 1) {
hashParts[number] = '';
setBusy(true);
loadLocalFile(disk2, number, inputRef.current.files[0])
.catch(console.error)
.finally(() => {
setBusy(false);
onClose();
});
}
if (disk2 && filename) {
const name = filename.match(/\/([^/]+).json$/) || ['', ''];
hashParts[number] = name[1];
setBusy(true);
loadJSON(disk2, number, filename)
.catch(console.error)
.finally(() => {
setBusy(false);
onClose();
});
}
setHashParts(hashParts);
}, [ disk2, filename, number, onClose ]);
const onChange = useCallback(() => {
if (inputRef) {
setEmpty(!inputRef.current?.files?.length);
}
}, [ inputRef ]);
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 (
<Modal title="Open File" isOpen={isOpen}>
<ModalContent>
<div id="load-modal">
<select multiple onChange={doSelectCategory}>
{categoryNames.map((name) => (
<option>{name}</option>
))}
</select>
<select multiple onChange={doSelectFilename}>
{disks.map((disk) => (
<option value={disk.filename}>
{disk.name}
{disk.disk ? ` - ${disk.disk}` : ''}
</option>
))}
</select>
</div>
<input type="file" ref={inputRef} onChange={onChange} />
</ModalContent>
<ModalFooter>
<button onClick={doCancel}>Cancel</button>
<button onClick={doOpen} disabled={busy || empty}>Open</button>
</ModalFooter>
</Modal>
);
};