// ==UserScript==
// @name Wanikani: Progress Percentages
// @namespace http://tampermonkey.net/
// @version 1.2.7
// @description Calculates the percentage of known kanji for each JLPT level, Joyo grade, Frequency bracket, and various other sources.
// @author Kumirei
// @include /^https://(www|preview).wanikani.com/(dashboard)?$/
// @require https://gf.qytechs.cn/scripts/377613-wanikani-open-framework-jlpt-joyo-and-frequency-filters/code/Wanikani%20Open%20Framework%20JLPT,%20Joyo,%20and%20Frequency%20filters.user.js
// @license MIT; http://opensource.org/licenses/MIT
// @grant none
// ==/UserScript==
(function() {
// Make sure WKOF is installed
var wkof = window.wkof;
if (!wkof) {
var response = confirm('Wanikani: JLPT Progress 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;
}
else {
// Install menu
wkof.include('Menu,Settings');
wkof.ready('Menu,Settings').then(load_settings).then(install_menu);
// Initiate progress variable
var progress = {
jlpt: {1: {learned: 0, total: 1232}, 2: {learned: 0, total: 367}, 3: {learned: 0, total: 367}, 4: {learned: 0, total: 166}, 5: {learned: 0, total: 79}},
joyo: {1: {learned: 0, total: 80}, 2: {learned: 0, total: 160}, 3: {learned: 0, total: 200}, 4: {learned: 0, total: 200}, 5: {learned: 0, total: 185}, 6: {learned: 0, total: 181}, 9: {learned: 0, total: 1130}},
freq: {500: {learned: 0, total: 500}, 1000: {learned: 0, total: 500}, 1500: {learned: 0, total: 500}, 2000: {learned: 0, total: 500}, 2500: {learned: 0, total: 500}},
other: {nhk: {learned: 0, total: 0}, news: {learned: 0, total: 0}, aozora: {learned: 0, total: 0}, twitter: {learned: 0, total: 0}, wikipedia: {learned: 0, total: 0}}
};
// Fetch lesson info then process it
wkof.include('ItemData');
wkof.ready('ItemData')
.then(update_progress)
.then(calculate_percentages)
.then(display_data);
}
// Loads settings
function load_settings() {
var defaults = {cumulative: false,
threshold: 1,
position: 'top',
};
wkof.Settings.load('progress_percentages', defaults);
}
// Installs the options button in the menu
function install_menu() {
var config = {
name: 'progress_percentages_settings',
submenu: 'Settings',
title: 'Progress Percentages',
on_click: open_settings
};
wkof.Menu.insert_script_link(config);
}
// Creates the options
function open_settings(items) {
var config = {
script_id: 'progress_percentages',
title: 'Progress Percentages',
content: {
cumulative: {
type: 'checkbox',
label: 'Cumulative percentages',
hover_tip: 'Eg. N3 = N3 + N4 + N4',
default: false
},
threshold: {
type: 'list',
label: 'Learned threshold',
hover_tip: 'Items at or above this SRS level will be counted as learned',
multi: false,
size: 9,
default: '1',
content: {
1: 'Apprentice 1',
2: 'Apprentice 2',
3: 'Apprentice 3',
4: 'Apprentice 4',
5: 'Guru 1',
6: 'Guru 2',
7: 'Master',
8: 'Enlightened',
9: 'Burned'
}
},
position: {
type: 'dropdown',
label: 'Position',
hover_tip: 'Position of the Progress Percentages element on the dashboard',
default: 'search',
content: {
top: 'Top of page',
below_srs: 'Below SRS boxes',
},
on_change: (setting, value)=>{
let elem = $('.progress_percentages');
elem.toggleClass('span12', value == "top")
if (value=="top") $('#search-form').before(elem);
if (value=="below_srs") $('.srs-progress').append(elem);
},
},
},
on_save: (()=>{
progress = {
jlpt: {1: {learned: 0, total: 1232}, 2: {learned: 0, total: 367}, 3: {learned: 0, total: 367}, 4: {learned: 0, total: 166}, 5: {learned: 0, total: 79}},
joyo: {1: {learned: 0, total: 80}, 2: {learned: 0, total: 160}, 3: {learned: 0, total: 200}, 4: {learned: 0, total: 200}, 5: {learned: 0, total: 185}, 6: {learned: 0, total: 181}, 9: {learned: 0, total: 1130}},
freq: {500: {learned: 0, total: 500}, 1000: {learned: 0, total: 500}, 1500: {learned: 0, total: 500}, 2000: {learned: 0, total: 500}, 2500: {learned: 0, total: 500}},
other: {nhk: {learned: 0, total: 0}, news: {learned: 0, total: 0}, aozora: {learned: 0, total: 0}, twitter: {learned: 0, total: 0}, wikipedia: {learned: 0, total: 0}}
};
update_progress()
.then(calculate_percentages)
.then(update_element);
})
};
var dialog = new wkof.Settings(config);
dialog.open();
}
// Updates element
function update_element(percentages) {
for (var key in percentages) {
for (var level in percentages[key]) {
var stage = (key == "jlpt" ? 6-level : level);
var elem = $('#'+key+'_'+stage)[0];
elem.title = percentages[key][stage].learned+(key!="other"?' of '+percentages[key][stage].total:"")+' learned';
elem.children[1].innerText = percentages[key][stage].percent+'%';
}
}
}
// Retreives lesson data
function update_progress() {
var resolve, promise = new Promise((res, rej)=>{resolve=res;});
var config = {wk_items: {options: {assignments: true},
filters: {item_type: 'kan',
include_jlpt_data: true,
include_joyo_data: true,
include_frequency_data: true
}
}
};
wkof.ItemData.get_items(config).then((data)=>{
for (var key in data) {
if (data[key].assignments && data[key].assignments.started_at != null) {
var keys = [["jlpt_level", "jlpt"],
["joyo_grade", "joyo"],
["frequency", "freq"],
["nhk_frequency", "nhk"],
["news_frequency", "news"],
["aozora_frequency", "aozora"],
["twitter_frequency", "twitter"],
["wikipedia_frequency", "wikipedia"]];
keys.forEach((val, i)=>{
var level = data[key][val[0]];
if (level && data[key].assignments.srs_stage >= wkof.settings.progress_percentages.threshold) {
if (level < 1) {
progress.other[val[1]].learned++;
progress.other[val[1]].total += level;
}
else progress[val[1]][level].learned++;
}
});
}
}
resolve();
});
return promise;
}
function calculate_percentages() {
var show_cum = wkof.settings.progress_percentages.cumulative;
var percentages = {jlpt: {1: {}, 2: {}, 3: {}, 4: {}, 5: {}},
joyo: {1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 9: {}},
freq: {500: {}, 1000: {}, 1500: {}, 2000: {}, 2500: {}},
other: {nhk: {}, news: {}, aozora: {}, twitter: {}, wikipedia: {}}};
for (var key in percentages) {
var cumulative = [0, 0];
for (var level in percentages[key]) {
var stage = (key == "jlpt" ? 6-level : level);
var learned = progress[key][stage].learned;
var total = progress[key][stage].total;
cumulative[0] += learned;
cumulative[1] += total;
let percent;
if (key != "other") percent = (show_cum ? cumulative[0]/cumulative[1] : learned/total);
else percent = total;
percent = (percent < 0.1 ? Math.floor(percent*1000)/10 : Math.floor(percent*100));
percentages[key][stage].percent = percent;
percentages[key][stage].learned = (show_cum ? cumulative[0] : learned);
percentages[key][stage].total = (show_cum ? cumulative[1] : total);
}
}
return percentages;
}
function display_data(percentages) {
// Add css
$('head').append(`<style id="progress_percentages">
.progress_percentages {
display: flex;
height: 28px;
background: #434343;
color: rgb(240, 240, 240);
line-height: 28px;
margin-bottom: 0;
border-radius: 5px;
text-align: center;
grid-row: 1;
grid-column: 1 / span 2;
margin-top: 15px;
}
.srs-progress .progress_percentages {
margin-top: 5px;
}
#search-form {
grid-row: 1;
}
.progress_percentages .PPprogress {
display: flex;
width: 100%;
justify-content: space-around;
}
.progress_percentages .PPbtn {
width: 20px;
height: auto;
color: rgb(240,240,240);
padding: 0 5px;
cursor: pointer;
font-size: 16px;
}
.progress_percentages .level {
font-weight: bold;
}
.progress_percentages .percent {
font-weight: normal !important;
}
.progress_percentages span {
font-size: 16px !important;
display: inline !important;
}
</style>`);
if (is_dark_theme()) {$('head').append(`<style id="progress_percentages_dark">
.progress_percentages {
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7), 2px 2px 2px rgba(0, 0, 0, 0.7);
}
.progress_percentages > div {
background: #232629 !important;
}
</style>`);}
// Add elements
var section = document.createElement('section');
section.className = 'progress_percentages';
var active_set = localStorage.getItem('WKProgressPercentagesActiveSet') || "jlpt";
var [next, prev] = get_new_sets(active_set);
var next_button = document.createElement('div');
next_button.className = 'next PPbtn';
next_button.innerHTML = '<i class="link icon-chevron-right"></i>';
next_button.onclick = toggle_percentages;
next_button.current = active_set;
next_button.next = next;
var prev_button = document.createElement('div');
prev_button.className = 'prev PPbtn';
prev_button.innerHTML = '<i class="link icon-chevron-left"></i>';
prev_button.onclick = toggle_percentages;
prev_button.current = active_set;
prev_button.next = prev;
var list = document.createElement('div');
list.className = 'PPprogress';
for (var key in percentages) {
for (var level in percentages[key]) {
var stage = (key == "jlpt" ? 6-level : level);
var prefix = (key == "jlpt" ? "N" : (key == "joyo" ? "G" : (key == "freq" ? "F" : "")));
var label = (key == "other" ? (stage == "nhk" ? "NHK" : stage.charAt(0).toUpperCase()+stage.slice(1)) : (key == "freq" ? stage/500 : stage));
$(list).append('<div class="'+key+'_percentages stage '+(key==active_set?"":"hidden")+'" id="'+key+'_'+stage+'" title="'+percentages[key][stage].learned+(key!="other"?' of '+percentages[key][stage].total:"")+' learned"><span class="level">'+prefix+label+' </span><span class="percent">'+percentages[key][stage].percent+'%</span></div>');
}
}
section.appendChild(prev_button);
section.appendChild(list);
section.appendChild(next_button);
if (wkof.settings.progress_percentages.position == "top") {
section.className += ' span12'
$('#search-bar').before(section);
}
else if (wkof.settings.progress_percentages.position == "below_srs") $(".srs-progress").append(section);
else $('#search-bar').before(section);
}
// Switches which percentages are showing
function toggle_percentages(event) {
var button = event.target;
if (button.nodeName == "I") button = button.parentElement;
var current_set = button.current;
var next_set = button.next;
$('.'+current_set+'_percentages').toggleClass('hidden');
$('.'+next_set+'_percentages').toggleClass('hidden');
var next_button = $('.progress_percentages .next')[0];
var prev_button = $('.progress_percentages .prev')[0];
var [next, prev] = get_new_sets(next_set);
next_button.next = next;
next_button.current = next_set;
prev_button.next = prev;
prev_button.current = next_set;
localStorage.setItem('WKProgressPercentagesActiveSet', next_set);
}
// Returns the next and previous sets
function get_new_sets(current_set) {
var sets = ["jlpt", "joyo", "freq", "other"];
var current_index = sets.indexOf(current_set);
return [sets[(current_index+1)%4], sets[(current_index+3)%4]];
}
// 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;
}
})();