// modal "welcome" will be filled in the selected language
#modalWelcome
Language
- Settings(:settings="settings")
+ Settings
ContactForm
+ UpsertUser
.container
.row(v-show="$route.path == '/'")
- // Header (on index only)
+ // Header (on index only ?!)
header
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
img(src="./assets/images/index/unicorn.svg")
// select options all variants + filter possible (as in problems)
| Home
router-link(to="/myGames")
- | {{ $tr["My games"] }}
+ | {{ st.tr["My games"] }}
router-link(to="/rules")
// Boxes OK for rules/Atomic/ ...etc
- | {{ $tr["Rules"] }}
+ | {{ st.tr["Rules"] }}
router-link(to="/problems")
- | {{ $tr["Problems"] }}
+ | {{ st.tr["Problems"] }}
#userMenu.clickable.right-menu(onClick="doClick('modalUser')")
.info-container
p
- span {{ !$user.email ? "Login" : "Update" }}
+ span {{ !st.user.id ? "Login" : "Update" }}
span.icon-user
#flagMenu.clickable.right-menu(onClick="doClick('modalLang')")
img(src="/images/flags/" + lang + ".svg")
#settings.clickable(onClick="doClick('modalSettings')")
+ | Settings
i(data-feather="settings")
.row
router-view
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2.text-center
a(href="https://github.com/yagu0/vchess") Source code
p.clickable(onClick="doClick('modalContact')")
- | {{ $tr["Contact form"] }}
+ | {{ st.tr["Contact form"] }}
//my-game(:game-ref="gameRef" :mode="mode" :settings="settings" @game-over="archiveGame")
//// TODO: add only the necessary icons to mini-css custom build
//script(src="//unpkg.com/feather-icons")
import ContactForm from "@/components/ContactForm.vue";
import Language from "@/components/Language.vue";
import Settings from "@/components/Settings.vue";
+import UpsertUser from "@/components/UpsertUser.vue";
+import { store } from "./store.js";
export default {
- data: function() {
- return {
- settings: {}, //TODO
- };
- },
components: {
ContactForm,
Language,
Settings,
+ UpsertUser,
+ },
+ data: function() {
+ return {
+ st: store.state,
+ };
},
};
</script>
div(role="dialog" aria-labelledby="contactTitle")
form.card.smallpad
label.modal-close(for="modalContact")
- h3#contactTitle.section {{ $tr["Contact form"] }}
+ h3#contactTitle.section {{ st.tr["Contact form"] }}
fieldset
- label(for="userEmail") {{ $tr["Email"] }}
+ label(for="userEmail") {{ st.tr["Email"] }}
input#userEmail(type="email")
fieldset
- label(for="mailSubject") {{ $tr["Subject"] }}
+ label(for="mailSubject") {{ st.tr["Subject"] }}
input#mailSubject(type="text")
fieldset
- label(for="mailContent") {{ $tr["Content"] }}
+ label(for="mailContent") {{ st.tr["Content"] }}
br
textarea#mailContent
fieldset
button(type="button" onClick="trySendMessage()") Send
- p#emailSent {{ $tr["Email sent!"] }}
+ p#emailSent {{ st.tr["Email sent!"] }}
</template>
<script>
import { ajax } from "../utils/ajax";
+import { store } from "@/store";
export default {
- name: "ContactForm",
+ name: "my-contact-form",
+ data: function() {
+ return {
+ st: store.state,
+ };
+ },
methods: {
// Note: not using Vue here, but would be possible
trySendMessage: function() {
label.modal-close(for="modalLang")
form
fieldset
- label(for="langSelect") {{ $tr["Language"] }}
+ label(for="langSelect") {{ st.tr["Language"] }}
select#langSelect
each language,langCode in langName
option(value=langCode selected=(lang==langCode))
</template>
<script>
+import { store } from "@/store";
export default {
- name: "Language",
+ name: "my-language",
+ data: function() {
+ return {
+ st: store.state,
+ };
+ },
methods: {
- // Used both on index and variant page, to switch language
setLanguage: function(e) {
localStorage["lang"] = e.target.value;
- this.$lang = e.target.value;
+ store.setLanguage(e.target.value);
},
},
};
div(role="dialog" aria-labelledby="settingsTitle")
.card.smallpad(@change="updateSettings")
label.modal-close(for="modalSettings")
- h3#settingsTitle.section {{ $tr["Preferences"] }}
+ h3#settingsTitle.section {{ st.tr["Preferences"] }}
fieldset
- label(for="setSqSize") {{ $tr["Square size (in pixels). 0 for 'adaptative'"] }}
- input#setSqSize(type="number" v-model="$settings.sqSize")
+ label(for="setSqSize") {{ st.tr["Square size (in pixels). 0 for 'adaptative'"] }}
+ input#setSqSize(type="number" v-model="st.settings.sqSize")
fieldset
- label(for="selectHints") {{ $tr["Show move hints?"] }}
- select#setHints(v-model="$settings.hints")
- option(value="0") {{ $tr["None"] }}
- option(value="1") {{ $tr["Moves from a square"] }}
- option(value="2") {{ $tr["Pieces which can move"] }}
+ label(for="selectHints") {{ st.tr["Show move hints?"] }}
+ select#setHints(v-model="st.settings.hints")
+ option(value="0") {{ st.tr["None"] }}
+ option(value="1") {{ st.tr["Moves from a square"] }}
+ option(value="2") {{ st.tr["Pieces which can move"] }}
fieldset
- label(for="setHighlight") {{ $tr["Highlight squares? (Last move & checks)"] }}
- input#setHighlight(type="checkbox" v-model="$settings.highlight")
+ label(for="setHighlight") {{ st.tr["Highlight squares? (Last move & checks)"] }}
+ input#setHighlight(type="checkbox" v-model="st.settings.highlight")
fieldset
- label(for="setCoords") {{ $tr["Show board coordinates?"] }}
- input#setCoords(type="checkbox" v-model="$settings.coords")
+ label(for="setCoords") {{ st.tr["Show board coordinates?"] }}
+ input#setCoords(type="checkbox" v-model="st.settings.coords")
fieldset
- label(for="selectColor") {{ $tr["Board colors"] }}
- select#setBcolor(v-model="$settings.bcolor")
- option(value="lichess") {{ $tr["brown"] }}
- option(value="chesscom") {{ $tr["green"] }}
- option(value="chesstempo") {{ $tr["blue"] }}
+ label(for="selectColor") {{ st.tr["Board colors"] }}
+ select#setBcolor(v-model="st.settings.bcolor")
+ option(value="lichess") {{ st.tr["brown"] }}
+ option(value="chesscom") {{ st.tr["green"] }}
+ option(value="chesstempo") {{ st.tr["blue"] }}
fieldset
- label(for="selectSound") {{ $tr["Play sounds?"] }}
- select#setSound(v-model="$settings.sound")
- option(value="0") {{ $tr["None"] }}
- option(value="1") {{ $tr["New game"] }}
- option(value="2") {{ $tr["All"] }}
+ label(for="selectSound") {{ st.tr["Play sounds?"] }}
+ select#setSound(v-model="st.settings.sound")
+ option(value="0") {{ st.tr["None"] }}
+ option(value="1") {{ st.tr["New game"] }}
+ option(value="2") {{ st.tr["All"] }}
</template>
<script>
+import { store } from "@/store.js";
export default {
- name: "Settings",
- //props: ["settings"],
+ name: "my-settings",
+ data: function() {
+ return {
+ st: store.state,
+ };
+ },
methods: {
updateSettings: function(event) {
const propName =
// Logic to login, or create / update a user (and also logout)
-vv = Vue.component('my-upsert-user', {
+<template lang="pug">
+div
+ input#modalUser.modal(type="checkbox" @change="trySetEnterTime")
+ div(role="dialog")
+ .card
+ label.modal-close(for="modalUser")
+ h3 {{ stage }}
+ form#userForm(@submit.prevent="onSubmit()")
+ div(v-show="stage!='Login'")
+ fieldset
+ label(for="username") Name
+ input#username(type="text" v-model="user.name")
+ fieldset
+ <label for="useremail">Email</label>
+ <input id="useremail" type="email" v-model="user.email"/>
+ fieldset
+ <label for="notifyNew">Notify new moves & games</label>
+ <input id="notifyNew" type="checkbox" v-model="user.notify"/>
+ div(v-show="stage=='Login'")
+ fieldset
+ <label for="nameOrEmail">Name or Email</label>
+ <input id="nameOrEmail" type="text" v-model="nameOrEmail"/>
+ .button-group
+ button#submit(@click="onSubmit()")
+ span {{ submitMessage }}
+ i.material-icons send
+ button(v-if="stage!='Update'" @click="toggleStage()")
+ span {{ stage=="Login" ? "Register" : "Login" }}
+ button(v-if="stage=='Update'" onClick="location.replace('/logout')")
+ span Logout
+ #dialog(:style="{display: displayInfo}") {{ infoMsg }}
+</template>
+
+<script>
+import { store } from "@/store";
+export default {
+ name: 'my-upsert-user',
data: function() {
return {
- user: user, //initialized with global user object
+ user: store.state.user, //initialized with global user object
nameOrEmail: "", //for login
- stage: (!user.email ? "Login" : "Update"),
+ stage: (!store.state.user.id ? "Login" : "Update"),
infoMsg: "",
enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy
};
},
- template: `
- <div>
- <input id="modalUser" class="modal" type="checkbox"
- @change="trySetEnterTime"/>
- <div role="dialog">
- <div class="card">
- <label class="modal-close" for="modalUser"></label>
- <h3>{{ stage }}</h3>
- <form id="userForm" @submit.prevent="onSubmit()">
- <div v-show="stage!='Login'">
- <fieldset>
- <label for="username">Name</label>
- <input id="username" type="text" v-model="user.name"/>
- </fieldset>
- <fieldset>
- <label for="useremail">Email</label>
- <input id="useremail" type="email" v-model="user.email"/>
- </fieldset>
- <fieldset>
- <label for="notifyNew">Notify new moves & games</label>
- <input id="notifyNew" type="checkbox" v-model="user.notify"/>
- </fieldset>
- </div>
- <div v-show="stage=='Login'">
- <fieldset>
- <label for="nameOrEmail">Name or Email</label>
- <input id="nameOrEmail" type="text" v-model="nameOrEmail"/>
- </fieldset>
- </div>
- </form>
- <div class="button-group">
- <button id="submit" @click="onSubmit()">
- <span>{{ submitMessage }}</span>
- <i class="material-icons">send</i>
- </button>
- <button v-if="stage!='Update'" @click="toggleStage()">
- <span>{{ stage=="Login" ? "Register" : "Login" }}</span>
- </button>
- <button v-if="stage=='Update'" onClick="location.replace('/logout')">
- <span>Logout</span>
- </button>
- </div>
- <div id="dialog" :style="{display: displayInfo}">{{ infoMsg }}</div>
- </div>
- </div>
- </div>
- `,
computed: {
submitMessage: function() {
switch (this.stage)
// Store our identifiers in local storage (by little anticipation...)
localStorage["myid"] = res.id;
localStorage["myname"] = res.name;
+ // Also in global object
+ this.$user.id = res.id;
+ this.$user.name = res.name;
}
setTimeout(() => {
this.infoMsg = "";
}
);
},
- }
-});
+ },
+};
+</script>
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
-import params from "./parameters"; //for socket connection
-import { ajax } from "./utils/ajax";
-import { util } from "./utils/misc";
+// Global store: see https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87
+import { store } from "./store";
Vue.config.productionTip = false;
return h(App);
},
// watch: {
-// $lang: async function(newLang) {
-// // Fill modalWelcome, and import translations from "./translations/$lang.js"
-// document.getElementById("modalWelcome").innerHTML =
-// require("raw-loader!pug-plain-loader!./modals/welcome/" + newLang + ".pug");
-// const tModule = await import("./translations/" + newLang + ".js");
-// Vue.prototype.$tr = tModule.translations;
-// //console.log(tModule.translations);
-// },
// $route: function(newRoute) {
// //console.log(this.$route.params);
// console.log("navig to " + newRoute);
// //TODO: conn.send("enter", newRoute)
// },
// },
- created: function() {
- const supportedLangs = ["en","es","fr"];
- Vue.prototype.$lang = localStorage["lang"] ||
- supportedLangs.includes(navigator.language)
- ? navigator.language
- : "en";
- Vue.prototype.$variants = []; //avoid runtime error
- ajax("/variants", "GET", res => { Vue.prototype.$variants = res.variantArray; });
- Vue.prototype.$tr = {}; //to avoid a compiler error
- Vue.prototype.$user = {}; //TODO: from storage
- // TODO: if there is a socket ID in localStorage, it means a live game was interrupted (and should resume)
- const myid = localStorage["myid"] || util.getRandString();
- // NOTE: in this version, we don't say on which page we are, yet
- // ==> we'll say "enter/leave" page XY (in fact juste "enter", seemingly)
- Vue.prototype.$conn = new WebSocket(params.socketUrl + "/?sid=" + myid);
- // Settings initialized with values from localStorage
- Vue.prototype.$settings = {
- bcolor: localStorage["bcolor"] || "lichess",
- sound: parseInt(localStorage["sound"]) || 2,
- hints: parseInt(localStorage["hints"]) || 1,
- coords: !!eval(localStorage["coords"]),
- highlight: !!eval(localStorage["highlight"]),
- sqSize: parseInt(localStorage["sqSize"]),
- };
- const socketCloseListener = () => {
- Vue.prototype.$conn = new WebSocket(params.socketUrl + "/?sid=" + myid);
- }
- Vue.prototype.$conn.onclose = socketCloseListener;
- //TODO: si une partie en cours dans storage, rediriger vers cette partie
- //(à condition que l'URL n'y corresponde pas déjà !)
- // TODO: à l'arrivée sur le site : set peerID (un identifiant unique
- // en tout cas...) si pas trouvé dans localStorage "myid"
- // (l'identifiant de l'utilisateur si connecté)
-// if (!!localStorage["variant"])
-// location.hash = "#game?id=" + localStorage["gameId"];
- },
- // Later, for icons (if using feather):
-// mounted: function() {
-// feather.replace();
-// },
+ created: function() {
+ window.doClick = (elemId) => { document.getElementById(elemId).click() };
+
+ //TODO: si une partie en cours dans storage, rediriger vers cette partie
+ //(à condition que l'URL n'y corresponde pas déjà !)
+ // TODO: à l'arrivée sur le site : set peerID (un identifiant unique
+ // en tout cas...) si pas trouvé dans localStorage "myid"
+ // (l'identifiant de l'utilisateur si connecté)
+// if (!!localStorage["variant"])
+// location.hash = "#game?id=" + localStorage["gameId"];
+ },
+ // Later, for icons (if using feather):
+// mounted: function() {
+// feather.replace();
+// },
+ mounted: function() {
+ store.initialize();
+ },
}).$mount("#app");
// TODO: get rules, dynamic import
// Load a rules page (AJAX)
// router.get("/rules/:vname([a-zA-Z0-9]+)", access.ajax, (req,res) => {
-// const lang = selectLanguage(req, res);
-// res.render("rules/" + req.params["vname"] + "/" + lang);
+// const lang = selectLanguage(req, res);
+// res.render("rules/" + req.params["vname"] + "/" + lang);
// });
//
// board2, 3, 4 automatiquement, mais rules separement (les 3 pour une)
// problems: on-demand
//
// See https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
-// created: function() {
-// window.onhashchange = this.setDisplay;
-// },
+// created: function() {
+// window.onhashchange = this.setDisplay;
+// },
//});
--- /dev/null
+import { ajax } from "./utils/ajax";
+import { getRandString } from "./utils/alea";
+import params from "./parameters"; //for socket connection
+
+export const store =
+{
+ state: {
+ variants: [],
+ tr: {},
+ user: {},
+ conn: null,
+ settings: {},
+ lang: "",
+ },
+ initialize() {
+ ajax("/variants", "GET", res => { this.state.variants = res.variantArray; });
+ this.state.user = {
+ // id and name could be undefined
+ id: localStorage["myuid"],
+ name: localStorage["myname"],
+ };
+ // TODO: if there is a socket ID in localStorage, it means a live game was interrupted (and should resume)
+ const mysid = localStorage["mysid"] || getRandString();
+ this.state.conn = new WebSocket(params.socketUrl + "/?sid=" + mysid);
+ // Settings initialized with values from localStorage
+ this.state.settings = {
+ bcolor: localStorage["bcolor"] || "lichess",
+ sound: parseInt(localStorage["sound"]) || 2,
+ hints: parseInt(localStorage["hints"]) || 1,
+ coords: !!eval(localStorage["coords"]),
+ highlight: !!eval(localStorage["highlight"]),
+ sqSize: parseInt(localStorage["sqSize"]),
+ };
+ const socketCloseListener = () => {
+ this.state.conn = new WebSocket(params.socketUrl + "/?sid=" + mysid);
+ }
+ this.state.conn.onclose = socketCloseListener;
+ const supportedLangs = ["en","es","fr"];
+ this.state.lang = localStorage["lang"] ||
+ supportedLangs.includes(navigator.language)
+ ? navigator.language
+ : "en";
+ this.setTranslations();
+ },
+ setTranslations: async function() {
+ // Fill modalWelcome, and import translations from "./translations/$lang.js"
+ document.getElementById("modalWelcome").innerHTML =
+ require("raw-loader!pug-plain-loader!@/welcome/" + this.state.lang + ".pug");
+ const tModule = await import("@/translations/" + this.state.lang + ".js");
+ this.state.tr = tModule.translations;
+ },
+ setLanguage(lang) {
+ this.state.lang = lang;
+ this.setTranslations();
+ },
+};
--- /dev/null
+// Random (enough) string for socket and game IDs
+export function getRandString()
+{
+ return (Date.now().toString(36) + Math.random().toString(36).substr(2, 7))
+ .toUpperCase();
+}
+
+export function random (min, max)
+{
+ if (!max)
+ {
+ max = min;
+ min = 0;
+ }
+ return Math.floor(Math.random() * (max - min) ) + min;
+}
+
+// Inspired by https://github.com/jashkenas/underscore/blob/master/underscore.js
+export function sample (arr, n)
+{
+ n = n || 1;
+ let cpArr = arr.map(e => e);
+ for (let index = 0; index < n; index++)
+ {
+ const rand = getRandInt(index, n);
+ const temp = cpArr[index];
+ cpArr[index] = cpArr[rand];
+ cpArr[rand] = temp;
+ }
+ return cpArr.slice(0, n);
+}
+
+export function shuffle(arr)
+{
+ return sample(arr, arr.length);
+}
{
return [...Array(size1)].map(e => Array(size2).fill(initElem));
}
+
+export function range(max)
+{
+ return [...Array(max).keys()];
+}
--- /dev/null
+// Source: https://www.quirksmode.org/js/cookies.html
+export function setCookie(name, value)
+{
+ var date = new Date();
+ date.setTime(date.getTime()+(183*24*60*60*1000)); //6 months
+ var expires = "; expires="+date.toGMTString();
+ document.cookie = name+"="+value+expires+"; path=/";
+}
+
+export function getCookie(name, defaut) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for (var i=0;i < ca.length;i++)
+ {
+ var c = ca[i];
+ while (c.charAt(0)==' ')
+ c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) == 0)
+ return c.substring(nameEQ.length,c.length);
+ }
+ return defaut; //cookie not found
+}
+++ /dev/null
-export const util =
-{
- // Source: https://www.quirksmode.org/js/cookies.html
- setCookie: function(name, value)
- {
- var date = new Date();
- date.setTime(date.getTime()+(183*24*60*60*1000)); //6 months
- var expires = "; expires="+date.toGMTString();
- document.cookie = name+"="+value+expires+"; path=/";
- },
-
- getCookie: function(name, defaut) {
- var nameEQ = name + "=";
- var ca = document.cookie.split(';');
- for (var i=0;i < ca.length;i++)
- {
- var c = ca[i];
- while (c.charAt(0)==' ')
- c = c.substring(1,c.length);
- if (c.indexOf(nameEQ) == 0)
- return c.substring(nameEQ.length,c.length);
- }
- return defaut; //cookie not found
- },
-
- random: function(min, max)
- {
- if (!max)
- {
- max = min;
- min = 0;
- }
- return Math.floor(Math.random() * (max - min) ) + min;
- },
-
- // Inspired by https://github.com/jashkenas/underscore/blob/master/underscore.js
- sample: function(arr, n)
- {
- n = n || 1;
- let cpArr = arr.map(e => e);
- for (let index = 0; index < n; index++)
- {
- const rand = getRandInt(index, n);
- const temp = cpArr[index];
- cpArr[index] = cpArr[rand];
- cpArr[rand] = temp;
- }
- return cpArr.slice(0, n);
- },
-
- shuffle: function(arr)
- {
- return sample(arr, arr.length);
- },
-
- range: function(max)
- {
- return [...Array(max).keys()];
- },
-
- // TODO: rename into "cookie" et supprimer les deux ci-dessous
- // Random (enough) string for socket and game IDs
- getRandString: function()
- {
- return (Date.now().toString(36) + Math.random().toString(36).substr(2, 7))
- .toUpperCase();
- },
-
- // Shortcut for an often used click (on a modal)
- doClick: function(elemId)
- {
- document.getElementById(elemId).click(); //or ".checked = true"
- },
-};
p
| For informations about hundreds (if not thousands) of variants, you
| can visit the excellent
- a(href="https://www.chessvariants.com/" _target="blank" rel="noopener")
- | chessvariants
- | website.
- p.smallfont
- | Image credit:
- a(href=wikipediaUrl _target="blank" rel="noopener") Wikipedia
+ a(href="https://www.chessvariants.com/" _target="blank" rel="noopener")
+ | chessvariants
+ | website.
+ p.smallfont
+ | Image credit:
+ a(href=wikipediaUrl _target="blank" rel="noopener") Wikipedia