2023-06-20 19:17:02 +00:00
|
|
|
class EmulatorJS {
|
|
|
|
createElement(type) {
|
|
|
|
return document.createElement(type);
|
|
|
|
}
|
2023-06-22 16:00:13 +00:00
|
|
|
addEventListener(element, listener, callback) {
|
|
|
|
const listeners = listener.split(" ");
|
|
|
|
for (let i=0; i<listeners.length; i++) {
|
|
|
|
element.addEventListener(listeners[i], callback);
|
|
|
|
this.listeners.push({cb:callback, elem:element, listener:listeners[i]});
|
|
|
|
}
|
|
|
|
}
|
2023-06-21 15:55:16 +00:00
|
|
|
downloadFile(path, cb, progressCB, notWithPath, opts) {
|
2023-06-20 21:17:49 +00:00
|
|
|
const basePath = notWithPath ? '' : this.config.dataPath;
|
2023-06-21 15:55:16 +00:00
|
|
|
path = basePath + path;
|
|
|
|
let url;
|
|
|
|
try {url=new URL(path)}catch(e){};
|
|
|
|
if ((url && ['http:', 'https:'].includes(url.protocol)) || !url) {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
if (progressCB instanceof Function) {
|
|
|
|
xhr.addEventListener('progress', (e) => {
|
|
|
|
const progress = e.total ? ' '+Math.floor(e.loaded / e.total * 100).toString()+'%' : ' '+(e.loaded/1048576).toFixed(2)+'MB';
|
|
|
|
progressCB(progress);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
xhr.onload = function() {
|
|
|
|
if (xhr.readyState === xhr.DONE) {
|
|
|
|
let data = xhr.response;
|
|
|
|
try {data=JSON.parse(data)}catch(e){}
|
|
|
|
cb({
|
|
|
|
data: data,
|
|
|
|
headers: {
|
|
|
|
"content-length": xhr.getResponseHeader('content-length'),
|
|
|
|
"content-type": xhr.getResponseHeader('content-type'),
|
|
|
|
"last-modified": xhr.getResponseHeader('last-modified')
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xhr.responseType = opts.responseType;
|
|
|
|
xhr.onerror = () => cb(-1);
|
|
|
|
xhr.open(opts.method, path, true);
|
|
|
|
xhr.send();
|
|
|
|
} else {
|
|
|
|
(async () => {
|
|
|
|
//Most commonly blob: urls. Not sure what else it could be
|
|
|
|
if (opts.method === 'HEAD') {
|
|
|
|
cb({headers:{}});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let res;
|
|
|
|
try {
|
|
|
|
res = await fetch(path);
|
|
|
|
if (opts.type && opts.type.toLowerCase() === 'arraybuffer') {
|
|
|
|
res = await res.arrayBuffer();
|
|
|
|
} else {
|
|
|
|
res = await res.text();
|
|
|
|
try {res = JSON.parse(res)} catch(e) {}
|
|
|
|
}
|
|
|
|
} catch(e) {
|
|
|
|
cb(-1);
|
|
|
|
}
|
|
|
|
if (path.startsWith('blob:')) URL.revokeObjectURL(path);
|
|
|
|
cb({
|
|
|
|
data: res,
|
|
|
|
headers: {}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}
|
2023-06-20 19:17:02 +00:00
|
|
|
}
|
|
|
|
constructor(element, config) {
|
2023-06-22 16:00:13 +00:00
|
|
|
this.debug = (window.EJS_DEBUG_XX === true);
|
2023-06-20 19:17:02 +00:00
|
|
|
this.setElements(element);
|
2023-06-21 15:55:16 +00:00
|
|
|
this.started = false;
|
2023-06-22 16:00:13 +00:00
|
|
|
this.paused = true;
|
2023-06-20 19:17:02 +00:00
|
|
|
this.listeners = [];
|
|
|
|
this.config = config;
|
2023-06-20 21:17:49 +00:00
|
|
|
this.canvas = this.createElement('canvas');
|
|
|
|
this.canvas.classList.add('ejs_canvas');
|
2023-06-21 15:55:16 +00:00
|
|
|
this.bindListeners();
|
2023-06-20 19:17:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
this.game.classList.add("ejs_game");
|
|
|
|
|
|
|
|
this.createStartButton();
|
|
|
|
|
|
|
|
console.log(this)
|
|
|
|
}
|
|
|
|
setElements(element) {
|
|
|
|
this.game = document.querySelector(element);
|
|
|
|
this.elements = {
|
|
|
|
main: this.game,
|
|
|
|
parent: this.game.parentElement
|
|
|
|
}
|
|
|
|
this.elements.parent.classList.add("ejs_parent");
|
|
|
|
}
|
|
|
|
// Start button
|
|
|
|
createStartButton() {
|
|
|
|
const button = this.createElement("div");
|
|
|
|
button.classList.add("ejs_start_button");
|
|
|
|
button.innerText = this.localization("Start Game");
|
|
|
|
this.elements.parent.appendChild(button);
|
|
|
|
this.addEventListener(button, "click", this.startButtonClicked.bind(this));
|
|
|
|
}
|
|
|
|
startButtonClicked(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.target.remove();
|
|
|
|
this.createText();
|
2023-06-20 21:17:49 +00:00
|
|
|
this.downloadGameCore();
|
2023-06-20 19:17:02 +00:00
|
|
|
}
|
|
|
|
// End start button
|
|
|
|
createText() {
|
2023-06-20 21:17:49 +00:00
|
|
|
this.textElem = this.createElement("div");
|
|
|
|
this.textElem.classList.add("ejs_loading_text");
|
|
|
|
this.textElem.innerText = this.localization("Loading...");
|
|
|
|
this.elements.parent.appendChild(this.textElem);
|
2023-06-20 19:17:02 +00:00
|
|
|
}
|
|
|
|
localization(text) {
|
|
|
|
//todo
|
|
|
|
return text;
|
|
|
|
}
|
2023-06-21 15:55:16 +00:00
|
|
|
checkCompression(data, msg) {
|
|
|
|
if (msg) {
|
|
|
|
this.textElem.innerText = msg;
|
|
|
|
}
|
|
|
|
//to be put in another file
|
2023-06-20 19:17:02 +00:00
|
|
|
function isCompressed(data) { //https://www.garykessler.net/library/file_sigs.html
|
|
|
|
//todo. Use hex instead of numbers
|
|
|
|
if ((data[0] === 80 && data[1] === 75) && ((data[2] === 3 && data[3] === 4) || (data[2] === 5 && data[3] === 6) || (data[2] === 7 && data[3] === 8))) {
|
|
|
|
return 'zip';
|
|
|
|
} else if (data[0] === 55 && data[1] === 122 && data[2] === 188 && data[3] === 175 && data[4] === 39 && data[5] === 28) {
|
|
|
|
return '7z';
|
|
|
|
} else if ((data[0] === 82 && data[1] === 97 && data[2] === 114 && data[3] === 33 && data[4] === 26 && data[5] === 7) && ((data[6] === 0) || (data[6] === 1 && data[7] == 0))) {
|
|
|
|
return 'rar';
|
|
|
|
}
|
|
|
|
}
|
2023-06-20 21:17:49 +00:00
|
|
|
const createWorker = (path) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
2023-06-21 15:55:16 +00:00
|
|
|
this.downloadFile(path, (res) => {
|
|
|
|
if (res === -1) {
|
|
|
|
this.textElem.innerText = "Error";
|
|
|
|
this.textElem.style.color = "red";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const blob = new Blob([res.data], {
|
2023-06-20 21:17:49 +00:00
|
|
|
'type': 'application/javascript'
|
|
|
|
})
|
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
|
resolve(new Worker(url));
|
2023-06-21 15:55:16 +00:00
|
|
|
}, null, false, {responseType: "arraybuffer", method: "GET"});
|
2023-06-20 21:17:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
const decompress7z = (file) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const files = {};
|
2023-06-21 15:55:16 +00:00
|
|
|
const onMessage = (data) => {
|
2023-06-20 21:17:49 +00:00
|
|
|
if (!data.data) return;
|
|
|
|
//data.data.t/ 4=progress, 2 is file, 1 is zip done
|
2023-06-21 15:55:16 +00:00
|
|
|
if (data.data.t === 4 && msg) {
|
|
|
|
const pg = data.data;
|
|
|
|
const num = Math.floor(pg.current / pg.total * 100);
|
|
|
|
if (isNaN(num)) return;
|
|
|
|
const progress = ' '+num.toString()+'%';
|
|
|
|
this.textElem.innerText = msg + progress;
|
|
|
|
}
|
2023-06-20 21:17:49 +00:00
|
|
|
if (data.data.t === 2) {
|
|
|
|
files[data.data.file] = data.data.data;
|
|
|
|
}
|
|
|
|
if (data.data.t === 1) {
|
|
|
|
resolve(files);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
createWorker('compression/extract7z.js').then((worker) => {
|
|
|
|
worker.onmessage = onMessage;
|
|
|
|
worker.postMessage(file);
|
|
|
|
//console.log(file);
|
|
|
|
})
|
|
|
|
})
|
2023-06-20 19:17:02 +00:00
|
|
|
}
|
|
|
|
async function decompressRar() {
|
|
|
|
|
|
|
|
}
|
2023-06-20 21:17:49 +00:00
|
|
|
const compression = isCompressed(data.slice(0, 10));
|
2023-06-20 19:17:02 +00:00
|
|
|
if (compression) {
|
2023-06-20 21:17:49 +00:00
|
|
|
//Need to do zip and rar still
|
|
|
|
return decompress7z(data);
|
2023-06-20 19:17:02 +00:00
|
|
|
} else {
|
2023-06-20 21:17:49 +00:00
|
|
|
return new Promise(resolve => resolve(data));
|
2023-06-20 19:17:02 +00:00
|
|
|
}
|
|
|
|
|
2023-06-20 21:17:49 +00:00
|
|
|
}
|
|
|
|
downloadGameCore() {
|
2023-06-21 15:55:16 +00:00
|
|
|
this.textElem.innerText = this.localization("Download Game Core");
|
|
|
|
this.downloadFile('cores/'+this.getCore()+'-wasm.data', (res) => {
|
|
|
|
if (res === -1) {
|
|
|
|
this.textElem.innerText = "Error";
|
|
|
|
this.textElem.style.color = "red";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.checkCompression(new Uint8Array(res.data), this.localization("Decompress Game Core")).then((data) => {
|
2023-06-20 21:17:49 +00:00
|
|
|
//console.log(data);
|
|
|
|
let js, wasm;
|
|
|
|
for (let k in data) {
|
|
|
|
if (k.endsWith(".wasm")) {
|
|
|
|
wasm = data[k];
|
|
|
|
} else if (k.endsWith(".js")) {
|
|
|
|
js = data[k];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.initGameCore(js, wasm);
|
|
|
|
});
|
2023-06-21 15:55:16 +00:00
|
|
|
}, (progress) => {
|
|
|
|
this.textElem.innerText = this.localization("Download Game Core") + progress;
|
|
|
|
}, false, {responseType: "arraybuffer", method: "GET"});
|
2023-06-20 21:17:49 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
initGameCore(js, wasm) {
|
|
|
|
this.initModule(wasm);
|
|
|
|
let script = this.createElement("script");
|
|
|
|
script.src = URL.createObjectURL(new Blob([js], {type: "application/javascript"}));
|
|
|
|
document.body.appendChild(script);
|
|
|
|
}
|
|
|
|
getCore() {
|
|
|
|
const core = this.config.system;
|
|
|
|
//switch case or an object holding this data
|
|
|
|
if (core === 'nes') {
|
|
|
|
return 'fceumm';
|
|
|
|
}
|
2023-06-20 19:17:02 +00:00
|
|
|
}
|
|
|
|
downloadRom() {
|
2023-06-22 16:00:13 +00:00
|
|
|
this.gameManager = new window.EJS_GameManager(this.Module);
|
|
|
|
|
2023-06-21 15:55:16 +00:00
|
|
|
this.textElem.innerText = this.localization("Download Game Data");
|
|
|
|
this.downloadFile(this.config.gameUrl, (res) => {
|
|
|
|
if (res === -1) {
|
|
|
|
this.textElem.innerText = "Error";
|
|
|
|
this.textElem.style.color = "red";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.checkCompression(new Uint8Array(res.data), this.localization("Decompress Game Data")).then((data) => {
|
2023-06-20 21:17:49 +00:00
|
|
|
FS.writeFile("/game", data);
|
|
|
|
this.startGame();
|
2023-06-20 19:17:02 +00:00
|
|
|
});
|
2023-06-21 15:55:16 +00:00
|
|
|
}, (progress) => {
|
|
|
|
this.textElem.innerText = this.localization("Download Game Data") + progress;
|
|
|
|
}, true, {responseType: "arraybuffer", method: "GET"});
|
2023-06-20 21:17:49 +00:00
|
|
|
}
|
|
|
|
initModule(wasmData) {
|
|
|
|
window.Module = {
|
|
|
|
'TOTAL_MEMORY': 0x10000000,
|
|
|
|
'noInitialRun': true,
|
2023-06-21 15:55:16 +00:00
|
|
|
'onRuntimeInitialized': this.downloadRom.bind(this),
|
2023-06-20 21:17:49 +00:00
|
|
|
'arguments': [],
|
|
|
|
'preRun': [],
|
|
|
|
'postRun': [],
|
|
|
|
'canvas': this.canvas,
|
2023-06-22 16:00:13 +00:00
|
|
|
'print': (msg) => {
|
|
|
|
if (this.debug) {
|
2023-06-20 21:17:49 +00:00
|
|
|
console.log(msg);
|
|
|
|
}
|
|
|
|
},
|
2023-06-22 16:00:13 +00:00
|
|
|
'printErr': (msg) => {
|
|
|
|
if (this.debug) {
|
2023-06-20 21:17:49 +00:00
|
|
|
console.log(msg);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'totalDependencies': 0,
|
|
|
|
'monitorRunDependencies': () => {},
|
|
|
|
'locateFile': function(fileName) {
|
|
|
|
console.log(fileName);
|
|
|
|
if (fileName.endsWith(".wasm")) {
|
|
|
|
return URL.createObjectURL(new Blob([wasmData], {type: "application/wasm"}));
|
|
|
|
}
|
|
|
|
},
|
2023-06-21 15:55:16 +00:00
|
|
|
'readAsync': function(a, b, c) {
|
|
|
|
console.log(a, b, c)
|
2023-06-20 21:17:49 +00:00
|
|
|
}
|
|
|
|
};
|
2023-06-22 16:00:13 +00:00
|
|
|
this.Module = window.Module;
|
2023-06-20 21:17:49 +00:00
|
|
|
}
|
|
|
|
startGame() {
|
|
|
|
this.textElem.remove();
|
|
|
|
this.textElem = null;
|
|
|
|
this.game.classList.remove("ejs_game");
|
|
|
|
this.game.appendChild(this.canvas);
|
|
|
|
const args = [];
|
2023-06-22 16:00:13 +00:00
|
|
|
if (this.debug) args.push('-v');
|
2023-06-20 21:17:49 +00:00
|
|
|
args.push('/game');
|
|
|
|
Module.callMain(args);
|
|
|
|
Module.resumeMainLoop();
|
2023-06-21 15:55:16 +00:00
|
|
|
this.started = true;
|
2023-06-22 16:00:13 +00:00
|
|
|
this.paused = false;
|
2023-06-20 21:17:49 +00:00
|
|
|
Module.setCanvasSize(800, 600);
|
|
|
|
let i=0;
|
2023-06-21 15:55:16 +00:00
|
|
|
// this needs to be fixed. Ugh.
|
2023-06-20 21:17:49 +00:00
|
|
|
let j = setInterval(function() { // some cores have a messed up screen size on load (for example - gba)
|
|
|
|
if (i>20) clearInterval(j);
|
|
|
|
i++;
|
|
|
|
Module.setCanvasSize(800, 600);
|
|
|
|
}, 100)
|
|
|
|
}
|
|
|
|
bindListeners() {
|
2023-06-21 15:55:16 +00:00
|
|
|
this.createContextMenu();
|
2023-06-22 16:00:13 +00:00
|
|
|
this.createBottomMenuBar();
|
2023-06-20 21:17:49 +00:00
|
|
|
//keyboard, etc...
|
2023-06-21 15:55:16 +00:00
|
|
|
}
|
|
|
|
createContextMenu() {
|
|
|
|
this.elements.contextmenu = this.createElement('div');
|
|
|
|
this.elements.contextmenu.classList.add("ejs_context_menu");
|
2023-06-20 21:17:49 +00:00
|
|
|
this.addEventListener(this.game, 'contextmenu', (e) => {
|
2023-06-21 15:55:16 +00:00
|
|
|
if (this.started) {
|
|
|
|
this.elements.contextmenu.style.display = "block";
|
|
|
|
this.elements.contextmenu.style.left = e.offsetX;
|
|
|
|
this.elements.contextmenu.style.top = e.offsetY;
|
|
|
|
}
|
2023-06-20 21:17:49 +00:00
|
|
|
e.preventDefault();
|
|
|
|
})
|
2023-06-22 16:00:13 +00:00
|
|
|
const hideMenu = () => {
|
|
|
|
this.elements.contextmenu.style.display = "none";
|
|
|
|
}
|
2023-06-21 15:55:16 +00:00
|
|
|
this.addEventListener(this.elements.contextmenu, 'contextmenu', (e) => e.preventDefault());
|
|
|
|
this.addEventListener(this.elements.parent, 'contextmenu', (e) => e.preventDefault());
|
2023-06-22 16:00:13 +00:00
|
|
|
this.addEventListener(this.game, 'mousedown', hideMenu);
|
|
|
|
const parent = this.createElement("ul");
|
2023-06-21 15:55:16 +00:00
|
|
|
const addButton = (title, hidden, functi0n) => {
|
2023-06-22 16:00:13 +00:00
|
|
|
//<li><a href="#" onclick="return false">'+title+'</a></li>
|
|
|
|
const li = this.createElement("li");
|
|
|
|
if (hidden) li.hidden = true;
|
|
|
|
const a = this.createElement("a");
|
2023-06-21 15:55:16 +00:00
|
|
|
if (functi0n instanceof Function) {
|
2023-06-22 17:27:21 +00:00
|
|
|
this.addEventListener(li, 'click', (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
functi0n();
|
|
|
|
});
|
2023-06-21 15:55:16 +00:00
|
|
|
}
|
2023-06-22 16:00:13 +00:00
|
|
|
a.href = "#";
|
|
|
|
a.onclick = "return false";
|
|
|
|
a.innerText = title;
|
|
|
|
li.appendChild(a);
|
|
|
|
parent.appendChild(li);
|
|
|
|
hideMenu();
|
2023-06-21 15:55:16 +00:00
|
|
|
}
|
2023-06-22 16:00:13 +00:00
|
|
|
let screenshotUrl;
|
|
|
|
addButton("Take Screenshot", false, () => {
|
|
|
|
if (screenshotUrl) URL.revokeObjectURL(screenshotUrl);
|
|
|
|
const screenshot = this.gameManager.screenshot();
|
|
|
|
const blob = new Blob([screenshot]);
|
|
|
|
screenshotUrl = URL.createObjectURL(blob);
|
|
|
|
const a = this.createElement("a");
|
|
|
|
a.href = screenshotUrl;
|
|
|
|
a.download = "screenshot.png";
|
|
|
|
a.click();
|
|
|
|
hideMenu();
|
|
|
|
});
|
|
|
|
addButton("Quick Save", false, () => {
|
|
|
|
this.gameManager.quickSave();
|
|
|
|
hideMenu();
|
|
|
|
});
|
|
|
|
addButton("Quick Load", false, () => {
|
|
|
|
this.gameManager.quickLoad();
|
|
|
|
hideMenu();
|
|
|
|
});
|
2023-06-22 17:27:21 +00:00
|
|
|
addButton("EmulatorJS", false, () => {
|
|
|
|
hideMenu();
|
|
|
|
const body = this.createPopup("EmulatorJS", {
|
|
|
|
"Close": () => {
|
|
|
|
this.closePopup();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
body.innerText = "Todo. Write about, include tabs on side with licenses, links to docs/repo/discord?";
|
|
|
|
|
|
|
|
});
|
2023-06-21 15:55:16 +00:00
|
|
|
|
2023-06-22 16:00:13 +00:00
|
|
|
this.elements.contextmenu.appendChild(parent);
|
2023-06-21 15:55:16 +00:00
|
|
|
|
|
|
|
this.elements.parent.appendChild(this.elements.contextmenu);
|
2023-06-20 19:17:02 +00:00
|
|
|
}
|
2023-06-22 17:27:21 +00:00
|
|
|
closePopup() {
|
|
|
|
if (this.currentPopup !== null) {
|
|
|
|
try {
|
|
|
|
this.currentPopup.remove();
|
|
|
|
} catch(e){}
|
|
|
|
this.currentPopup = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//creates a full box popup.
|
|
|
|
createPopup(popupTitle, buttons) {
|
|
|
|
this.closePopup();
|
|
|
|
this.currentPopup = this.createElement('div');
|
|
|
|
this.currentPopup.classList.add("ejs_popup_container");
|
|
|
|
this.elements.parent.appendChild(this.currentPopup);
|
|
|
|
const title = this.createElement("h4");
|
|
|
|
title.innerText = popupTitle;
|
|
|
|
const main = this.createElement("div");
|
|
|
|
main.classList.add("ejs_popup_body");
|
|
|
|
|
|
|
|
this.currentPopup.appendChild(title);
|
|
|
|
this.currentPopup.appendChild(main);
|
|
|
|
|
|
|
|
for (let k in buttons) {
|
|
|
|
const button = this.createElement("a");
|
|
|
|
if (buttons[k] instanceof Function) {
|
|
|
|
button.addEventListener("click", (e) => {
|
|
|
|
buttons[k]();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
button.classList.add("ejs_button");
|
|
|
|
button.innerText = k;
|
|
|
|
this.currentPopup.appendChild(button);
|
|
|
|
}
|
|
|
|
|
|
|
|
return main;
|
|
|
|
}
|
2023-06-22 16:00:13 +00:00
|
|
|
selectFile() {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const file = this.createElement("input");
|
|
|
|
file.type = "file";
|
|
|
|
this.addEventListener(file, "change", (e) => {
|
|
|
|
resolve(e.target.files[0]);
|
|
|
|
})
|
|
|
|
file.click();
|
|
|
|
})
|
|
|
|
}
|
|
|
|
createBottomMenuBar() {
|
|
|
|
this.elements.menu = this.createElement("div");
|
|
|
|
this.elements.menu.classList.add("ejs_menu_bar");
|
|
|
|
this.elements.menu.classList.add("ejs_menu_bar_hidden");
|
2023-06-20 19:17:02 +00:00
|
|
|
|
2023-06-22 16:00:13 +00:00
|
|
|
let timeout = null;
|
|
|
|
const hide = () => {
|
|
|
|
if (this.paused) return;
|
|
|
|
this.elements.menu.classList.add("ejs_menu_bar_hidden");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.addEventListener(this.elements.parent, 'mousemove click', (e) => {
|
|
|
|
if (!this.started) return;
|
|
|
|
if (timeout !== null) clearTimeout(timeout);
|
|
|
|
timeout = setTimeout(hide, 3000);
|
|
|
|
this.elements.menu.classList.remove("ejs_menu_bar_hidden");
|
|
|
|
})
|
|
|
|
this.menu = {
|
|
|
|
close: () => {
|
|
|
|
if (!this.started) return;
|
|
|
|
if (timeout !== null) clearTimeout(timeout);
|
|
|
|
this.elements.menu.classList.remove("ejs_menu_bar_hidden");
|
|
|
|
},
|
|
|
|
open: () => {
|
|
|
|
if (!this.started) return;
|
|
|
|
if (timeout !== null) clearTimeout(timeout);
|
|
|
|
timeout = setTimeout(hide, 3000);
|
|
|
|
this.elements.menu.classList.remove("ejs_menu_bar_hidden");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.elements.parent.appendChild(this.elements.menu);
|
|
|
|
|
|
|
|
//Now add buttons
|
|
|
|
const addButton = (title, image, callback) => {
|
|
|
|
const button = this.createElement("button");
|
|
|
|
button.type = "button";
|
|
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
|
|
svg.setAttribute("role", "presentation");
|
|
|
|
svg.setAttribute("focusable", "false");
|
|
|
|
svg.innerHTML = image;
|
|
|
|
const text = this.createElement("span");
|
|
|
|
text.innerText = title;
|
|
|
|
text.classList.add("ejs_menu_text");
|
|
|
|
|
|
|
|
button.classList.add("ejs_menu_button");
|
|
|
|
button.appendChild(svg);
|
|
|
|
button.appendChild(text);
|
|
|
|
this.elements.menu.appendChild(button);
|
|
|
|
if (callback instanceof Function) {
|
|
|
|
this.addEventListener(button, 'click', callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//todo. Center text on not restart button
|
|
|
|
|
|
|
|
addButton("Restart", '<svg viewBox="0 0 512 512"><path d="M496 48V192c0 17.69-14.31 32-32 32H320c-17.69 0-32-14.31-32-32s14.31-32 32-32h63.39c-29.97-39.7-77.25-63.78-127.6-63.78C167.7 96.22 96 167.9 96 256s71.69 159.8 159.8 159.8c34.88 0 68.03-11.03 95.88-31.94c14.22-10.53 34.22-7.75 44.81 6.375c10.59 14.16 7.75 34.22-6.375 44.81c-39.03 29.28-85.36 44.86-134.2 44.86C132.5 479.9 32 379.4 32 256s100.5-223.9 223.9-223.9c69.15 0 134 32.47 176.1 86.12V48c0-17.69 14.31-32 32-32S496 30.31 496 48z"/></svg>', () => {
|
|
|
|
this.gameManager.restart();
|
|
|
|
});
|
|
|
|
let stateUrl;
|
|
|
|
addButton("Save State", '<svg viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z"/></svg>', async () => {
|
|
|
|
if (stateUrl) URL.revokeObjectURL(stateUrl);
|
|
|
|
const state = await this.gameManager.getState();
|
|
|
|
const blob = new Blob([state]);
|
|
|
|
stateUrl = URL.createObjectURL(blob);
|
|
|
|
const a = this.createElement("a");
|
|
|
|
a.href = stateUrl;
|
|
|
|
a.download = "game.state";
|
|
|
|
a.click();
|
|
|
|
});
|
|
|
|
addButton("Load State", '<svg viewBox="0 0 576 512"><path fill="currentColor" d="M572.694 292.093L500.27 416.248A63.997 63.997 0 0 1 444.989 448H45.025c-18.523 0-30.064-20.093-20.731-36.093l72.424-124.155A64 64 0 0 1 152 256h399.964c18.523 0 30.064 20.093 20.73 36.093zM152 224h328v-48c0-26.51-21.49-48-48-48H272l-64-64H48C21.49 64 0 85.49 0 112v278.046l69.077-118.418C86.214 242.25 117.989 224 152 224z"/></svg>', async () => {
|
|
|
|
const file = await this.selectFile();
|
|
|
|
const state = new Uint8Array(await file.arrayBuffer());
|
|
|
|
this.gameManager.loadState(state);
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
2023-06-20 19:17:02 +00:00
|
|
|
|
|
|
|
}
|