import MicroModal from 'micromodal'; import Audio from './audio'; import DriveLights from './drive_lights'; import { DISK_FORMATS } from '../types'; 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'; var paused = false; var focused = false; var startTime = Date.now(); var lastCycles = 0; var lastFrames = 0; var lastRenderedFrames = 0; var hashtag = document.location.hash; 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; var _smartPort; var _printer; 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'); } } export function openAlert(msg) { var el = document.querySelector('#alert-modal .message'); el.innerText = msg; MicroModal.show('alert-modal'); } /******************************************************************** * * Drag and Drop */ 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) { doLoadLocal(drive, dt.files[0]); } 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('|'); }); } } } } function loadingStart () { var meter = document.querySelector('#loading-modal .meter'); meter.style.display = 'none'; MicroModal.show('loading-modal'); } 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) { _apple2.run(); } } export function loadAjax(drive, url) { loadingStart(); 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); } else if (DISK_FORMATS.indexOf(data.type) > -1) { loadDisk(drive, data); } initGamepad(data.gamepad); loadingStop(); }).catch(function(error) { loadingStop(); openAlert(error.message); }); } 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'); window.setTimeout(() => openAlert('Saved'), 0); } export function doDelete(name) { if (window.confirm('Delete ' + name + '?')) { deleteLocalStorage(name); } } function doLoadLocal(drive, file) { var parts = file.name.split('.'); var ext = parts[parts.length - 1].toLowerCase(); if (DISK_FORMATS.indexOf(ext) > -1) { doLoadLocalDisk(drive, file); } else if (TAPE_TYPES.indexOf(ext) > -1) { tape.doLoadLocalTape(file); } else { openAlert('Unknown file type: ' + ext); } } function doLoadLocalDisk(drive, file) { loadingStart(); var fileReader = new FileReader(); fileReader.onload = function() { var parts = file.name.split('.'); var ext = parts.pop().toLowerCase(); var name = parts.join('.'); if (this.result.byteLength >= 800 * 1024) { if (_smartPort.setBinary(drive, name, ext, this.result)) { driveLights.label(drive, name); focused = false; initGamepad(); } } else { if (_disk2.setBinary(drive, name, ext, this.result)) { driveLights.label(drive, name); focused = false; initGamepad(); } } loadingStop(); }; fileReader.readAsArrayBuffer(file); } export function doLoadHTTP(drive, _url) { if (!_url) { MicroModal.close('http-modal'); } loadingStart(); var url = _url || document.querySelector('#http_url').value; if (url) { fetch(url).then(function(response) { if (response.ok) { 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); }); } 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('.')); if (data.byteLength >= 800 * 1024) { if (_smartPort.setBinary(drive, name, ext, data)) { driveLights.label(drive, name); initGamepad(); } } else { if (_disk2.setBinary(drive, name, ext, data)) { driveLights.label(drive, name); initGamepad(); } } loadingStop(); }).catch(function(error) { loadingStop(); openAlert(error.message); }); } } function openLoadHTTP(drive) { _currentDrive = parseInt(drive, 10); MicroModal.show('http-modal'); } function openManage() { MicroModal.show('manage-modal'); } var prefs = new Prefs(); var showStats = 0; export function updateKHz() { var now = Date.now(); var ms = now - startTime; var cycles = cpu.getCycles(); var delta; var fps; var khz; switch (showStats) { 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'; } } startTime = now; lastCycles = cycles; lastRenderedFrames = stats.renderedFrames; lastFrames = stats.frames; } export function toggleShowFPS() { showStats = ++showStats % 3; } 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('
'); wind.document.write(_disk2.getJSON(drive, true)); wind.document.write(''); 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; driveLights.label(drive, name); _disk2.setDisk(drive, disk); initGamepad(disk.gamepad); } /* * LocalStorage Disk Storage */ function updateLocalStorage() { var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}'); var names = Object.keys(diskIndex), cat; 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 = '' + name + ' Delete