add "private copy" feature for client-only exploration of moves
This commit is contained in:
parent
6dd6c2e3d6
commit
6495c49022
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
17
index.html
17
index.html
|
|
@ -17,10 +17,10 @@
|
|||
<div id="board_ui">
|
||||
<div id="header">
|
||||
<button id="settings" title="Settings" type="button"><span class="fas fa-cog"></span></button>
|
||||
<button id="cb_choose_game" title="Choose Game" type="button"><span class="fas fa-list"></span></button>
|
||||
<h1>Paco Ŝako</h1>
|
||||
<button id="cb_choose_game" title="Choose Game" type="button" class="cb-hide-if-private"><span class="fas fa-list"></span></button>
|
||||
<h1>Paco Ŝako<span class="cb-show-if-private" style="display: none"> - Private Mode</span></h1>
|
||||
<div class="checkbox-container"><input id="cb_reverse" title="Reverse Board" type="checkbox" autocomplete="off" class="image-checkbox fas fa-sync"></div>
|
||||
<div class="checkbox-container"><input id="cb_notify" title="Notify" type="checkbox" autocomplete="off" class="image-checkbox fas fa-bell-slash"></div>
|
||||
<div class="checkbox-container cb-hide-if-private"><input id="cb_notify" title="Notify" type="checkbox" autocomplete="off" class="image-checkbox fas fa-bell-slash"></div>
|
||||
<button id="help" title="Help" type="button"><span class="fas fa-question-circle"></span></button>
|
||||
</div>
|
||||
|
||||
|
|
@ -149,7 +149,10 @@
|
|||
</table>
|
||||
</div>
|
||||
<div id="cb_status">
|
||||
<div id="cb_message"></div>
|
||||
<div class="hbox">
|
||||
<div id="cb_message"></div>
|
||||
<div class="cb-hide-if-private"><a class="private-link" target="_blank" title="Open Private Copy"><span class="fas fa-copy"></span></a></div>
|
||||
</div>
|
||||
<div id="cb_explain_check"></div>
|
||||
<div id="cb_scrollable">
|
||||
<div id="cb_history">
|
||||
|
|
@ -157,7 +160,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cb_names">
|
||||
<div id="cb_names" class="cb-hide-if-private">
|
||||
<div id="cb_names_text">
|
||||
<input id="cb_light_name" autocomplete="off" placeholder="Light">
|
||||
<span class="cb-names-vs">vs.</span>
|
||||
|
|
@ -172,8 +175,8 @@
|
|||
<div class="nav-spacer"></div>
|
||||
<button id="cb_nav_first" title="View First Turn" type="button" disabled="true"><span class="fas fa-fast-backward"></span></button>
|
||||
<button id="cb_nav_prev_turn" title="View Prior Turn" type="button" disabled="true"><span class="fas fa-backward"></span></button>
|
||||
<button id="cb_nav_prev_state" title="View Prior State" type="button" disabled="true"><span class="fas fa-play fa-flip-horizontal"></span></button>
|
||||
<button id="cb_nav_next_state" title="View Next State" type="button" disabled="true"><span class="fas fa-play"></span></button>
|
||||
<button id="cb_nav_prev_state" title="View Prior Move" type="button" disabled="true"><span class="fas fa-play fa-flip-horizontal"></span></button>
|
||||
<button id="cb_nav_next_state" title="View Next Move" type="button" disabled="true"><span class="fas fa-play"></span></button>
|
||||
<button id="cb_nav_next_turn" title="View Next Turn" type="button" disabled="true"><span class="fas fa-forward"></span></button>
|
||||
<button id="cb_nav_last" title="View Current Move" type="button" disabled="true"><span class="fas fa-fast-forward"></span></button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue