diff --git a/js/pacosako_ui.js b/js/pacosako_ui.js index 6bfb208..2ad798d 100644 --- a/js/pacosako_ui.js +++ b/js/pacosako_ui.js @@ -436,6 +436,112 @@ $(function (){ putMeta({ board: moves }); } + let noticeBox = null; + + function openNoticeBox(content) { + if (noticeBox) { + noticeBox.setContent(content); + noticeBox.open(); + } else { + noticeBox = new jBox('Notice', { + autoClose: false, + closeOnEsc: false, + closeOnClick: false, + content: content, + delayClose: 2000, + delayOpen: 100, + onClose() { + if (noticeBox === this) { + noticeBox = null; + } + }, + onCloseComplete() { + this.destroy(); + }, + }); + } + } + + const updateQueue = { + head: 0, /* next item to be sent */ + tail: 0, /* ID to assign to the next update added to the queue */ + lastGameId: null, + lastModified: null, + newModified: 0, + sending: false, + + add(gameId, data, modified) { + const wasEmpty = this.head === this.tail; + data = Object.assign({}, data, { modified }); + + this[this.tail] = { gameId, data, modified }; + this.tail += 1; + + if (!this.sending) { + openNoticeBox('Saving...'); + this.sending = true; + this.sendNext(); + } + }, + + sendNext() { + const queue = this; + + if (queue.head === queue.tail) { + return; + } + + const update = queue[queue.head]; + delete queue[queue.head]; + queue.head += 1; + + if (update.gameId === queue.lastGameId && update.modified === queue.lastModified) { + update.data.modified = queue.newModified; + } + + IO.sendUpdate(update.gameId, update.data).then((response) => { + queue.lastGameId = update.gameId; + if (response && response.data && 'modified' in response.data) { + /* + * If we queued two or more updates with the same .modified (thus + * based on the same server data), send the next update with the + * new .modified assigned by the server as a result of this update. + */ + queue.lastModified = update.modified; /* original, not adjusted */ + queue.newModified = response.data.modified; + } else { + queue.lastModified = null; + queue.newModified = 0; + } + + if (queue.head === queue.tail) { + queue.sending = false; + /* close the Saving... notice*/ + noticeBox.close({ ignoreDelay: true }); + } else { + queue.sendNext(); + } + }).catch((err) => { + openNoticeBox('Failed to send update to server.'); + noticeBox.close(); + debug('update error', err); + + /* additional updates are unlikely to succeed, so empty the queue */ + while (queue.head < queue.tail) { + delete queue[queue.head]; + queue.head += 1; + } + + queue.sending = false; + + /* force a reset back to the latest server data */ + if (update.gameId === $('#cb_board').data('gameId')) { + switchGameId(update.gameId, true); + } + }); + }, + }; + function putMeta(extra) { const gameId = $('#cb_board').data('gameId'); const lightName = $('#cb_light_name').val(); @@ -462,30 +568,7 @@ $(function (){ Object.assign(meta, extra); } - const noticeBox = new jBox('Notice', { - content: 'Saving...', - autoClose: false, - delayOpen: 200, - delayClose: 2000, - onCloseComplete() { this.destroy(); }, - }); - - const modified = meta.modified = $('#cb_board').data('modified'); - IO.sendUpdate(gameId, meta).then((response) => { - noticeBox.close({ ignoreDelay: true }); - if (response && response.data && Number.isInteger(response.data.modified)) { - if (response.data.modified > modified) { - $('#cb_board').data('modified', response.data.modified); - } - } - }).catch((err) => { - noticeBox.setContent('Failed to send update to server.'); - noticeBox.close(); - debug('update error', err); - - /* force a reset back to the latest server data */ - switchGameId(gameId, true); - }); + updateQueue.add(gameId, meta, $('#cb_board').data('modified')); } function switchGameId(newId, force) {