mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Add the recommended eslint plugins for TypeScript (#121)
This adds both the recommended TypeScript checks, plus the recommended TypeScript checks that require type checking. This latter addition means that eslint essentially has to compile all of the TypeScript in the project, causing it to be slower. This isn't much of a problem in VS Code because there's a lot of caching being done, but it's clearly slower when run on the commandline. All of the errors are either fixed or suppressed. Some errors are suppressed because fixing them would be too laborious for the little value gained. The eslint config is also slightly refactored to separate the strictly TypeScript checks from the JavaScript checks.
This commit is contained in:
parent
d4db26574d
commit
04ae0327c2
179
.eslintrc.json
179
.eslintrc.json
@ -1,7 +1,10 @@
|
||||
{
|
||||
// Global
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint/eslint-plugin"
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:jest/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
@ -19,47 +22,15 @@
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"smart"
|
||||
],
|
||||
"prefer-const": [
|
||||
"error"
|
||||
],
|
||||
"semi": "off",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"no-var": "error",
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-use-before-define": [
|
||||
"error",
|
||||
{
|
||||
"functions": false,
|
||||
"classes": false
|
||||
}
|
||||
],
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"no-redeclare": "off",
|
||||
"@typescript-eslint/no-redeclare": ["error"],
|
||||
"no-dupe-class-members": "off",
|
||||
"no-console": [
|
||||
"error",
|
||||
@ -71,39 +42,118 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/require-await": ["error"],
|
||||
"jest/expect-expect": ["error", {
|
||||
"assertFunctionNames": [
|
||||
"expect*",
|
||||
"checkImageData",
|
||||
"testCode"
|
||||
]
|
||||
}]
|
||||
// Jest configuration
|
||||
"jest/expect-expect": [
|
||||
"error",
|
||||
{
|
||||
"assertFunctionNames": [
|
||||
"expect*",
|
||||
"checkImageData",
|
||||
"testCode"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
"builtin": true,
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"overrides": [
|
||||
// All overrides matching a file are applied in-order, with the last
|
||||
// taking precedence.
|
||||
//
|
||||
// TypeScript/TSX-specific configuration
|
||||
{
|
||||
"files": [
|
||||
"**/*.ts"
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint/eslint-plugin"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
],
|
||||
"rules": {
|
||||
"no-var": "error"
|
||||
// recommended is just "warn"
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
// enforce semicolons at ends of statements
|
||||
"semi": "off",
|
||||
"@typescript-eslint/semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
// enforce semicolons to separate members
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
// definitions must come before uses for variables
|
||||
"@typescript-eslint/no-use-before-define": [
|
||||
"error",
|
||||
{
|
||||
"functions": false,
|
||||
"classes": false
|
||||
}
|
||||
],
|
||||
// no used variables
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
// no redeclaration of classes, members or variables
|
||||
"no-redeclare": "off",
|
||||
"@typescript-eslint/no-redeclare": [
|
||||
"error"
|
||||
],
|
||||
// allow empty interface definitions and empty extends
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
// allow explicit type declaration
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
// allow some non-string types in templates
|
||||
"@typescript-eslint/restrict-template-expressions": [
|
||||
"error",
|
||||
{
|
||||
"allowNumber": true,
|
||||
"allowBoolean": true
|
||||
}
|
||||
],
|
||||
// react rules
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "error"
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
},
|
||||
// UI elements
|
||||
{
|
||||
"files": [
|
||||
"js/ui/**.ts"
|
||||
],
|
||||
"rules": {
|
||||
// allow non-null assertions since these classes reference the DOM
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
},
|
||||
// JS Node configuration
|
||||
{
|
||||
"files": [
|
||||
"bin/*",
|
||||
@ -119,6 +169,7 @@
|
||||
"browser": false
|
||||
}
|
||||
},
|
||||
// Test configuration
|
||||
{
|
||||
"files": [
|
||||
"test/**/*"
|
||||
@ -132,6 +183,7 @@
|
||||
"no-console": 0
|
||||
}
|
||||
},
|
||||
// Entry point configuration
|
||||
{
|
||||
"files": [
|
||||
"js/entry2.ts",
|
||||
@ -142,6 +194,7 @@
|
||||
"commonjs": true
|
||||
}
|
||||
},
|
||||
// Worker configuration
|
||||
{
|
||||
"files": [
|
||||
"workers/*"
|
||||
@ -151,11 +204,13 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignorePatterns": ["coverage/**/*"],
|
||||
"ignorePatterns": [
|
||||
"coverage/**/*"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "h",
|
||||
"version": "16"
|
||||
"pragma": "h",
|
||||
"version": "16"
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ import RAM, { RAMState } from './ram';
|
||||
import SYMBOLS from './symbols';
|
||||
import Debugger, { DebuggerContainer } from './debugger';
|
||||
|
||||
import { Restorable, rom } from './types';
|
||||
import { ReadonlyUint8Array, Restorable, rom } from './types';
|
||||
import { processGamepad } from './ui/gamepad';
|
||||
|
||||
export interface Apple2Options {
|
||||
@ -47,7 +47,7 @@ export interface Stats {
|
||||
renderedFrames: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
export interface State {
|
||||
cpu: CpuState;
|
||||
vm: VideoModesState;
|
||||
io: Apple2IOState;
|
||||
@ -91,8 +91,8 @@ export class Apple2 implements Restorable<State>, DebuggerContainer {
|
||||
}
|
||||
|
||||
async init(options: Apple2Options) {
|
||||
const romImportPromise = import(`./roms/system/${options.rom}`);
|
||||
const characterRomImportPromise = import(`./roms/character/${options.characterRom}`);
|
||||
const romImportPromise = import(`./roms/system/${options.rom}`) as Promise<{ default: new () => ROM }>;
|
||||
const characterRomImportPromise = import(`./roms/character/${options.characterRom}`) as Promise<{ default: ReadonlyUint8Array }>;
|
||||
|
||||
const LoresPage = options.gl ? LoresPageGL : LoresPage2D;
|
||||
const HiresPage = options.gl ? HiresPageGL : HiresPage2D;
|
||||
|
@ -52,7 +52,7 @@ const LOC = {
|
||||
};
|
||||
|
||||
export default class Apple2IO implements MemoryPages, Restorable<Apple2IOState> {
|
||||
private _slot: Array<Card | null> = new Array(7).fill(null);
|
||||
private _slot: Array<Card | null> = new Array<Card | null>(7).fill(null);
|
||||
private _auxRom: Memory | null = null;
|
||||
|
||||
private _khz = 1023;
|
||||
@ -81,7 +81,7 @@ export default class Apple2IO implements MemoryPages, Restorable<Apple2IOState>
|
||||
private _annunciators: Annunciators = [false, false, false, false];
|
||||
|
||||
private _tape: TapeData = [];
|
||||
private _tapeOffset = 0;
|
||||
private _tapeOffset: number = 0;
|
||||
private _tapeNext: number = 0;
|
||||
private _tapeCurrent = false;
|
||||
|
||||
@ -106,7 +106,7 @@ export default class Apple2IO implements MemoryPages, Restorable<Apple2IOState>
|
||||
if (this._audioListener) {
|
||||
this._audioListener(this._sample);
|
||||
}
|
||||
this._sample = new Array(this._sample_size);
|
||||
this._sample = new Array<number>(this._sample_size);
|
||||
this._sampleIdx = 0;
|
||||
}
|
||||
}
|
||||
@ -226,7 +226,7 @@ export default class Apple2IO implements MemoryPages, Restorable<Apple2IOState>
|
||||
this._tapeCurrent = this._tape[this._tapeOffset][1];
|
||||
while (now >= this._tapeNext) {
|
||||
if ((this._tapeOffset % 1000) === 0) {
|
||||
debug('Read ' + (this._tapeOffset / 1000));
|
||||
debug(`Read ${this._tapeOffset / 1000}`);
|
||||
}
|
||||
this._tapeCurrent = this._tape[this._tapeOffset][1];
|
||||
this._tapeNext += this._tape[this._tapeOffset++][0];
|
||||
@ -451,7 +451,7 @@ export default class Apple2IO implements MemoryPages, Restorable<Apple2IOState>
|
||||
}
|
||||
|
||||
setTape(tape: TapeData) {
|
||||
debug('Tape length: ' + tape.length);
|
||||
debug(`Tape length: ${tape.length}`);
|
||||
this._tape = tape;
|
||||
this._tapeOffset = -1;
|
||||
}
|
||||
@ -459,7 +459,7 @@ export default class Apple2IO implements MemoryPages, Restorable<Apple2IOState>
|
||||
sampleRate(rate: number, sample_size: number) {
|
||||
this._rate = rate;
|
||||
this._sample_size = sample_size;
|
||||
this._sample = new Array(this._sample_size);
|
||||
this._sample = new Array<number>(this._sample_size);
|
||||
this._sampleIdx = 0;
|
||||
this._calcSampleRate();
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ export function base64_decode(data: string | null | undefined): memory | undefin
|
||||
|
||||
const DATA_URL_PREFIX = 'data:application/octet-stream;base64,';
|
||||
|
||||
export function base64_json_parse(json: string) {
|
||||
export function base64_json_parse(json: string): unknown {
|
||||
const reviver = (_key: string, value: unknown) => {
|
||||
if (typeof value ==='string' && value.startsWith(DATA_URL_PREFIX)) {
|
||||
return base64_decode(value.slice(DATA_URL_PREFIX.length));
|
||||
|
@ -308,7 +308,7 @@ function setDriveState(state: DriveState) {
|
||||
/**
|
||||
* Emulates the 16-sector and 13-sector versions of the Disk ][ drive and controller.
|
||||
*/
|
||||
export default class DiskII implements Card {
|
||||
export default class DiskII implements Card<State> {
|
||||
|
||||
private drives: Drive[] = [
|
||||
{ // Drive 1
|
||||
@ -488,7 +488,7 @@ export default class DiskII implements Card {
|
||||
return;
|
||||
}
|
||||
if (this.on && (this.skip || this.writeMode)) {
|
||||
const track = this.cur.tracks![this.cur.track >> 2];
|
||||
const track = this.cur.tracks[this.cur.track >> 2];
|
||||
if (track && track.length) {
|
||||
if (this.cur.head >= track.length) {
|
||||
this.cur.head = 0;
|
||||
@ -520,7 +520,7 @@ export default class DiskII implements Card {
|
||||
* tracks by activating two neighboring coils at once.
|
||||
*/
|
||||
private setPhase(phase: Phase, on: boolean) {
|
||||
this.debug('phase ' + phase + (on ? ' on' : ' off'));
|
||||
this.debug(`phase ${phase}${on ? ' on' : ' off'}`);
|
||||
if (on) {
|
||||
this.cur.track += PHASE_DELTA[this.cur.phase][phase] * 2;
|
||||
this.cur.phase = phase;
|
||||
@ -682,7 +682,7 @@ export default class DiskII implements Card {
|
||||
} else {
|
||||
// It's not explicitly stated, but writes to any address set the
|
||||
// data register.
|
||||
this.bus = val!;
|
||||
this.bus = val;
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -703,7 +703,9 @@ export default class DiskII implements Card {
|
||||
return this.bootstrapRom[off];
|
||||
}
|
||||
|
||||
write() { }
|
||||
write() {
|
||||
// not writable
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.on) {
|
||||
@ -722,7 +724,7 @@ export default class DiskII implements Card {
|
||||
this.moveHead();
|
||||
}
|
||||
|
||||
getState() {
|
||||
getState(): State {
|
||||
const result = {
|
||||
drives: [] as DriveState[],
|
||||
skip: this.skip,
|
||||
|
@ -217,7 +217,9 @@ export default class Mouse implements Card, Restorable<MouseState> {
|
||||
return rom[off];
|
||||
}
|
||||
|
||||
write() {}
|
||||
write() {
|
||||
// not writable
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers interrupts based on activity since the last tick
|
||||
|
@ -110,6 +110,7 @@ export default class NoSlotClock {
|
||||
}
|
||||
|
||||
setState(_: unknown) {
|
||||
// Setting the state makes no sense.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,11 +37,15 @@ export default class Parallel implements Card, Restorable<ParallelState> {
|
||||
return rom[off];
|
||||
}
|
||||
|
||||
write() {}
|
||||
write() {
|
||||
// not writable
|
||||
}
|
||||
|
||||
getState() {
|
||||
return {};
|
||||
}
|
||||
|
||||
setState(_state: ParallelState) {}
|
||||
setState(_state: ParallelState) {
|
||||
// can't set the state
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,9 @@ export default class RAMFactor implements Card, Restorable<RAMFactorState> {
|
||||
return rom[this.firmware << 12 | (page - 0xC0) << 8 | off];
|
||||
}
|
||||
|
||||
write() {}
|
||||
write() {
|
||||
// not writable
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.firmware = 0;
|
||||
|
@ -197,9 +197,9 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||
*/
|
||||
|
||||
readBlock(state: CpuState, drive: number, block: number, buffer: Address) {
|
||||
this.debug('read drive=' + drive);
|
||||
this.debug('read buffer=' + buffer);
|
||||
this.debug('read block=$' + toHex(block));
|
||||
this.debug(`read drive=${drive}`);
|
||||
this.debug(`read buffer=${buffer.toString()}`);
|
||||
this.debug(`read block=$${toHex(block)}`);
|
||||
|
||||
if (!this.disks[drive]?.blocks.length) {
|
||||
debug('Drive', drive, 'is empty');
|
||||
@ -224,9 +224,9 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||
*/
|
||||
|
||||
writeBlock(state: CpuState, drive: number, block: number, buffer: Address) {
|
||||
this.debug('write drive=' + drive);
|
||||
this.debug('write buffer=' + buffer);
|
||||
this.debug('write block=$' + toHex(block));
|
||||
this.debug(`write drive=${drive}`);
|
||||
this.debug(`write buffer=${buffer.toString()}`);
|
||||
this.debug(`write block=$${toHex(block)}`);
|
||||
|
||||
if (!this.disks[drive]?.blocks.length) {
|
||||
debug('Drive', drive, 'is empty');
|
||||
@ -332,11 +332,11 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||
buffer = bufferAddr.readAddress();
|
||||
block = blockAddr.readWord();
|
||||
|
||||
this.debug('cmd=' + cmd);
|
||||
this.debug(`cmd=${cmd}`);
|
||||
this.debug('unit=$' + toHex(unit));
|
||||
|
||||
this.debug('slot=' + driveSlot + ' drive=' + drive);
|
||||
this.debug('buffer=' + buffer + ' block=$' + toHex(block));
|
||||
this.debug(`slot=${driveSlot} drive=${drive}`);
|
||||
this.debug(`buffer=${buffer.toString()} block=$${toHex(block)}`);
|
||||
|
||||
switch (cmd) {
|
||||
case 0: // INFO
|
||||
@ -362,14 +362,14 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||
|
||||
const retVal = stackAddr.readAddress();
|
||||
|
||||
this.debug('return=' + retVal);
|
||||
this.debug(`return=${retVal.toString()}`);
|
||||
|
||||
const cmdBlockAddr = retVal.inc(1);
|
||||
cmd = cmdBlockAddr.readByte();
|
||||
const cmdListAddr = cmdBlockAddr.inc(1).readAddress();
|
||||
|
||||
this.debug('cmd=' + cmd);
|
||||
this.debug('cmdListAddr=' + cmdListAddr);
|
||||
this.debug(`cmd=${cmd}`);
|
||||
this.debug(`cmdListAddr=${cmdListAddr.toString()}`);
|
||||
|
||||
stackAddr.writeAddress(retVal.inc(3));
|
||||
|
||||
@ -378,13 +378,13 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||
buffer = cmdListAddr.inc(2).readAddress();
|
||||
let status;
|
||||
|
||||
this.debug('parameterCount=' + parameterCount);
|
||||
this.debug(`parameterCount=${parameterCount}`);
|
||||
switch (cmd) {
|
||||
case 0x00: // INFO
|
||||
status = cmdListAddr.inc(4).readByte();
|
||||
this.debug('info unit=' + unit);
|
||||
this.debug('info buffer=' + buffer);
|
||||
this.debug('info status=' + status);
|
||||
this.debug(`info unit=${unit}`);
|
||||
this.debug(`info buffer=${buffer.toString()}`);
|
||||
this.debug(`info status=${status}`);
|
||||
switch (unit) {
|
||||
case 0:
|
||||
switch (status) {
|
||||
@ -482,6 +482,7 @@ export default class SmartPort implements Card, MassStorage, Restorable<SmartPor
|
||||
}
|
||||
|
||||
write() {
|
||||
// not writable
|
||||
}
|
||||
|
||||
getState() {
|
||||
|
@ -138,6 +138,7 @@ export default class Thunderclock implements Card, Restorable<ThunderclockState>
|
||||
}
|
||||
|
||||
write() {
|
||||
// not writable
|
||||
}
|
||||
|
||||
ioSwitch(off: byte, val?: byte) {
|
||||
@ -148,5 +149,7 @@ export default class Thunderclock implements Card, Restorable<ThunderclockState>
|
||||
return {};
|
||||
}
|
||||
|
||||
setState() {}
|
||||
setState(_state: ThunderclockState) {
|
||||
// can't set the state
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export const Apple2 = (props: Apple2Props) => {
|
||||
if (screen.current) {
|
||||
const options = {
|
||||
canvas: screen.current,
|
||||
tick: () => {},
|
||||
tick: () => { /* do nothing */ },
|
||||
...props,
|
||||
};
|
||||
const apple2 = new Apple2Impl(options);
|
||||
|
@ -46,7 +46,9 @@ export const Drives = ({ io, sectors }: DrivesProps) => {
|
||||
side,
|
||||
}));
|
||||
},
|
||||
dirty: () => {}
|
||||
dirty: () => {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
if (io) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { h, Fragment } from 'preact';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { noAwait } from './util/promises';
|
||||
|
||||
export interface FilePickerAcceptType {
|
||||
description?: string | undefined;
|
||||
@ -41,7 +42,7 @@ interface ExtraProps {
|
||||
|
||||
const InputFileChooser = ({
|
||||
disabled = false,
|
||||
onChange = () => { },
|
||||
onChange = () => { /* do nothing */ },
|
||||
accept = [],
|
||||
}: InputFileChooserProps) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@ -104,7 +105,7 @@ interface FilePickerChooserProps {
|
||||
|
||||
const FilePickerChooser = ({
|
||||
disabled = false,
|
||||
onChange = () => { },
|
||||
onChange = () => { /* do nothing */ },
|
||||
accept = [ACCEPT_EVERYTHING_TYPE]
|
||||
}: FilePickerChooserProps) => {
|
||||
const [busy, setBusy] = useState<boolean>(false);
|
||||
@ -142,7 +143,7 @@ const FilePickerChooser = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={onClickInternal} disabled={disabled || busy}>
|
||||
<button onClick={noAwait(onClickInternal)} disabled={disabled || busy}>
|
||||
Choose File
|
||||
</button>
|
||||
|
||||
|
@ -7,6 +7,7 @@ import DiskII from '../cards/disk2';
|
||||
|
||||
import index from 'json/disks/index.json';
|
||||
import { FileChooser, FilePickerAcceptType, FileSystemFileHandleLike } from './FileChooser';
|
||||
import { noAwait } from './util/promises';
|
||||
|
||||
const DISK_TYPES: FilePickerAcceptType[] = [
|
||||
{
|
||||
@ -125,7 +126,7 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<button onClick={doCancel}>Cancel</button>
|
||||
<button onClick={doOpen} disabled={busy || empty}>Open</button>
|
||||
<button onClick={noAwait(doOpen)} disabled={busy || empty}>Open</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
|
@ -82,7 +82,7 @@ export const loadJSON = async (disk2: DiskII, number: DriveNumber, url: string)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error loading: ${response.statusText}`);
|
||||
}
|
||||
const data: JSONDisk = await response.json();
|
||||
const data = await response.json() as JSONDisk;
|
||||
if (!includes(NIBBLE_FORMATS, data.type)) {
|
||||
throw new Error(`Type ${data.type} not recognized.`);
|
||||
}
|
||||
@ -112,7 +112,10 @@ export const loadHttpFile = async (
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error loading: ${response.statusText}`);
|
||||
}
|
||||
const reader = response.body!.getReader();
|
||||
if (!response.body) {
|
||||
throw new Error('Error loading: no body');
|
||||
}
|
||||
const reader = response.body.getReader();
|
||||
let received = 0;
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
@ -131,7 +134,7 @@ export const loadHttpFile = async (
|
||||
}
|
||||
|
||||
const urlParts = url.split('/');
|
||||
const file = urlParts.pop()!;
|
||||
const file = urlParts.pop() || url;
|
||||
const fileParts = file.split('.');
|
||||
const ext = fileParts.pop()?.toLowerCase() || '[none]';
|
||||
const name = decodeURIComponent(fileParts.join('.'));
|
||||
|
12
js/components/util/promises.ts
Normal file
12
js/components/util/promises.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Converts a function type returning a `Promise` to a function type returning `void`.
|
||||
*/
|
||||
export type NoAwait<F extends (...args: unknown[]) => Promise<unknown>> =
|
||||
(...args: Parameters<F>) => void;
|
||||
|
||||
/**
|
||||
* Signals that the argument returns a `Promise` that is intentionally not being awaited.
|
||||
*/
|
||||
export function noAwait<F extends (...args: unknown[]) => Promise<unknown>>(f: F): NoAwait<F> {
|
||||
return f as NoAwait<F>;
|
||||
}
|
@ -135,7 +135,7 @@ function isResettablePageHandler(pageHandler: MemoryPages | ResettablePageHandle
|
||||
|
||||
const BLANK_PAGE: Memory = {
|
||||
read: function () { return 0; },
|
||||
write: function () { }
|
||||
write: function () { /* not writable */ }
|
||||
};
|
||||
|
||||
interface Opts {
|
||||
@ -196,7 +196,7 @@ export default class CPU6502 {
|
||||
private addr: word = 0;
|
||||
|
||||
/** Filled array of memory handlers by address page */
|
||||
private memPages: Memory[] = new Array(0x100);
|
||||
private memPages: Memory[] = new Array<Memory>(0x100);
|
||||
/** Callbacks invoked on reset signal */
|
||||
private resetHandlers: ResettablePageHandler[] = [];
|
||||
/** Elapsed cycles */
|
||||
@ -246,7 +246,7 @@ export default class CPU6502 {
|
||||
}
|
||||
|
||||
// Certain browsers benefit from using arrays over maps
|
||||
this.opary = new Array(0x100);
|
||||
this.opary = new Array<Instruction>(0x100);
|
||||
|
||||
for (let idx = 0; idx < 0x100; idx++) {
|
||||
this.opary[idx] = ops[idx] || this.unknown(idx);
|
||||
|
@ -128,7 +128,7 @@ export default class Debugger {
|
||||
|
||||
result += this.padWithSymbol(pc);
|
||||
|
||||
const cmd = new Array(size);
|
||||
const cmd = new Array<number>(size);
|
||||
for (let idx = 0, jdx = pc; idx < size; idx++, jdx++) {
|
||||
cmd[idx] = this.cpu.read(jdx);
|
||||
}
|
||||
@ -238,13 +238,13 @@ export default class Debugger {
|
||||
case 'implied':
|
||||
break;
|
||||
case 'immediate':
|
||||
result += '#' + toHexOrSymbol(lsb);
|
||||
result += `#${toHexOrSymbol(lsb)}`;
|
||||
break;
|
||||
case 'absolute':
|
||||
result += '' + toHexOrSymbol(addr, 4);
|
||||
result += `${toHexOrSymbol(addr, 4)}`;
|
||||
break;
|
||||
case 'zeroPage':
|
||||
result += '' + toHexOrSymbol(lsb);
|
||||
result += `${toHexOrSymbol(lsb)}`;
|
||||
break;
|
||||
case 'relative':
|
||||
{
|
||||
@ -253,38 +253,38 @@ export default class Debugger {
|
||||
off -= 256;
|
||||
}
|
||||
pc += off + 2;
|
||||
result += '' + toHexOrSymbol(pc, 4) + ' (' + off + ')';
|
||||
result += `${toHexOrSymbol(pc, 4)} (${off})`;
|
||||
}
|
||||
break;
|
||||
case 'absoluteX':
|
||||
result += '' + toHexOrSymbol(addr, 4)+ ',X';
|
||||
result += `${toHexOrSymbol(addr, 4)},X`;
|
||||
break;
|
||||
case 'absoluteY':
|
||||
result += '' + toHexOrSymbol(addr, 4) + ',Y';
|
||||
result += `${toHexOrSymbol(addr, 4)},Y`;
|
||||
break;
|
||||
case 'zeroPageX':
|
||||
result += '' + toHexOrSymbol(lsb) + ',X';
|
||||
result += `${toHexOrSymbol(lsb)},X`;
|
||||
break;
|
||||
case 'zeroPageY':
|
||||
result += '' + toHexOrSymbol(lsb) + ',Y';
|
||||
result += `${toHexOrSymbol(lsb)},Y`;
|
||||
break;
|
||||
case 'absoluteIndirect':
|
||||
result += '(' + toHexOrSymbol(addr, 4) + ')';
|
||||
result += `(${toHexOrSymbol(addr, 4)})`;
|
||||
break;
|
||||
case 'zeroPageXIndirect':
|
||||
result += '(' + toHexOrSymbol(lsb) + ',X)';
|
||||
result += `(${toHexOrSymbol(lsb)},X)`;
|
||||
break;
|
||||
case 'zeroPageIndirectY':
|
||||
result += '(' + toHexOrSymbol(lsb) + '),Y';
|
||||
result += `(${toHexOrSymbol(lsb)},),Y`;
|
||||
break;
|
||||
case 'accumulator':
|
||||
result += 'A';
|
||||
break;
|
||||
case 'zeroPageIndirect':
|
||||
result += '(' + toHexOrSymbol(lsb) + ')';
|
||||
result += `(${toHexOrSymbol(lsb)})`;
|
||||
break;
|
||||
case 'absoluteXIndirect':
|
||||
result += '(' + toHexOrSymbol(addr, 4) + ',X)';
|
||||
result += `(${toHexOrSymbol(addr, 4)},X)`;
|
||||
break;
|
||||
case 'zeroPage_relative':
|
||||
val = lsb;
|
||||
@ -293,7 +293,7 @@ export default class Debugger {
|
||||
off -= 256;
|
||||
}
|
||||
pc += off + 2;
|
||||
result += '' + toHexOrSymbol(val) + ',' + toHexOrSymbol(pc, 4) + ' (' + off + ')';
|
||||
result += `${toHexOrSymbol(val)},${toHexOrSymbol(pc, 4)} (${off})`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { h, render } from 'preact';
|
||||
import { App } from './components/App';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
render(<App />, document.getElementById('app')!);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { bit, byte, memory } from '../types';
|
||||
import { base64_decode, base64_encode } from '../base64';
|
||||
import { bytify, debug, toHex } from '../util';
|
||||
import { NibbleDisk, ENCODING_NIBBLE } from './types';
|
||||
import { NibbleDisk, ENCODING_NIBBLE, JSONDisk } from './types';
|
||||
|
||||
/**
|
||||
* DOS 3.3 Physical sector order (index is physical sector, value is DOS sector).
|
||||
@ -465,14 +465,14 @@ export function jsonEncode(disk: NibbleDisk, pretty: boolean): string {
|
||||
|
||||
export function jsonDecode(data: string): NibbleDisk {
|
||||
const tracks: memory[] = [];
|
||||
const json = JSON.parse(data);
|
||||
const v = json.volume;
|
||||
const readOnly = json.readOnly;
|
||||
const json = JSON.parse(data) as JSONDisk;
|
||||
const v = json.volume || 254;
|
||||
const readOnly = json.readOnly || false;
|
||||
for (let t = 0; t < json.data.length; t++) {
|
||||
let track: byte[] = [];
|
||||
for (let s = 0; s < json.data[t].length; s++) {
|
||||
const _s = json.type === 'po' ? PO[s] : DO[s];
|
||||
const sector: string = json.data[t][_s];
|
||||
const sector: string = json.data[t][_s] as string;
|
||||
const d = base64_decode(sector);
|
||||
track = track.concat(explodeSector16(v, t, s, d));
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ function stringFromBytes(data: DataView, start: number, end: number): string {
|
||||
return String.fromCharCode.apply(
|
||||
null,
|
||||
new Uint8Array(data.buffer.slice(data.byteOffset + start, data.byteOffset + end))
|
||||
);
|
||||
) as string;
|
||||
}
|
||||
|
||||
export class InfoChunk {
|
||||
|
47
js/gl.ts
47
js/gl.ts
@ -352,6 +352,22 @@ export class LoresPageGL implements LoresPage {
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
const _drawPixel = (data: Uint8ClampedArray, off: number, color: Color) => {
|
||||
const c0 = color[0], c1 = color[1], c2 = color[2];
|
||||
|
||||
data[off + 0] = data[off + 4] = c0;
|
||||
data[off + 1] = data[off + 5] = c1;
|
||||
data[off + 2] = data[off + 6] = c2;
|
||||
};
|
||||
|
||||
const _drawHalfPixel = (data: Uint8ClampedArray, off: number, color: Color) => {
|
||||
const c0 = color[0], c1 = color[1], c2 = color[2];
|
||||
|
||||
data[off + 0] = c0;
|
||||
data[off + 1] = c1;
|
||||
data[off + 2] = c2;
|
||||
};
|
||||
|
||||
export class HiresPageGL implements HiresPage {
|
||||
public imageData: ImageData;
|
||||
dirty: Region = {...notDirty};
|
||||
@ -371,22 +387,6 @@ export class HiresPageGL implements HiresPage {
|
||||
this.vm.setHiresPage(page, this);
|
||||
}
|
||||
|
||||
private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) {
|
||||
const c0 = color[0], c1 = color[1], c2 = color[2];
|
||||
|
||||
data[off + 0] = data[off + 4] = c0;
|
||||
data[off + 1] = data[off + 5] = c1;
|
||||
data[off + 2] = data[off + 6] = c2;
|
||||
}
|
||||
|
||||
private _drawHalfPixel(data: Uint8ClampedArray, off: number, color: Color) {
|
||||
const c0 = color[0], c1 = color[1], c2 = color[2];
|
||||
|
||||
data[off + 0] = c0;
|
||||
data[off + 1] = c1;
|
||||
data[off + 2] = c2;
|
||||
}
|
||||
|
||||
bank0(): MemoryPages {
|
||||
return {
|
||||
start: () => this._start(),
|
||||
@ -453,9 +453,9 @@ export class HiresPageGL implements HiresPage {
|
||||
let bits = val;
|
||||
for (let jdx = 0; jdx < 7; jdx++, offset += 4) {
|
||||
if (bits & 0x01) {
|
||||
this._drawHalfPixel(data, offset, whiteCol);
|
||||
_drawHalfPixel(data, offset, whiteCol);
|
||||
} else {
|
||||
this._drawHalfPixel(data, offset, blackCol);
|
||||
_drawHalfPixel(data, offset, blackCol);
|
||||
}
|
||||
bits >>= 1;
|
||||
}
|
||||
@ -468,17 +468,17 @@ export class HiresPageGL implements HiresPage {
|
||||
if (hbs) {
|
||||
const val0 = this._buffer[bank][base - 1] || 0;
|
||||
if (val0 & 0x40) {
|
||||
this._drawHalfPixel(data, offset, whiteCol);
|
||||
_drawHalfPixel(data, offset, whiteCol);
|
||||
} else {
|
||||
this._drawHalfPixel(data, offset, blackCol);
|
||||
_drawHalfPixel(data, offset, blackCol);
|
||||
}
|
||||
offset += 4;
|
||||
}
|
||||
let bits = val;
|
||||
for (let idx = 0; idx < 7; idx++, offset += 8) {
|
||||
const drawPixel = cropLastPixel && idx === 6
|
||||
? this._drawHalfPixel
|
||||
: this._drawPixel;
|
||||
? _drawHalfPixel
|
||||
: _drawPixel;
|
||||
if (bits & 0x01) {
|
||||
drawPixel(data, offset, whiteCol);
|
||||
} else {
|
||||
@ -587,6 +587,9 @@ export class VideoModesGL implements VideoModes {
|
||||
}
|
||||
|
||||
async init() {
|
||||
// There is a typing bug in https://github.com/whscullin/apple2shader/blob/master/index.d.ts
|
||||
// that declares initOpenGL as returning void when it actually returns Promise<void>.
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await this._sv.initOpenGL();
|
||||
|
||||
this._displayConfig = this.defaultMonitor();
|
||||
|
@ -51,6 +51,7 @@ switch (romVersion) {
|
||||
}
|
||||
|
||||
const options = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
canvas: document.querySelector<HTMLCanvasElement>('#screen')!,
|
||||
gl: prefs.readPref('gl_canvas', 'true') === 'true',
|
||||
rom,
|
||||
|
@ -42,6 +42,7 @@ switch (romVersion) {
|
||||
|
||||
const options = {
|
||||
gl: prefs.readPref('gl_canvas', 'true') === 'true',
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
canvas: document.querySelector<HTMLCanvasElement>('#screen')!,
|
||||
rom,
|
||||
characterRom,
|
||||
|
14
js/mmu.ts
14
js/mmu.ts
@ -525,19 +525,19 @@ export default class MMU implements Memory, Restorable<MMUState> {
|
||||
|
||||
switch(off) {
|
||||
case LOC.BSRBANK2:
|
||||
this._debug('Bank 2 Read ' + !this._bank1);
|
||||
this._debug(`Bank 2 Read ${!this._bank1 ? 'true' : 'false'}`);
|
||||
result = !this._bank1 ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.BSRREADRAM:
|
||||
this._debug('Bank SW RAM Read ' + this._readbsr);
|
||||
this._debug(`Bank SW RAM Read ${this._readbsr ? 'true' : 'false'}`);
|
||||
result = this._readbsr ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.RAMRD: // 0xC013
|
||||
this._debug('Aux RAM Read ' + this._auxRamRead);
|
||||
this._debug(`Aux RAM Read ${this._auxRamRead ? 'true' : 'false'}`);
|
||||
result = this._auxRamRead ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.RAMWRT: // 0xC014
|
||||
this._debug('Aux RAM Write ' + this._auxRamWrite);
|
||||
this._debug(`Aux RAM Write ${this._auxRamWrite ? 'true' : 'false'}`);
|
||||
result = this._auxRamWrite ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.INTCXROM: // 0xC015
|
||||
@ -545,15 +545,15 @@ export default class MMU implements Memory, Restorable<MMUState> {
|
||||
result = this._intcxrom ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.ALTZP: // 0xC016
|
||||
this._debug('Alt ZP ' + this._altzp);
|
||||
this._debug(`Alt ZP ${this._altzp ? 'true' : 'false'}`);
|
||||
result = this._altzp ? 0x80 : 0x0;
|
||||
break;
|
||||
case LOC.SLOTC3ROM: // 0xC017
|
||||
this._debug('Slot C3 ROM ' + this._slot3rom);
|
||||
this._debug(`Slot C3 ROM ${this._slot3rom ? 'true' : 'false'}`);
|
||||
result = this._slot3rom ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC._80STORE: // 0xC018
|
||||
this._debug('80 Store ' + this._80store);
|
||||
this._debug(`80 Store ${this._80store ? 'true' : 'false'}`);
|
||||
result = this._80store ? 0x80 : 0x00;
|
||||
break;
|
||||
case LOC.VERTBLANK: // 0xC019
|
||||
|
@ -5,10 +5,10 @@ export type ROMState = null;
|
||||
export default class ROM implements MemoryPages, Restorable<ROMState> {
|
||||
|
||||
constructor(
|
||||
private readonly startPage: byte,
|
||||
private readonly endPage: byte,
|
||||
private readonly rom: rom) {
|
||||
const expectedLength = (endPage-startPage+1) * 256;
|
||||
private readonly startPage: byte,
|
||||
private readonly endPage: byte,
|
||||
private readonly rom: rom) {
|
||||
const expectedLength = (endPage - startPage + 1) * 256;
|
||||
if (rom.length !== expectedLength) {
|
||||
throw Error(`rom does not have the correct length: expected ${expectedLength} was ${rom.length}`);
|
||||
}
|
||||
@ -24,10 +24,12 @@ export default class ROM implements MemoryPages, Restorable<ROMState> {
|
||||
return this.rom[(page - this.startPage) << 8 | off];
|
||||
}
|
||||
write() {
|
||||
// not writable
|
||||
}
|
||||
getState() {
|
||||
return null;
|
||||
}
|
||||
setState(_state: null) {
|
||||
setState(_state: ROMState) {
|
||||
// not restorable
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ export interface MemoryPages extends Memory {
|
||||
}
|
||||
|
||||
/* An interface card */
|
||||
export interface Card extends Memory, Restorable {
|
||||
export interface Card<StateT = unknown> extends Memory, Restorable<StateT> {
|
||||
/* Reset the card */
|
||||
reset?(): void;
|
||||
|
||||
|
@ -23,7 +23,7 @@ import ApplesoftDump from '../applesoft/decompiler';
|
||||
import ApplesoftCompiler from '../applesoft/compiler';
|
||||
|
||||
import { debug } from '../util';
|
||||
import { Apple2, Stats } from '../apple2';
|
||||
import { Apple2, Stats, State as Apple2State } from '../apple2';
|
||||
import DiskII from '../cards/disk2';
|
||||
import CPU6502 from '../cpu6502';
|
||||
import { VideoModes } from '../videomodes';
|
||||
@ -123,7 +123,7 @@ export function openSave(driveString: string, event: MouseEvent) {
|
||||
const a = document.querySelector<HTMLAnchorElement>('#local_save_link')!;
|
||||
|
||||
if (!data) {
|
||||
alert('No data from drive ' + drive);
|
||||
alert(`No data from drive ${drive}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -211,7 +211,7 @@ function loadingProgress(current: number, total: number) {
|
||||
const meter = document.querySelector<HTMLDivElement>('#loading-modal .meter')!;
|
||||
const progress = document.querySelector<HTMLDivElement>('#loading-modal .progress')!;
|
||||
meter.style.display = 'block';
|
||||
progress.style.width = current / total * meter.clientWidth + 'px';
|
||||
progress.style.width = `${current / total * meter.clientWidth}px`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,13 +236,13 @@ export function loadAjax(drive: DriveNumber, url: string) {
|
||||
}
|
||||
}).then(function (data: JSONDisk | JSONBinaryImage) {
|
||||
if (data.type === 'binary') {
|
||||
loadBinary(data as JSONBinaryImage);
|
||||
loadBinary(data );
|
||||
} else if (includes(DISK_FORMATS, data.type)) {
|
||||
loadDisk(drive, data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
loadingStop();
|
||||
}).catch(function (error) {
|
||||
}).catch(function (error: Error) {
|
||||
loadingStop();
|
||||
openAlert(error.message);
|
||||
console.error(error);
|
||||
@ -414,7 +414,7 @@ export function doLoadHTTP(drive: DriveNumber, url?: string) {
|
||||
if (url) {
|
||||
fetch(url).then(function (response) {
|
||||
if (response.ok) {
|
||||
const reader = response!.body!.getReader();
|
||||
const reader = response.body!.getReader();
|
||||
let received = 0;
|
||||
const chunks: Uint8Array[] = [];
|
||||
const contentLength = parseInt(response.headers.get('content-length')!, 10);
|
||||
@ -468,7 +468,7 @@ export function doLoadHTTP(drive: DriveNumber, url?: string) {
|
||||
throw new Error(`Extension ${ext} not recognized.`);
|
||||
}
|
||||
loadingStop();
|
||||
}).catch(function (error) {
|
||||
}).catch((error: Error) => {
|
||||
loadingStop();
|
||||
openAlert(error.message);
|
||||
console.error(error);
|
||||
@ -499,19 +499,19 @@ export function updateKHz() {
|
||||
case 0: {
|
||||
delta = cycles - lastCycles;
|
||||
khz = Math.trunc(delta / ms);
|
||||
kHzElement.innerText = khz + ' kHz';
|
||||
kHzElement.innerText = `${khz} kHz`;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
delta = stats.renderedFrames - lastRenderedFrames;
|
||||
fps = Math.trunc(delta / (ms / 1000));
|
||||
kHzElement.innerText = fps + ' rps';
|
||||
kHzElement.innerText = `${fps} rps`;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
delta = stats.frames - lastFrames;
|
||||
fps = Math.trunc(delta / (ms / 1000));
|
||||
kHzElement.innerText = fps + ' fps';
|
||||
kHzElement.innerText = `${fps} fps`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -579,7 +579,7 @@ export function selectCategory() {
|
||||
const file = cat[idx];
|
||||
let name = file.name;
|
||||
if (file.disk) {
|
||||
name += ' - ' + file.disk;
|
||||
name += ` - ${file.disk}`;
|
||||
}
|
||||
const option = document.createElement('option');
|
||||
option.value = file.filename;
|
||||
@ -622,7 +622,7 @@ function loadDisk(drive: DriveNumber, disk: JSONDisk) {
|
||||
*/
|
||||
|
||||
function updateLocalStorage() {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
const names = Object.keys(diskIndex);
|
||||
|
||||
const cat: DiskDescriptor[] = disk_categories['Local Saves'] = [];
|
||||
@ -654,7 +654,7 @@ type LocalDiskIndex = {
|
||||
};
|
||||
|
||||
function saveLocalStorage(drive: DriveNumber, name: string) {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex || '{}') as LocalDiskIndex;
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
|
||||
const json = _disk2.getJSON(drive);
|
||||
diskIndex[name] = json;
|
||||
@ -667,7 +667,7 @@ function saveLocalStorage(drive: DriveNumber, name: string) {
|
||||
}
|
||||
|
||||
function deleteLocalStorage(name: string) {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex || '{}') as LocalDiskIndex;
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
if (diskIndex[name]) {
|
||||
delete diskIndex[name];
|
||||
openAlert('Deleted');
|
||||
@ -677,7 +677,7 @@ function deleteLocalStorage(name: string) {
|
||||
}
|
||||
|
||||
function loadLocalStorage(drive: DriveNumber, name: string) {
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex || '{}') as LocalDiskIndex;
|
||||
const diskIndex = JSON.parse(window.localStorage.diskIndex as string || '{}') as LocalDiskIndex;
|
||||
if (diskIndex[name]) {
|
||||
_disk2.setJSON(drive, diskIndex[name]);
|
||||
driveLights.label(drive, name);
|
||||
@ -891,7 +891,7 @@ function onLoaded(apple2: Apple2, disk2: DiskII, massStorage: MassStorage, print
|
||||
});
|
||||
keyboard.setFunction('F9', () => {
|
||||
if (window.localStorage.state) {
|
||||
_apple2.setState(base64_json_parse(window.localStorage.state));
|
||||
_apple2.setState(base64_json_parse(window.localStorage.state as string) as Apple2State);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -12,13 +12,17 @@ declare global {
|
||||
function registerProcessor(name: string, ctor :{ new(): AudioWorkletProcessor }): void;
|
||||
}
|
||||
|
||||
export interface AppleAudioMessageEvent extends MessageEvent {
|
||||
data: Float32Array;
|
||||
}
|
||||
|
||||
export class AppleAudioProcessor extends AudioWorkletProcessor {
|
||||
private samples: Float32Array[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
console.info('AppleAudioProcessor constructor');
|
||||
this.port.onmessage = (ev: MessageEvent) => {
|
||||
this.port.onmessage = (ev: AppleAudioMessageEvent) => {
|
||||
this.samples.push(ev.data);
|
||||
if (this.samples.length > 256) {
|
||||
this.samples.shift();
|
||||
|
@ -3,11 +3,12 @@ import type { DriveNumber } from '../formats/types';
|
||||
|
||||
export default class DriveLights implements Callbacks {
|
||||
public driveLight(drive: DriveNumber, on: boolean) {
|
||||
const disk =
|
||||
document.querySelector('#disk' + drive)! as HTMLElement;
|
||||
disk.style.backgroundImage =
|
||||
on ? 'url(css/red-on-16.png)' :
|
||||
'url(css/red-off-16.png)';
|
||||
const disk = document.querySelector<HTMLElement>(`#disk${drive}`);
|
||||
if (disk) {
|
||||
disk.style.backgroundImage =
|
||||
on ? 'url(css/red-on-16.png)' :
|
||||
'url(css/red-off-16.png)';
|
||||
}
|
||||
}
|
||||
|
||||
public dirty(_drive: DriveNumber, _dirty: boolean) {
|
||||
@ -15,11 +16,11 @@ export default class DriveLights implements Callbacks {
|
||||
}
|
||||
|
||||
public label(drive: DriveNumber, label?: string, side?: string) {
|
||||
const labelElement =
|
||||
document.querySelector('#disk-label' + drive)! as HTMLElement;
|
||||
if (label) {
|
||||
labelElement.innerText = label + (side ? ` - ${side}` : '');
|
||||
const labelElement = document.querySelector<HTMLElement>(`#disk-label${drive}`);
|
||||
const labelText = `${label || ''} ${(side ? `- ${side}` : '')}`;
|
||||
if (label && labelElement) {
|
||||
labelElement.innerText = labelText;
|
||||
}
|
||||
return labelElement.innerText;
|
||||
return labelText;
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export function processGamepad(io: Apple2IO) {
|
||||
if (val <= 0) {
|
||||
io.buttonDown(-val as 0 | 1 | 2);
|
||||
} else {
|
||||
io.keyDown(gamepadMap[idx]!);
|
||||
io.keyDown(val);
|
||||
}
|
||||
} else if (!pressed && old) {
|
||||
if (val <= 0) {
|
||||
|
@ -362,7 +362,7 @@ export default class KeyBoard {
|
||||
for (y = 0; y < 5; y++) {
|
||||
row = document.createElement('div');
|
||||
row.classList.add('row');
|
||||
row.classList.add('row' + y);
|
||||
row.classList.add(`row${y}`);
|
||||
this.kb.append(row);
|
||||
for (x = 0; x < this.keys[0][y].length; x++) {
|
||||
const key1 = this.keys[0][y][x];
|
||||
|
@ -32,8 +32,8 @@ export default class Tape {
|
||||
let old = (datum > 0.0), current;
|
||||
let last = 0;
|
||||
let delta: number;
|
||||
debug('Sample Count: ' + data.length);
|
||||
debug('Sample rate: ' + buffer.sampleRate);
|
||||
debug(`Sample Count: ${data.length}`);
|
||||
debug(`Sample rate: ${buffer.sampleRate}`);
|
||||
for (let idx = 1; idx < data.length; idx++) {
|
||||
datum = data[idx];
|
||||
if ((datum > 0.1) || (datum < -0.1)) {
|
||||
@ -65,7 +65,7 @@ export default class Tape {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}, function (error) {
|
||||
}, (error: Error) => {
|
||||
window.alert(error.message);
|
||||
});
|
||||
};
|
||||
|
@ -46,7 +46,7 @@ export function bytify(ary: number[]): memory {
|
||||
|
||||
/** Writes to the console. */
|
||||
export function debug(...args: unknown[]): void {
|
||||
console.log.apply(console, args);
|
||||
console.log(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,6 +22,8 @@ const FAKE_FILE_HANDLE = {
|
||||
isDirectory: false,
|
||||
} as const;
|
||||
|
||||
const NOP = () => { /* do nothing */ };
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const EMPTY_FILE_LIST = backdoors.newFileList();
|
||||
|
||||
@ -30,13 +32,13 @@ const FAKE_FILE = new File([], 'fake');
|
||||
describe('FileChooser', () => {
|
||||
describe('input-based chooser', () => {
|
||||
it('should be instantiable', () => {
|
||||
const { container } = render(<FileChooser control='input' onChange={() => { }} />);
|
||||
const { container } = render(<FileChooser control='input' onChange={NOP} />);
|
||||
|
||||
expect(container).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should use the file input element', async () => {
|
||||
render(<FileChooser control='input' onChange={() => { }} />);
|
||||
render(<FileChooser control='input' onChange={NOP} />);
|
||||
|
||||
const inputElement = await screen.findByRole('button') as HTMLInputElement;
|
||||
expect(inputElement.type).toBe('file');
|
||||
@ -90,7 +92,7 @@ describe('FileChooser', () => {
|
||||
});
|
||||
|
||||
it('should be instantiable', () => {
|
||||
const { container } = render(<FileChooser control='picker' onChange={() => { }} />);
|
||||
const { container } = render(<FileChooser control='picker' onChange={NOP} />);
|
||||
|
||||
expect(container).not.toBeNull();
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ export const screenEmu = (function () {
|
||||
DisplayConfiguration: class {},
|
||||
Point: class {},
|
||||
ScreenView: class {
|
||||
initOpenGL() {}
|
||||
initOpenGL() { return Promise.resolve(); }
|
||||
},
|
||||
Size: class{},
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ describe('createDiskFromJsonDisk', () => {
|
||||
readOnly: undefined,
|
||||
side: 'Front',
|
||||
volume: 254,
|
||||
tracks: expect.any(Array)
|
||||
tracks: expect.any(Array) as number[][]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
9
test/js/formats/testdata/woz.ts
vendored
9
test/js/formats/testdata/woz.ts
vendored
@ -1,3 +1,4 @@
|
||||
import { byte } from 'js/types';
|
||||
import {
|
||||
numberToBytes,
|
||||
stringToBytes,
|
||||
@ -49,7 +50,7 @@ const mockInfo2 = [
|
||||
* Track map all pointing to track 0
|
||||
*/
|
||||
|
||||
export const mockTMAP = new Array(160);
|
||||
export const mockTMAP = new Array<byte>(160);
|
||||
mockTMAP.fill(0);
|
||||
|
||||
/**
|
||||
@ -58,7 +59,7 @@ mockTMAP.fill(0);
|
||||
|
||||
// 24 bits of track data, padded
|
||||
|
||||
const mockTrackData = new Array(6646);
|
||||
const mockTrackData = new Array<byte>(6646);
|
||||
mockTrackData.fill(0);
|
||||
mockTrackData[0] = 0xd5;
|
||||
mockTrackData[1] = 0xaa;
|
||||
@ -82,7 +83,7 @@ const mockTRKS = [
|
||||
* Version 2 TRKS structure
|
||||
*/
|
||||
|
||||
const mockTrackMap = new Array(160 * 8);
|
||||
const mockTrackMap = new Array<byte>(160 * 8);
|
||||
mockTrackMap.fill(0);
|
||||
mockTrackMap[0x00] = 0x03;
|
||||
mockTrackMap[0x01] = 0x00;
|
||||
@ -93,7 +94,7 @@ mockTrackMap[0x07] = 0x00;
|
||||
mockTrackMap[0x08] = 0x00;
|
||||
mockTrackMap[0x09] = 0x00;
|
||||
|
||||
const mockTrackData2 = new Array(512);
|
||||
const mockTrackData2 = new Array<byte>(512);
|
||||
mockTrackData2.fill(0);
|
||||
mockTrackData2[0] = 0xd5;
|
||||
mockTrackData2[1] = 0xaa;
|
||||
|
@ -24,7 +24,7 @@ export function compareSequences(track: memory, bytes: number[], pos: number): b
|
||||
export function expectSequence(track: memory, pos: number, bytes: number[]): number {
|
||||
if (!compareSequences(track, bytes, pos)) {
|
||||
const track_slice = track.slice(pos, Math.min(track.length, pos + bytes.length));
|
||||
throw new Error(`expected ${bytes} got ${track_slice}`);
|
||||
throw new Error(`expected ${bytes.toString()} got ${track_slice.toString()}`);
|
||||
}
|
||||
return pos + bytes.length;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WozDisk, ENCODING_BITSTREAM } from 'js/formats/types';
|
||||
import { ENCODING_BITSTREAM } from 'js/formats/types';
|
||||
import createDiskFromWoz from 'js/formats/woz';
|
||||
import {
|
||||
mockWoz1,
|
||||
@ -19,7 +19,7 @@ describe('woz', () => {
|
||||
rawData: mockWoz1
|
||||
};
|
||||
|
||||
const disk = createDiskFromWoz(options) as WozDisk;
|
||||
const disk = createDiskFromWoz(options) ;
|
||||
expect(disk).toEqual({
|
||||
name: 'Mock Woz 1',
|
||||
readOnly: true,
|
||||
@ -58,7 +58,7 @@ describe('woz', () => {
|
||||
rawData: mockWoz2
|
||||
};
|
||||
|
||||
const disk = createDiskFromWoz(options) as WozDisk;
|
||||
const disk = createDiskFromWoz(options) ;
|
||||
expect(disk).toEqual({
|
||||
name: 'Mock Woz 2',
|
||||
side: 'B',
|
||||
|
@ -23,6 +23,7 @@ export class Program implements MemoryPages {
|
||||
}
|
||||
|
||||
write(_page: byte, _off: byte, _val: byte) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
export const createImageFromImageData = (data: ImageData) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = data.width;
|
||||
|
Loading…
Reference in New Issue
Block a user