diff --git a/runtime/icons/close.svg b/runtime/icons/close.svg new file mode 100644 index 0000000..5f1267d --- /dev/null +++ b/runtime/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/runtime/jsspeccy.js b/runtime/jsspeccy.js index e2666e2..ce7be15 100644 --- a/runtime/jsspeccy.js +++ b/runtime/jsspeccy.js @@ -367,6 +367,10 @@ window.JSSpeccy = (container, opts) => { } emu.on('setAutoLoadTapes', updateAutoLoadTapesCheckbox); updateAutoLoadTapesCheckbox(); + + fileMenu.addItem('Find games...', () => { + openGameBrowser(); + }); } const machineMenu = ui.menuBar.addMenu('Machine'); @@ -476,6 +480,89 @@ window.JSSpeccy = (container, opts) => { }); } + const openGameBrowser = () => { + emu.pause(); + const body = ui.showDialog(); + body.innerHTML = ` + +
+- powered by Internet Archive
'; + const ul = resultsContainer.querySelector('ul'); + const results = data.response.docs; + results.forEach(result => { + const li = document.createElement('li'); + ul.appendChild(li); + const resultLink = document.createElement('a'); + resultLink.href = '#'; + resultLink.innerText = result.title; + const creator = document.createTextNode(' - ' + result.creator) + li.appendChild(resultLink); + li.appendChild(creator); + resultLink.addEventListener('click', (e) => { + e.preventDefault(); + fetch( + 'https://archive.org/metadata/' + result.identifier + ).then(response => response.json()).then(data => { + let chosenFilename = null; + data.files.forEach(file => { + const ext = file.name.split('.').pop().toLowerCase(); + if (ext == 'z80' || ext == 'sna' || ext == 'tap' || ext == 'tzx' || ext == 'szx') { + chosenFilename = file.name; + } + }); + if (!chosenFilename) { + alert('No loadable file found'); + } else { + const finalUrl = 'https://cors.archive.org/cors/' + result.identifier + '/' + chosenFilename; + emu.openUrl(finalUrl).catch((err) => { + alert(err); + }).then(() => { + ui.hideDialog(); + emu.start(); + }); + } + }) + }) + }) + }) + }) + input.focus(); + } + const exit = () => { emu.exit(); ui.unload(); diff --git a/runtime/ui.js b/runtime/ui.js index 5aa2518..af0d42f 100644 --- a/runtime/ui.js +++ b/runtime/ui.js @@ -1,6 +1,7 @@ import EventEmitter from 'events'; import playIcon from './icons/play.svg'; +import closeIcon from './icons/close.svg'; export class MenuBar { @@ -212,6 +213,23 @@ export class UIController extends EventEmitter { this.canvas = emulator.canvas; /* build UI elements */ + this.dialog = document.createElement('div'); + this.dialog.style.display = 'none'; + container.appendChild(this.dialog); + const dialogCloseButton = document.createElement('button'); + dialogCloseButton.innerHTML = closeIcon; + dialogCloseButton.style.float = 'right'; + dialogCloseButton.style.border = 'none'; + dialogCloseButton.firstChild.style.height = '20px'; + dialogCloseButton.firstChild.style.verticalAlign = 'middle'; + this.dialog.appendChild(dialogCloseButton); + dialogCloseButton.addEventListener('click', () => { + this.hideDialog(); + }) + this.dialogBody = document.createElement('div'); + this.dialogBody.style.clear = 'both'; + this.dialog.appendChild(this.dialogBody); + this.appContainer = document.createElement('div'); container.appendChild(this.appContainer); this.appContainer.style.position = 'relative'; @@ -391,7 +409,28 @@ export class UIController extends EventEmitter { this.toolbar.show(); } } + showDialog() { + this.dialog.style.display = 'block'; + this.dialog.style.position = 'absolute'; + this.dialog.style.backgroundColor = '#eee'; + this.dialog.style.zIndex = '100'; + this.dialog.style.width = '75%'; + this.dialog.style.height = '80%'; + this.dialog.style.left = '12%'; + this.dialog.style.top = '10%'; + this.dialog.style.overflow = 'scroll'; // TODO: less hacky scrolling that doesn't hide the close button + this.dialogBody.style.paddingLeft = '8px'; + this.dialogBody.style.paddingRight = '8px'; + this.dialogBody.style.paddingBottom = '8px'; + + return this.dialogBody; + } + hideDialog() { + this.dialog.style.display = 'none'; + this.dialogBody.innerHTML = ''; + } unload() { + this.dialog.remove(); this.appContainer.remove(); } }