(function (){
// var wsURL = "wss://tmp.iekei.org/jstype";
var wsURL = "wss://" + location.host + "/jstrr";
var ws; // for WebSocket Object
var textlist = [];
var nText = nText||4;
var nLine = nLine||2;
var area = [];
var team = {}, teamnames = new Set(), tmsel, nmsel, txsel;
var mystate = {};
var infoBox;
var loginhead, prompt, input, tmpkey;
var singleTextMode = true;
//
// https://developer.mozilla.org/ja/docs/Web/API/Element/classList
function setElementIDText(id, text) {
document.getElementById(id).innerText = text;
}
function fillText(text, column) {
var tx = text.replace(/\n/g, " ").replace(/\s+/, " ");
var array = [], pos=0, thisline="";
var re = /(\S+\s+)/g;
var found = tx.split(" ");
console.log("found LEN="+found.length);
for (w of found) {
//console.log("tl="+w+", len="+w.length);
//console.log("thisllen="+thisline.length);
if (thisline.length + w.length < column) {
if (w.endsWith(".")) w += " ";
thisline += w+" ";
// console.log("THISLINE=["+thisline+"]");
} else {
array.push(thisline.trim());
//console.log("ArrayLen="+array.length);
//console.log("Array=["+array.join("/")+"]");
thisline = "";
}
}
if (thisline > "") array.push(thisline.trim());
return array;
}
function loadTextToElement(hash, elm1, elm2) {
let txt = hash.text, subtxt="";
let array = txt.split("\n"),
b = 5, // number of buffer lines
number = Math.max(0, array.length-nLine-b);
let s;
let trial = 10;
if (!txt.match(/Article/)) trial = 1;
while (trial-- > 0 && !subtxt.match(/^(|\.\s+)[0-9A-Z]/)) {
s = Math.floor(Math.random()*number);
subtxt = array.slice(s, s+nLine+b).join("\n");
// console.log("subtxt=["+subtxt+"]");
}
if (txt[0] == "#") { // Maybe source of scripting languege
array = subtxt.split("\n");
} else {
if (!subtxt.match(/^[0-9A-Z]/)) {
console.log("REP!!!!!!!! "+subtxt.substr(0, 10));
subtxt.replace(/^.*?\. */, "");
console.log("DONE: "+subtxt.substr(0, 10));
}
array = fillText(subtxt, 40);
}
if (array.length > nLine) {
txt = "";
for (let i=0; i<nLine; i++) {
txt += array[i].trim()+"\n";
// txt += array[i].trim()+"\n";
txt.trim();
}
}
elm1.textContent = txt[0];
elm2.textContent = txt.substr(1);
resetState();
}
function updateMemberOption(e) {
let tn = tmsel.value;
console.log(nmsel.childNodes);
// We should repeat remove lastChild when deleting all children
// https://into-the-program.com/removechild/
while (nmsel.lastChild) nmsel.removeChild(nmsel.lastChild);
for (let mem of team[tn]) {
let o = document.createElement("option")
o.textContent = mem.name;
nmsel.appendChild(o);
o.selected = true;
}
}
function sendJSONtoServer(json) {
if (ws.readyState == WebSocket.OPEN) { // ==1
ws.send(JSON.stringify(json));
}
}
function sendMynameToServer() {
sendJSONtoServer({"name": nmsel.value, "team": tmsel.value});
}
function loadTeamList(file, elm) {
fetch(file).then((resp) => {
if (resp.ok) return resp.text();
}).then((txt) => {
let teamcsv = new CSV(txt, {header: true}).parse();
let tmpa;
for (let row of teamcsv) { // Collect all team members
if (!team[row.teamname]) team[row.teamname]=[];
team[row.teamname].push({uid: row.uid, name: row.name});
}
if (tmsel) {
for (let tn of Object.keys(team)) {
let o = document.createElement("option")
o.textContent = tn;
tmsel.appendChild(o);
}
updateMemberOption();
tmsel.addEventListener("change", updateMemberOption);
nmsel.addEventListener("change", sendMynameToServer);
console.log(Object.keys(team));
}
});
}
// Typing engine
var pos=0, nArea=0;
function switchToArea(n) {
if (singleTextMode) {
for (let i=0; i<area.length; i++) {
area[i].style.display = (i==n ? "block" : "none");
}
}
area[nArea].classList.remove("current");
nArea = n;
if (nArea < area.length) {
area[nArea].classList.add("current");
}
}
function startTrr(time) {
mystate.start = time;
sendJSONtoServer({
"text": null, // XXXXX not yet
"start": time
});
info.classList.add("go");
infoBox.innerText = "GO!!";
mystate.miss = 0;
document.body.addEventListener("click", _entryFocus);
}
function commentScore(score) {
if (score < 10)
return "努力あるのみ";
else if (score < 50)
return "まず正しい指使いを覚えなさい";
else if (score < 100)
return "見ながら打っているようではダメよ";
else if (score < 160)
return "ここからが勝負よ";
else if (score < 200)
return "あと一息で業界標準の200点よ";
else if (score < 220)
return "業界標準ね";
else if (score < 300)
return "業界目標の300点までもう少し!";
else if (score < 400)
return "業界一流の400点は行っておかないと";
else
return "業界一流ね. ここから先は自分との戦いよ";
}
function updateScoreField() {
let text = textlist[nArea]["file"],
data = mystate.scoreinfo[text];
setElementIDText("name", mystate.scoreinfo.gecos);
if (data) {
let st = mystate.scoreinfo.steptrial[text][data.step];
setElementIDText("step", data.step);
setElementIDText("target", 10*(data.step+1));
setElementIDText("hs", data.highscore);
setElementIDText("trial", data.trial);
setElementIDText("strial", st);
} else {
for (let i of ["step", "target", "hs", "trial", "strial"])
setElementIDText(i, "");
}
}
function finishTrr(data) {
document.body.removeEventListener("click", _entryFocus);
setElementIDText("time", data.time+"s");
setElementIDText("score", Math.max(Math.round(data.score), 0));
setElementIDText("types", mystate.sum);
setElementIDText("speed", data.speed);
setElementIDText("comment", data.message+"\n(TEXT選択ボタンで再挑戦)");
mystate.scoreinfo = data.scoreinfo;
updateScoreField();
setElementIDText("info", "GOAL!!");
}
function sendFinishTrr() {
let finish = Date.now()/1000;
let elapse = finish - mystate.start, sum=0;
mystate.sum = sum = area[nArea].textContent.length+1;
console.log("nArea="+nArea);
console.log("LEN="+sum);
console.log("elapse="+elapse);
sendJSONtoServer({
"text": textlist[nArea]["file"],
"start": mystate.start,
"finish": finish,
"miss": mystate.miss,
"types": sum
});
}
function trr(e) {
e.preventDefault();
let done = area[nArea].childNodes[0],
curs = area[nArea].childNodes[1],
text = area[nArea].childNodes[2],
inp = e.target;
let str, len=done.textContent.length;
if (e.keyCode == 13) {
console.log("ENTER");
str = "\n";
} else {
str = inp.value;
}
if (mystate.finish > 0) {
return;
} else if (mystate.start == 0) {
if (e.keyCode == 8) { // First Hit of [BS] Start Self-training
startTrr(Date.now()/1000);
mystate.singleUserMode = true;
return;
} else {
info.innerText = "まだだよ(BSキーで練習スタート)";
}
} else {
while (str > "") {
if (str[0] == curs.textContent) {
if (text.textContent > "") {
if (mystate.missflag) {
done.innerHTML +=
'<span class="miss">'+str[0]+'</span>';
console.log("done="+done.innerHTML);
} else {
console.log("X"+str);
done.innerHTML += str[0];
console.log("DONE="+done.innerHTML);
}
mystate.missflag = false;
curs.textContent = text.textContent[0];
} else {
curs.textContent = ""; // Prevent adding newline
}
pos++;
text.textContent = text.textContent.substr(1);
} else {
console.log("MISSSSSSSS"+str);
mystate.miss++;
mystate.missflag = true;
}
str = str.substr(1);
}
sendJSONtoServer({"types": done.textContent.length});
setElementIDText("miss", mystate.miss);
if (len>0) {
let ratio = " "+Math.round(1000*mystate.miss/len)/10+"%";
ratio = ratio.substr(-6);
setElementIDText("ratio", ratio);
}
}
inp.value = "";
if (curs.textContent == "") {
mystate.finish = Date.now()/1000;
if (nArea+1 < area.length && !mystate.singleUserMode) {
switchToArea(++nArea);
} else { // Finished!!
// finishTrr();
sendFinishTrr();
}
}
}
function selTextAndSend(e) {
let n = e.target.value;
for (let j=0; j<area.length; j++) {
if (n==j)
area[j].classList.add("mytext");
else
area[j].classList.remove("mytext");
}
console.log("TL="+textlist)
sendJSONtoServer({"settext": n});
// updateScoreField();
}
function reloadText(e) {
let n = e.target.value;
if (n<0 || n>=textlist.length) return;
let stage = area[n],
dne = stage.querySelector(".done"),
cur = stage.querySelector(".cursor"),
txt = stage.querySelector(".text");
console.log("stage="+stage);
console.log("cur="+cur);
console.log("txt="+txt);
dne.textContent = "";
loadTextToElement(textlist[n], cur, txt);
switchToArea(n);
setTimeout(()=>{entry.focus();}, 100);
}
function prepareStage() {
fetch("TEXT").then((resp) => {
if (resp.ok) return resp.text();
}).then(async (text) => { // Should be async func for serial loading
let tl0 = text.split("\n"), tl=[], f;
for (f of tl0)
if (f!="\n" && f!="")
tl.push(f); // Eliminate non-filenames
for (f of tl) {
// console.log("LOADING "+f);
let csv = f.split(","), file=csv[0], title=csv[1];
await fetch(file).then((r)=>{ // Load sequentially
if (r.ok) return r.text();
}).then((text) => {
textlist.push({"file":file, "title":title, "text":text});
if (textlist.length == tl.length)
createStage();
});
}
if ("scoreinfo" in mystate) {
txsel.querySelector("label+label>input").click();
//Fake update by click()
setTimeout(()=>{txsel.querySelector("input").click()}, 100);
// setElementIDText("hs", hs.text||"0")
}
});
}
function createStage() {
let stage = document.getElementById("typing");
// for (let i=0; i<nText; i++)
for (let i=0; i<textlist.length; i++) {
let pre = document.createElement("pre"),
done = document.createElement("span"),
curs = document.createElement("span"),
text = document.createElement("span"),
rbtn = document.createElement("input");
let texthash = textlist[i];
done.classList.add("done");
curs.classList.add("cursor");
text.classList.add("text");
stage.appendChild(pre);
pre.appendChild(done);
pre.appendChild(curs);
pre.appendChild(text);
pre.setAttribute("contentEditable", false);
area.push(pre)
// console.log("area==="+area);
let textlabel = document.createElement("button");
textlabel.textContent = texthash.title;
//textlabel = document.createTextNode(texthash.file);
loadTextToElement(texthash, curs, text)
rbtn.setAttribute("type", "radio");
rbtn.setAttribute("name", "text");
rbtn.setAttribute("value", i);
rbtn.addEventListener("change", selTextAndSend);
rbtn.addEventListener("click", reloadText);
let label = document.createElement("label");
// https://qiita.com/sola-msr/items/bdec752da00c5ab677b3
textlabel.style.pointerEvents = "none";
label.appendChild(rbtn);
label.appendChild(textlabel);
txsel.appendChild(label);
}
switchToArea(0);
}
function showRank(list) {
// user, max(score), max(step), count(score), sum(time)/60, max(at)
let tbl = document.getElementById("ranking-table"), me;
for (let i=0; i<40; i++) {
let tr = document.createElement("tr")
tr.innerHTML="<tr><th>"+i+"</th></tr>";
tbl.appendChild(tr);
}
for (let i in list) {
let tr = document.createElement("tr"),
rank = document.createElement("th");
rank.textContent = parseInt(i+1)+"";
tr.appendChild(rank);
for (let j=0; j<list[i].length; j++) {
let td = document.createElement("td");
td.textContent = list[i][j];
tr.appendChild(td);
}
tbl.appendChild(tr);
console.log("MU="+mystate.user+", "+list[i][0]);
if (mystate.user == list[i][0]) me = tr;
}
if (me) {
me.classList.add("me");
setTimeout(() => {
me.scrollIntoView({behavior: "smooth", block: "center"});
console.log(me);
}, 1000);
}
}
function getServerMessage(e) {
console.log("Get: "+e.data);
let data = JSON.parse(e.data);
if (data.myid) {
mystate.myid = data.myid;
//infoBox.innerText = "Ready";
resetState();
} else if (data.start) {
startTrr(data.start);
} else if (data.disable) {
for (let lbl of txsel.childNodes) {
let inp = lbl.firstChild;
console.log(inp.tagName);
console.log(inp.value==data.disable);
if (!inp.tagName.match(/input/i)) continue;
if (inp.value==data.disable) {
inp.disabled = true;
lbl.classList.add("disabled");
} else {
inp.disabled = false;
lbl.classList.remove("disabled");
}
}
} else if (data.email) {
loginhead.innerText = "Sent passcode to "+data.email;
prompt.innerText = "Passcode"
tmpkey = data.tmpkey; // Don't save tmp-skey
mystate.user = data.user;
input.value = "";
input.setAttribute("type", "password");
input.focus();
} else if ("step" in data) {
finishTrr(data);
} else if (data.fail) {
switch (data.fail) {
case "fail":
loginhead.innerHTML = "Invalid passcode<br>Try again ";
input.value = "";
input.focus();
break;
case "nokey":
alert("Ooops - session forcibly been timeout")
break;
}
} else if (data.user && data.skey) {
console.log("You are "+data.user);
let logdiv = document.querySelector("div.login");
localStorage.setItem("trrSkey", data.skey);
localStorage.setItem("trrUser", data.user);
tmpkey = null; // Reset temporary skey
console.log("LSK="+localStorage.getItem("trrSkey"));
console.log("LSU="+localStorage.getItem("trrUser"));
if (logdiv) logdiv.setAttribute("class", "login2");
mystate.user = data.user;
mystate.scoreinfo = data.scoreinfo;
setElementIDText("user", data.user);
setTimeout(() => {
logdiv.remove();
console.log("BYEBYE");
}, 1000);
} else if (data.scoreinfo) {
console.log("DSSSS="+JSON.stringify(data.scoreinfo));
console.log("STRI="+JSON.stringify(data.scoreinfo.steptrial));
mystate.scoreinfo = data.scoreinfo;
updateScoreField();
} else if (data.ranking) {
console.log("RANKING="+JSON.stringify(data.ranking));
showRank(data.ranking);
} else {
console.log("message: "+data.message);
}
}
function ranking(ev) {
var div = document.createElement("div");
div.classList.add("login2");
setTimeout(()=>{div.setAttribute("class", "ranking")}, 1000);
div.innerHTML = "<p class=\"c t\"><button>Back to stage</button></p>"
var d2 = document.createElement("div");
d2.classList.add("tbl");
var tbl = document.createElement("table");
tbl.id = "ranking-table";
tbl.classList.add("ranking");
tbl.innerHTML = "<tr>\
<th>Rank</th><th>User</th><th>Score</th><th>Step</th>\
<th>Try</th><th>Minutes</th><th>Time</th></tr>"
div.appendChild(d2);
d2.appendChild(tbl);
document.body.appendChild(div);
if (history.pushState) {
let h = location.href.replace(/#.*/, '')+"#ranking";
history.pushState({url: h}, null, h);
window.addEventListener("popstate", (e) => {
if (div) {
div.remove(); div = null;
}
}, false);
}
let text = textlist[nArea].file;
sendJSONtoServer({ranking: text});
div.addEventListener("click", ()=>{history.back();});
div.addEventListener("keydown", ()=>{history.back();}, false);
// div.querySelector("button").focus();
}
function resetState() {
mystate.start = mystate.miss = mystate.finish = 0;
for (id of ["miss", "ratio", "types", "time", "score"]) {
setElementIDText(id, "");
}
setElementIDText("comment", "自主練は BS で開始よ。");
setElementIDText("info", "Ready...");
}
function tryLogin() {
let skey = localStorage.getItem("trrSkey");
// alert(skey);
if (skey && skey > "") {
let user = mystate.user||localStorage.getItem("trrUser");
console.log("Sending"+skey);
sendJSONtoServer({"user": user, "skey": skey});
}
}
function initLogin() {
function loginProcedure(json) {
}
function _send(e) {
loginhead = document.getElementById("loginhead");
prompt = document.getElementById("prompt");
input = document.getElementById("inputvalue");
console.log("prom="+prompt.innerText+", val="+input.value);
if (prompt && prompt.innerText > "" && input && input.value > "") {
let user = mystate.user||localStorage.getItem("trrUser"),
skey = localStorage.getItem("trrSkey");
let j = {}
j[prompt.innerText] = input.value;
if (user && user>"") j.user = user;
if (tmpkey && tmpkey>"") j.tmpkey = tmpkey;
input.value = ""
sendJSONtoServer(j);
}
};
function _reset(e) {
localStorage.clear();
mystate.user = "";
tmpkey = null;
}
tryLogin();
document.getElementById("inputvalue").addEventListener(
"keypress", (e) => {if (e.key=='Enter') _send(e);});
document.getElementById("login").addEventListener("click", _send);
document.getElementById("reset").addEventListener("click", _reset);
}
function wsInit() {
ws = new WebSocket(wsURL);
infoBox.removeEventListener("click", wsInit);
ws.onopen = function () {
console.log("WS-OK");
initLogin();
};
ws.onmessage = getServerMessage;
ws.onclose = function () {
setElementIDText("info", "No Server Connection. Click to reconnect");
infoBox.addEventListener("click", wsInit);
};
}
function _entryFocus() {
if (entry) entry.focus();
}
function init() {
tmsel = document.querySelector("select[name=team]");
nmsel = document.querySelector("select[name=name]");
txsel = document.getElementById("text");
document.getElementById("ranking").addEventListener("click", ranking);
prepareStage();
// resetState();
let entry = document.createElement("input");
entry.id = "entry"; entry.type = "text";
document.body.appendChild(entry);
infoBox = entry.insertAdjacentHTML(
"afterend", '<p class="info" id="info"><p>');
infoBox = document.getElementById("info");
wsInit();
entry.focus();
// entry.addEventListener("input", trr, false);
entry.addEventListener("keyup", trr, false);
}
document.addEventListener("DOMContentLoaded", init, false);
loadTeamList("teams.csv");
})();