From 88f2e61928c3d8132433d348ffb119c3043e042e Mon Sep 17 00:00:00 2001 From: Jesse McDonald Date: Wed, 29 Apr 2020 23:54:59 -0500 Subject: [PATCH] replace drop-down list with new modal game selection dialog --- css/chess.css | 199 +++++++++++++++++++++++++++++++++-- index.html | 9 +- js/pacosako_ui.js | 256 +++++++++++++++++++++++++++------------------- svg/new-game.svg | 63 ++++++++++++ 4 files changed, 403 insertions(+), 124 deletions(-) create mode 100644 svg/new-game.svg diff --git a/css/chess.css b/css/chess.css index ba20e23..caeae96 100644 --- a/css/chess.css +++ b/css/chess.css @@ -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; diff --git a/index.html b/index.html index 2e76d32..00285b1 100644 --- a/index.html +++ b/index.html @@ -157,6 +157,7 @@
+
@@ -210,18 +211,14 @@ +
+
vs.
-
- - -

diff --git a/js/pacosako_ui.js b/js/pacosako_ui.js index 7a1a352..1ba5aac 100644 --- a/js/pacosako_ui.js +++ b/js/pacosako_ui.js @@ -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 = $(`
`).appendTo('body'); + let gameTiles = $(` +
+
+
New
Game
+
+
+ `).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 = $(''); - 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 : + $(`
`).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 = $('
').appendTo(tile); - window.setInterval(function(){ - $('#cb_select_game').first().children('.cb-game-option').each(function(idx,opt){ - updateTitle(opt); + $('').appendTo(titleBlock) + .text(shortenName(String(data.lightName || 'Light'))); + + $(`vs.`).appendTo(titleBlock); + + $('').appendTo(titleBlock) + .text(shortenName(String(data.darkName || 'Dark'))); + + $('
').appendTo(tile); + + if (data.status) { + $('
').appendTo(tile).text(data.status); + } + + if (data.moves) { + $('
').appendTo(tile) + .text(inWords(data.moves, 'turn')); + } + + $('
').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)/); diff --git a/svg/new-game.svg b/svg/new-game.svg new file mode 100644 index 0000000..219edbb --- /dev/null +++ b/svg/new-game.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + +