[Userscript] Dashboard Level Progress Detail. update to broken script

Hi thanks for this script it’s really nice to see which levels you have trouble with and what your progress is.

I’ve just reached level 60 and the script has stopped working, it throws an Empty string passed to getElementById() error. I think it’s just because there’s nothing to display anymore… I’ll deactivate it but wanted to tell you about this marginal bug.

Could I ask you to also add a 50% marker?

Hi, sorry but no. I don’t personally want a 50% marker for myself and even getting the 90% marker was a lot of work because I’m not good at this sort of thing. I did it purely for myself and then shared with everyone else as an afterthought. Sorry I couldn’t help you further.

2 Likes

That’s fair. There’s plenty of arbitrary markers you could put that’d be nice for some but annoying use of space for others. The only proper way to do that would be customization markers which sounds like a nightmare to make if just doing them normally was hard. Glad you put in the 90% though, since that’s important to the leveling.

1 Like

Hi @BlazzBolt,

I was having an issue with this script where it showed some locked items as unstarted instead of locked.

I took a look at the source and it seems you are calculating the number of locked items based on how many items don’t have any assignments associated with them.
I found that in my case, there are some locked items that for some reason do have assignments associated with them. They do not show up in my lessons however.

I found an easy fix for it though. You can check whether an item is unlocked by the item.assignments.unlocked_at field. This will be set to null if it hasn’t been unlocked.

A quick fix for this is to replace line 127

if(item.assignments != undefined) {

by

if(item.assignments != undefined && item.assignments.unlocked_at != null) {

It would be nice if you could fix this upstream as well.

Thanks for making this great script btw!

P.S. I’d submit it as a PR but I can’t find a link to any github page or the like.

1 Like

This doesn’t really work with breeze dark so I made a quick visual fix.

.progress{
    display: block !important;
    padding: 5px !important;
    overflow: hidden !important;
    height: 10px !important;
}

.progress > .bar{
    border-radius: 0px !important;
    margin:0px!important;
    opacity:1!important;
    height:100%!important;
}

.progress .bar:nth-child(2){
    opacity: 0.5 !important;
}

.progress > .bar:nth-child(3){
    opacity:0.35 !important;
}
​
.progress > .bar:nth-child(4){
    opacity:0.24499999999999997 !important;
}

.progress > .bar:nth-child(5){
    opacity:0.17149999999999996 !important;
}

.progress > .bar:nth-child(6){
    float: right;
    box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7), 2px 2px 2px rgba(255, 255, 255, 0.7) !important;
    background-color: #a8a8a8 !important;
    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQMAAABKLAcXAAAABlBMVEX////p6emlmyooAAAAAnRSTlMAgJsrThgAAAA1SURBVDjLY3huea54DpQ4wIBgnyuewDAHSdKAAUnhuQIGJIVzHjCMmjJqyqgpo6aMmkKkKQC2XQWeSEU1BQAAAABJRU5ErkJggg==') !important;
    filter:invert(1)
}

Just paste this at the end of the theme and it should work.

2 Likes

Hi @BlazzBolt, great script, I love it! I was just wondering if having the words “radical”, “kanji”, and “vocabulary” uncapitalized while “Progression” is was a style choice, or just something that has gone unnoticed?

I didn’t create the visual components of this script, and I never noticed that. But now that you pointed it out, I can’t unsee it.

Edit: Looked at it and found the cause.
Edit2: Published fix, plus fix from yackeroeni’s post.

2 Likes

My hero!!!

I’ve loved and depended on this script for a long time, but I’m starting to see a new issue. Every time a go to a new page or reload a page I get this dialog box:

This just started happening in the last hour. I thought it might be a user script and started turning them off one by one and it seems like Dashboard Level Progress Detail is the culprit. Tampermonkey was recently updated for Safari on macOS so I assume there might be some issue there? Is there some reason why this script needs to store over a Gig of data locally?

Just installed and it’s not working

Just installed it and im getting
“Uncaught (in promise) Error: generate_apikey
at script.js:64”

Something wrong with my api key?

Had a look at the code and it doesn’t seem to be this script causing the error. You should check that you installed the right script, and/or confirm which script is causing it

1 Like

Hey dude, I actually just added a 50% marker for myself. Paste these lines from line 57-60 in the newest code


                '<div class="threshold" style="width: '+ Math.ceil(progress.max * 0.5)*100/progress.max +'%;height:100%;position:absolute;padding-right:0.5em;color:#a6a6a6;font-family:Helvetica, Arial, sans-serif;text-align:right;border-right:1px solid rgba(0,0,0,0.1);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:1px 0 0 #eee;-moz-box-shadow:1px 0 0 #eee;box-shadow:1px 0 0 #eee;text-shadow:0 1px 0 rgba(255,255,255,0.5)"><div style="position:absolute;bottom:0;right:0;">'+
                Math.ceil(progress.max * 0.5) +
                '&nbsp</div></div>' ) +

and in case you wanted the whole thing, below. Sorry that I don’t have a hosted link for you (I probably will someday; this is fun!)

// ==UserScript==
// @name         WaniKani Dashboard Level Progress Detail
// @version      0.1.1.6
// @description  Show detailed progress bars.
// @author       hitechbunny, blazzbolt
// @include      https://www.wanikani.com/
// @include      https://www.wanikani.com/dashboard
// @include      https://www.wanikani.com/review
// @run-at       document-end
// @grant        none
// @namespace https://greasyfork.org/users/149329
// ==/UserScript==

(function() {
    'use strict';

    // Hook into App Store
    // try { $('.app-store-menu-item').remove(); $('<li class="app-store-menu-item"><a href="https://community.wanikani.com/t/there-are-so-many-user-scripts-now-that-discovering-them-is-hard/20709">App Store</a></li>').insertBefore($('.navbar .dropdown-menu .nav-header:contains("Account")')); window.appStoreRegistry = window.appStoreRegistry || {}; window.appStoreRegistry[GM_info.script.uuid] = GM_info; localStorage.appStoreRegistry = JSON.stringify(appStoreRegistry); } catch (e) {}

    if (!window.wkof) {
        alert('SRS Grid requires Wanikani Open Framework.\nYou will now be forwarded to installation instructions.');
        window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
        return;
    }
    window.wkof.include('ItemData, Apiv2');

    var locked_data_url = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQMAAABKLAcXAAAABlBMVEX////p6emlmyooAAAAAnRSTlMAgJsrThgAAAA1SURBVDjLY3huea54DpQ4wIBgnyuewDAHSdKAAUnhuQIGJIVzHjCMmjJqyqgpo6aMmkKkKQC2XQWeSEU1BQAAAABJRU5ErkJggg==')";

    function render(json) {
        $('.progression').empty();

        //console.log(json);
        var progresses = [];
        while(json.progresses.length > 3) {
            var progress = json.progresses[0];
            var total_learned = progress.srs_level_totals.slice(1, 10).reduce((a, b) => a + b, 0); // 0 of the srs_level_totals is unlearned, so it's sliced out
            //if (progress.max === 0 || progress.gurued_total*100.0/progress.max >= 90) {
            // adding requirement that all items in the category must be learned
            if (!(progress.max === 0 || (progress.gurued_total*100.0/progress.max >= 90 && total_learned == progress.max))) {
                progresses.push(progress);
            }
            json.progresses = json.progresses.slice(1);
        }

        json.progresses = progresses.concat(json.progresses);

        var stageNames = ['', 'Apprentice I', 'Apprentice II', 'Apprentice III', 'Apprentice IV'];
        json.progresses.forEach(function(progress, j) {
            var html =
                '<div id="progress-'+progress.level+'-'+progress.type+'" class="vocab-progress">'+
                '  <h3>Level '+progress.level+' '+progress.type.charAt(0).toUpperCase() + progress.type.slice(1)+' Progression</h3>'+
                '<div class="chart" style="position:relative;">'+
                ( progress.max < 10 ? "" :
                '<div class="threshold" style="width: '+ Math.ceil(progress.max * 0.9)*100/progress.max +'%;height:100%;position:absolute;padding-right:0.5em;color:#a6a6a6;font-family:Helvetica, Arial, sans-serif;text-align:right;border-right:1px solid rgba(0,0,0,0.1);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:1px 0 0 #eee;-moz-box-shadow:1px 0 0 #eee;box-shadow:1px 0 0 #eee;text-shadow:0 1px 0 rgba(255,255,255,0.5)"><div style="position:absolute;bottom:0;right:0;">'+
                Math.ceil(progress.max * 0.9) +
                '&nbsp</div></div>' +

                '<div class="threshold" style="width: '+ Math.ceil(progress.max * 0.5)*100/progress.max +'%;height:100%;position:absolute;padding-right:0.5em;color:#a6a6a6;font-family:Helvetica, Arial, sans-serif;text-align:right;border-right:1px solid rgba(0,0,0,0.1);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:1px 0 0 #eee;-moz-box-shadow:1px 0 0 #eee;box-shadow:1px 0 0 #eee;text-shadow:0 1px 0 rgba(255,255,255,0.5)"><div style="position:absolute;bottom:0;right:0;">'+
                Math.ceil(progress.max * 0.5) +
                '&nbsp</div></div>' ) +

                '    <div class="progress" title="Unstarted ('+progress.srs_level_totals[0]+'/'+progress.max+')">'+
                '      <div class="bar" title="Guru+ ('+progress.gurued_total+'/'+progress.max+')"  style="background-color: #a100f1; background-image: linear-gradient(to bottom, #a0f, #9300dd); width: '+(progress.gurued_total*100.0/progress.max)+'%;">'+
                '        <span class="dark" style="display: none;">&nbsp;</span>'+
                '      </div>';

            var opacity = 0.5;
            for(var i=4; i>=1; i--) {
                var percentage = progress.srs_level_totals[i]*100.0/progress.max;
                //console.log(cssClass, i, progress.srs_level_totals[i], progress.max, percentage);

                html +=
                    '      <div class="bar bar-supplemental"  title="'+stageNames[i]+' ('+progress.srs_level_totals[i]+'/'+progress.max+')" style="opacity: '+opacity+'; background-color: #a100f1; background-image: linear-gradient(to bottom, #f0a, #dd0093); width: '+(percentage)+'%;">'+
                    '        <span class="dark" style="display: none;"></span>'+
                    '      </div>';

                opacity *= 0.7;
            }

            var unlockedCount = 0;
            progress.srs_level_totals.forEach(function(srs_level_total) {
                unlockedCount += srs_level_total;
            });
            var lockedCount = progress.max - unlockedCount;

            html +=
                '      <div class="bar bar-supplemental" title="Locked ('+lockedCount+'/'+progress.max+')" style="float:right; background-color: #a8a8a8; background-image: '+locked_data_url+'; width: '+(lockedCount*100.0/progress.max)+'%;">'+
                '        <span class="dark" style="display: none;"></span>'+
                '      </div>';

            html +=
                '    </div>'+progress.gurued_total+'<span class="pull-right total">'+progress.max+'</span>'+
                '  </div>'+
                '</div>';

            if (j != json.progresses.length-1) {
                //html += '<hr class="custom-splitter"/>';
            }

            $('.progression').append(html);
        });
    }

    var cached_json = localStorage.getItem('level-progress-cache');
    if (cached_json) {
        render(JSON.parse(cached_json));
    }

    window.wkof.ready('ItemData').then(() => {
        window.wkof.ready('Apiv2').then(() => {
            window.wkof.Apiv2.get_endpoint('level_progressions').then(levels => {
                var level_list = [];
                for (var id in levels) {
                    level_list.push(levels[id]);
                }
                var top_level = level_list.find(l => l.data.abandoned_at == null && l.data.passed_at == null && l.data.unlocked_at != null).data.level;
                window.wkof.ItemData.get_items('assignments').then(items => {
                    var collection = [];
                    items.forEach(item => {
                        prog = collection.find(p => p.level == item.data.level && p.type == item.object);
                        if (prog == undefined) {
                            var prog = {
                                level: item.data.level,
                                type: item.object,
                                srs_level_totals: Array(10).fill(0),
                                gurued_total: 0,
                                max: 0
                            };
                            collection.push(prog);
                        }
                        if(item.assignments != undefined && item.assignments.unlocked_at != null) {
                            prog.srs_level_totals[item.assignments.srs_stage] += 1;
                            if (item.assignments.srs_stage >= 5) {
                                prog.gurued_total += 1;
                            }
                        }
                        prog.max += 1;
                    });
                    collection = collection.filter(p => {
                        return p.level <= top_level //p.level == top_level || ( p.srs_level_totals[0] != p.max && p.gurued_total != p.max && p.level <= top_level );
                    }).sort((a, b) => {
                        var order = ['radical', 'kanji', 'vocabulary'];
                        return a.level - b.level + (order.indexOf(a.type) - order.indexOf(b.type)) / 10;
                    });
                    var json = {progresses: collection};
                    localStorage.setItem('level-progress-cache', JSON.stringify(json));
                    render(json);
        });
        }) });
    });

/*
    window.WKHelper.init(GM_info, function() {
        window.WKHelper.ajax_retry('https://wanikanitools-golang.curiousattemptbunny.com/level/progress?api_key='+window.WKHelper.api_key_v2).then(function(json) {
            localStorage.setItem('level-progress-cache', JSON.stringify(json));
            render(json);
        });
    });
*/
})();
2 Likes

The announced updates to the dashboard

include a change to the progression section that this script hooks into.

    <section class="progression">

(current layout)

becomes

      <section class="dashboard-progress">

A simple adjustment in the render function to switch the old name to the new name will make it work for the current preview. I’ve locally just duplicated the

    $('.progression').empty();
    $('.dashboard-progress').empty();

        $('.progression').append(html);
        $('.dashboard-progress').append(html);

lines to temporarily make it work for both. There’s probably a more elegant way of doing this, but I need to get some sleep :frowning:

4 Likes

You are awesome. Appreciate you! Added those lines at 31/32 and ~102. Worked great, and will remove the .progression ones once the old is gone.

This is such a great script.

Not that it’s really any more elegant, but there’s also this way:

$('.progression, .dashboard-progress').empty();

$('.progression, .dashboard-progress').append(html)

But I’d probably stick with your way, and just add comments indicating which lines apply to which versions of the site.

(And I also need to get some sleep :slight_smile: )

2 Likes

I didn’t know you could do that in jQuery. Neat

Yep, also supported in native Javascript, e.g.:

document.querySelectorAll('.progression, .dashboard-progress');
1 Like

I had no idea! Maybe I should go through a basic tutorial or something :sweat_smile:

1 Like