Wanikani: Progress Percentages

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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
        )
    }
})()