// ==UserScript==
// @name Wanikani: Progress Percentages
// @namespace http://tampermonkey.net/
// @version 1.2.8
// @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://greasyfork.org/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'
$('.progress-and-forecast').before(section)
} else if (wkof.settings.progress_percentages.position == 'below_srs') $('.srs-progress').append(section)
else $('.progress-and-forecast').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
)
}
})()