Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | 1161x 21x 141x 141x 141x 141x 141x 141x 141x 282x 282x 282x 7x 7x 7x 5x 5x 2x 2x 2x 2x 2x 2x 49x 354x 18x 10x 10x 9x 9x 8x 8x 8x 8x 6x 6x 1x 67x 65x 65x 65x 65x 65x 65x 6x 65x 62x 4x 3x 3x 3x 3x | import { toast } from "./toast.js";
import { DiscLayout, toSsdOrDsd } from "../disc.js";
import { toHfe } from "../disc-hfe.js";
import { downloadDriveData } from "./dom-utils.js";
import { DriveTracks } from "../url-params.js";
/** The 40/80 switch's label for a drive stepping `tracksPerStep` physical tracks per logical one. */
export const tracksLabel = (tracksPerStep) => (tracksPerStep === 2 ? "40" : "80");
export const tracksPerStepOf = (label) => (label === "40" ? 2 : 1);
/**
* The disc drives as the page sees them: putting a disc in and taking it out,
* each drive's 40/80 track switch, and downloading what a drive holds.
* Raises "tracks-changed" when a switch moves.
*/
export class Drives extends EventTarget {
constructor({ fdc, driveTracks, confirm, urlState }) {
super();
this.fdc = fdc;
this.driveTracks = driveTracks;
this.confirm = confirm;
this.urlState = urlState;
this.saidWritesAreNotKept = false;
for (const driveIndex of [0, 1]) {
const drive = fdc?.drives[driveIndex];
const fixed = drive ? this.tracksPerStepForDrive(driveIndex) : undefined;
if (fixed !== undefined) drive.tracksPerStep = fixed;
}
}
/** @returns {import("../disc.js").Disc|null} the disc in the drive, saying so when there is nothing to download */
discToDownload(driveIndex) {
const disc = this.fdc?.drives[driveIndex].disc;
if (!disc) toast(`There is no disc in drive ${driveIndex} to download.`, { title: "Disc" });
return disc ?? null;
}
/**
* A drive's disc as a sector image, asking first when a track would not fit one.
*
* @returns {Promise<Uint8Array|null>} the image, or null when there is none to give
*/
async sectorImage(driveIndex) {
const disc = this.discToDownload(driveIndex);
if (!disc) return null;
try {
return toSsdOrDsd(disc);
} catch (e) {
if (await this.confirm(`${e.message} Save anyway, losing what will not fit?`, "Save anyway", "Cancel"))
return toSsdOrDsd(disc, { force: true });
return null;
}
}
/** Downloads a drive's disc as a sector image, asking first if a flux-only track would be lost. */
async downloadSsdOrDsd(driveIndex) {
const image = await this.sectorImage(driveIndex);
Eif (!image) return;
const disc = this.fdc.drives[driveIndex].disc;
downloadDriveData(image, disc.name, disc.isDoubleSided ? ".dsd" : ".ssd");
}
/** Downloads a drive's disc as HFE, the format that keeps every flux transition. */
downloadHfe(driveIndex) {
const disc = this.discToDownload(driveIndex);
Eif (!disc) return;
downloadDriveData(toHfe(disc), disc.name, ".hfe");
}
/** @returns {string} the DiscLayout to load an image for this drive with */
layoutForDrive(driveIndex) {
return this.driveTracks[driveIndex] === DriveTracks.eighty ? DiscLayout.contiguous : DiscLayout.auto;
}
/** @returns {Number|undefined} the tracksPerStep the user fixed this drive at, if they fixed one */
tracksPerStepForDrive(driveIndex) {
if (this.driveTracks[driveIndex] === DriveTracks.auto) return undefined;
return tracksPerStepOf(this.driveTracks[driveIndex]);
}
/**
* Throws a drive's 40/80 switch: the disc in it now is read at that pitch, and so is
* whatever is put in next, until the switch is thrown again.
*/
setTracksPerStep(driveIndex, tracksPerStep) {
const drive = this.fdc?.drives[driveIndex];
if (!drive) return;
const setting = tracksLabel(tracksPerStep);
if (drive.tracksPerStep === tracksPerStep && this.driveTracks[driveIndex] === setting) return;
drive.tracksPerStep = tracksPerStep;
this.driveTracks[driveIndex] = setting;
this.urlState.set({ [`drive${driveIndex}Tracks`]: setting });
this.dispatchEvent(new CustomEvent("tracks-changed", { detail: { driveIndex } }));
}
/**
* A restored state has set the drive's pitch behind the switch's back. A switch the user
* pinned follows it, so the URL and the drive agree; one on auto has nothing to say.
*/
followRestoredPitch(driveIndex) {
const drive = this.fdc?.drives[driveIndex];
if (drive && this.tracksPerStepForDrive(driveIndex) !== undefined)
this.setTracksPerStep(driveIndex, drive.tracksPerStep);
}
putDiscIn(driveIndex, loadedDisc) {
if (!this.fdc) throw new Error("This machine has no disc drives");
const drive = this.fdc.drives[driveIndex];
const fixed = this.tracksPerStepForDrive(driveIndex);
const was = drive.tracksPerStep;
this.fdc.loadDisc(driveIndex, loadedDisc, fixed);
this.noteUnsavedWrites(loadedDisc);
// A switch the user fixed does not move, so anything it does is not news.
if (fixed === undefined && drive.tracksPerStep !== was) this.noteDriveTracks(driveIndex, loadedDisc.name);
}
eject(driveIndex) {
this.fdc.loadDisc(driveIndex, undefined, this.tracksPerStepForDrive(driveIndex));
}
noteUnsavedWrites(loadedDisc) {
if (loadedDisc.savesChanges || this.saidWritesAreNotKept) return;
loadedDisc.notifyOnFirstTrackWrite(() => {
if (this.saidWritesAreNotKept) return;
this.saidWritesAreNotKept = true;
toast(`Changes to ${loadedDisc.name} are not saved. Use the drive's Save button to keep a copy.`, {
title: "Disc",
quietKey: "quietDiscNotSaved",
});
});
}
noteDriveTracks(driveIndex, discName) {
const tracks = tracksLabel(this.fdc.drives[driveIndex].tracksPerStep);
toast(`Drive ${driveIndex} switched to ${tracks} track for ${discName}.`, {
title: "Disc drive",
quietKey: "quietDriveTracks",
});
}
}
|