(function (){ // var wsURL = "wss://tmp.iekei.org/jstype"; var wsURL = "wss://" + location.host + "/jtserv"; 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 xxx_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 xxx_loadTextToElement0(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 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": 40 }); 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 }); } 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) { // 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"); 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.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 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 _reset(e) { localStorage.clear(); mystate.user = ""; tmpkey = null; } 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); } }; 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); var typing = document.getElementById("typing"); ws.onopen = function () { console.log("WS-OK"); infoBox.classList.remove("warn"); 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() { tmsel = document.querySelector("select[name=team]"); nmsel = document.querySelector("select[name=name]"); txsel = document.getElementById("text"); document.getElementById("ranking").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"); wsInit(); } document.addEventListener("DOMContentLoaded", init, false); //loadTeamList("teams.csv"); })();