Wanikani: Progress Percentages

Calculates the percentage of known kanji for each JLPT level, Joyo grade, Frequency bracket, and various other sources.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Wanikani: Progress Percentages
// @namespace    http://tampermonkey.net/
// @version      1.2.8
// @description  Calculates the percentage of known kanji for each JLPT level, Joyo grade, Frequency bracket, and various other sources.
// @author       Kumirei
// @include      /^https://(www|preview).wanikani.com/(dashboard)?$/
// @require      https://greasyfork.org/scripts/377613-wanikani-open-framework-jlpt-joyo-and-frequency-filters/code/Wanikani%20Open%20Framework%20JLPT,%20Joyo,%20and%20Frequency%20filters.user.js
// @license      MIT; http://opensource.org/licenses/MIT
// @grant        none
// ==/UserScript==

;(function () {
    // Make sure WKOF is installed
    var wkof = window.wkof
    if (!wkof) {
        var response = confirm(
            'Wanikani: JLPT Progress requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.',
        )
        if (response)
            window.location.href =
                'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549'
        return
    } else {
        // Install menu
        wkof.include('Menu,Settings')
        wkof.ready('Menu,Settings').then(load_settings).then(install_menu)

        // Initiate progress variable
        var progress = {
            jlpt: {
                1: { learned: 0, total: 1232 },
                2: { learned: 0, total: 367 },
                3: { learned: 0, total: 367 },
                4: { learned: 0, total: 166 },
                5: { learned: 0, total: 79 },
            },
            joyo: {
                1: { learned: 0, total: 80 },
                2: { learned: 0, total: 160 },
                3: { learned: 0, total: 200 },
                4: { learned: 0, total: 200 },
                5: { learned: 0, total: 185 },
                6: { learned: 0, total: 181 },
                9: { learned: 0, total: 1130 },
            },
            freq: {
                500: { learned: 0, total: 500 },
                1000: { learned: 0, total: 500 },
                1500: { learned: 0, total: 500 },
                2000: { learned: 0, total: 500 },
                2500: { learned: 0, total: 500 },
            },
            other: {
                nhk: { learned: 0, total: 0 },
                news: { learned: 0, total: 0 },
                aozora: { learned: 0, total: 0 },
                twitter: { learned: 0, total: 0 },
                wikipedia: { learned: 0, total: 0 },
            },
        }

        // Fetch lesson info then process it
        wkof.include('ItemData')
        wkof.ready('ItemData').then(update_progress).then(calculate_percentages).then(display_data)
    }

    // Loads settings
    function load_settings() {
        var defaults = { cumulative: false, threshold: 1, position: 'top' }
        wkof.Settings.load('progress_percentages', defaults)
    }

    // Installs the options button in the menu
    function install_menu() {
        var config = {
            name: 'progress_percentages_settings',
            submenu: 'Settings',
            title: 'Progress Percentages',
            on_click: open_settings,
        }
        wkof.Menu.insert_script_link(config)
    }

    // Creates the options
    function open_settings(items) {
        var config = {
            script_id: 'progress_percentages',
            title: 'Progress Percentages',
            content: {
                cumulative: {
                    type: 'checkbox',
                    label: 'Cumulative percentages',
                    hover_tip: 'Eg. N3 = N3 + N4 + N4',
                    default: false,
                },
                threshold: {
                    type: 'list',
                    label: 'Learned threshold',
                    hover_tip: 'Items at or above this SRS level will be counted as learned',
                    multi: false,
                    size: 9,
                    default: '1',
                    content: {
                        1: 'Apprentice 1',
                        2: 'Apprentice 2',
                        3: 'Apprentice 3',
                        4: 'Apprentice 4',
                        5: 'Guru 1',
                        6: 'Guru 2',
                        7: 'Master',
                        8: 'Enlightened',
                        9: 'Burned',
                    },
                },
                position: {
                    type: 'dropdown',
                    label: 'Position',
                    hover_tip: 'Position of the Progress Percentages element on the dashboard',
                    default: 'search',
                    content: {
                        top: 'Top of page',
                        below_srs: 'Below SRS boxes',
                    },
                    on_change: (setting, value) => {
                        let elem = $('.progress_percentages')
                        elem.toggleClass('span12', value == 'top')
                        if (value == 'top') $('#search-form').before(elem)
                        if (value == 'below_srs') $('.srs-progress').append(elem)
                    },
                },
            },
            on_save: () => {
                progress = {
                    jlpt: {
                        1: { learned: 0, total: 1232 },
                        2: { learned: 0, total: 367 },
                        3: { learned: 0, total: 367 },
                        4: { learned: 0, total: 166 },
                        5: { learned: 0, total: 79 },
                    },
                    joyo: {
                        1: { learned: 0, total: 80 },
                        2: { learned: 0, total: 160 },
                        3: { learned: 0, total: 200 },
                        4: { learned: 0, total: 200 },
                        5: { learned: 0, total: 185 },
                        6: { learned: 0, total: 181 },
                        9: { learned: 0, total: 1130 },
                    },
                    freq: {
                        500: { learned: 0, total: 500 },
                        1000: { learned: 0, total: 500 },
                        1500: { learned: 0, total: 500 },
                        2000: { learned: 0, total: 500 },
                        2500: { learned: 0, total: 500 },
                    },
                    other: {
                        nhk: { learned: 0, total: 0 },
                        news: { learned: 0, total: 0 },
                        aozora: { learned: 0, total: 0 },
                        twitter: { learned: 0, total: 0 },
                        wikipedia: { learned: 0, total: 0 },
                    },
                }
                update_progress().then(calculate_percentages).then(update_element)
            },
        }
        var dialog = new wkof.Settings(config)
        dialog.open()
    }

    // Updates element
    function update_element(percentages) {
        for (var key in percentages) {
            for (var level in percentages[key]) {
                var stage = key == 'jlpt' ? 6 - level : level
                var elem = $('#' + key + '_' + stage)[0]
                elem.title =
                    percentages[key][stage].learned +
                    (key != 'other' ? ' of ' + percentages[key][stage].total : '') +
                    ' learned'
                elem.children[1].innerText = percentages[key][stage].percent + '%'
            }
        }
    }

    // Retreives lesson data
    function update_progress() {
        var resolve,
            promise = new Promise((res, rej) => {
                resolve = res
            })
        var config = {
            wk_items: {
                options: { assignments: true },
                filters: {
                    item_type: 'kan',
                    include_jlpt_data: true,
                    include_joyo_data: true,
                    include_frequency_data: true,
                },
            },
        }
        wkof.ItemData.get_items(config).then((data) => {
            for (var key in data) {
                if (data[key].assignments && data[key].assignments.started_at != null) {
                    var keys = [
                        ['jlpt_level', 'jlpt'],
                        ['joyo_grade', 'joyo'],
                        ['frequency', 'freq'],
                        ['nhk_frequency', 'nhk'],
                        ['news_frequency', 'news'],
                        ['aozora_frequency', 'aozora'],
                        ['twitter_frequency', 'twitter'],
                        ['wikipedia_frequency', 'wikipedia'],
                    ]
                    keys.forEach((val, i) => {
                        var level = data[key][val[0]]
                        if (level && data[key].assignments.srs_stage >= wkof.settings.progress_percentages.threshold) {
                            if (level < 1) {
                                progress.other[val[1]].learned++
                                progress.other[val[1]].total += level
                            } else progress[val[1]][level].learned++
                        }
                    })
                }
            }
            resolve()
        })
        return promise
    }

    function calculate_percentages() {
        var show_cum = wkof.settings.progress_percentages.cumulative
        var percentages = {
            jlpt: { 1: {}, 2: {}, 3: {}, 4: {}, 5: {} },
            joyo: { 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 9: {} },
            freq: { 500: {}, 1000: {}, 1500: {}, 2000: {}, 2500: {} },
            other: { nhk: {}, news: {}, aozora: {}, twitter: {}, wikipedia: {} },
        }
        for (var key in percentages) {
            var cumulative = [0, 0]
            for (var level in percentages[key]) {
                var stage = key == 'jlpt' ? 6 - level : level
                var learned = progress[key][stage].learned
                var total = progress[key][stage].total
                cumulative[0] += learned
                cumulative[1] += total
                let percent
                if (key != 'other') percent = show_cum ? cumulative[0] / cumulative[1] : learned / total
                else percent = total
                percent = percent < 0.1 ? Math.floor(percent * 1000) / 10 : Math.floor(percent * 100)
                percentages[key][stage].percent = percent
                percentages[key][stage].learned = show_cum ? cumulative[0] : learned
                percentages[key][stage].total = show_cum ? cumulative[1] : total
            }
        }
        return percentages
    }

    function display_data(percentages) {
        // Add css
        $('head').append(`<style id="progress_percentages">
    .progress_percentages {
        display: flex;
        height: 28px;
        background: #434343;
        color: rgb(240, 240, 240);
        line-height: 28px;
        //margin-bottom: 0;
        border-radius: 5px;
        text-align: center;
        grid-row: 1;
        grid-column: 1 / span 2;
        //margin-top: 15px;
    }
    .srs-progress .progress_percentages {
        margin-top: 5px;
    }
    #search-form {
        grid-row: 1;
    }
    .progress_percentages .PPprogress {
        display: flex;
        width: 100%;
        justify-content: space-around;
    }
    .progress_percentages .PPbtn {
        width: 20px;
        height: auto;
        color: rgb(240,240,240);
        padding: 0 5px;
        cursor: pointer;
        font-size: 16px;
    }
    .progress_percentages .level {
        font-weight: bold;
    }
    .progress_percentages .percent {
        font-weight: normal !important;
    }
    .progress_percentages span {
        font-size: 16px !important;
        display: inline !important;
    }
    </style>`)
        if (is_dark_theme()) {
            $('head').append(`<style id="progress_percentages_dark">
    .progress_percentages {
        box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7), 2px 2px 2px rgba(0, 0, 0, 0.7);
    }
    .progress_percentages > div {
        background: #232629 !important;
    }
    </style>`)
        }
        // Add elements
        var section = document.createElement('section')
        section.className = 'progress_percentages'

        var active_set = localStorage.getItem('WKProgressPercentagesActiveSet') || 'jlpt'
        var [next, prev] = get_new_sets(active_set)

        var next_button = document.createElement('div')
        next_button.className = 'next PPbtn'
        next_button.innerHTML = '<i class="link icon-chevron-right"></i>'
        next_button.onclick = toggle_percentages
        next_button.current = active_set
        next_button.next = next

        var prev_button = document.createElement('div')
        prev_button.className = 'prev PPbtn'
        prev_button.innerHTML = '<i class="link icon-chevron-left"></i>'
        prev_button.onclick = toggle_percentages
        prev_button.current = active_set
        prev_button.next = prev

        var list = document.createElement('div')
        list.className = 'PPprogress'
        for (var key in percentages) {
            for (var level in percentages[key]) {
                var stage = key == 'jlpt' ? 6 - level : level
                var prefix = key == 'jlpt' ? 'N' : key == 'joyo' ? 'G' : key == 'freq' ? 'F' : ''
                var label =
                    key == 'other'
                        ? stage == 'nhk'
                            ? 'NHK'
                            : stage.charAt(0).toUpperCase() + stage.slice(1)
                        : key == 'freq'
                        ? stage / 500
                        : stage
                $(list).append(
                    '<div class="' +
                        key +
                        '_percentages stage ' +
                        (key == active_set ? '' : 'hidden') +
                        '" id="' +
                        key +
                        '_' +
                        stage +
                        '" title="' +
                        percentages[key][stage].learned +
                        (key != 'other' ? ' of ' + percentages[key][stage].total : '') +
                        ' learned"><span class="level">' +
                        prefix +
                        label +
                        ' </span><span class="percent">' +
                        percentages[key][stage].percent +
                        '%</span></div>',
                )
            }
        }
        section.appendChild(prev_button)
        section.appendChild(list)
        section.appendChild(next_button)
        if (wkof.settings.progress_percentages.position == 'top') {
            //section.className += ' span12'
            $('.progress-and-forecast').before(section)
        } else if (wkof.settings.progress_percentages.position == 'below_srs') $('.srs-progress').append(section)
        else $('.progress-and-forecast').before(section)
    }

    // Switches which percentages are showing
    function toggle_percentages(event) {
        var button = event.target
        if (button.nodeName == 'I') button = button.parentElement
        var current_set = button.current
        var next_set = button.next
        $('.' + current_set + '_percentages').toggleClass('hidden')
        $('.' + next_set + '_percentages').toggleClass('hidden')
        var next_button = $('.progress_percentages .next')[0]
        var prev_button = $('.progress_percentages .prev')[0]
        var [next, prev] = get_new_sets(next_set)
        next_button.next = next
        next_button.current = next_set
        prev_button.next = prev
        prev_button.current = next_set
        localStorage.setItem('WKProgressPercentagesActiveSet', next_set)
    }

    // Returns the next and previous sets
    function get_new_sets(current_set) {
        var sets = ['jlpt', 'joyo', 'freq', 'other']
        var current_index = sets.indexOf(current_set)
        return [sets[(current_index + 1) % 4], sets[(current_index + 3) % 4]]
    }

    // Handy little function that rfindley wrote. Checks whether the theme is dark.
    function is_dark_theme() {
        // Grab the <html> background color, average the RGB.  If less than 50% bright, it's dark theme.
        return (
            $('body')
                .css('background-color')
                .match(/\((.*)\)/)[1]
                .split(',')
                .slice(0, 3)
                .map((str) => Number(str))
                .reduce((a, i) => a + i) /
                (255 * 3) <
            0.5
        )
    }
})()