From 2daef8040f3c268fd741ab049f7737fe9d2d8942 Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Sat, 2 Oct 2021 11:45:09 -0700 Subject: [PATCH] Keep track of disk sides (#87) Disk side information was being dropped and thus not displayable in the UI. This plumbs the value through various formats and adds some light testing. Also fixes an issue where URL encoded hashes were not properly interpreted. --- js/cards/disk2.ts | 21 +++++++++++++++------ js/formats/create_disk.ts | 8 +++++--- js/formats/d13.ts | 3 ++- js/formats/do.ts | 3 ++- js/formats/nib.ts | 3 ++- js/formats/po.ts | 3 ++- js/formats/types.ts | 25 +++++++++++++++++++------ js/formats/woz.ts | 11 +++++++---- js/ui/apple2.ts | 3 ++- js/ui/drive_lights.ts | 4 ++-- test/js/formats/create_disk.spec.ts | 17 +++++++++++++++++ test/js/formats/testdata/json.ts | 22 ++++++++++++++++++++++ test/js/formats/testdata/woz.ts | 4 ++-- test/js/formats/woz.spec.ts | 1 + 14 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 test/js/formats/create_disk.spec.ts create mode 100644 test/js/formats/testdata/json.ts diff --git a/js/cards/disk2.ts b/js/cards/disk2.ts index b538a6b..9f96b6b 100644 --- a/js/cards/disk2.ts +++ b/js/cards/disk2.ts @@ -165,7 +165,7 @@ const PHASE_DELTA = [ export interface Callbacks { driveLight: (drive: DriveNumber, on: boolean) => void; dirty: (drive: DriveNumber, dirty: boolean) => void; - label: (drive: DriveNumber, name: string) => void; + label: (drive: DriveNumber, name?: string, side?: string) => void; } /** Common information for Nibble and WOZ disks. */ @@ -177,6 +177,8 @@ interface BaseDrive { volume: byte, /** Displayed disk name */ name: string, + /** (Optional) Disk side (Front/Back, A/B) */ + side?: string, /** Quarter track position of read/write head. */ track: byte, /** Position of the head on the track. */ @@ -222,6 +224,7 @@ interface DriveState { encoding: typeof ENCODING_BITSTREAM | typeof ENCODING_NIBBLE volume: byte, name: string, + side?: string, tracks: memory[], track: byte, head: byte, @@ -247,6 +250,7 @@ function getDriveState(drive: Drive): DriveState { encoding: drive.encoding, volume: drive.volume, name: drive.name, + side: drive.side, tracks: [], track: drive.track, head: drive.head, @@ -279,6 +283,7 @@ function setDriveState(state: DriveState) { encoding: ENCODING_NIBBLE, volume: state.volume, name: state.name, + side: state.side, tracks: [], track: state.track, head: state.head, @@ -295,6 +300,7 @@ function setDriveState(state: DriveState) { encoding: ENCODING_BITSTREAM, volume: state.volume, name: state.name, + side: state.side, track: state.track, head: state.head, phase: state.phase, @@ -752,9 +758,10 @@ export default class DiskII implements Card { for (const d of DRIVE_NUMBERS) { const idx = d - 1; this.drives[idx] = setDriveState(state.drives[idx]); - this.callbacks.label(d, state.drives[idx].name); + const { name, side, dirty } = state.drives[idx]; + this.callbacks.label(d, name, side); this.callbacks.driveLight(d, this.on); - this.callbacks.dirty(d, this.drives[idx].dirty); + this.callbacks.dirty(d, dirty); } this.cur = this.drives[this.drive - 1]; } @@ -800,7 +807,7 @@ export default class DiskII implements Card { const cur = this.drives[drive - 1]; Object.assign(cur, disk); this.updateDirty(drive, false); - this.callbacks.label(drive, disk.name); + this.callbacks.label(drive, disk.name, disk.side); return true; } } @@ -858,9 +865,10 @@ export default class DiskII implements Card { const disk = createDisk(fmt, options); if (disk) { const cur = this.drives[drive - 1]; + const { name, side } = cur; Object.assign(cur, disk); this.updateDirty(drive, true); - this.callbacks.label(drive, name); + this.callbacks.label(drive, name, side); return true; } @@ -880,8 +888,9 @@ export default class DiskII implements Card { if (disk) { const cur = this.drives[drive - 1]; Object.assign(cur, disk); + const { name, side } = cur; this.updateDirty(drive, true); - this.callbacks.label(drive, disk.name); + this.callbacks.label(drive, name, side); } } break; diff --git a/js/formats/create_disk.ts b/js/formats/create_disk.ts index a52abb1..f0ff47b 100644 --- a/js/formats/create_disk.ts +++ b/js/formats/create_disk.ts @@ -46,6 +46,7 @@ export function createDiskFromJsonDisk(disk: JSONDisk): FloppyDisk | null { const fmt = disk.type; const readOnly = disk.readOnly; const name = disk.name; + const side = disk.disk; if (includes(NIBBLE_FORMATS, fmt)) { let trackData: memory[][]; @@ -53,11 +54,11 @@ export function createDiskFromJsonDisk(disk: JSONDisk): FloppyDisk | null { trackData = []; for (let t = 0; t < disk.data.length; t++) { trackData[t] = []; - if (fmt == 'nib') { - trackData[t][0] = base64_decode(disk.data[t] as string); + if (disk.type === 'nib') { + trackData[t][0] = base64_decode(disk.data[t]); } else { for (let s = 0; s < disk.data[t].length; s++) { - trackData[t][s] = base64_decode(disk.data[t][s] as string); + trackData[t][s] = base64_decode(disk.data[t][s]); } } } @@ -71,6 +72,7 @@ export function createDiskFromJsonDisk(disk: JSONDisk): FloppyDisk | null { volume, readOnly, name, + side, data: trackData } as DiskOptions; diff --git a/js/formats/d13.ts b/js/formats/d13.ts index fd2a879..ea7c3cb 100644 --- a/js/formats/d13.ts +++ b/js/formats/d13.ts @@ -18,11 +18,12 @@ import { NibbleDisk, DiskOptions, ENCODING_NIBBLE } from './types'; * @returns A nibblized disk */ export default function createDiskFromDOS13(options: DiskOptions) { - const { data, name, rawData, volume, readOnly } = options; + const { data, name, side, rawData, volume, readOnly } = options; const disk: NibbleDisk = { format: 'd13', encoding: ENCODING_NIBBLE, name, + side, volume, readOnly, tracks: [] diff --git a/js/formats/do.ts b/js/formats/do.ts index 5092fee..94543c1 100644 --- a/js/formats/do.ts +++ b/js/formats/do.ts @@ -20,11 +20,12 @@ import { NibbleDisk, DiskOptions, ENCODING_NIBBLE } from './types'; * @returns A nibblized disk */ export default function createDiskFromDOS(options: DiskOptions): NibbleDisk { - const { data, name, rawData, volume, readOnly } = options; + const { data, name, side, rawData, volume, readOnly } = options; const disk: NibbleDisk = { format: 'dsk', encoding: ENCODING_NIBBLE, name, + side, volume, readOnly, tracks: [], diff --git a/js/formats/nib.ts b/js/formats/nib.ts index 83c0366..3970697 100644 --- a/js/formats/nib.ts +++ b/js/formats/nib.ts @@ -18,11 +18,12 @@ import { memory } from '../types'; * @returns A nibblized disk */ export default function createDiskFromNibble(options: DiskOptions): NibbleDisk { - const { data, name, rawData, volume, readOnly } = options; + const { data, name, side, rawData, volume, readOnly } = options; const disk: NibbleDisk = { format: 'nib', encoding: ENCODING_NIBBLE, name, + side, volume: volume || 254, readOnly: readOnly || false, tracks: [] diff --git a/js/formats/po.ts b/js/formats/po.ts index 016cf16..0c5f7f7 100644 --- a/js/formats/po.ts +++ b/js/formats/po.ts @@ -20,11 +20,12 @@ import { NibbleDisk, DiskOptions, ENCODING_NIBBLE } from './types'; * @returns A nibblized disk */ export default function createDiskFromProDOS(options: DiskOptions) { - const { data, name, rawData, volume, readOnly } = options; + const { data, name, side, rawData, volume, readOnly } = options; const disk: NibbleDisk = { format: 'nib', encoding: ENCODING_NIBBLE, name, + side, volume: volume || 254, tracks: [], readOnly: readOnly || false, diff --git a/js/formats/types.ts b/js/formats/types.ts index 2640ae9..efd8408 100644 --- a/js/formats/types.ts +++ b/js/formats/types.ts @@ -21,6 +21,7 @@ export type DriveNumber = MemberOf; export interface DiskOptions { name: string + side?: string volume: byte readOnly: boolean data?: memory[][] @@ -35,6 +36,7 @@ export interface DiskOptions { export interface Disk { name: string + side?: string readOnly: boolean } @@ -97,19 +99,29 @@ export type DiskFormat = MemberOf; export class JSONDiskBase { type: DiskFormat name: string - disk?: number + disk?: string category?: string - writeProtected?: boolean - volume: byte - readOnly: boolean + volume?: byte + readOnly?: boolean gamepad?: GamepadConfiguration } /** - * JSON Disk format with base64 encoded tracks + * JSON Disk format with base64 encoded tracks with sectors */ export interface Base64JSONDisk extends JSONDiskBase { + type: Exclude + encoding: 'base64' + data: string[][] +} + +/** + * JSON Disk format with base64 encoded nibblized tracks + */ + +export interface Base64JSONNibbleDisk extends JSONDiskBase { + type: 'nib' encoding: 'base64' data: string[] } @@ -119,6 +131,7 @@ export interface Base64JSONDisk extends JSONDiskBase { */ export interface BinaryJSONDisk extends JSONDiskBase { + type: DiskFormat encoding: 'binary' data: memory[][] } @@ -127,7 +140,7 @@ export interface BinaryJSONDisk extends JSONDiskBase { * General JSON Disk format */ -export type JSONDisk = Base64JSONDisk | BinaryJSONDisk; +export type JSONDisk = Base64JSONDisk | Base64JSONNibbleDisk | BinaryJSONDisk; /** * Process Disk message payloads for worker diff --git a/js/formats/woz.ts b/js/formats/woz.ts index 12cee92..a713fe9 100644 --- a/js/formats/woz.ts +++ b/js/formats/woz.ts @@ -297,13 +297,16 @@ export default function createDiskFromWoz(options: DiskOptions): WozDisk { debug(chunks); + const { meta, tmap, trks } = chunks; + const disk: WozDisk = { encoding: ENCODING_BITSTREAM, - trackMap: chunks.tmap?.trackMap || [], - tracks: chunks.trks?.tracks || [], - rawTracks: chunks.trks?.rawTracks || [], + trackMap: tmap?.trackMap || [], + tracks: trks?.tracks || [], + rawTracks: trks?.rawTracks || [], readOnly: true, //chunks.info.writeProtected === 1; - name: chunks.meta?.values['title'] || options.name + name: meta?.values['title'] || options.name, + side: meta?.values['side_name'] || meta?.values['side'], }; return disk; diff --git a/js/ui/apple2.ts b/js/ui/apple2.ts index f95fd46..f168f91 100644 --- a/js/ui/apple2.ts +++ b/js/ui/apple2.ts @@ -844,7 +844,8 @@ function gup(name: string) { /** Returns the URL fragment. */ function hup() { const regex = new RegExp('#(.*)'); - const results = regex.exec(window.location.hash); + const hash = decodeURIComponent(window.location.hash); + const results = regex.exec(hash); if (!results) return ''; else diff --git a/js/ui/drive_lights.ts b/js/ui/drive_lights.ts index 01cdfde..f189dcb 100644 --- a/js/ui/drive_lights.ts +++ b/js/ui/drive_lights.ts @@ -14,11 +14,11 @@ export default class DriveLights implements Callbacks { // document.querySelector('#disksave' + drive).disabled = !dirty; } - public label(drive: DriveNumber, label?: string) { + public label(drive: DriveNumber, label?: string, side?: string) { const labelElement = document.querySelector('#disk-label' + drive)! as HTMLElement; if (label) { - labelElement.innerText = label; + labelElement.innerText = label + (side ? ` - ${side}` : ''); } return labelElement.innerText; } diff --git a/test/js/formats/create_disk.spec.ts b/test/js/formats/create_disk.spec.ts new file mode 100644 index 0000000..191973a --- /dev/null +++ b/test/js/formats/create_disk.spec.ts @@ -0,0 +1,17 @@ +import { createDiskFromJsonDisk } from 'js/formats/create_disk'; +import { testDisk } from './testdata/json'; + +describe('createDiskFromJsonDisk', () => { + it('parses a JSON disk', () => { + const disk = createDiskFromJsonDisk(testDisk); + expect(disk).toEqual({ + encoding: 'nibble', + format: 'dsk', + name: 'Test Disk', + readOnly: undefined, + side: 'Front', + volume: 254, + tracks: expect.any(Array) + }); + }); +}); diff --git a/test/js/formats/testdata/json.ts b/test/js/formats/testdata/json.ts new file mode 100644 index 0000000..c089edd --- /dev/null +++ b/test/js/formats/testdata/json.ts @@ -0,0 +1,22 @@ +import { base64_encode } from 'js/base64'; +import { JSONDisk } from 'js/formats/types'; + +export const testDisk: JSONDisk = { + name: 'Test Disk', + disk: 'Front', + category: 'Test', + type: 'dsk', + encoding: 'base64', + data: [] +}; + +const sector = new Uint8Array(256); +sector.fill(0); + +for (let idx = 0; idx < 35; idx++) { + const track: string[] = []; + for (let jdx = 0; jdx < 16; jdx++) { + track.push(base64_encode(sector)); + } + testDisk.data.push(track); +} diff --git a/test/js/formats/testdata/woz.ts b/test/js/formats/testdata/woz.ts index f61d360..b822608 100644 --- a/test/js/formats/testdata/woz.ts +++ b/test/js/formats/testdata/woz.ts @@ -108,8 +108,8 @@ const mockTRKS2 = [ * META structures */ -const mockMETA1 = 'title\tMock Woz 1'; -const mockMETA2 = 'title\tMock Woz 2'; +const mockMETA1 = 'title\tMock Woz 1\t'; +const mockMETA2 = 'title\tMock Woz 2\nside_name\tB'; /** * Woz Version 1 diff --git a/test/js/formats/woz.spec.ts b/test/js/formats/woz.spec.ts index 02b32c1..45df19d 100644 --- a/test/js/formats/woz.spec.ts +++ b/test/js/formats/woz.spec.ts @@ -61,6 +61,7 @@ describe('woz', () => { const disk = createDiskFromWoz(options) as WozDisk; expect(disk).toEqual({ name: 'Mock Woz 2', + side: 'B', readOnly: true, encoding: ENCODING_BITSTREAM, trackMap: mockTMAP,