您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Creates a progress bar on the dashboard for every level
// ==UserScript== // @name Wanikani: Overall Progress Bars // @namespace http://tampermonkey.net/ // @version 1.4.2 // @description Creates a progress bar on the dashboard for every level // @author Kumirei // @include /^https://(www|preview).wanikani.com/(dashboard)?$/ // @grant none // @license MIT // ==/UserScript== ;(async () => { const { wkof, $ } = window // Script info const script_name = 'Overall Progress Bars' const script_id = 'overall_progress_bar' // Constants srs_names = { '-1': 'Locked', 0: 'In lessons', 1: 'Apprentice 1', 2: 'Apprentice 2', 3: 'Apprentice 3', 4: 'Apprentice 4', 5: 'Guru 1', 6: 'Guru 2', 7: 'Master', 8: 'Enlightened', 9: 'Burned', } const positions = [ () => $('.progress-and-forecast'), () => $('.srs-progress'), () => $('.recent-unlocks').closest('.row'), () => $('.forum-topics-list').closest('.row'), ] // Init confirm_wkof() wkof.include('Menu,Settings,ItemData') await wkof.ready('Menu,Settings,ItemData').then(load_settings).then(install_menu) const settings = wkof.settings[script_id] let color // Get items by level const items = await wkof.ItemData.get_items('assignments') const items_by_level = wkof.ItemData.get_index(items, 'level') // Get counts per SRS level per level const counts_by_level_and_srs = {} for (let [level, items] of Object.entries(items_by_level)) { counts_by_level_and_srs[level] = Object.fromEntries( Object.entries(wkof.ItemData.get_index(items, 'srs_stage')).map(([srs, items]) => [srs, items.length]), ) } display() function display() { set_color_theme() injectCss() $('.srs-level-graph').remove() positions[settings.position]().before( `<section class="srs-level-graph">${Object.entries(counts_by_level_and_srs) .filter(([level]) => { if (level > settings.max_level) return false if (settings.hide_locked && level > wkof.user.level) return false return true }) .map(get_level) .join('')}</section`, ) } function set_color_theme() { color = { '-1': settings.theme === 'breeze' ? '#31363B' : '#aaaaaa', 0: settings.theme === 'breeze' ? '#31363B' : '#aaaaaa', 1: settings.theme === 'breeze' ? '#3fbbf3' : '#ff00bb', 2: settings.theme === 'breeze' ? '#2eaaf4' : '#ee00aa', 3: settings.theme === 'breeze' ? '#1d99f3' : '#dd0099', 4: settings.theme === 'breeze' ? '#0c88e2' : '#cc0088', 5: settings.theme === 'breeze' ? '#1cdc9a' : '#9339aa', 6: settings.theme === 'breeze' ? '#1cdc9a' : '#882d9e', 7: settings.theme === 'breeze' ? '#c9ce3b' : '#294ddb', 8: settings.theme === 'breeze' ? '#f67400' : '#0093dd', 9: settings.theme === 'breeze' ? '#da4453' : '#FAAF0E', } } function get_level([level, counts_by_srs]) { const total = Object.values(counts_by_srs).reduce((sum, val) => sum + val, 0) const bars = Object.entries(counts_by_srs) .map((item) => get_bar(...item, total)) .join('') return `<div class="level"><div class="bars" style="background: ${get_color( counts_by_srs, )};">${bars}</div><div class="lbl" title="Level ${level}">${level}</div></div>` } function get_bar(srs_level, count, total) { const percent = Math.round((count / total) * 100) return `<div class="srs" data-srs="${srs_level}" title="${srs_names[srs_level]}: ${count} / ${total} items (${percent}%)" style="flex-grow: ${count}"></div>` } function get_color(counts_by_srs) { if (!['blend', 'avg_srs'].includes(settings.display)) return '' const srs_levels = Object.entries(counts_by_srs).reduce( (srs_items, [srs_level, count]) => srs_items.concat(new Array(count).fill(srs_level < 0 ? 0 : srs_level)), [], ) const avg_srs = srs_levels.reduce((sum, val) => sum + Number(val), 0) / srs_levels.length if (settings.display === 'blend') { return averageColors(srs_levels) } else if (settings.display === 'avg_srs') { return interpolate_color(color[Math.floor(avg_srs)], color[Math.ceil(avg_srs)], avg_srs % 1) } return '' } function averageColors(levelItems) { let itemValues = [0, 0, 0] let averageColor = '#' // For each level, a mean average is taken of each item's color for (let i = 0; i < levelItems.length; i++) { for (let j = 0; j < 3; j++) { itemValues[j] += parseInt('0x0' + color[levelItems[i]].slice(2 * j + 1, 2 * j + 3), 16) } } // Divide by the total to get the means for RGB for (let k = 0; k < itemValues.length; k++) { itemValues[k] = itemValues[k] / levelItems.length itemValues[k] = Math.round(itemValues[k]) } // Convert back into hex averageColor = '#' + itemValues.map((a) => `00${parseInt(a, 10).toString(16)}`.slice(-2)).join('') return averageColor } function interpolate_color(a, b, amount) { var ah = parseInt(a.replace(/#/g, ''), 16), ar = ah >> 16, ag = (ah >> 8) & 0xff, ab = ah & 0xff, bh = parseInt(b.replace(/#/g, ''), 16), br = bh >> 16, bg = (bh >> 8) & 0xff, bb = bh & 0xff, rr = ar + amount * (br - ar), rg = ag + amount * (bg - ag), rb = ab + amount * (bb - ab) return '#' + (((1 << 24) + (rr << 16) + (rg << 8) + rb) | 0).toString(16).slice(1) } function injectCss() { let srs_css = '' for (let i = -1; i < 10; i++) { srs_css += ` .srs-level-graph .srs[data-srs="${i}"] { background-color: ${color[i]}; ${settings.display !== 'bars' ? 'width: 100' : 'height: ' + (i + 1) * 10}%; }` } const css = `.srs-level-graph { justify-content: space-evenly; gap: 0.2em; padding: 16px 12px 12px; background-color: ${settings.theme === 'breeze' ? '#232629' : '#f4f4f4'}; border-radius: 5px; margin: 0 0 30px 0 !important; display: grid; grid-template-columns: repeat(auto-fit, minmax(16px, 1fr)); grid-auto-rows: 3em; } .srs-level-graph .level { width: 100%; display: flex; flex-direction: column; } .srs-level-graph .bars { display: flex; align-items: flex-end; flex-grow: 1; flex-direction: ${settings.display !== 'bars' ? 'column' : 'row-reverse'}; } .srs-level-graph .lbl { font-size: 0.75em; line-height: 1.5em; text-align: center; vertical-align: bottom; } .srs-level-graph .srs { ${['blend', 'avg_srs'].includes(settings.display) ? 'display:none;' : ''} ${settings.display !== 'bars' ? '' : 'border-radius: 0.1em 0.1em 0 0;'} } .srs-level-graph .srs[data-srs="-1"] { order: -1; } ${srs_css}` $(`#overall-progress-bars-css`).remove() $('head').append(`<style id="overall-progress-bars-css">${css}</style>`) } /* ----------------------------------------------------------*/ // WKOF setup /* ----------------------------------------------------------*/ // Makes sure that WKOF is installed async function confirm_wkof() { if (!wkof) { let response = confirm( `${script_name} requires WaniKani Open Framework.\nClick "OK" to be forwarded to installation instructions.`, ) if (response) { window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549' } return } } // Load WKOF settings function load_settings() { const defaults = { display: 'stack', max_level: 60, hide_locked: false, theme: 'default', position: 0, } return wkof.Settings.load(script_id, defaults) } // Installs the options button in the menu function install_menu() { const config = { name: script_id, submenu: 'Settings', title: script_name, on_click: open_settings, } wkof.Menu.insert_script_link(config) } // Opens settings dialogue when button is pressed function open_settings() { let config = { script_id: script_id, title: script_name, on_save: display, content: { display: { type: 'dropdown', label: 'Display as', default: 'stack', hover_tip: 'Changes how the bars look', content: { stack: 'Stack', bars: 'Bars', avg_srs: 'Single Color (average SRS)', blend: 'Single Color (blend)', }, }, max_level: { type: 'number', label: 'Max Level', hover_tip: 'The highest level to display', max: 60, min: 1, }, hide_locked: { type: 'checkbox', default: false, label: 'Hide Locked Levels', hover_tip: 'Do not display bars above your current level', }, theme: { type: 'dropdown', label: 'Theme', default: 0, hover_tip: 'Changes the colors of the bars', content: { default: 'Default', breeze: 'Breeze Dark' }, }, position: { type: 'dropdown', label: 'Position', default: 0, hover_tip: 'Changes the colors of the bars', content: ['Top', 'Above SRS Counts', 'Above Panels', 'Above Recent Topics'], }, }, } let dialog = new wkof.Settings(config) dialog.open() } })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址