您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds review and lesson heatmaps to the dashboard.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/410910/845491/Heatmap.js
// ==UserScript== // @name Heatmap // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description Adds review and lesson heatmaps to the dashboard. // @author Kumirei // @include /^https://(www|preview).wanikani.com/(dashboard)?$/ // @grant none // ==/UserScript== (function($) { class Heatmap { constructor(config, data) { this.maps = {}; this.config = config; this.data = {}; if (data !== undefined) this.initiate(data); } initiate(data) { let dates = this._get_dates(data); let parsed_data = this._parse_data(data, dates); this.data = parsed_data; if (this.config.type === "year") { for (let year=dates.first_year; year<=dates.last_year; year++) { this.maps[year] = this._init_year(year, parsed_data, dates); } this._add_markings(this.config.markings, this.maps); } if (this.config.type === "day") { this.maps.day = this._init_single_day(parsed_data, dates); } } _parse_data(data, dates) { let parsed_data = {}; for (let year=dates.first_year; year<=dates.last_year; year++) { parsed_data[year] = {}; for (let month=1; month<=12; month++) { parsed_data[year][month] = {}; for (let day=0; day<=31; day++) { parsed_data[year][month][day] = {counts: {}, lists: {}, hours: new Array(24).fill().map(()=>{return {counts: {}, lists: {}}})}; } } } for (let [date, counts, lists] of data) { let [year, month, day, hour] = this._get_ymdh(date-1000*60*60*this.config.day_start); if (date-1000*60*60*this.config.day_start < new Date(this.config.first_date).getTime() || date-1000*60*60*this.config.day_start > new Date(this.config.last_date || date+1).getTime()) continue; let parsed_day = parsed_data[year][month][day]; for (let [key, value] of Object.entries(counts)) { if (!parsed_day.counts[key]) parsed_day.counts[key] = value || 0; else parsed_day.counts[key] += value || 0; if (!parsed_day.hours[hour].counts[key]) parsed_day.hours[hour].counts[key] = value; else parsed_day.hours[hour].counts[key] += value; } for (let [key, value] of Object.entries(lists)) { if (!parsed_day.lists[key]) parsed_day.lists[key] = [value]; else parsed_day.lists[key].push(value); if (!parsed_day.hours[hour].lists[key]) parsed_day.hours[hour].lists[key] = [value]; else parsed_day.hours[hour].lists[key].push(value); } } return parsed_data; } _init_year(year, data, dates) { let year_elem = this._create_elem({type: 'div', class: 'year heatmap '+this.config.id+(this.config.segment_years?' segment_years':'')+(this.config.zero_gap?' zero_gap':'')}); year_elem.setAttribute('data-year', year); let labels = this._create_elem({type: 'div', class: 'year-labels', children: this._get_year_labels(year)}); let months = this._create_elem({type: 'div', class: 'months'}); for (let month=1; month<=12; month++) { months.append(this._init_month(year, month, data, dates)); } year_elem.append(labels, months); return year_elem; } _get_year_labels(year) { let year_label = this._create_elem({type: 'div', class: 'year-label hover-wrapper-target', children: [String(year), this._create_elem({type: 'div', class: 'hover-wrapper above', child: year})]}); let day_labels = this._create_elem({type: 'div', class: 'day-labels'}); for (let day=0; day<7; day++) { day_labels.append(this._create_elem({type: 'div', class: 'day-label', child: ['M','T','W','T','F','S','S'][(day+Number(this.config.week_start))%7]})); }; return [year_label, day_labels]; } _init_month(year, month, data, dates) { let offset = (new Date(year+'-'+month+'-01').getDay()+6-this.config.week_start)%7; let month_elem = this._create_elem({type: 'div', class: 'month offset-'+offset}); if (year===dates.first_year && month<dates.first_month) month_elem.classList.add('no-data'); let label = this._create_elem({type: 'div', class: 'month-label', child: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][month-1]}); let days = this._create_elem({type: 'div', class: 'days'}); let days_in_month = this._get_days_in_month(year, month); for (let day=1; day<=days_in_month; day++) { days.append(this._init_day(year, month, day, data, dates)); } month_elem.append(label, days); return month_elem; } _init_day(year, month, day, data, dates) { let day_data = data[year][month][day]; let day_elem = this._create_elem({type: 'div', class: 'day hover-wrapper-target', info: {counts: day_data.counts, lists: day_data.lists}, child: this._create_elem({type: 'div', class: 'hover-wrapper', child: this.config.day_hover_callback([year, month, day], day_data)})}); day_elem.setAttribute('data-date', `${year}-${month}-${day}`); if (year===dates.first_year && month===dates.first_month && day<dates.first_day) day_elem.classList.add('no-data'); day_elem.style.setProperty('background-color', this.config.color_callback([year, month, day], day_data)); return day_elem; } _init_single_day(data, dates) { let day = this._create_elem({type: 'div', class: 'single-day '+this.config.id}); let hour_data = data[dates.first_year][dates.first_month][dates.first_day].hours; let current_hour = new Date().getHours(); for (let i=0; i<24; i++) { let j = (i+this.config.day_start)%24; let hour = this._create_elem({type: 'div', class: 'hour hover-wrapper-target'+(j===current_hour?' today marked':''), info: {counts: hour_data[i].counts, lists: hour_data[i].lists}}); let hover = this._create_elem({type: 'div', class: 'hover-wrapper', child: this.config.day_hover_callback([dates.first_year, dates.first_month, dates.first_day, j], hour_data[i])}); hour.append(hover); hour.style.setProperty('background-color', this.config.color_callback([dates.first_year, dates.first_month, dates.first_day, j], hour_data[i])); day.append(hour); } day.instance = this; return day; } _add_markings(markings, years) { for (let [date, mark] of markings) { let [year, month, day] = this._get_ymdh(date); if (years[year]) years[year].querySelector(`.day[data-date="${year}-${month}-${day}"]`).classList.add(...mark.split(' '), 'marked'); } } _get_days_in_month(year, month) {return [31, this._is_leap_year(year)?29:28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month-1];} _is_leap_year(year) {return year%4==0 && (year%100!=0 || year%400==0);} _create_elem(config) { let div = document.createElement(config.type); for (let [attr, value] of Object.entries(config)) { if (attr === "type") continue; else if (attr === "class") div.className = value; else if (attr === "child") div.append(value); else if (attr === "children") div.append(...value); else div[attr] = value; } return div; } _get_dates() { let [first_year, first_month, first_day] = this._get_ymdh(this.config.first_date); let [last_year, last_month, last_day] = this._get_ymdh(this.config.last_date || Date.now()); return {first_year, first_month, first_day, last_year, last_month, last_day, }; } _get_ymdh(date) {let d = new Date(date); return [d.getFullYear(), d.getMonth()+1, d.getDate(), d.getHours()];} } window.Heatmap = Heatmap; })();