2019-12-27 23:04:07 +00:00
|
|
|
import MicroModal from 'micromodal';
|
|
|
|
|
2021-02-28 03:17:36 +00:00
|
|
|
import { base64_json_parse, base64_json_stringify } from '../base64';
|
2019-12-27 23:04:07 +00:00
|
|
|
import Audio from './audio';
|
|
|
|
import DriveLights from './drive_lights';
|
2021-02-08 04:50:50 +00:00
|
|
|
import { DISK_FORMATS } from '../types';
|
2019-12-27 23:04:07 +00:00
|
|
|
import { gamepad, configGamepad, initGamepad } from './gamepad';
|
|
|
|
import KeyBoard from './keyboard';
|
|
|
|
import Tape, { TAPE_TYPES } from './tape';
|
|
|
|
|
|
|
|
import ApplesoftDump from '../applesoft/decompiler';
|
|
|
|
import ApplesoftCompiler from '../applesoft/compiler';
|
|
|
|
|
|
|
|
import { debug, gup, hup } from '../util';
|
|
|
|
import Prefs from '../prefs';
|
|
|
|
|
2020-01-02 19:11:04 +00:00
|
|
|
var paused = false;
|
|
|
|
|
2019-12-27 23:04:07 +00:00
|
|
|
var focused = false;
|
|
|
|
var startTime = Date.now();
|
|
|
|
var lastCycles = 0;
|
|
|
|
var lastFrames = 0;
|
2019-12-30 19:13:35 +00:00
|
|
|
var lastRenderedFrames = 0;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
2020-01-02 19:11:04 +00:00
|
|
|
var hashtag = document.location.hash;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
|
|
|
var disk_categories = {'Local Saves': []};
|
|
|
|
var disk_sets = {};
|
|
|
|
var disk_cur_name = [];
|
|
|
|
var disk_cur_cat = [];
|
|
|
|
|
|
|
|
var _apple2;
|
|
|
|
var cpu;
|
|
|
|
var stats;
|
|
|
|
var vm;
|
|
|
|
var tape;
|
|
|
|
var _disk2;
|
2020-09-13 02:42:18 +00:00
|
|
|
var _smartPort;
|
2020-10-11 15:48:16 +00:00
|
|
|
var _printer;
|
2019-12-27 23:04:07 +00:00
|
|
|
var audio;
|
|
|
|
var keyboard;
|
|
|
|
var io;
|
|
|
|
var _currentDrive = 1;
|
|
|
|
|
|
|
|
export const driveLights = new DriveLights();
|
|
|
|
|
|
|
|
export function dumpAppleSoftProgram() {
|
|
|
|
var dumper = new ApplesoftDump(cpu);
|
|
|
|
debug(dumper.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
export function compileAppleSoftProgram(program) {
|
|
|
|
var compiler = new ApplesoftCompiler(cpu);
|
|
|
|
compiler.compile(program);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function openLoad(drive, event) {
|
|
|
|
_currentDrive = parseInt(drive, 10);
|
|
|
|
if (event.metaKey) {
|
|
|
|
openLoadHTTP(drive);
|
|
|
|
} else {
|
|
|
|
if (disk_cur_cat[drive]) {
|
|
|
|
document.querySelector('#category_select').value = disk_cur_cat[drive];
|
|
|
|
selectCategory();
|
|
|
|
}
|
|
|
|
MicroModal.show('load-modal');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function openSave(drive, event) {
|
|
|
|
_currentDrive = parseInt(drive, 10);
|
|
|
|
|
|
|
|
var mimeType = 'application/octet-stream';
|
|
|
|
var data = _disk2.getBinary(drive);
|
|
|
|
var a = document.querySelector('#local_save_link');
|
|
|
|
|
|
|
|
var blob = new Blob([data], { 'type': mimeType });
|
|
|
|
a.href = window.URL.createObjectURL(blob);
|
|
|
|
a.download = driveLights.label(drive) + '.dsk';
|
|
|
|
|
|
|
|
if (event.metaKey) {
|
|
|
|
dumpDisk(drive);
|
|
|
|
} else {
|
|
|
|
document.querySelector('#save_name').value = driveLights.label(drive);
|
|
|
|
MicroModal.show('save-modal');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-02 19:11:04 +00:00
|
|
|
export function openAlert(msg) {
|
|
|
|
var el = document.querySelector('#alert-modal .message');
|
|
|
|
el.innerText = msg;
|
|
|
|
MicroModal.show('alert-modal');
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************
|
|
|
|
*
|
|
|
|
* Drag and Drop
|
|
|
|
*/
|
|
|
|
|
2019-12-27 23:04:07 +00:00
|
|
|
export function handleDragOver(drive, event) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.dataTransfer.dropEffect = 'copy';
|
|
|
|
}
|
|
|
|
|
|
|
|
export function handleDragEnd(drive, event) {
|
|
|
|
var dt = event.dataTransfer;
|
|
|
|
if (dt.items) {
|
|
|
|
for (var i = 0; i < dt.items.length; i++) {
|
|
|
|
dt.items.remove(i);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
event.dataTransfer.clearData();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function handleDrop(drive, event) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
if (drive < 1) {
|
|
|
|
if (!_disk2.getMetadata(1)) {
|
|
|
|
drive = 1;
|
|
|
|
} else if (!_disk2.getMetadata(2)) {
|
|
|
|
drive = 2;
|
|
|
|
} else {
|
|
|
|
drive = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var dt = event.dataTransfer;
|
|
|
|
if (dt.files.length == 1) {
|
2021-02-08 05:58:19 +00:00
|
|
|
var runOnLoad = event.shiftKey;
|
|
|
|
doLoadLocal(drive, dt.files[0], { runOnLoad });
|
2019-12-27 23:04:07 +00:00
|
|
|
} else if (dt.files.length == 2) {
|
|
|
|
doLoadLocal(1, dt.files[0]);
|
|
|
|
doLoadLocal(2, dt.files[1]);
|
|
|
|
} else {
|
|
|
|
for (var idx = 0; idx < dt.items.length; idx++) {
|
|
|
|
if (dt.items[idx].type === 'text/uri-list') {
|
|
|
|
dt.items[idx].getAsString(function(url) {
|
|
|
|
var parts = document.location.hash.split('|');
|
|
|
|
parts[drive - 1] = url;
|
|
|
|
document.location.hash = parts.join('|');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-02 19:11:04 +00:00
|
|
|
function loadingStart () {
|
|
|
|
var meter = document.querySelector('#loading-modal .meter');
|
|
|
|
meter.style.display = 'none';
|
2019-12-27 23:04:07 +00:00
|
|
|
MicroModal.show('loading-modal');
|
2020-01-02 19:11:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function loadingProgress (current, total) {
|
|
|
|
if (total) {
|
|
|
|
var meter = document.querySelector('#loading-modal .meter');
|
|
|
|
var progress = document.querySelector('#loading-modal .progress');
|
|
|
|
meter.style.display = 'block';
|
|
|
|
progress.style.width = current / total * meter.clientWidth + 'px';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadingStop () {
|
|
|
|
MicroModal.close('loading-modal');
|
|
|
|
|
|
|
|
if (!paused) {
|
2021-02-22 02:38:21 +00:00
|
|
|
vm.ready.then(() => {
|
|
|
|
_apple2.run();
|
|
|
|
});
|
2020-01-02 19:11:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function loadAjax(drive, url) {
|
|
|
|
loadingStart();
|
2019-12-27 23:04:07 +00:00
|
|
|
|
|
|
|
fetch(url).then(function(response) {
|
|
|
|
if (response.ok) {
|
|
|
|
return response.json();
|
|
|
|
} else {
|
|
|
|
throw new Error('Error loading: ' + response.statusText);
|
|
|
|
}
|
|
|
|
}).then(function(data) {
|
|
|
|
if (data.type == 'binary') {
|
|
|
|
loadBinary(drive, data);
|
2021-02-08 04:50:50 +00:00
|
|
|
} else if (DISK_FORMATS.indexOf(data.type) > -1) {
|
2019-12-27 23:04:07 +00:00
|
|
|
loadDisk(drive, data);
|
|
|
|
}
|
|
|
|
initGamepad(data.gamepad);
|
2020-01-02 19:11:04 +00:00
|
|
|
loadingStop();
|
2019-12-27 23:04:07 +00:00
|
|
|
}).catch(function(error) {
|
2020-01-02 19:11:04 +00:00
|
|
|
loadingStop();
|
|
|
|
openAlert(error.message);
|
2019-12-27 23:04:07 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function doLoad() {
|
|
|
|
MicroModal.close('load-modal');
|
|
|
|
var urls = document.querySelector('#disk_select').value, url;
|
|
|
|
if (urls && urls.length) {
|
|
|
|
if (typeof(urls) == 'string') {
|
|
|
|
url = urls;
|
|
|
|
} else {
|
|
|
|
url = urls[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var files = document.querySelector('#local_file').files;
|
|
|
|
if (files.length == 1) {
|
|
|
|
doLoadLocal(_currentDrive, files[0]);
|
|
|
|
} else if (url) {
|
|
|
|
var filename;
|
|
|
|
MicroModal.close('load-modal');
|
|
|
|
if (url.substr(0,6) == 'local:') {
|
|
|
|
filename = url.substr(6);
|
|
|
|
if (filename == '__manage') {
|
|
|
|
openManage();
|
|
|
|
} else {
|
|
|
|
loadLocalStorage(_currentDrive, filename);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var r1 = /json\/disks\/(.*).json$/.exec(url);
|
|
|
|
if (r1) {
|
|
|
|
filename = r1[1];
|
|
|
|
} else {
|
|
|
|
filename = url;
|
|
|
|
}
|
|
|
|
var parts = document.location.hash.split('|');
|
|
|
|
parts[_currentDrive - 1] = filename;
|
|
|
|
document.location.hash = parts.join('|');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function doSave() {
|
|
|
|
var name = document.querySelector('#save_name').value;
|
|
|
|
saveLocalStorage(_currentDrive, name);
|
|
|
|
MicroModal.close('save-modal');
|
2020-10-11 15:53:33 +00:00
|
|
|
window.setTimeout(() => openAlert('Saved'), 0);
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function doDelete(name) {
|
|
|
|
if (window.confirm('Delete ' + name + '?')) {
|
|
|
|
deleteLocalStorage(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 05:58:19 +00:00
|
|
|
const CIDERPRESS_EXTENSION = /#([0-9a-f]{2})([0-9a-f]{4})$/i;
|
|
|
|
const BIN_TYPES = ['bin'];
|
|
|
|
|
|
|
|
function doLoadLocal(drive, file, options = {}) {
|
2019-12-27 23:04:07 +00:00
|
|
|
var parts = file.name.split('.');
|
|
|
|
var ext = parts[parts.length - 1].toLowerCase();
|
2021-02-08 05:58:19 +00:00
|
|
|
var matches = file.name.match(CIDERPRESS_EXTENSION);
|
|
|
|
var type, aux;
|
|
|
|
if (matches && matches.length === 3) {
|
|
|
|
[, type, aux] = matches;
|
|
|
|
}
|
|
|
|
if (DISK_FORMATS.includes(ext)) {
|
2019-12-27 23:04:07 +00:00
|
|
|
doLoadLocalDisk(drive, file);
|
2021-02-08 05:58:19 +00:00
|
|
|
} else if (TAPE_TYPES.includes(ext)) {
|
2019-12-27 23:04:07 +00:00
|
|
|
tape.doLoadLocalTape(file);
|
2021-02-08 05:58:19 +00:00
|
|
|
} else if (BIN_TYPES.includes(ext) || type === '06' || options.address) {
|
|
|
|
doLoadBinary(file, { address: parseInt(aux || '2000', 16), ...options });
|
2019-12-27 23:04:07 +00:00
|
|
|
} else {
|
2020-01-02 19:11:04 +00:00
|
|
|
openAlert('Unknown file type: ' + ext);
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 05:58:19 +00:00
|
|
|
function doLoadBinary(file, options) {
|
|
|
|
loadingStart();
|
|
|
|
|
|
|
|
var fileReader = new FileReader();
|
|
|
|
fileReader.onload = function() {
|
|
|
|
let { address } = options;
|
|
|
|
const bytes = new Uint8Array(this.result);
|
|
|
|
for (let idx = 0; idx < this.result.byteLength; idx++) {
|
|
|
|
cpu.write(address >> 8, address & 0xff, bytes[idx]);
|
|
|
|
address++;
|
|
|
|
}
|
|
|
|
if (options.runOnLoad) {
|
|
|
|
cpu.reset();
|
|
|
|
cpu.setPC(options.address);
|
|
|
|
}
|
|
|
|
loadingStop();
|
|
|
|
};
|
|
|
|
fileReader.readAsArrayBuffer(file);
|
|
|
|
}
|
|
|
|
|
2019-12-27 23:04:07 +00:00
|
|
|
function doLoadLocalDisk(drive, file) {
|
2020-01-02 19:11:04 +00:00
|
|
|
loadingStart();
|
2019-12-27 23:04:07 +00:00
|
|
|
var fileReader = new FileReader();
|
|
|
|
fileReader.onload = function() {
|
|
|
|
var parts = file.name.split('.');
|
|
|
|
var ext = parts.pop().toLowerCase();
|
|
|
|
var name = parts.join('.');
|
2021-02-08 05:58:19 +00:00
|
|
|
|
|
|
|
// Remove any json file reference
|
|
|
|
var files = document.location.hash.split('|');
|
|
|
|
files[drive - 1] = '';
|
|
|
|
document.location.hash = files.join('|');
|
|
|
|
|
2020-01-02 19:11:04 +00:00
|
|
|
if (this.result.byteLength >= 800 * 1024) {
|
2020-09-13 02:42:18 +00:00
|
|
|
if (_smartPort.setBinary(drive, name, ext, this.result)) {
|
2020-04-24 02:47:44 +00:00
|
|
|
focused = false;
|
2020-01-02 19:11:04 +00:00
|
|
|
initGamepad();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (_disk2.setBinary(drive, name, ext, this.result)) {
|
2020-04-24 02:47:44 +00:00
|
|
|
focused = false;
|
2020-01-02 19:11:04 +00:00
|
|
|
initGamepad();
|
|
|
|
}
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
2020-01-02 19:11:04 +00:00
|
|
|
loadingStop();
|
2019-12-27 23:04:07 +00:00
|
|
|
};
|
|
|
|
fileReader.readAsArrayBuffer(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function doLoadHTTP(drive, _url) {
|
2020-01-02 19:11:04 +00:00
|
|
|
if (!_url) {
|
|
|
|
MicroModal.close('http-modal');
|
|
|
|
}
|
|
|
|
|
|
|
|
loadingStart();
|
2019-12-27 23:04:07 +00:00
|
|
|
var url = _url || document.querySelector('#http_url').value;
|
|
|
|
if (url) {
|
|
|
|
fetch(url).then(function(response) {
|
|
|
|
if (response.ok) {
|
2020-01-02 19:11:04 +00:00
|
|
|
var reader = response.body.getReader();
|
|
|
|
var received = 0;
|
|
|
|
var chunks = [];
|
|
|
|
var contentLength = parseInt(response.headers.get('content-length'), 10);
|
|
|
|
|
|
|
|
return reader.read().then(function readChunk(result) {
|
|
|
|
if (result.done) {
|
|
|
|
var data = new Uint8Array(received);
|
|
|
|
var offset = 0;
|
|
|
|
for (var idx = 0; idx < chunks.length; idx++) {
|
|
|
|
data.set(chunks[idx], offset);
|
|
|
|
offset += chunks[idx].length;
|
|
|
|
}
|
|
|
|
return data.buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
received += result.value.length;
|
|
|
|
if (contentLength) {
|
|
|
|
loadingProgress(received, contentLength);
|
|
|
|
}
|
|
|
|
chunks.push(result.value);
|
|
|
|
|
|
|
|
return reader.read().then(readChunk);
|
|
|
|
});
|
2019-12-27 23:04:07 +00:00
|
|
|
} else {
|
|
|
|
throw new Error('Error loading: ' + response.statusText);
|
|
|
|
}
|
|
|
|
}).then(function(data) {
|
|
|
|
var urlParts = url.split('/');
|
|
|
|
var file = urlParts.pop();
|
|
|
|
var fileParts = file.split('.');
|
|
|
|
var ext = fileParts.pop().toLowerCase();
|
|
|
|
var name = decodeURIComponent(fileParts.join('.'));
|
2020-01-02 19:11:04 +00:00
|
|
|
if (data.byteLength >= 800 * 1024) {
|
2020-09-13 02:42:18 +00:00
|
|
|
if (_smartPort.setBinary(drive, name, ext, data)) {
|
2020-01-02 19:11:04 +00:00
|
|
|
initGamepad();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (_disk2.setBinary(drive, name, ext, data)) {
|
|
|
|
initGamepad();
|
|
|
|
}
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
2020-01-02 19:11:04 +00:00
|
|
|
loadingStop();
|
2019-12-27 23:04:07 +00:00
|
|
|
}).catch(function(error) {
|
2020-01-02 19:11:04 +00:00
|
|
|
loadingStop();
|
|
|
|
openAlert(error.message);
|
2019-12-27 23:04:07 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function openLoadHTTP(drive) {
|
|
|
|
_currentDrive = parseInt(drive, 10);
|
|
|
|
MicroModal.show('http-modal');
|
|
|
|
}
|
|
|
|
|
|
|
|
function openManage() {
|
|
|
|
MicroModal.show('manage-modal');
|
|
|
|
}
|
|
|
|
|
|
|
|
var prefs = new Prefs();
|
2019-12-30 19:13:35 +00:00
|
|
|
var showStats = 0;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
|
|
|
export function updateKHz() {
|
|
|
|
var now = Date.now();
|
|
|
|
var ms = now - startTime;
|
2020-11-16 01:54:13 +00:00
|
|
|
var cycles = cpu.getCycles();
|
2019-12-27 23:04:07 +00:00
|
|
|
var delta;
|
2019-12-30 19:13:35 +00:00
|
|
|
var fps;
|
|
|
|
var khz;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
2019-12-30 19:13:35 +00:00
|
|
|
switch (showStats) {
|
2020-11-26 01:28:37 +00:00
|
|
|
case 0: {
|
|
|
|
delta = cycles - lastCycles;
|
|
|
|
khz = parseInt(delta/ms);
|
|
|
|
document.querySelector('#khz').innerText = khz + ' kHz';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: {
|
|
|
|
delta = stats.renderedFrames - lastRenderedFrames;
|
|
|
|
fps = parseInt(delta/(ms/1000), 10);
|
|
|
|
document.querySelector('#khz').innerText = fps + ' rps';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
delta = stats.frames - lastFrames;
|
|
|
|
fps = parseInt(delta/(ms/1000), 10);
|
|
|
|
document.querySelector('#khz').innerText = fps + ' fps';
|
|
|
|
}
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
startTime = now;
|
|
|
|
lastCycles = cycles;
|
2019-12-30 19:13:35 +00:00
|
|
|
lastRenderedFrames = stats.renderedFrames;
|
|
|
|
lastFrames = stats.frames;
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function toggleShowFPS() {
|
2019-12-30 19:13:35 +00:00
|
|
|
showStats = ++showStats % 3;
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function updateSound() {
|
|
|
|
var on = document.querySelector('#enable_sound').checked;
|
|
|
|
var label = document.querySelector('#toggle-sound i');
|
|
|
|
audio.enable(on);
|
|
|
|
if (on) {
|
|
|
|
label.classList.remove('fa-volume-off');
|
|
|
|
label.classList.add('fa-volume-up');
|
|
|
|
} else {
|
|
|
|
label.classList.remove('fa-volume-up');
|
|
|
|
label.classList.add('fa-volume-off');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function dumpDisk(drive) {
|
|
|
|
var wind = window.open('', '_blank');
|
|
|
|
wind.document.title = driveLights.label(drive);
|
|
|
|
wind.document.write('<pre>');
|
|
|
|
wind.document.write(_disk2.getJSON(drive, true));
|
|
|
|
wind.document.write('</pre>');
|
|
|
|
wind.document.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function reset() {
|
|
|
|
_apple2.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadBinary(bin) {
|
|
|
|
for (var idx = 0; idx < bin.length; idx++) {
|
|
|
|
var pos = bin.start + idx;
|
|
|
|
cpu.write(pos >> 8, pos & 0xff, bin.data[idx]);
|
|
|
|
}
|
|
|
|
cpu.reset();
|
|
|
|
cpu.setPC(bin.start);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function selectCategory() {
|
|
|
|
document.querySelector('#disk_select').innerHTML = '';
|
|
|
|
var cat = disk_categories[document.querySelector('#category_select').value];
|
|
|
|
if (cat) {
|
|
|
|
for (var idx = 0; idx < cat.length; idx++) {
|
|
|
|
var file = cat[idx], name = file.name;
|
|
|
|
if (file.disk) {
|
|
|
|
name += ' - ' + file.disk;
|
|
|
|
}
|
|
|
|
var option = document.createElement('option');
|
|
|
|
option.value = file.filename;
|
|
|
|
option.innerText = name;
|
|
|
|
document.querySelector('#disk_select').append(option);
|
|
|
|
if (disk_cur_name[_currentDrive] == name) {
|
|
|
|
option.selected = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function selectDisk() {
|
|
|
|
document.querySelector('#local_file').value = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
export function clickDisk() {
|
|
|
|
doLoad();
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadDisk(drive, disk) {
|
|
|
|
var name = disk.name;
|
|
|
|
var category = disk.category;
|
|
|
|
|
|
|
|
if (disk.disk) {
|
|
|
|
name += ' - ' + disk.disk;
|
|
|
|
}
|
|
|
|
|
|
|
|
disk_cur_cat[drive] = category;
|
|
|
|
disk_cur_name[drive] = name;
|
|
|
|
|
|
|
|
_disk2.setDisk(drive, disk);
|
|
|
|
initGamepad(disk.gamepad);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* LocalStorage Disk Storage
|
|
|
|
*/
|
|
|
|
|
|
|
|
function updateLocalStorage() {
|
|
|
|
var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
|
2020-03-27 03:01:24 +00:00
|
|
|
var names = Object.keys(diskIndex), cat;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
|
|
|
cat = disk_categories['Local Saves'] = [];
|
|
|
|
document.querySelector('#manage-modal-content').innerHTML = '';
|
|
|
|
|
|
|
|
names.forEach(function(name) {
|
|
|
|
cat.push({
|
|
|
|
'category': 'Local Saves',
|
|
|
|
'name': name,
|
|
|
|
'filename': 'local:' + name
|
|
|
|
});
|
|
|
|
document.querySelector('#manage-modal-content').innerHTML =
|
|
|
|
'<span class="local_save">' +
|
|
|
|
name +
|
2020-04-24 02:47:44 +00:00
|
|
|
' <a href="#" onclick="Apple2.doDelete(\'' +
|
2019-12-27 23:04:07 +00:00
|
|
|
name +
|
|
|
|
'\')">Delete</a><br /></span>';
|
|
|
|
});
|
|
|
|
cat.push({
|
|
|
|
'category': 'Local Saves',
|
|
|
|
'name': 'Manage Saves...',
|
|
|
|
'filename': 'local:__manage'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveLocalStorage(drive, name) {
|
|
|
|
var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
|
|
|
|
|
|
|
|
var json = _disk2.getJSON(drive);
|
|
|
|
diskIndex[name] = json;
|
|
|
|
|
|
|
|
window.localStorage.diskIndex = JSON.stringify(diskIndex);
|
|
|
|
|
|
|
|
driveLights.label(drive, name);
|
|
|
|
driveLights.dirty(drive, false);
|
|
|
|
updateLocalStorage();
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteLocalStorage(name) {
|
|
|
|
var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
|
|
|
|
if (diskIndex[name]) {
|
|
|
|
delete diskIndex[name];
|
2020-01-02 19:11:04 +00:00
|
|
|
openAlert('Deleted');
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
window.localStorage.diskIndex = JSON.stringify(diskIndex);
|
|
|
|
updateLocalStorage();
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadLocalStorage(drive, name) {
|
|
|
|
var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
|
|
|
|
if (diskIndex[name]) {
|
|
|
|
_disk2.setJSON(drive, diskIndex[name]);
|
|
|
|
driveLights.label(drive, name);
|
|
|
|
driveLights.dirty(drive, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (window.localStorage !== undefined) {
|
|
|
|
document.querySelectorAll('.disksave').forEach(function (el) { el.style.display = 'inline-block';});
|
|
|
|
}
|
|
|
|
|
|
|
|
var oldcat = '';
|
|
|
|
var option;
|
|
|
|
for (var idx = 0; idx < window.disk_index.length; idx++) {
|
|
|
|
var file = window.disk_index[idx];
|
|
|
|
var cat = file.category;
|
|
|
|
var name = file.name, disk = file.disk;
|
2020-06-18 09:34:31 +00:00
|
|
|
if (file.e && !window.e) {
|
2019-12-27 23:04:07 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (cat != oldcat) {
|
|
|
|
option = document.createElement('option');
|
|
|
|
option.value = cat;
|
|
|
|
option.innerText = cat;
|
|
|
|
document.querySelector('#category_select').append(option);
|
|
|
|
|
|
|
|
disk_categories[cat] = [];
|
|
|
|
oldcat = cat;
|
|
|
|
}
|
|
|
|
disk_categories[cat].push(file);
|
|
|
|
if (disk) {
|
|
|
|
if (!disk_sets[name]) {
|
|
|
|
disk_sets[name] = [];
|
|
|
|
}
|
|
|
|
disk_sets[name].push(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
option = document.createElement('option');
|
|
|
|
option.innerText = 'Local Saves';
|
|
|
|
document.querySelector('#category_select').append(option);
|
|
|
|
|
|
|
|
updateLocalStorage();
|
|
|
|
|
|
|
|
function processHash(hash) {
|
|
|
|
var files = hash.split('|');
|
|
|
|
for (var idx = 0; idx < files.length; idx++) {
|
|
|
|
var file = files[idx];
|
|
|
|
if (file.indexOf('://') > 0) {
|
|
|
|
var parts = file.split('.');
|
|
|
|
var ext = parts[parts.length - 1].toLowerCase();
|
|
|
|
if (ext == 'json') {
|
|
|
|
loadAjax(idx + 1, file);
|
|
|
|
} else {
|
|
|
|
doLoadHTTP(idx + 1, file);
|
|
|
|
}
|
2019-12-30 03:13:59 +00:00
|
|
|
} else if (file) {
|
2019-12-27 23:04:07 +00:00
|
|
|
loadAjax(idx + 1, 'json/disks/' + file + '.json');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-04 05:39:16 +00:00
|
|
|
|
2019-12-27 23:04:07 +00:00
|
|
|
/*
|
|
|
|
* Keyboard/Gamepad routines
|
|
|
|
*/
|
|
|
|
|
|
|
|
function _keydown(evt) {
|
2020-06-21 22:52:39 +00:00
|
|
|
if (!focused && (!evt.metaKey || evt.ctrlKey || window.e)) {
|
2019-12-27 23:04:07 +00:00
|
|
|
evt.preventDefault();
|
|
|
|
|
|
|
|
var key = keyboard.mapKeyEvent(evt);
|
2021-02-28 03:17:36 +00:00
|
|
|
if (key !== 0xff) {
|
2019-12-27 23:04:07 +00:00
|
|
|
io.keyDown(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (evt.keyCode === 112) { // F1 - Reset
|
|
|
|
cpu.reset();
|
|
|
|
evt.preventDefault(); // prevent launching help
|
|
|
|
} else if (evt.keyCode === 113) { // F2 - Full Screen
|
|
|
|
var elem = document.getElementById('screen');
|
|
|
|
if (evt.shiftKey) { // Full window, but not full screen
|
2019-12-30 19:40:28 +00:00
|
|
|
document.body.classList.toggle('full-page');
|
2019-12-27 23:04:07 +00:00
|
|
|
} else if (document.webkitCancelFullScreen) {
|
|
|
|
if (document.webkitIsFullScreen) {
|
|
|
|
document.webkitCancelFullScreen();
|
|
|
|
} else {
|
|
|
|
if (Element.ALLOW_KEYBOARD_INPUT) {
|
|
|
|
elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
|
|
|
|
} 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
|
2021-02-28 03:17:36 +00:00
|
|
|
window.localStorage.state = base64_json_stringify(_apple2.getState());
|
2019-12-27 23:04:07 +00:00
|
|
|
} else if (evt.keyCode === 120) { // F9 Quick Restore
|
2021-02-28 03:17:36 +00:00
|
|
|
if (window.localStorage.state) {
|
|
|
|
_apple2.setState(base64_json_parse(window.localStorage.state));
|
|
|
|
}
|
2019-12-27 23:04:07 +00:00
|
|
|
} else if (evt.keyCode == 16) { // Shift
|
|
|
|
keyboard.shiftKey(true);
|
2020-04-24 02:47:44 +00:00
|
|
|
} else if (evt.keyCode == 20) { // Caps lock
|
|
|
|
keyboard.capslockKey();
|
2019-12-27 23:04:07 +00:00
|
|
|
} 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) {
|
|
|
|
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() {
|
2021-02-22 02:38:21 +00:00
|
|
|
var mono = document.querySelector('#mono_screen').checked;
|
2019-12-27 23:04:07 +00:00
|
|
|
var scanlines = document.querySelector('#show_scanlines').checked;
|
2021-02-22 02:38:21 +00:00
|
|
|
var gl = document.querySelector('#gl_canvas').checked;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
2020-01-03 05:12:34 +00:00
|
|
|
var screen = document.querySelector('#screen');
|
|
|
|
var overscan = document.querySelector('.overscan');
|
2021-02-22 02:38:21 +00:00
|
|
|
if (scanlines && !gl) {
|
2020-01-03 05:12:34 +00:00
|
|
|
overscan.classList.add('scanlines');
|
|
|
|
} else {
|
|
|
|
overscan.classList.remove('scanlines');
|
|
|
|
}
|
2021-02-22 02:38:21 +00:00
|
|
|
if (mono && !gl) {
|
|
|
|
screen.classList.add('mono');
|
2020-01-03 05:12:34 +00:00
|
|
|
} else {
|
2021-02-22 02:38:21 +00:00
|
|
|
screen.classList.remove('mono');
|
2020-01-03 05:12:34 +00:00
|
|
|
}
|
2021-02-22 02:38:21 +00:00
|
|
|
vm.mono(mono);
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function updateCPU() {
|
|
|
|
var accelerated = document.querySelector('#accelerator_toggle').checked;
|
2019-12-30 03:13:59 +00:00
|
|
|
var kHz = accelerated ? 4092 : 1023;
|
|
|
|
io.updateKHz(kHz);
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function updateUI() {
|
|
|
|
if (document.location.hash != hashtag) {
|
|
|
|
hashtag = document.location.hash;
|
|
|
|
var hash = hup();
|
|
|
|
if (hash) {
|
|
|
|
processHash(hash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var disableMouseJoystick = false;
|
|
|
|
var flipX = false;
|
|
|
|
var flipY = false;
|
|
|
|
var swapXY = false;
|
|
|
|
|
|
|
|
export function updateJoystick() {
|
|
|
|
disableMouseJoystick = document.querySelector('#disable_mouse').checked;
|
|
|
|
flipX = document.querySelector('#flip_x').checked;
|
|
|
|
flipY = document.querySelector('#flip_y').checked;
|
|
|
|
swapXY = document.querySelector('#swap_x_y').checked;
|
|
|
|
configGamepad(flipX, flipY);
|
|
|
|
|
|
|
|
if (disableMouseJoystick) {
|
|
|
|
io.paddle(0, 0.5);
|
|
|
|
io.paddle(1, 0.5);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _mousemove(evt) {
|
|
|
|
if (gamepad || disableMouseJoystick) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var s = document.querySelector('#screen');
|
|
|
|
var offset = s.getBoundingClientRect();
|
|
|
|
var x = (evt.pageX - offset.left) / s.clientWidth,
|
|
|
|
y = (evt.pageY - offset.top) / s.clientHeight,
|
|
|
|
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() {
|
|
|
|
var label = document.querySelector('#pause-run i');
|
|
|
|
if (paused) {
|
2021-02-22 02:38:21 +00:00
|
|
|
vm.ready.then(() => {
|
|
|
|
_apple2.run();
|
|
|
|
});
|
2019-12-27 23:04:07 +00:00
|
|
|
label.classList.remove('fa-play');
|
|
|
|
label.classList.add('fa-pause');
|
|
|
|
} else {
|
2019-12-28 02:14:33 +00:00
|
|
|
_apple2.stop();
|
2019-12-27 23:04:07 +00:00
|
|
|
label.classList.remove('fa-pause');
|
|
|
|
label.classList.add('fa-play');
|
|
|
|
}
|
|
|
|
paused = !paused;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function toggleSound() {
|
|
|
|
var enableSound = document.querySelector('#enable_sound');
|
|
|
|
enableSound.checked = !enableSound.checked;
|
|
|
|
updateSound();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function openOptions() {
|
|
|
|
MicroModal.show('options-modal');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function openPrinterModal() {
|
2020-11-07 23:49:05 +00:00
|
|
|
const mimeType = 'application/octet-stream';
|
|
|
|
const data = _printer.getRawOutput();
|
|
|
|
const a = document.querySelector('#raw_printer_output');
|
2020-10-17 23:53:13 +00:00
|
|
|
|
2020-11-07 23:49:05 +00:00
|
|
|
const blob = new Blob([data], { 'type': mimeType});
|
2020-10-17 23:53:13 +00:00
|
|
|
a.href = window.URL.createObjectURL(blob);
|
|
|
|
a.download = 'raw_printer_output.bin';
|
2019-12-27 23:04:07 +00:00
|
|
|
MicroModal.show('printer-modal');
|
|
|
|
}
|
|
|
|
|
2020-10-11 15:48:16 +00:00
|
|
|
export function clearPrinterPaper() {
|
|
|
|
_printer.clear();
|
|
|
|
}
|
|
|
|
|
2021-03-16 04:08:33 +00:00
|
|
|
function onLoaded(apple2, disk2, smartPort, printer, e) {
|
2019-12-27 23:04:07 +00:00
|
|
|
_apple2 = apple2;
|
|
|
|
cpu = _apple2.getCPU();
|
|
|
|
io = _apple2.getIO();
|
|
|
|
stats = apple2.getStats();
|
|
|
|
vm = apple2.getVideoModes();
|
|
|
|
tape = new Tape(io);
|
|
|
|
_disk2 = disk2;
|
2020-09-13 02:42:18 +00:00
|
|
|
_smartPort = smartPort;
|
2020-10-11 15:48:16 +00:00
|
|
|
_printer = printer;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
|
|
|
keyboard = new KeyBoard(cpu, io, e);
|
|
|
|
keyboard.create('#keyboard');
|
|
|
|
audio = new Audio(io);
|
|
|
|
|
|
|
|
MicroModal.init();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Input Handling
|
|
|
|
*/
|
|
|
|
|
|
|
|
window.addEventListener('keydown', _keydown);
|
|
|
|
window.addEventListener('keyup', _keyup);
|
2020-01-03 05:12:34 +00:00
|
|
|
|
|
|
|
window.addEventListener('keydown', audio.autoStart);
|
|
|
|
if (window.ontouchstart !== undefined) {
|
|
|
|
window.addEventListener('touchstart', audio.autoStart);
|
|
|
|
}
|
|
|
|
window.addEventListener('mousedown', audio.autoStart);
|
2019-12-27 23:04:07 +00:00
|
|
|
|
2020-09-04 05:39:16 +00:00
|
|
|
window.addEventListener('paste', (event) => {
|
|
|
|
var paste = (event.clipboardData || window.clipboardData).getData('text');
|
|
|
|
io.setKeyBuffer(paste);
|
|
|
|
event.preventDefault();
|
|
|
|
});
|
|
|
|
|
|
|
|
window.addEventListener('copy', (event) => {
|
|
|
|
event.clipboardData.setData('text/plain', vm.getText());
|
|
|
|
event.preventDefault();
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2019-12-27 23:04:07 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
2020-01-12 19:42:44 +00:00
|
|
|
canvas.addEventListener('contextmenu', function(evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
});
|
2019-12-27 23:04:07 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
document.body.addEventListener('mousemove', _mousemove);
|
|
|
|
|
|
|
|
document.querySelectorAll('input,textarea').forEach(function(input) {
|
2020-04-24 02:47:44 +00:00
|
|
|
input.addEventListener('focus', function() { focused = true; });
|
2019-12-27 23:04:07 +00:00
|
|
|
input.addEventListener('blur', function() { focused = false; });
|
|
|
|
});
|
|
|
|
|
|
|
|
if (prefs.havePrefs()) {
|
|
|
|
document.querySelectorAll('#options-modal input[type=checkbox]').forEach(function(el) {
|
|
|
|
var 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('#options-modal select').forEach(function(el) {
|
|
|
|
var val = prefs.readPref(el.id);
|
|
|
|
if (val) {
|
|
|
|
el.value = val;
|
|
|
|
}
|
|
|
|
el.addEventListener('change', function() {
|
|
|
|
prefs.writePref(el.id, el.value);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-01-02 19:11:04 +00:00
|
|
|
if (navigator.standalone) {
|
|
|
|
document.body.classList.add('standalone');
|
|
|
|
}
|
|
|
|
|
2019-12-27 23:04:07 +00:00
|
|
|
cpu.reset();
|
|
|
|
setInterval(updateKHz, 1000);
|
|
|
|
updateSound();
|
|
|
|
updateScreen();
|
|
|
|
updateCPU();
|
|
|
|
initGamepad();
|
|
|
|
|
|
|
|
// Check for disks in hashtag
|
|
|
|
|
|
|
|
var hash = gup('disk') || hup();
|
|
|
|
if (hash) {
|
2020-01-02 19:11:04 +00:00
|
|
|
_apple2.stop();
|
2019-12-27 23:04:07 +00:00
|
|
|
processHash(hash);
|
2020-01-02 19:11:04 +00:00
|
|
|
} else {
|
2021-02-22 02:38:21 +00:00
|
|
|
vm.ready.then(() => {
|
|
|
|
_apple2.run();
|
|
|
|
});
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-16 04:08:33 +00:00
|
|
|
|
|
|
|
export function initUI(apple2, disk2, smartPort, printer, e) {
|
|
|
|
window.addEventListener('load', () => {
|
2021-03-16 13:33:32 +00:00
|
|
|
onLoaded(apple2, disk2, smartPort, printer, e);
|
2021-03-16 04:08:33 +00:00
|
|
|
});
|
|
|
|
}
|