Wanikani: Dashboard Apprentice

Displays all your apprentice items on the dashboard

As of 2019-11-25. See the latest version.

// ==UserScript==
// @name         Wanikani: Dashboard Apprentice
// @namespace    http://tampermonkey.net/
// @version      1.0.3
// @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==

(function() {
    // Make sure WKOF is installed
    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('ItemData');
        wkof.ready('ItemData')
            .then(fetch_items)
            .then(get_apprentice)
            .then(display);
        add_css();
    }

    // Fetches the items
    function fetch_items() {
        var [promise, resolve] = new_promise();
        wkof.ItemData.get_items('assignments')
            .then(data=>resolve(data))
        return promise;
    }

    // Returns the apprentice items divided by SRS level
    function get_apprentice(data) {
        var apprentice = {1: [], 2: [], 3: [], 4: []};
        for (var i=0; i<data.length; i++) {
            var item = data[i];
            if (item.assignments) {
                var srs_level = item.assignments.srs_stage;
                if (srs_level < 5 && srs_level > 0) {
                    apprentice[srs_level].push(item);
                }
            }
        }
        return apprentice;
    }

    // Puts the information on the dashboard
    function display(data) {
        var elem = $('<div id="wkda_items"></div>')[0];
        if (is_dark_theme()) elem.className = 'dark';
        for (var i=1; i<5; i++) {
            var srs_elem = $('<div class="apprentice_'+i+'"></div>')[0];
            var title = $('<span>Apprentice '+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];
                if (item.data.characters === null) console.log(item);
                var info = {
                    type: item.object,
                    characters: item.data.characters,
                    meanings: [],
                    readings: [],
                    level: item.data.level,
                    available: 0,
                    url: item.data.document_url,
                    svg: (item.data.characters === null ? item.data.character_images[1].url : null),
                    available: ((Date.parse(item.assignments.available_at) < Date.now() ? 'Now' : s_to_dhm((Date.parse(item.assignments.available_at)-Date.now())/1000)))
                };
                for (var k=0; k<item.data.meanings.length; k++) {
                    info.meanings.push(item.data.meanings[k].meaning);
                }
                if (item.data.readings) {
                    for (var k=0; k<item.data.readings.length; k++) {
                        info.readings.push(item.data.readings[k].reading);
                    }
                }
                var adjust_left = false;
                if (event.pageX > window.innerWidth/2) adjust_left = true;
                var item_elem = $('<div class="item '+info.type+'"'+(adjust_left ? ' style"transform: translateX(-100%);"' : '')+'>'+
                    '<div class="hover_elem">'+
                        '<div class="left">'+
                            '<a class="'+info.type+'" href="'+info.url+'">'+(info.characters === null ? '<img src="'+info.svg+'">' : 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 === null ? '<img src="'+info.svg+'">' : info.characters)+'</a>'+
                '</div>')[0];
                items.appendChild(item_elem);
            }
            elem.appendChild(srs_elem);
        }
        document.getElementsByClassName('progression')[0].appendChild(elem)

    }

    // Adds the CSS to the page
    function add_css() {
        $('head').append('<style id="wkda_css">'+
                            '#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 4px;'+
                            '    margin: 1.5px;'+
                            '    border-radius: 3px;'+
                            '    position: relative;'+
                            '}'+
                            '#wkda_items .items .radical {'+
                            '    background: #0096e7;'+
                            '    order: 0;'+
                            '}'+
                            '#wkda_items .items .kanji {'+
                            '    background: #ff00aa;'+
                            '    order: 1;'+
                            '}'+
                            '#wkda_items .items .vocabulary {'+
                            '    background: #9800e8;'+
                            '    order: 2;'+
                            '}'+
                            '#wkda_items.dark .items .radical {'+
                            '    background: #3daee9;'+
                            '}'+
                            '#wkda_items.dark .items .kanji {'+
                            '    background: #fdbc4b;'+
                            '}'+
                            '#wkda_items.dark .items .vocabulary {'+
                            '    background: #2ecc71;'+
                            '}'+
                            '#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;'+
                            '    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 img {'+
                            '    height: 14px;'+
                            '}'+
                            '#wkda_items:not(.dark) .items .radical img {'+
                            '    filter: invert(1);'+
                            '}'+
                            '#wkda_items .items .radical .hover_elem img {'+
                            '    height: 74px;'+
                            '}'+
                            '#wkda_items .right table td:first-child {'+
                            '    padding-right: 10px;'+
                            '    font-weight: bold;'+
                            '}'+
                            '#wkda_items .items table td,'+
                            '#wkda_items .items > div a {'+
                            '    color: rgb(240, 240, 240) !important;'+
                            '}'+
                            '#wkda_items.dark .items > div a {'+
                            '    color: black !important;'+
                            '}'+
                        '</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;
  }
})();