Fix problems editing (hopefully)
[vchess.git] / client / src / views / Problems.vue
CommitLineData
89021f18
BA
1<template lang="pug">
2main
910d631b
BA
3 input#modalNewprob.modal(
4 type="checkbox"
5 @change="infoMsg=''"
6 )
7 div#newprobDiv(
8 role="dialog"
9 data-checkbox="modalNewprob"
10 )
866842c3 11 .card
89021f18 12 label#closeNewprob.modal-close(for="modalNewprob")
604b951e
BA
13 fieldset
14 label(for="selectVariant") {{ st.tr["Variant"] }}
15 select#selectVariant(
16 v-model="curproblem.vid"
17 @change="changeVariant(curproblem)"
18 )
19 option(
20 v-for="v in [emptyVar].concat(st.variants)"
21 :value="v.id"
22 :selected="curproblem.vid==v.id"
23 )
24 | {{ v.name }}
25 fieldset
604b951e
BA
26 input#inputFen(
27 type="text"
bd76b456 28 placeholder="FEN"
604b951e
BA
29 v-model="curproblem.fen"
30 @input="trySetDiagram(curproblem)"
31 )
bd76b456 32 #diagram(v-html="curproblem.diag")
604b951e 33 fieldset
bd76b456 34 textarea(
604b951e
BA
35 :placeholder="st.tr['Instructions']"
36 v-model="curproblem.instruction"
37 )
38 p(v-html="parseHtml(curproblem.instruction)")
39 fieldset
bd76b456 40 textarea(
604b951e
BA
41 :placeholder="st.tr['Solution']"
42 v-model="curproblem.solution"
43 )
44 p(v-html="parseHtml(curproblem.solution)")
45 button(@click="sendProblem()") {{ st.tr["Send"] }}
89021f18 46 #dialog.text-center {{ st.tr[infoMsg] }}
604b951e 47 .row(v-if="showOne")
866842c3 48 .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
bd76b456 49 #topPage
2f258c37 50 span.vname {{ curproblem.vname }}
98fb976e 51 span.uname ({{ curproblem.uname }})
bd76b456
BA
52 button.marginleft(@click="backToList()") {{ st.tr["Back to list"] }}
53 button.nomargin(
604b951e
BA
54 v-if="st.user.id == curproblem.uid"
55 @click="editProblem(curproblem)"
56 )
57 | {{ st.tr["Edit"] }}
bd76b456 58 button.nomargin(
604b951e
BA
59 v-if="st.user.id == curproblem.uid"
60 @click="deleteProblem(curproblem)"
61 )
62 | {{ st.tr["Delete"] }}
866842c3 63 p.oneInstructions.clickable(
2f258c37 64 v-html="parseHtml(curproblem.instruction)"
bd76b456
BA
65 @click="curproblem.showSolution=!curproblem.showSolution"
66 )
604b951e
BA
67 | {{ st.tr["Show solution"] }}
68 p(
69 v-show="curproblem.showSolution"
70 v-html="parseHtml(curproblem.solution)"
71 )
72 .row(v-else)
89021f18 73 .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
bd76b456 74 #controls
6808d7a1 75 button#newProblem(onClick="window.doClick('modalNewprob')")
bd76b456
BA
76 | {{ st.tr["New problem"] }}
77 label(for="checkboxMine") {{ st.tr["My problems"] }}
78 input#checkboxMine(
79 type="checkbox"
80 v-model="onlyMines"
604b951e 81 )
bd76b456
BA
82 label(for="selectVariant") {{ st.tr["Variant"] }}
83 select#selectVariant(v-model="selectedVar")
84 option(
85 v-for="v in [emptyVar].concat(st.variants)"
86 :value="v.id"
87 )
88 | {{ v.name }}
89 table
90 tr
91 th {{ st.tr["Variant"] }}
92 th {{ st.tr["Instructions"] }}
2f258c37 93 th {{ st.tr["Number"] }}
bd76b456 94 tr(
6808d7a1 95 v-for="p in problems"
bd76b456
BA
96 v-show="displayProblem(p)"
97 @click="setHrefPid(p)"
98 )
99 td {{ p.vname }}
2f258c37
BA
100 td {{ firstChars(p.instruction) }}
101 td {{ p.id }}
910d631b
BA
102 BaseGame(
103 v-if="showOne"
104 :game="game"
105 :vr="vr"
106 )
89021f18
BA
107</template>
108
109<script>
110import { store } from "@/store";
111import { ajax } from "@/utils/ajax";
112import { checkProblem } from "@/data/problemCheck";
113import { getDiagram } from "@/utils/printDiagram";
bd76b456
BA
114import { processModalClick } from "@/utils/modalClick";
115import { ArrayFun } from "@/utils/array";
604b951e 116import BaseGame from "@/components/BaseGame.vue";
89021f18
BA
117export default {
118 name: "my-problems",
604b951e 119 components: {
6808d7a1 120 BaseGame
604b951e 121 },
89021f18
BA
122 data: function() {
123 return {
604b951e 124 st: store.state,
89021f18
BA
125 emptyVar: {
126 vid: 0,
6808d7a1 127 vname: ""
89021f18 128 },
604b951e
BA
129 // Problem currently showed, or edited:
130 curproblem: {
131 id: 0, //used in case of edit
89021f18
BA
132 vid: 0,
133 fen: "",
604b951e 134 diag: "",
89021f18
BA
135 instruction: "",
136 solution: "",
6808d7a1 137 showSolution: false
89021f18 138 },
604b951e
BA
139 loadedVar: 0, //corresponding to loaded V
140 selectedVar: 0, //to filter problems based on variant
89021f18 141 problems: [],
604b951e
BA
142 onlyMines: false,
143 showOne: false,
89021f18 144 infoMsg: "",
604b951e
BA
145 vr: null, //"variant rules" object initialized from FEN
146 game: {
6808d7a1
BA
147 players: [{ name: "Problem" }, { name: "Problem" }],
148 mode: "analyze"
149 }
89021f18
BA
150 };
151 },
152 created: function() {
6808d7a1
BA
153 ajax("/problems", "GET", res => {
154 // Show newest problem first:
155 this.problems = res.problems.sort((p1, p2) => p2.added - p1.added);
604b951e 156 if (this.st.variants.length > 0)
bd76b456
BA
157 this.problems.forEach(p => this.setVname(p));
158 // Retrieve all problems' authors' names
159 let names = {};
160 this.problems.forEach(p => {
6808d7a1 161 if (p.uid != this.st.user.id) names[p.uid] = "";
6808d7a1 162 else p.uname = this.st.user.name;
bd76b456 163 });
2f258c37
BA
164 const showOneIfPid = () => {
165 const pid = this.$route.query["id"];
6808d7a1 166 if (pid) this.showProblem(this.problems.find(p => p.id == pid));
2f258c37 167 };
6808d7a1
BA
168 if (Object.keys(names).length > 0) {
169 ajax("/users", "GET", { ids: Object.keys(names).join(",") }, res2 => {
170 res2.users.forEach(u => {
171 names[u.id] = u.name;
172 });
c9c0c57a
BA
173 this.problems.forEach(p => {
174 if (!p.uname)
175 p.uname = names[p.uid];
176 });
6808d7a1
BA
177 showOneIfPid();
178 });
179 } else showOneIfPid();
89021f18
BA
180 });
181 },
bd76b456 182 mounted: function() {
6808d7a1
BA
183 document
184 .getElementById("newprobDiv")
185 .addEventListener("click", processModalClick);
bd76b456 186 },
604b951e
BA
187 watch: {
188 // st.variants changes only once, at loading from [] to [...]
6808d7a1 189 "st.variants": function() {
604b951e
BA
190 // Set problems vname (either all are set or none)
191 if (this.problems.length > 0 && this.problems[0].vname == "")
192 this.problems.forEach(p => this.setVname(p));
193 },
6808d7a1 194 $route: function(to) {
bd76b456 195 const pid = to.query["id"];
6808d7a1
BA
196 if (pid) this.showProblem(this.problems.find(p => p.id == pid));
197 else this.showOne = false;
198 }
98fb976e 199 },
89021f18 200 methods: {
604b951e
BA
201 setVname: function(prob) {
202 prob.vname = this.st.variants.find(v => v.id == prob.vid).name;
203 },
2f258c37
BA
204 firstChars: function(text) {
205 let preparedText = text
206 // Replace line jumps and <br> by spaces
6808d7a1
BA
207 .replace(/\n/g, " ")
208 .replace(/<br\/?>/g, " ")
2f258c37
BA
209 .replace(/<[^>]+>/g, "") //remove remaining HTML tags
210 .replace(/[ ]+/g, " ") //remove series of spaces by only one
211 .trim();
212 const maxLength = 32; //arbitrary...
213 if (preparedText.length > maxLength)
6808d7a1 214 return preparedText.substr(0, 32) + "...";
2f258c37
BA
215 return preparedText;
216 },
604b951e 217 copyProblem: function(p1, p2) {
6808d7a1 218 for (let key in p1) p2[key] = p1[key];
604b951e 219 },
bd76b456
BA
220 setHrefPid: function(p) {
221 // Change href => $route changes, watcher notices, call showProblem
222 const curHref = document.location.href;
223 document.location.href = curHref.split("?")[0] + "?id=" + p.id;
224 },
225 backToList: function() {
226 // Change href => $route change, watcher notices, reset showOne to false
227 document.location.href = document.location.href.split("?")[0];
228 },
604b951e
BA
229 resetCurProb: function() {
230 this.curproblem.id = 0;
231 this.curproblem.uid = 0;
232 this.curproblem.vid = "";
233 this.curproblem.vname = "";
234 this.curproblem.fen = "";
235 this.curproblem.diag = "";
236 this.curproblem.instruction = "";
237 this.curproblem.solution = "";
238 this.curproblem.showSolution = false;
89021f18 239 },
604b951e
BA
240 parseHtml: function(txt) {
241 return !txt.match(/<[/a-zA-Z]+>/)
242 ? txt.replace(/\n/g, "<br/>") //no HTML tag
243 : txt;
244 },
245 changeVariant: function(prob) {
246 this.setVname(prob);
6808d7a1
BA
247 this.loadVariant(prob.vid, () => {
248 // Set FEN if possible (might not be correct yet)
249 if (V.IsGoodFen(prob.fen)) this.setDiagram(prob);
250 });
604b951e
BA
251 },
252 loadVariant: async function(vid, cb) {
253 // Condition: vid is a valid variant ID
254 this.loadedVar = 0;
255 const variant = this.st.variants.find(v => v.id == vid);
89021f18
BA
256 const vModule = await import("@/variants/" + variant.name + ".js");
257 window.V = vModule.VariantRules;
604b951e
BA
258 this.loadedVar = vid;
259 cb();
260 },
261 trySetDiagram: function(prob) {
262 // Problem edit: FEN could be wrong or incomplete,
263 // variant could not be ready, or not defined
264 if (prob.vid > 0 && this.loadedVar == prob.vid && V.IsGoodFen(prob.fen))
265 this.setDiagram(prob);
89021f18 266 },
604b951e
BA
267 setDiagram: function(prob) {
268 // Condition: prob.fen is correct and global V is ready
269 const parsedFen = V.ParseFen(prob.fen);
270 const args = {
271 position: parsedFen.position,
6808d7a1 272 orientation: parsedFen.turn
604b951e
BA
273 };
274 prob.diag = getDiagram(args);
89021f18 275 },
604b951e 276 displayProblem: function(p) {
6808d7a1 277 return (
866842c3 278 (!this.selectedVar || p.vid == this.selectedVar) &&
6808d7a1
BA
279 ((this.onlyMines && p.uid == this.st.user.id) ||
280 (!this.onlyMines && p.uid != this.st.user.id))
281 );
604b951e
BA
282 },
283 showProblem: function(p) {
6808d7a1
BA
284 this.loadVariant(p.vid, () => {
285 // The FEN is already checked at this stage:
286 this.vr = new V(p.fen);
287 this.game.vname = p.vname;
288 this.game.mycolor = this.vr.turn; //diagram orientation
289 this.game.fen = p.fen;
290 this.$set(this.game, "fenStart", p.fen);
291 this.copyProblem(p, this.curproblem);
292 this.showOne = true;
293 });
604b951e
BA
294 },
295 sendProblem: function() {
296 const error = checkProblem(this.curproblem);
6808d7a1 297 if (error) {
866842c3 298 alert(this.st.tr[error]);
6808d7a1
BA
299 return;
300 }
604b951e
BA
301 const edit = this.curproblem.id > 0;
302 this.infoMsg = "Processing... Please wait";
303 ajax(
304 "/problems",
305 edit ? "PUT" : "POST",
6808d7a1
BA
306 { prob: this.curproblem },
307 ret => {
308 if (edit) {
604b951e
BA
309 let editedP = this.problems.find(p => p.id == this.curproblem.id);
310 this.copyProblem(this.curproblem, editedP);
c9c0c57a 311 this.showProblem(editedP);
0cd02605 312 }
6808d7a1 313 else {
0cd02605 314 // New problem
604b951e
BA
315 let newProblem = Object.assign({}, this.curproblem);
316 newProblem.id = ret.id;
bd76b456
BA
317 newProblem.uid = this.st.user.id;
318 newProblem.uname = this.st.user.name;
57b3f30e 319 this.problems = [newProblem].concat(this.problems);
0cd02605 320 this.resetCurProb();
604b951e 321 }
b638a2e7 322 document.getElementById("modalNewprob").checked = false;
c9c0c57a 323 this.infoMsg = "";
604b951e
BA
324 }
325 );
326 },
327 editProblem: function(prob) {
bec548e7
BA
328 // prob.diag might correspond to some other problem or be empty:
329 this.setDiagram(prob); //V is loaded at this stage
604b951e 330 this.copyProblem(prob, this.curproblem);
6808d7a1 331 window.doClick("modalNewprob");
604b951e
BA
332 },
333 deleteProblem: function(prob) {
6808d7a1
BA
334 if (confirm(this.st.tr["Are you sure?"])) {
335 ajax("/problems", "DELETE", { id: prob.id }, () => {
bd76b456
BA
336 ArrayFun.remove(this.problems, p => p.id == prob.id);
337 this.backToList();
338 });
339 }
6808d7a1
BA
340 }
341 }
89021f18
BA
342};
343</script>
344
345<style lang="sass" scoped>
bd76b456
BA
346[type="checkbox"].modal+div .card
347 max-width: 767px
348 max-height: 100%
910d631b 349
bd76b456
BA
350#inputFen
351 width: 100%
910d631b 352
bd76b456
BA
353textarea
354 width: 100%
910d631b 355
bd76b456
BA
356#diagram
357 margin: 0 auto
358 max-width: 400px
910d631b 359
bd76b456
BA
360#controls
361 margin: 0
362 width: 100%
363 text-align: center
364 & > *
365 margin: 0
2f258c37 366
866842c3
BA
367p.oneInstructions
368 margin: 0
369 padding: 2px 5px
370 background-color: lightgreen
371
bd76b456 372#topPage
2f258c37 373 span.vname
bd76b456 374 font-weight: bold
feae89d3 375 padding-left: var(--universal-margin)
2f258c37
BA
376 span.uname
377 padding-left: var(--universal-margin)
bd76b456
BA
378 margin: 0 auto
379 & > .nomargin
380 margin: 0
381 & > .marginleft
382 margin: 0 0 0 15px
383
2f258c37
BA
384@media screen and (max-width: 767px)
385 #topPage
386 text-align: center
89021f18 387</style>