xwords/xwords4/wasm/xwutils.js

528 lines
14 KiB
JavaScript
Raw Normal View History

2021-02-13 17:42:48 +01:00
var state = {client: null,
closure: null,
connected: false,
2021-03-12 16:05:21 +01:00
disconnTimeMS: 0,
connTimeMS: 0,
connChangeStamp: 0,
2021-02-13 17:42:48 +01:00
};
2021-03-25 01:29:27 +01:00
function fontSize(adj=1.0) {
return (adj * state.height / 500) + 'rem';
}
2021-02-21 19:07:51 +01:00
function ccallString(proc, closure, str) {
Module.ccall('cbckString', null, ['number', 'number', 'string'],
[proc, closure, str]);
2021-02-13 17:42:48 +01:00
}
2021-03-09 20:39:43 +01:00
function registerOnce(devid, gitrev, now, dbg) {
2021-02-22 06:59:03 +01:00
let nextTimeKey = 'next_reg';
let gitKey = 'last_write';
2021-02-22 19:53:28 +01:00
let nextTime = parseInt(localStorage.getItem(nextTimeKey));
2021-02-22 06:59:03 +01:00
let prevGit = localStorage.getItem(gitKey);
2021-02-22 19:53:28 +01:00
if ( prevGit == gitrev && now < nextTime ) {
2021-03-09 20:39:43 +01:00
if ( dbg ) {
console.log('registerOnce(): next in ' + (nextTime - now) + ' secs');
}
2021-02-22 06:59:03 +01:00
} else {
2021-03-09 20:39:43 +01:00
let vrntName = window.location.host;
vrntName += window.location.pathname.split('/').slice(0, -1).join('/');
2021-02-22 06:59:03 +01:00
let args = { devid: devid,
gitrev: gitrev,
loc: navigator.language,
os: navigator.appName,
vers: '0.0',
2021-03-09 20:39:43 +01:00
dbg: dbg,
2021-02-22 06:59:03 +01:00
myNow: now,
2021-03-09 20:39:43 +01:00
vrntName: vrntName,
2021-02-22 06:59:03 +01:00
};
let body = JSON.stringify(args);
fetch('/xw4/api/v1/register', {
method: 'post',
body: body,
headers: {
'Content-Type': 'application/json'
},
2021-03-09 20:39:43 +01:00
}).then(handleFetchErrors)
.then(res => {
2021-02-22 06:59:03 +01:00
return res.json();
}).then(data => {
2021-03-07 04:55:21 +01:00
// console.log('data: ' + JSON.stringify(data));
2021-02-22 06:59:03 +01:00
if ( data.success ) {
localStorage.setItem(nextTimeKey, data.atNext);
localStorage.setItem(gitKey, gitrev);
}
2021-03-09 20:39:43 +01:00
}).catch(ex => {
console.error('registerOnce(): fetch()=>' + ex);
2021-02-22 06:59:03 +01:00
});
}
}
function postAck(sum, gameID) {
fetch('/xw4/api/v1/ack', {
method: 'post',
body: JSON.stringify({ "sum": sum, "gid": gameID, }),
headers: {
'Content-Type': 'application/json'
},
})
.then(handleFetchErrors)
.then(res => {
return res.json();
}).then(data => {
console.log('data from ack: ' + JSON.stringify(data));
}).catch(ex => {
console.error('postAck(): fetch()=>' + ex);
});
}
2021-03-08 05:51:22 +01:00
function handleFetchErrors(response) {
if ( response.ok ) {
return response;
} else {
throw Error(response.statusText);
}
}
function getDict(langs, proc, closure) {
2021-03-08 16:39:47 +01:00
function callWhenDone(xwd, lc, langName, data, len) {
2021-03-08 05:51:22 +01:00
Module.ccall('gotDictBinary', null,
2021-03-08 16:39:47 +01:00
['number', 'number', 'string', 'string', 'string', 'array', 'number'],
[proc, closure, xwd, lc, langName, data, len ]);
2021-03-08 05:51:22 +01:00
}
let gots = {}; // for later
2021-02-26 05:39:08 +01:00
console.log('langs: ' + langs + '; langs[0]: ' + langs[0]);
let args = '?lc=' + langs.join('|');
console.log('args: ' + args);
fetch('/xw4/info.py/listDicts' + args, {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
2021-03-08 05:51:22 +01:00
}).then(handleFetchErrors)
.then(response => {
return response.json();
}).then(data => {
// console.log('data: ' + JSON.stringify(data));
for ( lang of data.langs ) {
let dict = null;
for ( one of lang.dicts ) {
2021-03-08 21:46:42 +01:00
if ( !dict || one.nBytes > dict.nBytes ) {
dict = one;
}
}
if ( dict ) {
gots.xwd = dict.xwd;
gots.langName = lang.lang;
gots.lc = lang.lc;
2021-03-08 05:51:22 +01:00
let path = '/' + ['and_wordlists', gots.langName, gots.xwd].join('/');
return fetch(path);
}
}
2021-03-08 05:51:22 +01:00
}).then(handleFetchErrors)
.then(response => {
// console.log('got here!!!' + response);
return response.arrayBuffer();
}).then(data=> {
let len = data.byteLength;
let dataPtr = Module._malloc(len);
// Copy data to Emscripten heap
var dataHeap = new Uint8Array(Module.HEAPU8.buffer, dataPtr, len);
dataHeap.set( new Uint8Array(data) );
2021-03-08 16:39:47 +01:00
callWhenDone(gots.xwd, gots.lc, gots.langName, dataHeap, len);
Module._free(dataPtr);
2021-03-08 05:51:22 +01:00
}).catch(ex => {
2021-03-08 16:39:47 +01:00
callWhenDone(null, null, null, [], 0);
});
}
// Called from main() asap after things are initialized etc.
function jssetup(closure, dbg, devid, gitrev, now, noTabProc,
focusProc, msgProc, subTopics) {
// Set a unique tag so we know if somebody comes along later
let tabID = Math.random();
2021-03-09 21:04:45 +01:00
let item = 'tabID/' + dbg;
localStorage.setItem(item, tabID);
2021-02-21 19:07:51 +01:00
let listener = function () {
2021-03-09 21:04:45 +01:00
newTabID = localStorage.getItem(item);
if ( newTabID != tabID ) {
state.client.disconnect();
ccallString(noTabProc, state.closure, '');
2021-02-21 19:07:51 +01:00
window.removeEventListener('storage', listener);
}
2021-02-21 19:07:51 +01:00
};
window.addEventListener('storage', listener);
window.onfocus = function () {
2021-03-10 05:13:42 +01:00
ccallString(focusProc, state.closure, 'focus');
};
window.onblur = function () {
ccallString(focusProc, state.closure, 'blur');
};
2021-02-13 17:42:48 +01:00
state.closure = closure;
state.msgProc = msgProc;
state.subTopics = subTopics;
2021-03-09 20:39:43 +01:00
registerOnce(devid, gitrev, now, dbg);
2023-05-26 05:22:52 +02:00
document.getElementById("git_rev").textContent = gitrev;
2023-02-26 19:48:13 +01:00
document.getElementById("mqtt_span").textContent = devid;
2021-02-13 17:42:48 +01:00
2021-03-12 16:05:21 +01:00
function onConnChange(isConn) {
state.connected = isConn;
const now = Date.now();
if ( 0 != state.connChangeStamp ) {
const incr = now - state.connChangeStamp;
if ( isConn ) {
state.disconnTimeMS += incr;
} else {
state.connTimeMS += incr;
}
}
state.connChangeStamp = now;
let stateStr = isConn ? 'Connected' : 'Disconnected';
document.getElementById("mqtt_status").textContent = stateStr;
Module.ccall('MQTTConnectedChanged', null, ['number', 'boolean'],
[state.closure, isConn]);
2021-03-12 18:39:25 +01:00
console.error('new conn state: ', isConn, '; total conn: ', state.connTimeMS,
'; total disconn: ', state.disconnTimeMS );
2021-02-15 23:59:44 +01:00
}
state.client = new Paho.MQTT.Client("eehouse.org", 9001, '/wss', devid);
2021-02-13 17:42:48 +01:00
// set callback handlers
state.client.onConnectionLost = function onConnectionLost(responseObject) {
2021-03-12 16:05:21 +01:00
onConnChange(false);
2021-02-13 17:42:48 +01:00
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost:"+responseObject.errorMessage);
}
};
state.client.onMessageArrived = function onMessageArrived(message) {
let topic = message.topic;
let payload = message.payloadBytes;
let length = payload.length;
Module.ccall('cbckMsg', null, ['number', 'number', 'string', 'number', 'array'],
[state.msgProc, state.closure, topic, length, payload]);
2021-02-13 17:42:48 +01:00
};
function onConnect() {
2021-03-12 16:05:21 +01:00
onConnChange(true);
2021-02-13 17:42:48 +01:00
var subscribeOptions = {
qos: 2, // QoS
// invocationContext: {foo: true}, // Passed to success / failure callback
// onSuccess: function() { alert('subscribe succeeded'); },
onFailure: function() { alert('subscribe failed'); },
timeout: 10,
};
for ( topic of state.subTopics ) {
// console.log('calling subscribe('+topic+')');
state.client.subscribe(topic, subscribeOptions);
}
2021-02-13 17:42:48 +01:00
}
state.client.connect({mqttVersion: 3,
userName: "xwuser",
password: "xw4r0cks",
useSSL: true,
reconnect: true,
onSuccess: onConnect,
2021-03-12 16:05:21 +01:00
onFailure: function() { console.error('mqtt.client.connect.onFailure'); },
2021-02-13 17:42:48 +01:00
});
function callResize() {
state.width = window.innerWidth;
state.height = state.width * 2;
if ( state.height > window.innerHeight ) {
state.height = window.innerHeight;
state.width = state.height / 2;
}
state.height = state.height * 100 / 151;
2021-03-25 01:29:27 +01:00
for ( const id of ['nbalert', 'gamename'] ) {
document.getElementById(id).style['font-size'] = fontSize();
}
ccall('onResize', null, ['number', 'number', 'number'],
[state.closure, state.width, state.height]);
}
window.addEventListener('resize', function() {
const innerWidth = window.innerWidth;
const innerHeight = window.innerHeight;
window.setTimeout( function() {
if ( innerWidth == window.innerWidth && innerHeight == window.innerHeight ) {
callResize();
}
}, 500 );
});
callResize(); // so client knows initial size
2021-02-13 17:42:48 +01:00
}
function mqttSend( topic, ptr ) {
let canSend = null != state.client && state.connected;
if ( canSend ) {
message = new Paho.MQTT.Message(ptr);
message.destinationName = topic;
message.qos = 2;
message.retained = true;
2021-02-13 17:42:48 +01:00
state.client.send(message);
} else {
2021-03-12 16:05:21 +01:00
console.error('mqttSend: not connected');
2021-02-13 17:42:48 +01:00
}
return canSend;
}
2021-02-20 02:49:20 +01:00
function addDepthNote(dlg) {
let depth = dlg.parentNode.childElementCount;
if ( depth > 1 ) {
let div = document.createElement('div');
div.textContent = '(Depth: ' + depth + ')';
dlg.appendChild(div);
}
}
2021-02-16 23:25:22 +01:00
function newDlgWMsg(msg) {
2021-02-16 22:25:21 +01:00
let container = document.getElementById('nbalert');
let dlg = document.createElement('div');
dlg.classList.add('nbalert');
2021-02-16 23:25:22 +01:00
dlg.style.zIndex = 10000 + container.childElementCount;
2021-02-16 22:25:21 +01:00
container.appendChild( dlg );
let txtDiv = document.createElement('div');
2021-02-16 22:25:21 +01:00
txtDiv.textContent = msg
2021-02-16 23:25:22 +01:00
dlg.appendChild( txtDiv );
return dlg;
}
function newButtonDiv(buttons, proc, asDivs) {
let div = document.createElement('div');
div.classList.add('buttonRow');
for ( let ii = 0; ii < buttons.length; ++ii ) {
let buttonTxt = buttons[ii];
let button = document.createElement('button');
2021-03-10 03:17:45 +01:00
button.classList.add('xwbutton');
button.textContent = buttonTxt;
2021-03-25 01:29:27 +01:00
button.style['font-size'] = fontSize(1.2);
button.onclick = function() { proc(ii); };
if ( asDivs ) {
let bdiv = document.createElement('div');
bdiv.appendChild(button);
button = bdiv;
}
div.appendChild(button);
}
return div;
}
function nbDialog(msg, buttons, proc, closure) {
const dlg = newDlgWMsg( msg );
const butProc = function(indx) {
dlg.parentNode.removeChild(dlg);
ccallString(proc, closure, buttons[indx]);
}
dlg.appendChild( newButtonDiv( buttons, butProc, false ) );
2021-02-20 02:49:20 +01:00
addDepthNote(dlg);
}
2021-02-18 03:58:46 +01:00
function nbBlankPick(title, buttons, proc, closure) {
let dlg = newDlgWMsg( title );
const butProc = function(indx) {
2021-02-18 03:58:46 +01:00
dlg.parentNode.removeChild(dlg);
ccallString(proc, closure, indx.toString());
2021-02-18 03:58:46 +01:00
}
dlg.appendChild( newButtonDiv( buttons, butProc, false ) );
2021-02-20 02:49:20 +01:00
addDepthNote(dlg);
2021-02-18 03:58:46 +01:00
}
2021-02-23 21:42:17 +01:00
// To enable sorting of names on buttons while keeping existing code,
// I'm creating an array of pairs then sorting that.
2021-02-19 20:19:16 +01:00
function nbGamePick(title, gameMap, proc, closure) {
let dlg = newDlgWMsg( title );
2021-02-23 21:42:17 +01:00
let pairs = [];
2021-02-19 20:19:16 +01:00
Object.keys(gameMap).forEach( function(key) {
2021-02-23 21:42:17 +01:00
pairs.push({id:key, txt:gameMap[key]});
});
pairs.sort(function(a, b){
var stra = a.txt.toLowerCase();
var strb = b.txt.toLowerCase();
if (stra < strb) {return -1;}
if (stra > strb) {return 1;}
return parseInt(a.id) - parseInt(b.id);
2021-02-19 20:19:16 +01:00
});
2021-02-23 21:42:17 +01:00
let buttons = [];
for ( pair of pairs ) {
buttons.push(pair.txt);
}
2021-02-19 20:19:16 +01:00
butProc = function(indx) {
2021-02-19 20:19:16 +01:00
dlg.parentNode.removeChild(dlg);
2021-02-23 21:42:17 +01:00
ccallString(proc, closure, pairs[indx].id);
2021-02-19 20:19:16 +01:00
}
dlg.appendChild( newButtonDiv( buttons, butProc, true ) );
cancelProc = function() {
dlg.parentNode.removeChild(dlg);
ccallString(proc, closure, null);
};
dlg.appendChild( newButtonDiv( ['Cancel'], cancelProc, false ) );
2021-02-19 20:19:16 +01:00
2021-02-20 02:49:20 +01:00
addDepthNote(dlg);
2021-02-19 20:19:16 +01:00
}
2021-02-19 04:20:20 +01:00
function setDivButtons(divid, buttons, proc, closure) {
let parent = document.getElementById(divid);
while ( parent.lastElementChild ) {
parent.removeChild(parent.lastElementChild);
}
butProc = function(indx) {
ccallString(proc, closure, buttons[indx]);
2021-02-19 04:20:20 +01:00
}
parent.appendChild( newButtonDiv( buttons, butProc, false ) );
2021-02-19 04:20:20 +01:00
}
2021-02-16 23:25:22 +01:00
function nbGetString(msg, dflt, proc, closure) {
let dlg = newDlgWMsg( msg );
2021-03-11 05:33:24 +01:00
let div = document.createElement('div');
div.classList.add('emscripten');
dlg.appendChild(div);
2021-02-16 23:25:22 +01:00
let tarea = document.createElement('textarea');
tarea.classList.add('stringedit');
tarea.style['font-size'] = fontSize();
2021-02-16 23:25:22 +01:00
tarea.value = dflt;
2021-03-11 05:33:24 +01:00
div.appendChild( tarea );
2021-02-16 23:25:22 +01:00
dismissed = function(str) {
dlg.parentNode.removeChild(dlg);
2021-02-21 19:07:51 +01:00
ccallString(proc, closure, str);
2021-02-16 23:25:22 +01:00
}
let buttons = ["Cancel", "OK"];
butProc = function(indx) {
if ( buttons[indx] == 'Cancel' ) {
dismissed(null);
} else if ( buttons[indx] == 'OK' ) {
dismissed(tarea.value);
}
}
dlg.appendChild( newButtonDiv( buttons, butProc, false ) );
2021-02-20 02:49:20 +01:00
addDepthNote(dlg);
2021-02-16 23:25:22 +01:00
}
2021-03-08 17:15:11 +01:00
function newRadio(txt, id, isSet, proc) {
let span = document.createElement('span');
let radio = document.createElement('input');
radio.type = 'radio';
radio.id = id;
radio.name = id;
radio.onclick = proc;
2021-03-08 17:15:11 +01:00
radio.checked = isSet;
let label = document.createElement('label')
label.htmlFor = id;
var description = document.createTextNode(txt);
label.appendChild(description);
span.appendChild(radio);
span.appendChild(label);
return span;
}
2021-03-10 18:10:50 +01:00
function newCheckbox(txt, id, val) {
let span = document.createElement('span');
let checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = id;
checkbox.checked = val;
let label = document.createElement('label')
var description = document.createTextNode(txt);
label.appendChild(description);
span.appendChild(checkbox);
span.appendChild(label);
2021-03-10 18:10:50 +01:00
return span;
}
function nbGetNewGame(closure, msg, allowHints, isRobot, langs, langName) {
const dlg = newDlgWMsg(msg);
2021-03-10 18:10:50 +01:00
let hintsDiv = document.createElement('div');
dlg.appendChild(hintsDiv);
hintsDiv.appendChild(newCheckbox("Allow hints", 'allowHints', allowHints));
2021-03-10 18:10:50 +01:00
const explDiv = document.createElement('div');
dlg.appendChild( explDiv );
2021-03-10 03:17:45 +01:00
explDiv.textContent = '>> Is your opponent a robot or someone you will invite?';
const radioDiv = document.createElement('div');
dlg.appendChild( radioDiv );
let robotSet = isRobot;
2021-03-08 17:15:11 +01:00
radioDiv.appendChild(newRadio('Robot', 'newgame', robotSet,
function() {robotSet = true;}));
radioDiv.appendChild(newRadio('Remote player', 'newgame', !robotSet,
function() {robotSet = false;} ));
let chosenLang = langName ? langName : langs[0];
if ( 1 < langs.length ) {
const langsExplDiv = document.createElement('div');
dlg.appendChild( langsExplDiv );
2021-03-10 03:17:45 +01:00
langsExplDiv.textContent = ">> Choose your game language";
const langsDiv = document.createElement('div');
dlg.appendChild( langsDiv );
for ( let ii = 0; ii < langs.length; ++ii ) {
let langName = langs[ii];
let isSet = langName == chosenLang;
langsDiv.appendChild(newRadio(langName, 'lang', isSet,
2021-03-08 17:15:11 +01:00
function() {chosenLang = langName;}));
}
}
let dupModeDiv = document.createElement('div');
dlg.appendChild(dupModeDiv);
dupModeDiv.appendChild(newCheckbox("Play in Duplicate Mode", 'dupMode', false));
const buttons = ['Cancel', 'OK'];
const butProc = function(indx) {
if ( buttons[indx] == 'OK' ) {
2021-03-10 18:10:50 +01:00
let allowHints = document.getElementById('allowHints').checked;
let dupMode = document.getElementById('dupMode').checked;
const types = ['number', 'boolean', 'string', 'boolean', 'boolean'];
const params = [closure, robotSet, chosenLang, allowHints, dupMode];
Module.ccall('onNewGame', null, types, params);
}
dlg.parentNode.removeChild(dlg);
}
dlg.appendChild( newButtonDiv( buttons, butProc, false ) );
2021-02-20 02:49:20 +01:00
addDepthNote(dlg);
}
2021-02-13 17:42:48 +01:00
for ( let one of ['paho-mqtt.js'] ) {
let script = document.createElement('script');
script.src = one;
2021-02-13 17:42:48 +01:00
document.body.appendChild(script);
}