Guest User

Dashboard Level Progress Indicator 1.0+ / Improvements

a guest
Dec 22nd, 2021
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         WaniKani Dashboard Level Progress Detail
  3. // @version      1.1.2
  4. // @description  Show detailed progress bars.
  5. // @author       UInt2048
  6. // @include      /^https://(www|preview).wanikani.com/(dashboard)?$/
  7. // @run-at       document-end
  8. // @grant        none
  9. // @namespace https://greasyfork.org/users/149329
  10. // ==/UserScript==
  11.  
  12. (function() {
  13.     'use strict';
  14.  
  15.     if (!window.wkof) {
  16.         alert('WK Dashboard Level Progress Detail requires Wanikani Open Framework.\nYou will now be forwarded to installation instructions.');
  17.         window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
  18.         return;
  19.     }
  20.     window.wkof.include('ItemData, Apiv2, Menu, Settings');
  21.  
  22.     var locked_data_url = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQMAAABKLAcXAAAABlBMVEX////p6emlmyooAAAAAnRSTlMAgJsrThgAAAA1SURBVDjLY3huea54DpQ4wIBgnyuewDAHSdKAAUnhuQIGJIVzHjCMmjJqyqgpo6aMmkKkKQC2XQWeSEU1BQAAAABJRU5ErkJggg==')";
  23.  
  24.     function render(json) {
  25.         const burnStage = 9;
  26.         const enlightenedStage = 8;
  27.         const masterStage = 7;
  28.         const guruStage = 5;
  29.         const apprenticeStage = 1;
  30.         const stageNames = ['', 'Apprentice I', 'Apprentice II', 'Apprentice III', 'Apprentice IV', 'Guru I', 'Guru II', 'Master', 'Enlightened', 'Burned'];
  31.  
  32.         function getDesiredLevel() {
  33.             switch (settings.progress_hidden) {
  34.                 case '3':
  35.                 case '4':
  36.                     return masterStage;
  37.                 case '5':
  38.                 case '6':
  39.                     return enlightenedStage;
  40.                 case '7':
  41.                     return burnStage;
  42.                 default:
  43.                     return guruStage;
  44.             }
  45.         }
  46.  
  47.         const settings = window.wkof.settings.level_progress_detail;
  48.         const usePassed = settings.progress_hidden % 2 == 0;
  49.         const desiredLevel = getDesiredLevel();
  50.         const percentageRequired = settings.progress_hidden_percentage;
  51.  
  52.         const burnedOpacity = 1;
  53.         const enlightenedOpacity = 1;
  54.         const masterOpacity = 1;
  55.         const initialGuruOpacity = 1;
  56.         const guruOpacityChange = settings.opacity_multiplier_guru / 100.;
  57.         const initialApprenticeOpacity = 1;
  58.         const apprenticeOpacityChange = settings.opacity_multiplier_apprentice / 100.;
  59.         const show_user_specified_marker_count_threshold = 0.97;
  60.  
  61.         function getColorCode(stage) {
  62.             if (stage >= burnStage) return settings.colorcode_burned;
  63.             else if (stage >= enlightenedStage) return settings.colorcode_enlightened;
  64.             else if (stage >= masterStage) return settings.colorcode_master;
  65.             else if (stage >= guruStage) return settings.colorcode_guru;
  66.             else if (stage >= apprenticeStage) return settings.colorcode_apprentice;
  67.         }
  68.  
  69.         function totalAtLeast(progress, stage) {
  70.             return progress.srs_level_totals.slice(stage).reduce((a, b) => a + b, 0);
  71.         }
  72.  
  73.         window.$(".progress-component").children().slice(0, -2).remove();
  74.         if (settings.hide_current_level) { window.$(".progress-component").empty(); }
  75.  
  76.         var progresses = [];
  77.         while (json.progresses.length > settings.unconditional_progressions) {
  78.             var progress = json.progresses[0];
  79.             var total_learned = totalAtLeast(progress, apprenticeStage);
  80.             var desired_level_plus_total = totalAtLeast(progress, desiredLevel);
  81.             var learnedRequired = settings.require_learned ? progress.max : 0;
  82.             var percentageTotal = usePassed ? progress.passed_total : desired_level_plus_total;
  83.  
  84.             if (!(percentageTotal * 100.0 / progress.max >= percentageRequired && total_learned >= learnedRequired) && progress.max !== 0) {
  85.                 progresses.push(progress);
  86.             }
  87.             json.progresses = json.progresses.slice(1);
  88.         }
  89.  
  90.         json.progresses = progresses.concat(json.progresses);
  91.  
  92.         var runningHTML = "";
  93.         json.progresses.forEach(function(progress, j) {
  94.             var user_specified_marker = settings.progress_hidden_percentage / 100.;
  95.             var html =
  96.                 '<div id="progress-' + progress.level + '-' + progress.type + '" class="vocab-progress">' +
  97.                 '  <h3>Level ' + progress.level + ' ' + progress.type.charAt(0).toUpperCase() + progress.type.slice(1) + ' Progression</h3>' +
  98.                 '<div class="chart" style="position:relative;">' +
  99.                 (progress.max < 10 || Math.round(progress.max * user_specified_marker) == progress.max ? "" :
  100.                  '<div class="threshold" style="width: ' + Math.round(progress.max * user_specified_marker) * 100 / progress.max + '% !important;height:100% !important;position:absolute !important;padding-right:0.5em !important;color:#a6a6a6 !important;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;">' +
  101.                  (user_specified_marker <= show_user_specified_marker_count_threshold ? Math.round(progress.max * user_specified_marker) : "") +
  102.                  '&nbsp</div></div>') + // user-specified % marker
  103.                 (progress.max < 10 || progress.type != "kanji" || progress.passed_total >= Math.ceil(progress.max * 0.9) ? "" :
  104.                  '<div class="threshold" style="width: ' + Math.ceil(progress.max * 0.9) * 100 / progress.max + '% !important;height:100% !important;position:absolute !important;padding-right:0.5em !important;color:rgb(0, 220, 0, 1) !important;font-family:Helvetica, Arial, sans-serif;text-align:right;border-right:1px solid rgba(0,220,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="font-weight:bold;position:absolute;bottom:0;right:0;">' +
  105.                  Math.ceil(progress.max * 0.9) +
  106.                  '&nbsp</div></div>') + // current level kanji passing marker
  107.                 (progress.max < 2 || !settings.show_halfway_marker ? "" :
  108.                  '<div class="threshold" style="width: ' + Math.ceil(progress.max * 0.5) * 100 / progress.max + '% !important;height:100% !important;position:absolute !important;padding-right:0.5em !important;color:#a6a6a6 !important;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;">' +
  109.                  Math.ceil(progress.max * 0.5) +
  110.                  '&nbsp</div></div>'); // 50% marker
  111.  
  112.             let opacity = settings.distinguish_beyond_guru ? burnedOpacity : initialGuruOpacity;
  113.             let gurued_plus_total = totalAtLeast(progress, guruStage);
  114.  
  115.             html += '    <div class="progress" title="Unstarted (' + progress.srs_level_totals[0] + '/' + progress.max + ')" style="border-radius:' + settings.border_radius + 'px !important;">';
  116.             for (let i = settings.distinguish_beyond_guru ? stageNames.length - 1 : guruStage; i >= apprenticeStage; i--) {
  117.                 let name = (!settings.distinguish_beyond_guru && i == guruStage) ? "Guru+" : stageNames[i];
  118.                 let total = (!settings.distinguish_beyond_guru && i == guruStage) ? gurued_plus_total : progress.srs_level_totals[i];
  119.                 let percentage = total * 100.0 / progress.max;
  120.                 let gradient = "linear-gradient(to bottom, " + getColorCode(i) + ", " + (settings.shadow ? "#222" : getColorCode(i)) + ")";
  121.  
  122.                 html +=
  123.                     '      <div class="bar bar-supplemental"  title="' + name + ' (' + total + '/' + progress.max + ')" style="float: left !important; opacity: ' + opacity + ' !important; background-color: #a100f1 !important; background-image: ' + gradient + ' !important; width: ' + (percentage) + '% !important; height: 100% !important; margin:0px !important; border-radius:' + settings.border_radius + 'px !important;">' +
  124.                     '        <span class="dark" style="display: none;"></span>' +
  125.                     '      </div>';
  126.  
  127.                 if (i == burnStage) opacity = enlightenedOpacity;
  128.                 else if (i == enlightenedStage) opacity = masterOpacity;
  129.                 else if (i == masterStage) opacity = initialGuruOpacity;
  130.                 else if (i > guruStage) opacity *= guruOpacityChange;
  131.                 else if (i == guruStage) opacity = initialApprenticeOpacity;
  132.                 else opacity *= apprenticeOpacityChange;
  133.             }
  134.  
  135.             var unlockedCount = 0;
  136.             progress.srs_level_totals.forEach(function(srs_level_total) {
  137.                 unlockedCount += srs_level_total;
  138.             });
  139.             var lockedCount = progress.max - unlockedCount;
  140.             var notStartedWidth = progress.srs_level_totals[0] * 100.0 / progress.max;
  141.             var lockedWidth = lockedCount * 100.0 / progress.max;
  142.  
  143.             html +=
  144.                 '      <div class="bar bar-supplemental" title="Locked (' + lockedCount + '/' + progress.max + ')" style="float:left !important; background-color: #a8a8a8 !important; background-image: ' + locked_data_url + ' !important; width: ' + lockedWidth + '% !important; height: 100% !important; margin:0px !important; margin-left: ' + notStartedWidth + '% !important; border-radius:' + settings.border_radius + 'px !important;">' +
  145.                 '        <span class="dark" style="display: none;"></span>' +
  146.                 '      </div>';
  147.  
  148.             var total = gurued_plus_total == progress.max ? 0 : gurued_plus_total;
  149.             html +=
  150.                 '    </div>' + total + '<span class="pull-right total">' + progress.max + '</span>' +
  151.                 '  </div>' +
  152.                 '</div>';
  153.  
  154.             runningHTML += html;
  155.         });
  156.         window.$('.progress-component').prepend(runningHTML);
  157.     }
  158.  
  159.     function prepareForRender() {
  160.         var cached_json = localStorage.getItem('level-progress-cache');
  161.         var didRender = false;
  162.         if (cached_json) {
  163.             render(JSON.parse(cached_json));
  164.             didRender = true;
  165.         }
  166.  
  167.         window.wkof.ready('ItemData, Apiv2').then(() => {
  168.             window.wkof.Apiv2.get_endpoint('level_progressions').then(levels => {
  169.                 var level_list = [];
  170.                 for (var id in levels) {
  171.                     level_list.push(levels[id]);
  172.                 }
  173.                 var top_level = (level_list.find(l => l.data.abandoned_at == null && l.data.passed_at == null && l.data.unlocked_at != null) || level_list.slice(-1)[0]).data.level;
  174.                 window.wkof.ItemData.get_items('assignments').then(items => {
  175.                     var collection = [];
  176.                     items.forEach(item => {
  177.                         prog = collection.find(p => p.level == item.data.level && p.type == item.object);
  178.                         if (prog == undefined) {
  179.                             var prog = {
  180.                                 level: item.data.level,
  181.                                 type: item.object,
  182.                                 srs_level_totals: Array(10).fill(0),
  183.                                 passed_total: 0,
  184.                                 max: 0
  185.                             };
  186.                             collection.push(prog);
  187.                         }
  188.                         if (item.assignments != undefined && item.assignments.unlocked_at != null) {
  189.                             prog.srs_level_totals[item.assignments.srs_stage]++;
  190.                             if (item.assignments.passed_at != null) {
  191.                                 prog.passed_total++;
  192.                             }
  193.                         }
  194.                         prog.max++;
  195.                     });
  196.                     collection = collection.filter(p => {
  197.                         return p.level <= top_level
  198.                     }).sort((a, b) => {
  199.                         var order = ['radical', 'kanji', 'vocabulary'];
  200.                         return a.level - b.level + (order.indexOf(a.type) - order.indexOf(b.type)) / 10;
  201.                     });
  202.                     var json = {
  203.                         progresses: collection
  204.                     };
  205.                     localStorage.setItem('level-progress-cache', JSON.stringify(json));
  206.                     if (cached_json != json) { render(json); }
  207.                 }); // assignments
  208.             }); // level progressions
  209.         }); // Item Data, APIv2
  210.     }
  211.  
  212.     window.wkof.ready('Menu,Settings').then(load_settings).then(install_menu).then(prepareForRender);
  213.  
  214.     // Load settings and set defaults
  215.     function load_settings() {
  216.         var defaults = {
  217.             progress_hidden: '2',
  218.             progress_hidden_percentage: 90,
  219.             unconditional_progressions: 0,
  220.             border_radius: 10,
  221.             hide_current_level: true,
  222.             require_learned: true,
  223.             show_halfway_marker: true,
  224.             distinguish_beyond_guru: false,
  225.             colorcode_apprentice: '#f300a2',
  226.             colorcode_guru: '#9d34b7',
  227.             colorcode_master: '#4867e0',
  228.             colorcode_enlightened: '#00a5f7',
  229.             colorcode_burned: '#fbb41c',
  230.             shadow: false,
  231.             opacity_multiplier_apprentice: '70',
  232.             opacity_multiplier_guru: '70'
  233.         };
  234.         return window.wkof.Settings.load('level_progress_detail', defaults);
  235.     }
  236.  
  237.     // Installs the options button in the menu
  238.     function install_menu() {
  239.         var config = {
  240.             name: 'level_progress_detail_settings',
  241.             submenu: 'Settings',
  242.             title: 'Dashboard Level Progress Detail',
  243.             on_click: open_settings
  244.         };
  245.         window.wkof.Menu.insert_script_link(config);
  246.     }
  247.  
  248.     // Create the options
  249.     function open_settings(items) {
  250.         var config = {
  251.             script_id: 'level_progress_detail',
  252.             title: 'Dashboard Level Progress Detail',
  253.             content: {
  254.                 tabs: {type:'tabset', content: {
  255.                     pgFilter: {type:'page', label:'Filters', content: {
  256.                         progress_hidden: {
  257.                             type: 'dropdown',
  258.                             label: 'Progress hidden criteria',
  259.                             hover_tip: 'Choose criteria for what progress to hide',
  260.                             default: '2',
  261.                             content: {
  262.                                 1: 'Guru or higher right now',
  263.                                 2: 'Has been guru or higher at any point',
  264.                                 3: 'Master or higher right now',
  265.                                 5: 'Enlightened or higher right now',
  266.                                 7: 'Burnt right now'
  267.                             }
  268.                         },
  269.                         progress_hidden_percentage: {
  270.                             type: 'number',
  271.                             label: 'Progress hidden percentage',
  272.                             hover_tip: 'Determines the percentage of progress necessary to hide',
  273.                             min: 0,
  274.                             max: 100,
  275.                             step: '1',
  276.                             default: '90'
  277.                         },
  278.                         unconditional_progressions: {
  279.                             type: 'number',
  280.                             label: 'Progressions shown unconditionally',
  281.                             hover_tip: 'For example, 3 will always show current level radical, kanji, and vocab progressions',
  282.                             min: 0,
  283.                             default: 3
  284.                         },
  285.                         border_radius: {
  286.                             type: 'number',
  287.                             label: 'Roundedness of progression (in pixels)',
  288.                             hover_tip: 'Choose zero for no roundedness, and 10 for maximum roundedness.',
  289.                             min: 0,
  290.                             default: 10
  291.                         },
  292.                         hide_current_level: {
  293.                             type: 'checkbox',
  294.                             label: 'Hide current level items',
  295.                             hover_tip: 'Check this box to hide the list of radicals and kanji.',
  296.                             default: false
  297.                         },
  298.                         require_learned: {
  299.                             type: 'checkbox',
  300.                             label: 'Require all items to be learned to hide',
  301.                             hover_tip: 'Check this box to require every item to have completed its lesson in a category before hiding it.',
  302.                             default: true
  303.                         },
  304.                         show_halfway_marker: {
  305.                             type: 'checkbox',
  306.                             label: 'Show halfway marker',
  307.                             hover_tip: 'Show 50% marker in addition to 90% marker.',
  308.                             default: true
  309.                         },
  310.                         distinguish_beyond_guru: {
  311.                             type: 'checkbox',
  312.                             label: 'Distinguish beyond Guru',
  313.                             hover_tip: 'Show different colored bars for Guru, Master, Enlightened and Burned',
  314.                             default: false
  315.                         }}},
  316.                     pgColor: {type:'page', label:'Colors', content: {
  317.                         colorcode_apprentice:{
  318.                             type: 'color',
  319.                             label: 'Color Apprentice',
  320.                             hover_tip: 'Color for your Apprentice Progression bar',
  321.                             default: '#f300a2'
  322.                         },
  323.                         colorcode_guru:{
  324.                             type: 'color',
  325.                             label: 'Color Guru',
  326.                             hover_tip: 'Color for your Guru Progression bar',
  327.                             default: '#9d34b7'
  328.                         },
  329.                         colorcode_master:{
  330.                             type: 'color',
  331.                             label: 'Color Master',
  332.                             hover_tip: 'Color for your Master Progression bar',
  333.                             default: '#4867e0'
  334.                         },
  335.                         colorcode_enlightened:{
  336.                             type: 'color',
  337.                             label: 'Color Enlightened',
  338.                             hover_tip: 'Color for your Enlightend Progression Bar',
  339.                             default: '#00a5f7'
  340.                         },
  341.                         colorcode_burned:{
  342.                             type: 'color',
  343.                             label: 'Color Burned',
  344.                             hover_tip: 'Color for your Burned Progression Bar',
  345.                             default: '#fbb41c'
  346.                         },
  347.                         shadow:{
  348.                             type: 'checkbox',
  349.                             label: 'Shadow',
  350.                             hover_tip: 'Display shadow for each progress bar. Works best in dark mode.',
  351.                             default: false
  352.                         },
  353.                         opacity_multiplier_apprentice:{
  354.                             type: 'number',
  355.                             label: 'Fading multiplier Apprentice',
  356.                             hover_tip: 'Determines how quickly the color fades when the Apprentice rank decreases. Should be a number between 1 and 100',
  357.                             min: 1,
  358.                             max: 100,
  359.                             step: '1',
  360.                             default: 70
  361.                         },
  362.                         opacity_multiplier_guru:{
  363.                             type: 'number',
  364.                             label: 'Fading multiplier Guru',
  365.                             hover_tip: 'Determines how quickly the color fades when the Guru rank decreases. Should be a number between 1 and 100',
  366.                             min: 1,
  367.                             max: 100,
  368.                             step: '1',
  369.                             default: 70
  370.                         }
  371.                     }}
  372.                 }
  373.                       }}
  374.         }
  375.         var dialog = new window.wkof.Settings(config);
  376.         dialog.open();
  377.     }
  378. })();
Add Comment
Please, Sign In to add comment