apple2js/js/components/DiskDragTarget.tsx

98 lines
3.3 KiB
TypeScript
Raw Normal View History

Split disk data out into its own record (#158) * Harmonize drive and disk type hierarchies Before, the `XXXDrive` and `XXXDisk` type hierarchies were similar, but not exactly the same. For example, `encoding` and `format` were missing on some `XXXDisk` types where they existed on the `XXXDrive` type. This change attempts to bring the hierarchies closer together. However, the biggest visible consequence is the introduction of the `FLOPPY_FORMATS` array and its associated `FloppyFormat` type. This replaces `NIBBLE_FORMATS` in most places. A couple of new type guards for disk formats and disks have been added as well. All tests pass, everything compiles with no errors, and both WOZ and nibble format disks load in the emulator. * Move disk data to a `disk` field in the drive Before, disk data was mixed in with state about the drive itself (like track, motor phase, etc.). This made it hard to know exactly what data was necessary for different image formats. Now, the disk data is in a `disk` field whose type depends on the drive type. This makes responisbility a bit easier. One oddity, though, is that the `Drive` has metadata _and_ the `Disk` has metadata. When a disk is in the drive, these should be `===`, but when there is no disk in the drive, obviously only the drive metadata is set. All tests pass, everything compiles, and both WOZ and nibble disks work in the emulator (both preact and classic). * Squash the `Drive` type hierarchy Before, the type of the drive depended on the type of the disk in the drive. Thus, `NibbleDrive` contained a `NibbleDisk` and a `WozDrive` contained a `WozDisk`. With the extraction of the disk data to a single field, this type hierarchy makes no sense. Instead, it suffices to check the type of the disk. This change removes the `NibbleDrive` and `WozDrive` types and type guards, checking the disk type where necessary. This change also introduces the `NoFloppyDisk` type to represent the lack of a disk. This allows the drive to have metadata, for one. All tests pass, everything compiles, and both WOZ and nibble disks work locally. * Use more destructuring assignment Now, more places use constructs like: ```TypeScript const { metadata, readOnly, track, head, phase, dirty } = drive; return { disk: getDiskState(drive.disk), metadata: {...metadata}, readOnly, track, head, phase, dirty, }; ``` * Remove the `Disk` object from the `Drive` object This change splits out the disk objects into a record parallel to the drive objects. The idea is that the `Drive` structure becomes a representation of the state of the drive that is separate from the disk image actually in the drive. This helps in an upcoming refactoring. This also changes the default empty disks to be writable. While odd, the write protect switch should be in the "off" position since there is no disk pressing on it. Finally, `insertDisk` now resets the head position to 0 since there is no way of preserving the head position across disks. (Even in the real world, the motor-off delay plus spindle spin-down would make it impossible to know the disk head position with any accuracy.)
2022-09-17 13:41:35 +00:00
import { BLOCK_FORMATS, DISK_FORMATS, DriveNumber, FLOPPY_FORMATS, MassStorage } from 'js/formats/types';
import { h, JSX, RefObject } from 'preact';
import { useEffect, useRef } from 'preact/hooks';
import { loadLocalFile } from './util/files';
import { spawn } from './util/promises';
export interface DiskDragTargetProps<T> extends JSX.HTMLAttributes<HTMLDivElement> {
storage: MassStorage<T> | undefined;
drive?: DriveNumber;
Split disk data out into its own record (#158) * Harmonize drive and disk type hierarchies Before, the `XXXDrive` and `XXXDisk` type hierarchies were similar, but not exactly the same. For example, `encoding` and `format` were missing on some `XXXDisk` types where they existed on the `XXXDrive` type. This change attempts to bring the hierarchies closer together. However, the biggest visible consequence is the introduction of the `FLOPPY_FORMATS` array and its associated `FloppyFormat` type. This replaces `NIBBLE_FORMATS` in most places. A couple of new type guards for disk formats and disks have been added as well. All tests pass, everything compiles with no errors, and both WOZ and nibble format disks load in the emulator. * Move disk data to a `disk` field in the drive Before, disk data was mixed in with state about the drive itself (like track, motor phase, etc.). This made it hard to know exactly what data was necessary for different image formats. Now, the disk data is in a `disk` field whose type depends on the drive type. This makes responisbility a bit easier. One oddity, though, is that the `Drive` has metadata _and_ the `Disk` has metadata. When a disk is in the drive, these should be `===`, but when there is no disk in the drive, obviously only the drive metadata is set. All tests pass, everything compiles, and both WOZ and nibble disks work in the emulator (both preact and classic). * Squash the `Drive` type hierarchy Before, the type of the drive depended on the type of the disk in the drive. Thus, `NibbleDrive` contained a `NibbleDisk` and a `WozDrive` contained a `WozDisk`. With the extraction of the disk data to a single field, this type hierarchy makes no sense. Instead, it suffices to check the type of the disk. This change removes the `NibbleDrive` and `WozDrive` types and type guards, checking the disk type where necessary. This change also introduces the `NoFloppyDisk` type to represent the lack of a disk. This allows the drive to have metadata, for one. All tests pass, everything compiles, and both WOZ and nibble disks work locally. * Use more destructuring assignment Now, more places use constructs like: ```TypeScript const { metadata, readOnly, track, head, phase, dirty } = drive; return { disk: getDiskState(drive.disk), metadata: {...metadata}, readOnly, track, head, phase, dirty, }; ``` * Remove the `Disk` object from the `Drive` object This change splits out the disk objects into a record parallel to the drive objects. The idea is that the `Drive` structure becomes a representation of the state of the drive that is separate from the disk image actually in the drive. This helps in an upcoming refactoring. This also changes the default empty disks to be writable. While odd, the write protect switch should be in the "off" position since there is no disk pressing on it. Finally, `insertDisk` now resets the head position to 0 since there is no way of preserving the head position across disks. (Even in the real world, the motor-off delay plus spindle spin-down would make it impossible to know the disk head position with any accuracy.)
2022-09-17 13:41:35 +00:00
formats: typeof FLOPPY_FORMATS
| typeof BLOCK_FORMATS
| typeof DISK_FORMATS;
dropRef?: RefObject<HTMLElement>;
onError: (error: unknown) => void;
}
export const DiskDragTarget = ({
storage,
drive,
dropRef,
formats,
onError,
children,
...props
}: DiskDragTargetProps<unknown>) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const div = dropRef?.current || ref.current;
if (div) {
const onDragOver = (event: DragEvent) => {
event.preventDefault();
const dt = event.dataTransfer;
if (dt) {
if (Array.from(dt.items).every((item) => item.kind === 'file')) {
dt.dropEffect = 'copy';
} else {
dt.dropEffect = 'none';
}
}
};
const onDragEnd = (event: DragEvent) => {
const dt = event.dataTransfer;
if (dt?.items) {
for (let i = 0; i < dt.items.length; i++) {
dt.items.remove(i);
}
} else {
dt?.clearData();
}
};
const onDrop = (event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
const targetDrive = drive ?? 1; //TODO(whscullin) Maybe pick available drive
const dt = event.dataTransfer;
if (dt?.files.length === 1 && storage) {
spawn(async () => {
try {
await loadLocalFile(storage, formats, targetDrive, dt.files[0]);
} catch (e) {
onError(e);
}
});
} else if (dt?.files.length === 2 && storage) {
spawn(async () => {
try {
await loadLocalFile(storage, formats, 1, dt.files[0]);
await loadLocalFile(storage, formats, 2, dt.files[1]);
} catch (e) {
onError(e);
}
});
}
};
div.addEventListener('dragover', onDragOver);
div.addEventListener('dragend', onDragEnd);
div.addEventListener('drop', onDrop);
return () => {
div.removeEventListener('dragover', onDragOver);
div.removeEventListener('dragend', onDragEnd);
div.removeEventListener('drop', onDrop);
};
}
}, [drive, dropRef, formats, onError, storage]);
return (
<div ref={ref} {...props}>
{children}
</div>
);
};