Improve games/challenges display and fix MyGames page reactivity (using () for now...)
[vchess.git] / client / src / views / MyGames.vue
1 <template lang="pug">
2 main
3 .row
4 .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
5 .button-group
6 button.tabbtn#liveGames(@click="setDisplay('live',$event)")
7 | {{ st.tr["Live games"] }}
8 button.tabbtn#corrGames(@click="setDisplay('corr',$event)")
9 | {{ st.tr["Correspondance games"] }}
10 GameList(
11 ref="livegames"
12 v-show="display=='live'"
13 :games="liveGames"
14 @show-game="showGame"
15 @abortgame="abortGame"
16 )
17 GameList(
18 ref="corrgames"
19 v-show="display=='corr'"
20 :games="corrGames"
21 @show-game="showGame"
22 @abortgame="abortGame"
23 )
24 </template>
25
26 <script>
27 import { store } from "@/store";
28 import { GameStorage } from "@/utils/gameStorage";
29 import { ajax } from "@/utils/ajax";
30 import { getScoreMessage } from "@/utils/scoring";
31 import params from "@/parameters";
32 import { getRandString } from "@/utils/alea";
33 import GameList from "@/components/GameList.vue";
34 export default {
35 name: "my-my-games",
36 components: {
37 GameList
38 },
39 data: function() {
40 return {
41 st: store.state,
42 display: "live",
43 liveGames: [],
44 corrGames: [],
45 conn: null,
46 connexionString: ""
47 };
48 },
49 created: function() {
50 // Initialize connection
51 this.connexionString =
52 params.socketUrl +
53 "/?sid=" +
54 this.st.user.sid +
55 "&id=" +
56 this.st.user.id +
57 "&tmpId=" +
58 getRandString() +
59 "&page=" +
60 encodeURIComponent(this.$route.path);
61 this.conn = new WebSocket(this.connexionString);
62 this.conn.onmessage = this.socketMessageListener;
63 this.conn.onclose = this.socketCloseListener;
64 },
65 mounted: function() {
66 const adjustAndSetDisplay = () => {
67 // showType is the last type viwed by the user (default)
68 let showType = localStorage.getItem("type-myGames") || "live";
69 // Live games, my turn: highest priority:
70 if (this.liveGames.some(g => !!g.myTurn)) showType = "live";
71 // Then corr games, my turn:
72 else if (this.corrGames.some(g => !!g.myTurn)) showType = "corr";
73 else {
74 // If a listing is empty, try showing the other (if non-empty)
75 const types = ["corr", "live"];
76 for (let i of [0,1]) {
77 if (
78 this[types[i] + "Games"].length > 0 &&
79 this[types[1-i] + "Games"].length == 0
80 ) {
81 showType = types[i];
82 }
83 }
84 }
85 this.setDisplay(showType);
86 };
87 GameStorage.getAll(localGames => {
88 localGames.forEach(g => g.type = "live");
89 this.decorate(localGames);
90 this.liveGames = localGames;
91 if (this.st.user.id > 0) {
92 ajax(
93 "/games",
94 "GET",
95 {
96 data: { uid: this.st.user.id },
97 success: (res) => {
98 let serverGames = res.games.filter(g => {
99 const mySide =
100 g.players[0].uid == this.st.user.id
101 ? "White"
102 : "Black";
103 return !g["deletedBy" + mySide];
104 });
105 serverGames.forEach(g => g.type = "corr");
106 this.decorate(serverGames);
107 this.corrGames = serverGames;
108 adjustAndSetDisplay();
109 }
110 }
111 );
112 } else adjustAndSetDisplay();
113 });
114 },
115 beforeDestroy: function() {
116 this.conn.send(JSON.stringify({code: "disconnect"}));
117 },
118 methods: {
119 setDisplay: function(type, e) {
120 this.display = type;
121 localStorage.setItem("type-myGames", type);
122 let elt = e ? e.target : document.getElementById(type + "Games");
123 elt.classList.add("active");
124 elt.classList.remove("somethingnew"); //in case of
125 if (elt.previousElementSibling)
126 elt.previousElementSibling.classList.remove("active");
127 else elt.nextElementSibling.classList.remove("active");
128 },
129 tryShowNewsIndicator: function(type) {
130 if (
131 (type == "live" && this.display == "corr") ||
132 (type == "corr" && this.display == "live")
133 ) {
134 document
135 .getElementById(type + "Games")
136 .classList.add("somethingnew");
137 }
138 },
139 // Called at loading to augment games with myColor + myTurn infos
140 decorate: function(games) {
141 games.forEach(g => {
142 g.myColor =
143 (g.type == "corr" && g.players[0].uid == this.st.user.id) ||
144 (g.type == "live" && g.players[0].sid == this.st.user.sid)
145 ? 'w'
146 : 'b';
147 // If game is over, myTurn doesn't exist:
148 if (g.score == "*") {
149 const rem = g.movesCount % 2;
150 if ((rem == 0 && g.myColor == 'w') || (rem == 1 && g.myColor == 'b'))
151 g.myTurn = true;
152 }
153 });
154 },
155 socketMessageListener: function(msg) {
156 const data = JSON.parse(msg.data);
157 let gamesArrays = {
158 "corr": this.corrGames,
159 "live": this.liveGames
160 };
161 switch (data.code) {
162 case "notifyturn":
163 case "notifyscore": {
164 const info = data.data;
165 const type = (!!parseInt(info.gid) ? "corr" : "live");
166 let game = gamesArrays[type].find(g => g.id == info.gid);
167 // "notifything" --> "thing":
168 const thing = data.code.substr(6);
169 game[thing] = info[thing];
170 if (thing == "turn") {
171 game.myTurn = !game.myTurn;
172 if (game.myTurn) this.tryShowNewsIndicator(type);
173 }
174 // TODO: forcing refresh like that is ugly and wrong.
175 // How to do it cleanly?
176 this.$refs[type + "games"].$forceUpdate();
177 break;
178 }
179 case "notifynewgame": {
180 const gameInfo = data.data;
181 // st.variants might be uninitialized,
182 // if unlucky and newgame right after connect:
183 const v = this.st.variants.find(v => v.id == gameInfo.vid);
184 const vname = !!v ? v.name : "";
185 const type = (gameInfo.cadence.indexOf('d') >= 0 ? "corr": "live");
186 let game = Object.assign(
187 {
188 vname: vname,
189 type: type,
190 score: "*",
191 created: Date.now()
192 },
193 gameInfo
194 );
195 game.myTurn =
196 (type == "corr" && game.players[0].uid == this.st.user.id) ||
197 (type == "live" && game.players[0].sid == this.st.user.sid);
198 gamesArrays[type].push(game);
199 if (game.myTurn) this.tryShowNewsIndicator(type);
200 // TODO: cleaner refresh
201 this.$refs[type + "games"].$forceUpdate();
202 break;
203 }
204 }
205 },
206 socketCloseListener: function() {
207 this.conn = new WebSocket(this.connexionString);
208 this.conn.addEventListener("message", this.socketMessageListener);
209 this.conn.addEventListener("close", this.socketCloseListener);
210 },
211 showGame: function(game) {
212 if (game.type == "live" || !game.myTurn) {
213 this.$router.push("/game/" + game.id);
214 return;
215 }
216 // It's my turn in this game. Are there others?
217 let nextIds = "";
218 let otherCorrGamesMyTurn = this.corrGames.filter(g =>
219 g.id != game.id && !!g.myTurn);
220 if (otherCorrGamesMyTurn.length > 0) {
221 nextIds += "/?next=[";
222 otherCorrGamesMyTurn.forEach(g => { nextIds += g.id + ","; });
223 // Remove last comma and close array:
224 nextIds = nextIds.slice(0, -1) + "]";
225 }
226 this.$router.push("/game/" + game.id + nextIds);
227 },
228 abortGame: function(game) {
229 // Special "trans-pages" case: from MyGames to Game
230 // TODO: also for corr games? (It's less important)
231 if (game.type == "live") {
232 const oppsid =
233 game.players[0].sid == this.st.user.sid
234 ? game.players[1].sid
235 : game.players[0].sid;
236 this.conn.send(
237 JSON.stringify(
238 {
239 code: "mabort",
240 gid: game.id,
241 // NOTE: target might not be online
242 target: oppsid
243 }
244 )
245 );
246 }
247 else if (!game.deletedByWhite || !game.deletedByBlack) {
248 // Set score if game isn't deleted on server:
249 ajax(
250 "/games",
251 "PUT",
252 {
253 data: {
254 gid: game.id,
255 newObj: {
256 score: "?",
257 scoreMsg: getScoreMessage("?")
258 }
259 }
260 }
261 );
262 }
263 }
264 }
265 };
266 </script>
267
268 <style lang="sass">
269 .active
270 color: #42a983
271
272 .tabbtn
273 background-color: #f9faee
274
275 table.game-list
276 max-height: 100%
277
278 .somethingnew
279 background-color: #c5fefe !important
280 </style>