From 8ef618ef05070642849f50861399116c2d69a816 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 10 Jan 2019 12:05:34 +0100
Subject: [PATCH] Advance on problems page

---
 countLines.sh                                 |   3 +
 models/User.js                                |   3 +
 public/javascripts/components/board.js        |   8 +-
 .../{problemPreview.js => problemSummary.js}  |  15 +-
 public/javascripts/components/problems.js     | 134 +++++++++++-------
 5 files changed, 108 insertions(+), 55 deletions(-)
 rename public/javascripts/components/{problemPreview.js => problemSummary.js} (52%)

diff --git a/countLines.sh b/countLines.sh
index 3de43049..deed541e 100755
--- a/countLines.sh
+++ b/countLines.sh
@@ -19,9 +19,12 @@ count=$((count + $(countFoldExt "app.js" "js")))
 count=$((count + $(countFoldExt "gulpfile.js" "js")))
 count=$((count + $(countFoldExt "sockets.js" "js")))
 count=$((count + $(countFoldExt "bin" "www")))
+count=$((count + $(countFoldExt "config" "js")))
 count=$((count + $(countFoldExt "db" "sql")))
+count=$((count + $(countFoldExt "models" "js")))
 count=$((count + $(countFoldExt "public" "js")))
 count=$((count + $(countFoldExt "public" "sass")))
 count=$((count + $(countFoldExt "routes" "js")))
+count=$((count + $(countFoldExt "utils" "js")))
 count=$((count + $(countFoldExt "views" "pug")))
 echo $count
diff --git a/models/User.js b/models/User.js
index 6eff2735..171dc2c2 100644
--- a/models/User.js
+++ b/models/User.js
@@ -14,6 +14,9 @@ var params = require("../config/parameters");
  *   notify: boolean (send email notifications for corr games)
  */
 
+// TODO: consider sanitizing http://www.unixwiz.net/techtips/sql-injection.html
+// But parameters are supposed to already be cleaned (in controller).
+
 // User creation
 exports.create = function(name, email, notify, callback)
 {
diff --git a/public/javascripts/components/board.js b/public/javascripts/components/board.js
index e786e899..2861cf7f 100644
--- a/public/javascripts/components/board.js
+++ b/public/javascripts/components/board.js
@@ -8,7 +8,9 @@
 			vr: null, //object to check moves, store them, FEN..
 	orientation: "w", //useful if click on "flip board"	
 	
-	
+
+// TODO: watch for property change "fen"
+// send event after each move, to notify what was played
 	
 	const [sizeX,sizeY] = [V.size.x,V.size.y];
 		// Precompute hints squares to facilitate rendering
@@ -289,6 +291,10 @@
 				this.possibleMoves = [];
 				if (this.score == "*")
 				{
+
+// TODO: essentially adapt this (all other things do not change much)
+// if inside a real game, mycolor should be provided ? (simplest way)
+
 					const color = ["friend","problem"].includes(this.mode)
 						? this.vr.turn
 						: this.mycolor;
diff --git a/public/javascripts/components/problemPreview.js b/public/javascripts/components/problemSummary.js
similarity index 52%
rename from public/javascripts/components/problemPreview.js
rename to public/javascripts/components/problemSummary.js
index 53365035..2c9ea94e 100644
--- a/public/javascripts/components/problemPreview.js
+++ b/public/javascripts/components/problemSummary.js
@@ -1,6 +1,6 @@
 // Preview a problem on variant page
 Vue.component('my-problem-preview', {
-	props: ['prob'],
+	props: ['prob','userid'],
 	template: `
 		<div class="row problem">
 			<div class="col-sm-12 col-md-6 diagram"
@@ -8,7 +8,12 @@ Vue.component('my-problem-preview', {
 			</div>
 			<div class="col-sm-12 col-md-6">
 				<p v-html="prob.instructions"></p>
-				<p v-html="prob.solution"></p>
+				<p v-if="!!prob.preview" v-html="prob.solution"></p>
+				<p v-else class="problem-time">{{ timestamp2date(prob.added) }}</p>
+				<div v-show="prob.uid==userid" class="button-group">
+					<button @click="sendSignal('edit')'">Edit</button>
+					<button @click="sendSignal('delete')">Delete</button>
+				</div>
 			</div>
 		</div>
 	`,
@@ -21,5 +26,11 @@ Vue.component('my-problem-preview', {
 				// No need for flags here
 			});
 		},
+		timestamp2date(ts) {
+			return getDate(new Date(ts));
+		},
+		sendSignal: function(action) {
+			this.$emit(action + "-problem");
+		},
 	},
 })
diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js
index 93b3353a..e3ab998d 100644
--- a/public/javascripts/components/problems.js
+++ b/public/javascripts/components/problems.js
@@ -17,8 +17,6 @@ Vue.component('my-problems', {
 			},
 		};
 	},
-	// TODO: problem edit, just fill modalProb + adjust AJAX call
-	// problem delete: just AJAX call + confirm
 	template: `
 		<div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
 			<div id="problemControls" class="button-group">
@@ -34,54 +32,75 @@ Vue.component('my-problems', {
 			</div>
 			<div id="mainBoard" v-show="curIdx>=0">
 				<div id="instructions-div" class="section-content">
-					<p id="problem-instructions">{{ curProb.instructions }}</p>
+					<p id="problem-instructions">
+						{{ curProb.instructions }}
+					</p>
 				</div>
 				<my-board :fen="curProb.fen"></my-board>
 				<div id="solution-div" class="section-content">
 					<h3 class="clickable" @click="showSolution = !showSolution">
 						{{ translations["Show solution"] }}
 					</h3>
-					<p id="problem-solution" v-show="showSolution">{{ curProb.solution }}</p>
-					<div class="button-group" v-show="curProb.uid==userId">
-						<button>Edit</button>
-						<button>Delete</button>
-					</div>
+					<p id="problem-solution" v-show="showSolution">
+						{{ curProb.solution }}
+					</p>
 				</div>
 			</div>
-			<button v-if="!!userId" @click="toggleListDisplay()">My problems (only)</button>
+			<button v-if="!!userId" @click="toggleListDisplay()">
+				<span>My problems (only)</span>
+			</button>
 			<my-problem-summary v-show="curIdx<0"
 				v-for="(p,idx) in sortedProblems" @click="setCurIdx(idx)"
-				v-bind:prob="p" v-bind:preview="false" v-bind:key="p.id">
+				v-bind:prob="p" v-bind:userid="userId" v-bind:key="p.id">
 			</my-problem-summary>
-			<input type="checkbox" id="modal-newproblem" class="modal">
+			<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="previewNewProblem()">
+					<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>
+							<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>
+								: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>
+								: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"></label>
-					<my-problem-summary v-bind:prob="modalProb" v-bind:preview="true"></my-problem-summary>
+					<label for="modal-newproblem" class="modal-close">
+					</label>
+					<my-problem-summary v-bind:prob="modalProb" v-bind:userid="userId">
+					</my-problem-summary>
 					<div class="button-group">
-						<button @click="modalProb.preview=false">{{ translate("Cancel") }}</button>
-						<button @click="sendNewProblem()">{{ translate("Send") }}</button>
+						<button @click="modalProb.preview=false">
+							{{ translate("Cancel") }}
+						</button>
+						<button @click="sendProblem()">
+							{{ translate("Send") }}
+						</button>
 					</div>
 				</div>
 			</div>
@@ -105,7 +124,7 @@ Vue.component('my-problems', {
 	created: function() {
 		if (location.hash.length > 0)
 		{
-			this.getOneProblem(location.hash.slice(1));
+			this.getOneProblem(location.hash.slice(1)); //callback?
 			this.curIdx = 0; //TODO: a bit more subtle, depending if it's my problem or not (set display)
 		}
 		else
@@ -131,11 +150,11 @@ Vue.component('my-problems', {
 					return this.myProblems;
 			}
 		},
-		// TODO: dans tous les cas si on n'affiche qu'un seul problème,
-		// le curseur ne doit se déplacer que d'une unité.
+		// TODO?: get 50 from server but only show 10 at a time (for example)
 		showNext: function(direction) {
 			if (this.curIdx < 0)
-				this.fetchProblems(direction);
+				return this.fetchProblems(direction);
+			// Show next problem (older or newer):
 			let curProbs = this.curProblems();
 			if ((this.curIdx > 0 && direction=="backward")
 				|| (this.curIdx < curProbs.length-1 && direction=="forward"))
@@ -146,17 +165,19 @@ Vue.component('my-problems', {
 			{
 				const curSize = curProbs.length;
 				this.fetchProblems(direction);
-				if (curProbs.length
+				const newSize = curProbs.length;
+				if (curSize == newSize) //no problems found
+					return;
+				switch (direction)
+				{
+					case "forward":
+						this.setCurIdx(this.curIdx+1);
+						break;
+					case "backward":
+						this.setCurIdx(newSize - curSize + this.curIdx-1);
+						break;
+				}
 			}
-			else
-				this.setCurIndex(--this.curIdx);
-			
-			
-			if (this.curIdx == this.problems.length - 1)
-				this.fetchProblems("forward");
-			else
-				this.curIdx++;
-			location.hash = this.curIdx;
 		},
 		toggleListDisplay: function() {
 			this.display = (this.display == "list" ? "myList" : "list");
@@ -188,7 +209,7 @@ Vue.component('my-problems', {
 				}
 			});
 		},
-		previewNewProblem: function() {
+		previewProblem: function() {
 			if (!V.IsGoodFen(this.newProblem.fen))
 				return alert(translations["Bad FEN description"]);
 			if (this.newProblem.instructions.trim().length == 0)
@@ -197,20 +218,29 @@ Vue.component('my-problems', {
 				return alert(translations["Empty solution"]);
 			this.modalProb.preview = true;
 		},
-		// TODO: adjust for update too
-		sendNewProblem: function() {
+		sendProblem: function() {
 			// Send it to the server and close modal
-			ajax("/problems/" + variant.name, "POST", { //TODO: with variant._id ?
-				fen: this.newProblem.fen,
-				instructions: this.newProblem.instructions,
-				solution: this.newProblem.solution,
-			}, response => {
-				this.modalProb.added = Date.now();
-				this.curProblems().push(JSON.parse(JSON.stringify(this.modalProb)));
-				document.getElementById("modal-newproblem").checked = false;
-				this.modalProb.preview = false;
-			});
+			ajax(
+				"/problems/" + variant.name, //TODO: with variant.id ?
+				(this.modalProb.id > 0 ? "PUT" : "POST"),
+				this.modalProb,
+				response => {
+					document.getElementById("modal-newproblem").checked = false;
+					if (this.modalProb.id == 0)
+					{
+						this.modalProb.added = Date.now();
+						this.modalProb.preview = false;
+						this.curProblems().push(JSON.parse(JSON.stringify(this.modalProb)));
+					}
+					else
+						this.modalProb.id = 0;
+				}
+			);
+		},
+		// TODO: catch signal edit or delete ; on edit: modify modalProb and show modal
+		deleteProblem: function(pid) {
+			// TODO: AJAX call
+			// TODO: delete problem in curProblems() list
 		},
-		// TODO: AJAX for problem deletion
 	},
 })
-- 
2.44.0