diff --git a/css/chess.css b/css/chess.css index 03fa63e..bc9c2e6 100644 --- a/css/chess.css +++ b/css/chess.css @@ -179,6 +179,7 @@ button#settings, button#cb_choose_game { #cb_message { height: 1.2em; margin-bottom: 0.1rem; + flex: 1 1 auto; } #cb_explain_check { @@ -478,6 +479,39 @@ button#settings, button#cb_choose_game { z-index: 1; } +.cb-private .cb-hide-if-private { + display: none !important; +} + +.cb-show-if-private { + display: none !important; +} + +.cb-private .cb-show-if-private { + display: initial !important; +} + +div.hbox { + display: flex; + flex-flow: row nowrap; + justify-content: stretch; + align-items: stretch; +} + +.private-link { + flex: 0 1 auto; + margin: 0.1em; + color: black; +} + +.private-link:visited { + color: black; +} + +.private-link:hover { + color: blue; +} + .jBox-title h2 { margin: 0; } diff --git a/index.html b/index.html index 13db667..7165092 100644 --- a/index.html +++ b/index.html @@ -17,10 +17,10 @@
@@ -149,7 +149,10 @@
-
+
+
+
+
@@ -157,7 +160,7 @@
-
+
vs. @@ -172,8 +175,8 @@ - - + +
diff --git a/js/pacosako.js b/js/pacosako.js index 655ebc9..32e3967 100644 --- a/js/pacosako.js +++ b/js/pacosako.js @@ -573,7 +573,30 @@ class Game { return this._version; } - toJSON() { + toJSON(style) { + function shrinkMove(move) { + if (move.resign) { + return { side: move.side, resign: true }; + } else if (move.phantom) { + return { side: move.side, phantom: true, to: move.to }; + } else { + return { side: move.side, from: move.from, to: move.to }; + } + } + + if (style === 'minify') { + /* Just the fields that are used by the constructor to replay the game */ + /* Omits metadata and extra annotation fields */ + const state = { past: [], future: [], version: this._version }; + for (const move of this._moves) { + state.past.push(shrinkMove(move)); + } + for (const move of this._redo) { + state.future.push(shrinkMove(move)); + } + return JSON.stringify(state); + } + return JSON.stringify({ past: this._moves, future: this._redo, diff --git a/js/pacosako_ui.js b/js/pacosako_ui.js index 276d271..2d7aca7 100644 --- a/js/pacosako_ui.js +++ b/js/pacosako_ui.js @@ -23,6 +23,9 @@ import {Workbox, messageSW} from 'workbox-window'; import {ResizeSensor, ElementQueries} from 'css-element-queries'; ElementQueries.listen(); +import {Buffer} from 'buffer'; +import pako from 'pako'; + /* "Waterdrop" by Porphyr (freesound.org/people/Porphyr) / CC BY 3.0 (creativecommons.org/licenses/by/3.0) */ import Waterdrop from '../mp3/191678__porphyr__waterdrop.mp3'; @@ -408,6 +411,14 @@ $(function (){ function setCurrentGame(game, animate) { currentGame = game; setVisibleGame(game, animate); + const state = game.toJSON('minify'); + const deflated = pako.deflate(Buffer.from(state)); + const encoded = Buffer.from(deflated).toString('base64'); + if ($('#page').hasClass('cb-private')) { + history.replaceState(null, document.title, `#/private/${encoded}`); + } else { + $('.private-link').attr('href', `#/private/${encoded}`); + } } function randomId(){ @@ -583,7 +594,12 @@ $(function (){ function player(side) { return (side === PS.LIGHT ? 'light' : 'dark'); } + const gameId = $('#cb_board').data('gameId'); + if (gameId === 'private') { + return; + } + const lightName = $('#cb_light_name').val(); const darkName = $('#cb_dark_name').val(); const turns = currentGame.turns; @@ -646,38 +662,47 @@ $(function (){ $('#cb_light_name').val(''); $('#cb_dark_name').val(''); - cancelGameCallback = IO.onGameUpdate(newId, function(data/*, gameId*/) { - updateQueue.idle.then(() => { - if (data.modified > $('#cb_board').data('modified')) { - try { - const newGame = new PS.Game(JSON.stringify(data.board)); - const newState = JSON.parse(newGame.toJSON()); - const oldState = JSON.parse(currentGame.toJSON()); + if (newId === 'private') { + cancelGameCallback = function() {}; + } else { + cancelGameCallback = IO.onGameUpdate(newId, function(data/*, gameId*/) { + updateQueue.idle.then(() => { + if (data.modified > $('#cb_board').data('modified')) { + try { + const newGame = new PS.Game(JSON.stringify(data.board)); + const newState = JSON.parse(newGame.toJSON()); + const oldState = JSON.parse(currentGame.toJSON()); - if (!deepEqual(newState, oldState)) { - debug('got board', newGame.moves); - setCurrentGame(newGame, newGame.moves.length > currentGame.moves.length); + if (!deepEqual(newState, oldState)) { + debug('got board', newGame.moves); + setCurrentGame(newGame, newGame.moves.length > currentGame.moves.length); + } + } catch (err) { + debug('Error parsing board data', err); } - } catch (err) { - debug('Error parsing board data', err); + + const d = data || {}; + $('#cb_board').data('lightName', shortenName(String(d.lightName || 'Light'))); + $('#cb_board').data('darkName', shortenName(String(d.darkName || 'Dark'))); + $('#cb_light_name').val(String(d.lightName || '')); + $('#cb_dark_name').val(String(d.darkName || '')); + + $('#cb_board').data('modified', data.modified); } - - const d = data || {}; - $('#cb_board').data('lightName', shortenName(String(d.lightName || 'Light'))); - $('#cb_board').data('darkName', shortenName(String(d.darkName || 'Dark'))); - $('#cb_light_name').val(String(d.lightName || '')); - $('#cb_dark_name').val(String(d.darkName || '')); - - $('#cb_board').data('modified', data.modified); - } + }); }); - }); - const notifyList = $('#cb_notify').data('gameList'); - const doNotify = notifyList.includes('*') || notifyList.includes(newId); - setNotifyChecked(doNotify); - if (doNotify) { - requestNotify(); + const notifyList = $('#cb_notify').data('gameList'); + const doNotify = notifyList.includes('*') || notifyList.includes(newId); + setNotifyChecked(doNotify); + if (doNotify) { + requestNotify(); + } + + /* Ensure that the selected game is in the list (for new games). */ + if ($('#game_tile_' + newId).length < 1) { + updateSelectGameMeta({}, newId); + } } const reverseList = $('#cb_reverse').data('gameList'); @@ -685,11 +710,6 @@ $(function (){ $('#cb_reverse').prop('checked', doReverse); arrangeBoard(doReverse); - /* Ensure that the selected game is in the list (for new games). */ - if ($('#game_tile_' + newId).length < 1) { - updateSelectGameMeta({}, newId); - } - /* This is in case the old tile no longer qualifies to be in the list. */ const oldGameTile = $('#game_tile_' + gameId); if (oldGameTile.length >= 1) { @@ -1273,8 +1293,6 @@ $(function (){ selectBox.setContent(gameSelectContent); } - IO.onMetaUpdate(updateSelectGameMeta); - const lastNotifyState = {}; function notifyForGame(meta, gameId) { @@ -1343,20 +1361,39 @@ $(function (){ } } - IO.onMetaUpdate(notifyForGame); - - window.onpopstate = function(/*event*/){ + window.onhashchange = function(/*event*/){ const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/); if (foundId) { switchGameId(foundId[1]); } }; - const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/); - if (foundId) { - switchGameId(foundId[1]); + const foundPrivate = location.hash.match(/^#\/private((\/(.*)?)?)$/); + if (foundPrivate) { + switchGameId('private'); + $('#page').addClass('cb-private'); + let state; + try { + const decoded = Buffer.from(foundPrivate[1].slice(1), 'base64'); + const inflated = pako.inflate(decoded); + state = JSON.parse(Buffer.from(inflated).toString()); + } catch(err) {/*ignore*/} + if (state) { + setCurrentGame(new PS.Game(JSON.stringify(state))); + } } else { - switchGameId(randomId()); + const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/); + if (foundId) { + switchGameId(foundId[1]); + } else { + switchGameId(randomId()); + } + + IO.onMetaUpdate(async (meta, gameId) => { + await updateQueue.idle; + updateSelectGameMeta(meta, gameId); + notifyForGame(meta, gameId); + }); } function adjustBoardSize() { diff --git a/package.json b/package.json index 131bc38..310f624 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "lodash": "^4.17.15", "mini-css-extract-plugin": "^0.9.0", "optimize-css-assets-webpack-plugin": "^5.0.3", + "pako": "^1.0.11", "svgo": "^1.3.2", "svgo-loader": "^2.2.1", "webpack": "^4.43.0",