Some debug, all components enabled (but not finished: socket+AJAX missing)
[vchess.git] / public / javascripts / components / problems.js
CommitLineData
4ecf423b 1Vue.component('my-problems', {
582df349 2 props: ["queryHash","settings"],
da06a6eb
BA
3 data: function () {
4 return {
ff1d4c3f 5 userId: user.id,
81da2786 6 problems: [], //oldest first
ff1d4c3f 7 myProblems: [], //same, but only mine
936dc463
BA
8 singletons: [], //requested problems (using #num)
9 display: "others", //or "mine"
10 curProb: null, //(reference to) current displayed problem (if any)
ff1d4c3f 11 showSolution: false,
60d9063f 12 nomoreMessage: "",
936dc463 13 pbNum: 0, //to navigate directly to some problem
ff1d4c3f
BA
14 // New problem (to upload), or existing problem to edit:
15 modalProb: {
16 id: 0, //defined if it's an edit
77fa6d1f 17 fen: "",
45109880
BA
18 instructions: "",
19 solution: "",
a9f262f3 20 preview: false,
45109880 21 },
da06a6eb
BA
22 };
23 },
4ecf423b 24 template: `
a5d56686
BA
25 <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
26 <div id="problemControls" class="button-group">
ff1d4c3f 27 <button :aria-label='translate("Previous problem(s)")' class="tooltip" @click="showNext('backward')">
a5d56686
BA
28 <i class="material-icons">skip_previous</i>
29 </button>
ff1d4c3f 30 <button :aria-label='translate("Add a problem")' class="tooltip" onClick="doClick('modal-newproblem')">
247356cd 31 {{ translate("New") }}
a5d56686 32 </button>
ff1d4c3f 33 <button :aria-label='translate("Next problem(s)")' class="tooltip" @click="showNext('forward')">
a5d56686
BA
34 <i class="material-icons">skip_next</i>
35 </button>
36 </div>
60d9063f 37 <div id="mainBoard" v-if="!!curProb">
ff1d4c3f 38 <div id="instructions-div" class="section-content">
8ef618ef
BA
39 <p id="problem-instructions">
40 {{ curProb.instructions }}
41 </p>
ff1d4c3f 42 </div>
582df349 43 <my-game :fen="curProb.fen" :mode="analyze" :allowMovelist="true" :settings="settings">
60d9063f 44 </my-game>
ff1d4c3f
BA
45 <div id="solution-div" class="section-content">
46 <h3 class="clickable" @click="showSolution = !showSolution">
47 {{ translations["Show solution"] }}
48 </h3>
8ef618ef
BA
49 <p id="problem-solution" v-show="showSolution">
50 {{ curProb.solution }}
51 </p>
ff1d4c3f 52 </div>
936dc463
BA
53 <button @click="displayList()">
54 <span>Back to list display</span>
55 </button>
56 </div>
57 <div>
58 <input type="text" placeholder="Type problem number" v-model="pbNum"/>
59 <button @click="showProblem()">
60 <span>Show problem</span>
61 </button>
ff1d4c3f 62 </div>
8ef618ef
BA
63 <button v-if="!!userId" @click="toggleListDisplay()">
64 <span>My problems (only)</span>
65 </button>
936dc463
BA
66 <my-problem-summary v-show="!curProb"
67 v-on:edit-problem="editProblem(p)" v-on:delete-problem="deleteProblem(p.id)"
68 v-for="p in curProblems" @click="curProb=p"
8ef618ef 69 v-bind:prob="p" v-bind:userid="userId" v-bind:key="p.id">
da06a6eb 70 </my-problem-summary>
8ef618ef 71 <input type="checkbox" id="modal-newproblem" class="modal"/>
ff1d4c3f
BA
72 <div role="dialog" aria-labelledby="modalProblemTxt">
73 <div v-show="!modalProb.preview" class="card newproblem-form">
8ef618ef
BA
74 <label for="modal-newproblem" class="modal-close">
75 </label>
76 <h3 id="modalProblemTxt">
77 {{ translate("Add a problem") }}
78 </h3>
79 <form @submit.prevent="previewProblem()">
da06a6eb 80 <fieldset>
247356cd 81 <label for="newpbFen">FEN</label>
ff1d4c3f 82 <input id="newpbFen" type="text" v-model="modalProb.fen"
e081ffe3 83 :placeholder='translate("Full FEN description")'/>
da06a6eb
BA
84 </fieldset>
85 <fieldset>
8ef618ef
BA
86 <p class="emphasis">
87 {{ translate("Safe HTML tags allowed") }}
88 </p>
89 <label for="newpbInstructions">
90 {{ translate("Instructions") }}
91 </label>
ff1d4c3f 92 <textarea id="newpbInstructions" v-model="modalProb.instructions"
8ef618ef
BA
93 :placeholder='translate("Describe the problem goal")'>
94 </textarea>
95 <label for="newpbSolution">
96 {{ translate("Solution") }}
97 </label>
ff1d4c3f 98 <textarea id="newpbSolution" v-model="modalProb.solution"
8ef618ef
BA
99 :placeholder='translate("How to solve the problem?")'>
100 </textarea>
101 <button class="center-btn">
102 {{ translate("Preview") }}
103 </button>
da06a6eb 104 </fieldset>
da06a6eb
BA
105 </form>
106 </div>
ff1d4c3f 107 <div v-show="modalProb.preview" class="card newproblem-preview">
8ef618ef
BA
108 <label for="modal-newproblem" class="modal-close">
109 </label>
110 <my-problem-summary v-bind:prob="modalProb" v-bind:userid="userId">
111 </my-problem-summary>
a5d56686 112 <div class="button-group">
8ef618ef
BA
113 <button @click="modalProb.preview=false">
114 {{ translate("Cancel") }}
115 </button>
116 <button @click="sendProblem()">
117 {{ translate("Send") }}
118 </button>
b5fb8e69 119 </div>
45109880 120 </div>
da06a6eb 121 </div>
936dc463
BA
122 <input id="modalNomore" type="checkbox" class="modal"/>
123 <div role="dialog" aria-labelledby="nomoreMessage">
124 <div class="card smallpad small-modal text-center">
125 <label for="modalNomore" class="modal-close"></label>
126 <h3 id="nomoreMessage" class="section">
127 {{ nomoreMessage }}
128 </h3>
129 </div>
130 </div>
4ecf423b
BA
131 </div>
132 `,
582df349
BA
133 watch: {
134 queryHash: function(newQhash) {
135 if (!!newQhash)
136 {
137 // New query hash = "id=42"; get 42 as problem ID
138 const pid = parseInt(newQhash.substr(2));
139 this.showProblem(pid);
140 }
141 else
142 this.curProb = null; //(back to) list display
143 },
144 },
298c42e6 145 created: function() {
582df349
BA
146 if (!!this.queryHash)
147 {
148 const pid = parseInt(this.queryHash.substr(2));
149 this.showProblem(pid);
150 }
ff1d4c3f 151 else
936dc463 152 this.firstFetch();
298c42e6 153 },
da06a6eb 154 methods: {
936dc463
BA
155 firstFetch: function() {
156 // Fetch most recent problems from server, for both lists
157 this.fetchProblems("others", "bacwkard");
158 this.fetchProblems("mine", "bacwkard");
159 this.listsInitialized = true;
160 },
161 showProblem: function(num) {
162 const pid = num || this.pbNum;
163 location.hash = "#" + pid;
164 const pIdx = this.singletons.findIndex(p => p.id == pid);
165 if (pIdx >= 0)
166 curProb = this.singletons[pIdx];
167 else
168 {
169 // Cannot find problem in current set; get from server, and add to singletons.
170 ajax(
171 "/problems/" + variant.name + "/" + pid, //TODO: use variant._id ?
172 "GET",
173 response => {
174 if (!!response.problem)
175 {
176 this.singletons.push(response.problem);
177 this.curProb = response.problem;
178 }
179 else
180 this.noMoreProblems("Sorry, problem " + pid + " does not exist");
181 }
182 );
183 }
ff1d4c3f 184 },
247356cd
BA
185 translate: function(text) {
186 return translations[text];
187 },
ff1d4c3f
BA
188 curProblems: function() {
189 switch (this.display)
190 {
936dc463 191 case "others":
ff1d4c3f 192 return this.problems;
936dc463 193 case "mine":
ff1d4c3f
BA
194 return this.myProblems;
195 }
81da2786 196 },
8ef618ef 197 // TODO?: get 50 from server but only show 10 at a time (for example)
ff1d4c3f 198 showNext: function(direction) {
936dc463
BA
199 if (!this.curProb)
200 return this.fetchProblems(this.display, direction);
8ef618ef 201 // Show next problem (older or newer):
ff1d4c3f 202 let curProbs = this.curProblems();
936dc463
BA
203 // Try to find a neighbour problem in the direction, among current set
204 const neighbor = this.findClosestNeighbor(this.curProb, curProbs, direction);
205 if (!!neighbor)
ff1d4c3f 206 {
936dc463
BA
207 this.curProb = neighbor;
208 return;
ff1d4c3f 209 }
936dc463
BA
210 // Boundary case: nothing in current set, need to fetch from server
211 const curSize = curProbs.length;
212 this.fetchProblems(this.display, direction);
213 const newSize = curProbs.length;
214 if (curSize == newSize) //no problems found
215 return this.noMoreProblems("No more problems in this direction");
216 // Ok, found something:
217 this.curProb = this.findClosestNeighbor(this.curProb, curProbs, direction);
218 },
219 findClosestNeighbor: function(problem, probList, direction) {
220 let neighbor = undefined;
221 let smallestDistance = Number.MAX_SAFE_INTEGER;
222 for (let prob of probList)
ff1d4c3f 223 {
936dc463
BA
224 const delta = Math.abs(prob.id - problem.id);
225 if (delta < smallestDistance &&
226 ((direction == "backward" && prob.id < problem.id)
227 || (direction == "forward" && prob.id > problem.id)))
8ef618ef 228 {
936dc463
BA
229 neighbor = prob;
230 smallestDistance = delta;
8ef618ef 231 }
ff1d4c3f 232 }
936dc463
BA
233 return neighbor;
234 },
235 noMoreProblems: function(message) {
236 this.nomoreMessage = message;
237 let modalNomore = document.getElementById("modalNomore");
238 modalNomore.checked = true;
239 setTimeout(() => modalNomore.checked = false, 2000);
240 },
241 displayList: function() {
242 this.curProb = null;
243 location.hash = "";
244 // Fetch problems if first call (if #num, and then lists)
245 if (!this.listsInitialized)
246 this.firstFetch();
ff1d4c3f
BA
247 },
248 toggleListDisplay: function() {
936dc463 249 this.display = (this.display == "others" ? "mine" : "others");
81da2786 250 },
936dc463
BA
251 fetchProblems: function(type, direction) {
252 let problems = (type == "others" ? this.problems : this.myProblems);
253 let last_dt = (direction=="forward" ? 0 : Number.MAX_SAFE_INTEGER);
254 if (this.problems.length > 0)
7931e479 255 {
936dc463
BA
256 // Search for newest date (or oldest)
257 last_dt = problems[0].added;
258 for (let i=1; i<problems.length; i++)
7931e479 259 {
936dc463
BA
260 if ((direction == "forward" && this.problems[i].added > last_dt) ||
261 (direction == "backward" && this.problems[i].added < last_dt))
262 {
263 last_dt = this.problems[i].added;
264 }
7931e479
BA
265 }
266 }
936dc463 267 ajax(
582df349 268 "/problems/" + variant.id,
936dc463 269 "GET",
81da2786 270 {
936dc463
BA
271 type: type,
272 direction: direction,
273 last_dt: last_dt,
274 },
275 response => {
276 if (response.problems.length > 0)
277 {
278 Array.prototype.push.apply(problems,
279 response.problems.sort((p1,p2) => { return p1.added - p2.added; }));
280 // If one list is empty but not the other, show the non-empty
281 const otherArray = (type == "mine" ? this.problems : this.myProblems);
282 if (problems.length > 0 && otherArray.length == 0)
283 this.display = type;
284 }
81da2786 285 }
936dc463 286 );
da06a6eb 287 },
8ef618ef 288 previewProblem: function() {
45109880 289 if (!V.IsGoodFen(this.newProblem.fen))
e081ffe3 290 return alert(translations["Bad FEN description"]);
6b5517b4
BA
291 if (this.newProblem.instructions.trim().length == 0)
292 return alert(translations["Empty instructions"]);
293 if (this.newProblem.solution.trim().length == 0)
294 return alert(translations["Empty solution"]);
ff1d4c3f 295 this.modalProb.preview = true;
45109880 296 },
936dc463
BA
297 editProblem: function(prob) {
298 this.modalProb = prob;
299 document.getElementById("modal-newproblem").checked = true;
300 },
301 deleteProblem: function(pid) {
302 ajax(
582df349 303 "/problems/" + variant.id + "/" + pid,
936dc463
BA
304 "DELETE",
305 response => {
306 // Delete problem from the list on client side
307 let problems = this.curProblems();
308 const pIdx = problems.findIndex(p => p.id == pid);
309 problems.splice(pIdx, 1);
310 }
311 );
312 },
8ef618ef 313 sendProblem: function() {
45109880 314 // Send it to the server and close modal
8ef618ef 315 ajax(
582df349 316 "/problems/" + variant.id,
8ef618ef
BA
317 (this.modalProb.id > 0 ? "PUT" : "POST"),
318 this.modalProb,
319 response => {
320 document.getElementById("modal-newproblem").checked = false;
321 if (this.modalProb.id == 0)
322 {
323 this.modalProb.added = Date.now();
324 this.modalProb.preview = false;
936dc463 325 this.myProblems.push(JSON.parse(JSON.stringify(this.modalProb)));
8ef618ef
BA
326 }
327 else
328 this.modalProb.id = 0;
329 }
330 );
331 },
da06a6eb 332 },
4ecf423b 333})