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 {
|
#cb_message {
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
margin-bottom: 0.1rem;
|
margin-bottom: 0.1rem;
|
||||||
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cb_explain_check {
|
#cb_explain_check {
|
||||||
|
|
@ -478,6 +479,39 @@ button#settings, button#cb_choose_game {
|
||||||
z-index: 1;
|
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 {
|
.jBox-title h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
index.html
15
index.html
|
|
@ -17,10 +17,10 @@
|
||||||
<div id="board_ui">
|
<div id="board_ui">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<button id="settings" title="Settings" type="button"><span class="fas fa-cog"></span></button>
|
<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>
|
<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</h1>
|
<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_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>
|
<button id="help" title="Help" type="button"><span class="fas fa-question-circle"></span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -149,7 +149,10 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div id="cb_status">
|
<div id="cb_status">
|
||||||
|
<div class="hbox">
|
||||||
<div id="cb_message"></div>
|
<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_explain_check"></div>
|
||||||
<div id="cb_scrollable">
|
<div id="cb_scrollable">
|
||||||
<div id="cb_history">
|
<div id="cb_history">
|
||||||
|
|
@ -157,7 +160,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="cb_names">
|
<div id="cb_names" class="cb-hide-if-private">
|
||||||
<div id="cb_names_text">
|
<div id="cb_names_text">
|
||||||
<input id="cb_light_name" autocomplete="off" placeholder="Light">
|
<input id="cb_light_name" autocomplete="off" placeholder="Light">
|
||||||
<span class="cb-names-vs">vs.</span>
|
<span class="cb-names-vs">vs.</span>
|
||||||
|
|
@ -172,8 +175,8 @@
|
||||||
<div class="nav-spacer"></div>
|
<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_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_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_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 State" type="button" disabled="true"><span class="fas fa-play"></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_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>
|
<button id="cb_nav_last" title="View Current Move" type="button" disabled="true"><span class="fas fa-fast-forward"></span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -573,7 +573,30 @@ class Game {
|
||||||
return this._version;
|
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({
|
return JSON.stringify({
|
||||||
past: this._moves,
|
past: this._moves,
|
||||||
future: this._redo,
|
future: this._redo,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ import {Workbox, messageSW} from 'workbox-window';
|
||||||
import {ResizeSensor, ElementQueries} from 'css-element-queries';
|
import {ResizeSensor, ElementQueries} from 'css-element-queries';
|
||||||
ElementQueries.listen();
|
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) */
|
/* "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';
|
import Waterdrop from '../mp3/191678__porphyr__waterdrop.mp3';
|
||||||
|
|
||||||
|
|
@ -408,6 +411,14 @@ $(function (){
|
||||||
function setCurrentGame(game, animate) {
|
function setCurrentGame(game, animate) {
|
||||||
currentGame = game;
|
currentGame = game;
|
||||||
setVisibleGame(game, animate);
|
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(){
|
function randomId(){
|
||||||
|
|
@ -583,7 +594,12 @@ $(function (){
|
||||||
function player(side) {
|
function player(side) {
|
||||||
return (side === PS.LIGHT ? 'light' : 'dark');
|
return (side === PS.LIGHT ? 'light' : 'dark');
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameId = $('#cb_board').data('gameId');
|
const gameId = $('#cb_board').data('gameId');
|
||||||
|
if (gameId === 'private') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lightName = $('#cb_light_name').val();
|
const lightName = $('#cb_light_name').val();
|
||||||
const darkName = $('#cb_dark_name').val();
|
const darkName = $('#cb_dark_name').val();
|
||||||
const turns = currentGame.turns;
|
const turns = currentGame.turns;
|
||||||
|
|
@ -646,6 +662,9 @@ $(function (){
|
||||||
$('#cb_light_name').val('');
|
$('#cb_light_name').val('');
|
||||||
$('#cb_dark_name').val('');
|
$('#cb_dark_name').val('');
|
||||||
|
|
||||||
|
if (newId === 'private') {
|
||||||
|
cancelGameCallback = function() {};
|
||||||
|
} else {
|
||||||
cancelGameCallback = IO.onGameUpdate(newId, function(data/*, gameId*/) {
|
cancelGameCallback = IO.onGameUpdate(newId, function(data/*, gameId*/) {
|
||||||
updateQueue.idle.then(() => {
|
updateQueue.idle.then(() => {
|
||||||
if (data.modified > $('#cb_board').data('modified')) {
|
if (data.modified > $('#cb_board').data('modified')) {
|
||||||
|
|
@ -680,15 +699,16 @@ $(function (){
|
||||||
requestNotify();
|
requestNotify();
|
||||||
}
|
}
|
||||||
|
|
||||||
const reverseList = $('#cb_reverse').data('gameList');
|
|
||||||
const doReverse = reverseList.includes('*') || reverseList.includes(newId);
|
|
||||||
$('#cb_reverse').prop('checked', doReverse);
|
|
||||||
arrangeBoard(doReverse);
|
|
||||||
|
|
||||||
/* Ensure that the selected game is in the list (for new games). */
|
/* Ensure that the selected game is in the list (for new games). */
|
||||||
if ($('#game_tile_' + newId).length < 1) {
|
if ($('#game_tile_' + newId).length < 1) {
|
||||||
updateSelectGameMeta({}, newId);
|
updateSelectGameMeta({}, newId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reverseList = $('#cb_reverse').data('gameList');
|
||||||
|
const doReverse = reverseList.includes('*') || reverseList.includes(newId);
|
||||||
|
$('#cb_reverse').prop('checked', doReverse);
|
||||||
|
arrangeBoard(doReverse);
|
||||||
|
|
||||||
/* This is in case the old tile no longer qualifies to be in the list. */
|
/* This is in case the old tile no longer qualifies to be in the list. */
|
||||||
const oldGameTile = $('#game_tile_' + gameId);
|
const oldGameTile = $('#game_tile_' + gameId);
|
||||||
|
|
@ -1273,8 +1293,6 @@ $(function (){
|
||||||
selectBox.setContent(gameSelectContent);
|
selectBox.setContent(gameSelectContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
IO.onMetaUpdate(updateSelectGameMeta);
|
|
||||||
|
|
||||||
const lastNotifyState = {};
|
const lastNotifyState = {};
|
||||||
|
|
||||||
function notifyForGame(meta, gameId) {
|
function notifyForGame(meta, gameId) {
|
||||||
|
|
@ -1343,15 +1361,27 @@ $(function (){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IO.onMetaUpdate(notifyForGame);
|
window.onhashchange = function(/*event*/){
|
||||||
|
|
||||||
window.onpopstate = function(/*event*/){
|
|
||||||
const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/);
|
const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/);
|
||||||
if (foundId) {
|
if (foundId) {
|
||||||
switchGameId(foundId[1]);
|
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 {
|
||||||
const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/);
|
const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/);
|
||||||
if (foundId) {
|
if (foundId) {
|
||||||
switchGameId(foundId[1]);
|
switchGameId(foundId[1]);
|
||||||
|
|
@ -1359,6 +1389,13 @@ $(function (){
|
||||||
switchGameId(randomId());
|
switchGameId(randomId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IO.onMetaUpdate(async (meta, gameId) => {
|
||||||
|
await updateQueue.idle;
|
||||||
|
updateSelectGameMeta(meta, gameId);
|
||||||
|
notifyForGame(meta, gameId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function adjustBoardSize() {
|
function adjustBoardSize() {
|
||||||
let container = $('#cb_container');
|
let container = $('#cb_container');
|
||||||
let outerWidth = container.width();
|
let outerWidth = container.width();
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||||
|
"pako": "^1.0.11",
|
||||||
"svgo": "^1.3.2",
|
"svgo": "^1.3.2",
|
||||||
"svgo-loader": "^2.2.1",
|
"svgo-loader": "^2.2.1",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^4.43.0",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue