Refactoring: split files into components (not working, broken state)
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 8 Jan 2019 00:44:19 +0000 (01:44 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 8 Jan 2019 00:44:19 +0000 (01:44 +0100)
13 files changed:
TODO
public/javascripts/components/board.js [new file with mode: 0644]
public/javascripts/components/chat.js [new file with mode: 0644]
public/javascripts/components/game.js
public/javascripts/components/moveList.js [new file with mode: 0644]
public/javascripts/components/problemPreview.js [new file with mode: 0644]
public/javascripts/components/problemSummary.js [deleted file]
public/javascripts/components/problems.js
public/javascripts/components/room.js
public/javascripts/settings.js
public/javascripts/utils/storage.js [new file with mode: 0644]
public/javascripts/variant.js
views/variant.pug

diff --git a/TODO b/TODO
index 071c476..52fb8bb 100644 (file)
--- a/TODO
+++ b/TODO
@@ -40,3 +40,18 @@ mais permettrait mode analyse (avec bouton "analyse", comme sur ancien site).
 
 espagnol : jugada ou movimiento ?
 fin de la partida au lieu de final de partida ?
+
+Bouton new game ==> human only. Indiquer adversaire (éventuellement), cadence (ou "infini")
+Mode analyse : accessible à tout moment d'une partie (HH, ou computer) terminée.
+
+Coordonnées sur échiquier: sur cases, à gauche (verticale) ou en bas (horizontale)
+
+Import game : en local dans indexedDb, affichage dans "Games --> Imported"
+
+Checkered : si intervention d'un 3eme joueur, initialiser son temps à la moyenne des temps restants des deux autres...
+
+Mode contre ordinateur : seulement accessible depuis onglet "Rules" (son principal intérêt)
+
+Hexachess: McCooey et Shafran (deux tailles, randomisation OK)
+http://www.math.bas.bg/~iad/tyalie/shegra/shegrax.html
+http://www.quadibloc.com/chess/ch0401.htm
diff --git a/public/javascripts/components/board.js b/public/javascripts/components/board.js
new file mode 100644 (file)
index 0000000..4cadb0b
--- /dev/null
@@ -0,0 +1,385 @@
+                       hints: (!localStorage["hints"] ? true : localStorage["hints"] === "1"),
+                       bcolor: localStorage["bcolor"] || "lichess", //lichess, chesscom or chesstempo
+                       possibleMoves: [], //filled after each valid click/dragstart
+                       choices: [], //promotion pieces, or checkered captures... (as moves)
+                       selectedPiece: null, //moving piece (or clicked piece)
+                       incheck: [],
+                       start: {}, //pixels coordinates + id of starting square (click or drag)
+                       vr: null, //object to check moves, store them, FEN..
+       orientation: "w", //useful if click on "flip board"     
+       
+       
+       
+       const [sizeX,sizeY] = [V.size.x,V.size.y];
+               // Precompute hints squares to facilitate rendering
+               let hintSquares = doubleArray(sizeX, sizeY, false);
+               this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; });
+               // Also precompute in-check squares
+               let incheckSq = doubleArray(sizeX, sizeY, false);
+               this.incheck.forEach(sq => { incheckSq[sq[0]][sq[1]] = true; });
+                       const choices = h('div',
+                               {
+                                       attrs: { "id": "choices" },
+                                       'class': { 'row': true },
+                                       style: {
+                                               "display": this.choices.length>0?"block":"none",
+                                               "top": "-" + ((sizeY/2)*squareWidth+squareWidth/2) + "px",
+                                               "width": (this.choices.length * squareWidth) + "px",
+                                               "height": squareWidth + "px",
+                                       },
+                               },
+                               this.choices.map( m => { //a "choice" is a move
+                                       return h('div',
+                                               {
+                                                       'class': {
+                                                               'board': true,
+                                                               ['board'+sizeY]: true,
+                                                       },
+                                                       style: {
+                                                               'width': (100/this.choices.length) + "%",
+                                                               'padding-bottom': (100/this.choices.length) + "%",
+                                                       },
+                                               },
+                                               [h('img',
+                                                       {
+                                                               attrs: { "src": '/images/pieces/' +
+                                                                       VariantRules.getPpath(m.appear[0].c+m.appear[0].p) + '.svg' },
+                                                               'class': { 'choice-piece': true },
+                                                               on: {
+                                                                       "click": e => { this.play(m); this.choices=[]; },
+                                                                       // NOTE: add 'touchstart' event to fix a problem on smartphones
+                                                                       "touchstart": e => { this.play(m); this.choices=[]; },
+                                                               },
+                                                       })
+                                               ]
+                                       );
+                               })
+                       );
+                       // Create board element (+ reserves if needed by variant or mode)
+                       const lm = this.vr.lastMove;
+                       const showLight = this.hints && variant!="Dark" &&
+                               (this.mode != "idle" ||
+                                       (this.vr.moves.length > 0 && this.cursor==this.vr.moves.length));
+                       const gameDiv = h('div',
+                               {
+                                       'class': {
+                                               'game': true,
+                                               'clearer': true,
+                                       },
+                               },
+                               [_.range(sizeX).map(i => {
+                                       let ci = (this.mycolor=='w' ? i : sizeX-i-1);
+                                       return h(
+                                               'div',
+                                               {
+                                                       'class': {
+                                                               'row': true,
+                                                       },
+                                                       style: { 'opacity': this.choices.length>0?"0.5":"1" },
+                                               },
+                                               _.range(sizeY).map(j => {
+                                                       let cj = (this.mycolor=='w' ? j : sizeY-j-1);
+                                                       let elems = [];
+                                                       if (this.vr.board[ci][cj] != VariantRules.EMPTY && (variant!="Dark"
+                                                               || this.score!="*" || this.vr.enlightened[this.mycolor][ci][cj]))
+                                                       {
+                                                               elems.push(
+                                                                       h(
+                                                                               'img',
+                                                                               {
+                                                                                       'class': {
+                                                                                               'piece': true,
+                                                                                               'ghost': !!this.selectedPiece
+                                                                                                       && this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj,
+                                                                                       },
+                                                                                       attrs: {
+                                                                                               src: "/images/pieces/" +
+                                                                                                       VariantRules.getPpath(this.vr.board[ci][cj]) + ".svg",
+                                                                                       },
+                                                                               }
+                                                                       )
+                                                               );
+                                                       }
+                                                       if (this.hints && hintSquares[ci][cj])
+                                                       {
+                                                               elems.push(
+                                                                       h(
+                                                                               'img',
+                                                                               {
+                                                                                       'class': {
+                                                                                               'mark-square': true,
+                                                                                       },
+                                                                                       attrs: {
+                                                                                               src: "/images/mark.svg",
+                                                                                       },
+                                                                               }
+                                                                       )
+                                                               );
+                                                       }
+                                                       return h(
+                                                               'div',
+                                                               {
+                                                                       'class': {
+                                                                               'board': true,
+                                                                               ['board'+sizeY]: true,
+                                                                               'light-square': (i+j)%2==0,
+                                                                               'dark-square': (i+j)%2==1,
+                                                                               [this.bcolor]: true,
+                                                                               'in-shadow': variant=="Dark" && this.score=="*"
+                                                                                       && !this.vr.enlightened[this.mycolor][ci][cj],
+                                                                               'highlight': showLight && !!lm && _.isMatch(lm.end, {x:ci,y:cj}),
+                                                                               'incheck': showLight && incheckSq[ci][cj],
+                                                                       },
+                                                                       attrs: {
+                                                                               id: this.getSquareId({x:ci,y:cj}),
+                                                                       },
+                                                               },
+                                                               elems
+                                                       );
+                                               })
+                                       );
+                               }), choices]
+                       );
+                       if (!!this.vr.reserve)
+                       {
+                               const shiftIdx = (this.mycolor=="w" ? 0 : 1);
+                               let myReservePiecesArray = [];
+                               for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
+                               {
+                                       myReservePiecesArray.push(h('div',
+                                       {
+                                               'class': {'board':true, ['board'+sizeY]:true},
+                                               attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) }
+                                       },
+                                       [
+                                               h('img',
+                                               {
+                                                       'class': {"piece":true, "reserve":true},
+                                                       attrs: {
+                                                               "src": "/images/pieces/" +
+                                                                       this.vr.getReservePpath(this.mycolor,i) + ".svg",
+                                                       }
+                                               }),
+                                               h('sup',
+                                                       {"class": { "reserve-count": true } },
+                                                       [ this.vr.reserve[this.mycolor][VariantRules.RESERVE_PIECES[i]] ]
+                                               )
+                                       ]));
+                               }
+                               let oppReservePiecesArray = [];
+                               const oppCol = this.vr.getOppCol(this.mycolor);
+                               for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
+                               {
+                                       oppReservePiecesArray.push(h('div',
+                                       {
+                                               'class': {'board':true, ['board'+sizeY]:true},
+                                               attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) }
+                                       },
+                                       [
+                                               h('img',
+                                               {
+                                                       'class': {"piece":true, "reserve":true},
+                                                       attrs: {
+                                                               "src": "/images/pieces/" +
+                                                                       this.vr.getReservePpath(oppCol,i) + ".svg",
+                                                       }
+                                               }),
+                                               h('sup',
+                                                       {"class": { "reserve-count": true } },
+                                                       [ this.vr.reserve[oppCol][VariantRules.RESERVE_PIECES[i]] ]
+                                               )
+                                       ]));
+                               }
+                               let reserves = h('div',
+                                       {
+                                               'class':{
+                                                       'game': true,
+                                                       "reserve-div": true,
+                                               },
+                                       },
+                                       [
+                                               h('div',
+                                                       {
+                                                               'class': {
+                                                                       'row': true,
+                                                                       "reserve-row-1": true,
+                                                               },
+                                                       },
+                                                       myReservePiecesArray
+                                               ),
+                                               h('div',
+                                                       { 'class': { 'row': true }},
+                                                       oppReservePiecesArray
+                                               )
+                                       ]
+                               );
+                               elementArray.push(reserves);
+                       }
+                               // Show current FEN (just below board, lower right corner)
+// (if mode != Dark ...)
+                               elementArray.push(
+                                       h('div',
+                                               {
+                                                       attrs: { id: "fen-div" },
+                                                       "class": { "section-content": true },
+                                               },
+                                               [
+                                                       h('p',
+                                                               {
+                                                                       attrs: { id: "fen-string" },
+                                                                       domProps: { innerHTML: this.vr.getBaseFen() },
+                                                                       "class": { "text-center": true },
+                                                               }
+                                                       )
+                                               ]
+                                       )
+                               );
+                               on: {
+                                       mousedown: this.mousedown,
+                                       mousemove: this.mousemove,
+                                       mouseup: this.mouseup,
+                                       touchstart: this.mousedown,
+                                       touchmove: this.mousemove,
+                                       touchend: this.mouseup,
+                               },
+
+
+               // TODO: "chessground-like" component
+               // Get the identifier of a HTML table cell from its numeric coordinates o.x,o.y.
+               getSquareId: function(o) {
+                       // NOTE: a separator is required to allow any size of board
+                       return  "sq-" + o.x + "-" + o.y;
+               },
+               // Inverse function
+               getSquareFromId: function(id) {
+                       let idParts = id.split('-');
+                       return [parseInt(idParts[1]), parseInt(idParts[2])];
+               },
+               mousedown: function(e) {
+                       e = e || window.event;
+                       let ingame = false;
+                       let elem = e.target;
+                       while (!ingame && elem !== null)
+                       {
+                               if (elem.classList.contains("game"))
+                               {
+                                       ingame = true;
+                                       break;
+                               }
+                               elem = elem.parentElement;
+                       }
+                       if (!ingame) //let default behavior (click on button...)
+                               return;
+                       e.preventDefault(); //disable native drag & drop
+                       if (!this.selectedPiece && e.target.classList.contains("piece"))
+                       {
+                               // Next few lines to center the piece on mouse cursor
+                               let rect = e.target.parentNode.getBoundingClientRect();
+                               this.start = {
+                                       x: rect.x + rect.width/2,
+                                       y: rect.y + rect.width/2,
+                                       id: e.target.parentNode.id
+                               };
+                               this.selectedPiece = e.target.cloneNode();
+                               this.selectedPiece.style.position = "absolute";
+                               this.selectedPiece.style.top = 0;
+                               this.selectedPiece.style.display = "inline-block";
+                               this.selectedPiece.style.zIndex = 3000;
+                               const startSquare = this.getSquareFromId(e.target.parentNode.id);
+                               this.possibleMoves = [];
+                               if (this.score == "*")
+                               {
+                                       const color = ["friend","problem"].includes(this.mode)
+                                               ? this.vr.turn
+                                               : this.mycolor;
+                                       if (this.vr.canIplay(color,startSquare))
+                                               this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
+                               }
+                               // Next line add moving piece just after current image
+                               // (required for Crazyhouse reserve)
+                               e.target.parentNode.insertBefore(this.selectedPiece, e.target.nextSibling);
+                       }
+               },
+               mousemove: function(e) {
+                       if (!this.selectedPiece)
+                               return;
+                       e = e || window.event;
+                       // If there is an active element, move it around
+                       if (!!this.selectedPiece)
+                       {
+                               const [offsetX,offsetY] = !!e.clientX
+                                       ? [e.clientX,e.clientY] //desktop browser
+                                       : [e.changedTouches[0].pageX, e.changedTouches[0].pageY]; //smartphone
+                               this.selectedPiece.style.left = (offsetX-this.start.x) + "px";
+                               this.selectedPiece.style.top = (offsetY-this.start.y) + "px";
+                       }
+               },
+               mouseup: function(e) {
+                       if (!this.selectedPiece)
+                               return;
+                       e = e || window.event;
+                       // Read drop target (or parentElement, parentNode... if type == "img")
+                       this.selectedPiece.style.zIndex = -3000; //HACK to find square from final coords
+                       const [offsetX,offsetY] = !!e.clientX
+                               ? [e.clientX,e.clientY]
+                               : [e.changedTouches[0].pageX, e.changedTouches[0].pageY];
+                       let landing = document.elementFromPoint(offsetX, offsetY);
+                       this.selectedPiece.style.zIndex = 3000;
+                       // Next condition: classList.contains(piece) fails because of marks
+                       while (landing.tagName == "IMG")
+                               landing = landing.parentNode;
+                       if (this.start.id == landing.id)
+                       {
+                               // A click: selectedPiece and possibleMoves are already filled
+                               return;
+                       }
+                       // OK: process move attempt
+                       let endSquare = this.getSquareFromId(landing.id);
+                       let moves = this.findMatchingMoves(endSquare);
+                       this.possibleMoves = [];
+                       if (moves.length > 1)
+                               this.choices = moves;
+                       else if (moves.length==1)
+                               this.play(moves[0]);
+                       // Else: impossible move
+                       this.selectedPiece.parentNode.removeChild(this.selectedPiece);
+                       delete this.selectedPiece;
+                       this.selectedPiece = null;
+               },
+               findMatchingMoves: function(endSquare) {
+                       // Run through moves list and return the matching set (if promotions...)
+                       let moves = [];
+                       this.possibleMoves.forEach(function(m) {
+                               if (endSquare[0] == m.end.x && endSquare[1] == m.end.y)
+                                       moves.push(m);
+                       });
+                       return moves;
+               },
+               animateMove: function(move) {
+                       let startSquare = document.getElementById(this.getSquareId(move.start));
+                       let endSquare = document.getElementById(this.getSquareId(move.end));
+                       let rectStart = startSquare.getBoundingClientRect();
+                       let rectEnd = endSquare.getBoundingClientRect();
+                       let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y};
+                       let movingPiece =
+                               document.querySelector("#" + this.getSquareId(move.start) + " > img.piece");
+                       // HACK for animation (with positive translate, image slides "under background")
+                       // Possible improvement: just alter squares on the piece's way...
+                       squares = document.getElementsByClassName("board");
+                       for (let i=0; i<squares.length; i++)
+                       {
+                               let square = squares.item(i);
+                               if (square.id != this.getSquareId(move.start))
+                                       square.style.zIndex = "-1";
+                       }
+                       movingPiece.style.transform = "translate(" + translation.x + "px," +
+                               translation.y + "px)";
+                       movingPiece.style.transitionDuration = "0.2s";
+                       movingPiece.style.zIndex = "3000";
+                       setTimeout( () => {
+                               for (let i=0; i<squares.length; i++)
+                                       squares.item(i).style.zIndex = "auto";
+                               movingPiece.style = {}; //required e.g. for 0-0 with KR swap
+                               this.play(move);
+                       }, 250);
+               },
diff --git a/public/javascripts/components/chat.js b/public/javascripts/components/chat.js
new file mode 100644 (file)
index 0000000..a148990
--- /dev/null
@@ -0,0 +1,106 @@
+                       myname: localStorage["username"] || "anonymous",
+                       oppName: "anonymous", //opponent name, revealed after a game (if provided)
+                       chats: [], //chat messages after human game
+               
+       
+       
+               let chatEltsArray =
+               [
+                       h('label',
+                               {
+                                       attrs: { "id": "close-chat", "for": "modal-chat" },
+                                       "class": { "modal-close": true },
+                               }
+                       ),
+                       h('h3',
+                               {
+                                       attrs: { "id": "titleChat" },
+                                       "class": { "section": true },
+                                       domProps: { innerHTML: translations["Chat with "] + this.oppName },
+                               }
+                       )
+               ];
+               for (let chat of this.chats)
+               {
+                       chatEltsArray.push(
+                               h('p',
+                                       {
+                                               "class": {
+                                                       "my-chatmsg": chat.author==this.myid,
+                                                       "opp-chatmsg": chat.author==this.oppid,
+                                               },
+                                               domProps: { innerHTML: chat.msg }
+                                       }
+                               )
+                       );
+               }
+               chatEltsArray = chatEltsArray.concat([
+                       h('input',
+                               {
+                                       attrs: {
+                                               "id": "input-chat",
+                                               type: "text",
+                                               placeholder: translations["Type here"],
+                                       },
+                                       on: { keyup: this.trySendChat }, //if key is 'enter'
+                               }
+                       ),
+                       h('button',
+                               {
+                                       attrs: { id: "sendChatBtn"},
+                                       on: { click: this.sendChat },
+                                       domProps: { innerHTML: translations["Send"] },
+                               }
+                       )
+               ]);
+               const modalChat = [
+                       h('input',
+                               {
+                                       attrs: { "id": "modal-chat", type: "checkbox" },
+                                       "class": { "modal": true },
+                               }),
+                       h('div',
+                               {
+                                       attrs: { "role": "dialog", "aria-labelledby": "titleChat" },
+                               },
+                               [
+                                       h('div',
+                                               {
+                                                       "class": { "card": true, "smallpad": true },
+                                               },
+                                               chatEltsArray
+                                       )
+                               ]
+                       )
+               ];
+               elementArray = elementArray.concat(modalChat);
+       
+       
+                               case "newchat":
+                                       // Receive new chat
+                                       this.chats.push({msg:data.msg, author:this.oppid});
+                                       break;
+                               case "oppname":
+                                       // Receive opponent's name
+                                       this.oppName = data.name;
+                                       break;
+       
+       
+       // TODO: complete this component
+               trySendChat: function(e) {
+                       if (e.keyCode == 13) //'enter' key
+                               this.sendChat();
+               },
+               sendChat: function() {
+                       let chatInput = document.getElementById("input-chat");
+                       const chatTxt = chatInput.value;
+                       chatInput.value = "";
+                       this.chats.push({msg:chatTxt, author:this.myid});
+                       this.conn.send(JSON.stringify({
+                               code:"newchat", oppid: this.oppid, msg: chatTxt}));
+               },
+               startChat: function(e) {
+                       this.getRidOfTooltip(e.currentTarget);
+                       document.getElementById("modal-chat").checked = true;
+               },
+
index c2cce62..8009edd 100644 (file)
@@ -1,30 +1,23 @@
 // Game logic on a variant page
 Vue.component('my-game', {
-       props: ["problem"],
+       props: ["gameId"], //to find the game in storage (assumption: it exists)
        data: function() {
                return {
-                       vr: null, //object to check moves, store them, FEN..
-                       mycolor: "w",
-                       possibleMoves: [], //filled after each valid click/dragstart
-                       choices: [], //promotion pieces, or checkered captures... (as moves)
-                       start: {}, //pixels coordinates + id of starting square (click or drag)
-                       selectedPiece: null, //moving piece (or clicked piece)
-                       conn: null, //socket connection
-                       score: "*", //'*' means 'unfinished'
-                       mode: "idle", //human, friend, problem, computer or idle (if not playing)
+                       
+                       // TODO: merge next variables into "game"
+                       // if oppid == "computer" then mode = "computer" (otherwise human)
                        myid: "", //our ID, always set
+               //this.myid = localStorage.getItem("myid")
                        oppid: "", //opponent ID in case of HH game
-                       gameId: "", //useful if opponent started other human games after we disconnected
-                       myname: localStorage["username"] || "anonymous",
-                       oppName: "anonymous", //opponent name, revealed after a game (if provided)
-                       chats: [], //chat messages after human game
+                       score: "*", //'*' means 'unfinished'
+                       mycolor: "w",
+                       fromChallenge: false, //if true, show chat during game
+                       
+                       conn: null, //socket connection
                        oppConnected: false,
                        seek: false,
                        fenStart: "",
-                       incheck: [],
                        pgnTxt: "",
-                       hints: (!localStorage["hints"] ? true : localStorage["hints"] === "1"),
-                       bcolor: localStorage["bcolor"] || "lichess", //lichess, chesscom or chesstempo
                        // sound level: 0 = no sound, 1 = sound only on newgame, 2 = always
                        sound: parseInt(localStorage["sound"] || "2"),
                        // Web worker to play computer moves without freezing interface:
@@ -32,757 +25,47 @@ Vue.component('my-game', {
                        timeStart: undefined, //time when computer starts thinking
                };
        },
-       watch: {
-               problem: function(p) {
-                       // 'problem' prop changed: update board state
-                       this.newGame("problem", p.fen, V.ParseFen(p.fen).turn);
+       computed: {
+               mode: function() {
+                       return (this.game.oppid == "computer" ? "computer" ? "human");
+               },
+               showChat: function() {
+                       return this.mode=='human' &&
+                               (this.game.score != '*' || this.game.fromChallenge);
+               },
+               showMoves: function() {
+                       return window.innerWidth >= 768;
                },
        },
-       // TODO: split the rendering in other components ?
-       // At least divide in parts, it's too big now.
-       render(h) {
-               const [sizeX,sizeY] = [V.size.x,V.size.y];
-               // Precompute hints squares to facilitate rendering
-               let hintSquares = doubleArray(sizeX, sizeY, false);
-               this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; });
-               // Also precompute in-check squares
-               let incheckSq = doubleArray(sizeX, sizeY, false);
-               this.incheck.forEach(sq => { incheckSq[sq[0]][sq[1]] = true; });
-               let elementArray = [];
-               let actionArray = [];
-               actionArray.push(
-                       h('button',
-                       {
-                               on: { click: this.clickGameSeek },
-                               attrs: { "aria-label": translations['New live game'] },
-                               'class': {
-                                       "tooltip": true,
-                                       "play": true,
-                                       "seek": this.seek,
-                                       "playing": this.mode == "human" && this.score == "*",
-                               },
-                       },
-                       [h('i', { 'class': { "material-icons": true } }, "accessibility")])
-               );
-               if (["idle","computer","friend"].includes(this.mode)
-                       || (this.mode == "human" && this.score != "*"))
-               {
-                       actionArray.push(
-                               h('button',
-                               {
-                                       on: { click: this.clickComputerGame },
-                                       attrs: { "aria-label": translations['New game versus computer'] },
-                                       'class': {
-                                               "tooltip":true,
-                                               "play": true,
-                                               "playing": this.mode == "computer" && this.score == "*",
-                                               "spaceleft": true,
-                                       },
-                               },
-                               [h('i', { 'class': { "material-icons": true } }, "computer")])
-                       );
-               }
-               if (variant != "Dark" && (["idle","friend"].includes(this.mode)
-                       || (["computer","human"].includes(this.mode) && this.score != "*")))
-               {
-                       actionArray.push(
-                               h('button',
-                               {
-                                       on: { click: this.clickFriendGame },
-                                       attrs: { "aria-label": translations['Analysis mode'] },
-                                       'class': {
-                                               "tooltip":true,
-                                               "play": true,
-                                               "playing": this.mode == "friend",
-                                               "spaceleft": true,
-                                       },
-                               },
-                               [h('i', { 'class': { "material-icons": true } }, "people")])
-                       );
-               }
-               if (!!this.vr)
-               {
-                       const square00 = document.getElementById("sq-0-0");
-                       const squareWidth = !!square00
-                               ? parseFloat(window.getComputedStyle(square00).width.slice(0,-2))
-                               : 0;
-                       const settingsBtnElt = document.getElementById("settingsBtn");
-                       const settingsStyle = !!settingsBtnElt
-                               ? window.getComputedStyle(settingsBtnElt)
-                               : {width:"46px", height:"26px"};
-                       const [indicWidth,indicHeight] = //[44,24];
-                       [
-                               // NOTE: -2 for border
-                               parseFloat(settingsStyle.width.slice(0,-2)) - 2,
-                               parseFloat(settingsStyle.height.slice(0,-2)) - 2
-                       ];
-                       let aboveBoardElts = [];
-                       if (this.mode == "human")
-                       {
-                               const connectedIndic = h(
-                                       'div',
-                                       {
-                                               "class": {
-                                                       "indic-left": true,
-                                                       "connected": this.oppConnected,
-                                                       "disconnected": !this.oppConnected,
-                                               },
-                                               style: {
-                                                       "width": indicWidth + "px",
-                                                       "height": indicHeight + "px",
-                                               },
-                                       }
-                               );
-                               aboveBoardElts.push(connectedIndic);
-                       }
-                       if (this.mode == "human" && this.score != "*")
-                       {
-                               const chatButton = h(
-                                       'button',
-                                       {
-                                               on: { click: this.startChat },
-                                               attrs: {
-                                                       "aria-label": translations['Start chat'],
-                                                       "id": "chatBtn",
-                                               },
-                                               'class': {
-                                                       "tooltip": true,
-                                                       "play": true,
-                                                       "above-board": true,
-                                                       "indic-left": true,
-                                               },
-                                       },
-                                       [h('i', { 'class': { "material-icons": true } }, "chat")]
-                               );
-                               aboveBoardElts.push(chatButton);
-                       }
-                       if (["human","computer","friend"].includes(this.mode))
-                       {
-                               const clearButton = h(
-                                       'button',
-                                       {
-                                               on: { click: this.clearCurrentGame },
-                                               attrs: {
-                                                       "aria-label": translations['Clear current game'],
-                                                       "id": "clearBtn",
-                                               },
-                                               'class': {
-                                                       "tooltip": true,
-                                                       "play": true,
-                                                       "above-board": true,
-                                                       "indic-left": true,
-                                               },
-                                       },
-                                       [h('i', { 'class': { "material-icons": true } }, "clear")]
-                               );
-                               aboveBoardElts.push(clearButton);
-                       }
-                       const turnIndic = h(
-                               'div',
-                               {
-                                       "class": {
-                                               "indic-right": true,
-                                               "white-turn": this.vr.turn=="w",
-                                               "black-turn": this.vr.turn=="b",
-                                       },
-                                       style: {
-                                               "width": indicWidth + "px",
-                                               "height": indicHeight + "px",
-                                       },
-                               }
-                       );
-                       aboveBoardElts.push(turnIndic);
-                       elementArray.push(
-                               h('div',
-                                       { "class": { "aboveboard-wrapper": true } },
-                                       aboveBoardElts
-                               )
-                       );
-                       if (this.mode == "problem")
-                       {
-                               // Show problem instructions
-                               elementArray.push(
-                                       h('div',
-                                               {
-                                                       attrs: { id: "instructions-div" },
-                                                       "class": {
-                                                               "clearer": true,
-                                                               "section-content": true,
-                                                       },
-                                               },
-                                               [
-                                                       h('p',
-                                                               {
-                                                                       attrs: { id: "problem-instructions" },
-                                                                       domProps: { innerHTML: this.problem.instructions }
-                                                               }
-                                                       )
-                                               ]
-                                       )
-                               );
-                       }
-                       const choices = h('div',
-                               {
-                                       attrs: { "id": "choices" },
-                                       'class': { 'row': true },
-                                       style: {
-                                               "display": this.choices.length>0?"block":"none",
-                                               "top": "-" + ((sizeY/2)*squareWidth+squareWidth/2) + "px",
-                                               "width": (this.choices.length * squareWidth) + "px",
-                                               "height": squareWidth + "px",
-                                       },
-                               },
-                               this.choices.map( m => { //a "choice" is a move
-                                       return h('div',
-                                               {
-                                                       'class': {
-                                                               'board': true,
-                                                               ['board'+sizeY]: true,
-                                                       },
-                                                       style: {
-                                                               'width': (100/this.choices.length) + "%",
-                                                               'padding-bottom': (100/this.choices.length) + "%",
-                                                       },
-                                               },
-                                               [h('img',
-                                                       {
-                                                               attrs: { "src": '/images/pieces/' +
-                                                                       VariantRules.getPpath(m.appear[0].c+m.appear[0].p) + '.svg' },
-                                                               'class': { 'choice-piece': true },
-                                                               on: {
-                                                                       "click": e => { this.play(m); this.choices=[]; },
-                                                                       // NOTE: add 'touchstart' event to fix a problem on smartphones
-                                                                       "touchstart": e => { this.play(m); this.choices=[]; },
-                                                               },
-                                                       })
-                                               ]
-                                       );
-                               })
-                       );
-                       // Create board element (+ reserves if needed by variant or mode)
-                       const lm = this.vr.lastMove;
-                       const showLight = this.hints && variant!="Dark" &&
-                               (this.mode != "idle" ||
-                                       (this.vr.moves.length > 0 && this.cursor==this.vr.moves.length));
-                       const gameDiv = h('div',
-                               {
-                                       'class': {
-                                               'game': true,
-                                               'clearer': true,
-                                       },
-                               },
-                               [_.range(sizeX).map(i => {
-                                       let ci = (this.mycolor=='w' ? i : sizeX-i-1);
-                                       return h(
-                                               'div',
-                                               {
-                                                       'class': {
-                                                               'row': true,
-                                                       },
-                                                       style: { 'opacity': this.choices.length>0?"0.5":"1" },
-                                               },
-                                               _.range(sizeY).map(j => {
-                                                       let cj = (this.mycolor=='w' ? j : sizeY-j-1);
-                                                       let elems = [];
-                                                       if (this.vr.board[ci][cj] != VariantRules.EMPTY && (variant!="Dark"
-                                                               || this.score!="*" || this.vr.enlightened[this.mycolor][ci][cj]))
-                                                       {
-                                                               elems.push(
-                                                                       h(
-                                                                               'img',
-                                                                               {
-                                                                                       'class': {
-                                                                                               'piece': true,
-                                                                                               'ghost': !!this.selectedPiece
-                                                                                                       && this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj,
-                                                                                       },
-                                                                                       attrs: {
-                                                                                               src: "/images/pieces/" +
-                                                                                                       VariantRules.getPpath(this.vr.board[ci][cj]) + ".svg",
-                                                                                       },
-                                                                               }
-                                                                       )
-                                                               );
-                                                       }
-                                                       if (this.hints && hintSquares[ci][cj])
-                                                       {
-                                                               elems.push(
-                                                                       h(
-                                                                               'img',
-                                                                               {
-                                                                                       'class': {
-                                                                                               'mark-square': true,
-                                                                                       },
-                                                                                       attrs: {
-                                                                                               src: "/images/mark.svg",
-                                                                                       },
-                                                                               }
-                                                                       )
-                                                               );
-                                                       }
-                                                       return h(
-                                                               'div',
-                                                               {
-                                                                       'class': {
-                                                                               'board': true,
-                                                                               ['board'+sizeY]: true,
-                                                                               'light-square': (i+j)%2==0,
-                                                                               'dark-square': (i+j)%2==1,
-                                                                               [this.bcolor]: true,
-                                                                               'in-shadow': variant=="Dark" && this.score=="*"
-                                                                                       && !this.vr.enlightened[this.mycolor][ci][cj],
-                                                                               'highlight': showLight && !!lm && _.isMatch(lm.end, {x:ci,y:cj}),
-                                                                               'incheck': showLight && incheckSq[ci][cj],
-                                                                       },
-                                                                       attrs: {
-                                                                               id: this.getSquareId({x:ci,y:cj}),
-                                                                       },
-                                                               },
-                                                               elems
-                                                       );
-                                               })
-                                       );
-                               }), choices]
-                       );
-                       if (["human","computer"].includes(this.mode))
-                       {
-                               if (this.score == "*")
-                               {
-                                       actionArray.push(
-                                               h('button',
-                                                       {
-                                                               on: { click: this.resign },
-                                                               attrs: { "aria-label": translations['Resign'] },
-                                                               'class': {
-                                                                       "tooltip":true,
-                                                                       "play": true,
-                                                                       "spaceleft": true,
-                                                               },
-                                                       },
-                                                       [h('i', { 'class': { "material-icons": true } }, "flag")])
-                                       );
-                               }
-                               else
-                               {
-                                       // A game finished, and another is not started yet: allow navigation
-                                       actionArray = actionArray.concat([
-                                               h('button',
-                                                       {
-                                                               on: { click: e => this.undo() },
-                                                               attrs: { "aria-label": translations['Undo'] },
-                                                               "class": {
-                                                                       "play": true,
-                                                                       "big-spaceleft": true,
-                                                               },
-                                                       },
-                                                       [h('i', { 'class': { "material-icons": true } }, "fast_rewind")]),
-                                               h('button',
-                                                       {
-                                                               on: { click: e => this.play() },
-                                                               attrs: { "aria-label": translations['Play'] },
-                                                               "class": {
-                                                                       "play": true,
-                                                                       "spaceleft": true,
-                                                               },
-                                                       },
-                                                       [h('i', { 'class': { "material-icons": true } }, "fast_forward")]),
-                                               ]
-                                       );
-                               }
-                       }
-                       if (["friend","problem"].includes(this.mode))
-                       {
-                               actionArray = actionArray.concat(
-                               [
-                                       h('button',
-                                               {
-                                                       on: { click: this.undoInGame },
-                                                       attrs: { "aria-label": translations['Undo'] },
-                                                       "class": {
-                                                               "play": true,
-                                                               "big-spaceleft": true,
-                                                       },
-                                               },
-                                               [h('i', { 'class': { "material-icons": true } }, "undo")]
-                                       ),
-                                       h('button',
-                                               {
-                                                       on: { click: () => { this.mycolor = this.vr.getOppCol(this.mycolor) } },
-                                                       attrs: { "aria-label": translations['Flip board'] },
-                                                       "class": {
-                                                               "play": true,
-                                                               "spaceleft": true,
-                                                       },
-                                               },
-                                               [h('i', { 'class': { "material-icons": true } }, "cached")]
-                                       ),
-                               ]);
-                       }
-                       elementArray.push(gameDiv);
-                       if (!!this.vr.reserve)
-                       {
-                               const shiftIdx = (this.mycolor=="w" ? 0 : 1);
-                               let myReservePiecesArray = [];
-                               for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
-                               {
-                                       myReservePiecesArray.push(h('div',
-                                       {
-                                               'class': {'board':true, ['board'+sizeY]:true},
-                                               attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) }
-                                       },
-                                       [
-                                               h('img',
-                                               {
-                                                       'class': {"piece":true, "reserve":true},
-                                                       attrs: {
-                                                               "src": "/images/pieces/" +
-                                                                       this.vr.getReservePpath(this.mycolor,i) + ".svg",
-                                                       }
-                                               }),
-                                               h('sup',
-                                                       {"class": { "reserve-count": true } },
-                                                       [ this.vr.reserve[this.mycolor][VariantRules.RESERVE_PIECES[i]] ]
-                                               )
-                                       ]));
-                               }
-                               let oppReservePiecesArray = [];
-                               const oppCol = this.vr.getOppCol(this.mycolor);
-                               for (let i=0; i<VariantRules.RESERVE_PIECES.length; i++)
-                               {
-                                       oppReservePiecesArray.push(h('div',
-                                       {
-                                               'class': {'board':true, ['board'+sizeY]:true},
-                                               attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) }
-                                       },
-                                       [
-                                               h('img',
-                                               {
-                                                       'class': {"piece":true, "reserve":true},
-                                                       attrs: {
-                                                               "src": "/images/pieces/" +
-                                                                       this.vr.getReservePpath(oppCol,i) + ".svg",
-                                                       }
-                                               }),
-                                               h('sup',
-                                                       {"class": { "reserve-count": true } },
-                                                       [ this.vr.reserve[oppCol][VariantRules.RESERVE_PIECES[i]] ]
-                                               )
-                                       ]));
-                               }
-                               let reserves = h('div',
-                                       {
-                                               'class':{
-                                                       'game': true,
-                                                       "reserve-div": true,
-                                               },
-                                       },
-                                       [
-                                               h('div',
-                                                       {
-                                                               'class': {
-                                                                       'row': true,
-                                                                       "reserve-row-1": true,
-                                                               },
-                                                       },
-                                                       myReservePiecesArray
-                                               ),
-                                               h('div',
-                                                       { 'class': { 'row': true }},
-                                                       oppReservePiecesArray
-                                               )
-                                       ]
-                               );
-                               elementArray.push(reserves);
-                       }
-                       const modalEog = [
-                               h('input',
-                                       {
-                                               attrs: { "id": "modal-eog", type: "checkbox" },
-                                               "class": { "modal": true },
-                                       }),
-                               h('div',
-                                       {
-                                               attrs: { "role": "dialog", "aria-labelledby": "eogMessage" },
-                                       },
-                                       [
-                                               h('div',
-                                                       {
-                                                               "class": {
-                                                                       "card": true,
-                                                                       "smallpad": true,
-                                                                       "small-modal": true,
-                                                                       "text-center": true,
-                                                               },
-                                                       },
-                                                       [
-                                                               h('label',
-                                                                       {
-                                                                               attrs: { "for": "modal-eog" },
-                                                                               "class": { "modal-close": true },
-                                                                       }
-                                                               ),
-                                                               h('h3',
-                                                                       {
-                                                                               attrs: { "id": "eogMessage" },
-                                                                               "class": { "section": true },
-                                                                               domProps: { innerHTML: this.endgameMessage },
-                                                                       }
-                                                               )
-                                                       ]
-                                               )
-                                       ]
-                               )
-                       ];
-                       elementArray = elementArray.concat(modalEog);
-               }
-               const modalFenEdit = [
-                       h('input',
-                               {
-                                       attrs: { "id": "modal-fenedit", type: "checkbox" },
-                                       "class": { "modal": true },
-                               }),
-                       h('div',
-                               {
-                                       attrs: { "role": "dialog", "aria-labelledby": "titleFenedit" },
-                               },
-                               [
-                                       h('div',
-                                               {
-                                                       "class": { "card": true, "smallpad": true },
-                                               },
-                                               [
-                                                       h('label',
-                                                               {
-                                                                       attrs: { "id": "close-fenedit", "for": "modal-fenedit" },
-                                                                       "class": { "modal-close": true },
-                                                               }
-                                                       ),
-                                                       h('h3',
-                                                               {
-                                                                       attrs: { "id": "titleFenedit" },
-                                                                       "class": { "section": true },
-                                                                       domProps: { innerHTML: translations["Game state (FEN):"] },
-                                                               }
-                                                       ),
-                                                       h('input',
-                                                               {
-                                                                       attrs: {
-                                                                               "id": "input-fen",
-                                                                               type: "text",
-                                                                               value: VariantRules.GenRandInitFen(),
-                                                                       },
-                                                               }
-                                                       ),
-                                                       h('button',
-                                                               {
-                                                                       on: { click:
-                                                                               () => {
-                                                                                       const fen = document.getElementById("input-fen").value;
-                                                                                       document.getElementById("modal-fenedit").checked = false;
-                                                                                       this.newGame("friend", fen);
-                                                                               }
-                                                                       },
-                                                                       domProps: { innerHTML: translations["Ok"] },
-                                                               }
-                                                       ),
-                                                       h('button',
-                                                               {
-                                                                       on: { click:
-                                                                               () => {
-                                                                                       document.getElementById("input-fen").value =
-                                                                                               VariantRules.GenRandInitFen();
-                                                                               }
-                                                                       },
-                                                                       domProps: { innerHTML: translations["Random"] },
-                                                               }
-                                                       ),
-                                               ]
-                                       )
-                               ]
-                       )
-               ];
-               elementArray = elementArray.concat(modalFenEdit);
-               let chatEltsArray =
-               [
-                       h('label',
-                               {
-                                       attrs: { "id": "close-chat", "for": "modal-chat" },
-                                       "class": { "modal-close": true },
-                               }
-                       ),
-                       h('h3',
-                               {
-                                       attrs: { "id": "titleChat" },
-                                       "class": { "section": true },
-                                       domProps: { innerHTML: translations["Chat with "] + this.oppName },
-                               }
-                       )
-               ];
-               for (let chat of this.chats)
-               {
-                       chatEltsArray.push(
-                               h('p',
-                                       {
-                                               "class": {
-                                                       "my-chatmsg": chat.author==this.myid,
-                                                       "opp-chatmsg": chat.author==this.oppid,
-                                               },
-                                               domProps: { innerHTML: chat.msg }
-                                       }
-                               )
-                       );
-               }
-               chatEltsArray = chatEltsArray.concat([
-                       h('input',
-                               {
-                                       attrs: {
-                                               "id": "input-chat",
-                                               type: "text",
-                                               placeholder: translations["Type here"],
-                                       },
-                                       on: { keyup: this.trySendChat }, //if key is 'enter'
-                               }
-                       ),
-                       h('button',
-                               {
-                                       attrs: { id: "sendChatBtn"},
-                                       on: { click: this.sendChat },
-                                       domProps: { innerHTML: translations["Send"] },
-                               }
-                       )
-               ]);
-               const modalChat = [
-                       h('input',
-                               {
-                                       attrs: { "id": "modal-chat", type: "checkbox" },
-                                       "class": { "modal": true },
-                               }),
-                       h('div',
-                               {
-                                       attrs: { "role": "dialog", "aria-labelledby": "titleChat" },
-                               },
-                               [
-                                       h('div',
-                                               {
-                                                       "class": { "card": true, "smallpad": true },
-                                               },
-                                               chatEltsArray
-                                       )
-                               ]
-                       )
-               ];
-               elementArray = elementArray.concat(modalChat);
-               const actions = h('div',
-                       {
-                               attrs: { "id": "actions" },
-                               'class': { 'text-center': true },
-                       },
-                       actionArray
-               );
-               elementArray.push(actions);
-               if (!!this.vr)
-               {
-                       if (this.mode == "problem")
-                       {
-                               // Show problem solution (on click)
-                               elementArray.push(
-                                       h('div',
-                                               {
-                                                       attrs: { id: "solution-div" },
-                                                       "class": { "section-content": true },
-                                               },
-                                               [
-                                                       h('h3',
-                                                               {
-                                                                       "class": { clickable: true },
-                                                                       domProps: { innerHTML: translations["Show solution"] },
-                                                                       on: { click: this.toggleShowSolution },
-                                                               }
-                                                       ),
-                                                       h('p',
-                                                               {
-                                                                       attrs: { id: "problem-solution" },
-                                                                       domProps: { innerHTML: this.problem.solution }
-                                                               }
-                                                       )
-                                               ]
-                                       )
-                               );
-                       }
-                       if (variant != "Dark" || this.score!="*")
-                       {
-                               // Show current FEN
-                               elementArray.push(
-                                       h('div',
-                                               {
-                                                       attrs: { id: "fen-div" },
-                                                       "class": { "section-content": true },
-                                               },
-                                               [
-                                                       h('p',
-                                                               {
-                                                                       attrs: { id: "fen-string" },
-                                                                       domProps: { innerHTML: this.vr.getBaseFen() },
-                                                                       "class": { "text-center": true },
-                                                               }
-                                                       )
-                                               ]
-                                       )
-                               );
-                       }
-                       elementArray.push(
-                               h('div',
-                                       {
-                                               attrs: { id: "pgn-div" },
-                                               "class": { "section-content": true },
-                                       },
-                                       [
-                                               h('a',
-                                                       {
-                                                               attrs: {
-                                                                       id: "download",
-                                                                       href: "#",
-                                                               }
-                                                       }
-                                               ),
-                                               h('button',
-                                                       {
-                                                               attrs: { "id": "downloadBtn" },
-                                                               on: { click: this.download },
-                                                               domProps: { innerHTML: translations["Download PGN"] },
-                                                       }
-                                               ),
-                                       ]
-                               )
-                       );
-               }
-               return h(
-                       'div',
-                       {
-                               'class': {
-                                       "col-sm-12":true,
-                                       "col-md-10":true,
-                                       "col-md-offset-1":true,
-                                       "col-lg-8":true,
-                                       "col-lg-offset-2":true,
-                               },
-                               // NOTE: click = mousedown + mouseup
-                               on: {
-                                       mousedown: this.mousedown,
-                                       mousemove: this.mousemove,
-                                       mouseup: this.mouseup,
-                                       touchstart: this.mousedown,
-                                       touchmove: this.mousemove,
-                                       touchend: this.mouseup,
-                               },
-                       },
-                       elementArray
-               );
-       },
+       // Modal end of game, and then sub-components
+       template: `
+               <div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
+                       <input id="modal-eog" type="checkbox" class="modal"/>
+                       <div role="dialog" aria-labelledby="eogMessage">
+                               <div class="card smallpad small-modal text-center">
+                                       <label for="modal-eog" class="modal-close"></label>
+                                       <h3 id="eogMessage" class="section">{{ endgameMessage }}</h3>
+
+                       <my-chat v-if="showChat"></my-chat>
+                       //TODO: connection + turn indicators en haut à droite (superposé au menu)
+                       <my-board></my-board>
+                       // TODO: controls: abort, clear, resign, draw (avec confirm box)
+                       // et si partie terminée : (mode analyse) just clear, back / play
+                       // + flip button toujours disponible
+                       
+                       <div id="pgn-div" class="section-content">
+                               <a id="download" href: "#"></a>
+                               <button id="downloadBtn" @click="download">
+                                       {{ translations["Download PGN"] }}
+                               </button>
+                       
+                       <my-move-list v-if="showMoves"></my-move-list>
+               </div>
+       `,
        computed: {
                endgameMessage: function() {
                        let eogMessage = "Unfinished";
-                       switch (this.score)
+                       switch (this.game.score)
                        {
                                case "1-0":
                                        eogMessage = translations["White win"];
@@ -799,19 +82,11 @@ Vue.component('my-game', {
        },
        created: function() {
                const url = socketUrl;
-               const humanContinuation = (localStorage.getItem("variant") === variant);
-               const computerContinuation = (localStorage.getItem("comp-variant") === variant);
-               const friendContinuation = (localStorage.getItem("anlz-variant") === variant);
-               this.myid = (humanContinuation ? localStorage.getItem("myid") : getRandString());
                this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant);
-               const socketOpenListener = () => {
-                       if (humanContinuation) //game VS human has priority
-                               this.continueGame("human");
-                       else if (computerContinuation)
-                               this.continueGame("computer");
-                       else if (friendContinuation)
-                               this.continueGame("friend");
-               };
+//             const socketOpenListener = () => {
+//             };
+
+// TODO: after game, archive in indexedDB
 
                // TODO: this events listener is central. Refactor ? How ?
                const socketMessageListener = msg => {
@@ -819,25 +94,6 @@ Vue.component('my-game', {
                        let L = undefined;
                        switch (data.code)
                        {
-                               case "oppname":
-                                       // Receive opponent's name
-                                       this.oppName = data.name;
-                                       break;
-                               case "newchat":
-                                       // Receive new chat
-                                       this.chats.push({msg:data.msg, author:this.oppid});
-                                       break;
-                               case "duplicate":
-                                       // We opened another tab on the same game
-                                       this.mode = "idle";
-                                       this.vr = null;
-                                       alert(translations[
-                                               "Already playing a game in this variant on another tab!"]);
-                                       break;
-                               case "newgame": //opponent found
-                                       // oppid: opponent socket ID
-                                       this.newGame("human", data.fen, data.color, data.oppid, data.gameid);
-                                       break;
                                case "newmove": //..he played!
                                        this.play(data.move, (variant!="Dark" ? "animate" : null));
                                        break;
@@ -906,14 +162,17 @@ Vue.component('my-game', {
 
                const socketCloseListener = () => {
                        this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant);
-                       this.conn.addEventListener('open', socketOpenListener);
+                       //this.conn.addEventListener('open', socketOpenListener);
                        this.conn.addEventListener('message', socketMessageListener);
                        this.conn.addEventListener('close', socketCloseListener);
                };
-               this.conn.onopen = socketOpenListener;
+               //this.conn.onopen = socketOpenListener;
                this.conn.onmessage = socketMessageListener;
                this.conn.onclose = socketCloseListener;
+               
+               
                // Listen to keyboard left/right to navigate in game
+               // TODO: also mouse wheel !
                document.onkeydown = event => {
                        if (["human","computer"].includes(this.mode) &&
                                !!this.vr && this.vr.moves.length > 0 && [37,39].includes(event.keyCode))
@@ -925,6 +184,8 @@ Vue.component('my-game', {
                                        this.play();
                        }
                };
+
+
                // Computer moves web worker logic: (TODO: also for observers in HH games)
                this.compWorker.postMessage(["scripts",variant]);
                const self = this;
@@ -951,59 +212,12 @@ Vue.component('my-game', {
                        }, delay);
                }
        },
-       methods: {
-               // TODO: in settings.js
-               setMyname: function(e) {
-                       this.myname = e.target.value;
-                       localStorage["username"] = this.myname;
-               },
-               showSettings: function(e) {
-                       this.getRidOfTooltip(e.currentTarget);
-                       document.getElementById("modal-settings").checked = true;
-               },
-               toggleHints: function() {
-                       this.hints = !this.hints;
-                       localStorage["hints"] = (this.hints ? "1" : "0");
-               },
-               setBoardColor: function(e) {
-                       this.bcolor = e.target.options[e.target.selectedIndex].value;
-                       localStorage["bcolor"] = this.bcolor;
-               },
-               setSound: function(e) {
-                       this.sound = parseInt(e.target.options[e.target.selectedIndex].value);
-                       localStorage["sound"] = this.sound;
-               },
-
-               // TODO: in another component
-               trySendChat: function(e) {
-                       if (e.keyCode == 13) //'enter' key
-                               this.sendChat();
-               },
-               sendChat: function() {
-                       let chatInput = document.getElementById("input-chat");
-                       const chatTxt = chatInput.value;
-                       chatInput.value = "";
-                       this.chats.push({msg:chatTxt, author:this.myid});
-                       this.conn.send(JSON.stringify({
-                               code:"newchat", oppid: this.oppid, msg: chatTxt}));
-               },
-               startChat: function(e) {
-                       this.getRidOfTooltip(e.currentTarget);
-                       document.getElementById("modal-chat").checked = true;
-               },
 
-               // TODO: in  problems component
-               toggleShowSolution: function() {
-                       let problemSolution = document.getElementById("problem-solution");
-                       problemSolution.style.display =
-                               !problemSolution.style.display || problemSolution.style.display == "none"
-                                       ? "block"
-                                       : "none";
-               },
 
+       methods: {
                download: function() {
                        // Variants may have special PGN structure (so next function isn't defined here)
-                       const content = this.vr.getPGN(this.mycolor, this.score, this.fenStart, this.mode);
+                       const content = V.GetPGN(this.moves, this.mycolor, this.score, this.fenStart, this.mode);
                        // Prepare and trigger download link
                        let downloadAnchor = document.getElementById("download");
                        downloadAnchor.setAttribute("download", "game.pgn");
@@ -1031,217 +245,6 @@ Vue.component('my-game', {
                        }
                        this.cursor = this.vr.moves.length; //to navigate in finished game
                },
-
-               // TODO: elsewhere (general methods to access/retrieve from storage, to be generalized)
-               // https://developer.mozilla.org/fr/docs/Web/API/API_IndexedDB
-               // https://dexie.org/
-               getStoragePrefix: function(mode) {
-                       let prefix = "";
-                       if (mode == "computer")
-                               prefix = "comp-";
-                       else if (mode == "friend")
-                               prefix = "anlz-";
-                       return prefix;
-               },
-               setStorage: function() {
-                       if (this.mode=="human")
-                       {
-                               localStorage.setItem("myid", this.myid);
-                               localStorage.setItem("oppid", this.oppid);
-                               localStorage.setItem("gameId", this.gameId);
-                       }
-                       const prefix = this.getStoragePrefix(this.mode);
-                       localStorage.setItem(prefix+"variant", variant);
-                       localStorage.setItem(prefix+"mycolor", this.mycolor);
-                       localStorage.setItem(prefix+"fenStart", this.fenStart);
-                       localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves));
-                       localStorage.setItem(prefix+"fen", this.vr.getFen());
-                       localStorage.setItem(prefix+"score", "*");
-               },
-               updateStorage: function() {
-                       const prefix = this.getStoragePrefix(this.mode);
-                       localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves));
-                       localStorage.setItem(prefix+"fen", this.vr.getFen());
-                       if (this.score != "*")
-                               localStorage.setItem(prefix+"score", this.score);
-               },
-               // "computer mode" clearing is done through the menu
-               clearStorage: function() {
-                       if (this.mode == "human")
-                       {
-                               delete localStorage["myid"];
-                               delete localStorage["oppid"];
-                               delete localStorage["gameId"];
-                       }
-                       const prefix = this.getStoragePrefix(this.mode);
-                       delete localStorage[prefix+"variant"];
-                       delete localStorage[prefix+"mycolor"];
-                       delete localStorage[prefix+"fenStart"];
-                       delete localStorage[prefix+"moves"];
-                       delete localStorage[prefix+"fen"];
-                       delete localStorage[prefix+"score"];
-               },
-               clearCurrentGame: function(e) {
-                       this.getRidOfTooltip(e.currentTarget);
-                       this.clearStorage();
-                       location.reload(); //to see clearing effects
-               },
-
-               // HACK because mini-css tooltips are persistent after click...
-               // NOTE: seems to work only in chrome/chromium. TODO...
-               getRidOfTooltip: function(elt) {
-                       elt.style.visibility = "hidden";
-                       setTimeout(() => { elt.style.visibility="visible"; }, 100);
-               },
-
-               // TODO: elsewhere, probably (new game button)
-               clickGameSeek: function(e) {
-                       this.getRidOfTooltip(e.currentTarget);
-                       if (this.mode == "human" && this.score == "*")
-                               return; //no newgame while playing
-                       if (this.seek)
-                       {
-                               this.conn.send(JSON.stringify({code:"cancelnewgame"}));
-                               this.seek = false;
-                       }
-                       else
-                               this.newGame("human");
-               },
-               clickComputerGame: function(e) {
-                       this.getRidOfTooltip(e.currentTarget);
-                       if (this.mode == "computer" && this.score == "*"
-                               && this.vr.turn != this.mycolor)
-                       {
-                               // Wait for computer reply first (avoid potential "ghost move" bug)
-                               return;
-                       }
-                       this.newGame("computer");
-               },
-               clickFriendGame: function(e) {
-                       this.getRidOfTooltip(e.currentTarget);
-                       document.getElementById("modal-fenedit").checked = true;
-               },
-               // In main hall :
-               newGame: function(mode, fenInit, color, oppId, gameId) {
-                       const fen = fenInit || VariantRules.GenRandInitFen();
-                       console.log(fen); //DEBUG
-                       if (mode=="human" && !oppId)
-                       {
-                               const storageVariant = localStorage.getItem("variant");
-                               if (!!storageVariant && storageVariant !== variant
-                                       && localStorage["score"] == "*")
-                               {
-                                       return alert(translations["Finish your "] +
-                                               storageVariant + translations[" game first!"]);
-                               }
-                               // Send game request and wait..
-                               try {
-                                       this.conn.send(JSON.stringify({code:"newgame", fen:fen, gameid: getRandString() }));
-                               } catch (INVALID_STATE_ERR) {
-                                       return; //nothing achieved
-                               }
-                               this.seek = true;
-                               let modalBox = document.getElementById("modal-newgame");
-                               modalBox.checked = true;
-                               setTimeout(() => { modalBox.checked = false; }, 2000);
-                               return;
-                       }
-                       const prefix = this.getStoragePrefix(mode);
-                       if (mode == "computer")
-                       {
-                               const storageVariant = localStorage.getItem(prefix+"variant");
-                               if (!!storageVariant)
-                               {
-                                       const score = localStorage.getItem(prefix+"score");
-                                       if (storageVariant !== variant && score == "*")
-                                       {
-                                               if (!confirm(storageVariant +
-                                                       translations[": unfinished computer game will be erased"]))
-                                               {
-                                                       return;
-                                               }
-                                       }
-                               }
-                       }
-                       else if (mode == "friend")
-                       {
-                               const storageVariant = localStorage.getItem(prefix+"variant");
-                               if (!!storageVariant)
-                               {
-                                       const score = localStorage.getItem(prefix+"score");
-                                       if (storageVariant !== variant && score == "*")
-                                       {
-                                               if (!confirm(storageVariant +
-                                                       translations[": current analysis will be erased"]))
-                                               {
-                                                       return;
-                                               }
-                                       }
-                               }
-                       }
-                       this.vr = new VariantRules(fen, []);
-                       this.score = "*";
-                       this.pgnTxt = ""; //redundant with this.score = "*", but cleaner
-                       this.mode = mode;
-                       this.incheck = [];
-                       this.fenStart = V.ParseFen(fen).position; //this is enough
-                       if (mode=="human")
-                       {
-                               // Opponent found!
-                               this.gameId = gameId;
-                               this.oppid = oppId;
-                               this.oppConnected = true;
-                               this.mycolor = color;
-                               this.seek = false;
-                               if (this.sound >= 1)
-                                       new Audio("/sounds/newgame.mp3").play().catch(err => {});
-                               document.getElementById("modal-newgame").checked = false;
-                       }
-                       else if (mode == "computer")
-                       {
-                               this.compWorker.postMessage(["init",this.vr.getFen()]);
-                               this.mycolor = (Math.random() < 0.5 ? 'w' : 'b');
-                               if (this.mycolor != this.vr.turn)
-                                       this.playComputerMove();
-                       }
-                       else if (mode == "friend")
-                               this.mycolor = "w"; //convention...
-                       //else: problem solving: nothing more to do
-                       if (mode != "problem")
-                               this.setStorage(); //store game state in case of interruptions
-               },
-               continueGame: function(mode) {
-                       this.mode = mode;
-                       this.oppid = (mode=="human" ? localStorage.getItem("oppid") : undefined);
-                       const prefix = this.getStoragePrefix(mode);
-                       this.mycolor = localStorage.getItem(prefix+"mycolor");
-                       const moves = JSON.parse(localStorage.getItem(prefix+"moves"));
-                       const fen = localStorage.getItem(prefix+"fen");
-                       const score = localStorage.getItem(prefix+"score"); //set in "endGame()"
-                       this.fenStart = localStorage.getItem(prefix+"fenStart");
-                       this.vr = new VariantRules(fen, moves);
-                       this.incheck = this.vr.getCheckSquares(this.vr.turn);
-                       if (mode == "human")
-                       {
-                               this.gameId = localStorage.getItem("gameId");
-                               // Send ping to server (answer pong if opponent is connected)
-                               this.conn.send(JSON.stringify({
-                                       code:"ping",oppid:this.oppid,gameId:this.gameId}));
-                       }
-                       else if (mode == "computer")
-                       {
-                               this.compWorker.postMessage(["init",fen]);
-                               if (score == "*" && this.mycolor != this.vr.turn)
-                                       this.playComputerMove();
-                       }
-                       //else: nothing special to do in friend mode
-                       if (score != "*")
-                       {
-                               // Small delay required when continuation run faster than drawing page
-                               setTimeout(() => this.endGame(score), 100);
-                       }
-               },
-
                resign: function(e) {
                        this.getRidOfTooltip(e.currentTarget);
                        if (this.mode == "human" && this.oppConnected)
@@ -1258,147 +261,6 @@ Vue.component('my-game', {
                        this.timeStart = Date.now();
                        this.compWorker.postMessage(["askmove"]);
                },
-
-               // TODO: purely graphical, move in a "chessground-like" component
-               // Get the identifier of a HTML table cell from its numeric coordinates o.x,o.y.
-               getSquareId: function(o) {
-                       // NOTE: a separator is required to allow any size of board
-                       return  "sq-" + o.x + "-" + o.y;
-               },
-               // Inverse function
-               getSquareFromId: function(id) {
-                       let idParts = id.split('-');
-                       return [parseInt(idParts[1]), parseInt(idParts[2])];
-               },
-               mousedown: function(e) {
-                       e = e || window.event;
-                       let ingame = false;
-                       let elem = e.target;
-                       while (!ingame && elem !== null)
-                       {
-                               if (elem.classList.contains("game"))
-                               {
-                                       ingame = true;
-                                       break;
-                               }
-                               elem = elem.parentElement;
-                       }
-                       if (!ingame) //let default behavior (click on button...)
-                               return;
-                       e.preventDefault(); //disable native drag & drop
-                       if (!this.selectedPiece && e.target.classList.contains("piece"))
-                       {
-                               // Next few lines to center the piece on mouse cursor
-                               let rect = e.target.parentNode.getBoundingClientRect();
-                               this.start = {
-                                       x: rect.x + rect.width/2,
-                                       y: rect.y + rect.width/2,
-                                       id: e.target.parentNode.id
-                               };
-                               this.selectedPiece = e.target.cloneNode();
-                               this.selectedPiece.style.position = "absolute";
-                               this.selectedPiece.style.top = 0;
-                               this.selectedPiece.style.display = "inline-block";
-                               this.selectedPiece.style.zIndex = 3000;
-                               const startSquare = this.getSquareFromId(e.target.parentNode.id);
-                               this.possibleMoves = [];
-                               if (this.score == "*")
-                               {
-                                       const color = ["friend","problem"].includes(this.mode)
-                                               ? this.vr.turn
-                                               : this.mycolor;
-                                       if (this.vr.canIplay(color,startSquare))
-                                               this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
-                               }
-                               // Next line add moving piece just after current image
-                               // (required for Crazyhouse reserve)
-                               e.target.parentNode.insertBefore(this.selectedPiece, e.target.nextSibling);
-                       }
-               },
-               mousemove: function(e) {
-                       if (!this.selectedPiece)
-                               return;
-                       e = e || window.event;
-                       // If there is an active element, move it around
-                       if (!!this.selectedPiece)
-                       {
-                               const [offsetX,offsetY] = !!e.clientX
-                                       ? [e.clientX,e.clientY] //desktop browser
-                                       : [e.changedTouches[0].pageX, e.changedTouches[0].pageY]; //smartphone
-                               this.selectedPiece.style.left = (offsetX-this.start.x) + "px";
-                               this.selectedPiece.style.top = (offsetY-this.start.y) + "px";
-                       }
-               },
-               mouseup: function(e) {
-                       if (!this.selectedPiece)
-                               return;
-                       e = e || window.event;
-                       // Read drop target (or parentElement, parentNode... if type == "img")
-                       this.selectedPiece.style.zIndex = -3000; //HACK to find square from final coords
-                       const [offsetX,offsetY] = !!e.clientX
-                               ? [e.clientX,e.clientY]
-                               : [e.changedTouches[0].pageX, e.changedTouches[0].pageY];
-                       let landing = document.elementFromPoint(offsetX, offsetY);
-                       this.selectedPiece.style.zIndex = 3000;
-                       // Next condition: classList.contains(piece) fails because of marks
-                       while (landing.tagName == "IMG")
-                               landing = landing.parentNode;
-                       if (this.start.id == landing.id)
-                       {
-                               // A click: selectedPiece and possibleMoves are already filled
-                               return;
-                       }
-                       // OK: process move attempt
-                       let endSquare = this.getSquareFromId(landing.id);
-                       let moves = this.findMatchingMoves(endSquare);
-                       this.possibleMoves = [];
-                       if (moves.length > 1)
-                               this.choices = moves;
-                       else if (moves.length==1)
-                               this.play(moves[0]);
-                       // Else: impossible move
-                       this.selectedPiece.parentNode.removeChild(this.selectedPiece);
-                       delete this.selectedPiece;
-                       this.selectedPiece = null;
-               },
-               findMatchingMoves: function(endSquare) {
-                       // Run through moves list and return the matching set (if promotions...)
-                       let moves = [];
-                       this.possibleMoves.forEach(function(m) {
-                               if (endSquare[0] == m.end.x && endSquare[1] == m.end.y)
-                                       moves.push(m);
-                       });
-                       return moves;
-               },
-               animateMove: function(move) {
-                       let startSquare = document.getElementById(this.getSquareId(move.start));
-                       let endSquare = document.getElementById(this.getSquareId(move.end));
-                       let rectStart = startSquare.getBoundingClientRect();
-                       let rectEnd = endSquare.getBoundingClientRect();
-                       let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y};
-                       let movingPiece =
-                               document.querySelector("#" + this.getSquareId(move.start) + " > img.piece");
-                       // HACK for animation (with positive translate, image slides "under background")
-                       // Possible improvement: just alter squares on the piece's way...
-                       squares = document.getElementsByClassName("board");
-                       for (let i=0; i<squares.length; i++)
-                       {
-                               let square = squares.item(i);
-                               if (square.id != this.getSquareId(move.start))
-                                       square.style.zIndex = "-1";
-                       }
-                       movingPiece.style.transform = "translate(" + translation.x + "px," +
-                               translation.y + "px)";
-                       movingPiece.style.transitionDuration = "0.2s";
-                       movingPiece.style.zIndex = "3000";
-                       setTimeout( () => {
-                               for (let i=0; i<squares.length; i++)
-                                       squares.item(i).style.zIndex = "auto";
-                               movingPiece.style = {}; //required e.g. for 0-0 with KR swap
-                               this.play(move);
-                       }, 250);
-               },
-
                // OK, these last functions can stay here (?!)
                play: function(move, programmatic) {
                        if (!move)
diff --git a/public/javascripts/components/moveList.js b/public/javascripts/components/moveList.js
new file mode 100644 (file)
index 0000000..9798004
--- /dev/null
@@ -0,0 +1 @@
+//TODO: component for moves list on the right
diff --git a/public/javascripts/components/problemPreview.js b/public/javascripts/components/problemPreview.js
new file mode 100644 (file)
index 0000000..5336503
--- /dev/null
@@ -0,0 +1,25 @@
+// Preview a problem on variant page
+Vue.component('my-problem-preview', {
+       props: ['prob'],
+       template: `
+               <div class="row problem">
+                       <div class="col-sm-12 col-md-6 diagram"
+                               v-html="getDiagram(prob.fen)">
+                       </div>
+                       <div class="col-sm-12 col-md-6">
+                               <p v-html="prob.instructions"></p>
+                               <p v-html="prob.solution"></p>
+                       </div>
+               </div>
+       `,
+       methods: {
+               getDiagram: function(fen) {
+                       const fenParsed = V.ParseFen(fen);
+                       return getDiagram({
+                               position: fenParsed.position,
+                               turn: fenParsed.turn,
+                               // No need for flags here
+                       });
+               },
+       },
+})
diff --git a/public/javascripts/components/problemSummary.js b/public/javascripts/components/problemSummary.js
deleted file mode 100644 (file)
index 5f0fd44..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-// Show a problem summary on variant page or new problem preview
-Vue.component('my-problem-summary', {
-       props: ['prob','preview'],
-       template: `
-               <div class="row problem">
-                       <div class="col-sm-12 col-md-6 diagram"
-                               v-html="getDiagram(prob.fen)">
-                       </div>
-                       <div class="col-sm-12 col-md-6">
-                               <p v-html="prob.instructions"></p>
-                               <p v-if="preview" v-html="prob.solution"></p>
-                               <p v-else class="problem-time">{{ timestamp2date(prob.added) }}</p>
-                               <button v-if="!preview" @click="showProblem()">{{ translate("Solve") }}</button>
-                       </div>
-               </div>
-       `,
-       methods: {
-               translate: function(text) {
-                       return translations[text];
-               },
-               getDiagram: function(fen) {
-                       const fenParsed = V.ParseFen(fen);
-                       return getDiagram({
-                               position: fenParsed.position,
-                               turn: fenParsed.turn,
-                               // No need for flags here
-                       });
-               },
-               timestamp2date(ts) {
-                       return getDate(new Date(ts));
-               },
-               // Propagate "show problem" event to parent component (my-problems)
-               showProblem: function() {
-                       this.$emit('show-problem');
-               },
-       },
-})
index bcd069c..ad8c54c 100644 (file)
@@ -1,38 +1,98 @@
 Vue.component('my-problems', {
        data: function () {
                return {
-                       problems: [],
+                       problems: [], //oldest first
+                       curIdx: 0, //index in problems array
+                       stage: "nothing", //or "preview" after new problem is filled
                        newProblem: {
                                fen: "",
                                instructions: "",
                                solution: "",
-                               stage: "nothing", //or "preview" after new problem is filled
                        },
                };
        },
        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">
-                               <button :aria-label='translate("Load previous problems")' class="tooltip"
-                                               @click="fetchProblems('backward')">
+                               <button :aria-label='translate("Load previous problem")' class="tooltip"
+                                               @click="showPreviousProblem()">
                                        <i class="material-icons">skip_previous</i>
                                </button>
                                <button :aria-label='translate("Add a problem")' class="tooltip"
                                                @click="showNewproblemModal">
                                        {{ translate("New") }}
                                </button>
-                               <button :aria-label='translate("Load next problems")' class="tooltip"
-                                               @click="fetchProblems('forward')">
+                               <button :aria-label='translate("Load next problem")' class="tooltip"
+                                               @click="showNextProblem()">
                                        <i class="material-icons">skip_next</i>
                                </button>
                        </div>
-                       <my-problem-summary v-on:show-problem="bubbleUp(p)"
-                               v-for="(p,idx) in sortedProblems"
+               
+
+
+                       if (this.mode == "problem")
+                       {
+                               // Show problem instructions
+                               elementArray.push(
+                                       h('div',
+                                               {
+                                                       attrs: { id: "instructions-div" },
+                                                       "class": {
+                                                               "clearer": true,
+                                                               "section-content": true,
+                                                       },
+                                               },
+                                               [
+                                                       h('p',
+                                                               {
+                                                                       attrs: { id: "problem-instructions" },
+                                                                       domProps: { innerHTML: this.problem.instructions }
+                                                               }
+                                                       )
+                                               ]
+                                       )
+                               );
+                       }
+
+
+                       // TODO ici :: instrus + diag interactif + solution
+                       my-board + pilotage via movesList + VariantRules !
+                       
+                       <my-problem-preview v-show="stage=='preview'"
+                               v-for="(p,idx) in problems"
                                v-bind:prob="p" v-bind:preview="false" v-bind:key="idx">
                        </my-problem-summary>
+                       if (this.mode == "problem")
+                       {
+                               // Show problem solution (on click)
+                               elementArray.push(
+                                       h('div',
+                                               {
+                                                       attrs: { id: "solution-div" },
+                                                       "class": { "section-content": true },
+                                               },
+                                               [
+                                                       h('h3',
+                                                               {
+                                                                       "class": { clickable: true },
+                                                                       domProps: { innerHTML: translations["Show solution"] },
+                                                                       on: { click: this.toggleShowSolution },
+                                                               }
+                                                       ),
+                                                       h('p',
+                                                               {
+                                                                       attrs: { id: "problem-solution" },
+                                                                       domProps: { innerHTML: this.problem.solution }
+                                                               }
+                                                       )
+                                               ]
+                                       )
+                               );
+                       }
+                       
                        <input type="checkbox" id="modal-newproblem" class="modal">
                        <div role="dialog" aria-labelledby="newProblemTxt">
-                               <div v-show="newProblem.stage=='nothing'" class="card newproblem-form">
+                               <div v-show="stage=='nothing'" class="card newproblem-form">
                                        <label for="modal-newproblem" class="modal-close"></label>
                                        <h3 id="newProblemTxt">{{ translate("Add a problem") }}</h3>
                                        <form @submit.prevent="previewNewProblem">
@@ -53,10 +113,9 @@ Vue.component('my-problems', {
                                                </fieldset>
                                        </form>
                                </div>
-                               <div v-show="newProblem.stage=='preview'" class="card newproblem-preview">
+                               <div v-show="stage=='preview'" class="card newproblem-preview">
                                        <label for="modal-newproblem" class="modal-close"></label>
-                                       <my-problem-summary v-bind:prob="newProblem" v-bind:preview="true">
-                                       </my-problem-summary>
+                                       <my-problem-preview v-bind:prob="newProblem"></my-problem-summary>
                                        <div class="button-group">
                                                <button @click="newProblem.stage='nothing'">{{ translate("Cancel") }}</button>
                                                <button @click="sendNewProblem()">{{ translate("Send") }}</button>
@@ -68,10 +127,10 @@ Vue.component('my-problems', {
        computed: {
                sortedProblems: function() {
                        // Newest problem first
-                       return this.problems.sort((p1,p2) => { return p2.added - p1.added; });
                },
        },
        created: function() {
+               // Analyse URL: if a single problem required, show it. Otherwise,
                // TODO: fetch most recent problems from server
        },
        methods: {
@@ -83,6 +142,26 @@ Vue.component('my-problems', {
 //             bubbleUp: function(problem) {
 //                     this.$emit('show-problem', JSON.stringify(problem));
 //             },
+               toggleShowSolution: function() {
+                       let problemSolution = document.getElementById("problem-solution");
+                       problemSolution.style.display =
+                               !problemSolution.style.display || problemSolution.style.display == "none"
+                                       ? "block"
+                                       : "none";
+               },
+               showPreviousProblem: function() {
+                       if (this.curIdx == 0)
+                               this.fetchProblems("backward");
+                       else
+                               this.curIdx--;
+               },
+               showNextProblem: function() {
+                       if (this.curIdx == this.problems.length - 1)
+                               this.fetchProblems("forward");
+                       else
+                               this.curIdx++;
+               },
+               // TODO: modal "no more problems"
                fetchProblems: function(direction) {
                        if (this.problems.length == 0)
                                return; //what could we do?!
@@ -101,7 +180,11 @@ Vue.component('my-problems', {
                                last_dt: last_dt,
                        }, response => {
                                if (response.problems.length > 0)
-                                       this.problems = response.problems;
+                               {
+                                       this.problems = response.problems
+                                               .sort((p1,p2) => { return p1.added - p2.added; });
+                                       this.curIdx = response.problems.length - 1;
+                               }
                        });
                },
                showNewproblemModal: function() {
index 941434b..16fcc90 100644 (file)
@@ -18,3 +18,225 @@ chat général (gauche, activé ou non (bool global storage)).
 quand je poste un lastMove corr, supprimer mon ancien lastMove le cas échéant (tlm l'a eu)
 fin de partie corr: garder maxi nbPlayers lastMove sur serveur, pendant 7 jours (arbitraire)
 */
+                               case "newgame": //opponent found
+                                       // oppid: opponent socket ID
+                                       this.newGame("human", data.fen, data.color, data.oppid, data.gameid);
+                                       break;
+
+               // TODO: elsewhere, probably (new game button)
+               clickGameSeek: function(e) {
+                       this.getRidOfTooltip(e.currentTarget);
+                       if (this.mode == "human" && this.score == "*")
+                               return; //no newgame while playing
+                       if (this.seek)
+                       {
+                               this.conn.send(JSON.stringify({code:"cancelnewgame"}));
+                               this.seek = false;
+                       }
+                       else
+                               this.newGame("human");
+               },
+               clickComputerGame: function(e) {
+                       this.getRidOfTooltip(e.currentTarget);
+                       if (this.mode == "computer" && this.score == "*"
+                               && this.vr.turn != this.mycolor)
+                       {
+                               // Wait for computer reply first (avoid potential "ghost move" bug)
+                               return;
+                       }
+                       this.newGame("computer");
+               },
+               clickFriendGame: function(e) {
+                       this.getRidOfTooltip(e.currentTarget);
+                       document.getElementById("modal-fenedit").checked = true;
+               },
+               // In main hall :
+               newGame: function(mode, fenInit, color, oppId, gameId) {
+                       const fen = fenInit || VariantRules.GenRandInitFen();
+                       console.log(fen); //DEBUG
+                       if (mode=="human" && !oppId)
+                       {
+                               const storageVariant = localStorage.getItem("variant");
+                               if (!!storageVariant && storageVariant !== variant
+                                       && localStorage["score"] == "*")
+                               {
+                                       return alert(translations["Finish your "] +
+                                               storageVariant + translations[" game first!"]);
+                               }
+                               // Send game request and wait..
+                               try {
+                                       this.conn.send(JSON.stringify({code:"newgame", fen:fen, gameid: getRandString() }));
+                               } catch (INVALID_STATE_ERR) {
+                                       return; //nothing achieved
+                               }
+                               this.seek = true;
+                               let modalBox = document.getElementById("modal-newgame");
+                               modalBox.checked = true;
+                               setTimeout(() => { modalBox.checked = false; }, 2000);
+                               return;
+                       }
+                       const prefix = this.getStoragePrefix(mode);
+                       if (mode == "computer")
+                       {
+                               const storageVariant = localStorage.getItem(prefix+"variant");
+                               if (!!storageVariant)
+                               {
+                                       const score = localStorage.getItem(prefix+"score");
+                                       if (storageVariant !== variant && score == "*")
+                                       {
+                                               if (!confirm(storageVariant +
+                                                       translations[": unfinished computer game will be erased"]))
+                                               {
+                                                       return;
+                                               }
+                                       }
+                               }
+                       }
+                       else if (mode == "friend")
+                       {
+                               const storageVariant = localStorage.getItem(prefix+"variant");
+                               if (!!storageVariant)
+                               {
+                                       const score = localStorage.getItem(prefix+"score");
+                                       if (storageVariant !== variant && score == "*")
+                                       {
+                                               if (!confirm(storageVariant +
+                                                       translations[": current analysis will be erased"]))
+                                               {
+                                                       return;
+                                               }
+                                       }
+                               }
+                       }
+                       this.vr = new VariantRules(fen, []);
+                       this.score = "*";
+                       this.pgnTxt = ""; //redundant with this.score = "*", but cleaner
+                       this.mode = mode;
+                       this.incheck = [];
+                       this.fenStart = V.ParseFen(fen).position; //this is enough
+                       if (mode=="human")
+                       {
+                               // Opponent found!
+                               this.gameId = gameId;
+                               this.oppid = oppId;
+                               this.oppConnected = true;
+                               this.mycolor = color;
+                               this.seek = false;
+                               if (this.sound >= 1)
+                                       new Audio("/sounds/newgame.mp3").play().catch(err => {});
+                               document.getElementById("modal-newgame").checked = false;
+                       }
+                       else if (mode == "computer")
+                       {
+                               this.compWorker.postMessage(["init",this.vr.getFen()]);
+                               this.mycolor = (Math.random() < 0.5 ? 'w' : 'b');
+                               if (this.mycolor != this.vr.turn)
+                                       this.playComputerMove();
+                       }
+                       else if (mode == "friend")
+                               this.mycolor = "w"; //convention...
+                       //else: problem solving: nothing more to do
+                       if (mode != "problem")
+                               this.setStorage(); //store game state in case of interruptions
+               },
+               continueGame: function(mode) {
+                       this.mode = mode;
+                       this.oppid = (mode=="human" ? localStorage.getItem("oppid") : undefined);
+                       const prefix = this.getStoragePrefix(mode);
+                       this.mycolor = localStorage.getItem(prefix+"mycolor");
+                       const moves = JSON.parse(localStorage.getItem(prefix+"moves"));
+                       const fen = localStorage.getItem(prefix+"fen");
+                       const score = localStorage.getItem(prefix+"score"); //set in "endGame()"
+                       this.fenStart = localStorage.getItem(prefix+"fenStart");
+                       this.vr = new VariantRules(fen, moves);
+                       this.incheck = this.vr.getCheckSquares(this.vr.turn);
+                       if (mode == "human")
+                       {
+                               this.gameId = localStorage.getItem("gameId");
+                               // Send ping to server (answer pong if opponent is connected)
+                               this.conn.send(JSON.stringify({
+                                       code:"ping",oppid:this.oppid,gameId:this.gameId}));
+                       }
+                       else if (mode == "computer")
+                       {
+                               this.compWorker.postMessage(["init",fen]);
+                               if (score == "*" && this.mycolor != this.vr.turn)
+                                       this.playComputerMove();
+                       }
+                       //else: nothing special to do in friend mode
+                       if (score != "*")
+                       {
+                               // Small delay required when continuation run faster than drawing page
+                               setTimeout(() => this.endGame(score), 100);
+                       }
+               },
+               
+       
+       // TODO: option du bouton "new game"
+       const modalFenEdit = [
+                       h('input',
+                               {
+                                       attrs: { "id": "modal-fenedit", type: "checkbox" },
+                                       "class": { "modal": true },
+                               }),
+                       h('div',
+                               {
+                                       attrs: { "role": "dialog", "aria-labelledby": "titleFenedit" },
+                               },
+                               [
+                                       h('div',
+                                               {
+                                                       "class": { "card": true, "smallpad": true },
+                                               },
+                                               [
+                                                       h('label',
+                                                               {
+                                                                       attrs: { "id": "close-fenedit", "for": "modal-fenedit" },
+                                                                       "class": { "modal-close": true },
+                                                               }
+                                                       ),
+                                                       h('h3',
+                                                               {
+                                                                       attrs: { "id": "titleFenedit" },
+                                                                       "class": { "section": true },
+                                                                       domProps: { innerHTML: translations["Game state (FEN):"] },
+                                                               }
+                                                       ),
+                                                       h('input',
+                                                               {
+                                                                       attrs: {
+                                                                               "id": "input-fen",
+                                                                               type: "text",
+                                                                               value: VariantRules.GenRandInitFen(),
+                                                                       },
+                                                               }
+                                                       ),
+                                                       h('button',
+                                                               {
+                                                                       on: { click:
+                                                                               () => {
+                                                                                       const fen = document.getElementById("input-fen").value;
+                                                                                       document.getElementById("modal-fenedit").checked = false;
+                                                                                       this.newGame("friend", fen);
+                                                                               }
+                                                                       },
+                                                                       domProps: { innerHTML: translations["Ok"] },
+                                                               }
+                                                       ),
+                                                       h('button',
+                                                               {
+                                                                       on: { click:
+                                                                               () => {
+                                                                                       document.getElementById("input-fen").value =
+                                                                                               VariantRules.GenRandInitFen();
+                                                                               }
+                                                                       },
+                                                                       domProps: { innerHTML: translations["Random"] },
+                                                               }
+                                                       ),
+                                               ]
+                                       )
+                               ]
+                       )
+               ];
+               elementArray = elementArray.concat(modalFenEdit);
index c9adbf1..85f89c1 100644 (file)
@@ -1,3 +1,23 @@
 // TODO:
 //à chaque onChange, envoyer matching event settings update
 //(par exemple si mise à jour du nom, juste envoyer cet update aux autres connectés ...etc)
+               setMyname: function(e) {
+                       this.myname = e.target.value;
+                       localStorage["username"] = this.myname;
+               },
+               showSettings: function(e) {
+                       this.getRidOfTooltip(e.currentTarget);
+                       document.getElementById("modal-settings").checked = true;
+               },
+               toggleHints: function() {
+                       this.hints = !this.hints;
+                       localStorage["hints"] = (this.hints ? "1" : "0");
+               },
+               setBoardColor: function(e) {
+                       this.bcolor = e.target.options[e.target.selectedIndex].value;
+                       localStorage["bcolor"] = this.bcolor;
+               },
+               setSound: function(e) {
+                       this.sound = parseInt(e.target.options[e.target.selectedIndex].value);
+                       localStorage["sound"] = this.sound;
+               },
diff --git a/public/javascripts/utils/storage.js b/public/javascripts/utils/storage.js
new file mode 100644 (file)
index 0000000..8bd43f0
--- /dev/null
@@ -0,0 +1,55 @@
+               // TODO: general methods to access/retrieve from storage, to be generalized
+               // https://developer.mozilla.org/fr/docs/Web/API/API_IndexedDB
+               // https://dexie.org/
+               getStoragePrefix: function(mode) {
+                       let prefix = "";
+                       if (mode == "computer")
+                               prefix = "comp-";
+                       else if (mode == "friend")
+                               prefix = "anlz-";
+                       return prefix;
+               },
+               setStorage: function() {
+                       if (this.mode=="human")
+                       {
+                               localStorage.setItem("myid", this.myid);
+                               localStorage.setItem("oppid", this.oppid);
+                               localStorage.setItem("gameId", this.gameId);
+                       }
+                       const prefix = this.getStoragePrefix(this.mode);
+                       localStorage.setItem(prefix+"variant", variant);
+                       localStorage.setItem(prefix+"mycolor", this.mycolor);
+                       localStorage.setItem(prefix+"fenStart", this.fenStart);
+                       localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves));
+                       localStorage.setItem(prefix+"fen", this.vr.getFen());
+                       localStorage.setItem(prefix+"score", "*");
+               },
+               updateStorage: function() {
+                       const prefix = this.getStoragePrefix(this.mode);
+                       localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves));
+                       localStorage.setItem(prefix+"fen", this.vr.getFen());
+                       if (this.score != "*")
+                               localStorage.setItem(prefix+"score", this.score);
+               },
+               // "computer mode" clearing is done through the menu
+               clearStorage: function() {
+                       if (this.mode == "human")
+                       {
+                               delete localStorage["myid"];
+                               delete localStorage["oppid"];
+                               delete localStorage["gameId"];
+                       }
+                       const prefix = this.getStoragePrefix(this.mode);
+                       delete localStorage[prefix+"variant"];
+                       delete localStorage[prefix+"mycolor"];
+                       delete localStorage[prefix+"fenStart"];
+                       delete localStorage[prefix+"moves"];
+                       delete localStorage[prefix+"fen"];
+                       delete localStorage[prefix+"score"];
+               },
+               clearCurrentGame: function(e) {
+                       this.getRidOfTooltip(e.currentTarget);
+                       this.clearStorage();
+                       location.reload(); //to see clearing effects
+               },
+
index 736d8b3..510ea29 100644 (file)
@@ -21,6 +21,10 @@ new Vue({
                },
        },
 });
+               
+const continuation = (localStorage.getItem("variant") === variant);
+                       if (continuation) //game VS human has priority
+                               this.continueGame("human");
 
 // TODO:
 // si quand on arrive il y a une continuation "humaine" : display="game" et retour à la partie !
index a43ba60..2fbf21e 100644 (file)
@@ -26,10 +26,9 @@ block content
                                                i.material-icons settings
                .row
                        my-room(v-show="display=='room'")
-                       my-games-list(v-show="display=='gameList'")
+                       my-game-list(v-show="display=='gameList'")
                        my-rules(v-show="display=='rules'")
                        my-problems(v-show="display=='problems'")
-                       // my-game: for room and games-list components
                        my-game(v-show="display=='game'" :gameId="gameid")
 
 block javascripts
@@ -43,8 +42,10 @@ block javascripts
        script.
                const V = VariantRules; //because this variable is often used
                const variant = "#{variant}";
+       script(src="/javascripts/components/room.js")
+       script(src="/javascripts/components/gameList.js")
        script(src="/javascripts/components/rules.js")
-       script(src="/javascripts/components/game.js")
        script(src="/javascripts/components/problemSummary.js")
        script(src="/javascripts/components/problems.js")
+       script(src="/javascripts/components/game.js")
        script(src="/javascripts/variant.js")