(function (){
// var wsURL = "wss://tmp.iekei.org/jstype";
var wsURL = "wss://" + location.host + "/koekitrr";
var ws; // for WebSocket Object
var textlist = [];
var nText = nText||4;
var nLine = nLine||3;
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 loadTextToElement1(text) {
console.log("elm1="+mystate.elm1);
mystate.elm1.textContent = text[0];
mystate.elm2.textContent = text.substr(1);
resetState();
}
function loadTextToElement(hash, elm1, elm2) {
sendJSONtoServer({
"gettext": hash.file,
"lines": nLine,
"fill": 60
});
mystate.elm1 = elm1;
mystate.elm2 = elm2;
}
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,
"trrmode": mystate.trrmode
});
}
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 {
if (str.length > 10) {
alert("あら、なにかおかしいわね。");
alert("これはちょっと...");
alert("頭を冷やして明日またやりましょう。");
inp.value = ""
resetState();
return;
}
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 xxx_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];
if (false) {
// XXXXXXXXXXXXXXXXXXXXXXXXXXXX
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();
});
} else {
textlist.push({"file":file, "title":title});
}
}
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 prepareStage(textfiles) {
if (textlist.length > 1) return; // Maybe 2nd time
textlist = textfiles;
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")
}
let entry = document.getElementById("entry");
entry.focus();
entry.addEventListener("keyup", trr, false);
}
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) {
mystate["ranklist"] = list;
// user, max(score), max(step), count(score), sum(time)/60, max(at)
let tbl = document.getElementById("ranking-table"), me;
if (false) {
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");
if (i>0) rank.textContent = parseInt(i);
tr.appendChild(rank);
for (let j=0; j<list[i].length; j++) {
let td = document.createElement(i==0?"th":"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 downloadCSV(ev) {
ev.stopPropagation();
if (!mystate["ranklist"]) return;
let list = mystate["ranklist"],
mode = mystate["rankmode"],
str="";
text = textlist[nArea]["file"];
for (let i in list) {
str += (i==0?"Text":text)+","+list[i].join(",")+"\n";
}
let bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
var a = document.createElement("a");
var csv = new Blob([bom, str], {type: "text/csv"});
var uri = URL.createObjectURL(csv);
a.download = text+(mode ? "-"+mode : "")+".csv";
a.href = uri;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
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.tmpkey) {
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");
} else {
loginhead.innerText = "Input Your Login ID";
}
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 ";
logdiv.classList.remove("login2");
input.value = "";
input.focus();
break;
case "nokey":
// alert("Ooops - session forcibly been timeout")
_reset();
break;
case "overlimit":
alert(data.message);
break;
}
} else if (data.user && data.skey) {
console.log("You are "+data.user);
prepareStage(data.textfiles);
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"));
mystate.user = data.user;
mystate.scoreinfo = data.scoreinfo;
setElementIDText("user", data.user);
if (logdiv) {
logdiv.setAttribute("class", "login2");
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 if (data.yourtext) {
console.log("YT="+data.yourtext);
loadTextToElement1(data.yourtext);
} else {
console.log("message: "+data.message);
}
}
function ranking(ev) {
var mode = null;
switch (ev.target.id) {
case "ranking": mode = null; break;
default: mode = ev.target.id.replace("ranking:", ""); break;
}
mystate["rankmode"] = mode;
var div = document.createElement("div");
div.classList.add("login2");
setTimeout(()=>{div.setAttribute("class", "ranking")}, 1000);
var p1 = document.createElement("p");
p1.className = "c t";
p1.innerHTML = "<button>Back to stage</button><p>"+
(mode||"Total")+"</p>";
var p2 = document.createElement("p");
p2.className = "c t";
var b2 = document.createElement("button");
b2.innerText = "Download CSV";
b2.addEventListener("click", downloadCSV, false);
p2.appendChild(b2);
var d2 = document.createElement("div");
d2.classList.add("tbl");
var tbl = document.createElement("table");
tbl.id = "ranking-table";
tbl.classList.add("ranking");
div.appendChild(p1);
div.appendChild(d2);
div.appendChild(p2);
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;
mystate["ranklist"] = null; // Clear ranking list
let request = {ranking: text};
if (mode) request["mode"] = mode;
sendJSONtoServer(request);
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, "");
}
if (area[nArea] && area[nArea].textContent
&& area[nArea].textContent >= 0)
setElementIDText("comment", "自主練は BS で開始よ。");
else
setElementIDText("comment", "まずTEXTを選択するところからよ。");
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 _reset(e) {
localStorage.clear();
mystate.user = "";
tmpkey = null;
}
function initLogin() {
function loginProcedure(json) {
}
function _send(e) {
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);
}
};
tryLogin();
input.addEventListener(
"keypress", (e) => {if (e.key=='Enter') _send(e);});
let login = document.getElementById("login");
if (login) {
login.addEventListener("click", _send);
document.getElementById("reset").addEventListener("click", _reset);
}
}
function logout() {
_reset();
location.reload();
}
function trrmode(ev) {
document.body.classList.remove(mystate["trrmode"]);
document.body.classList.add(mystate["trrmode"] = ev.target.value);
_entryFocus();
}
function setupTrrMode() {
var modesel = document.getElementById("trrmode");
modesel.addEventListener("change", trrmode);
modesel.options[0].selected = true;
}
var wsInitRetry = 10;
function wsInit() {
loginhead.textContent = "Connecting Server....."+wsInitRetry;
input.disabled = true; // Start with input prompt disabled
ws = new WebSocket(wsURL);
ws.onerror = (e) => {
console.log("Oh...");
if (--wsInitRetry > 0)
setTimeout(wsInit, 5000);
};
infoBox.removeEventListener("click", wsInit);
var typing = document.getElementById("typing");
ws.onopen = function () {
console.log("WS-OK");
input.disabled = false;
input.focus();
infoBox.classList.remove("warn");
loginhead.textContent = "jsTRR";
initLogin();
};
ws.onmessage = getServerMessage;
ws.onclose = function () {
setElementIDText("info", "No Server Connection. Click to reconnect");
infoBox.classList.add("warn");
infoBox.addEventListener("click", wsInit);
};
}
function _entryFocus() {
if (entry) entry.focus();
}
function init() {
let btn;
prompt = document.getElementById("prompt");
input = document.getElementById("inputvalue");
console.log("prom="+prompt.innerText+", val="+input.value);
tmsel = document.querySelector("select[name=team]");
nmsel = document.querySelector("select[name=name]");
txsel = document.getElementById("text");
loginhead = document.getElementById("loginhead");
for (let btn of document.querySelectorAll("button")) {
if (btn.id.match(/^ranking/))
btn.addEventListener("click", ranking);
}
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");
document.getElementById("logout").addEventListener("click", logout);
setupTrrMode()
wsInit();
}
document.addEventListener("DOMContentLoaded", init, false);
//loadTeamList("teams.csv");
})();