Implement `FileSystemFileHandle` for file input (#124)

This removes the `FileSystemFileHandleLike` interface in preference to
just implementing the correct interface. The advantage of the
`FileSystemFileHandle` interface is that it can be passed to the
worker directly to load the file.
This commit is contained in:
Ian Flanigan 2022-06-03 19:12:30 +02:00 committed by GitHub
parent 3bbc77049d
commit 203d89b8d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 12 additions and 27 deletions

View File

@ -12,17 +12,9 @@ const ACCEPT_EVERYTHING_TYPE: FilePickerAcceptType = {
accept: { '*/*': [] }, accept: { '*/*': [] },
}; };
export interface FileSystemFileHandleLike {
readonly name: string;
readonly kind: string;
readonly isWritable: boolean;
getFile(): Promise<File>;
createWritable: FileSystemFileHandle['createWritable'];
}
export interface FileChooserProps { export interface FileChooserProps {
disabled?: boolean; disabled?: boolean;
onChange: (handles: Array<FileSystemFileHandleLike>) => void; onChange: (handles: Array<FileSystemFileHandle>) => void;
accept?: FilePickerAcceptType[]; accept?: FilePickerAcceptType[];
control?: typeof controlDefault; control?: typeof controlDefault;
} }
@ -170,7 +162,7 @@ export const FileChooser = ({
}: FileChooserProps) => { }: FileChooserProps) => {
const onChangeForInput = useCallback((files: FileList) => { const onChangeForInput = useCallback((files: FileList) => {
const handles: FileSystemFileHandleLike[] = []; const handles: FileSystemFileHandle[] = [];
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files.item(i); const file = files.item(i);
if (file === null) { if (file === null) {
@ -180,25 +172,19 @@ export const FileChooser = ({
kind: 'file', kind: 'file',
name: file.name, name: file.name,
getFile: () => Promise.resolve(file), getFile: () => Promise.resolve(file),
isWritable: false,
createWritable: (_options) => Promise.reject('File not writable.'), createWritable: (_options) => Promise.reject('File not writable.'),
queryPermission: (descriptor) => Promise.resolve(descriptor === 'read' ? 'granted' : 'denied'),
requestPermission: (descriptor) => Promise.resolve(descriptor === 'read' ? 'granted' : 'denied'),
isSameEntry: (_unused) => Promise.resolve(false),
isDirectory: false,
isFile: true,
}); });
} }
onChange(handles); onChange(handles);
}, [onChange]); }, [onChange]);
const onChangeForPicker = useCallback((fileHandles: FileSystemFileHandle[]) => { const onChangeForPicker = useCallback((fileHandles: FileSystemFileHandle[]) => {
const handles: FileSystemFileHandleLike[] = []; onChange(fileHandles);
for (const fileHandle of fileHandles) {
handles.push({
kind: fileHandle.kind,
name: fileHandle.name,
getFile: () => fileHandle.getFile(),
isWritable: true,
createWritable: (options) => fileHandle.createWritable(options),
});
}
onChange(handles);
}, [onChange]); }, [onChange]);
return control === 'picker' return control === 'picker'

View File

@ -7,9 +7,9 @@ import DiskII from '../cards/disk2';
import { ErrorModal } from './ErrorModal'; import { ErrorModal } from './ErrorModal';
import index from 'json/disks/index.json'; import index from 'json/disks/index.json';
import { FileChooser, FilePickerAcceptType, FileSystemFileHandleLike } from './FileChooser';
import { noAwait } from './util/promises'; import { noAwait } from './util/promises';
import { useHash } from './hooks/useHash'; import { useHash } from './hooks/useHash';
import { FileChooser, FilePickerAcceptType } from './FileChooser';
const DISK_TYPES: FilePickerAcceptType[] = [ const DISK_TYPES: FilePickerAcceptType[] = [
{ {
@ -50,7 +50,7 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
const [busy, setBusy] = useState<boolean>(false); const [busy, setBusy] = useState<boolean>(false);
const [empty, setEmpty] = useState<boolean>(true); const [empty, setEmpty] = useState<boolean>(true);
const [category, setCategory] = useState<string>(); const [category, setCategory] = useState<string>();
const [handles, setHandles] = useState<FileSystemFileHandleLike[]>(); const [handles, setHandles] = useState<FileSystemFileHandle[]>();
const [filename, setFilename] = useState<string>(); const [filename, setFilename] = useState<string>();
const [error, setError] = useState<unknown>(); const [error, setError] = useState<unknown>();
const hash = useHash(); const hash = useHash();
@ -82,7 +82,7 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
setHashParts(hashParts); setHashParts(hashParts);
}, [disk2, filename, number, onClose, handles, hash]); }, [disk2, filename, number, onClose, handles, hash]);
const onChange = useCallback((handles: FileSystemFileHandleLike[]) => { const onChange = useCallback((handles: FileSystemFileHandle[]) => {
setEmpty(handles.length === 0); setEmpty(handles.length === 0);
setHandles(handles); setHandles(handles);
}, []); }, []);

View File

@ -69,9 +69,9 @@ describe('FileChooser', () => {
const handle = handleList[0]; const handle = handleList[0];
expect(handle.kind).toBe('file'); expect(handle.kind).toBe('file');
expect(handle.name).toBe(FAKE_FILE.name); expect(handle.name).toBe(FAKE_FILE.name);
expect(handle.isWritable).toBe(false);
await expect(handle.getFile()).resolves.toBe(FAKE_FILE); await expect(handle.getFile()).resolves.toBe(FAKE_FILE);
await expect(handle.createWritable()).rejects.toEqual('File not writable.'); await expect(handle.createWritable()).rejects.toEqual('File not writable.');
await expect(handle.queryPermission({ mode: 'readwrite' })).resolves.toBe('denied');
}); });
}); });
}); });
@ -125,7 +125,6 @@ describe('FileChooser', () => {
const handle = handleList[0]; const handle = handleList[0];
expect(handle.kind).toBe(FAKE_FILE_HANDLE.kind); expect(handle.kind).toBe(FAKE_FILE_HANDLE.kind);
expect(handle.name).toBe(FAKE_FILE_HANDLE.name); expect(handle.name).toBe(FAKE_FILE_HANDLE.name);
expect(handle.isWritable).toBe(true);
}); });
}); });
}); });