mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Options modal (#75)
Refactor Options modal, webgl scanline, restore `Apple2.apple2`, other minor fixes.
This commit is contained in:
parent
5c7e335aad
commit
09c6d6fbb1
@ -135,97 +135,6 @@
|
||||
</button>
|
||||
</header>
|
||||
<main class="modal__content" id="options-modal-content">
|
||||
<h3>Type</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<select id="computer_type2" value="apple2plus" onchange="Apple2.updateCPU()">
|
||||
<option value="apple2plus">Apple ][+</option>
|
||||
<option value="apple2">Autostart Apple ][</option>
|
||||
<option value="apple213">13 Sector Apple ][</option>
|
||||
<option value="original">Apple ][</option>
|
||||
<option value="apple2j">Apple ][j+</option>
|
||||
<option value="apple2lc">Apple ][+ (lowercase font)</option>
|
||||
<option value="apple2pig">Apple ][+ (pig font)</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<i>* Reload page to take effect</i>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>CPU</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="Apple2.updateCPU()"/>
|
||||
<label for="accelerator_toggle">
|
||||
Accelerated CPU
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Joystick</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="disable_mouse"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="disable_mouse">
|
||||
Disable Mouse Joystick
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="flip_x"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="flip_x">
|
||||
Flip X-Axis
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="flip_y"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="flip_y">
|
||||
Flip Y-Axis
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="swap_x_y"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="swap_x_y">
|
||||
Swap X-Y Axis
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Monitor</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="mono_screen"
|
||||
onclick="Apple2.updateScreen()" />
|
||||
<label for="mono_screen">
|
||||
Mono Screen
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="show_scanlines"
|
||||
onclick="Apple2.updateScreen()" />
|
||||
<label for="show_scanlines">
|
||||
Show Scanlines
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="gl_canvas"
|
||||
onclick="Apple2.updateScreen()" />
|
||||
<label for="gl_canvas">
|
||||
GL Renderer (Restart required)
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Sound</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="enable_sound"
|
||||
onclick="Apple2.updateSound()" checked="checked" />
|
||||
<label for="enable_sound">
|
||||
Enable
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
<footer class="modal__footer">
|
||||
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
|
||||
|
@ -144,93 +144,6 @@
|
||||
</button>
|
||||
</header>
|
||||
<main class="modal__content" id="options-modal-content">
|
||||
<h3>Type</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<select id="computer_type2e" value="apple2enh" onchange="Apple2.updateCPU()">
|
||||
<option value="apple2enh">Enhanced Apple //e</option>
|
||||
<option value="apple2e">Apple //e</option>
|
||||
<option value="apple2rm">Enhanced Apple //e (Reactive Micro)</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<i>* Reload page to take effect</i>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>CPU</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="Apple2.updateCPU()"/>
|
||||
<label for="accelerator_toggle">
|
||||
Accelerated CPU
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Joystick</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="disable_mouse"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="disable_mouse">
|
||||
Disable Mouse Joystick
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="flip_x"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="flip_x">
|
||||
Flip X-Axis
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="flip_y"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="flip_y">
|
||||
Flip Y-Axis
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="swap_x_y"
|
||||
onclick="Apple2.updateJoystick()" />
|
||||
<label for="swap_x_y">
|
||||
Swap X-Y Axis
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Monitor</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="mono_screen"
|
||||
onclick="Apple2.updateScreen()" />
|
||||
<label for="mono_screen">
|
||||
Mono Screen
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="show_scanlines"
|
||||
onclick="Apple2.updateScreen()" />
|
||||
<label for="show_scanlines">
|
||||
Show Scanlines
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="gl_canvas"
|
||||
onclick="Apple2.updateScreen()" />
|
||||
<label for="gl_canvas">
|
||||
GL Renderer (Restart required)
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Sound</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" id="enable_sound"
|
||||
onclick="Apple2.updateSound()" checked="checked" />
|
||||
<label for="enable_sound">
|
||||
Enable
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
<footer class="modal__footer">
|
||||
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
|
||||
|
@ -213,6 +213,7 @@ canvas {
|
||||
}
|
||||
|
||||
#screen:-webkit-full-screen {
|
||||
background-color: black;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@ -632,10 +633,6 @@ button:focus {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#textarea textarea {
|
||||
font-family: courier;
|
||||
}
|
||||
|
||||
#http_url {
|
||||
width: 500px;
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
module.exports = {
|
||||
'moduleNameMapper': {
|
||||
'^js/(.*)': '<rootDir>/js/$1',
|
||||
},
|
||||
'roots': [
|
||||
'js/',
|
||||
'test/',
|
||||
@ -10,4 +13,4 @@ module.exports = {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
};
|
||||
};
|
||||
|
17
js/canvas.ts
17
js/canvas.ts
@ -1146,6 +1146,11 @@ export class VideoModes2D implements VideoModes {
|
||||
}
|
||||
|
||||
mono(on: boolean) {
|
||||
if (on) {
|
||||
this.canvas.classList.add('mono');
|
||||
} else {
|
||||
this.canvas.classList.remove('mono');
|
||||
}
|
||||
this._grs[0].mono(on);
|
||||
this._grs[1].mono(on);
|
||||
this._hgrs[0].mono(on);
|
||||
@ -1153,6 +1158,18 @@ export class VideoModes2D implements VideoModes {
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
scanlines(on: boolean) {
|
||||
// Can't apply scanline filter to canvas
|
||||
const parent = this.canvas.parentElement;
|
||||
if (parent) {
|
||||
if (on) {
|
||||
parent.classList.add('scanlines');
|
||||
} else {
|
||||
parent.classList.remove('scanlines');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getText() {
|
||||
return this._grs[pageMode - 1].getText();
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { apple2 as _apple2 } from './main2';
|
||||
import { apple2 } from './main2';
|
||||
import * as UI from './ui/apple2';
|
||||
|
||||
export const Apple2 = UI;
|
||||
export const apple2 = _apple2;
|
||||
export const Apple2 = { ...UI, apple2 };
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { apple2 as _apple2 } from './main2e';
|
||||
import { apple2 } from './main2e';
|
||||
import * as UI from './ui/apple2';
|
||||
|
||||
export const Apple2 = UI;
|
||||
export const apple2 = _apple2;
|
||||
export const Apple2 = { ...UI, apple2 };
|
||||
|
11
js/gl.ts
11
js/gl.ts
@ -602,6 +602,7 @@ export class VideoModesGL implements VideoModes {
|
||||
private _sv: any;
|
||||
private _displayConfig: screenEmu.DisplayConfiguration;
|
||||
private _monoMode: boolean = false;
|
||||
private _scanlines: boolean = false;
|
||||
|
||||
public ready: Promise<void>
|
||||
|
||||
@ -631,7 +632,6 @@ export class VideoModesGL implements VideoModes {
|
||||
private defaultMonitor(): screenEmu.DisplayConfiguration {
|
||||
const config = new screenEmu.DisplayConfiguration();
|
||||
config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height);
|
||||
config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height);
|
||||
config.displayScanlineLevel = 0.5;
|
||||
config.videoWhiteOnly = true;
|
||||
config.videoSaturation = 0.8;
|
||||
@ -645,7 +645,6 @@ export class VideoModesGL implements VideoModes {
|
||||
// Values taken from openemulator/libemulation/res/library/Monitors/Apple Monitor II.xml
|
||||
const config = new screenEmu.DisplayConfiguration();
|
||||
config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height);
|
||||
config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height);
|
||||
config.videoDecoder = 'CANVAS_MONOCHROME';
|
||||
config.videoBrightness = 0.15;
|
||||
config.videoContrast = 0.8;
|
||||
@ -657,7 +656,7 @@ export class VideoModesGL implements VideoModes {
|
||||
config.displayBarrel = 0.1;
|
||||
config.displayScanlineLevel = 0.5;
|
||||
config.displayCenterLighting = 0.5;
|
||||
config.displayLuninanceGain = 1.5;
|
||||
config.displayLuminanceGain = 1.5;
|
||||
return config;
|
||||
}
|
||||
|
||||
@ -671,6 +670,7 @@ export class VideoModesGL implements VideoModes {
|
||||
|
||||
if (this._displayConfig) {
|
||||
this._displayConfig.videoWhiteOnly = textMode || this._monoMode;
|
||||
this._displayConfig.displayScanlineLevel = this._scanlines ? 0.5 : 0;
|
||||
this._sv.displayConfiguration = this._displayConfig;
|
||||
}
|
||||
}
|
||||
@ -881,6 +881,11 @@ export class VideoModesGL implements VideoModes {
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
scanlines(on: boolean) {
|
||||
this._scanlines = on;
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
getText() {
|
||||
return this._grs[pageMode - 1].getText();
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ import OriginalROM from './roms/original';
|
||||
|
||||
import { Apple2 } from './apple2';
|
||||
|
||||
export * from './ui/apple2';
|
||||
|
||||
var prefs = new Prefs();
|
||||
var romVersion = prefs.readPref('computer_type2');
|
||||
var rom;
|
||||
|
@ -18,8 +18,6 @@ import Apple2eEnhancedROM from './roms/apple2enh';
|
||||
|
||||
import { Apple2 } from './apple2';
|
||||
|
||||
export * from './ui/apple2';
|
||||
|
||||
var prefs = new Prefs();
|
||||
var romVersion = prefs.readPref('computer_type2e');
|
||||
var enhanced = false;
|
||||
|
@ -17,6 +17,8 @@ export default class Prefs {
|
||||
return havePrefs;
|
||||
}
|
||||
|
||||
readPref(name: string): string | null
|
||||
readPref(name: string, defaultValue: string): string
|
||||
readPref(name: string, defaultValue: string | null = null) {
|
||||
if (havePrefs) {
|
||||
return window.localStorage.getItem(name) ?? defaultValue;
|
||||
|
294
js/ui/apple2.ts
294
js/ui/apple2.ts
@ -1,10 +1,10 @@
|
||||
import MicroModal from 'micromodal';
|
||||
|
||||
import { base64_json_parse, base64_json_stringify } from '../base64';
|
||||
import Audio from './audio';
|
||||
import { Audio, SOUND_ENABLED_OPTION } from './audio';
|
||||
import DriveLights from './drive_lights';
|
||||
import { byte, DISK_FORMATS, includes, word } from '../types';
|
||||
import { gamepad, configGamepad, initGamepad, GamepadConfiguration } from './gamepad';
|
||||
import { initGamepad, GamepadConfiguration } from './gamepad';
|
||||
import KeyBoard from './keyboard';
|
||||
import Tape, { TAPE_TYPES } from './tape';
|
||||
|
||||
@ -12,7 +12,6 @@ import ApplesoftDump from '../applesoft/decompiler';
|
||||
import ApplesoftCompiler from '../applesoft/compiler';
|
||||
|
||||
import { debug, gup, hup } from '../util';
|
||||
import Prefs from '../prefs';
|
||||
import { Apple2, Stats } from '../apple2';
|
||||
import DiskII, { DriveNumber, DRIVE_NUMBERS } from '../cards/disk2';
|
||||
import SmartPort from '../cards/smartport';
|
||||
@ -22,9 +21,13 @@ import Apple2IO from '../apple2io';
|
||||
import { JSONDisk } from '../formats/format_utils';
|
||||
import Printer from './printer';
|
||||
|
||||
import { OptionsModal } from './options_modal';
|
||||
import { Screen } from './screen';
|
||||
import { JoyStick } from './joystick';
|
||||
import { System } from './system';
|
||||
|
||||
let paused = false;
|
||||
|
||||
let focused = false;
|
||||
let startTime = Date.now();
|
||||
let lastCycles = 0;
|
||||
let lastFrames = 0;
|
||||
@ -32,6 +35,8 @@ let lastRenderedFrames = 0;
|
||||
|
||||
let hashtag = document.location.hash;
|
||||
|
||||
const optionsModal = new OptionsModal();
|
||||
|
||||
interface DiskDescriptor {
|
||||
name: string;
|
||||
disk?: number;
|
||||
@ -60,6 +65,9 @@ let _disk2: DiskII;
|
||||
let _smartPort: SmartPort;
|
||||
let _printer: Printer;
|
||||
let audio: Audio;
|
||||
let screen: Screen;
|
||||
let joystick: JoyStick;
|
||||
let system: System;
|
||||
let keyboard: KeyBoard;
|
||||
let io: Apple2IO;
|
||||
let _currentDrive: DriveNumber = 1;
|
||||
@ -350,13 +358,11 @@ function doLoadLocalDisk(drive: DriveNumber, file: File) {
|
||||
|
||||
if (result.byteLength >= 800 * 1024) {
|
||||
if (_smartPort.setBinary(drive, name, ext, result)) {
|
||||
focused = false;
|
||||
initGamepad();
|
||||
}
|
||||
} else {
|
||||
if (includes(DISK_FORMATS, ext)
|
||||
&& _disk2.setBinary(drive, name, ext, result)) {
|
||||
focused = false;
|
||||
initGamepad();
|
||||
} else {
|
||||
openAlert(`Unable to load ${name}`);
|
||||
@ -441,7 +447,6 @@ function openManage() {
|
||||
MicroModal.show('manage-modal');
|
||||
}
|
||||
|
||||
const prefs = new Prefs();
|
||||
let showStats = 0;
|
||||
|
||||
export function updateKHz() {
|
||||
@ -483,11 +488,18 @@ export function toggleShowFPS() {
|
||||
showStats = ++showStats % 3;
|
||||
}
|
||||
|
||||
export function updateSound() {
|
||||
const soundCheckbox = document.querySelector<HTMLInputElement>('#enable_sound')!;
|
||||
const on = soundCheckbox.checked;
|
||||
export function toggleSound() {
|
||||
const on = !audio.isEnabled();
|
||||
optionsModal.setOption(SOUND_ENABLED_OPTION, on);
|
||||
updateSoundButton(on);
|
||||
}
|
||||
|
||||
function initSoundToggle() {
|
||||
updateSoundButton(audio.isEnabled());
|
||||
}
|
||||
|
||||
function updateSoundButton(on: boolean) {
|
||||
const label = document.querySelector<HTMLDivElement>('#toggle-sound i')!;
|
||||
audio.enable(on);
|
||||
if (on) {
|
||||
label.classList.remove('fa-volume-off');
|
||||
label.classList.add('fa-volume-up');
|
||||
@ -651,7 +663,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
let oldcat = '';
|
||||
let oldCat = '';
|
||||
let option;
|
||||
for (let idx = 0; idx < window.disk_index.length; idx++) {
|
||||
const file = window.disk_index[idx];
|
||||
@ -661,14 +673,14 @@ for (let idx = 0; idx < window.disk_index.length; idx++) {
|
||||
if (file.e && !window.e) {
|
||||
continue;
|
||||
}
|
||||
if (cat != oldcat) {
|
||||
if (cat != oldCat) {
|
||||
option = document.createElement('option');
|
||||
option.value = cat;
|
||||
option.innerText = cat;
|
||||
categorySelect.append(option);
|
||||
|
||||
disk_categories[cat] = [];
|
||||
oldcat = cat;
|
||||
oldCat = cat;
|
||||
}
|
||||
disk_categories[cat].push(file);
|
||||
if (disk) {
|
||||
@ -711,128 +723,6 @@ function processHash(hash: string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Keyboard/Gamepad routines
|
||||
*/
|
||||
declare global {
|
||||
interface Document {
|
||||
webkitCancelFullScreen: () => void;
|
||||
webkitIsFullScreen: boolean;
|
||||
mozCancelFullScreen: () => void;
|
||||
mozIsFullScreen: boolean;
|
||||
}
|
||||
interface Element {
|
||||
webkitRequestFullScreen: (options?: any) => void;
|
||||
mozRequestFullScreen: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _keydown(evt: KeyboardEvent) {
|
||||
if (!focused && (!evt.metaKey || evt.ctrlKey || window.e)) {
|
||||
evt.preventDefault();
|
||||
|
||||
const key = keyboard.mapKeyEvent(evt);
|
||||
if (key !== 0xff) {
|
||||
io.keyDown(key);
|
||||
}
|
||||
}
|
||||
if (evt.keyCode === 112) { // F1 - Reset
|
||||
cpu.reset();
|
||||
evt.preventDefault(); // prevent launching help
|
||||
} else if (evt.keyCode === 113) { // F2 - Full Screen
|
||||
const elem = document.getElementById('screen')!;
|
||||
if (evt.shiftKey) { // Full window, but not full screen
|
||||
document.body.classList.toggle('full-page');
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
if (document.webkitIsFullScreen) {
|
||||
document.webkitCancelFullScreen();
|
||||
} else {
|
||||
const allowKeyboardInput = (Element as any).ALLOW_KEYBOARD_INPUT;
|
||||
if (allowKeyboardInput) {
|
||||
elem.webkitRequestFullScreen(allowKeyboardInput);
|
||||
} else {
|
||||
elem.webkitRequestFullScreen();
|
||||
}
|
||||
}
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
if (document.mozIsFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else {
|
||||
elem.mozRequestFullScreen();
|
||||
}
|
||||
}
|
||||
} else if (evt.keyCode === 114) { // F3
|
||||
io.keyDown(0x1b);
|
||||
} else if (evt.keyCode === 117) { // F6 Quick Save
|
||||
window.localStorage.state = base64_json_stringify(_apple2.getState());
|
||||
} else if (evt.keyCode === 120) { // F9 Quick Restore
|
||||
if (window.localStorage.state) {
|
||||
_apple2.setState(base64_json_parse(window.localStorage.state));
|
||||
}
|
||||
} else if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(true);
|
||||
} else if (evt.keyCode == 20) { // Caps lock
|
||||
keyboard.capslockKey();
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(true);
|
||||
} else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
|
||||
keyboard.commandKey(true);
|
||||
} else if (evt.keyCode == 18) { // Alt
|
||||
if (evt.location == 1) {
|
||||
keyboard.commandKey(true);
|
||||
} else {
|
||||
keyboard.optionKey(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _keyup(evt: KeyboardEvent) {
|
||||
if (!focused)
|
||||
io.keyUp();
|
||||
|
||||
if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(false);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(false);
|
||||
} else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
|
||||
keyboard.commandKey(false);
|
||||
} else if (evt.keyCode == 18) { // Alt
|
||||
if (evt.location == 1) {
|
||||
keyboard.commandKey(false);
|
||||
} else {
|
||||
keyboard.optionKey(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function updateScreen() {
|
||||
const mono = document.querySelector<HTMLInputElement>('#mono_screen')!.checked;
|
||||
const scanlines = document.querySelector<HTMLInputElement>('#show_scanlines')!.checked;
|
||||
const gl = document.querySelector<HTMLInputElement>('#gl_canvas')!.checked;
|
||||
|
||||
const screen = document.querySelector<HTMLDivElement>('#screen')!;
|
||||
const overscan = document.querySelector<HTMLDivElement>('.overscan')!;
|
||||
if (scanlines && !gl) {
|
||||
overscan.classList.add('scanlines');
|
||||
} else {
|
||||
overscan.classList.remove('scanlines');
|
||||
}
|
||||
if (mono && !gl) {
|
||||
screen.classList.add('mono');
|
||||
} else {
|
||||
screen.classList.remove('mono');
|
||||
}
|
||||
vm.mono(mono);
|
||||
}
|
||||
|
||||
export function updateCPU() {
|
||||
const accelerated = document.querySelector<HTMLInputElement>('#accelerator_toggle')!.checked;
|
||||
const kHz = accelerated ? 4092 : 1023;
|
||||
io.updateKHz(kHz);
|
||||
}
|
||||
|
||||
export function updateUI() {
|
||||
if (document.location.hash != hashtag) {
|
||||
hashtag = document.location.hash;
|
||||
@ -843,45 +733,6 @@ export function updateUI() {
|
||||
}
|
||||
}
|
||||
|
||||
let disableMouseJoystick = false;
|
||||
let flipX = false;
|
||||
let flipY = false;
|
||||
let swapXY = false;
|
||||
|
||||
export function updateJoystick() {
|
||||
disableMouseJoystick = document.querySelector<HTMLInputElement>('#disable_mouse')!.checked;
|
||||
flipX = document.querySelector<HTMLInputElement>('#flip_x')!.checked;
|
||||
flipY = document.querySelector<HTMLInputElement>('#flip_y')!.checked;
|
||||
swapXY = document.querySelector<HTMLInputElement>('#swap_x_y')!.checked;
|
||||
configGamepad(flipX, flipY);
|
||||
|
||||
if (disableMouseJoystick) {
|
||||
io.paddle(0, 0.5);
|
||||
io.paddle(1, 0.5);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function _mousemove(evt: MouseEvent) {
|
||||
if (gamepad || disableMouseJoystick) {
|
||||
return;
|
||||
}
|
||||
|
||||
const s = document.querySelector<HTMLDivElement>('#screen')!;
|
||||
const offset = s.getBoundingClientRect();
|
||||
let x = (evt.pageX - offset.left) / s.clientWidth;
|
||||
let y = (evt.pageY - offset.top) / s.clientHeight;
|
||||
const z = x;
|
||||
|
||||
if (swapXY) {
|
||||
x = y;
|
||||
y = z;
|
||||
}
|
||||
|
||||
io.paddle(0, flipX ? 1 - x : x);
|
||||
io.paddle(1, flipY ? 1 - y : y);
|
||||
}
|
||||
|
||||
export function pauseRun() {
|
||||
const label = document.querySelector<HTMLElement>('#pause-run i')!;
|
||||
if (paused) {
|
||||
@ -898,14 +749,8 @@ export function pauseRun() {
|
||||
paused = !paused;
|
||||
}
|
||||
|
||||
export function toggleSound() {
|
||||
const enableSound = document.querySelector<HTMLInputElement>('#enable_sound')!;
|
||||
enableSound.checked = !enableSound.checked;
|
||||
updateSound();
|
||||
}
|
||||
|
||||
export function openOptions() {
|
||||
MicroModal.show('options-modal');
|
||||
optionsModal.openModal();
|
||||
}
|
||||
|
||||
export function openPrinterModal() {
|
||||
@ -946,25 +791,39 @@ function onLoaded(apple2: Apple2, disk2: DiskII, smartPort: SmartPort, printer:
|
||||
_smartPort = smartPort;
|
||||
_printer = printer;
|
||||
|
||||
keyboard = new KeyBoard(cpu, io, e);
|
||||
keyboard.create('#keyboard');
|
||||
system = new System(io, e);
|
||||
optionsModal.addOptions(system);
|
||||
|
||||
joystick = new JoyStick(io);
|
||||
optionsModal.addOptions(joystick);
|
||||
|
||||
screen = new Screen(vm);
|
||||
optionsModal.addOptions(screen);
|
||||
|
||||
audio = new Audio(io);
|
||||
optionsModal.addOptions(audio);
|
||||
initSoundToggle();
|
||||
|
||||
MicroModal.init();
|
||||
|
||||
keyboard = new KeyBoard(cpu, io, e);
|
||||
keyboard.create('#keyboard');
|
||||
keyboard.setFunction('F1', () => cpu.reset());
|
||||
keyboard.setFunction('F2', screen.enterFullScreen);
|
||||
keyboard.setFunction('F3', () => io.keyDown(0x1b)); // Escape
|
||||
keyboard.setFunction('F6', () => {
|
||||
window.localStorage.state = base64_json_stringify(_apple2.getState());
|
||||
});
|
||||
keyboard.setFunction('F9', () => {
|
||||
if (window.localStorage.state) {
|
||||
_apple2.setState(base64_json_parse(window.localStorage.state));
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Input Handling
|
||||
*/
|
||||
|
||||
window.addEventListener('keydown', _keydown);
|
||||
window.addEventListener('keyup', _keyup);
|
||||
|
||||
window.addEventListener('keydown', audio.autoStart);
|
||||
if (window.ontouchstart !== undefined) {
|
||||
window.addEventListener('touchstart', audio.autoStart);
|
||||
}
|
||||
window.addEventListener('mousedown', audio.autoStart);
|
||||
|
||||
window.addEventListener('paste', (event: Event) => {
|
||||
const paste = (event.clipboardData || window.clipboardData)!.getData('text');
|
||||
io.setKeyBuffer(paste);
|
||||
@ -976,61 +835,12 @@ function onLoaded(apple2: Apple2, disk2: DiskII, smartPort: SmartPort, printer:
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
document.querySelectorAll('canvas').forEach(function (canvas) {
|
||||
canvas.addEventListener('mousedown', function (evt) {
|
||||
if (!gamepad) {
|
||||
io.buttonDown(evt.which == 1 ? 0 : 1);
|
||||
}
|
||||
evt.preventDefault();
|
||||
});
|
||||
canvas.addEventListener('mouseup', function (evt) {
|
||||
if (!gamepad) {
|
||||
io.buttonUp(evt.which == 1 ? 0 : 1);
|
||||
}
|
||||
});
|
||||
canvas.addEventListener('contextmenu', function (evt) {
|
||||
evt.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
document.body.addEventListener('mousemove', _mousemove);
|
||||
|
||||
document.querySelectorAll('input,textarea').forEach(function (input) {
|
||||
input.addEventListener('focus', function () { focused = true; });
|
||||
input.addEventListener('blur', function () { focused = false; });
|
||||
});
|
||||
|
||||
if (prefs.havePrefs()) {
|
||||
document.querySelectorAll<HTMLInputElement>('#options-modal input[type=checkbox]').forEach(function (el) {
|
||||
const val = prefs.readPref(el.id);
|
||||
if (val) {
|
||||
el.checked = JSON.parse(val);
|
||||
}
|
||||
el.addEventListener('change', function () {
|
||||
prefs.writePref(el.id, JSON.stringify(el.checked));
|
||||
});
|
||||
});
|
||||
document.querySelectorAll<HTMLSelectElement>('#options-modal select').forEach(function (el) {
|
||||
const val = prefs.readPref(el.id);
|
||||
if (val) {
|
||||
el.value = val;
|
||||
}
|
||||
el.addEventListener('change', function () {
|
||||
prefs.writePref(el.id, el.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (navigator.standalone) {
|
||||
document.body.classList.add('standalone');
|
||||
}
|
||||
|
||||
cpu.reset();
|
||||
setInterval(updateKHz, 1000);
|
||||
updateSound();
|
||||
updateScreen();
|
||||
updateCPU();
|
||||
initGamepad();
|
||||
|
||||
// Check for disks in hashtag
|
||||
|
@ -9,6 +9,7 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import { BOOLEAN_OPTION, OptionHandler } from './options_modal';
|
||||
import Apple2IO from '../apple2io';
|
||||
import { debug } from '../util';
|
||||
|
||||
@ -19,9 +20,17 @@ import { debug } from '../util';
|
||||
const SAMPLE_SIZE = 1024;
|
||||
const SAMPLE_RATE = 44000;
|
||||
|
||||
const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
|
||||
export const SOUND_ENABLED_OPTION = 'enable_sound';
|
||||
|
||||
export default class Audio {
|
||||
declare global {
|
||||
interface Window {
|
||||
webkitAudioContext: AudioContext;
|
||||
}
|
||||
}
|
||||
|
||||
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
|
||||
export class Audio implements OptionHandler {
|
||||
private sound = true;
|
||||
private samples: number[][] = [];
|
||||
|
||||
@ -66,6 +75,13 @@ export default class Audio {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', this.autoStart);
|
||||
if (window.ontouchstart !== undefined) {
|
||||
window.addEventListener('touchstart', this.autoStart);
|
||||
}
|
||||
window.addEventListener('mousedown', this.autoStart);
|
||||
|
||||
debug('Sound initialized');
|
||||
}
|
||||
|
||||
@ -85,7 +101,30 @@ export default class Audio {
|
||||
}
|
||||
}
|
||||
|
||||
enable(enable: boolean) {
|
||||
this.sound = enable;
|
||||
isEnabled() {
|
||||
return this.sound;
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'Audio',
|
||||
options: [
|
||||
{
|
||||
name: SOUND_ENABLED_OPTION,
|
||||
label: 'Enabled',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
setOption(name: string, value: boolean) {
|
||||
switch (name) {
|
||||
case SOUND_ENABLED_OPTION:
|
||||
this.sound = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
104
js/ui/joystick.ts
Normal file
104
js/ui/joystick.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import Apple2IO from '../apple2io';
|
||||
import { BOOLEAN_OPTION, OptionHandler } from './options_modal';
|
||||
|
||||
const JOYSTICK_DISABLE = 'disable_mouse';
|
||||
const JOYSTICK_FLIP_X_AXIS = 'flip_x';
|
||||
const JOYSTICK_FLIP_Y_AXIS = 'flip_y';
|
||||
const JOYSTICK_SWAP_AXIS = 'swap_x_y';
|
||||
|
||||
export class JoyStick implements OptionHandler {
|
||||
private disableMouseJoystick = false;
|
||||
private flipX = false;
|
||||
private flipY = false;
|
||||
private swapXY = false;
|
||||
private gamepad = false;
|
||||
|
||||
constructor(private io: Apple2IO) {
|
||||
document.addEventListener('mousemove', this.mousemove);
|
||||
document.querySelectorAll('canvas').forEach((canvas) => {
|
||||
canvas.addEventListener('mousedown', (evt) => {
|
||||
if (!this.gamepad) {
|
||||
io.buttonDown(evt.which == 1 ? 0 : 1);
|
||||
}
|
||||
evt.preventDefault();
|
||||
});
|
||||
canvas.addEventListener('mouseup', (evt) => {
|
||||
if (!this.gamepad) {
|
||||
io.buttonUp(evt.which == 1 ? 0 : 1);
|
||||
}
|
||||
});
|
||||
canvas.addEventListener('contextmenu', (evt) => {
|
||||
evt.preventDefault();
|
||||
});
|
||||
});
|
||||
window.addEventListener('gamepadconnected', (e: GamepadEvent) => {
|
||||
this.gamepad = !!e.gamepad;
|
||||
});
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'Joystick',
|
||||
options: [
|
||||
{
|
||||
name: JOYSTICK_DISABLE,
|
||||
label: 'Disable Mouse Joystick',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: false,
|
||||
},
|
||||
{
|
||||
name: JOYSTICK_FLIP_X_AXIS,
|
||||
label: 'Flip X-Axis',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: false,
|
||||
},
|
||||
{
|
||||
name: JOYSTICK_FLIP_Y_AXIS,
|
||||
label: 'Flip Y-Axis',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: false,
|
||||
},
|
||||
{
|
||||
name: JOYSTICK_SWAP_AXIS,
|
||||
label: 'Swap Axis',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setOption(name: string, _value: boolean) {
|
||||
switch (name) {
|
||||
case JOYSTICK_DISABLE:
|
||||
this.io.paddle(0, 0.5);
|
||||
this.io.paddle(1, 0.5);
|
||||
break;
|
||||
case JOYSTICK_FLIP_X_AXIS:
|
||||
case JOYSTICK_FLIP_Y_AXIS:
|
||||
case JOYSTICK_SWAP_AXIS:
|
||||
}
|
||||
}
|
||||
|
||||
private mousemove = (evt: MouseEvent) => {
|
||||
if (this.gamepad || this.disableMouseJoystick) {
|
||||
return;
|
||||
}
|
||||
|
||||
const s = document.querySelector<HTMLDivElement>('#screen')!;
|
||||
const offset = s.getBoundingClientRect();
|
||||
let x = (evt.pageX - offset.left) / s.clientWidth;
|
||||
let y = (evt.pageY - offset.top) / s.clientHeight;
|
||||
const z = x;
|
||||
|
||||
if (this.swapXY) {
|
||||
x = y;
|
||||
y = z;
|
||||
}
|
||||
|
||||
this.io.paddle(0, this.flipX ? 1 - x : x);
|
||||
this.io.paddle(1, this.flipY ? 1 - y : y);
|
||||
}
|
||||
}
|
@ -213,6 +213,8 @@ type Key2e = DeepMemberOf<typeof keys2e>;
|
||||
|
||||
type Key = Key2 | Key2e;
|
||||
|
||||
type KeyFunction = (key: KeyboardEvent) => void
|
||||
|
||||
export default class KeyBoard {
|
||||
private kb: HTMLElement;
|
||||
private keys;
|
||||
@ -227,8 +229,17 @@ export default class KeyBoard {
|
||||
private optioned = false;
|
||||
private commanded = false;
|
||||
|
||||
private functions: Record<string, KeyFunction> = {};
|
||||
|
||||
constructor(private cpu: CPU6502, private io: Apple2IO, private e: boolean) {
|
||||
this.keys = e ? keys2e : keys2;
|
||||
|
||||
window.addEventListener('keydown', this.keydown);
|
||||
window.addEventListener('keyup', this.keyup);
|
||||
}
|
||||
|
||||
setFunction(key: string, fn: KeyFunction) {
|
||||
this.functions[key] = fn;
|
||||
}
|
||||
|
||||
mapKeyEvent(evt: KeyboardEvent) {
|
||||
@ -497,7 +508,65 @@ export default class KeyBoard {
|
||||
};
|
||||
}
|
||||
|
||||
private dialogOpen() {
|
||||
return !!document.querySelector('.modal.is-open');
|
||||
}
|
||||
|
||||
private genMouseUp(target: HTMLElement) {
|
||||
return () => target.classList.remove('pressed');
|
||||
}
|
||||
|
||||
private keydown = (evt: KeyboardEvent) => {
|
||||
if (!this.dialogOpen() && (!evt.metaKey || evt.ctrlKey || this.e)) {
|
||||
evt.preventDefault();
|
||||
|
||||
const key = this.mapKeyEvent(evt);
|
||||
if (key !== 0xff) {
|
||||
this.io.keyDown(key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.key === 'Shift') {
|
||||
this.shiftKey(true);
|
||||
} else if (evt.key === 'CapsLock') {
|
||||
this.capslockKey();
|
||||
} else if (evt.key === 'Control') {
|
||||
this.controlKey(true);
|
||||
} else if (evt.key === 'Meta') { // AKA Command
|
||||
this.commandKey(true);
|
||||
} else if (evt.key === 'Alt') {
|
||||
if (evt.location === 1) {
|
||||
this.commandKey(true);
|
||||
} else {
|
||||
this.optionKey(true);
|
||||
}
|
||||
} else {
|
||||
if (evt.key in this.functions) {
|
||||
this.functions[evt.key](evt);
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private keyup = (evt: KeyboardEvent) => {
|
||||
if (!this.dialogOpen()) {
|
||||
this.io.keyUp();
|
||||
}
|
||||
|
||||
if (evt.key === 'Shift') { // Shift
|
||||
this.shiftKey(false);
|
||||
} else if (evt.key === 'Control') { // Control
|
||||
this.controlKey(false);
|
||||
} else if (evt.key === 'Meta') { // AKA Command
|
||||
this.commandKey(false);
|
||||
} else if (evt.key === 'Alt') { // Alt
|
||||
if (evt.location === 1) {
|
||||
this.commandKey(false);
|
||||
} else {
|
||||
this.optionKey(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
178
js/ui/options_modal.ts
Normal file
178
js/ui/options_modal.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import Prefs from '../prefs';
|
||||
import MicroModal from 'micromodal';
|
||||
|
||||
export const BOOLEAN_OPTION = 'BOOLEAN_OPTION';
|
||||
export const SELECT_OPTION = 'SELECT_OPTION';
|
||||
|
||||
export interface Option {
|
||||
name: string
|
||||
label: string
|
||||
type: string
|
||||
defaultVal: string | boolean
|
||||
}
|
||||
|
||||
export interface BooleanOption extends Option {
|
||||
type: typeof BOOLEAN_OPTION
|
||||
defaultVal: boolean
|
||||
}
|
||||
|
||||
export interface SelectOption extends Option {
|
||||
type: typeof SELECT_OPTION
|
||||
defaultVal: string
|
||||
values: Array<{name: string, value: string}>
|
||||
}
|
||||
|
||||
export interface OptionSection {
|
||||
name: string
|
||||
options: Option[]
|
||||
}
|
||||
|
||||
export interface OptionHandler {
|
||||
getOptions: () => OptionSection[]
|
||||
setOption: (name: string, value: string | boolean) => void
|
||||
}
|
||||
|
||||
export class OptionsModal {
|
||||
private prefs: Prefs = new Prefs();
|
||||
private options: Record<string, Option> = {}
|
||||
private handlers: Record<string, OptionHandler> = {}
|
||||
private sections: OptionSection[] = []
|
||||
|
||||
construct() {
|
||||
this.prefs = new Prefs();
|
||||
}
|
||||
|
||||
addOptions(handler: OptionHandler) {
|
||||
const sections = handler.getOptions();
|
||||
for (const section of sections) {
|
||||
const { options } = section;
|
||||
for (const option of options) {
|
||||
const { name } = option;
|
||||
this.handlers[name] = handler;
|
||||
this.options[name] = option;
|
||||
const value = this.getOption(name);
|
||||
if (value != null) {
|
||||
handler.setOption(name, value);
|
||||
}
|
||||
}
|
||||
this.sections.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
getOption(name: string): string | boolean | undefined {
|
||||
const option = this.options[name];
|
||||
if (option) {
|
||||
const { name, defaultVal, type } = option;
|
||||
const stringVal = String(defaultVal);
|
||||
const prefVal = this.prefs.readPref(name, stringVal);
|
||||
switch (type) {
|
||||
case BOOLEAN_OPTION:
|
||||
return prefVal === 'true';
|
||||
default:
|
||||
return prefVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setOption(name: string, value: string | boolean) {
|
||||
if (name in this.options) {
|
||||
const handler = this.handlers[name];
|
||||
const option = this.options[name];
|
||||
this.prefs.writePref(name, String(value));
|
||||
switch (option.type) {
|
||||
case BOOLEAN_OPTION:
|
||||
handler.setOption(name, Boolean(value));
|
||||
break;
|
||||
default:
|
||||
handler.setOption(name, String(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openModal() {
|
||||
const content = document.querySelector('#options-modal-content');
|
||||
if (content) {
|
||||
content.innerHTML = '';
|
||||
for (const section of this.sections) {
|
||||
const { name, options } = section;
|
||||
|
||||
// Section header
|
||||
const header = document.createElement('h3');
|
||||
header.textContent = name;
|
||||
content.appendChild(header);
|
||||
|
||||
// Preferences
|
||||
const list = document.createElement('ul');
|
||||
for (const option of options) {
|
||||
const { name, label, defaultVal, type } = option;
|
||||
const onChange = (evt: InputEvent) => {
|
||||
if (evt.target) {
|
||||
const inputElement = evt.target as HTMLInputElement;
|
||||
switch (type) {
|
||||
case BOOLEAN_OPTION:
|
||||
this.setOption(name, inputElement.checked);
|
||||
break;
|
||||
default:
|
||||
this.setOption(name, inputElement.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const listItem = document.createElement('li');
|
||||
|
||||
let element: HTMLElement;
|
||||
switch (type) {
|
||||
case BOOLEAN_OPTION:
|
||||
{
|
||||
const inputElement = document.createElement('input');
|
||||
const checked = this.prefs.readPref(name, String(defaultVal)) === 'true';
|
||||
inputElement.setAttribute('type', 'checkbox');
|
||||
inputElement.checked = checked;
|
||||
element = inputElement;
|
||||
}
|
||||
break;
|
||||
case SELECT_OPTION:
|
||||
{
|
||||
const selectOption = option as SelectOption;
|
||||
const selectElement = document.createElement('select');
|
||||
const selected = this.prefs.readPref(name, String(defaultVal));
|
||||
for (const value of selectOption.values) {
|
||||
const optionElement = document.createElement('option');
|
||||
optionElement.value = value.value;
|
||||
optionElement.textContent = value.name;
|
||||
optionElement.selected = value.value === selected;
|
||||
selectElement.appendChild(optionElement);
|
||||
}
|
||||
element = selectElement;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
const inputElement = document.createElement('input');
|
||||
const value = this.prefs.readPref(name, String(defaultVal));
|
||||
inputElement.value = value;
|
||||
element = inputElement;
|
||||
}
|
||||
}
|
||||
element.id = name;
|
||||
element.addEventListener('change', onChange);
|
||||
listItem.appendChild(element);
|
||||
const labelElement = document.createElement('label');
|
||||
|
||||
labelElement.textContent = label;
|
||||
labelElement.setAttribute('for', name);
|
||||
listItem.appendChild(labelElement);
|
||||
|
||||
list.appendChild(listItem);
|
||||
}
|
||||
content.appendChild(list);
|
||||
}
|
||||
const reloadElement = document.createElement('i');
|
||||
reloadElement.textContent = '* Reload page to take effect';
|
||||
content.append(reloadElement);
|
||||
} else {
|
||||
console.error('Cannot find target div#options-modal-content');
|
||||
}
|
||||
MicroModal.show('options-modal');
|
||||
}
|
||||
}
|
85
js/ui/screen.ts
Normal file
85
js/ui/screen.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { VideoModes } from '../videomodes';
|
||||
import { BOOLEAN_OPTION, OptionHandler } from './options_modal';
|
||||
|
||||
const SCREEN_MONO = 'mono_screen';
|
||||
const SCREEN_SCANLINE = 'show_scanlines';
|
||||
const SCREEN_GL = 'gl_canvas';
|
||||
|
||||
declare global {
|
||||
interface Document {
|
||||
webkitCancelFullScreen: () => void;
|
||||
webkitIsFullScreen: boolean;
|
||||
mozCancelFullScreen: () => void;
|
||||
mozIsFullScreen: boolean;
|
||||
}
|
||||
interface Element {
|
||||
webkitRequestFullScreen: (options?: any) => void;
|
||||
mozRequestFullScreen: () => void;
|
||||
}
|
||||
}
|
||||
export class Screen implements OptionHandler {
|
||||
constructor(private vm: VideoModes) {}
|
||||
|
||||
enterFullScreen = (evt: KeyboardEvent) => {
|
||||
const elem = document.getElementById('screen')!;
|
||||
if (evt.shiftKey) { // Full window, but not full screen
|
||||
document.body.classList.toggle('full-page');
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
if (document.webkitIsFullScreen) {
|
||||
document.webkitCancelFullScreen();
|
||||
} else {
|
||||
const allowKeyboardInput = (Element as any).ALLOW_KEYBOARD_INPUT;
|
||||
if (allowKeyboardInput) {
|
||||
elem.webkitRequestFullScreen(allowKeyboardInput);
|
||||
} else {
|
||||
elem.webkitRequestFullScreen();
|
||||
}
|
||||
}
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
if (document.mozIsFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else {
|
||||
elem.mozRequestFullScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'Screen',
|
||||
options: [
|
||||
{
|
||||
name: SCREEN_MONO,
|
||||
label: 'Mono Screen',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: false,
|
||||
},
|
||||
{
|
||||
name: SCREEN_SCANLINE,
|
||||
label: 'Show Scanlines',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: false,
|
||||
},
|
||||
{
|
||||
name: SCREEN_GL,
|
||||
label: 'GL Renderer *',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
setOption(name: string, value: boolean) {
|
||||
switch (name) {
|
||||
case SCREEN_MONO:
|
||||
this.vm.mono(value);
|
||||
break;
|
||||
case SCREEN_SCANLINE:
|
||||
this.vm.scanlines(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
98
js/ui/system.ts
Normal file
98
js/ui/system.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { BOOLEAN_OPTION, SELECT_OPTION, OptionHandler } from './options_modal';
|
||||
import Apple2IO from '../apple2io';
|
||||
|
||||
const SYSTEM_TYPE_APPLE2E = 'computer_type2e';
|
||||
const SYSTEM_TYPE_APPLE2 = 'computer_type2';
|
||||
const SYSTEM_CPU_ACCELERATED = 'accelerator_toggle';
|
||||
|
||||
export class System implements OptionHandler {
|
||||
constructor(private io: Apple2IO, private e: boolean) {}
|
||||
|
||||
getOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'Type',
|
||||
options: this.e ? [
|
||||
{
|
||||
name: SYSTEM_TYPE_APPLE2E,
|
||||
label: ' *',
|
||||
type: SELECT_OPTION,
|
||||
defaultVal: 'apple2enh',
|
||||
values: [
|
||||
{
|
||||
value: 'apple2enh',
|
||||
name: 'Enhanced Apple //e'
|
||||
},
|
||||
{
|
||||
value: 'apple2e',
|
||||
name: 'Apple //e'
|
||||
},
|
||||
{
|
||||
value: 'apple2rm',
|
||||
name: 'Enhanced Apple //e (Reactive Micro)'
|
||||
},
|
||||
]
|
||||
}
|
||||
] : [
|
||||
{
|
||||
name: SYSTEM_TYPE_APPLE2,
|
||||
label: ' *',
|
||||
type: SELECT_OPTION,
|
||||
defaultVal: 'apple2plus',
|
||||
values: [
|
||||
{
|
||||
value: 'apple2plus',
|
||||
name: 'Apple ][+'
|
||||
},
|
||||
{
|
||||
value: 'apple2',
|
||||
name: 'Autostart Apple ]['
|
||||
},
|
||||
{
|
||||
value: 'apple213',
|
||||
name: '13 Sector Apple ]['
|
||||
},
|
||||
{
|
||||
value: 'original',
|
||||
name: 'Apple ]['
|
||||
},
|
||||
{
|
||||
value: 'apple2j',
|
||||
name: 'Apple ][j+'
|
||||
},
|
||||
{
|
||||
value: 'apple2lc',
|
||||
name: 'Apple ][+ (lowercase font)'
|
||||
},
|
||||
{
|
||||
value: 'apple2pig',
|
||||
name: 'Apple ][+ (pig font)'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}, {
|
||||
name: 'CPU',
|
||||
options: [
|
||||
{
|
||||
name: SYSTEM_CPU_ACCELERATED,
|
||||
label: 'Accelerated',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
setOption(value: string, name: string | boolean ) {
|
||||
switch (name) {
|
||||
case SYSTEM_CPU_ACCELERATED:
|
||||
{
|
||||
const kHz = value ? 4092 : 1023;
|
||||
this.io.updateKHz(kHz);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
11
js/util.ts
11
js/util.ts
@ -97,16 +97,9 @@ export function toBinary(v: byte) {
|
||||
* @param name the parameter name. Note that `name` must not have any RegExp
|
||||
* meta-characters except '[' and ']' or it will fail.
|
||||
*/
|
||||
// From http://www.netlobo.com/url_query_string_javascript.html
|
||||
export function gup(name: string) {
|
||||
name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
|
||||
const regexS = '[\\?&]' + name + '=([^&#]*)';
|
||||
const regex = new RegExp(regexS);
|
||||
const results = regex.exec(window.location.href);
|
||||
if (!results)
|
||||
return '';
|
||||
else
|
||||
return results[1];
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.get(name);
|
||||
}
|
||||
|
||||
/** Returns the URL fragment. */
|
||||
|
@ -71,6 +71,7 @@ export interface VideoModes extends Restorable<VideoModesState> {
|
||||
isText(): boolean
|
||||
|
||||
mono(on: boolean): void
|
||||
scanlines(on: boolean): void
|
||||
|
||||
getText(): string
|
||||
|
||||
|
294
package-lock.json
generated
294
package-lock.json
generated
@ -8,12 +8,14 @@
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"apple2shader": "whscullin/apple2shader#f42252d8c2ad7055e3e5f5c39e235a710804e7a6",
|
||||
"apple2shader": "whscullin/apple2shader#d365fa3b45f27890c97a2fe6ecbab6a77d02f177",
|
||||
"micromodal": "^0.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"@testing-library/dom": "^7.30.3",
|
||||
"@testing-library/user-event": "^13.1.3",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@types/micromodal": "^0.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.6.1",
|
||||
@ -1243,6 +1245,16 @@
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime-corejs3": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.10.tgz",
|
||||
"integrity": "sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"core-js-pure": "^3.0.0",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
|
||||
@ -2039,6 +2051,117 @@
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "7.30.3",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.30.3.tgz",
|
||||
"integrity": "sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^4.2.0",
|
||||
"aria-query": "^4.2.2",
|
||||
"chalk": "^4.1.0",
|
||||
"dom-accessibility-api": "^0.5.4",
|
||||
"lz-string": "^1.4.4",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/user-event": {
|
||||
"version": "13.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.3.tgz",
|
||||
"integrity": "sha512-PDTAb7CDhWzxdEFh29vnSyNFx/gigFPsVli2lgxoX+cbX6Dy2kTetB1J3dLm5TKh4d5nUWFfLpaPMoZuLLZ1Dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@testing-library/dom": ">=7.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
|
||||
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz",
|
||||
@ -2765,8 +2888,8 @@
|
||||
},
|
||||
"node_modules/apple2shader": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "git+ssh://git@github.com/whscullin/apple2shader.git#f42252d8c2ad7055e3e5f5c39e235a710804e7a6",
|
||||
"integrity": "sha512-J57fukETfkuq1nrYYH83bYLV2Nuzkaq/Q4JF8Y8w7wUtiJHDytnhxI0TD/aGLMxkXACwYXKG8zfja+rFRZfpXw==",
|
||||
"resolved": "git+ssh://git@github.com/whscullin/apple2shader.git#d365fa3b45f27890c97a2fe6ecbab6a77d02f177",
|
||||
"integrity": "sha512-RPexfw95Jk34UUHteg66rCG6GpNXySl2dD7P/WqyhJZ+7J5U9AJrIB/AxJMaBlHTyI8SZTlHvGX4r7tNCC6+qw==",
|
||||
"license": "GPL-2.0"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
@ -2778,6 +2901,19 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
|
||||
"integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"@babel/runtime-corejs3": "^7.10.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
@ -3815,6 +3951,17 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js-pure": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.10.1.tgz",
|
||||
"integrity": "sha512-PeyJH2SE0KuxY5eCGNWA+W+CeDpB6M1PN3S7Am7jSv/Ttuxz2SnWbIiVQOn/TDaGaGtxo8CRWHkXwJscbUHtVw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
@ -4227,6 +4374,12 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-accessibility-api": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz",
|
||||
"integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
|
||||
@ -8607,6 +8760,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
@ -14195,6 +14357,16 @@
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@babel/runtime-corejs3": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.10.tgz",
|
||||
"integrity": "sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js-pure": "^3.0.0",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz",
|
||||
@ -14820,6 +14992,88 @@
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"@testing-library/dom": {
|
||||
"version": "7.30.3",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.30.3.tgz",
|
||||
"integrity": "sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^4.2.0",
|
||||
"aria-query": "^4.2.2",
|
||||
"chalk": "^4.1.0",
|
||||
"dom-accessibility-api": "^0.5.4",
|
||||
"lz-string": "^1.4.4",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@testing-library/user-event": {
|
||||
"version": "13.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.3.tgz",
|
||||
"integrity": "sha512-PDTAb7CDhWzxdEFh29vnSyNFx/gigFPsVli2lgxoX+cbX6Dy2kTetB1J3dLm5TKh4d5nUWFfLpaPMoZuLLZ1Dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
}
|
||||
},
|
||||
"@types/aria-query": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
|
||||
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz",
|
||||
@ -15403,9 +15657,9 @@
|
||||
}
|
||||
},
|
||||
"apple2shader": {
|
||||
"version": "git+ssh://git@github.com/whscullin/apple2shader.git#f42252d8c2ad7055e3e5f5c39e235a710804e7a6",
|
||||
"integrity": "sha512-J57fukETfkuq1nrYYH83bYLV2Nuzkaq/Q4JF8Y8w7wUtiJHDytnhxI0TD/aGLMxkXACwYXKG8zfja+rFRZfpXw==",
|
||||
"from": "apple2shader@whscullin/apple2shader#f42252d8c2ad7055e3e5f5c39e235a710804e7a6"
|
||||
"version": "git+ssh://git@github.com/whscullin/apple2shader.git#d365fa3b45f27890c97a2fe6ecbab6a77d02f177",
|
||||
"integrity": "sha512-RPexfw95Jk34UUHteg66rCG6GpNXySl2dD7P/WqyhJZ+7J5U9AJrIB/AxJMaBlHTyI8SZTlHvGX4r7tNCC6+qw==",
|
||||
"from": "apple2shader@whscullin/apple2shader#d365fa3b45f27890c97a2fe6ecbab6a77d02f177"
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
@ -15416,6 +15670,16 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"aria-query": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
|
||||
"integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"@babel/runtime-corejs3": "^7.10.2"
|
||||
}
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
@ -16248,6 +16512,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"core-js-pure": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.10.1.tgz",
|
||||
"integrity": "sha512-PeyJH2SE0KuxY5eCGNWA+W+CeDpB6M1PN3S7Am7jSv/Ttuxz2SnWbIiVQOn/TDaGaGtxo8CRWHkXwJscbUHtVw==",
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
@ -16565,6 +16835,12 @@
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dom-accessibility-api": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz",
|
||||
"integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"domexception": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
|
||||
@ -19884,6 +20160,12 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"lz-string": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
|
||||
"dev": true
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
|
@ -26,6 +26,8 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"@testing-library/dom": "^7.30.3",
|
||||
"@testing-library/user-event": "^13.1.3",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@types/micromodal": "^0.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.6.1",
|
||||
@ -45,7 +47,7 @@
|
||||
"webpack-dev-server": "^4.0.0-beta.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"apple2shader": "whscullin/apple2shader#f42252d8c2ad7055e3e5f5c39e235a710804e7a6",
|
||||
"apple2shader": "whscullin/apple2shader#d365fa3b45f27890c97a2fe6ecbab6a77d02f177",
|
||||
"micromodal": "^0.4.2"
|
||||
}
|
||||
}
|
||||
|
64
test/js/ui/__snapshots__/options_modal.spec.ts.snap
Normal file
64
test/js/ui/__snapshots__/options_modal.spec.ts.snap
Normal file
@ -0,0 +1,64 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`OptionsModal openModal renders 1`] = `
|
||||
<div
|
||||
id="options-modal-content"
|
||||
>
|
||||
<h3>
|
||||
Section 1
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input
|
||||
id="option_1"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="option_1"
|
||||
>
|
||||
Option 1
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<select
|
||||
id="option_2"
|
||||
>
|
||||
<option
|
||||
value="select_1"
|
||||
>
|
||||
Select 1
|
||||
</option>
|
||||
<option
|
||||
value="select_2"
|
||||
>
|
||||
Select 2
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
for="option_2"
|
||||
>
|
||||
Option 2
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>
|
||||
Section 2
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input
|
||||
id="option_3"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="option_3"
|
||||
>
|
||||
Option 3
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<i>
|
||||
* Reload page to take effect
|
||||
</i>
|
||||
</div>
|
||||
`;
|
138
test/js/ui/options_modal.spec.ts
Normal file
138
test/js/ui/options_modal.spec.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import { screen } from '@testing-library/dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import {
|
||||
BOOLEAN_OPTION,
|
||||
SELECT_OPTION,
|
||||
OptionHandler,
|
||||
OptionsModal
|
||||
} from 'js/ui/options_modal';
|
||||
|
||||
jest.mock('micromodal');
|
||||
|
||||
const mockOptionHandler: OptionHandler = {
|
||||
getOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'Section 1',
|
||||
options: [
|
||||
{
|
||||
name: 'option_1',
|
||||
label: 'Option 1',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: false,
|
||||
},
|
||||
{
|
||||
name: 'option_2',
|
||||
label: 'Option 2',
|
||||
type: SELECT_OPTION,
|
||||
defaultVal: 'select_1',
|
||||
values: [
|
||||
{
|
||||
name: 'Select 1',
|
||||
value: 'select_1',
|
||||
},
|
||||
{
|
||||
name: 'Select 2',
|
||||
value: 'select_2',
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Section 2',
|
||||
options: [
|
||||
{
|
||||
name: 'option_3',
|
||||
label: 'Option 3',
|
||||
type: BOOLEAN_OPTION,
|
||||
defaultVal: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
setOption: jest.fn()
|
||||
};
|
||||
|
||||
describe('OptionsModal', () => {
|
||||
let modal: OptionsModal;
|
||||
beforeEach(() => {
|
||||
modal = new OptionsModal();
|
||||
modal.addOptions(mockOptionHandler);
|
||||
});
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('openModal', () => {
|
||||
let content: HTMLDivElement;
|
||||
|
||||
beforeEach(() => {
|
||||
content = document.createElement('div');
|
||||
content.id = 'options-modal-content';
|
||||
document.body.appendChild(content);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
content.remove();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
modal.openModal();
|
||||
expect(content).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('toggles booleans', () => {
|
||||
modal.openModal();
|
||||
const toggle = screen.getByText('Option 3');
|
||||
userEvent.click(toggle);
|
||||
expect(mockOptionHandler.setOption)
|
||||
.toHaveBeenCalledWith('option_3', false);
|
||||
});
|
||||
|
||||
it('selects', () => {
|
||||
modal.openModal();
|
||||
const combobox = screen.getByRole('combobox');
|
||||
userEvent.selectOptions(combobox, 'select_2');
|
||||
|
||||
expect(mockOptionHandler.setOption)
|
||||
.toHaveBeenCalledWith('option_2', 'select_2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOption', () => {
|
||||
beforeEach(() => {
|
||||
modal = new OptionsModal();
|
||||
modal.addOptions(mockOptionHandler);
|
||||
});
|
||||
it('gets boolean', () => {
|
||||
expect(modal.getOption('option_1'))
|
||||
.toEqual(false);
|
||||
expect(modal.getOption('option_3'))
|
||||
.toEqual(true);
|
||||
});
|
||||
|
||||
it('gets selector', () => {
|
||||
expect(modal.getOption('option_2'))
|
||||
.toEqual('select_1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOption', () => {
|
||||
it('sets boolean', () => {
|
||||
modal.setOption('option_1', true);
|
||||
expect(mockOptionHandler.setOption)
|
||||
.toHaveBeenCalledWith('option_1', true);
|
||||
});
|
||||
|
||||
it('sets selector', () => {
|
||||
modal.setOption('option_2', 'select_2');
|
||||
expect(mockOptionHandler.setOption)
|
||||
.toHaveBeenCalledWith('option_2', 'select_2');
|
||||
});
|
||||
});
|
||||
});
|
@ -18,10 +18,14 @@
|
||||
"*": [
|
||||
"node_modules/*",
|
||||
"types/*"
|
||||
],
|
||||
"js/*": [
|
||||
"js/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"js/**/*"
|
||||
"js/**/*",
|
||||
"test/**/*",
|
||||
]
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ module.exports =
|
||||
compress: true,
|
||||
static: {
|
||||
watch: {
|
||||
ignored: /(node_modules|\.git)/
|
||||
ignored: /(node_modules|test|\.git)/
|
||||
},
|
||||
directory: __dirname,
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user