Wanikani: Dashboard Apprentice

Displays all your apprentice items on the dashboard

2023-05-17 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name         Wanikani: Dashboard Apprentice
// @namespace    http://tampermonkey.net/
// @version      1.2.1
// @description  Displays all your apprentice items on the dashboard
// @author       Kumirei
// @match        https://www.wanikani.com/
// @match        https://www.wanikani.com/dashboard
// @match        https://preview.wanikani.com/
// @match        https://preview.wanikani.com/dashboard
// @grant        none
// ==/UserScript==
/*jshint esversion: 8 */

(function() {
    // Make sure WKOF is installed
    let script_id = "dashboard_apprentice";
    if (!wkof) {
        var script_name = 'Wanikani: Dashboard Apprentice';
        var response = confirm(script_name + ' 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;
    }
    // Ready to go
    else {
        wkof.include('Menu,Settings,ItemData');
        wkof.ready('Menu,Settings,ItemData')
            .then(load_settings)
            .then(install_menu)
            .then(add_css)
            .then(fetch_items)
            .then(display);
    }

    function install_menu() {
        let config = {
            name: script_id,
            submenu: 'Settings',
            title: 'Dashboard Apprentice',
            on_click: open_settings
        };
        wkof.Menu.insert_script_link(config);
    }
    function open_settings() {
        var config = {
            script_id: script_id,
            title: 'Dashboard Apprentice',
            content: {
                theme: {type: 'dropdown', label: 'Theme', default: 0, hover_tip: 'Changes the colors of the items', content: {0: "Default", 1: "Breeze Dark",},},
                srs_start: {type: 'number', label: 'First SRS stage', default: 1, hover_tip: 'First SRS stage to display.\n-1: Locked items\n0: Items in your lessons\n1-4: Apprentice\n5-6: Guru\n7: Master\n8: Enlightened\n9: Burned',},
                srs_end: {type: 'number', label: 'Last SRS stage', default: 4, hover_tip: 'Last SRS stage to display.\n-1: Locked items\n0: Items in your lessons\n1-4: Apprentice\n5-6: Guru\n7: Master\n8: Enlightened\n9: Burned',},
                types: {type: 'list', label: 'Item types', multi: true, hover_tip: 'Which items you want to display', default: {rad: true, kan: true, voc: true}, content: {rad: 'Radicals', kan: 'Kanji', voc: 'Vocabulary'},},
            },
        };
        let dialog = new wkof.Settings(config);
        dialog.open();
    }

    function load_settings() {
        let defaults = {theme: 0, srs_start: 1, srs_end: 4, types: {rad: true, kan: true, voc: true}};
        return wkof.Settings.load(script_id, defaults);
    }

    // Fetches the items
    async function fetch_items() {
        let types = Object.entries(wkof.settings[script_id].types).filter(a=>a[1]).map(a=>a[0]);
        return wkof.ItemData.get_index(await wkof.ItemData.get_items({wk_items: {options: {assignments: true},filters: {item_type: types}}}), 'srs_stage');
    }

    // Puts the information on the dashboard
    async function display(data) {
        let names = {"-1": 'Locked', 0: 'Lessons', 1: 'Apprentice 1', 2: 'Apprentice 2', 3: 'Apprentice 3', 4: 'Apprentice 4', 5: 'Guru 1', 6: 'Guru 2', 7: 'Master', 8: 'Enlightened', 9: 'Burned'};
        var elem = $('<div id="wkda_items"></div>')[0];
        if (is_dark_theme()) elem.className = 'dark';
        let settings = wkof.settings[script_id];
        for (var i=settings.srs_start; i<=settings.srs_end; i++) {
            if (!data[i]) continue;
            var srs_elem = $('<div class="apprentice_'+i+'"></div>')[0];
            var title = $('<span>'+names[i]+' </span>')[0];
            var items = $('<div class="items"></div>')[0];
            srs_elem.appendChild(title);
            srs_elem.appendChild(items);
            for (var j=0; j<data[i].length; j++) {
                var item = data[i][j];
                var info = {
                    type: item.object,
                    characters: item.data.characters !== null ? item.data.characters : await wkof.load_file(item.data.character_images.find(c => c.content_type === "image/svg+xml" && !c.metadata.inline_styles).url, true),
                    meanings: [],
                    readings: [],
                    level: item.data.level,
                    url: item.data.document_url,
                    available: (i==-1?'Locked':i==0?'In lesson queue':item.assignments.srs_stage==9?'Burned':(Date.parse(item.assignments.available_at) < Date.now() ? 'Now' : s_to_dhm((Date.parse(item.assignments.available_at)-Date.now())/1000)))
                };
                for (let k=0; k<item.data.meanings.length; k++) {
                    info.meanings.push(item.data.meanings[k].meaning);
                }
                if (item.data.readings) {
                    for (let k=0; k<item.data.readings.length; k++) {
                        info.readings.push(item.data.readings[k].reading);
                    }
                }
                var item_elem = $('<div class="item '+info.type+'"'+'>'+
                    '<div class="hover_elem">'+
                        '<div class="left">'+
                            '<a class="'+info.type+'" href="'+info.url+'">'+info.characters+'</a>'+
                        '</div>'+
                        '<div class="right">'+
                            '<table>'+
                                '<tr><td>Meanings</td><td>'+info.meanings.join(', ')+'</td></tr>'+
                                '<tr><td>Readings</td><td>'+info.readings.join('、')+'</td></tr>'+
                                '<tr><td>Level</td><td>'+info.level+'</td></tr>'+
                                '<tr><td>Available</td><td>'+info.available+'</td></tr>'+
                            '</table>'+
                        '</div>'+
                    '</div>'+
                    '<a class="'+info.type+'" href="'+info.url+'">'+info.characters+'</a>'+
                '</div>')[0];
                items.appendChild(item_elem);
            }
            elem.appendChild(srs_elem);
        }
        let target = document.querySelector('.span12 > .row');
        target.parentElement.insertBefore(elem, target);

    }

    // Adds the CSS to the page
    function add_css() {
        let theme = wkof.settings[script_id].theme;
        $('head').append('<style id="wkda_css">'+
                            '#wkda_items {'+
                            '    background-color: #f4f4f4;'+
                            '    border-radius: 5px;'+
                            '    padding: 16px 24px 12px;'+
                            '}'+
                            '#wkda_items.dark {'+
                            '    background-color: #232629;'+
                            '}'+
                            '#wkda_items > div {'+
                            '    margin-bottom: 10px;'+
                            '}'+
                            '#wkda_items > div > span {'+
                            '    font-size: 16px;'+
                            '}'+
                            '#wkda_items .items {'+
                            '    position: relative;'+
                            '    display: flex;'+
                            '    flex-direction: row;'+
                            '    flex-wrap: wrap;'+
                            '    justify-content: flex-start;'+
                            '    margin-left: -2px;'+
                            '}'+
                            '#wkda_items .items .item {'+
                            '    display: inline-block;'+
                            '    padding: 0 3px;'+
                            '    margin: 1.5px;'+
                            '    border-radius: 3px;'+
                            '    position: relative;'+
                            '}'+
                            '#wkda_items .items .radical {'+
                            '    background: '+['#0096e7', '#3daee9'][theme]+';'+
                            '    order: 0;'+
                            '    width: 14px;'+
                            '}'+
                            '#wkda_items .items .kanji {'+
                            '    background: '+['#ff00aa', '#fdbc4b'][theme]+';'+
                            '    order: 1;'+
                            '}'+
                            '#wkda_items .items .vocabulary {'+
                            '    background: '+['#9800e8', '#2ecc71'][theme]+';'+
                            '    order: 2;'+
                            '}'+
                            '#wkda_items .hover_elem {'+
                            '    visibility: hidden;'+
                            '    position: absolute;'+
                            '    background-color: rgba(0, 0, 0, 0.9);'+
                            '    z-index: 2;'+
                            '    padding: 5px;'+
                            '    border-radius: 3px;'+
                            '    width: max-content;'+
                            '    transform: translate(-50%, calc(0px - 100% - 5px));'+
                            '    left: 50%;'+
                            '}'+
                            '#wkda_items .item:hover .hover_elem {'+
                            '    visibility: visible; '+
                            '}'+
                            '#wkda_items .hover_elem::after {'+
                            '    visibility: hidden;'+
                            '    position: absolute;'+
                            '    width: 0;'+
                            '    border-top: 5px solid rgba(0, 0, 0, 0.9);'+
                            '    border-right: 5px solid transparent;'+
                            '    border-left: 5px solid transparent;'+
                            '    content: " ";'+
                            '    font-size: 0;'+
                            '    line-height: 0;'+
                            '    left: 50%;'+
                            '    bottom: -5px;'+
                            '    transform: translateX(-50%);'+
                            '}'+
                            '#wkda_items .item:hover .hover_elem::after {'+
                            '    visibility: visible;'+
                            '}'+
                            '#wkda_items .hover_elem > div {'+
                            '    display: inline-block;'+
                            '}'+
                            '#wkda_items .item.vocabulary .hover_elem > div {'+
                            '    display: block;'+
                            '}'+
                            '#wkda_items .left {'+
                            '    vertical-align: top;'+
                            '}'+
                            '#wkda_items .item.vocabulary .hover_elem .left {'+
                            '    margin-bottom: 5px;'+
                            '}'+
                            '#wkda_items .left a {'+
                            '    font-size: 74px;'+
                            '    line-height: 73px;'+
                            '    min-width: 73px;'+
                            '    display: block;'+
                            '    padding: 5px;'+
                            '    border-radius: 3px;'+
                            '    margin: 3px 10px 0 3px;'+
                            '}'+
                            '#wkda_items .item.vocabulary .left a {'+
                            '    margin-right: 3px;'+
                            '    text-align: center;'+
                            '}'+
                            '#wkda_items .items .radical svg {'+
                            '    height: 14px;'+
                            '    stroke: currentColor;'+
                            '    fill: none;'+
                            '    stroke-linecap: square;'+
                            '    stroke-width: 68;'+
                            '}'+
                            '#wkda_items .items .radical svg g {'+
                            '    clip-path: none;'+
                            '}'+
                            '#wkda_items .items .radical .hover_elem svg {'+
                            '    height: 74px;'+
                            '    width: 1em;'+
                            '}'+
                            '#wkda_items .right table td:first-child {'+
                            '    padding-right: 10px;'+
                            '    font-weight: bold;'+
                            '}'+
                            '#wkda_items .items table td {'+
                            '    color: rgb(240, 240, 240);'+
                            '}'+
                            '#wkda_items .items > div a {'+
                            '    color: '+['rgb(240, 240, 240)', 'black'][theme]+' !important;'+
                            '}'+
                            '#wkda_items .item.vocabulary .hover_elem {'+
                            '	max-width: 320px;'+
                            '}'+
                        '</style>');
    }

    // Converts seconds to days, hours, and minutes
    function s_to_dhm(s) {
        var d = Math.floor(s/60/60/24);
        var h = Math.floor(s%(60*60*24)/60/60);
        var m = Math.ceil(s%(60*60*24)%(60*60)/60);
        return (d>0?d+'d ':'')+(h>0?h+'h ':'')+(m>0?m+'m':'1m');
    }

   // Returns a promise and a resolve function
   function new_promise() {
       var resolve, promise = new Promise((res, rej)=>{resolve = res;});
       return [promise, resolve];
   }

  // 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;
  }
})();