Vue.component('my-problems', { props: ["probId","settings"], data: function () { return { userId: user.id, problems: [], //oldest first myProblems: [], //same, but only mine singletons: [], //requested problems (using #num) display: "others", //or "mine" curProb: null, //(reference to) current displayed problem (if any) showSolution: false, nomoreMessage: "", mode: "analyze", //for game component pbNum: 0, //to navigate directly to some problem // New problem (to upload), or existing problem to edit: modalProb: { id: 0, //defined if it's an edit uid: 0, //...also fen: "", instructions: "", solution: "", preview: false, }, }; }, // NOTE: always modals first, because otherwise "scroll to the end" undesirable effect template: `

{{ translate("Add a problem") }}

{{ translate("Safe HTML tags allowed") }}

{{ nomoreMessage }}

{{ curProb.instructions }}

{{ translate("Show solution") }}

{{ curProb.solution }}

`, watch: { probId: function() { this.showProblem(this.probId); }, }, created: function() { if (!!this.probId) this.showProblem(this.probId); else this.firstFetch(); }, methods: { translate: translate, firstFetch: function() { // Fetch most recent problems from server, for both lists this.fetchProblems("others", "bacwkard"); this.fetchProblems("mine", "bacwkard"); this.listsInitialized = true; }, showProblem: function(pid) { location.hash = "#problems?id=" + pid; for (let parray of [this.singletons,this.problems,this.myProblems]) { const pIdx = parray.findIndex(p => p.id == pid); if (pIdx >= 0) { this.curProb = parray[pIdx]; break; } } if (!this.curProb) { // Cannot find problem in current set; get from server, and add to singletons. ajax( "/problems/" + variant.id + "/" + pid, //TODO: variant ID should not be required "GET", response => { if (!!response.problem) { this.singletons.push(response.problem); this.curProb = response.problem; this.display = (response.problem.uid == this.userId ? "mine" : "others"); } else this.noMoreProblems("Sorry, problem " + pid + " does not exist"); } ); } else this.display = (this.curProb.uid == this.userId ? "mine" : "others"); }, curProblems: function() { switch (this.display) { case "others": return this.problems; case "mine": return this.myProblems; } }, // TODO?: get 50 from server but only show 10 at a time (for example) showNext: function(direction) { const nomorePb = problems => { if (!problems || problems.length == 0) this.noMoreProblems("No more problems in this direction"); }; if (!this.curProb) return this.fetchProblems(this.display, direction, nomorePb); // Show next problem (older or newer): let curProbs = this.curProblems(); // Try to find a neighbour problem in the direction, among current set const neighbor = this.findClosestNeighbor(this.curProb, curProbs, direction); if (!!neighbor) { this.curProb = neighbor; return; } // Boundary case: nothing in current set, need to fetch from server const curSize = curProbs.length; this.fetchProblems(this.display, direction, problems => { if (problems.length > 0) { // Ok, found something: this.curProb = this.findClosestNeighbor(this.curProb, curProbs, direction); } else nomorePb(); }); }, findClosestNeighbor: function(problem, probList, direction) { let neighbor = undefined; let smallestDistance = Number.MAX_SAFE_INTEGER; for (let prob of probList) { const delta = Math.abs(prob.id - problem.id); if (delta < smallestDistance && ((direction == "backward" && prob.id < problem.id) || (direction == "forward" && prob.id > problem.id))) { neighbor = prob; smallestDistance = delta; } } return neighbor; }, noMoreProblems: function(message) { this.nomoreMessage = message; let modalNomore = document.getElementById("modalNomore"); modalNomore.checked = true; setTimeout(() => modalNomore.checked = false, 2000); }, displayList: function() { this.curProb = null; location.hash = "#problems"; // Fetch problems if first call (if #num, and then lists) if (!this.listsInitialized) this.firstFetch(); }, toggleListDisplay: function() { const displays = ["mine","others"]; const curIndex = displays.findIndex(item => item == this.display); this.display = displays[1-curIndex]; }, fetchProblems: function(type, direction, cb) { let problems = (type == "others" ? this.problems : this.myProblems); // "last datetime" set at a value OK for an empty initial array let last_dt = (direction=="forward" ? 0 : Number.MAX_SAFE_INTEGER); if (problems.length > 0) { // Search for newest date (or oldest) last_dt = problems[0].added; for (let i=1; i last_dt) || (direction == "backward" && problems[i].added < last_dt)) { last_dt = problems[i].added; } } } ajax( "/problems/" + variant.id, "GET", { type: type, direction: direction, last_dt: last_dt, }, response => { if (response.problems.length > 0) { Array.prototype.push.apply(problems, response.problems.sort( (p1,p2) => { return p2.added - p1.added; })); // If one list is empty but not the other, show the non-empty const otherArray = (type == "mine" ? this.problems : this.myProblems); if (otherArray.length == 0) this.display = type; this.$forceUpdate(); //TODO... } if (!!cb) cb(response.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; } ); }, }, })