// ==UserScript==
// @name Wanikani Dashboard Progress Plus
// @namespace rfindley
// @description Display detailed level progress.
// @version 2.0.3
// @include https://www.wanikani.com/dashboard
// @include https://www.wanikani.com/
// @copyright 2018+, Robin Findley
// @license MIT; http://opensource.org/licenses/MIT
// @run-at document-end
// @grant none
// ==/UserScript==
window.dpp = {};
(function(gobj) {
//========================================================================
// Initialization of the Wanikani Open Framework.
//-------------------------------------------------------------------
var script_name = 'Dashboard Progress Plus';
var wkof_version_needed = '1.0.27';
if (!window.wkof) {
alert(script_name+' script 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;
}
if (!wkof.version || wkof.version.compare_to(wkof_version_needed) === 'older') {
alert(script_name+' script requires Wanikani Open Framework version '+wkof_version_needed+'.\nYou will now be forwarded to update page.');
window.location.href = 'https://gf.qytechs.cn/en/scripts/38582-wanikani-open-framework';
return;
}
wkof.include('ItemData, Menu, Settings');
wkof.ready('document,ItemData,Menu,Settings').then(load_settings).then(startup);
//========================================================================
// Global variables
//-------------------------------------------------------------------
var settings, settings_dialog;
//========================================================================
// Load the script settings.
//-------------------------------------------------------------------
function load_settings() {
var defaults = {
time_format: '12hour',
visible_items: 'all',
progress_rings: 'all',
hover_details: 'all',
locked_position: 'first',
show_90percent: true,
show_item_name: true,
compact_display: true,
};
return wkof.Settings.load('dpp', defaults).then(function(data){
settings = wkof.settings.dpp;
});
}
//========================================================================
// Startup
//-------------------------------------------------------------------
function startup() {
install_css();
install_menu();
init_ui();
wkof.ItemData.get_items({
wk_items:{
options:{
assignments:true,
review_statistics:true
},
filters:{
level:'+0',
item_type:'radical,kanji',
}
}
})
.then(process_items);
}
//========================================================================
// Interface customization
//-------------------------------------------------------------------
var progress_image =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJwAAAAnCAYAAAD6tSH7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccll'+
'PAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+ID'+
'x4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8w'+
'Mi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbn'+
'MjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5z'+
'OnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb2'+
'0veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyNjY0MTY4NzY1RUFFNDExOUE4OERFMDQ5OThDNEVFNiIgeG1wTU06'+
'RG9jdW1lbnRJRD0ieG1wLmRpZDo5RTk5NUIzQ0VFRTQxMUU0QUZFNzgxMEQwMDQwMzgwMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5RTk5NU'+
'IzQkVFRTQxMUU0QUZFNzgxMEQwMDQwMzgwMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpE'+
'ZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkVEQkNDNzgxNzBFREU0MTE5QjJFQzRERDc3QUZGN0I5IiBzdFJlZjpkb2N1bWVudE'+
'lEPSJ4bXAuZGlkOjI2NjQxNjg3NjVFQUU0MTE5QTg4REUwNDk5OEM0RUU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBt'+
'ZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+U3W7wQAAAkVJREFUeNrsXLFOwzAUdCpYqy5dqgp16Y7UmW7wA3wECz+Uj2BiKxudkdhZqqpi6YK6MoT3VF'+
'egiqZNMPa95E66DJEtXf1enFzt56woChcQuQuLO2oLpjNHuNdxRJMBlWx6YcIx2aLeY8Ix2aLeywJ/w50Lu8K+cCgcCcfCHsB3Utu05YgJGDrhfoMO'+
'4KVwIhwkDGqbtMHOdjESbocL4ZVnBhLUpmqDfbXGTLgdroU3/hWCEtSmaaNp+IEn4aNwA/ix3RRtsKYhlUt9Fs6EBWBgm6IN0jR0Ej+xc9C/Faxrg0'+
'w2B7DSoAP3DhrYJmjjSsMelsIX0KBa10bTcACvwg/QwFrVRtNQgpXwDTSolrXRNJRg4XBhURtNwwlPqwOeSaxqo2k4gDVwUK1qo2kowQY4qBa1wZqG'+
'M5CB+3Tx1yb/iv1t4ff+dyDqo2kwhG7gdjG00TQYRj9wu5jaaBoMYhi4XSxtNA1GMQrcLoY2rjQYnt3GJ7YdR57ljmmjaTAIrSk4tZCl59sjaKNpMA'+
'itJZhU7DPx/VC00TQYghatVK2WGvh+CNogv+NSFNFYgBas3LrqVVIKHdAHt92Zm0obTYMhTN22Oiqr2T/z/aeJtbW2ENrazFalFK8Mus45CzjT1dXW'+
'2kJodINQt9j42Ot17rlMoI2F0GAIdZzCMWjBi9Yg6LbwVSRtNA2JEfrAmDrQGgTdFr7wibd231uM/uOgncafnoSM3LUHrT49iQmXPvloGphw7Us2zn'+
'BMNpoGJlxzk00vXwIMAERrvuh7OTAxAAAAAElFTkSuQmCC';
// Apprentice,Guru,Master,Enlightened,Burned,Locked
var srs_radical_colors = '#00aaff,#b69acd,#9aa5cf,#a3c3d3,#999999,#00aaff';
var srs_kanji_colors = '#ff00aa,#b69acd,#9aa5cf,#a3c3d3,#999999,#ff00aa';
//========================================================================
// CSS Styling
//-------------------------------------------------------------------
var progress_css =
'.dpp-progress {background-position:39px 0px;background-repeat:no-repeat;background-image: url("##progress_image##");}'+
'.progression .lattice-single-character li {padding-top:3px;padding-bottom:3px;margin-top:2px;}'+
'.pct90 {background:#e7e7e7;}'+
'.pct90_left {border-top-left-radius:50%;border-bottom-left-radius:50%;padding-left:3px;}'+
'.pct90_right {border-top-right-radius:50%;border-bottom-right-radius:50%;padding-right:3px;}'+
// Radical colors
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="0"] {background-color:#00aaff;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="1"] {background-color:#00aaff;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="2"] {background-color:#00aaff;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="3"] {background-color:#00aaff;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="4"] {background-color:#00aaff;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="5"] {background-color:#b69acd;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="6"] {background-color:#b69acd;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="7"] {background-color:#9aa5cf;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="8"] {background-color:#a3c3d3;}'+
'.radicals-progress .lattice-single-character .dpp-progress[data-srs-lvl="9"] {background-color:#999999;}'+
// Kanji colors
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="0"] {background-color:#ff00aa;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="1"] {background-color:#ff00aa;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="2"] {background-color:#ff00aa;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="3"] {background-color:#ff00aa;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="4"] {background-color:#ff00aa;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="5"] {background-color:#b69acd;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="6"] {background-color:#b69acd;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="7"] {background-color:#9aa5cf;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="8"] {background-color:#a3c3d3;}'+
'.kanji-progress .lattice-single-character .dpp-progress[data-srs-lvl="9"] {background-color:#999999;}'+
// Progress rings
'.dpp-progress[data-srs-lvl="0"] {background-position:-117px 0px;}'+
'.dpp-progress[data-srs-lvl="1"] {background-position:39px 0px;}'+
'.dpp-progress[data-srs-lvl="2"] {background-position:0px 0px;}'+
'.dpp-progress[data-srs-lvl="3"] {background-position:-39px 0px;}'+
'.dpp-progress[data-srs-lvl="4"] {background-position:-78px 0px;}'+
'.dpp-progress[data-srs-lvl="5"] {background-position:39px 0px;}'+
'.dpp-progress[data-srs-lvl="6"] {background-position:39px 0px;}'+
'.progression[data-progress-rings="all"] .dpp-progress[data-srs-lvl="6"] {background-position:-39px 0px;}'+
'.dpp-progress[data-srs-lvl="7"] {background-position:39px 0px;}'+
'.dpp-progress[data-srs-lvl="8"] {background-position:39px 0px;}'+
'.dpp-progress[data-srs-lvl="9"] {background-position:39px 0px;}'+
'.lattice-single-character li.dpp-noshow {width:0px;}'+
'.progression[data-show-item-name="false"] .lattice-single-character li>a {color:rgba(0,0,0,0);text-shadow:0 0 0 rgba(0,0,0,0);}'+
// Compact settings
'body .progression.dpp_compact {padding-top:4px;padding-bottom:4px;}'+
'body .progression.dpp_compact .chart {display:none;margin:0px;}'+
'body .progression.dpp_compact h3 {font-size:14px;margin:4px;}'+
'body .progression.dpp_compact hr {margin:0px;}'+
'body .progression.dpp_compact .lattice-single-character {background-image:none;margin:0px 0px 5px 0px;}'+
'body .progression.dpp_compact .lattice-single-character>div:first-child {display:none;margin:0px;}'+
'body .progression.dpp_compact .lattice-single-character ul {display:block;margin:0px;}';
//========================================================================
// Install stylesheet.
//-------------------------------------------------------------------
function install_css() {
var css = progress_css.replace(/##progress_image##/g, progress_image);
$('head').append('<style>'+css+'</style>');
}
//========================================================================
// Install menu link
//-------------------------------------------------------------------
function install_menu() {
// Set up menu item to open script.
wkof.Menu.insert_script_link({name:'dpp',submenu:'Settings',title:'Dashboard Progress Plus',on_click:open_settings});
}
//========================================================================
// Initialize the user interface.
//-------------------------------------------------------------------
function init_ui() {
if (settings.compact_display) {
$('.progression').addClass('dpp_compact');
$('.progression .lattice-single-character ul').css('display','');
} else {
$('.progression').removeClass('dpp_compact');
}
$('.progression').attr('data-progress-rings', settings.progress_rings);
$('.progression').attr('data-show-item-name', settings.show_item_name);
}
//========================================================================
// Open the settings dialog
//-------------------------------------------------------------------
function open_settings() {
var config = {
script_id: 'dpp',
title: 'Dashboard Progress Plus',
on_save: settings_saved,
content: {
time_format: {type:'dropdown', label:'Time Format', default:'12hour', content:{'12hour':'12-hour','24hour':'24-hour'}, hover_tip:'Display time in 12 or 24-hour format.'},
visible_items: {type:'dropdown', label:'Visible Items', default:'all', content:{all:'All Items',appr_only:'Apprentice Only'}, hover_tip:'Choose which items to show.'},
progress_rings: {type:'dropdown', label:'Show Progress Rings', default:'all', content:{all:'Apprentice + Guru',appr_only:'Apprentice Only'}, hover_tip:'Choose which progress rings to show.'},
hover_details: {type:'dropdown', label:'Hover Details', default:'all', content:{all:'All',next_review:'Next Review Date Only'}, hover_tip:'Choose which details to show in the hover pop-up.'},
locked_position: {type:'dropdown', label:'Locked Item Position', default:'first', content:{first:'First',last:'Last'}, hover_tip:'Choose where locked items are placed.'},
show_90percent: {type:'checkbox', label:'Show 90% Bracket', default:true, hover_tip:'Show the bracket around 90% of items.'},
show_item_name: {type:'checkbox', label:'Show Item Name', default:true, hover_tip:'Show item names in circles.'},
compact_display: {type:'checkbox', label:'Compact Display', default:true, hover_tip:'Enable compact display mode.'},
}
};
var settings_dialog = new wkof.Settings(config);
settings_dialog.open();
}
//========================================================================
// Handler for when user clicks 'Save' in the settings window.
//-------------------------------------------------------------------
function settings_saved(new_settings) {
settings = new_settings;
init_ui();
populate_item_info('radical');
populate_item_info('kanji');
}
//========================================================================
// Populate level info from API.
//-------------------------------------------------------------------
function process_items(data) {
gobj.items = wkof.ItemData.get_index(data, 'item_type');
populate_item_info('radical');
populate_item_info('kanji');
}
function populate_item_info(itype) {
var ul;
if (itype === 'radical') {
ul = $('.radicals-progress .lattice-single-character>ul');
} else {
ul = $('.kanji-progress .lattice-single-character>ul');
}
var items = wkof.ItemData.get_index(gobj.items[itype], 'subject_id');
// Populate item data.
ul.children().each(function(idx, li){
var subject_id = li.attributes.id.value.split('-')[1];
var item = items[subject_id];
li = $(li);
var a = li.find('>a');
li.removeAttr('style'); // WK sometimes puts "display:none" here.
a.addClass('dpp-progress');
// Populate 'data-srs-lvl', which is a styling selector.
var srs = (item.assignments ? item.assignments.srs_stage : 0); // 0 == locked
a.attr('data-srs-lvl', srs);
if (!a.attr('data-dpp-hover-backup'))
a.attr('data-dpp-hover-backup', a.attr('data-original-title'));
// Populate the next review date.
var next = (settings.hover_details !== 'all' ? '' : '<br>');
if (item.assignments) {
if (item.assignments.srs_stage == 9) {
next += '<span style="font-size:75%;font-weight:bold;">Burned!</span>';
} else {
var date = formatDate(new Date(item.assignments.available_at));
next += '<span style="font-size:75%;font-weight:bold;">Next: '+date+'</span>';
}
} else {
next += '<span style="font-size:75%;font-weight:bold;">Locked!</span>';
}
// Populate remaining data for popup window.
var percent = 0;
var correct;
var total;
if (itype==='radicals') {
if (settings.hover_details !== 'all')
a.attr('data-original-title', next);
else
a.attr('data-original-title', a.attr('data-dpp-hover-backup')+next);
if (item.review_statistics) {
correct = item.review_statistics.meaning_correct;
total = correct + item.review_statistics.meaning_incorrect;
if (total > 0) percent = Math.floor(100.0*correct/total);
}
} else {
if (settings.hover_details !== 'all')
a.attr('data-original-title', next);
else
a.attr('data-original-title', a.attr('data-dpp-hover-backup')+next);
if (item.review_statistics) {
correct = item.review_statistics.meaning_correct + item.review_statistics.reading_correct;
total = correct + item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect;
if (total > 0) percent = Math.floor(100.0*correct/total);
}
}
a.attr('data-content', '<div class="progress"><div class="bar full" style="width: '+Math.max(percent,15)+'%;">'+percent+'%</div></div>');
try {
a.data('popover').options.content = null;
} catch(e) {}
if (settings.hide_circle_content) a.html(' ');
});
// Sort items by srs level, then review date, then meaning.
var li_arr = ul.children();
var srs_locked = (settings.locked_position === 'first' ? 0 : 10);
li_arr.sort(function(a,b){
a = items[a.attributes.id.value.split('-')[1]];
b = items[b.attributes.id.value.split('-')[1]];
var a_srs = (a && a.assignments && a.assignments.srs_stage ? a.assignments.srs_stage : srs_locked);
var b_srs = (b && b.assignments && b.assignments.srs_stage ? b.assignments.srs_stage : srs_locked);
if (a_srs < b_srs) return -1;
if (a_srs > b_srs) return 1;
var a_avail = (a && a.assignments && a.assignments.available_at ?
new Date(a.assignments.available_at).getTime() : Number.MAX_SAFE_INTEGER);
var b_avail = (b && b.assignments && b.assignments.available_at ?
new Date(b.assignments.available_at).getTime() : Number.MAX_SAFE_INTEGER);
if (a_avail < b_avail) return 1;
if (a_avail > b_avail) return -1;
if (a.data.slug < b.data.slug) return -1;
if (a.data.slug > b.data.slug) return 1;
return 0;
});
li_arr.detach().appendTo(ul);
$('.progression .lattice-single-character ul>li').removeClass('dpp-noshow pct90_left pct90 pct90_right');
if (settings.visible_items !== 'all') {
for (var srslvl=5; srslvl<=9; srslvl++) {
$('.dpp-progress[data-srs-lvl="'+srslvl+'"]').parent().addClass('dpp-noshow');
}
}
if (settings.show_90percent) {
// Add marker at 90%, indicating when level will be complete.
// First, make sure there are at least 10% of items left.
var idx90 = Math.floor(li_arr.length * 0.1);
var len = ul.children(':not(.dpp-noshow)').length;
if (idx90 < len) {
ul.children().eq(idx90).addClass('pct90_left');
ul.children().slice(idx90).addClass('pct90');
ul.children(':not(.dpp-noshow)').last().addClass('pct90_right');
}
}
}
//========================================================================
// Print date in pretty format.
//-------------------------------------------------------------------
function formatDate(d){
var s = '';
var now = new Date();
var YY = d.getFullYear(),
MM = d.getMonth(),
DD = d.getDate(),
hh = d.getHours(),
mm = d.getMinutes(),
one_day = 24*60*60*1000;
if (d < now) return "Available Now";
var same_day = ((YY == now.getFullYear()) && (MM == now.getMonth()) && (DD == now.getDate()) ? 1 : 0);
// If today: "Today 8:15pm"
// otherwise: "Wed, Apr 15, 8:15pm"
if (same_day) {
s += 'Today ';
} else {
s += ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()]+', '+
['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][MM]+' '+DD+', ';
}
if (settings.time_format === '24hour')
s += ('0'+hh).slice(-2)+':'+('0'+mm).slice(-2);
else
s += (((hh+11)%12)+1)+':'+('0'+mm).slice(-2)+['am','pm'][Math.floor(d.getHours()/12)];
// Append "(X days)".
if (!same_day) {
var days = (Math.floor((d.getTime()-d.getTimezoneOffset()*60*1000)/one_day)-Math.floor((now.getTime()-d.getTimezoneOffset()*60*1000)/one_day));
if (days) s += ' ('+days+' day'+(days>1?'s':'')+')';
}
return s;
}
})(window.dpp);