I’ll look into it when I get the time
Thanks for the userscript
Fast question: for a breeze dark theme, is it possible to set the white background to a dark gray one instead? (The same shade that’s used for srs boxes)
UPD. Successfully found the attribute, no need for help anymore!
Coul you share details? I would also like to use this progress bar with the dark breeze theme
In the edit mode for the script, you’ll nee to find this setion:
And change background-color attribute value to #232629 (or any other different color of your liking). After that save the changes and refresh the page.
Pretty sure it used to have a dark background. I wonder what changed. Eventually I’ll get to fixing it for everyone
Looks like it worked with the original Breeze Dark, but not Breeze Dark 2.
It should work again in version 1.4.1
Version 1.4.2 should work better on small screens. Let me know if you still have any issues after updating
Looks perfect! You are a-ma-zing!
@Kumirei to have easy support both with and without BD2 I’d recommend using the WK CSS variables as much as possible - i.e. in this case setting the background color to var(--color-wk-panel-background)
instead of hardcoding it. That way it works without any custom styles applied (and will match the other Dashboard panels if WK were to slightly change the colour or something) and also works with BD2 without any issues, as for that as much as possible I just change the built-in WK CSS variables, to make it easier to support!
And if you want to be super safe around WK potentially making changes and breaking your styling, you could for example keep the hardcoded values as backup like this? var(--color-wk-panel-background, #f4f4f4)
EDIT: Oh, I hadn’t actually realised that there’s a setting in the script to change color scheme. I’d probably still recommend though switching to use the built-in CSS variables as much as possible - there’s ones for almost everything now. That way you have to change less when the use changes colour scheme, and you could probably even remove the setting completely and just let the colours auto-change based on the CSS variables. All just my opinion of course, you might have other reasons not to do that!
Ah with the theme selection it works. Thanks @Hubbit200 I wasn’t aware of this!
Since I already have the theme chooser in the settings it’s not a high priority to get it switched over to CSS variables. I agree that it would be for the best, but I have little time to work on scripts as it is
It’s broken since de update.
Fix - you need to add one line at the top and adjust 2 CSS parameters:
Top part - add line to header
// @require https://code.jquery.com/jquery-3.6.0.min.js
Adjust CSS for the .srs-level-graph
item, I changed only padding
and grid-auto-rows
const css = `.srs-level-graph {
justify-content: space-evenly;
gap: 0.2em;
padding: 12px 7px;
background-color: ${settings.theme === 'breeze' ? '#232629' : '#f4f4f4'};
border-radius: 5px;
margin: 0 0 5px 0 !important;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16px, 1fr));
grid-auto-rows: 44px;
}
Thank you for sharing this tip!
Doesn’t seem to work for me after adding the line to the header and adjusting the .srs-level-graph section.
Edit: Actually, it works, but only when set to display above SRS Counts in position in the settings. Any other position, it just does not appear.
I looked into the positioning as well. I have a solution, there are many changes so I’ll post the whole script.
// ==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
// @downloadURL https://update.greasyfork.org/scripts/441185/Wanikani%3A%20Overall%20Progress%20Bars.user.js
// @updateURL https://update.greasyfork.org/scripts/441185/Wanikani%3A%20Overall%20Progress%20Bars.meta.js
// @require https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==
;(async () => {
const { wkof, $ } = window
// Script info
const script_name = 'Overall Progress Bars'
const script_id = 'overall_progress_bar'
// Constants
const 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',
}
// 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()
$('.dashboard__content').prepend(
`<section class="srs-level-graph position-${settings.position}">${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: 12px 7px;
background-color: ${settings.theme === 'breeze' ? '#232629' : '#f4f4f4'};
border-radius: 5px;
margin: 0 0 5px 0 !important;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16px, 1fr));
grid-auto-rows: 44px;
}
.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;
}
/* Above Lessons (wide) */
.position-0 {grid-column: 1/span 6; grid-row: 1/2;}
.position-0 ~ .dashboard__lessons-and-reviews {grid-row: 2/3;}
.position-0 ~ .dashboard__recent-mistakes {grid-row: 3/4;}
.position-0 ~ .dashboard__extra-study {grid-row: 4/5;}
.position-0 ~ .dashboard__level-progress {grid-row: 5/6;}
.position-0 ~ .dashboard__review-forecast {grid-row: 2/6;}
.position-0 ~ .dashboard__srs-progress {grid-row: 6/7;}
.position-0 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-0 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above Lessons (narrow) */
.position-1 {grid-column: 1/span 4; grid-row: 1/2;}
.position-1 ~ .dashboard__lessons-and-reviews {grid-row: 2/3;}
.position-1 ~ .dashboard__recent-mistakes {grid-row: 3/4;}
.position-1 ~ .dashboard__extra-study {grid-row: 4/5;}
.position-1 ~ .dashboard__level-progress {grid-row: 5/6;}
.position-1 ~ .dashboard__review-forecast {grid-row: 1/6;}
.position-1 ~ .dashboard__srs-progress {grid-row: 6/7;}
.position-1 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-1 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above Recent Mistakes */
.position-2 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-2 {grid-column: 1/span 4; grid-row: 2/3;}
.position-2 ~ .dashboard__recent-mistakes {grid-row: 3/4;}
.position-2 ~ .dashboard__extra-study {grid-row: 4/5;}
.position-2 ~ .dashboard__level-progress {grid-row: 5/6;}
.position-2 ~ .dashboard__review-forecast {grid-row: 1/6;}
.position-2 ~ .dashboard__srs-progress {grid-row: 6/7;}
.position-2 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-2 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above Extra study */
.position-3 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-3 ~ .dashboard__recent-mistakes {grid-row: 2/3;}
.position-3 {grid-column: 1/span 4; grid-row: 3/4;}
.position-3 ~ .dashboard__extra-study {grid-row: 4/5;}
.position-3 ~ .dashboard__level-progress {grid-row: 5/6;}
.position-3 ~ .dashboard__review-forecast {grid-row: 1/6;}
.position-3 ~ .dashboard__srs-progress {grid-row: 6/7;}
.position-3 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-3 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above Level Progress */
.position-4 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-4 ~ .dashboard__recent-mistakes {grid-row: 2/3;}
.position-4 ~ .dashboard__extra-study {grid-row: 3/4;}
.position-4 {grid-column: 1/span 4; grid-row: 4/5;}
.position-4 ~ .dashboard__level-progress {grid-row: 5/6;}
.position-4 ~ .dashboard__review-forecast {grid-row: 1/6;}
.position-4 ~ .dashboard__srs-progress {grid-row: 6/7;}
.position-4 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-4 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above SRS Progress (narrow) */
.position-5 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-5 ~ .dashboard__recent-mistakes {grid-row: 2/3;}
.position-5 ~ .dashboard__extra-study {grid-row: 3/4;}
.position-5 ~ .dashboard__level-progress {grid-row: 4/5;}
.position-5 {grid-column: 1/span 4; grid-row: 5/6;}
.position-5 ~ .dashboard__review-forecast {grid-row: 1/6;}
.position-5 ~ .dashboard__srs-progress {grid-row: 6/7;}
.position-5 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-5 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above SRS Progress (wide) */
.position-6 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-6 ~ .dashboard__recent-mistakes {grid-row: 2/3;}
.position-6 ~ .dashboard__extra-study {grid-row: 3/4;}
.position-6 ~ .dashboard__level-progress {grid-row: 4/5;}
.position-6 ~ .dashboard__review-forecast {grid-row: 1/5;}
.position-6 {grid-column: 1/span 6; grid-row: 5/6;}
.position-6 ~ .dashboard__srs-progress {grid-row: 6/7;}
.position-6 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-6 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above Item Lists */
.position-7 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-7 ~ .dashboard__recent-mistakes {grid-row: 2/3;}
.position-7 ~ .dashboard__extra-study {grid-row: 3/4;}
.position-7 ~ .dashboard__level-progress {grid-row: 4/5;}
.position-7 ~ .dashboard__review-forecast {grid-row: 1/5;}
.position-7 ~ .dashboard__srs-progress {grid-row: 5/6;}
.position-7 {grid-column: 1/span 6; grid-row: 6/7;}
.position-7 ~ .dashboard__item-lists {grid-row: 7/8;}
.position-7 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Above Community Banner */
.position-8 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-8 ~ .dashboard__recent-mistakes {grid-row: 2/3;}
.position-8 ~ .dashboard__extra-study {grid-row: 3/4;}
.position-8 ~ .dashboard__level-progress {grid-row: 4/5;}
.position-8 ~ .dashboard__review-forecast {grid-row: 1/5;}
.position-8 ~ .dashboard__srs-progress {grid-row: 5/6;}
.position-8 ~ .dashboard__item-lists {grid-row: 6/7;}
.position-8 {grid-column: 1/span 6; grid-row: 7/8;}
.position-8 ~ .dashboard__community-banner {grid-row: 8/9;}
/* Bottom */
.position-9 ~ .dashboard__lessons-and-reviews {grid-row: 1/2;}
.position-9 ~ .dashboard__recent-mistakes {grid-row: 2/3;}
.position-9 ~ .dashboard__extra-study {grid-row: 3/4;}
.position-9 ~ .dashboard__level-progress {grid-row: 4/5;}
.position-9 ~ .dashboard__review-forecast {grid-row: 1/5;}
.position-9 ~ .dashboard__srs-progress {grid-row: 5/6;}
.position-9 ~ .dashboard__item-lists {grid-row: 6/7;}
.position-9 ~ .dashboard__community-banner {grid-row: 7/8;}
.position-9 {grid-column: 1/span 6; grid-row: 8/9;}
${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: ['Above Lessons (wide)', 'Above Lessons (narrow)', 'Above Recent Mistakes', 'Above Extra study', 'Above Level Progress', 'Above SRS Progress (narrow)', 'Above SRS Progress (wide)', 'Above Item Lists', 'Above Community Banner', 'Bottom'],
},
},
}
let dialog = new wkof.Settings(config)
dialog.open()
}
})()
I am not sure how this interacts with other scripts, but if you need to change it, here is what I did:
- removed
positions
and instead the section added by script has new classposition-N
where N is position number - when adding element to page, it is appended to top of dashboard above all other sections so it can affect their positions
- the CSS lists new grid positions for each position, and for all page sections to fit added section
This worked, many thanks!
thanks buddy, worked for me as well