From afd3240d89a2f6191fe9426960dc0c1667b40c77 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Sun, 29 Dec 2019 23:21:43 +0100 Subject: [PATCH] A few fixes, drop planned problems support (replaced by forum + mode analyze) --- client/next_src/views/MyGames.vue | 45 ------------- client/next_src/views/Problem.vue | 94 +++++++++++++++++++++++++++ client/next_src/views/Problems.vue | 99 ----------------------------- client/src/App.vue | 7 +- client/src/components/GameList.vue | 6 +- client/src/router.js | 11 +++- client/src/stylesheets/variant.sass | 48 +------------- client/src/translations/en.js | 18 +----- client/src/translations/fr.js | 18 +----- client/src/views/Game.vue | 5 +- client/src/views/MyGames.vue | 73 +++++++++++++++++++-- server/models/Game.js | 13 ++-- server/routes/games.js | 2 +- 13 files changed, 194 insertions(+), 245 deletions(-) delete mode 100644 client/next_src/views/MyGames.vue create mode 100644 client/next_src/views/Problem.vue diff --git a/client/next_src/views/MyGames.vue b/client/next_src/views/MyGames.vue deleted file mode 100644 index 5f183da1..00000000 --- a/client/next_src/views/MyGames.vue +++ /dev/null @@ -1,45 +0,0 @@ -<template> - <div class="about"> - <h1>This is an about page</h1> - </div> -</template> -// "My" games: tabs my archived local games, my correspondance games -// + my imported games (of any type). -// TODO: later, also add possibility to upload a game (parse PGN). -Vue.component("my-tab-games", { - props: ["settings"], - data: function() { - return { - display: "", - imported: [], - local: [], - corr: [] - }; - }, - template: ` - <div> - <div class="button-group"> - <button @click="display='local'">Local games</button> - <button @click="display='corr'">Correspondance games</button> - <button @click="display='imported'">Imported games</button> - </div> - <my-game-list v-show="display=='local'" :games="local"> - </my-game-list> - <my-game-list v-show="display=='corr'" :games="corr"> - </my-game-list> - <my-game-list v-show="display=='imported'" :games="imported"> - </my-game-list> - <button @click="update">Refresh</button> - </div> - `, - created: function() { - // TODO: fetch corr games, local and corr - // if any corr game where it's my turn, set display = "corr", - // else set display = "local" (if any) or imported (if any and no local) - }, - methods: { - update: function() { - // TODO: scan local + imported games, if any new then add it - }, - }, -}); diff --git a/client/next_src/views/Problem.vue b/client/next_src/views/Problem.vue new file mode 100644 index 00000000..b30a22e9 --- /dev/null +++ b/client/next_src/views/Problem.vue @@ -0,0 +1,94 @@ +//TODO: new problem form + problem visualisation, like Game.vue (but simpler) +// --> mode analyze, moves = [], "load problem" + <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2"> + <input type="checkbox" id="modal-newproblem" class="modal"/> + <div role="dialog" aria-labelledby="modalProblemTxt"> + <div v-show="!modalProb.preview" class="card newproblem-form"> + <label for="modal-newproblem" class="modal-close"> + </label> + <h3 id="modalProblemTxt">{{ translate("Add a problem") }}</h3> + <form @submit.prevent="previewProblem()"> + <fieldset> + <label for="newpbFen">FEN</label> + <input id="newpbFen" type="text" v-model="modalProb.fen" + :placeholder='translate("Full FEN description")'/> + </fieldset> + <fieldset> + <p class="emphasis">{{ translate("Safe HTML tags allowed") }}</p> + <label for="newpbInstructions">{{ translate("Instructions") }}</label> + <textarea id="newpbInstructions" v-model="modalProb.instructions" + :placeholder='translate("Describe the problem goal")'> + </textarea> + <label for="newpbSolution">{{ translate("Solution") }}</label> + <textarea id="newpbSolution" v-model="modalProb.solution" + :placeholder='translate("How to solve the problem?")'> + </textarea> + <button class="center-btn">{{ translate("Preview") }}</button> + </fieldset> + </form> + </div> + <div v-show="modalProb.preview" class="card newproblem-preview"> + <label for="modal-newproblem" class="modal-close" + @click="modalProb.preview=false"> + </label> + <my-problem-summary :prob="modalProb" :userid="userId" :preview="true"> + </my-problem-summary> + <div class="button-group"> + <button @click="modalProb.preview=false">{{ translate("Cancel") }}</button> + <button @click="sendProblem()">{{ translate("Send") }}</button> + </div> + </div> + </div> + previewProblem: function() { + if (!V.IsGoodFen(this.modalProb.fen)) + return alert(translations["Bad FEN description"]); + if (this.modalProb.instructions.trim().length == 0) + return alert(translations["Empty instructions"]); + if (this.modalProb.solution.trim().length == 0) + return alert(translations["Empty solution"]); + Vue.set(this.modalProb, "preview", true); + }, + editProblem: function(prob) { + this.modalProb = prob; + Vue.set(this.modalProb, "preview", false); + document.getElementById("modal-newproblem").checked = true; + }, + deleteProblem: function(pid) { + ajax( + "/problems/" + pid, + "DELETE", + response => { + // Delete problem from the list on client side + let problems = this.curProblems(); + const pIdx = problems.findIndex(p => p.id == pid); + problems.splice(pIdx, 1); + } + ); + }, + sendProblem: function() { + // Send it to the server and close modal + ajax( + "/problems/" + variant.id, + (this.modalProb.id > 0 ? "PUT" : "POST"), + this.modalProb, + response => { + document.getElementById("modal-newproblem").checked = false; + Vue.set(this.modalProb, "preview", false); + if (this.modalProb.id == 0) + { + this.myProblems.unshift({ + added: Date.now(), + id: response.id, + uid: user.id, + fen: this.modalProb.fen, + instructions: this.modalProb.instructions, + solution: this.modalProb.solution, + }); + if (!this.curProb && this.display != "mine") + this.display = "mine"; + } + else + this.modalProb.id = 0; + } + ); + }, diff --git a/client/next_src/views/Problems.vue b/client/next_src/views/Problems.vue index b7f217d0..368e09a7 100644 --- a/client/next_src/views/Problems.vue +++ b/client/next_src/views/Problems.vue @@ -47,52 +47,6 @@ Vue.component('my-problems', { }, // NOTE: always modals first, because otherwise "scroll to the end" undesirable effect template: ` - <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2"> - <input type="checkbox" id="modal-newproblem" class="modal"/> - <div role="dialog" aria-labelledby="modalProblemTxt"> - <div v-show="!modalProb.preview" class="card newproblem-form"> - <label for="modal-newproblem" class="modal-close"> - </label> - <h3 id="modalProblemTxt">{{ translate("Add a problem") }}</h3> - <form @submit.prevent="previewProblem()"> - <fieldset> - <label for="newpbFen">FEN</label> - <input id="newpbFen" type="text" v-model="modalProb.fen" - :placeholder='translate("Full FEN description")'/> - </fieldset> - <fieldset> - <p class="emphasis">{{ translate("Safe HTML tags allowed") }}</p> - <label for="newpbInstructions">{{ translate("Instructions") }}</label> - <textarea id="newpbInstructions" v-model="modalProb.instructions" - :placeholder='translate("Describe the problem goal")'> - </textarea> - <label for="newpbSolution">{{ translate("Solution") }}</label> - <textarea id="newpbSolution" v-model="modalProb.solution" - :placeholder='translate("How to solve the problem?")'> - </textarea> - <button class="center-btn">{{ translate("Preview") }}</button> - </fieldset> - </form> - </div> - <div v-show="modalProb.preview" class="card newproblem-preview"> - <label for="modal-newproblem" class="modal-close" - @click="modalProb.preview=false"> - </label> - <my-problem-summary :prob="modalProb" :userid="userId" :preview="true"> - </my-problem-summary> - <div class="button-group"> - <button @click="modalProb.preview=false">{{ translate("Cancel") }}</button> - <button @click="sendProblem()">{{ translate("Send") }}</button> - </div> - </div> - </div> - <input id="modalNomore" type="checkbox" class="modal"/> - <div role="dialog" aria-labelledby="nomoreMessage"> - <div class="card smallpad small-modal text-center"> - <label for="modalNomore" class="modal-close"></label> - <h3 id="nomoreMessage" class="section">{{ nomoreMessage }}</h3> - </div> - </div> <div id="problemControls" class="button-group"> <button :aria-label='translate("Previous problem(s)")' class="tooltip" @click="showNext('backward')" @@ -309,58 +263,5 @@ Vue.component('my-problems', { } ); }, - previewProblem: function() { - if (!V.IsGoodFen(this.modalProb.fen)) - return alert(translations["Bad FEN description"]); - if (this.modalProb.instructions.trim().length == 0) - return alert(translations["Empty instructions"]); - if (this.modalProb.solution.trim().length == 0) - return alert(translations["Empty solution"]); - Vue.set(this.modalProb, "preview", true); - }, - editProblem: function(prob) { - this.modalProb = prob; - Vue.set(this.modalProb, "preview", false); - document.getElementById("modal-newproblem").checked = true; - }, - deleteProblem: function(pid) { - ajax( - "/problems/" + pid, - "DELETE", - response => { - // Delete problem from the list on client side - let problems = this.curProblems(); - const pIdx = problems.findIndex(p => p.id == pid); - problems.splice(pIdx, 1); - } - ); - }, - sendProblem: function() { - // Send it to the server and close modal - ajax( - "/problems/" + variant.id, - (this.modalProb.id > 0 ? "PUT" : "POST"), - this.modalProb, - response => { - document.getElementById("modal-newproblem").checked = false; - Vue.set(this.modalProb, "preview", false); - if (this.modalProb.id == 0) - { - this.myProblems.unshift({ - added: Date.now(), - id: response.id, - uid: user.id, - fen: this.modalProb.fen, - instructions: this.modalProb.instructions, - solution: this.modalProb.solution, - }); - if (!this.curProb && this.display != "mine") - this.display = "mine"; - } - else - this.modalProb.id = 0; - } - ); - }, }, }) diff --git a/client/src/App.vue b/client/src/App.vue index a9c1d4cc..e06a2fef 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -8,7 +8,7 @@ .row .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 // Menu (top of page): - // Left: hall, variants, mygames, problems + // Left: hall, variants, mygames, forum (ext. link) // Right: usermenu, settings, flag nav label.drawer-toggle(for="drawerControl") @@ -22,8 +22,9 @@ | {{ st.tr["Variants"] }} router-link(to="/mygames") | {{ st.tr["My games"] }} - router-link(to="/problems") - | {{ st.tr["Problems"] }} + // TODO: parametric URL, "forumURL" + a(href="https://forum.vchess.club") + | {{ st.tr["Forum"] }} #rightMenu .clickable(onClick="doClick('modalUser')") | {{ st.user.id > 0 ? "Update" : "Login" }} diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue index 8481048d..2b7f6573 100644 --- a/client/src/components/GameList.vue +++ b/client/src/components/GameList.vue @@ -8,8 +8,8 @@ table th(v-if="showResult") Result tr(v-for="g in games" @click="$emit('show-game',g)") td {{ g.vname }} - td {{ g.players[0] }} - td {{ g.players[1] }} + td {{ g.players[0].name || "@nonymous" }} + td {{ g.players[1].name || "@nonymous" }} td {{ g.timeControl }} td(v-if="showResult") {{ g.score }} </template> @@ -20,7 +20,7 @@ export default { props: ["games"], computed: { showResult: function() { - return this.games.length > 0 && this.games[0].score != "*"; + return this.games.some(g => g.score != "*"); }, }, }; diff --git a/client/src/router.js b/client/src/router.js index 49f777d0..26bdee94 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -55,17 +55,26 @@ const router = new Router({ component: Hall, //redirect: "/", //problem: redirection before end of AJAX request }, + { + path: "/mygames", + name: "mygames", + component: loadView("MyGames"), + }, { path: "/game/:id", name: "game", component: loadView("Game"), }, + { + path: "/analyze/:vname([a-zA-Z0-9]+)", + name: "analyze", + component: loadView("Game"), + }, { path: "/about", name: "about", component: loadView("About"), }, - // TODO: myGames, problemId: https://router.vuejs.org/guide/essentials/dynamic-matching.html ] }); diff --git a/client/src/stylesheets/variant.sass b/client/src/stylesheets/variant.sass index 4c00a626..a1181597 100644 --- a/client/src/stylesheets/variant.sass +++ b/client/src/stylesheets/variant.sass @@ -172,50 +172,4 @@ button.seek margin: 10px 0 // Rules section: - - -// Problems section: - -.newproblem-form input, .newproblem-form textarea - width: 100% - -.emphasis - font-style: italic - -#newpbInstructions - margin-bottom: var(--universal-margin); - -.center-btn - margin-left: 40% - -//TODO? -.center-inline - text-align: center -.center-block - margin-left: auto - margin-right: auto - -.mistake-newproblem - color: #663300 - -#solution-div h3 - background-color: lightgrey - padding: 3px 5px - -.newproblem-form, .newproblem-preview - max-width: 90% - -#problemControls - width: 75% - margin: 0 auto - @media screen and (max-width: 767px) - width: 100% - margin: 0 - -.problem - margin: 10px 0 - -.only-mine - background-color: yellow - &:hover - background-color: yellow +// TODO diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 0e3b668c..b4af338e 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -3,7 +3,7 @@ export const translations = "Hall": "Hall", "Variants": "Variants", "My games": "My games", - "Problems": "Problems", + "Forum": "Forum", "Contact form": "Contact form", "Source code": "Source code", @@ -73,23 +73,7 @@ export const translations = "Type here": "Type here", "Send": "Send", "Download PGN": "Download PGN", - "Show solution": "Show solution", - "Load previous problems": "Load previous problems", - "Load next problems": "Load next problems", - "New": "New", - "Add a problem": "Add a problem", - "Full FEN description": "Full FEN description", - "Safe HTML tags allowed": "Safe HTML tags allowed", - "Instructions": "Instructions", - "Describe the problem goal": "Describe the problem goal", - "Solution": "Solution", - "How to solve the problem?": "How to solve the problem?", - "Preview": "Preview", "Cancel": "Cancel", - "Solve": "Solve", - "Bad FEN description": "Bad FEN description", - "Empty instructions": "Empty instructions", - "Empty solution": "Empty solution", "Already playing a game in this variant on another tab!": "Already playing a game in this variant on another tab!", "Finish your ": "Finish your ", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 4fa79575..815c2aa2 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -3,7 +3,7 @@ export const translations = "Hall": "Hall", "Variants": "Variantes", "My games": "Mes parties", - "Problems": "Problèmes", + "Forum": "Forum", "Contact form": "Formulaire de contact", "Source code": "Code source", @@ -67,23 +67,7 @@ export const translations = "Type here": "Ãcrivez ici", "Send": "Envoyer", "Download PGN": "Télécharger le PGN", - "Show solution": "Montrer la solution", - "Load previous problems": "Charger les problèmes précédents", - "Load next problems": "Charger les problèmes suivants", - "New": "Nouveau", - "Add a problem": "Ajouter un problème", - "Full FEN description": "Description FEN complète", - "Safe HTML tags allowed": "HTML 'sûr' autorisé", - "Instructions": "Instructions", - "Describe the problem goal": "Décrire le but du problème", - "Solution": "Solution", - "How to solve the problem?": "Comment résoudre le problème ?", - "Preview": "Prévisualiser", "Cancel": "Annuler", - "Solve": "Résoudre", - "Bad FEN string": "Mauvaise description FEN", - "Empty instructions": "Instructions vides", - "Empty solution": "Solution vide", "Already playing a game in this variant on another tab!": "Une partie est en cours sur cette variante dans un autre onglet !", "Finish your ": "Terminez votre ", diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index b10be754..1cb9af58 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -12,7 +12,7 @@ BaseGame(:game="game" :vr="vr" ref="basegame" @newmove="processMove" @gameover="gameOver") div Names: {{ game.players[0].name }} - {{ game.players[1].name }} - div Time: {{ virtualClocks[0] }} - {{ virtualClocks[1] }} + div(v-if="game.score=='*'") Time: {{ virtualClocks[0] }} - {{ virtualClocks[1] }} .button-group(v-if="game.mode!='analyze' && game.score=='*'") button(@click="offerDraw") Draw button(@click="() => abortGame()") Abort @@ -24,7 +24,6 @@ // TODO: movelist dans basegame et chat ici // ==> après, implémenter/vérifier les passages de challenges + parties en cours // observer, -// + problèmes, habiller et publier. (+ corr...) // when send to chat (or a move), reach only this group (send gid along) --> @@ -111,6 +110,8 @@ export default { this.gameRef.rid = this.$route.query["rid"]; this.loadGame(); } + // TODO: mode analyse (/analyze/Atomic/rn + // ... fen = query[], vname=params[] ... // 0.1] Ask server for room composition: const funcPollClients = () => { this.st.conn.send(JSON.stringify({code:"pollclients"})); diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index c630668f..d601bf04 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -1,5 +1,68 @@ - // TODO: AJAX call get corr games (all variants) - // si dernier lastMove sur serveur n'est pas le mien et nextColor == moi, alors background orange - // ==> background orange si à moi de jouer par corr (sur main index) - // (helper: static fonction "GetNextCol()" dans base_rules.js) -//use GameStorage.getAll() +<template lang="pug"> +main + .row + .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 + .button-group + button(@click="display='live'") Live games + button(@click="display='corr'") Correspondance games + GameList(v-show="display=='live'" :games="filterGames('live')" + @show-game="showGame") + GameList(v-show="display=='corr'" :games="filterGames('corr')" + @show-game="showGame") +</template> + +<script> +// TODO: background orange si à moi de jouer +// (helper: static fonction "GetNextCol()" dans base_rules.js) +// use GameStorage.getAll() + +import { store } from "@/store"; +import { GameStorage } from "@/utils/gameStorage"; +import { ajax } from "@/utils/ajax"; +import GameList from "@/components/GameList.vue"; +export default { + name: "my-games", + components: { + GameList, + }, + data: function() { + return { + st: store.state, + display: "live", + games: [], + }; + }, + created: function() { + GameStorage.getAll((localGames) => { + localGames.forEach((g) => g.type = this.classifyObject(g)); + Array.prototype.push.apply(this.games, localGames); + }); + if (this.st.user.id > 0) + { + ajax("/games", "GET", {uid: this.st.user.id}, (res) => { + res.games.forEach((g) => g.type = this.classifyObject(g)); + //Array.prototype.push.apply(this.games, res.games); //TODO: Vue 3 + this.games = this.games.concat(res.games); + }); + } + }, + methods: { + // TODO: classifyObject and filterGames are redundant (see Hall.vue) + classifyObject: function(o) { + return (o.timeControl.indexOf('d') === -1 ? "live" : "corr"); + }, + filterGames: function(type) { + return this.games.filter(g => g.type == type); + }, + showGame: function(g) { + // NOTE: we play in this game, since this is "MyGames" page + this.$router.push("/game/" + g.id); + }, + }, +}; +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped lang="sass"> +/* TODO */ +</style> diff --git a/server/models/Game.js b/server/models/Game.js index 786f1e78..0949fc98 100644 --- a/server/models/Game.js +++ b/server/models/Game.js @@ -98,19 +98,22 @@ const GameModel = "SELECT gid " + "FROM Players " + "WHERE uid " + (excluded ? "<>" : "=") + " " + uid; - db.run(query, (err,gameIds) => { + db.all(query, (err,gameIds) => { if (!!err) return cb(err); gameIds = gameIds || []; //might be empty let gameArray = []; - gameIds.forEach(gidRow => { - GameModel.getOne(gidRow["gid"], (err2,game) => { + for (let i=0; i<gameIds.length; i++) + { + GameModel.getOne(gameIds[i]["gid"], (err2,game) => { if (!!err2) return cb(err2); gameArray.push(game); + // Call callback function only when gameArray is complete: + if (i == gameIds.length - 1) + return cb(null, gameArray); }); - }); - return cb(null, gameArray); + } }); }); }, diff --git a/server/routes/games.js b/server/routes/games.js index 9bd9a30b..a444acdf 100644 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -44,7 +44,7 @@ router.get("/games", access.ajax, (req,res) => { const userId = req.query["uid"]; const excluded = !!req.query["excluded"]; GameModel.getByUser(userId, excluded, (err,games) => { - if (!!err) + if (!!err) return res.json({errmsg: err.errmsg || err.toString()}); res.json({games: games}); }); -- 2.48.1