Wanikani: Levels by SRS

Displays your level for each SRS stage.

Mint 2019.11.25.. Lásd a legutóbbi verzió

// ==UserScript==
// @name         Wanikani: Levels by SRS
// @namespace    http://tampermonkey.net/
// @version      1.1.1
// @description  Displays your level for each SRS stage.
// @author       Kumirei
// @match        https://www.wanikani.com/dashboard
// @match        https://www.wanikani.com
// @include      *preview.wanikani.com*
// @grant        none
// ==/UserScript==

(function() {
		//check that the Wanikani Framework is installed
		var script_name = 'Levels By SRS';
		if (!window.wkof) {
				if (confirm(script_name+' requires Wanikani Open Framework.\nDo you want to be forwarded to the installation instructions?'))
						window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
				return;
		}
		//if it's installed then do the stuffs
		else {
				wkof.include('Menu,Settings,ItemData');
				wkof.ready('Menu,Settings,ItemData')
						.then(load_settings)
						.then(install_menu)
						.then(add_css)
						.then(fetch_and_update);
		}

		// Fetches items and updates display
		function fetch_and_update() {
				fetch_items()
						.then(process_items)
						.then(update_display);
		}

		// Fetches the relevant items
		function fetch_items() {
				var [promise, resolve] = new_promise();
				var config = {
						wk_items: {
								options: {assignments: true},
								filters: {level: "1..+0"}
						}
				};
				wkof.ItemData.get_items(config).then(resolve);
				return promise;
		}

		// Retreives the levels by srs
		function process_items(data) {
				// Sort by level
				var levels = {};
				for (var i=0; i<data.length; i++) {
						var item = data[i];
						var level = item.data.level;
						if (!levels[level]) levels[level] = [];
						levels[level].push(item);
				}
				// Go through items level by level
				var srs_levels = {Ini: [0, 0, 0], App: [0, 0, 0], Gur: [0, 0, 0], Mas: [0, 0, 0], Enl: [0, 0, 0], Bur: [0, 0, 0]};
				for (i=1; i<wkof.user.level+1; i++) {
						// Get counts for level
						var by_srs = {Ini: 0, App: 0, Gur: 0, Mas: 0, Enl: 0, Bur: 0, total: 0};
						for (var j=0; j<levels[i].length; j++) {
								item = levels[i][j];
								if (item.assignments) by_srs[item.assignments.srs_stage_name.slice(0, 3)]++;
								else by_srs['Ini']++;
								by_srs.total++;
						}
						// Check if srs_level should be increased
						var types = ['Ini', 'App', 'Gur', 'Mas', 'Enl', 'Bur'];
						var cumulative = 0;
						for (j=0; j<types.length; j++) {
								var count = by_srs[types[j]];
								if (1-cumulative/by_srs.total >= wkof.settings.levels_by_srs.threshold/100 && i == srs_levels[types[j]][0]+1) {
										srs_levels[types[j]][0]++;
								}
								else if (1-cumulative/by_srs.total <= wkof.settings.levels_by_srs.threshold/100 && i == srs_levels[types[j]][0]+1) {
										srs_levels[types[j]][1] = 1-cumulative/by_srs.total;
										srs_levels[types[j]][2] = by_srs.total;
								}
								cumulative += count;
						}
				}
				return srs_levels;
		}

		// Updates the display element
		function update_display(srs_levels) {
				var types = ['App', 'Gur', 'Mas', 'Enl', 'Bur'];
				// If the element doesn't already exist, create it
				var display = $('#levels_by_srs');
				if (!display.length) {
						display = $('<div id="levels_by_srs"'+(is_dark_theme() ? ' class="dark_theme"' : '')+'></div>');
						for (var i=0; i<types.length; i++) display.append('<div class="'+types[i]+'"><span class="level_label">Level: </span><span class="value"></span></div>');
						$('.srs-progress').after(display);
				}
				// Update
				for (i=0; i<types.length; i++) {
						var elem = $(display).find('.'+types[i]+' span.value')[0];
						elem.innerText = srs_levels[types[i]][0];
						elem.parentElement.setAttribute('title', Math.floor(srs_levels[types[i]][1]*100) + '% to level ' + (srs_levels[types[i]][0]+1) + ' (' + Math.round(srs_levels[types[i]][1] * srs_levels[types[i]][2]) + ' of ' + srs_levels[types[i]][2] + ')');
				}
		}

		// Load stored settings or set defaults
		function load_settings() {
				var defaults = {threshold: 90};
				return wkof.Settings.load('levels_by_srs', defaults);
		}

		// Installs the options button in the menu
		function install_menu() {
				var config = {
						name: 'levels_by_srs',
						submenu: 'Settings',
						title: 'Levels By SRS',
						on_click: open_settings
				};
				wkof.Menu.insert_script_link(config);
		}

		// Create the options
		function open_settings(items) {
				var config = {
						script_id: 'levels_by_srs',
						title: 'Levels By SRS',
						on_save: fetch_and_update,
						content: {
								threshold: {
										type: 'number',
										label: 'Threshold',
										hover_tip: 'Percentage to consider level done',
										default: 90,
								}
						}
				}
				var dialog = new wkof.Settings(config);
				dialog.open();
		}

		// Adds the script's CSS to the page
		function add_css() {
				$('head').append('<style id="levels_by_srs_CSS">'+
								 '#levels_by_srs {'+
								 '    background: #434343;'+
								 '    border-radius: 0 0 3px 3px;'+
								 '    height: 30px;'+
								 '    line-height: 30px;'+
								 '    color: rgb(240, 240, 240);'+
								 '    font-size: 16px;'+
								 '}'+
								 '#levels_by_srs > div {'+
								 '    width: calc(20% - 1px);'+
								 '    display: inline-block;'+
								 '    text-align: center;'+
								 '}'+
								 '#levels_by_srs .level_label {'+
								 '    font-weight: bold;'+
								 '}'+
								 '.dashboard > .container > .row > .span12 > section.srs-progress {'+
								 '    margin-bottom: 0 !important;'+
								 '}'+
								 '.srs-progress > ul > li {'+
								 '    border-radius: 0 !important;'+
								 '}'+
								 '#levels_by_srs.dark_theme {'+
								 '    background: #232629;'+
								 '    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7), 2px 2px 2px rgba(0, 0, 0, 0.7);'+
								 '    margin: 0 3px 3px 3px;'+
								 '}'+
								 '#levels_by_srs.dark_theme > div:not(:last-child) {'+
								 '    border-right: 1px solid #31363b;'+
								 '}'+
								 '</style>');
		}

		// Returns a promise and a resolve function
		function new_promise() {
				var resolve, promise = new Promise((res, rej)=>{resolve = res;});
				return [promise, resolve];
		}

		// 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;
		}
})();