From: Benjamin Auder Date: Mon, 30 Mar 2020 13:06:08 +0000 (+0200) Subject: Draft game upload logic (unwritten yet for Analysis mode) X-Git-Url: https://git.auder.net/?p=vchess.git;a=commitdiff_plain;h=5f918a278904266a2a66a3c8e2a3655f37c2d2a7 Draft game upload logic (unwritten yet for Analysis mode) --- diff --git a/client/public/images/icons/SOURCE b/client/public/images/icons/SOURCE index 4d22cabf..235d0b93 100644 --- a/client/public/images/icons/SOURCE +++ b/client/public/images/icons/SOURCE @@ -10,6 +10,7 @@ https://www.onlinewebfonts.com/icon/256756 https://www.flaticon.com/free-icon/forward_2413353?term=forward&page=1&position=59 https://www.flaticon.com/free-icon/right_565870?term=forward&page=1&position=31 https://www.flaticon.com/free-icon/download_724933?term=download&page=1&position=3 +https://www.flaticon.com/free-icon/upload_725008?term=upload&page=1&position=14 https://www.flaticon.com/free-icon/resize_512182?term=resize&page=1&position=49 https://www.flaticon.com/free-icon/clear_565313?term=delete&page=1&position=33 https://www.flaticon.com/free-icon/clear_1632708?term=delete&page=1&position=3 diff --git a/client/public/images/icons/upload.svg b/client/public/images/icons/upload.svg new file mode 100644 index 00000000..4e8253a0 --- /dev/null +++ b/client/public/images/icons/upload.svg @@ -0,0 +1,126 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index cf0a70c7..639938b2 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -303,9 +303,9 @@ export default { elementArray.push(gameDiv); if (!!this.vr.reserve) elementArray.push(reserveBottom); const boardElt = document.querySelector(".game"); - // Square width might be undefine (at first drawing), + // boardElt might be undefine (at first drawing), // but it won't be used in this case. - const squareWidth = boardElt.offsetWidth / sizeY; + const squareWidth = (!!boardElt ? boardElt.offsetWidth / sizeY : 42); if (this.choices.length > 0 && !!boardElt) { // No choices to show at first drawing const offset = [boardElt.offsetTop, boardElt.offsetLeft]; @@ -446,10 +446,10 @@ export default { attrs: { id: "arrow", markerWidth: (2 * arrowWidth) + "px", - markerHeight: (2 * arrowWidth) + "px", + markerHeight: (3 * arrowWidth) + "px", markerUnits: "userSpaceOnUse", refX: "0", - refY: arrowWidth + "px", + refY: (1.5 * arrowWidth) + "px", orient: "auto" } }, @@ -460,8 +460,8 @@ export default { "class": { "arrow-head": true }, attrs: { d: ( - "M0,0 L0," + (2 * arrowWidth) + " " + - "L" + (2 * arrowWidth) + "," + arrowWidth + " z" + "M0,0 L0," + (3 * arrowWidth) + " L" + + (2 * arrowWidth) + "," + (1.5 * arrowWidth) + " z" ) } } diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue index a83eb85a..5596e448 100644 --- a/client/src/components/GameList.vue +++ b/client/src/components/GameList.vue @@ -28,6 +28,7 @@ div + + diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 7de0b9c1..e1d6189e 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -50,6 +50,7 @@ export const translations = { green: "green", Hall: "Hall", "Highlight last move": "Highlight last move", + "Imported games": "Imported games", Instructions: "Instructions", "Invalid email": "Invalid email", "It's your turn!": "It's your turn!", @@ -85,6 +86,7 @@ export const translations = { News: "News", "No challenges found :( Click on 'New game'!": "No challenges found :( Click on 'New game'!", "No games found :( Send a challenge!": "No games found :( Send a challenge!", + "No identifier found: use the upload button in analysis mode": "No identifier found: use the upload button in analysis mode", "No more problems": "No more problems", "No subject. Send anyway?": "No subject. Send anyway?", "Notifications by email": "Notifications by email", @@ -137,6 +139,7 @@ export const translations = { Time: "Time", "Undetermined result": "Undetermined result", Update: "Update", + "Upload a game": "Upload a game", "User creation failed. Try again": "User creation failed. Try again", "User name": "User name", "User name or email already in use": "User name or email already in use", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 3a4ece50..d14f4606 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -50,6 +50,7 @@ export const translations = { green: "verde", Hall: "Salón", "Highlight last move": "Resaltar el último movimiento", + "Imported games": "Partidas importadas", Instructions: "Instrucciones", "Invalid email": "Email inválido", "It's your turn!": "¡Es su turno!", @@ -85,6 +86,7 @@ export const translations = { News: "Noticias", "No challenges found :( Click on 'New game'!": "No se encontró ningún desafío :( ¡Haz clic en 'Nueva partida'!", "No games found :( Send a challenge!": "No se encontró partidas :( ¡Envía un desafío!", + "No identifier found: use the upload button in analysis mode": "No se encontró ningún identificador: use el botón enviar en modo de análisis", "No more problems": "No mas problemas", "No subject. Send anyway?": "Sin asunto. ¿Enviar sin embargo?", "Notifications by email": "Notificaciones por email", @@ -137,6 +139,7 @@ export const translations = { Time: "Tiempo", "Undetermined result": "Resultado indeterminado", Update: "Actualización", + "Upload a game": "Enviar una partida", "User creation failed. Try again": "Error al crear cuenta. inténtelo de nuevo", "User name": "Nombre de usuario", "User name or email already in use": "Nombre de usuario o correo electrónico ya en uso", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 3ed54e76..2d7b535b 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -50,6 +50,7 @@ export const translations = { green: "vert", Hall: "Salon", "Highlight last move": "Mettre en valeur le dernier coup", + "Imported games": "Parties importées", Instructions: "Instructions", "Invalid email": "Email invalide", "It's your turn!": "À vous de jouer !", @@ -85,6 +86,7 @@ export const translations = { News: "Nouvelles", "No challenges found :( Click on 'New game'!": "Aucun défi trouvé :( Cliquez sur 'Nouvelle partie' !", "No games found :( Send a challenge!": "Aucune partie trouvée :( Envoyez un défi !", + "No identifier found: use the upload button in analysis mode": "Pas d'identifiant trouvé : utilisez le bouton d'envoi en mode analyse", "No more problems": "Plus de problèmes", "No subject. Send anyway?": "Pas de sujet. Envoyer quand-même ??", "Notifications by email": "Notifications par email", @@ -137,6 +139,7 @@ export const translations = { Time: "Temps", "Undetermined result": "Résultat indéterminé", Update: "Mise à jour", + "Upload a game": "Envoyer une partie", "User creation failed. Try again": "Échec de la création du compte. Réessayez", "User name": "Nom d'utilisateur", "User name or email already in use": "Nom d'utilisateur ou email déjà utilisés", diff --git a/client/src/utils/compgameStorage.js b/client/src/utils/compgameStorage.js index e30a69fb..655fd6bf 100644 --- a/client/src/utils/compgameStorage.js +++ b/client/src/utils/compgameStorage.js @@ -38,7 +38,7 @@ function dbOperation(callback) { export const CompgameStorage = { add: function(game) { - dbOperation((err,db) => { + dbOperation((err, db) => { if (err) return; let objectStore = db .transaction("compgames", "readwrite") @@ -49,7 +49,7 @@ export const CompgameStorage = { // obj: move and/or fen update: function(gameId, obj) { - dbOperation((err,db) => { + dbOperation((err, db) => { let objectStore = db .transaction("compgames", "readwrite") .objectStore("compgames"); @@ -70,7 +70,7 @@ export const CompgameStorage = { // Retrieve any game from its identifier (variant name) // NOTE: need callback because result is obtained asynchronously get: function(gameId, callback) { - dbOperation((err,db) => { + dbOperation((err, db) => { let objectStore = db .transaction("compgames", "readonly") .objectStore("compgames"); @@ -82,7 +82,7 @@ export const CompgameStorage = { // Delete a game in indexedDB remove: function(gameId) { - dbOperation((err,db) => { + dbOperation((err, db) => { if (!err) { db.transaction("compgames", "readwrite") .objectStore("compgames") diff --git a/client/src/utils/gameStorage.js b/client/src/utils/gameStorage.js index 62ad1fab..109c8251 100644 --- a/client/src/utils/gameStorage.js +++ b/client/src/utils/gameStorage.js @@ -53,7 +53,7 @@ function dbOperation(callback) { export const GameStorage = { // Optional callback to get error status add: function(game, callback) { - dbOperation((err,db) => { + dbOperation((err, db) => { if (!!err) { callback("error"); return; @@ -74,7 +74,7 @@ export const GameStorage = { // obj: chat, move, fen, clocks, score[Msg], initime, ... update: function(gameId, obj) { // live - dbOperation((err,db) => { + dbOperation((err, db) => { let objectStore = db .transaction("games", "readwrite") .objectStore("games"); @@ -98,7 +98,7 @@ export const GameStorage = { // Retrieve (all) running local games getRunning: function(callback) { - dbOperation((err,db) => { + dbOperation((err, db) => { let objectStore = db .transaction("games", "readonly") .objectStore("games"); @@ -125,7 +125,7 @@ export const GameStorage = { // Retrieve completed local games getNext: function(upperDt, callback) { - dbOperation((err,db) => { + dbOperation((err, db) => { let objectStore = db .transaction("games", "readonly") .objectStore("games"); @@ -157,11 +157,10 @@ export const GameStorage = { }); }, - // Retrieve any game from its identifiers (locally or on server) + // Retrieve any game from its identifier. // NOTE: need callback because result is obtained asynchronously get: function(gameId, callback) { - // Local game - dbOperation((err,db) => { + dbOperation((err, db) => { let objectStore = db.transaction("games").objectStore("games"); objectStore.get(gameId).onsuccess = function(event) { // event.target.result is null if game not found @@ -172,7 +171,7 @@ export const GameStorage = { // Delete a game in indexedDB remove: function(gameId, callback) { - dbOperation((err,db) => { + dbOperation((err, db) => { if (!err) { let transaction = db.transaction("games", "readwrite"); transaction.oncomplete = function() { diff --git a/client/src/utils/importgameStorage.js b/client/src/utils/importgameStorage.js new file mode 100644 index 00000000..e5ad6e22 --- /dev/null +++ b/client/src/utils/importgameStorage.js @@ -0,0 +1,113 @@ +// Game object struct: see gameStorgae.js + +import { store } from "@/store"; + +function dbOperation(callback) { + let db = null; + let DBOpenRequest = window.indexedDB.open("vchess_import", 4); + + DBOpenRequest.onerror = function(event) { + alert(store.state.tr[ + "Database error: stop private browsing, or update your browser"]); + callback("error", null); + }; + + DBOpenRequest.onsuccess = function() { + db = DBOpenRequest.result; + callback(null, db); + db.close(); + }; + + DBOpenRequest.onupgradeneeded = function(event) { + let db = event.target.result; + let upgradeTransaction = event.target.transaction; + let objectStore = undefined; + if (!db.objectStoreNames.contains("importgames")) + objectStore = db.createObjectStore("importgames", { keyPath: "id" }); + else + objectStore = upgradeTransaction.objectStore("importgames"); + if (!objectStore.indexNames.contains("created")) + // To search by date intervals. Two games could start at the same time + objectStore.createIndex("created", "created", { unique: false }); + }; +} + +export const ImportgameStorage = { + // Optional callback to get error status + add: function(game, callback) { + dbOperation((err, db) => { + if (!!err) { + callback("error"); + return; + } + let transaction = db.transaction("importgames", "readwrite"); + transaction.oncomplete = function() { + // Everything's fine + callback(); + }; + transaction.onerror = function(err) { + // Duplicate key error (most likely) + callback(err); + }; + transaction.objectStore("importgames").add(game); + }); + }, + + // Retrieve next imported games + getNext: function(upperDt, callback) { + dbOperation((err, db) => { + let objectStore = db + .transaction("importgames", "readonly") + .objectStore("importgames"); + let index = objectStore.index("created"); + const range = IDBKeyRange.upperBound(upperDt); + let games = []; + index.openCursor(range).onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + // Most recent games first: + games = games.sort((g1, g2) => g2.created - g1.created); + // TODO: 20 games showed per request is arbitrary + callback(games.slice(0, 20)); + } + else { + // If there is still another cursor to go, keep running this code + let g = cursor.value; + // Do not retrieve moves or clocks (unused in list mode) + g.movesCount = g.moves.length; + delete g.moves; + delete g.clocks; + delete g.initime; + games.push(g); + cursor.continue(); + } + }; + }); + }, + + // Retrieve any game from its identifier. + // NOTE: need callback because result is obtained asynchronously + get: function(gameId, callback) { + dbOperation((err, db) => { + let objectStore = + db.transaction("importgames").objectStore("importgames"); + objectStore.get(gameId).onsuccess = function(event) { + // event.target.result is null if game not found + callback(event.target.result); + }; + }); + }, + + // Delete a game in indexedDB + remove: function(gameId, callback) { + dbOperation((err, db) => { + if (!err) { + let transaction = db.transaction("importgames", "readwrite"); + transaction.oncomplete = function() { + callback(); //everything's fine + }; + transaction.objectStore("importgames").delete(gameId); + } + }); + } +}; diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue index 1605b584..2f087b99 100644 --- a/client/src/views/Analyse.vue +++ b/client/src/views/Analyse.vue @@ -19,6 +19,8 @@ import BaseGame from "@/components/BaseGame.vue"; import { store } from "@/store"; export default { name: "my-analyse", + // TODO: game import ==> require some adjustments, like + // the ability to analyse from a list of moves... components: { BaseGame }, @@ -27,7 +29,6 @@ export default { return { st: store.state, gameRef: { - //given in URL (rid = remote ID) vname: "", fen: "" }, diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 68afce35..2a565f68 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -153,6 +153,7 @@ import BaseGame from "@/components/BaseGame.vue"; import Chat from "@/components/Chat.vue"; import { store } from "@/store"; import { GameStorage } from "@/utils/gameStorage"; +import { ImportgameStorage } from "@/utils/importgameStorage"; import { ppt } from "@/utils/datetime"; import { notify } from "@/utils/notifications"; import { ajax } from "@/utils/ajax"; @@ -1193,8 +1194,12 @@ export default { } } ); - } else - // Local game (or live remote) + } + else if (!!this.gameRef.match(/^I_/)) + // Game import (maybe remote) + ImportgameStorage.get(this.gameRef, callback); + else + // Local live game (or remote) GameStorage.get(this.gameRef, callback); }, re_setClocks: function() { diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index 3c3b9b5a..7522871f 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -7,6 +7,8 @@ main | {{ st.tr["Live games"] }} button.tabbtn#corrGames(@click="setDisplay('corr',$event)") | {{ st.tr["Correspondance games"] }} + button.tabbtn#importGames(@click="setDisplay('import',$event)") + | {{ st.tr["Imported games"] }} GameList( ref="livegames" v-show="display=='live'" @@ -21,16 +23,24 @@ main @show-game="showGame" @abortgame="abortGame" ) + GameList( + v-show="display=='import'" + ref="importgames" + :games="importGames" + @show-game="showGame" + ) button#loadMoreBtn( v-show="hasMore[display]" @click="loadMore(display)" ) | {{ st.tr["Load more"] }} + UploadGame(@game-uploaded="addGameImport")