BishBashBosh+

Extend BishBashBosh's great interface with more review types

// ==UserScript==
// @name         BishBashBosh+
// @namespace    http://tampermonkey.net/
// @version      0.17
// @description  Extend BishBashBosh's great interface with more review types
// @author       Ian Mason
// @match        http://jptools.s3-website-us-east-1.amazonaws.com/bishbashbosh.html
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    var styleSheet = document.createElement("style");
    styleSheet.type = "text/css";
    styleSheet.innerText = "#answer2 {font-size: 3em;width: 15em;text-align: center;} #config { margin-left: 40px; width: 50px; } #oneanddonelabel { margin-left: 40px; }";
    document.head.appendChild(styleSheet);

    var element = document.getElementById('contentSelectorForm');
    element.id = "contentSelectorForm2";
    var whitespace = "    ";
    element.innerHTML = element.innerHTML.concat(whitespace);

    {
        var p = document.createElement("b");
        p.innerHTML = "1&Done";
        p.id = "oneanddonelabel";
        element.appendChild(p);

        var oneanddone = document.createElement("Input");
        oneanddone.setAttribute("type", "checkbox");
        oneanddone.id = "oneanddone";

        element.appendChild(oneanddone);
    }
    var answer = document.getElementById('answer');
    var answer_parent = answer.parentNode;
    answer.style.display = "none";
    var cln = answer.cloneNode(false);
    answer_parent.appendChild(cln);

    var config = document.createElement("input");
    config.placeholder = "ALL";
    var idAttribute = document.createAttribute("id");
    idAttribute.value = "config";
    config.setAttributeNode(idAttribute);
    element.appendChild(config);


    cln.id = "answer";
    answer.id = "answer2";

    function getInputElement(name) {
        var input = document.createElement("Input");
        var idAttribute = document.createAttribute("id");
        idAttribute.value = name;
        var typeAttribute = document.createAttribute("type");
        typeAttribute.value = "radio";
        var nameAttribute = document.createAttribute("name");
        nameAttribute.value = "content";
        var valueAttribute = document.createAttribute("value");
        valueAttribute.value = name;
        input.setAttributeNode(idAttribute);
        input.setAttributeNode(typeAttribute);
        input.setAttributeNode(nameAttribute);
        input.setAttributeNode(valueAttribute);
        return input;
    }
    function getLabelElement(name, visibleName) {
        var label = document.createElement("Label");
        label.innerHTML = visibleName;//"ALL Ancient Gurus and Apprentice";
        var forAttribute = document.createAttribute("for");
        forAttribute.value = name;
        var idAttribute = document.createAttribute("id");
        idAttribute.value = name;
        label.setAttributeNode(forAttribute);
        label.setAttributeNode(idAttribute);
        return label;
    }

    var input = getInputElement("allOldGuruApprentice");
    var label = getLabelElement("allOldGuruApprentice", "ALL ancient guru and apprentice");

    var input2 = getInputElement("randomBurned");
    var label2 = getLabelElement("randomBurned", "ALL random burned");
    element.appendChild(input);
    element.appendChild(label);
    element.appendChild(input2);
    element.appendChild(label2);

    var one_and_done_mode = false;
/*
    var input = document.createElement("Input");
    var idAttribute = document.createAttribute("id");
    idAttribute.value = "test";
    var typeAttribute = document.createAttribute("type");
    typeAttribute.value = "radio";
    var nameAttribute = document.createAttribute("name");
    nameAttribute.value = "content";
    var valueAttribute = document.createAttribute("value");
    valueAttribute.value = "test";

    input.setAttributeNode(idAttribute);
    input.setAttributeNode(typeAttribute);
    input.setAttributeNode(nameAttribute);
    input.setAttributeNode(valueAttribute);
    element.appendChild(input);

    var label = document.createElement("Label");
    label.innerHTML = "ALL Ancient Gurus and Apprentice";
    var forAttribute = document.createAttribute("for");
    forAttribute.value = "test";
    label.setAttributeNode(forAttribute);

    element.appendChild(label);
*/
    console.log('/// Testing');

    //function fetchStudyMaterials() {
    //    console.log("Make useless");
    //    return [];
    //}

    //function fetchSubjects() {
    //    console.log("FetchSubjects overridden");
    //    return [];
    //}

    function fetchAncientThings() {
        // 4 random gurus older than 10 weeks and not seen in in the past 2 days,
        // picked from the oldest 20
        const ageCutoff = new Date(Date.now() - 1000 * 60 * 60 * 24 * 7 * 10);
        const recentCorrectCutoff = new Date(Date.now() - 1000 * 60 * 60 * 24 * 2);
        return pullWholeCollection("assignments?srs_stages=1,2,3,4,5,6").then(list => {
            const output = _(list)
            .sortBy(["data.created_at"])
            .filter(x => new Date(x.data_updated_at) < recentCorrectCutoff)
            .filter(x => ageCutoff > new Date(x.data.started_at))
            .shuffle()
            .take(isNaN(config.value) || config.value == "" ? 99999 : config.value)
            .value();
            console.log(output);
            return output;
        });
    }

    function fetchRandomBurned() {
        return pullWholeCollection("assignments?burned=true").then(list => {
            const output = _(list)
            .shuffle()
            .take(isNaN(config.value) || config.value == "" ? 99999 : config.value).value();
            console.log(output);
            return output;
        });
    }

    function play2(assignments, subjects) {
        const answerField_orig = document.querySelector("#answer");
        answerField_orig.style.display = "none";
        const answerField = document.querySelector("#answer2");
        const questionDiv = document.querySelector("#question");
        const whatToPut = document.querySelector("#whattoput");
        const solution = document.querySelector("#solution");
        const queue = document.querySelector("#queue");
        const result = document.querySelector("#result");
        const tpl = _.template(
            '<span class="label"><%= label %></span><br>' +
            '<span class="kind" style="border-color:<%= colours[object] %>"><%= object %></span>'
        );
        const solTpl = _.template('<h2><%= title %></h2><%= sub.length > 1 ? "<h4>" + sub.join(", ") + "</h4>" : "" %><p><%= body %></p>');

        const PH_MEANING = 1;
        const PH_READING = 2;

        const now = new Date();
        const deck2 = _(assignments).map(a => ({
            score: startingScore,
            perfect: true,
            type: a.data.subject_type,
            subject: subjects[a.data.subject_id],
            availNow: new Date(a.data.available_at) <= now
        })).shuffle().orderBy(["availNow"],["desc"]).value();
        const hand2 = deck2.splice(0, Math.min(initialHandSize, deck2.length));
        console.log(hand2);
        const pile2 = [];
        subjects = null; // free some ram

        let phase = PH_MEANING;
        let hadError = false;
        let curAudio = null;

        function check2() {
            if (phase === PH_READING && !wanakana.isKana(answerField.value)) {
                // catch unfinished n's etc
                answerField.value = wanakana.toKana(answerField.value);
            }
            const card2 = hand2[0], subj2 = card2.subject;
            console.log(subj2);
            const answer2 = _.trim(answerField.value).toLowerCase();
            console.log(answer2);

            if (answer2 === "") { return; }
            if (TESTMODE && answer2==="1" || answer2==="2") {
                return answer2 === "1" ? success2() : fail2();
            }
            if (phase === PH_MEANING) {
                const found = _.find(subj2.accepted_meanings,
                                     m => levenshtein(m, answer2) <= Math.floor(1 + m.length/5));
                return found ? success2() : fail2();
            } else {
                if (subj2.accepted_readings.includes(answer2)) {
                    return success2();
                } else if (subj2.bad_readings.includes(answer2)) {
                    return notQuite2();
                } else {
                    const rom = wanakana.toRomaji(answer2);
                    const foundClose = _.find(subj2.accepted_readings,
                                              m => levenshtein(wanakana.toRomaji(m), rom) <= (m.length < 2 ? 0 : 1));
                    if (foundClose) {
                        return notQuite2();
                    }
                }
                return fail2();
            }
        };
        function maybeAddFronDeck2() {
            const lowestHandScore = _(hand2).map("score").min();
            if (lowestHandScore >= increaseHandThreshold && hand2.length < maxHandSize && deck2.length) {
                hand2.unshift(deck2.pop());
            }
        }
        function animateResult2(isCorrect, score) {
            result.innerHTML = (isCorrect ? goodResults : badResults)[score];
            result.classList.remove("animate");
            void result.offsetWidth; // interesting hack
            result.classList.add("animate");
            result.classList.remove("right");
            result.classList.remove("wrong");
            result.classList.add(isCorrect ? "right" : "wrong");
        }
        function success2() {
            const card = hand2[0], subj = card.subject;
            animateResult2(true, card.score);
            if (subj.object === "radical" || phase === PH_READING) {
                if (curAudio) { curAudio.play(); }
                if (hadError) {
                    hand2.splice(1, 0, hand2.shift());
                } else {
                    card.score += card.perfect ? one_and_done_mode ? discardScore : 2 : 1;
                    const enoughStillInPlay = deck2.length > 0 || hand2.length > initialHandSize;
                    const allDone = _(hand2).map("score").min() >= discardScore;
                    if (card.score >= discardScore) {
                        if (!enoughStillInPlay) {
                            if (_(hand2).map("score").min() >= discardScore) {
                                // discard all
                                while (hand2.length) {
                                    pile2.push(hand2.shift());
                                }
                            } else {
                                // rotate to back
                                hand2.push(hand2.shift());
                            }
                        } else {
                            // discard
                            pile2.push(hand2.shift());
                        }
                    } else {
                        // move back in hand
                        hand2.splice(Math.min(hand2.length-1, card.score*3), 0, hand2.shift());
                    }
                    maybeAddFronDeck2();
                }
                hadError = false;
                phase = PH_MEANING;
            } else {
                phase = PH_READING;
            }
            display2();
        }
        function notQuite2() {
            const card = hand2[0];
            card.perfect = false;
            solution.innerHTML = "<h2>Not quite...</h2>";
            solution.style.display = "block";
            answerField.value = "";
        }
        function fail2() {
            const card2 = hand2[0], subj2 = card2.subject;
            animateResult2(false, card2.score);
            if (phase === PH_MEANING) {
                solution.innerHTML = solTpl({title: subj2.primary_meanings, sub: subj2.accepted_meanings, body: subj2.meaning_mnemonic});
            } else {
                if (curAudio) { curAudio.play(); };
                solution.innerHTML = solTpl({title: subj2.primary_readings, sub: subj2.accepted_readings, body: subj2.reading_mnemonic});
            }
            if (!hadError) {
                card2.score = Math.max(0, card2.score - 2);
                card2.perfect = false;
            }
            solution.style.display = "block";
            answerField.value = "";
            answerField.placeholder = "Nope :(    Try again...";
            hadError = true;
        }
        function display2() {
            const card = hand2[0];
            if (!card) {
                answerField.style.display = "none";
                whatToPut.style.display = "none";
                questionDiv.innerHTML = tpl({label:"BOSH!",object:""});
                document.querySelector("#info").style.display = "block";
                redrawQueue2();
                return;
            }
            questionDiv.innerHTML = tpl(card.subject);
            switchInputMode2(phase === PH_READING);
            whatToPut.innerHTML = phase === PH_READING ? "読み方" : "MEANING";
            answerField.value = "";
            answerField.placeholder = "";
            answerField.style.display = "inline-block";
            whatToPut.style.display = "block";
            solution.style.display = "none";
            curAudio = _.get(card, "subject.audio.length", 0) ? new Audio(_.sample(card.subject.audio)) : null;
            redrawQueue2();
            answerField.focus();
        }
        function redrawQueue2() {
            const style = score => {
                const offset = Math.round(score / discardScore * 55);
                return 'style="background-color: rgb(' + (255-offset) + ',' + (200+offset) + ',200);"';
            };
            queue.innerHTML = '<strong style="color:#2c2;">' + pile2.length + "</strong> + " +
                _.map(hand2, c => "<span " + style(c.score) + ">" + c.subject.label + "</span>").join("") +
                ' + <strong style="color:#f22;">' + deck2.length + "</strong>";
        }

        function switchInputMode2(isKana) {
            try {
                wanakana.unbind(answerField);
            } catch (err) {
                // fails if not bound already and I'm lazy
            }
            if (isKana) {
                wanakana.bind(answerField, {
                    IMEMode: true
                });
            }
        }

        answerField.addEventListener("keydown", ev => {
            if (ev.keyCode !== 13) { return; }
            check2();
        });

        display2();
    }

    document.querySelector("#contentSelectorForm2").addEventListener("change", ev => {
        if(ev.target.id == "oneanddone")
        {
            one_and_done_mode = !one_and_done_mode;
            return;
        }
        if(ev.target.id == "config")
        {
            if(ev.target.value == "")
            {
                label.innerHTML = "ALL ancient guru and apprentice";
                label2.innerHTML = "ALL random burned";
            }
            else
            {
                label.innerHTML = ev.target.value.toString() + " ancient guru and apprentice";
                label2.innerHTML = ev.target.value.toString() + " random burned";
            }
            return;
        }
        const content = usableFormData("contentSelectorForm2").content;
        storeLocal("content", content);
        document.querySelector("#loading").style.display = "block";
        document.querySelector("#main").style.display = "none";

        isLoading = true;

        const selectedContent = () => {
            switch (content) {
                case "apprentice1":
                    var lvl = queryStr().srsLevelOverride || "1";
                    return pullWholeCollection("assignments?srs_stages=" + lvl);
                case "recentlyFailed":
                    return fetchRecentlyFailed();
                case "oldestApprentices":
                    return fetchTenOldestApprentices();
                case "allThatStuff":
                    return fetchAllThatStuff();
                case "plusGurus":
                    return fetchAllThatStuffPlusGurus();
                case "allOldGuruApprentice":
                    return fetchAncientThings();
                case "randomBurned":
                    return fetchRandomBurned();
            }
        }

        Promise.all([selectedContent(), fetchSubjects(), fetchStudyMaterials()]).then(
            ([assignments, subjects, sm]) => {
                console.log(assignments);
                console.log(subjects);
                console.log(sm);
                subjects.forEach(s => {
                    const extra = sm[s.id];
                    if (extra) {
                        s.accepted_meanings = s.accepted_meanings.concat(extra.accepted_meanings);
                        s.meaning_mnemonic = extra.meaning_mnemonic + s.meaning_mnemonic;
                        s.reading_mnemonic = extra.reading_mnemonic + s.reading_mnemonic;
                    }
                });

                document.querySelector("#loading").style.display = "none";
                isLoading = false;

                if (!assignments || assignments.length < 1) {
                    window.alert("Nothing found to cram!");
                    document.querySelector("#info").style.display = "block";
                    return;
                }

                document.querySelector("#main").style.display = "block";
                console.log("PLAY");
                play2(assignments, subjects);
            }
        );
    });

})();