Wanikani: Levels by SRS

Displays your level for each SRS stage.

当前为 2020-03-12 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Wanikani: Levels by SRS
// @namespace    http://tampermonkey.net/
// @version      1.1.2
// @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').append(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);
								      display: flex;
								      justify-content: space-around;
								  }
								  #levels_by_srs > div {
								      display: flex;
								  }
								  #levels_by_srs .level_label {
                                      font-size: 16px;
                                      margin-right: 5px;
								  }
                                  #levels_by_srs .value {
                                      font-size: 16px;
								      font-weight: normal;
                                  }
								  #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;
								  }
								  .srs-progress > ul > li {
								      border-radius: 0 !important;
								  }
								  </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;
		}
})();