// ==UserScript==
// @name WK KanaMatrix
// @namespace EthanMcCoy
// @version 0.6
// @description On script enabled mobile phones (touchscreen devices) creates a kana matrix input
// @author Ethan McCoy
// @include *://www.wanikani.com/lesson/session*
// @include *://www.wanikani.com/review/session*
// @grant none
// ==/UserScript==
(function() {
'use strict';
var logEvent = function(eventName, message){
console.log("%cKM: %c" + eventName + "%c " + message, "background-color:black, color:white", "text-decoration:underline; text-decoration-color:red;", "");
};
// Some of these might only be used by constructCSS but some of them are used by other functions
// All number types are units in pixels
var buttonBkgColor = "#32323e;";
var buttonBorderColor = "black";
var buttonPetalColor = "transparent;";
var buttonTxtColor = "white;";
var buttonHt = 50;
var footerHt = 25;
var buttonBorderWd = 5;
var buttonWithBorder = buttonHt + 2 * buttonBorderWd;
var triangleColor = "#90dae077";
var petalWd = buttonHt * 0.23;
var buttonFontSizeMain = buttonHt * (1 - 0.23 * 2);
var petalTopPos = 2;
var petalBottomPos = 1;
var petalRightPos = 3;
var handakutenLeftM = 45;
// Define and call function so we can collapse it in the editor
var constructCSS = function(){
var kbd_css =
'#KMwrapper {'+
' user-select: none;' + //Might stop the keyboard stealing focus on holding the button, can't replicate on PC so here's hoping.
' position: fixed;'+
' left: 50%;'+
' margin-left: -50%;'+
' bottom:0px;'+
' pointer-events: none;'+
' height: ' + buttonWithBorder + 'px;'+
' transition: height 0.5s;'+
' width: 100%;'+
' z-index: 999999;'+
'}'+
'#showBoard {'+
' user-select: none;'+
' top:0px;'+
' z-index:10;'+
' position:absolute;'+
' background-color: purple;'+
' opacity:1;'+
' visibility:hidden;'+
'}'+
'#kanaBoard {'+
' user-select: none;'+
' top: ' + (buttonWithBorder) + 'px;'+
' position:relative;'+
' background-color: ' + buttonBorderColor + ';'+
' height: ' + 4 * buttonWithBorder + footerHt + 'px;' +
'}'+
'#kanaBoard, #showBoard{'+
' width: 100%;'+
'}'+
'.groupCore, .changeState{'+
' width: calc(25% - 10px);'+
' height: ' + buttonHt + 'px;'+
' line-height: ' + buttonHt + 'px;'+
' border-width: ' + buttonBorderWd + 'px;' +
' border-style:solid;'+
'}'+
'.showButton{'+
' background-color: ' + buttonBkgColor +
' color: ' + buttonTxtColor +
' width: calc((25% - ' + buttonBorderWd/1.5*2 + 'px) / 1.5);'+
' height: ' + (buttonWithBorder/1.5 - buttonBorderWd/1.5*2) + 'px;' +//calc(90px / 1.5);'+
' line-height: ' + (buttonWithBorder/1.5 - buttonBorderWd/1.5*2) + 'px;' +//calc(95px / 1.5);'+
' border-width: ' + buttonBorderWd/1.5 + 'px;' +
' box-shadow: 5px 5px #88888877;'+
' border-style:solid;'+
// Obviously this is a bit ironic, we should probably calculate it in javascript not css
' font-size: calc('+buttonFontSizeMain+'px / 1.5);'+
// Margin calculation is a bit of mystery, should check it to see how it works and if it's accurate
' margin: ' + (buttonWithBorder - buttonWithBorder/1.5)/2 + 'px calc((25% - 25% / 1.5) / 2 - ' + buttonBorderWd + 'px / 4);' +
'}'+
'#spacebar {'+
' height:' + (buttonHt+buttonBorderWd) * 2 +'px;'+
' font-size:'+buttonFontSizeMain * 2 +'px;'+
' float:right;' +
'}'+
'#modify {'+
' letter-spacing: normal;'+
'}'+
'#modify .buttonComps {' +
' font-size:' + buttonFontSizeMain * 2 + 'px;' + //64pt;' +
' margin-top:15px;' +
' width:100%;' +
' text-align:center;' +
' white-space: nowrap;' +
'}' +
'#modify .buttonComps:nth-child(1) {' + //handakuten
' text-align:center;' +
' text-indent:10%;' +
'}' +
'#backspace {'+
' font-size:'+buttonFontSizeMain+'px;'+
'}'+
'#enter {'+
' font-size:'+buttonFontSizeMain*2+'px;'+
'}'+
'.showPetal.leftPetal, .showPetal.rightPetal {'+
//' line-height: calc(95px / 1.5);'+
'}'+
'.showPetal.topPetal{'+
//' line-height: calc(25px / 1.5);'+
'}'+
'.showPetal.bottomPetal{'+
' line-height: calc(17px / 1.5);'+
'}'+
'.showPetal{'+
' color: ' + buttonTxtColor +
' font-size: calc(13pt / 1.5) !important;'+
'}'+
'.groupCore, .showButton {'+
' position:relative;'+
' overflow:hidden;'+
' text-align:center;'+
' vertical-align:middle;'+
' display:inline-block;'+
'}'+
'.groupCore {'+
' z-index:0;'+
' border-color:' + buttonBorderColor + ';'+
' background-color: ' + buttonBkgColor +
' color: ' + buttonTxtColor +
' font-size:'+buttonFontSizeMain+'px;'+
'}'+
'.changeState {'+
' font-size: ' + buttonFontSizeMain + 'px;' +
' border-color:black;'+
' background-color: ' + buttonBkgColor +
' color: ' + buttonTxtColor +
' display: block;'+
' vertical-align:top;'+
' display:inline-block;'+
' position:relative;'+
'}'+
'.buttonComps {'+
' position:absolute;'+
' left:50%;'+
' transform: translateX(-50%);'+
'}'+
'.groupFlower {'+
' pointer-events: none;'+
'}'+
'.groupPetal {'+
' pointer-events: none;'+
' z-index:5;'+
' position:absolute;'+
' background-color: ' + buttonPetalColor +
' font-size: ' + petalWd + 'px;' + //23%;'+//13pt;'+
'}'+
'.topPetal {'+
' line-height: ' + petalWd + 'px;' + //23%;'+
' top: ' + petalTopPos + 'px;'+
' height: ' + petalWd + 'px;' + //23%;'+
' width: 100%;'+
'}'+
'.leftPetal {'+
//' line-height: ' + buttonHt + 'px;' +
' top:0;'+
' left:0;'+
' height: 100%;'+
' width: 23%;' + //petalWd + 'px;' + // 23%;'+
'}'+
'.rightPetal {'+
//' line-height: ' + buttonHt + 'px;' +
' top:0;'+
' right:' + petalRightPos + 'px;'+
' height: 100%;'+
' width: 23%;' + //petalWd + 'px;' + // 23%;'+
'}'+
'.bottomPetal {'+
' line-height: ' + petalWd + 'px;' + // 23%;'+
' bottom: ' + petalBottomPos + 'px;'+
' height: ' + petalWd + 'px;' + // 23%;'+
' width: 100%;'+
'}'+
'.arrow{'+
' pointer-events: none;'+
' width: 0; '+
' height: 0; '+
' position:absolute;'+
' z-index:10;'+
' visibility:hidden;'+
'}'+
'.arrow-up {'+
' pointer-events: none;'+
' bottom:0;'+
' border-bottom: ' + buttonHt/2 + 'px solid ' + triangleColor + ';' +
'}'+
'.arrow-down {'+
' top:0;'+
' border-top: ' + buttonHt/2 + 'px solid ' + triangleColor + ';' +
'}'+
'.arrow-right {'+
' top:0;'+
' border-top: ' + buttonHt/2 + 'px solid transparent;' +
' border-bottom: ' + buttonHt/2 + 'px solid transparent;' +
'}'+
'.arrow-left {'+
' top:0;'+
' right:0;'+
' border-top: ' + buttonHt/2 + 'px solid transparent;' +
' border-bottom: ' + buttonHt/2 + 'px solid transparent;' +
'}'+
'#caret {'+
' opacity:1;shape-rendering: crispEdges;'+
'}'+
'svg {'+
' animation: 1s linear 0s infinite running blink;'+
'}'+
'@keyframes blink {'+
' 50% {'+
' stroke:black;'+
' visibility: visible;'+
' }'+
' 100% {'+
' visibility:hidden;'+
' }'+
'}';
return kbd_css;
};
// Define and call function so we can collapse it in the editor and refactor to build dynamically later
var constructHTML = function(){
var kbd_html =
'<div id="KMwrapper">'+
' <div id="showBoard">'+
' <div class="showButton" id="showA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal showPetal">う</div>'+
' <div class="groupPetal leftPetal showPetal">い</div>'+
' <div class="groupPetal rightPetal showPetal">え</div>'+
' <div class="groupPetal bottomPetal showPetal">お</div>'+
' あ'+
' </div>'+
' </div><div class="showButton" id="showKA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">く</div>'+
' <div class="groupPetal leftPetal">き</div>'+
' <div class="groupPetal rightPetal">け</div>'+
' <div class="groupPetal bottomPetal">こ</div>'+
' か'+
' </div>'+
' </div><div class="showButton" id="showSA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">す</div>'+
' <div class="groupPetal leftPetal">し</div>'+
' <div class="groupPetal rightPetal">せ</div>'+
' <div class="groupPetal bottomPetal">そ</div>'+
' さ'+
' </div>'+
' </div><div class="showButton">'+
' </div><div class="showButton" id="showTA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">つ</div>'+
' <div class="groupPetal leftPetal">ち</div>'+
' <div class="groupPetal rightPetal">て</div>'+
' <div class="groupPetal bottomPetal">と</div>'+
' た'+
' </div>'+
' </div><div class="showButton" id="showNA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ぬ</div>'+
' <div class="groupPetal leftPetal">に</div>'+
' <div class="groupPetal rightPetal">ね</div>'+
' <div class="groupPetal bottomPetal">の</div>'+
' な'+
' </div>'+
' </div><div class="showButton" id="showHA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ふ</div>'+
' <div class="groupPetal leftPetal">ひ</div>'+
' <div class="groupPetal rightPetal">へ</div>'+
' <div class="groupPetal bottomPetal">ほ</div>'+
' は'+
' </div>'+
' </div><div class="showButton">'+
' </div><div class="showButton" id="showMA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">む</div>'+
' <div class="groupPetal leftPetal">み</div>'+
' <div class="groupPetal rightPetal">め</div>'+
' <div class="groupPetal bottomPetal">も</div>'+
' ま'+
' </div>'+
' </div><div class="showButton" id="showYA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ゆ</div>'+
' <div class="groupPetal leftPetal">(</div>'+
' <div class="groupPetal rightPetal">)</div>'+
' <div class="groupPetal bottomPetal">よ</div>'+
' や'+
' </div>'+
' </div><div class="showButton" id="showRA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">る</div>'+
' <div class="groupPetal leftPetal">り</div>'+
' <div class="groupPetal rightPetal">れ</div>'+
' <div class="groupPetal bottomPetal">ろ</div>'+
' ら'+
' </div>'+
' </div><div class="showButton">'+
' </div><div class="showButton">'+
' </div><div class="showButton" id="showWA">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ん</div>'+
' <div class="groupPetal leftPetal">を</div>'+
' <div class="groupPetal rightPetal">ー</div>'+
' <div class="groupPetal bottomPetal">~</div>'+
' わ'+
' </div>'+
' </div><div class="showButton" id="showPUNC">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">?</div>'+
' <div class="groupPetal leftPetal">。</div>'+
' <div class="groupPetal rightPetal">!</div>'+
' <div class="groupPetal bottomPetal">…</div>'+
' 、'+
' </div>'+
' </div>'+
' </div>'+
' <div id="kanaBoard">'+
' <div class="groupCore" id="a">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">う</div>'+
' <div class="groupPetal leftPetal">い</div>'+
' <div class="groupPetal rightPetal">え</div>'+
' <div class="groupPetal bottomPetal">お</div>'+
' あ'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="groupCore" id="ka">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">く</div>'+
' <div class="groupPetal leftPetal">き</div>'+
' <div class="groupPetal rightPetal">け</div>'+
' <div class="groupPetal bottomPetal">こ</div>'+
' か'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="groupCore" id="sa">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">す</div>'+
' <div class="groupPetal leftPetal">し</div>'+
' <div class="groupPetal rightPetal">せ</div>'+
' <div class="groupPetal bottomPetal">そ</div>'+
' さ'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="changeState" id="backspace">'+
' <span class="buttonComps">⌫'+
' </span>'+
' </div><div class="groupCore" id="ta">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">つ</div>'+
' <div class="groupPetal leftPetal">ち</div>'+
' <div class="groupPetal rightPetal">て</div>'+
' <div class="groupPetal bottomPetal">と</div>'+
' た'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="groupCore" id="na">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ぬ</div>'+
' <div class="groupPetal leftPetal">に</div>'+
' <div class="groupPetal rightPetal">ね</div>'+
' <div class="groupPetal bottomPetal">の</div>'+
' な'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="groupCore" id="ha">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ふ</div>'+
' <div class="groupPetal leftPetal">ひ</div>'+
' <div class="groupPetal rightPetal">へ</div>'+
' <div class="groupPetal bottomPetal">ほ</div>'+
' は'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="changeState" id="spacebar">'+
' <span class="buttonComps">⎵'+
' </span>'+
' </div><div class="groupCore" id="ma">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">む</div>'+
' <div class="groupPetal leftPetal">み</div>'+
' <div class="groupPetal rightPetal">め</div>'+
' <div class="groupPetal bottomPetal">も</div>'+
' ま'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="groupCore" id="ya">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ゆ</div>'+
' <div class="groupPetal leftPetal">(</div>'+
' <div class="groupPetal rightPetal">)</div>'+
' <div class="groupPetal bottomPetal">よ</div>'+
' や'+
' </div>'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div><div class="groupCore" id="ra">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">る</div>'+
' <div class="groupPetal leftPetal">り</div>'+
' <div class="groupPetal rightPetal">れ</div>'+
' <div class="groupPetal bottomPetal">ろ</div>'+
' ら'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div>'+
' </div>'+
' <div class="changeState" id="modify">'+
' <span class="buttonComps">゙ ゚</span>'+
//' <span class="buttonComps"></span>'+
' <span class="buttonComps" style="font-size:16pt;margin-top:15px">大⇔小</span>'+
' </div><div class="groupCore" id="wa">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">ん</div>'+
' <div class="groupPetal leftPetal">を</div>'+
' <div class="groupPetal rightPetal">ー</div>'+
' <div class="groupPetal bottomPetal">~</div>'+
' わ'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div>'+
' </div><div class="groupCore" id="punc">'+
' <div class="groupFlower">'+
' <div class="groupPetal topPetal">?</div>'+
' <div class="groupPetal leftPetal">。</div>'+
' <div class="groupPetal rightPetal">!</div>'+
' <div class="groupPetal bottomPetal">…</div>'+
' 、'+
' <div class="arrow arrow-down"></div>'+
' <div class="arrow arrow-right"></div>'+
' <div class="arrow arrow-left"></div>'+
' <div class="arrow arrow-up"></div>'+
' </div>'+
' </div><div class="changeState" id="enter">'+
' <span class="buttonComps">⏎'+
' </span>'+
' </div>'+
' '+
' </div>'+
'</div>';
return kbd_html;
};
// Assign global variable initial values
var curItemKey = "currentItem";
var qTypeKey = "questionType";
var actQueueKey = "activeQueue";
var svgXmlNs = "http://www.w3.org/2000/svg";
var usrDiv = document.createElement("div");
var svgD = document.createElementNS(svgXmlNs, "svg");
svgD.setAttribute("xmlns", "http://www.w3.org/2000/svg");
var caret = document.createElementNS(svgXmlNs, "line");
// These are not CSS values, they are svg display instructions
caret.setAttribute("id", "caret");
caret.setAttribute("x1", "2");
caret.setAttribute("x2", "2");
caret.setAttribute("y1", "9"); // This may depend on the screen and input size, so should be defined later
caret.setAttribute("y2", "27"); // ditto ^
caret.setAttribute("stroke-width", "1");
svgD.appendChild(caret);
var answerCopySpan = document.createElement("span");
// This is a CSS value, should maybe assign in CSS declaration?
answerCopySpan.style.visibility = "hidden";
var updateValue = function(e) {
logEvent(e.type, "updateValue on e.target");
answerCopySpan.innerText = e.target.value;
};
var enableKbd = function(){
console.log("enableKbd %cArguments","color: purple", arguments);
var usr = $("#user-response");
usr[0].style.caretColor = "transparent";
var fld = usr.parent();
// Div that covers the answer field and steals focus so the navtive mobile keyboard pisses off.
//usrDiv.focus();
usrDiv.style.position = "absolute";
usrDiv.style.opacity = 1;
// From @media section of input css so may need dynamic adjustment
usrDiv.style.fontSize = "0.75em";
usrDiv.style.textAlign = "center";
usrDiv.style.backgroundColor="transparent";
// usrDiv.style.pointerEvents="none";
usrDiv.style.outline = "none";
fld.prepend(usrDiv);
svgD.setAttribute("width", "3");
//Put span in the div so our fake caret will be around the right spot (after the text)
answerCopySpan.innerText = usr.val();
usrDiv.appendChild(answerCopySpan);
console.log("appending", svgD);
usrDiv.appendChild(svgD);
};
var groupings = {
a:"あいうえお",
ka:"かきくけこ",
sa:"さしすせそ",
ta:"たちつてと",
na:"なにぬねの",
ha:"はひふへほ",
ma:"まみむめも",
ya:"や(ゆ)よ",
ra:"らりるれろ",
wa:"わをんー~",
punc:"、。?!…"
};
var makeDiv = function(cl, id, text){
var result = document.createElement("div");
if (cl){
result.className = cl;
}
if (id){
result.id = id;
}
if (text){
result.appendChild(document.createTextNode(text));
}
return result;
};
var createNodeFromGrouping = function(v, i, a){
var result;
if (i === 0){
//a - centre value: needs all the nodes
result = makeDiv("groupFlower", false, a[0]);
result.appendChild(makeDiv("groupPetal leftPetal showPetal", false, a[1]));
result.appendChild(makeDiv("groupPetal topPetal showPetal", false, a[2]));
result.appendChild(makeDiv("groupPetal rightPetal showPetal", false, a[3]));
result.appendChild(makeDiv("groupPetal bottomPetal showPetal", false, a[4]));
}
else{
result = document.createTextNode(v);
}
return result;
};
var buttonNodes = {};
var fillButtonNodes = function(buttonNodes){
for (var grouping in groupings){
var aiueoString = groupings[grouping];
// eg. buttonNodes["ka"] = [groupFlowerDiv, leftPetalDiv, topPetalDiv, rightPetalDiv, bottomPetalDiv]
buttonNodes[grouping] = Array.prototype.map.call(aiueoString, createNodeFromGrouping);
}
console.log("buttonNodes", buttonNodes);
};
// Just fills the object, which should be passed by reference so doesn't need to be reassigned
fillButtonNodes(buttonNodes);
var direction;
var mouseDownCoords = [];
var activeButton;
var showButton;
var clearArrowsBut = function(activeButton/** DivElement */, expt /** String */){
activeButton
.getElementsByClassName("arrow-up")[0].style.visibility=(expt==="arrow-up"?"visible":"hidden");
activeButton
.getElementsByClassName("arrow-left")[0].style.visibility=(expt==="arrow-left"?"visible":"hidden");
activeButton
.getElementsByClassName("arrow-right")[0].style.visibility=(expt==="arrow-right"?"visible":"hidden");
activeButton
.getElementsByClassName("arrow-down")[0].style.visibility=(expt==="arrow-down"?"visible":"hidden");
return activeButton;
};
var repaintDisplay = function(showButton, direction){
console.log("%crunning repaintDisplay", "color:red", arguments);
if (showButton){
while (showButton.childNodes[0]){
showButton.removeChild(showButton.childNodes[0]);
}
if (buttonNodes[activeButton.id]){
var noneNode = buttonNodes[activeButton.id][0];
// Expected to only have one node in each petal.
var upNode = buttonNodes[activeButton.id][2];
var leftNode = buttonNodes[activeButton.id][1];
var rightNode = buttonNodes[activeButton.id][3];
var downNode = buttonNodes[activeButton.id][4];
switch (direction){
case "up":
// Clear other arrow visibilities
clearArrowsBut(activeButton, "arrow-down");
showButton.appendChild(upNode);
break;
case "left":
//Clear other arrow visibilities
clearArrowsBut(activeButton, "arrow-right");
showButton.appendChild(leftNode);
break;
case "right":
//Clear other arrow visibilities
clearArrowsBut(activeButton, "arrow-left");
showButton.appendChild(rightNode);
break;
case "down":
//Clear other arrow visibilities
clearArrowsBut(activeButton, "arrow-up");
showButton.appendChild(downNode);
break;
default:
// Clear all triangles
clearArrowsBut(activeButton);
// Show default button
showButton.appendChild(noneNode);
break;
}
}
}
};
/** globalMouseMoveHandler
Should only be active while mouse button is down (dragging)
@todo convert to touchevents
@param {MouseEvent} e - the native mousemove event
*/
var globalMouseMoveHandler = function(e){
logEvent(e.type, "globalMouseMoveHandler on " + e.target);
var threshold = 30; //number of pixels before it's not the centre
console.log("%cactiveButton.id", "color:green", activeButton.id);
var pairId = "show"+activeButton.id.toUpperCase();
showButton = document.getElementById(pairId);
console.log(pairId, showButton);
if (mouseDownCoords.length){
var horizontalD = (e.clientX||e.touches[0].pageX)-mouseDownCoords[0];
var verticalD = (e.clientY||e.touches[0].pageY)-mouseDownCoords[1];
if (horizontalD > Math.max(threshold, Math.abs(verticalD))){
direction = "right";
}
else if (horizontalD < Math.min(-threshold, -Math.abs(verticalD))){
direction = "left";
}
else if (verticalD > Math.max(threshold, Math.abs(horizontalD))){
direction = "down";
}
else if (verticalD < Math.min(-threshold, -Math.abs(horizontalD))){
direction = "up";
}
else{
direction = "none";
}
repaintDisplay(showButton, direction);
// reset direction
//direction = "none";
}
//console.log("mousemove doc", e);
};
var setUpMoveTracker = function(){
//document.addEventListener("mousemove", globalMouseMoveHandler);
document.addEventListener("touchmove", globalMouseMoveHandler);
};
var outputField = document.getElementById("user-response");
var getOutput = function(outputField){
return outputField.value;
};
var setOutput = function(val, outputField){
console.log("setting", val);
if (!outputField.disabled){
outputField.value = val;
answerCopySpan.innerText = outputField.value;
}
};
var modifyOutput = function(val, outputField){
if (!outputField.disabled){
outputField.value += val;
answerCopySpan.innerText = outputField.value;
}
};
var listenForMouseUp = function(){
var onMouseUp = function(e){
logEvent(e.type, "onMouseUp on "+e.target);
//Get the elements showBoard pair
var pairId = "show"+activeButton.id.toUpperCase();
//Hide the button on showBoard
document.getElementById(pairId).style.visibility="hidden";
//Hide arrows
clearArrowsBut(activeButton);
// Remove globalMovementListener
//document.removeEventListener("mousemove", globalMouseMoveHandler);
document.removeEventListener("touchmove", globalMouseMoveHandler);
// Remove self from listeners
// using 'once' option for this purpose
// Output chosen character to text
// Get direction string position at release
var dirId = 0;
switch (direction){
case "left": dirId = 1; break;
case "up": dirId = 2; break;
case "right": dirId = 3; break;
case "down": dirId = 4; break;
}
if (groupings[activeButton.id]){
var val = groupings[activeButton.id][dirId];
modifyOutput(val, outputField);
}
// Drags are okay but MouseClicks (that fire with the touch events) cause blur event to fire, this line prevents that.
e.preventDefault();
};
document.addEventListener("touchend", onMouseUp, {
once:true
});
};
var initTouchEvent = function(elem){
var onMouseDown = function(e){
logEvent(e.type, "onMouseDown handler on "+e.target);
// e.preventDefault();
direction = "none";
activeButton = e.target;
//Get the elements showBoard pair
var pairId = "show"+activeButton.id.toUpperCase();
//Show the button on showBoard
var showButton = document.getElementById(pairId);
if (showButton){
showButton.style.visibility="visible";
repaintDisplay(showButton, direction);
//Store coordinates of click
mouseDownCoords = [e.clientX||e.touches[0].pageX, e.clientY||e.touches[0].pageY];
setUpMoveTracker();//mousemove
listenForMouseUp();//mouseup
}
};
elem.addEventListener("touchstart", onMouseDown);
};
/** Performs a character rotation for Kana
*/
var rotateChar = function(char){
var lookup = [
"あいうえおかきくけこさしすせそたちつてとはひふへほばびぶべぼやゆよわぁぃぅぇぉがぎぐげござじずぜぞだぢっづでどぱぴぷぺぽゃゅょゎ",
"ぁぃぅぇぉがぎぐげござじずぜぞだぢっでどばびぶべぼぱぴぷぺぽゃゅょゎあいうえおかきくけこさしすせそたちづつてとはひふへほやゆよわ"
];
console.log(lookup);
return lookup[1][lookup[0].indexOf(char)]||char;
};
//$(document).ready(function(){
// loadingScreen.remove is called after page is rendered, so sizes should be set by then
// It is also called when a new question is up, although there is nothing to remove, so we probably don't want to run it the other times
var runCounter = 0;
var kanaMxKbd = {
// Initial status
status: "hidden",
// Add relevant html and css on page load
inject: function(){
console.log("%cKM: %cInjecting Code into DOM", "color:white; background-color: black", "color:red; background-color: black");
$("#reviews").append(constructHTML());
enableKbd();
var oldEvaluate = answerChecker.evaluate;
// TODO Mutation observers
answerChecker.evaluate = function(){
logEvent("answerEvaluate", "Important 'event', but currently shimming when we should probably mutationObserve");
var result = oldEvaluate.apply(answerChecker, arguments);
// Answer submitted, so the field should be disabled (but might not be set yet?)
if (!result.exception && true||$("#user-response")[0].disabled){
console.log("%cAnswer Submitted (reviews)", "background-color:pink");
kanaMxKbd.disableKanaMatrixOnly();
}
return result;
};
// Observe input field
var inputObserver = new MutationObserver(function(mutationsList, observer){
console.log("%cDOM ready for measurement", "background-color:pink");
observer.disconnect();
kanaMxKbd.resize();
});
inputObserver.observe($("#user-response")[0], {attributes:true});
},
// Measure elements and resize divs
resize: function(){
console.log("%cKM: %cMeasuring widths and resizing element components", "color:white; background-color: black", "color:orange; background-color: black");
var usrDivwidth = $("#user-response")[0].scrollWidth;
var usrDivheight = $("#user-response")[0].scrollHeight;
console.log("%cKM: %cResize div cover: " + usrDivwidth + "px x " + usrDivheight + "px", "color:white; background-color: black", "color:orange;");
usrDiv.style.width = usrDivwidth+"px";
usrDiv.style.height = usrDivheight+"px";
svgD.setAttribute("height", usrDivheight);
svgD.setAttribute( "viewBox", "0 0 3 " + usrDivheight);
// Resize the triangles to a percentage of the document width
$(".arrow").each(setTriangleWidths);
//this.contextEnable();
},
// Enable keyboard (question is a reading)
enable: function(){
console.log("%cKM: %cShowing and activating the KanaMatrix", "color:white; background-color: black", "color:yellow; background-color: black");
if ($("#user-response")[0]){
// Hide the native text caret by making it transparent
$("#user-response")[0].style.caretColor = "transparent";
// Remove answerfield from tabIndex
$("#user-response")[0].setAttribute("tabIndex", -1);
}
// Add usrDiv (covering answerfield) to tabIndex
usrDiv.setAttribute("tabIndex", 0);
// enable pointerevents
usrDiv.style.pointerEvents = "";
// Give usrDiv focus to hide native kbd
usrDiv.focus();
// show kanamatrix
this.show();
},
// Answer submitted, don't want any field enabled
disableKanaMatrixOnly: function(){
console.log("%cKM: %cDisabling the KanaMatrix, but not hiding it", "color:white; background-color: black", "color:lightgreen; background-color: black");
kanaMxKbd.hideCaret();
usrDiv.setAttribute("tabIndex", -1);
usrDiv.style.pointerEvents = "none";
},
// Disable keyboard (question is a meaning)
disable: function(){
console.log("%cKM: %cHiding the KanaMatrix", "color:white; background-color: black", "color:lightblue; background-color: black");
if ($("#user-response")[0]){
//Show the native text caret by clearing caretColor
$("#user-response")[0].style.caretColor = "";//so I know it works
// Remove usrDiv from tabIndex
usrDiv.setAttribute("tabIndex", -1);
// Ignore pointerevents
usrDiv.style.pointerEvents = "none";
// Add answerfield back to tabIndex
$("#user-response")[0].setAttribute("tabIndex", 0);
// Give answerfield focus
$("#user-response")[0].focus();
}
//Hide the kanaBoard
this.hide();
},
// Enable or disable based on questionType
contextEnable: function(e){ // String from jStorage indicates what key was changed
console.log("%cKM: %cChecking circumstances to determine which action to take", "color:white; background-color: black", "color:violet; background-color: black");
if ($("#user-response")[0].disabled && e !== curItemKey){ // init was called when an answer has been submitted, probably resized window
console.log("%cCould have been called after a resize, or when questionType changes on lessons: ", "color:darkblue", e);
this.disableKanaMatrixOnly();
}
else if ($.jStorage.get(qTypeKey)==="reading"){
this.enable();
}
else{
this.disable();
}
},
// Reveal keyboard through animation
show: function(){
this.status = "shown";
this.showCaret();
// Named function so it is easy to remove
var transitionCnclFn = function(e){
logEvent(e.type, "transitionCnclFn on "+e.target);
};
if ($("#KMwrapper")[0]){
$("#KMwrapper")[0].style.height = (5 * buttonWithBorder + footerHt) + "px";
// Does nothing, but still should remove when we know whats going on, even with the 'once' setting, it may get added more often than it is executed and removed
$("#KMwrapper")[0].addEventListener("transitioncancel", transitionCnclFn, {
'once':true
});
$("#KMwrapper")[0].addEventListener("transitionend", function(e){
logEvent(e.type, "anonymous function on "+e.target);
$("#KMwrapper")[0].removeEventListener("transitioncancel", transitionCnclFn);
}, {
'once':true
});
}
},
// Hide keyboard through animation
hide: function(){
this.status = "hidden";
this.hideCaret();
if ($("#KMwrapper")[0]){
$("#KMwrapper")[0].style.height = buttonWithBorder + "px";
}
},
showCaret: function(){
caret.style.visibility = "";
},
hideCaret: function(){
caret.style.visibility = "hidden";
}
};
var main = function(e){
logEvent(e.type, "main on "+e.target);
console.log("%cKM: %cmain%c - run once and only once when the quiz is about to start.", "background-color:black; color:white;", "text-decoration:underline", "");
runCounter++;
// Inject only once per page load
if (runCounter === 1){
// Stop mousedowns from blurring the field when we want to keep kanaMatrix up.
document.body.addEventListener("mousedown", function(e){
logEvent(e.type, "anonymous function on "+e.target + ". Aimed at stopping the blur event on the inputfield when the usrDiv is clicked, or the key held down too long. WIP");
//e.preventDefault();
});
kanaMxKbd.inject();
//Assign listeners
$("#user-response")[0].addEventListener('input', updateValue);
var kanaBoard = document.getElementById("kanaBoard");
kanaBoard.addEventListener("touchmove", function(e){
logEvent(e.type, "anonymous function on "+e.target);
e.preventDefault();
}, false);
var kanaButtons = kanaBoard.getElementsByClassName("groupCore");
for (var key in kanaButtons) if (kanaButtons.hasOwnProperty(key)){
initTouchEvent(kanaButtons[key]);
}
var kanaModifyButtonHandler = function(e){
logEvent(e.type, "kanaModifyButtonHandler on "+e.target);
//Get current output (last character)
var output = getOutput(outputField);
var lastChar = output[output.length-1];
if(lastChar){
output = output.substr(0,output.length-1)+rotateChar(lastChar);
setOutput(output, outputField);
}
// prevent event from blurring usrDiv
e.preventDefault();
};
document.getElementById("modify").addEventListener("touchstart", kanaModifyButtonHandler);
var backspaceButtonHandler = function(e){
logEvent(e.type, "backspaceButtonHandler on "+e.target);
//Get current output (last character)
var output = getOutput(outputField);
var lastChar = output[output.length-1];
if(lastChar){
output = output.substr(0,output.length-1);
setOutput(output, outputField);
}
// prevent event from blurring usrDiv
e.preventDefault();
};
document.getElementById("backspace").addEventListener("touchstart", backspaceButtonHandler);
var spaceBarHandler = function(e){
logEvent(e.type, "spaceBarHandler on "+e.target);
//Get current output (last character)
var output = getOutput(outputField);
var lastChar = output[output.length-1];
if(lastChar){
output += " "; //Japanese space char, I don't think it will be used though
setOutput(output, outputField);
}
// prevent event from blurring usrDiv
e.preventDefault();
};
document.getElementById("spacebar").addEventListener("touchstart", spaceBarHandler);
var enterHandler = function(e){
logEvent(e.type, "enterHandler on "+e.target);
e.preventDefault();
return $("#answer-form button").click();
};
document.getElementById("enter").addEventListener("touchstart", enterHandler);
// Disable matrix while in transition
$("#KMwrapper")[0].addEventListener("transitionstart", function(){
logEvent(e.type, "anonymous function on "+e.target);
$("#kanaBoard")[0].style.pointerEvents = "none";
});
// Enable matrix when in place
$("#KMwrapper")[0].addEventListener("transitionend", function(){
logEvent(e.type, "anonymous function on "+e.target);
$("#kanaBoard")[0].style.pointerEvents = "auto";
});
}
else{
console.log("%cKM: Already initialized, this function has been called " + runCounter + " time(s)", "color:grey");
}
};
// Commands that require the DOM to be active
var init = function(){
$("head").append("<style>"+constructCSS()+"</style>");
var lessonOrReview = ($("#reviews").length?"r":$("#lessons").length?"l":false);
if (lessonOrReview === "r"){
// $.jStorage.listenKeyChange("questionType") fires before document.readyState is "complete" in reviews
// Code is therefore not injected yet.
if (document.readyState === 'complete')
main({type:"invoked by code (reviews)"});
else
window.addEventListener('load', main, false);
}
else if (lessonOrReview === "l"){
$("#lessons").append(constructHTML());
curItemKey ="l/currentQuizItem";
qTypeKey ="l/"+qTypeKey;
actQueueKey = "l/"+actQueueKey;
$.jStorage.listenKeyChange(curItemKey, function(e, a){
console.log("KM: initiating, because "+e+ " has just been "+ a);
main({type:"invoked by code (lessons)"});
});
}
};
init();
// Borders don't accept percentages so we need to calculate he pixel width of the buttons ourselves
var setTriangleWidths = function(ix, triElem){
var docWidth = window.document.body.clientWidth;
var numButtons = 4;
var triangleWidth = (docWidth - numButtons * buttonBorderWd * 2) / numButtons / 2;
triElem.style.borderLeft = triangleWidth + "px solid " + ($(triElem).hasClass("arrow-right")? triangleColor : "transparent");
triElem.style.borderRight = triangleWidth + "px solid " + ($(triElem).hasClass("arrow-left")? triangleColor : "transparent");
return triElem;
};
//**** Listeners ****//
// Did the answer field get focus?
usrDiv.addEventListener("focus", function(e){
logEvent(e.type, "anonymous function on "+e.target);
kanaMxKbd.show();
});
console.log("KM: Just added focus listener to ", usrDiv);
// Did the answer field lose focus?
usrDiv.addEventListener("blur", function(e){
logEvent(e.type, "anonymous function on "+e.target);
logEvent(e.type, "relatedTarget "+e.relatedTarget);
// I suspect the lesson code is focusing on the button element after some time, causing a blur on the div,
// check if relatedTarget is the input element and we are in a reading
var lessonOrReview = ($("#reviews").length?"r":$("#lessons").length?"l":false);
if (lessonOrReview === "l"){
if (e.relatedTarget === $("#answer-form button")[0]){
//Take back focus
console.log("KM: Taking back focus from '#answer-form button'");
e.target.focus();
}
else if (e.relatedTarget === $("#user-response")[0]){
//Take back focus
console.log("KM: Taking back focus from '#user-input'");
e.target.focus();
}
else{
kanaMxKbd.hide();
}
}
else{
kanaMxKbd.hide();
}
});
// Listening for currentItem to change as trigger for enabling or disabling the matrix
// In Reviews when first loading, this happens before the elements are given a size to measure
// In Lessons it occurs after the page has
$.jStorage.listenKeyChange(curItemKey, function(e){
logEvent(arguments[1], "anonymous function on "+arguments[0]);
console.log("%cKM:", "color:white; background-color: black", arguments);
// clear span so cursor returns to the centre
answerCopySpan.innerText = "";
kanaMxKbd.contextEnable(e);
});
/* loadingScreen.__remove = loadingScreen.remove;
loadingScreen.remove = function(){
runCounter++;
if (runCounter === 1){
}
return loadingScreen.__remove.apply(this, arguments);
}*/
// });
// fix all the divs on resize (hopefully)
window.addEventListener("resize", function(e){
logEvent(e.type, "anonymous function on "+e.target);
kanaMxKbd.resize();
//enableKbd();
//kanaMxKbd.contextEnable(e);
});
//for testing
window.kanaMxKbd = kanaMxKbd;
})();