replace drop-down list with new modal game selection dialog

This commit is contained in:
Jesse D. McDonald 2020-04-29 23:54:59 -05:00
parent 6c4cb52579
commit 88f2e61928
4 changed files with 403 additions and 124 deletions

View File

@ -120,30 +120,39 @@ button:disabled .silhouette {
margin-top: 0;
}
#cb_controls, #cb_theme, #cb_names, #cb_message, #cb_game, #cb_navigate {
#cb_controls, #cb_theme, #cb_names, #cb_message, #cb_navigate {
margin-top: 0.5em;
white-space: nowrap;
}
#cb_game {
#cb_controls, #cb_theme, #cb_names {
padding-right: calc(3.25em + 12px);
}
#cb_theme {
display: flex;
flex-flow: row nowrap;
align-items: baseline;
padding-right: 2em;
}
#cb_game label, #cb_theme label {
#cb_select_theme {
height: 100%;
}
.cb-spacer {
flex: 1 1 auto;
}
#cb_choose_game {
margin-left: 0.2em;
height: 100%;
}
#cb_theme label {
flex: 0 0 auto;
padding-right: 0.2em;
}
#cb_select_game {
width: 100%;
margin-left: 0em;
margin-right: auto;
flex: 0 1 auto;
}
#cb_undo {
margin-left: auto;
margin-right: 0.1em;
@ -390,6 +399,174 @@ button:disabled .silhouette {
margin: 0.6em;
}
.game-tiles {
max-width: 80vw;
max-height: 80vh;
overflow-y: auto;
text-align: center;
}
.game-tiles > * {
vertical-align: middle;
}
.game-tile {
position: relative;
display: inline-flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
width: 14em;
height: 14em;
border: 1px solid black;
box-shadow: 3px 3px 3px black;
margin: 1em;
padding: 0.5em;
border-radius: 5px;
background-color: #FFFFF0;
overflow: hidden;
background-image:
linear-gradient(45deg, #FAF0DD 25%, transparent 25%),
linear-gradient(-45deg, #FAF0DD 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #FAF0DD 75%),
linear-gradient(-45deg, transparent 75%, #FAF0DD 75%);
background-size: 80px 80px;
background-position: -20px -20px, -20px 20px, 20px -60px, -60px -20px;
font-size: 110%;
}
.game-tile:hover {
box-shadow: 3px 3px 3px blue;
}
.game-tile.game-tile-selected {
box-shadow: 0 0 0 2px blue, 5px 5px 3px black;
}
.game-tile.game-tile-selected:hover {
box-shadow: 0 0 0 2px blue, 5px 5px 3px blue;
}
.new-game-tile {
}
.new-game-text {
font-size: 3em;
}
.new-game-text::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
background-image: url(../svg/new-game.svg);
background-size: contain;
background-repeat: no-repeat;
opacity: 25%;
}
.game-tile-title {
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
flex: 0 0 auto;
width: 100%;
height: 50%;
overflow: hidden;
}
.game-tile-lt-name, .game-tile-dk-name {
font-size: 120%;
}
.game-tile-lt-name::before {
content: '';
display: inline-block;
background-image: url(../svg/pacosako/kl.svg);
background-size: contain;
background-repeat: no-repeat;
width: 1.2em;
height: 1.2em;
margin-left: -1.05em;
margin-top: -0.2em;
margin-bottom: -0.2em;
margin-right: -0.15em
}
.game-tile-dk-name::after {
content: '';
display: inline-block;
background-image: url(../svg/pacosako/kd.svg);
background-size: contain;
background-repeat: no-repeat;
width: 1.2em;
height: 1.2em;
margin-left: -0.15em;
margin-top: -0.2em;
margin-bottom: -0.2em;
margin-right: -1.05em
}
.game-tile-lt-name {
display: block;
}
.game-tile-title-vs {
display: block;
font-weight: bold;
margin-top: 0.25em;
margin-bottom: 0.25em;
}
.game-tile-dk-name {
display: block;
}
.game-tile-spacer {
display: block;
flex: 1 1 auto;
}
.game-tile-status {
display: block;
font-weight: bold;
margin-top: 0.5em;
}
.game-tile-moves {
display: block;
margin-top: 0.75em;
}
.game-tile-age {
display: block;
margin-top: 0.5em;
}
@media only screen and (min-width: 50em) and (max-width: 70em) {
.game-tile {
width: 12em;
height: 12em;
font-size: 100%;
}
}
@media only screen and (max-width: 50em) {
.game-tile {
width: 10em;
height: 10em;
font-size: 70%;
}
.new-game-text {
font-size: 3em;
}
}
@media only screen and (max-aspect-ratio: 3/2) {
#content {
flex-flow: column nowrap;

View File

@ -157,6 +157,7 @@
<div><input id="cb_sound" type="checkbox"><label for="cb_sound">Sound</label></div>
<div><input id="cb_reverse" type="checkbox"><label for="cb_reverse">Reverse</label></div>
</div>
<div class="cb-spacer"></div>
<div class="cb-vbox cb-align-stretch">
<div class="cb-hbox">
<button id="cb_undo" type="button" disabled="true">Undo</button>
@ -210,18 +211,14 @@
<option value="traditional">Traditional</option>
<option value="pacosako">Paco Ŝako</option>
</select>
<div class="cb-spacer"></div>
<button id="cb_choose_game">Choose Game…</button>
</div>
<div id="cb_names">
<label for="cb_light_name">Players:</label>
<input id="cb_light_name" placeholder="Light"> vs. <input id="cb_dark_name" placeholder="Dark">
</div>
<div id="cb_message"></div>
<div id="cb_game">
<label for="cb_select_game">Select game:</label>
<select id="cb_select_game">
<option id="cb_new_game">&mdash; New Game &mdash;</option>
</select>
</div>
</form>
<div id="cb_scrollable">
<p id="cb_history"><span id="cb_history_past"></span><span id="cb_history_future"></span></p>

View File

@ -8,6 +8,7 @@ import deepEqual from 'deep-equal';
import 'webpack-jquery-ui/draggable';
import 'webpack-jquery-ui/droppable';
import 'webpack-jquery-ui/selectmenu';
import 'webpack-jquery-ui/fold-effect';
import 'webpack-jquery-ui/css';
import escape from 'lodash/escape';
@ -190,7 +191,7 @@ $(function (){
if (piece.length < 1) {
piece = $('#cb_piece_' + code).clone().prop({ id: piece_id });
}
piece.stop(true);
piece.finish();
piece.attr('id', piece_id);
piece.removeClass('cb-selected');
piece.removeClass('cb-in-check');
@ -210,7 +211,7 @@ $(function (){
function renderBoard(animate) {
$('#cb_board').removeData('dragging_from');
$('#cb_board .cb-piece').stop(true);
$('#cb_board .cb-piece').finish();
$('#cb_board .cb-square').off('click.select');
$('#cb_board .cb-square').off('click.unselect');
$('#cb_board .cb-square').off('click.destination');
@ -599,8 +600,8 @@ $(function (){
}
function switchGameId(newId, force) {
let boardElem = $('#cb_board');
let gameId = boardElem.data('gameId');
const boardElem = $('#cb_board');
const gameId = boardElem.data('gameId');
debug('switching from ' + gameId + ' to ' + newId);
if (newId === gameId && !force) {
@ -684,17 +685,22 @@ $(function (){
}
});
let selOpt = $('#cb_game_' + newId);
if (selOpt.length === 1) {
$('#cb_select_game')[0].selectedIndex = selOpt.index();
} else {
$('#cb_select_game')[0].selectedIndex = -1;
}
$('#jitsi_link').attr('href', 'https://meet.jit.si/PacoSaco_' + newId);
$('#game_link').attr('href', location.href);
refreshSelectOptions();
/* 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) {
updateSelectGameMeta(oldGameTile.data('gameMeta'), gameId);
}
$('.game-tile-selected').removeClass('game-tile-selected');
$('#game_tile_' + newId).addClass('game-tile-selected');
}
function disableNotify(){
@ -971,123 +977,159 @@ $(function (){
applyTheme(theme);
});
$('#cb_select_game').on('change', function(){
let optIndex = $('#cb_select_game')[0].selectedIndex;
if (optIndex === 0) {
switchGameId(randomId());
} else if (optIndex >= 1) {
let opt = $('#cb_select_game option')[optIndex];
if (opt) {
switchGameId(opt.id.replace(/^cb_game_/, ''));
}
$('#cb_choose_game').on('click', function() {
if (selectBox) {
selectBox.open();
}
});
$('#cb_light_name, #cb_dark_name').on('input', function() { putMeta(); });
function updateTitle(opt){
opt = $(opt);
let gameSelectContent = $(`<div style="display: none"></div>`).appendTo('body');
let gameTiles = $(`
<div class="game-tiles">
<div class="game-tile new-game-tile">
<div class="new-game-text">New<br>Game</div>
</div>
</div>
`).appendTo(gameSelectContent);
const then = opt.data('timestamp');
var selectBox = new jBox('Modal', {
content: gameSelectContent,
blockScroll: false,
blockScrollAdjust: false,
isolateScroll: false,
delayClose: 750,
});
$('.new-game-tile').on('click', function() {
switchGameId(randomId());
selectBox.close();
});
function inWords(n, singular, plural) {
const words = [
'zero', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine'
];
return `${words[n] || n} ${n === 1 ? singular : (plural || (singular + 's'))}`;
}
function updateTileAges(tiles) {
const now = +new Date();
let age_str = '';
if (then > now) {
age_str = ' (future)';
} else if ((now - then) < 60*60*1000) {
age_str = ' (' + Math.floor((now - then) / (60*1000)) + 'm)';
} else if ((now - then) < 24*60*60*1000) {
age_str = ' (' + Math.floor((now - then) / (60*60*1000)) + 'h)';
} else {
if ((now - then) >= 14*24*60*60*1000) {
/* prune entries with no activity in 14 days, but leave the current game */
if (opt.data('gameId') !== $('#cb_board').data('gameId')) {
opt.remove();
return;
}
}
age_str = ' (' + Math.floor((now - then) / (24*60*60*1000)) + 'd)';
}
const newText = opt.data('title') + age_str;
if (opt.text() !== newText) {
opt.text(newText);
for (const tileElem of (tiles || $('.game-tile.existing-game'))) {
const tile = $(tileElem);
const game = tile.data('gameMeta');
if (!game || !game.timestamp) {
continue;
}
const then = game.timestamp;
let age_str = '';
if ((now - then) < 60*1000) {
age_str = `just now`;
} else if ((now - then) < 60*60*1000) {
const minutes = Math.floor((now - then) / (60*1000));
age_str = `${inWords(minutes, 'minute')} ago`;
} else if ((now - then) < 24*60*60*1000) {
const hours = Math.floor((now - then) / (60*60*1000));
age_str = `${inWords(hours, 'hour')} ago`;
} else {
const days = Math.floor((now - then) / (24*60*60*1000));
age_str = `${inWords(days, 'day')} ago`;
}
tile.find('.game-tile-age').text(age_str);
}
}
const refreshSelectOptions = (function(){
function updateSelectMeta(d, key) {
let opt = $('#cb_game_' + key);
window.setInterval(() => { updateTileAges(); }, 15000);
if (!d.lightName && !d.darkName && !d.moves && key !== $('#cb_board').data('gameId')) {
if (opt.length >= 1) {
opt.remove();
}
} else {
if (opt.length === 0) {
opt = $('<option></option>');
opt.attr('id', 'cb_game_' + key);
}
function updateSelectGameMeta(data, gameId) {
data = Object.assign({}, data, {
gameId: gameId || data.gameId,
timestamp: data.timestamp || +new Date(),
});
const lightName = shortenName(String(d.lightName || 'Light'));
const darkName = shortenName(String(d.darkName || 'Dark'));
const moves = !d.moves ? '' : `, ${d.moves} turn${d.moves === 1 ? '' : 's'}`;
const stat = !d.status ? '' : `, ${d.status}`;
const timestamp = d.timestamp || +new Date();
opt.data('gameId', key);
opt.data('title', lightName + ' vs. ' + darkName + moves + stat);
opt.data('timestamp', timestamp);
opt.addClass('cb-game-option');
updateTitle(opt);
const select = $('#cb_select_game');
const list = select.children('.cb-game-option').get();
list.push(opt[0]);
list.sort(function(a,b) {
const then_a = $(a).data('timestamp');
const then_b = $(b).data('timestamp');
return (then_a < then_b) ? 1 : (then_a === then_b) ? 0 : -1;
});
$(list).appendTo(select);
}
let selOpt = $('#cb_game_' + $('#cb_board').data('gameId'));
if (selOpt.length === 1) {
$('#cb_select_game')[0].selectedIndex = selOpt.index();
} else {
$('#cb_select_game')[0].selectedIndex = -1;
}
if (typeof gameId !== 'string' || !gameId.match(/^[0-9a-f]{16}$/)) {
debug('invalid game ID', gameId);
return;
}
let cancelMeta = null;
const currentGameId = $('#cb_board').data('gameId');
const oldTile = $('#game_tile_' + gameId).first();
return function() {
$('#cb_select_game .cb-game-option').remove();
/* cancel and resubscribe to get all the cached data again */
if (cancelMeta) {
/* cancel, but don't stop polling the server */
/* this is only a very brief interruption */
cancelMeta(true);
if (!data.lightName && !data.darkName && !data.moves && gameId !== currentGameId) {
oldTile.removeAttr('id');
if (oldTile.length >= 1) {
oldTile.hide({
effect: "fold",
complete() {
oldTile.remove();
},
});
}
return;
}
/* ensure there is an entry for the current game, whatever the server says */
updateSelectMeta({}, $('#cb_board').data('gameId'));
const tile = oldTile.length ? oldTile :
$(`<div class="game-tile existing-game-tile"></div>`).hide();
tile.attr('id', 'game_tile_' + gameId).data('gameMeta', data).empty();
tile.off('click');
cancelMeta = IO.onMetaUpdate(function(data, gameId) {
debug('got meta for key ' + gameId, data);
updateSelectMeta(data, gameId);
});
};
})();
if (gameId === $('#cb_board').data('gameId')) {
tile.addClass("game-tile-selected");
}
refreshSelectOptions();
const titleBlock = $('<div class="game-tile-title"></div>').appendTo(tile);
window.setInterval(function(){
$('#cb_select_game').first().children('.cb-game-option').each(function(idx,opt){
updateTitle(opt);
$('<span class="game-tile-lt-name"></span>').appendTo(titleBlock)
.text(shortenName(String(data.lightName || 'Light')));
$(`<span class="game-tile-title-vs">vs.</span>`).appendTo(titleBlock);
$('<span class="game-tile-dk-name"></span>').appendTo(titleBlock)
.text(shortenName(String(data.darkName || 'Dark')));
$('<div class="game-tile-spacer"></div>').appendTo(tile);
if (data.status) {
$('<div class="game-tile-status"></div>').appendTo(tile).text(data.status);
}
if (data.moves) {
$('<div class="game-tile-moves"></div>').appendTo(tile)
.text(inWords(data.moves, 'turn'));
}
$('<div></div>').addClass('game-tile-age').appendTo(tile);
updateTileAges(tile);
tile.on('click', function() {
$('.game-tile-selected').removeClass('game-tile-selected');
tile.addClass('game-tile-selected');
setTimeout(() => { switchGameId(gameId); }, 0);
selectBox.close();
});
}, 15000);
const list = gameTiles.find('.existing-game-tile').get();
list.push(tile[0]);
list.sort(function(a,b) {
const then_a = $(a).data('gameMeta').timestamp;
const then_b = $(b).data('gameMeta').timestamp;
return (then_a < then_b) ? 1 : (then_a === then_b) ? 0 : -1;
});
$(list).appendTo(gameTiles);
tile.show("fold");
selectBox.setContent(gameSelectContent);
}
IO.onMetaUpdate(updateSelectGameMeta);
window.onpopstate = function(event){
const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/);

63
svg/new-game.svg Normal file
View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256"
height="256"
viewBox="0 0 67.733332 67.733335"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="new-game.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="-128.57143"
inkscape:cy="80"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="999"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-229.26666)">
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
d="M 128 22.5 A 105.5 105.5 0 0 0 22.5 128 A 105.5 105.5 0 0 0 128 233.5 A 105.5 105.5 0 0 0 233.5 128 A 105.5 105.5 0 0 0 128 22.5 z M 116.84375 67.8125 L 139.15625 67.8125 L 139.15625 116.9375 L 188.09375 116.9375 L 188.09375 139.0625 L 139.15625 139.0625 L 139.15625 188.1875 L 116.84375 188.1875 L 116.84375 139.0625 L 67.90625 139.0625 L 67.90625 116.9375 L 116.84375 116.9375 L 116.84375 67.8125 z "
transform="matrix(0.26458333,0,0,0.26458333,0,229.26666)"
id="path815" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB