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,