X-Git-Url: https://git.auder.net/?p=vchess.git;a=blobdiff_plain;f=client%2Fsrc%2Fviews%2FProblems.vue;h=b989fbcb19024006b4d9366733acf016fdd9970f;hp=1e8ff6895344fceb26e85c3d9050d1e91e8b7314;hb=ad16f8397b0d8a22897537e2f76b4388182a84af;hpb=bec548e70ef8ff16d89b87e769e86a2de408d0d8 diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue index 1e8ff689..b989fbcb 100644 --- a/client/src/views/Problems.vue +++ b/client/src/views/Problems.vue @@ -1,8 +1,18 @@ @@ -110,7 +127,8 @@ main import { store } from "@/store"; import { ajax } from "@/utils/ajax"; import { checkProblem } from "@/data/problemCheck"; -import { getDiagram } from "@/utils/printDiagram"; +import params from "@/parameters"; +import { getDiagram, replaceByDiag } from "@/utils/printDiagram"; import { processModalClick } from "@/utils/modalClick"; import { ArrayFun } from "@/utils/array"; import BaseGame from "@/components/BaseGame.vue"; @@ -138,11 +156,18 @@ export default { }, loadedVar: 0, //corresponding to loaded V selectedVar: 0, //to filter problems based on variant - problems: [], - onlyMines: false, + problems: { "mine": [], "others": [] }, + // timestamp of oldest showed problem: + cursor: { + mine: Number.MAX_SAFE_INTEGER, + others: Number.MAX_SAFE_INTEGER + }, + // hasMore == TRUE: a priori there could be more problems to load + hasMore: { mine: true, others: true }, + onlyMine: false, showOne: false, infoMsg: "", - vr: null, //"variant rules" object initialized from FEN + rulesContent: "", game: { players: [{ name: "Problem" }, { name: "Problem" }], mode: "analyze" @@ -150,55 +175,82 @@ export default { }; }, created: function() { - ajax("/problems", "GET", res => { - // Show newest problem first: - this.problems = res.problems.sort((p1, p2) => p2.added - p1.added); - if (this.st.variants.length > 0) - this.problems.forEach(p => this.setVname(p)); - // Retrieve all problems' authors' names - let names = {}; - this.problems.forEach(p => { - if (p.uid != this.st.user.id) names[p.uid] = ""; - //unknwon for now - else p.uname = this.st.user.name; - }); - const showOneIfPid = () => { - const pid = this.$route.query["id"]; - if (pid) this.showProblem(this.problems.find(p => p.id == pid)); - }; - if (Object.keys(names).length > 0) { - ajax("/users", "GET", { ids: Object.keys(names).join(",") }, res2 => { - res2.users.forEach(u => { - names[u.id] = u.name; - }); - this.problems.forEach(p => (p.uname = names[p.uid])); - showOneIfPid(); - }); - } else showOneIfPid(); - }); + const pid = this.$route.query["id"]; + if (!!pid) this.showProblem(pid); + else this.loadMore("others", () => { this.loadMore("mine"); }); }, mounted: function() { - document - .getElementById("newprobDiv") - .addEventListener("click", processModalClick); + ["rulesDiv","newprobDiv"].forEach(eltName => { + document.getElementById(eltName) + .addEventListener("click", processModalClick) + }); }, watch: { // st.variants changes only once, at loading from [] to [...] "st.variants": function() { // Set problems vname (either all are set or none) - if (this.problems.length > 0 && this.problems[0].vname == "") - this.problems.forEach(p => this.setVname(p)); + let problems = this.problems["others"].concat(this.problems["mine"]); + if (problems.length > 0 && problems[0].vname == "") + problems.forEach(p => this.setVname(p)); }, $route: function(to) { const pid = to.query["id"]; - if (pid) this.showProblem(this.problems.find(p => p.id == pid)); - else this.showOne = false; + if (!!pid) this.showProblem(pid); + else { + if (this.cursor["others"] == Number.MAX_SAFE_INTEGER) + // Back from a single problem view at initial loading: + // problems lists are empty! + this.loadMore("others", () => { this.loadMore("mine"); }); + this.showOne = false; + } } }, methods: { + fenFocusIfOpened: function(event) { + if (event.target.checked) { + this.infoMsg = ""; + document.getElementById("inputFen").focus(); + } + }, + adjustHeight: function(elt) { + // https://stackoverflow.com/a/48460773 + let t = document.querySelector("." + elt + "-edit"); + t.style.height = ""; + t.style.height = (t.scrollHeight + 3) + "px"; + }, setVname: function(prob) { prob.vname = this.st.variants.find(v => v.id == prob.vid).name; }, + // Add vname and user names: + decorate: function(problems, callback) { + if (this.st.variants.length > 0) + problems.forEach(p => this.setVname(p)); + // Retrieve all problems' authors' names + let names = {}; + problems.forEach(p => { + if (p.uid != this.st.user.id) names[p.uid] = ""; + else p.uname = this.st.user.name; + }); + if (Object.keys(names).length > 0) { + ajax( + "/users", + "GET", + { + data: { ids: Object.keys(names).join(",") }, + success: (res2) => { + res2.users.forEach(u => { + names[u.id] = u.name; + }); + problems.forEach(p => { + if (!p.uname) + p.uname = names[p.uid]; + }); + if (!!callback) callback(); + } + } + ); + } else if (!!callback) callback(); + }, firstChars: function(text) { let preparedText = text // Replace line jumps and
by spaces @@ -237,7 +289,10 @@ export default { }, parseHtml: function(txt) { return !txt.match(/<[/a-zA-Z]+>/) - ? txt.replace(/\n/g, "
") //no HTML tag + ? + // No HTML tag + txt.replace(/\n\n/g, "
") + .replace(/\n/g, "
") : txt; }, changeVariant: function(prob) { @@ -245,22 +300,39 @@ export default { this.loadVariant(prob.vid, () => { // Set FEN if possible (might not be correct yet) if (V.IsGoodFen(prob.fen)) this.setDiagram(prob); + else prob.diag = ""; }); }, loadVariant: async function(vid, cb) { // Condition: vid is a valid variant ID this.loadedVar = 0; const variant = this.st.variants.find(v => v.id == vid); - const vModule = await import("@/variants/" + variant.name + ".js"); - window.V = vModule.VariantRules; - this.loadedVar = vid; - cb(); + await import("@/variants/" + variant.name + ".js") + .then((vModule) => { + window.V = vModule[variant.name + "Rules"]; + this.loadedVar = vid; + cb(); + }); + // (AJAX) Request to get rules content (plain text, HTML) + this.rulesContent = + require( + "raw-loader!@/translations/rules/" + + variant.name + "/" + + this.st.lang + ".pug" + ) + // Next two lines fix a weird issue after last update (2019-11) + .replace(/\\n/g, " ") + .replace(/\\"/g, '"') + .replace('module.exports = "', "") + .replace(/"$/, "") + .replace(/(fen:)([^:]*):/g, replaceByDiag); }, trySetDiagram: function(prob) { // Problem edit: FEN could be wrong or incomplete, // variant could not be ready, or not defined if (prob.vid > 0 && this.loadedVar == prob.vid && V.IsGoodFen(prob.fen)) this.setDiagram(prob); + else prob.diag = ""; }, setDiagram: function(prob) { // Condition: prob.fen is correct and global V is ready @@ -271,24 +343,71 @@ export default { }; prob.diag = getDiagram(args); }, - displayProblem: function(p) { - return ( - (!this.selectedVar || p.vid == this.selectedVar) && - ((this.onlyMines && p.uid == this.st.user.id) || - (!this.onlyMines && p.uid != this.st.user.id)) - ); + showProblem: function(p_id) { + const processWhenWeHaveProb = () => { + this.loadVariant(p.vid, () => { + this.onlyMine = (p.uid == this.st.user.id); + // The FEN is already checked at this stage: + this.game.vname = p.vname; + this.game.mycolor = V.ParseFen(p.fen).turn; //diagram orientation + this.game.fenStart = p.fen; + this.game.fen = p.fen; + this.showOne = true; + // $nextTick to be sure $refs["basegame"] exists + this.$nextTick(() => { + this.$refs["basegame"].re_setVariables(this.game); }); + this.curproblem.showSolution = false; //in case of + this.copyProblem(p, this.curproblem); + }); + }; + let p = undefined; + if (typeof p_id == "object") p = p_id; + else { + const problems = this.problems["others"].concat(this.problems["mine"]); + p = problems.find(prob => prob.id == p_id); + } + if (!p) { + // Bad luck: problem not in list. Get from server + ajax( + "/problems", + "GET", + { + data: { id: p_id }, + success: (res) => { + this.decorate([res.problem], () => { + p = res.problem; + const mode = (p.uid == this.st.user.id ? "mine" : "others"); + this.problems[mode].push(p); + processWhenWeHaveProb(); + }); + } + } + ); + } else processWhenWeHaveProb(); }, - showProblem: function(p) { - this.loadVariant(p.vid, () => { - // The FEN is already checked at this stage: - this.vr = new V(p.fen); - this.game.vname = p.vname; - this.game.mycolor = this.vr.turn; //diagram orientation - this.game.fen = p.fen; - this.$set(this.game, "fenStart", p.fen); - this.copyProblem(p, this.curproblem); - this.showOne = true; - }); + gotoPrevNext: function(prob, dir) { + const mode = (this.onlyMine ? "mine" : "others"); + const problems = this.problems[mode]; + const startIdx = problems.findIndex(p => p.id == prob.id); + const nextIdx = startIdx + dir; + if (nextIdx >= 0 && nextIdx < problems.length) + this.setHrefPid(problems[nextIdx]); + else if (this.hasMore[mode]) { + this.loadMore( + mode, + (nbProbs) => { + if (nbProbs > 0) this.gotoPrevNext(prob, dir); + else alert(this.st.tr["No more problems"]); + } + ); + } + else alert(this.st.tr["No more problems"]); + }, + prepareNewProblem: function() { + this.resetCurProb(); + this.adjustHeight("instructions"); + this.adjustHeight("solution"); + window.doClick("modalNewprob"); }, sendProblem: function() { const error = checkProblem(this.curproblem); @@ -301,59 +420,141 @@ export default { ajax( "/problems", edit ? "PUT" : "POST", - { prob: this.curproblem }, - ret => { - if (edit) { - let editedP = this.problems.find(p => p.id == this.curproblem.id); - this.copyProblem(this.curproblem, editedP); - } - else { - // New problem - let newProblem = Object.assign({}, this.curproblem); - newProblem.id = ret.id; - newProblem.uid = this.st.user.id; - newProblem.uname = this.st.user.name; - this.problems = [newProblem].concat(this.problems); - this.resetCurProb(); + { + data: { prob: this.curproblem }, + success: (ret) => { + if (edit) { + let editedP = this.problems["mine"] + .find(p => p.id == this.curproblem.id); + if (!editedP) + // I'm an admin and edit another user' problem + editedP = this.problems["others"] + .find(p => p.id == this.curproblem.id); + this.copyProblem(this.curproblem, editedP); + this.showProblem(editedP); + } + else { + let newProblem = Object.assign({}, this.curproblem); + newProblem.id = ret.id; + newProblem.uid = this.st.user.id; + newProblem.uname = this.st.user.name; + this.problems["mine"] = + [newProblem].concat(this.problems["mine"]); + } + document.getElementById("modalNewprob").checked = false; + this.infoMsg = ""; } - this.infoMsg = ""; - document.getElementById("modalNewprob").checked = false; } ); }, + canIedit: function(puid) { + return params.devs.concat([puid]).includes(this.st.user.id); + }, editProblem: function(prob) { // prob.diag might correspond to some other problem or be empty: this.setDiagram(prob); //V is loaded at this stage this.copyProblem(prob, this.curproblem); + this.adjustHeight("instructions"); + this.adjustHeight("solution"); window.doClick("modalNewprob"); }, deleteProblem: function(prob) { if (confirm(this.st.tr["Are you sure?"])) { - ajax("/problems", "DELETE", { id: prob.id }, () => { - ArrayFun.remove(this.problems, p => p.id == prob.id); - this.backToList(); - }); + ajax( + "/problems", + "DELETE", + { + data: { id: prob.id }, + success: () => { + const mode = prob.uid == (this.st.user.id ? "mine" : "others"); + ArrayFun.remove(this.problems[mode], p => p.id == prob.id); + this.backToList(); + } + } + ); } + }, + loadMore: function(mode, cb) { + ajax( + "/problems", + "GET", + { + data: { + uid: this.st.user.id, + mode: mode, + cursor: this.cursor[mode] + }, + success: (res) => { + const L = res.problems.length; + if (L > 0) { + this.cursor[mode] = res.problems[L - 1].added; + // Remove potential duplicates: + const pids = this.problems[mode].map(p => p.id); + ArrayFun.remove(res.problems, p => pids.includes(p.id), "all"); + this.decorate(res.problems); + this.problems[mode] = + this.problems[mode].concat(res.problems) + // TODO: problems are already sorted, would just need to insert + // the current individual problem in list; more generally + // there is probably only one misclassified problem. + // (Unless the user navigated several times by URL to show a + // single problem...) + .sort((p1, p2) => p2.added - p1.added); + } else this.hasMore[mode] = false; + if (!!cb) cb(L); + } + } + ); } } }; + +