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

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

Just reading through this was pretty helpful for me:
https://blog.garstasio.com/you-dont-need-jquery/

(or one very similar)

1 Like

That was a nice read. I’ve actually been using less and less jQuery as I learn more about the native JS. It is nice for being concise, but I agree that it doesn’t add much in terms of functionality.

I also feel that way. jquery provides an easy way (without writing much/long code) to change stuff in userscripts. But if your developing a webapp you are problably using the likes of React, Angular, Vue, Svelte… and jquery would actually break everything that you are doing

1 Like

Hi everyone. So a couple of things changed on the dashboard with the latest update that took effect today. I highly depend on this script with my progress, but it doesn’t show anymore after the update.

Any solution to the problem please?

Thanks

1 Like

The post got kind of buried already :slight_smile:

The progression section was renamed in the new layout. If you open the script and replace the two occurrences

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

with

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

It will work again.

9 Likes

THANK YOU SO MUCH!!

1 Like

I was desperately looking for this, thank you!!!

I want to replace the whole new block (progress and forecast) with our level-progress-detail widget. Reason is that 1/ I dont like the forecast either 2/ this block is white, whereas I am trying to keep the Dark Breeze theme with minimal change.

So a solution I experimented with :

  • modify the script mode to override the content of progress-and-forecast
$('.progress-and-forecast').empty();
$('.progress-and-forecast').append(html);
  • the WK site-designer has used a CSS ‘display=grid’ to format the progress-and-forecast. To avoid the level-progress-detail to be display as a grid, I have a piece of CSS:
.progress-and-forecast
{
    display:inline !important;
}

By the way, this ^^^ is equivalent to:

$('.progress-and-forecast').html(html);
2 Likes