您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Complete alternate dashboard style for the basic panels
// ==UserScript== // @name WaniKani Dashboard Cockpit // @namespace https://greasyfork.org/en/users/1364747 // @version 0.7.6 // @description Complete alternate dashboard style for the basic panels // @include /^https:\/\/(www|preview)\.wanikani\.com(\/(#)?dashboard)?(\/)?$/ // @run-at document-end // @grant none // @license GPL-3.0-or-later // ==/UserScript== (function () { 'use strict'; /* global $, wkof */ if (!window.wkof) { let response = confirm('WaniKani Dashboard Cockpit script requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.'); if (response) { window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549'; } return; } wkof.include('Menu,Settings,ItemData'); wkof.ready('Menu,Settings') .then(load_settings) .then(install_menu) .then(add_css) .then(createPanels) .then(createPanelContents) .then(displayCache) .then(wkof.ready('ItemData').then(fetch_and_update)); //-----------------------------------------------------------------------------------------------------------------------------------------------------// //-------------------------------------------------------------------LOGIC AND RULES-------------------------------------------------------------------// //-----------------------------------------------------------------------------------------------------------------------------------------------------// var CACHE_VERSION = '0.2'; var CACHE_KEY = 'db_cockpit_cache'; function displayCache() { var cached_json = localStorage.getItem(CACHE_KEY); if (cached_json) { var cached = JSON.parse(cached_json); if (cached.version == CACHE_VERSION) { lockedCount = cached.lockedCount; update_display(cached.itemBreakdown); } else if (cached.version == '0.1') { cached.itemBreakdown.allPassed = false; lockedCount = cached.lockedCount; update_display(cached.itemBreakdown); } } } function fetch_and_update() { fetch_locked_items() .then(processLockedItems) .then(fetch_assignment_items) .then(constructItemBreakdown) .then(update_display); } function update_display(itemBreakdown) { repositionMainPanel(); loadSizes(); clearCanvas(); drawSRSProgress(itemBreakdown); drawSRSProgressDetail(itemBreakdown); drawSRSProgressLeechCounts(itemBreakdown); drawSRSProgressLevel(itemBreakdown); // works on global var because of dual wkof retrieve // createLockedCount(itemBreakdown); createTotalProgressIndication(itemBreakdown); displayGrid(itemBreakdown.allPassed); reOffsetCanvas(); // fires a custom event to signal cockpit loaded const event = document.createEvent('Event'); event.initEvent("dashboard-cockpit-loaded", true, true); $('#cockpitPanel')[0].dispatchEvent(event); } // Fetches the relevant items function fetch_assignment_items() { var config = { wk_items: { options: { review_statistics: true, assignments: true }, filters: { srs: '0,1,2,3,4,5,6,7,8,9' } } }; return wkof.ItemData.get_items(config); } function fetch_locked_items() { const config = { wk_items: { options: { assignments: true }, filters: { srs: '-1' } } }; return wkof.ItemData.get_items(config); } // filtering by srs abbrev (App|Mas|Guru1|...) function filterBySRSCountOnAbbrev(counts, abbrev) { return counts.filter(function (countItem) { return countItem.name.slice(0, abbrev.length) == abbrev; }); } var lockedCount = { name: "Loc", total: 0, radical: 0, kanji: 0, vocabulary: 0 }; const SUBJECT_TYPE_MAP = { 'radical': 'radical', 'kanji': 'kanji', 'vocabulary': 'vocabulary', 'kana_vocabulary': 'vocabulary' }; function processLockedItems(data) { const type = wkof.ItemData.get_index(data, 'item_type'); lockedCount = { name: "Loc", total: 0, radical: 0, kanji: 0, vocabulary: 0 }; lockedCount.total = data.length; Object.keys(SUBJECT_TYPE_MAP).forEach(itemType => { var mappedType = SUBJECT_TYPE_MAP[itemType]; lockedCount[mappedType] += (type[itemType] || []).length; }); } // helper function to construct the counts overview objects function constructBreakdown() { return { total: 0, progressScore: 0, bySRSArray: [ { name: "Loc", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Ini", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Appr1", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Appr2", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Appr3", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Appr4", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Guru1", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Guru2", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Mas", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Enl", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 }, { name: "Bur", total: 0, radical: 0, kanji: 0, vocabulary: 0, leech: 0 } ] }; } // constants for the intervals var srsConstants = { duration: [0, 14400, 28800, 82800, 169200, 601200, 1206000, 2588400, 10364400, 0], stageWeight: [0, 1, 3, 5, 7, 10, 14, 20, 28, 36], stageProgressWeight: [0, 1, 1, 1, 1, 2, 2, 4, 4, 0] } // run past the list of items (data) to produce the various counts we need function constructItemBreakdown(data) { var itemBreakdown = { level: 0, allPassed: true, // start optimistically countBySRS: constructBreakdown(), countByLevel: [] }; var now = new Date(); // get the setting, but enforce range 1-4 var leechThreshold = Math.min(4, Math.max(1, wkof.settings.db_cockpit.leechThreshold)); var leechMinWrong = Math.min(10, Math.max(1, wkof.settings.db_cockpit.leechMinWrong)); var processItem = function (item) { var level = item.data.level; if (!itemBreakdown.countByLevel[level]) { itemBreakdown.countByLevel[level] = constructBreakdown(); } if (item.assignments) { var srsStage = item.assignments.srs_stage; itemBreakdown.countByLevel[level].bySRSArray[srsStage + 1][SUBJECT_TYPE_MAP[item.assignments.subject_type]]++; itemBreakdown.countByLevel[level].bySRSArray[srsStage + 1].total++; itemBreakdown.countBySRS.bySRSArray[srsStage + 1][SUBJECT_TYPE_MAP[item.assignments.subject_type]]++; itemBreakdown.countBySRS.bySRSArray[srsStage + 1].total++; // calculate progress score for this item var stageScore = srsConstants.stageWeight[item.assignments.srs_stage]; if (item.assignments.available_at != undefined) { stageScore += srsConstants.stageProgressWeight[srsStage] * (1 - (Math.max(((new Date(item.assignments.available_at) - now)), 0) / (1000 * srsConstants.duration[srsStage]))); } itemBreakdown.countByLevel[level].progressScore += stageScore; // check for leech status if (isLeech(item, leechThreshold, leechMinWrong)) { // console.log(\"Item \" + item.data.characters + \" identified as leech due to (meaning, reading) (\" + item.review_statistics.meaning_incorrect + \", \" + item.review_statistics.reading_incorrect + \") wrong answers\"); itemBreakdown.countBySRS.bySRSArray[srsStage + 1].leech++; } // check for passed itemBreakdown.allPassed = itemBreakdown.allPassed && (item.assignments.passed == undefined ? item.assignments.passed_at != null : item.assignments.passed); } else { // for locked items we have no information on type - this will need to be retrieved separately to display locked detail info itemBreakdown.countByLevel[level].bySRSArray[0].total++; itemBreakdown.countBySRS.bySRSArray[0].total++; itemBreakdown.allPassed = false; } itemBreakdown.countByLevel[level].total++; itemBreakdown.countBySRS.total++; } data.forEach(processItem); // and grab the user's current level itemBreakdown.level = $('li.user-summary__attribute a')[0].href.split('/level/')[1]; // cache the results for next page load var json = { version: CACHE_VERSION, itemBreakdown: itemBreakdown, lockedCount: lockedCount }; localStorage.setItem(CACHE_KEY, JSON.stringify(json)); return itemBreakdown; } function isLeech(item, threshold, leechMinWrong) { if (item.review_statistics === undefined) { return false; } // don't treat items that you got wrong once or twice as leeches immediately var sufficientErrors = ((item.review_statistics.meaning_incorrect + item.review_statistics.reading_incorrect) >= leechMinWrong); if (!sufficientErrors) { return false; } let reviewStats = item.review_statistics; let meaningScore = getLeechScore(reviewStats.meaning_incorrect, reviewStats.meaning_current_streak); let readingScore = getLeechScore(reviewStats.reading_incorrect, reviewStats.reading_current_streak); return meaningScore >= threshold || readingScore >= threshold; } function getLeechScore(incorrect, currentStreak) { return incorrect / Math.pow((currentStreak || 0.5), 1.5); } //-----------------------------------------------------------------------------------------------------------------------------------------------------// //---------------------------------------------------------INTEGRATION WITH EXTRA STUDY MOVER----------------------------------------------------------// //-----------------------------------------------------------------------------------------------------------------------------------------------------// // detects if the Extra Study Mover script is present. It should execute BEFORE the cockpit function isESMPresent() { return $('.es-mover').length; } // get the configured location from the Extra Study Mover added class // note: this also retrieves the applied style, but we can ignore that later function getESMLocation() { if (isESMPresent()) { var classList = $('.wk-panel--extra-study')[0].classList; return Array.from([...classList]).filter(name => name.startsWith('es-mover-')).map(name => name.slice(9)); } return []; } // true if Extra Study Mover is present and it isn't configured for header or none // false if it is configured for header or none // AND false if Extra Study Mover does not exist function moveToESMLocation(esmLocations) { var useLocations = esmLocations; if (!useLocations) useLocations = getESMLocation(); return !(useLocations.filter(name => (name == "header"))).length; } // true if the cockpit should move the Extra Study Panel at all function moveExtraStudy() { return !isESMPresent() || moveToESMLocation(); } // translates both the ESM location (if present) and the cockpit setting (if needed) // to an equivalent cockpit setting function getExtraStudyLocationSetting() { if (!isESMPresent()) { return wkof.settings.db_cockpit.showExtraStudy; } else { var esmLocations = getESMLocation(); if (moveToESMLocation(esmLocations)) { for (var loc in esmLocations) { if (esmLocations[loc] == 'top') return '7'; // cockpit doesn't have a proper top position available, so center top will have to do if (esmLocations[loc] == 'above-rm') return '8'; if (esmLocations[loc] == 'above-lp') return '1'; if (esmLocations[loc] == 'below-lp') return '6'; if (esmLocations[loc] == 'above-ib') return '5'; if (esmLocations[loc] == 'below-ib') return '2'; if (esmLocations[loc] == 'above-rf') return '3'; if (esmLocations[loc] == 'below-rf') return '4'; if (esmLocations[loc] == 'none') return '0'; // else zip, this might be a style, so go for the next one } } else { // not allowed to move (header) return '-1'; } } // we only get here if ESM adds an extra location that we // don't recognise. Default to Above Level Progress console.log("Unknown Extra Study Panel location. Defaulting to Above Level Progress"); return '1'; } //-----------------------------------------------------------------------------------------------------------------------------------------------------// //---------------------------------------------------------INTEGRATION WITH REVIEW SUMMARY-------------------------------------------------------------// //-----------------------------------------------------------------------------------------------------------------------------------------------------// // detects if the Review Summary script is present. It should execute BEFORE the cockpit function isRSPresent() { return $('#reviewSummaryDiv').length; } function isRSVisible() { return isRSPresent() && wkof.settings.db_cockpit.showReviewSummary; } //-----------------------------------------------------------------------------------------------------------------------------------------------------// //---------------------------------------------------------------COMBINATION VISIBILITY----------------------------------------------------------------// //-----------------------------------------------------------------------------------------------------------------------------------------------------// function hasFutureLessons() { var lessonCount = parseInt($('.navigation-shortcut--lessons')[0].getAttribute("data-count")); return ((lockedCount.total + lessonCount) > 0); } function showLessonsButton() { return (wkof.settings.db_cockpit.showMainButtons && (!wkof.settings.db_cockpit.lvl60Lessons || hasFutureLessons() ) ); } function showLockedCount(optionalSpecificType) { var mustBeVisible = (wkof.settings.db_cockpit.showSrsLocked != '0'); if (optionalSpecificType != undefined) { mustBeVisible = (wkof.settings.db_cockpit.showSrsLocked == optionalSpecificType); } return (mustBeVisible && (!wkof.settings.db_cockpit.lvl60Lessons || hasFutureLessons() ) ); } function showLevelProgress(allPassed) { return (!allPassed || !wkof.settings.db_cockpit.lvl60Progress ); } // true if either the forecast is visible or the extra study panel is displayed above/below the forecast position // included here as the latter is now possibly dependent on the ESM setting function isRightGridVisible(allPassed) { // need to show review forecast if (wkof.settings.db_cockpit.showForecast) { return true; } // need to show extra study var esLocation = getExtraStudyLocationSetting(); if (esLocation == '3' || esLocation == '4' ) { return true; } // need to show review button if (wkof.settings.db_cockpit.showMainButtons && !isLeftGridVisible(allPassed) ) { return true; } if (isRSVisible() && !isLeftGridVisible(allPassed) ) { return true; } return false; } function isLeftGridVisible(allPassed) { if ( // need to show level progress panel showLevelProgress(allPassed) // need to show lessons button || showLessonsButton() // need to show locked count || showLockedCount() ) { return true; } // need to show extra study var esLocation = getExtraStudyLocationSetting(); if (esLocation == '-1' // invisible extra study is visible recent mistakes on the left! || esLocation == '1' || esLocation == '6' ) { return true; } return false; } // NOTE! this function does not check for moving the reviews button to the left grid // always check isRightGridVisible() first function isTopRightCellVisible() { return ( // need to show review button wkof.settings.db_cockpit.showMainButtons // or the review summary || isRSVisible() ); } // NOTE this function only makes sense if leftGrid is known to be visible // it will block on a moved reviews button function isTopLeftCellVisible() { return ( // need to show lessons button showLessonsButton() // need to show locked count || showLockedCount() // need to show reviews button or the summary || ((wkof.settings.db_cockpit.showMainButtons || isRSVisible() ) && !isRightGridVisible(false) ) ); } function getLeftColumnWidth(allPassed) { var minWidth = 0; var marginToApply = 0; if (showLockedCount()) { minWidth += 120; marginToApply = 5; } if (showLessonsButton()) { minWidth += marginToApply; minWidth += 200; marginToApply = 10; } if (!isRightGridVisible(allPassed) && wkof.settings.db_cockpit.showMainButtons ) { minWidth += marginToApply; minWidth += 200; marginToApply = 10; if (isRSVisible()) { minWidth += marginToApply; minWidth += 120; marginToApply = 10; } } if (minWidth < BASE_COLUMN_WIDTH) { var esLocation = getExtraStudyLocationSetting(); if (esLocation == '-1' // invisible extra study is visible recent mistakes on the left! || esLocation == '1' || esLocation == '6' || showLevelProgress(allPassed) ) { minWidth = BASE_COLUMN_WIDTH; } } return minWidth; } //-----------------------------------------------------------------------------------------------------------------------------------------------------// //-----------------------------------------------------------------UTILITY FUNCTIONS-------------------------------------------------------------------// //-----------------------------------------------------------------------------------------------------------------------------------------------------// function containsSection(element, component) { return (element.querySelector(component) != null); } //-----------------------------------------------------------------------------------------------------------------------------------------------------// //--------------------------------------------------------------------PRESENTATION---------------------------------------------------------------------// //-----------------------------------------------------------------------------------------------------------------------------------------------------// var CANVAS_SIZE = [481, 401, 321]; // odd numbers to give it a center pixel function getCanvasSize() { var configured = wkof.settings.db_cockpit.srsSize ? parseInt(wkof.settings.db_cockpit.srsSize) : 0; return CANVAS_SIZE[configured]; } function getCanvasGridWidth() { return (getCanvasSize() - 11) / 2; } var BASE_COLUMN_WIDTH = 335; // size definitions we'll populate after loading the settings var MAINBAR_WIDTH; var HELPERBAR_WIDTH; var canvasCenter = { "x": 0, "y": 0 }; // main srs circle var srsOutsideRadius; var srsInsideRadius; var srsTextRadius; // srs detail circle var srsOutsideDetailRadius; var srsInsideDetailRadius; var srsDetailTextRadius; // srs level circle var srsOutsideLevelRadius; var srsInsideLevelRadius; var srsLevelTextRadius; var levelProgressTextRadius; // additionalInfo var addInfo1TextRadius; function loadSizes() { MAINBAR_WIDTH = getCanvasSize() / 7; HELPERBAR_WIDTH = MAINBAR_WIDTH / 2; // pre calculations canvasCenter.x = (getCanvasSize() / 2); canvasCenter.y = (getCanvasSize() / 2); // main srs circle srsOutsideRadius = (getCanvasSize() - 1) / 2 - HELPERBAR_WIDTH; srsInsideRadius = srsOutsideRadius - MAINBAR_WIDTH; srsTextRadius = (srsOutsideRadius + srsInsideRadius) / 2; // srs detail circle srsOutsideDetailRadius = srsOutsideRadius + HELPERBAR_WIDTH; srsInsideDetailRadius = srsOutsideRadius; srsDetailTextRadius = (srsOutsideDetailRadius + srsInsideDetailRadius) / 2; // srs level circle srsOutsideLevelRadius = srsInsideRadius; srsInsideLevelRadius = srsInsideRadius - HELPERBAR_WIDTH; srsLevelTextRadius = (srsOutsideLevelRadius + srsInsideLevelRadius) / 2; levelProgressTextRadius = srsInsideRadius - (7 * HELPERBAR_WIDTH / 12); // additionalInfo addInfo1TextRadius = srsOutsideRadius - (HELPERBAR_WIDTH / 2); // and all the font sizes!!! var configured = wkof.settings.db_cockpit.srsSize ? parseInt(wkof.settings.db_cockpit.srsSize) : 0; var levelFontSize = 80 - 10 * configured; var srsFontSize = 24 - 3 * configured; var helperFontSize = 14 - 1.5 * configured; var leechFontSize = 12 - configured; var levelProgressFontSize = 12 - configured; levelFont.fontStyle = "bold " + levelFontSize + "px Arial, sans-serif"; levelFont.fontSize = calculateFontSize(levelFont.fontStyle); srsFont.fontStyle = "bold " + srsFontSize + "px Arial, sans-serif"; srsFont.fontSize = calculateFontSize(srsFont.fontStyle); helperFont.fontStyle = helperFontSize + "px Arial, sans-serif"; helperFont.fontSize = calculateFontSize(helperFont.fontStyle); leechFont.fontStyle = leechFontSize + "px Arial, sans-serif"; leechFont.fontSize = calculateFontSize(leechFont.fontStyle); levelProgressFont.fontStyle = "bold " + levelProgressFontSize + "px Arial, sans-serif"; levelProgressFont.fontSize = calculateFontSize(levelProgressFont.fontStyle); } var srsStartAngle = [3.5, 2]; var arcWidth = Math.PI * 2 / 6; var srsFont = { color: "#ffffff", fontStyle: "bold 24px Arial, sans-serif", fontSize: calculateFontSize("bold 24px Arial, sans-serif") } var levelFont = { color: function () { if (wkof.settings.db_cockpit.presetLevelColor == '0') { return { start: "#dd0093", end: "#0093dd" } } return { start: wkof.settings.db_cockpit.levelNrInside, end: wkof.settings.db_cockpit.levelNrOutside } }, colorOld: { start: "#dd0093", end: "#0093dd" }, fontStyle: "bold 80px Arial, sans-serif", fontSize: calculateFontSize("bold 80px Arial, sans-serif"), opacity: function () { if (wkof.settings.db_cockpit.presetLevelColor == '2') { return 0.6; } return 0.2; }, lineWidth: 3 } // srs detail circle var helperFont = { color: "#ffffff", fontStyle: "14px Arial, sans-serif", fontSize: calculateFontSize("14px Arial, sans-serif") } var leechFont = { color: "#ffffff", fontStyle: "12px Arial, sans-serif", fontSize: calculateFontSize("12px Arial, sans-serif"), bgColor: "#000000", opacity: function () { var leechDisplay = parseInt(wkof.settings.db_cockpit.leechDisplay); switch (leechDisplay) { case 0: return 0.4; case 1: return 0.4; case 2: return 1.0; case 3: return 0.6; } }, highlightOpacity: 1.0, bgOpacity: 0.3, rounded: function () { return (wkof.settings.db_cockpit.leechDisplay == '3'); }, colorScheme: function () { if (wkof.settings.db_cockpit.leechDisplay == '3') { return { color: "#000000", bgColor: "#ffffff" } } else { return null; } } } var appOpacity = 0.7; var gurOpacity = 0.9; var opacityFactor = 0.85; // srs level circle var srsLevelColors = { bgColor: "#434343", darkBgColor: "#232629" } var levelProgressFont = { color: "#9df", fontStyle: "bold 12px Arial, sans-serif", fontSize: calculateFontSize("bold 12px Arial, sans-serif") }; var dashLength = 8; var dashFont = { color: "#ddff99", lineWidth: 2 } // additionalInfo var srsDrawing = [ { srsAbbrev: "App", srsName: "Apprentice", srsLevel: 1, srsLogo: "apprentice", color: function () { return { start: "#f0a", end: "#dd0093" } } }, { srsAbbrev: "Gur", srsName: "Guru", srsLevel: 5, srsLogo: "guru", color: function () { return { start: "#aa38c6", end: "#882d9e" } } }, { srsAbbrev: "Mas", srsName: "Master", srsLevel: 7, srsLogo: "master", color: function () { return { start: "#5571e2", end: "#294ddb" } } }, { srsAbbrev: "Enl", srsName: "Enlightened", srsLevel: 8, srsLogo: "enlightened", color: function () { return { start: "#0af", end: "#0093dd" } } }, { srsAbbrev: "Bur", srsName: "Burned", srsLevel: 9, srsLogo: "burned", color: function () { if (wkof.settings.db_cockpit.goldenBurn) { return { start: "#fbc550", end: "#faac05", progressText: "#777" } } else { return { start: "#555", end: "#434343" } } } } ]; var srsDetailSrs = [[ { srsAbbrev: "Appr1", color: "#dd0093", opacity: appOpacity * opacityFactor * opacityFactor * opacityFactor }, { srsAbbrev: "Appr2", color: "#dd0093", opacity: appOpacity * opacityFactor * opacityFactor }, { srsAbbrev: "Appr3", color: "#dd0093", opacity: appOpacity * opacityFactor }, { srsAbbrev: "Appr4", color: "#dd0093", opacity: appOpacity } ], [ { srsAbbrev: "Guru1", color: "#882d9e", opacity: gurOpacity * opacityFactor }, { srsAbbrev: "Guru2", color: "#882d9e", opacity: gurOpacity } ]]; // create the main panels function createPanels() { var cockpitPanel = document.createElement("div"); cockpitPanel.id = "cockpitPanel"; var dbc_gas = document.createElement("div"); dbc_gas.id = "dbc_gas"; cockpitPanel.append(dbc_gas); var dbc_currentLevel = document.createElement("div"); dbc_currentLevel.id = "dbc_currentLevel"; cockpitPanel.append(dbc_currentLevel); var dbc_core = document.createElement("div"); dbc_core.id = "dbc_core"; cockpitPanel.append(dbc_core); var dbc_bar60 = document.createElement("div"); dbc_bar60.id = "dbc_bar60"; cockpitPanel.append(dbc_bar60); var dbc_cruise = document.createElement("div"); dbc_cruise.id = "dbc_cruise"; cockpitPanel.append(dbc_cruise); var dbc_forecast = document.createElement("div"); dbc_forecast.id = "dbc_forecast"; cockpitPanel.append(dbc_forecast); if ($('#timeline').length) { $('#timeline').after(cockpitPanel); } else { $('.dashboard__content').before(cockpitPanel); } } // that turbo frame is obnoxious function restyleLessonsButton() { // button section update addition styling $('.todays-lessons').css("padding", "var(--spacing-tight)"); $('.todays-lessons__content').css('display', 'inline'); $('.todays-lessons__content').css("position", "relative"); $('.todays-lessons__text-content').css("position", "absolute"); $('.todays-lessons__text-content').css("bottom", "0px"); $('.todays-lessons__text-content').css("right", "0px"); $('.todays-lessons__text').detach(); $('.todays-lessons__subtitle').detach(); $('.todays-lessons__title-text').detach(); $('.todays-lessons__image-content').css("margin-bottom", "0px"); var buttons = $('.todays-lessons__button'); for (var i = 0; i < buttons.length; i++) { buttons[i].style["width"] = "100%"; var buttonInternal = buttons[i].querySelector('.wk-button'); buttonInternal.style["padding"] = "var(--spacing-tight)"; } } // due to the way the lessons section loads, this must be done later // and since we can't tell which is the lesson and which is the review button until AFTER // the lesson turboframe loads, we delay both of 'm function delayedButtonStyling() { var lessonsButton = null; var reviewsButton = null; var buttonSections = document.querySelectorAll('.dashboard__lessons-and-reviews-section'); buttonSections.forEach(section => { if (containsSection(section, '.todays-lessons')) { lessonsButton = section.parentNode.removeChild(section); } if (containsSection(section, '.reviews-dashboard')) { reviewsButton = section.parentNode.removeChild(section); } }); $('#dbc_gas').append(lessonsButton); $('#dbc_gas .dashboard__lessons-and-reviews-section').css("width", "100%"); $('#dbc_gas .dashboard__lessons-and-reviews-section').css("float", "right"); // delay further // setTimeout(restyleLessonsButton, 1000); // and twice for safety // setTimeout(restyleLessonsButton, 2000); var reviewButtonBlock = document.createElement("div"); reviewButtonBlock.id = "reviewButtonBlock"; $('#dbc_cruise').append(reviewButtonBlock); $('#reviewButtonBlock').append(reviewsButton); reviewsButton.style["width"] = "100%"; // button section update addition styling $('.reviews-dashboard').css("padding", "var(--spacing-tight)"); $('.reviews-dashboard__content').css('display', 'inline'); $('.reviews-dashboard__content').css("position", "relative"); $('.reviews-dashboard__text-content').css("position", "absolute"); $('.reviews-dashboard__text-content').css("bottom", "0px"); $('.reviews-dashboard__text-content').css("right", "0px"); $('.reviews-dashboard__text').detach(); $('.reviews-dashboard__title-text').detach(); $('.reviews-dashboard__image-content').css("margin-bottom", "0px"); var buttons = $('.reviews-dashboard__button'); for (var i = 0; i < buttons.length; i++) { buttons[i].style["width"] = "100%"; var buttonInternal = buttons[i].querySelector('.wk-button'); buttonInternal.style["padding"] = "var(--spacing-tight)"; } // integration with Kumirei's Review Hover Details var reviewHoverDetails = $('.dashboard #review_hover_details'); if (reviewHoverDetails) { reviewHoverDetails.detach(); $('.lessons-and-reviews__reviews-button').after(reviewHoverDetails); } if (isRSPresent()) { var summaryDiv = $('#reviewSummaryDiv').detach(); $('#dbc_cruise').append(summaryDiv); $('#reviewSummaryTile').css("float", "right"); $('#reviewSummaryTile').css("width", "120px"); $('#reviewSummaryTile').css("height", "100px"); $('#reviewSummaryTile #rs_tile_span_header').css("font-size", "14px"); $('#reviewSummaryTile #rs_tile_span_header').css("top", "6px"); $('#reviewSummaryTile #rs_tile_span_percentage').css("font-size", "28px"); $('#reviewSummaryTile #rs_tile_span_percentage').css("top", "16px"); $('#reviewSummaryTile #rs_tile_icon_percentage').css("top", "-2px"); } } // create the panel contents function createPanelContents() { var speedometer = document.createElement("canvas"); speedometer.id = "speedometer"; $('#dbc_core').append(speedometer); var canvasTooltips = document.createElement("div"); canvasTooltips.id = "canvasTooltips"; canvasTooltips.className = "db_cockpit_tooltips"; $('#dbc_core').append(canvasTooltips); var canvasClicktips = document.createElement("div"); canvasClicktips.id = "canvasClicktips"; canvasClicktips.className = "db_cockpit_tooltips"; $('#dbc_core').append(canvasClicktips); // temporarily relocate the button sections until we can figure out which is which var buttonSections = document.querySelectorAll('.dashboard__lessons-and-reviews-section'); buttonSections.forEach(section => { $('#dbc_cruise').append(section.parentNode.removeChild(section)); }); // lessons button is now a turbo frame and can load late setTimeout(delayedButtonStyling, 1000); var extraRecentStudy = document.createElement("div"); extraRecentStudy.id = "extraRecentStudy"; $('#dbc_currentLevel').append(extraRecentStudy); var recentMistakes = $('.wk-panel--recent-mistakes'); recentMistakes.detach(); recentMistakes.css("margin-bottom", "10px"); $('#extraRecentStudy').append(recentMistakes); if (moveExtraStudy()) { var extraStudy = $('.wk-panel--extra-study'); extraStudy.detach(); $('#extraRecentStudy').append(extraStudy); } var currentLevelProgress = $('.wk-panel--level-progress').detach(); $('#dbc_currentLevel').append(currentLevelProgress); var forecast = $('.wk-panel--review-forecast').detach(); $('#dbc_forecast').append(forecast); forecast.css("margin-bottom", "10px"); // clean up items that are no longer needed // new dashboard update, all are individual sections now. I can't just hide these, because they occupy individual // cells in the dashboard content grid. Removing them allows me to remove the grid rows $('.dashboard__lessons-and-reviews').detach(); $('.dashboard__recent-mistakes').detach(); $('.dashboard__extra-study').detach(); $('.dashboard__level-progress').detach(); $('.dashboard__review-forecast').detach(); // and move the rest up $('.dashboard__srs-progress').css("grid-row", "1/2"); $('.dashboard__item-lists').css("grid-row", "2/3"); $('.dashboard__community-banner').css("grid-row", "3/4"); // tricky tricky. New dashboard update puts a section around srs-progress (dashboard__srs-progress), which I ideally // want to hide, but other scripts that place around the srs-progress would be hidden as well, so we keep it at this for now $('.srs-progress').addClass('hidden'); $("#speedometer").mouseenter(function (e) { reOffsetCanvas(e); }); $("#speedometer").mousemove(function (e) { handleCanvasMouseMove(e); }); $("#speedometer").mouseleave(function (e) { // hide all tooltips $('.db_cockpit_tooltipText_visible').removeClass('db_cockpit_tooltipText_visible'); }); $("#speedometer").click(function (e) { handleCanvasMouseClick(e); }); $("body").click(hideClickTips); } function repositionMainPanel() { // ultimate timeline does not have a margin at the bottom, so we insert margin top if needed if ($('#timeline').length) { $("#cockpitPanel").css("margin-top", "10px"); } else { $("#cockpitPanel").css("margin-top", "0px"); } } // empty the canvas for redrawing function clearCanvas() { var speedometer = $('#speedometer')[0]; speedometer.width = getCanvasSize(); speedometer.height = getCanvasSize(); var ctx = speedometer.getContext("2d"); ctx.clearRect(0, 0, getCanvasSize(), getCanvasSize()); hotspots = []; clickHotspots = []; highlightHotspots = []; currentHighlight = null; $('#canvasTooltips div').detach(); $('#canvasTooltips span').detach(); $('#canvasClicktips div').detach(); } // creates a linear gradient for an arc shape (between opposing corners) function createArcGradient(ctx, centerX, centerY, outsideRadius, insideRadius, startAngle, endAngle, color) { if (color.start != undefined) { var ctxGradient = ctx.createLinearGradient( Math.cos(endAngle) * outsideRadius + centerX, Math.sin(endAngle) * outsideRadius + centerY, Math.cos(startAngle) * insideRadius + centerX, Math.sin(startAngle) * insideRadius + centerY ); ctxGradient.addColorStop(0, color.start); ctxGradient.addColorStop(1, color.end); return ctxGradient; } else { return color; } } // draws and fill the arc sections function drawSRSArc(ctx, centerX, centerY, outsideRadius, insideRadius, startAngle, endAngle, color, opacity) { ctx.save(); ctx.fillStyle = createArcGradient(ctx, centerX, centerY, outsideRadius, insideRadius, startAngle, endAngle, color); if (opacity != undefined) { ctx.globalAlpha = opacity; } ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.arc(centerX, centerY, outsideRadius, startAngle, endAngle); ctx.arc(centerX, centerY, insideRadius, endAngle, startAngle, true); ctx.closePath(); ctx.fill(); ctx.restore(); } function calculateFontSize(font) { var fontsize = font.match(/[\d|\.]+\s?px/g); if (fontsize != undefined) { fontsize = fontsize[0].match(/[\d|\.]+/g); if (fontsize != undefined) { fontsize = fontsize[0]; } } else { fontsize = font.match(/[\d|\.]+\s?em/g); if (fontsize.length != undefined) { fontsize = fontsize[0].match(/[\d|\.]+/g); if (fontsize.length != undefined) { // estimate is good enough, we're only shifting by 1/4 of these pixels fontsize = fontsize[0] * 16; } } } if (fontsize == undefined) { fontsize = 16; } return fontsize; } // adds text to the canvas function drawCenteredText(ctx, textX, textY, text, textFont, bright) { ctx.save(); ctx.font = textFont.fontStyle; if (textFont.lineWidth != undefined) { ctx.lineWidth = textFont.lineWidth; } var metrics = ctx.measureText(text); var placeX = textX - metrics.width / 2; var fontsize = textFont.fontSize; var placeY = textY + fontsize / 4; var resultDimensions = { minX: placeX - 2, minY: placeY - parseInt(fontsize) + 1, width: metrics.width + 4, height: parseInt(fontsize) + 2 }; // background opacity if (textFont.bgOpacity != undefined) { ctx.globalAlpha = textFont.bgOpacity; } // draw background for the text if required if (textFont.bgColor != undefined) { ctx.fillStyle = prioritiseColorSchemeBgColor(textFont); if (textFont.rounded()) { ctx.beginPath(); ctx.ellipse(resultDimensions.minX + resultDimensions.width / 2, resultDimensions.minY + resultDimensions.height / 2, resultDimensions.width * 0.6, resultDimensions.height * 0.6, 0, 0, 2 * Math.PI); ctx.fill(); } else { ctx.fillRect(resultDimensions.minX, resultDimensions.minY, resultDimensions.width, resultDimensions.height); } } // foreground opacity if (textFont.opacity != undefined) { if ((bright != undefined) && bright && (textFont.highlightOpacity != undefined)) { ctx.globalAlpha = textFont.highlightOpacity; } else { ctx.globalAlpha = textFont.opacity(); } } var resolvedColor = textFont.color; if (typeof resolvedColor == 'function') { resolvedColor = textFont.color(); } if (resolvedColor.start != undefined) { var ctxGradient = ctx.createRadialGradient( textX, textY, 1, textX, textY, fontsize ); ctxGradient.addColorStop(0, resolvedColor.start); ctxGradient.addColorStop(1, resolvedColor.end); ctx.fillStyle = ctxGradient; ctx.strokeStyle = ctxGradient; } else { ctx.strokeStyle = prioritiseColorSchemeColor(textFont); ctx.fillStyle = prioritiseColorSchemeColor(textFont); } if (textFont.lineWidth != undefined) { ctx.strokeText(text, placeX, placeY); } else { ctx.fillText(text, placeX, placeY); } ctx.restore(); return resultDimensions; } function prioritiseColorSchemeColor(font) { if (font.colorScheme && font.colorScheme() && font.colorScheme().color) { return font.colorScheme().color; } return font.color; } function prioritiseColorSchemeBgColor(font) { if (font.colorScheme && font.colorScheme() && font.colorScheme().bgColor) { return font.colorScheme().bgColor; } return font.bgColor; } // adds arrow heads to the canvas function drawArrowHead(ctx, radius, angle, color, fontSize) { ctx.save(); // we're going to rotate around the center point ctx.translate(canvasCenter.x, canvasCenter.y); ctx.rotate(angle); ctx.fillStyle = color; ctx.beginPath(); ctx.moveTo(radius, 0); ctx.lineTo(radius - fontSize, 0.5 * fontSize); ctx.lineTo(radius - (2 * fontSize / 3), 0); ctx.lineTo(radius - fontSize, -0.5 * fontSize); ctx.closePath(); ctx.fill(); ctx.restore(); } // adds a line to the canvas function drawLine(ctx, startPoint, endPoint, lineFont) { ctx.save(); if (lineFont.lineWidth != undefined) { ctx.lineWidth = lineFont.lineWidth; } ctx.strokeStyle = lineFont.color; ctx.beginPath(); ctx.moveTo(startPoint.x, startPoint.y); ctx.lineTo(endPoint.x, endPoint.y); ctx.stroke(); ctx.restore(); } // keep track of the canvas position on scrolling/resizing function reOffsetCanvas() { if ($('#speedometer').length) { var BB = $('#speedometer')[0].getBoundingClientRect(); offsetX = BB.left; offsetY = BB.top; windowOffsetX = $(window).scrollLeft(); windowOffsetY = $(window).scrollTop(); } } var offsetX, offsetY; var windowOffsetX, windowOffsetY; window.onscroll = function (e) { reOffsetCanvas(); } window.onresize = function (e) { reOffsetCanvas(); } function handleCanvasMouseMove(e) { // tell the browser we're handling this event e.preventDefault(); e.stopPropagation(); var mouseX = parseInt(e.clientX - offsetX); var mouseY = parseInt(e.clientY - offsetY); for (var i = 0; i < hotspots.length; i++) { var h = hotspots[i]; var dx = mouseX - h.x; var dy = mouseY - h.y; if (dx * dx + dy * dy < h.radius * h.radius) { showTooltip(hotspots[i]); highlightSpot(currentHighlight, false); return; } } // if no hotspot matches, hide all tooltips var visibleTT = $('.db_cockpit_tooltipText_visible'); visibleTT.removeClass('db_cockpit_tooltipText_visible'); // highlighting only for option 1 if (wkof.settings.db_cockpit.leechDisplay != '1') { return; } // then check for highlights for (i = 0; i < highlightHotspots.length; i++) { h = highlightHotspots[i]; if ((mouseX > h.textBg.minX) && (mouseX < (h.textBg.minX + h.textBg.width)) && (mouseY > h.textBg.minY) && (mouseY < (h.textBg.minY + h.textBg.height)) ) { highlightSpot(h, true); return; } } // if no highlights, then remove any current highlightSpot(currentHighlight, false); } function showTooltip(hotspot) { // hide all other tooltips var visibleTT = $('.db_cockpit_tooltipText_visible:not(#' + hotspot.id + ')'); visibleTT.removeClass('db_cockpit_tooltipText_visible'); // expose current tooltip if it wasn't already if (!$('.db_cockpit_tooltipText_visible #' + hotspot.id).length) { var spanWidth = $('#' + hotspot.id).width(); var spanHeight = $('#' + hotspot.id).height(); var offsetLeft = Math.floor(hotspot.x + offsetX + windowOffsetX - spanWidth * 0.4); var offsetTop = Math.floor(hotspot.y + offsetY + windowOffsetY - spanHeight - 30); $('#' + hotspot.id).css('left', offsetLeft + 'px'); $('#' + hotspot.id).css('top', offsetTop + 'px'); $('#' + hotspot.id).addClass('db_cockpit_tooltipText_visible'); } } var hotspots = []; function createHotspot(spotX, spotY, spotRadius, spotText) { var spotId = "tt" + Math.floor(spotX) + "_" + Math.floor(spotY); hotspots.push({ x: spotX, y: spotY, radius: spotRadius, id: spotId }); $('#canvasTooltips').append( '<span class="db_cockpit_tooltipText" id="' + spotId + '">' + spotText + '</span>' ); } function createBreakDownHotspot(spotX, spotY, spotRadius, logo, rTotal, kTotal, vTotal) { var spotId = "tt" + Math.floor(spotX) + "_" + Math.floor(spotY); hotspots.push({ x: spotX, y: spotY, radius: spotRadius, id: spotId }); $('#canvasTooltips').append( '<div class="db_cockpit_tooltipBreakdownText" id="' + spotId + '">' + '<div class="srs-logo ' + logo + '"></div>' + '<div class="tt_bd_totals"><ul>' + '<li>rad:<span>' + rTotal + '</span></li>' + '<li>kan:<span>' + kTotal + '</span></li>' + '<li>voc:<span>' + vTotal + '</span></li>' + '</ul></div>' + '</div>' ); } function handleCanvasMouseClick(e) { // tell the browser we're handling this event e.preventDefault(); e.stopPropagation(); var mouseX = parseInt(e.clientX - offsetX); var mouseY = parseInt(e.clientY - offsetY); for (var i = 0; i < clickHotspots.length; i++) { var h = clickHotspots[i]; var dx = mouseX - h.x; var dy = mouseY - h.y; if (dx * dx + dy * dy < h.radius * h.radius) { showClicktip(clickHotspots[i]); return; } } // if no clickHotspot matches, hide all clicktips var visibleTT = $('.db_cockpit_clicktipText_visible'); visibleTT.removeClass('db_cockpit_clicktipText_visible'); // temp addition to test level up animation // dx = mouseX - canvasCenter.x; // dy = mouseY - canvasCenter.y; // if (dx * dx + dy * dy < 6400) { // levelUpAnimation(11, 12); // } } function hideClickTips(e) { // hide all click tips var visibleTT = $('.db_cockpit_clicktipText_visible'); visibleTT.removeClass('db_cockpit_clicktipText_visible'); } function showClicktip(hotspot) { // hide all other clicktips var visibleTT = $('.db_cockpit_clicktipText_visible:not(#' + hotspot.id + ')'); visibleTT.removeClass('db_cockpit_clicktipText_visible'); // expose current clicktip if it wasn't already if (!$('.db_cockpit_clicktipText_visible #' + hotspot.id).length) { var spanWidth = $('#' + hotspot.id).width(); var spanHeight = $('#' + hotspot.id).height(); var offsetLeft = Math.floor(hotspot.x + offsetX + windowOffsetX - spanWidth * 0.4); var offsetTop = Math.floor(hotspot.y + offsetY + windowOffsetY - spanHeight - 30); $('#' + hotspot.id).css('left', offsetLeft + 'px'); $('#' + hotspot.id).css('top', offsetTop + 'px'); $('#' + hotspot.id).addClass('db_cockpit_clicktipText_visible'); } } function scrollDialLevel(e, currentSpotId, toLevel) { // tell the browser we're handling this event e.preventDefault(); e.stopPropagation(); // get current spot var currentDialDiv = $('#' + currentSpotId); // get next dial div and position var nextDialDiv = $('#' + createDialDivId(toLevel)); nextDialDiv.css('left', currentDialDiv.css('left')); nextDialDiv.css('top', currentDialDiv.css('top')); // swap visibility currentDialDiv.removeClass('db_cockpit_clicktipText_visible'); nextDialDiv.addClass('db_cockpit_clicktipText_visible'); } function hideZero(value) { if (value == 0) { return " "; } else { return value; } } var clickHotspots = []; function addDialHotspot(spotX, spotY, spotRadius, levelData) { var spotId = createDialDivId(levelData.level); clickHotspots.push({ x: spotX, y: spotY, radius: spotRadius, id: spotId }); } function createDialDiv(levelData, dialDiv) { var spotId = createDialDivId(levelData.level); var dialDivElem = '<div class="db_cockpit_tooltipDialText" id="' + spotId + '">' + '<table><tr>' + '<td class="db_cockpit_ctd_summary" colspan="2" style="width:100px">level</td>' + '<td style="width:20px"> </td><td class="db_cockpit_ctd_header">passed</td>' + '<td class="db_cockpit_ctd_header">rad</td><td class="db_cockpit_ctd_header">kan</td><td class="db_cockpit_ctd_header">voc</td>' + '</tr><tr>' + '<td class="db_cockpit_ctd_level db_cockpit_ctd_summary" colspan="2" rowspan="3">' + '<span class="db_cockpit_ctd_link" id="db_cockpit_' + levelData.level + '_previousLink"> </span> ' + levelData.level + ' <span class="db_cockpit_ctd_link" id="db_cockpit_' + levelData.level + '_nextLink"> </span></td>' + '<td class="db_cockpit_ctd_category" style="width:20px">-</td><td> </td>' + '<td>' + hideZero(levelData.unstarted.radical) + '</td><td>' + hideZero(levelData.unstarted.kanji) + '</td><td>' + hideZero(levelData.unstarted.vocabulary) + '</td>' + '</tr><tr>' + '<td class="db_cockpit_ctd_category" style="width:20px">A</td><td>' + levelData.App.passed + '%</td>' + '<td>' + hideZero(levelData.App.radical) + '</td><td>' + hideZero(levelData.App.kanji) + '</td><td>' + hideZero(levelData.App.vocabulary) + '</td>' + '</tr><tr>' + '<td class="db_cockpit_ctd_category" style="width:20px">G</td><td>' + levelData.Gur.passed + '%</td>' + '<td>' + hideZero(levelData.Gur.radical) + '</td><td>' + hideZero(levelData.Gur.kanji) + '</td><td>' + hideZero(levelData.Gur.vocabulary) + '</td>' + '</tr><tr>' + '<td class="db_cockpit_ctd_summary" colspan="2">progress</td>' + '<td class="db_cockpit_ctd_category" style="width:20px">M</td><td>' + levelData.Mas.passed + '%</td>' + '<td>' + hideZero(levelData.Mas.radical) + '</td><td>' + hideZero(levelData.Mas.kanji) + '</td><td>' + hideZero(levelData.Mas.vocabulary) + '</td>' + '</tr><tr>' + '<td class="db_cockpit_ctd_score db_cockpit_ctd_summary" colspan="2" rowspan="2">' + levelData.score + '%</td>' + '<td class="db_cockpit_ctd_category" style="width:20px">E</td><td>' + levelData.Enl.passed + '%</td>' + '<td>' + hideZero(levelData.Enl.radical) + '</td><td>' + hideZero(levelData.Enl.kanji) + '</td><td>' + hideZero(levelData.Enl.vocabulary) + '</td>' + '</tr><tr>' + '<td class="db_cockpit_ctd_category" style="width:20px">B</td><td>' + levelData.Bur.passed + '%</td>' + '<td>' + hideZero(levelData.Bur.radical) + '</td><td>' + hideZero(levelData.Bur.kanji) + '</td><td>' + hideZero(levelData.Bur.vocabulary) + '</td>' + '</tr></table>' + '</div>'; $('#canvasClicktips').append(dialDivElem); if (dialDiv.previousLevel) { $('#db_cockpit_' + levelData.level + '_previousLink').click(function (e) { scrollDialLevel(e, spotId, dialDiv.previousLevel); }); $('#db_cockpit_' + levelData.level + '_previousLink').addClass("db_cockpit_ctd_clickable"); $('#db_cockpit_' + levelData.level + '_previousLink').html("<"); } if (dialDiv.nextLevel) { $('#db_cockpit_' + levelData.level + '_nextLink').click(function (e) { scrollDialLevel(e, spotId, dialDiv.nextLevel); }); $('#db_cockpit_' + levelData.level + '_nextLink').addClass("db_cockpit_ctd_clickable"); $('#db_cockpit_' + levelData.level + '_nextLink').html(">"); } } var highlightHotspots = []; var currentHighlight; function createHighlightHotspot(centerX, centerY, outsideRadius, insideRadius, startAngle, endAngle, color, opacity, minX, minY, width, height, textX, textY, text, font) { var spotId = "hl" + Math.floor(minX) + "_" + Math.floor(minY); highlightHotspots.push({ id: spotId, arc: { centerX: centerX, centerY: centerY, outsideRadius: outsideRadius, insideRadius: insideRadius, startAngle: startAngle, endAngle: endAngle, color: color, opacity: opacity }, textBg: { minX: minX, minY: minY, width: width, height: height }, text: { textX: textX, textY: textY, text: text, font: font } }); } function highlightSpot(highlightHotspot, bright) { if (bright && (currentHighlight != undefined)) { if (currentHighlight.id == highlightHotspot.id) { // done return; } highlightSpot(currentHighlight, false); } if (!bright && (currentHighlight == undefined)) { // also done return; } drawHighlightSpot(highlightHotspot, bright); if (bright) { currentHighlight = highlightHotspot; } else { currentHighlight = null; } } function drawHighlightSpot(spot, bright) { var ctx = $('#speedometer')[0].getContext("2d"); ctx.save(); // clear the area ctx.clearRect(spot.textBg.minX, spot.textBg.minY, spot.textBg.width, spot.textBg.height); // and redraw the background arc if (spot.arc.opacity != undefined) { ctx.globalAlpha = spot.arc.opacity; } ctx.fillStyle = createArcGradient(ctx, spot.arc.centerX, spot.arc.centerY, spot.arc.outsideRadius, spot.arc.insideRadius, spot.arc.startAngle, spot.arc.endAngle, spot.arc.color); ctx.fillRect(spot.textBg.minX, spot.textBg.minY, spot.textBg.width, spot.textBg.height); ctx.restore(); // then redraw the text, highlighted or not drawCenteredText(ctx, spot.text.textX, spot.text.textY, spot.text.text, spot.text.font, bright); } // main srs progress blocks var levelUp = false; var oldLevel; function drawSRSProgress(itemBreakdown) { var ctx = $('#speedometer')[0].getContext("2d"); var countsBySRS = itemBreakdown.countBySRS.bySRSArray; var totalCount = 0; var radicalCount = 0; var kanjiCount = 0; var vocabularyCount = 0; var sumFunction = function (countItem) { totalCount += countItem.total; radicalCount += countItem.radical; kanjiCount += countItem.kanji; vocabularyCount += countItem.vocabulary; }; var barStartAngle = srsStartAngle[parseInt(wkof.settings.db_cockpit.srsOrientation)]; for (var srsLvl in srsDrawing) { drawSRSArc(ctx, canvasCenter.x, canvasCenter.y, srsOutsideRadius, srsInsideRadius, ((barStartAngle + parseInt(srsLvl))) * arcWidth, ((barStartAngle + parseInt(srsLvl) + 1)) * arcWidth, srsDrawing[srsLvl].color()); var relatedCounts = filterBySRSCountOnAbbrev(countsBySRS, srsDrawing[srsLvl].srsAbbrev); totalCount = 0; radicalCount = 0; kanjiCount = 0; vocabularyCount = 0; relatedCounts.forEach(sumFunction); var textX = Math.cos((barStartAngle + parseInt(srsLvl) + 0.5) * arcWidth) * srsTextRadius + canvasCenter.x; var textY = Math.sin((barStartAngle + parseInt(srsLvl) + 0.5) * arcWidth) * srsTextRadius + canvasCenter.y; drawCenteredText(ctx, textX, textY, totalCount, srsFont); createBreakDownHotspot(textX, textY, srsFont.fontSize, srsDrawing[srsLvl].srsLogo, radicalCount, kanjiCount, vocabularyCount); } if (levelUp) { // previous level was drawn earlier, draw it again in its previous form and prepare level up animation drawCenteredText(ctx, canvasCenter.x, canvasCenter.y, oldLevel, levelFont); setTimeout("window.db_cockpit_levelUpAnimation(" + oldLevel + ", " + itemBreakdown.level + ")", 100); // don't recelebrate upon redraw. Note that we do 'celebrate' upon a reset as well - but the animation isn't too festive, // so it can be considered an update instead of a celebration. levelUp = false; } else { var newLevel = $('li.user-summary__attribute a')[0].href.split('/level/')[1]; if (newLevel != itemBreakdown.level) { // we just leveled OR just reset and are drawing the old level from the cache levelUp = true; oldLevel = itemBreakdown.level; } // draw the user level in the center drawCenteredText(ctx, canvasCenter.x, canvasCenter.y, itemBreakdown.level, levelFont); } } var FRAME_DURATION = 80; function levelUpAnimation(oldLevel, newLevel) { var ctx = $('#speedometer')[0].getContext("2d"); ctx.font = levelFont.fontStyle; ctx.lineWidth = levelFont.lineWidth; var metrics = ctx.measureText(oldLevel); var rectWidth = metrics.width; metrics = ctx.measureText(newLevel); if (metrics.width > rectWidth) { rectWidth = metrics.width; } var levelFontSize = levelFont.fontSize; var offsetTop = levelFontSize * 3 / 4; var clearRectangle = "{ fromX: " + ((canvasCenter.x - rectWidth / 2 - 1)) + ", fromY: " + ((canvasCenter.y - offsetTop - 1)) + ", width: " + ((rectWidth + 2)) + ", height: " + ((parseInt(levelFontSize) + 5)) + " }"; var frame = 1; for (var tempSize = levelFontSize; tempSize > 0; tempSize -= 10) { setTimeout("window.db_cockpit_levelUpAnimationFrame(" + clearRectangle + ", " + tempSize + ", " + oldLevel + ", " + frame + ")", frame * FRAME_DURATION); frame++; } for (tempSize = levelFontSize % 10; tempSize <= levelFontSize; tempSize += 10) { setTimeout("window.db_cockpit_levelUpAnimationFrame(" + clearRectangle + ", " + tempSize + ", " + newLevel + ", " + frame + ")", frame * FRAME_DURATION); frame++; } } window.db_cockpit_levelUpAnimation = levelUpAnimation; function levelUpAnimationFrame(rectangle, fontSize, text, frame) { var ctx = $('#speedometer')[0].getContext("2d"); ctx.clearRect(rectangle.fromX, rectangle.fromY, rectangle.width, rectangle.height); var tempFont = { color: levelFont.color, opacity: levelFont.opacity, lineWidth: levelFont.lineWidth, fontStyle: levelFont.fontStyle.replace(/[\d|\.]+\s?px/g, fontSize + "px"), fontSize: fontSize } drawCenteredText(ctx, canvasCenter.x, canvasCenter.y, text, tempFont); } window.db_cockpit_levelUpAnimationFrame = levelUpAnimationFrame; // srs detail progress, dependent on setting function drawSRSProgressDetail(itemBreakdown) { var ctx = $('#speedometer')[0].getContext("2d"); var countsBySRS = itemBreakdown.countBySRS.bySRSArray; if (wkof.settings.db_cockpit.showSrsDetail) { var barStartAngle = srsStartAngle[parseInt(wkof.settings.db_cockpit.srsOrientation)]; for (var srsIndex in srsDetailSrs) { for (var srsLvl in srsDetailSrs[srsIndex]) { drawSRSArc(ctx, canvasCenter.x, canvasCenter.y, srsOutsideDetailRadius, srsInsideDetailRadius, (barStartAngle + parseInt(srsIndex) + parseInt(srsLvl) * 0.25 * (parseInt(srsIndex) + 1)) * arcWidth, (barStartAngle + parseInt(srsIndex) + (parseInt(srsLvl) + 1) * 0.25 * (parseInt(srsIndex) + 1)) * arcWidth, srsDetailSrs[srsIndex][srsLvl].color, srsDetailSrs[srsIndex][srsLvl].opacity); var relatedCounts = filterBySRSCountOnAbbrev(countsBySRS, srsDetailSrs[srsIndex][srsLvl].srsAbbrev); var textX = Math.cos((barStartAngle + parseInt(srsIndex) + (2 * parseInt(srsLvl) + 1) * 0.125 * (parseInt(srsIndex) + 1)) * arcWidth) * srsDetailTextRadius + canvasCenter.x; var textY = Math.sin((barStartAngle + parseInt(srsIndex) + (2 * parseInt(srsLvl) + 1) * 0.125 * (parseInt(srsIndex) + 1)) * arcWidth) * srsDetailTextRadius + canvasCenter.y; drawCenteredText(ctx, textX, textY, relatedCounts[0].total, helperFont); } } } } function drawSRSProgressLeechCounts(itemBreakdown) { if (!wkof.settings.db_cockpit.showLeechCount) { return; } var ctx = $('#speedometer')[0].getContext("2d"); var countsBySRS = itemBreakdown.countBySRS.bySRSArray; var leechCount = 0; var sumFunction = function (countItem) { leechCount += countItem.leech; }; var barStartAngle = srsStartAngle[parseInt(wkof.settings.db_cockpit.srsOrientation)]; for (var srsLvl in srsDrawing) { if (srsDrawing[srsLvl].srsAbbrev == 'Bur') { continue; } var relatedCounts = filterBySRSCountOnAbbrev(countsBySRS, srsDrawing[srsLvl].srsAbbrev); leechCount = 0; relatedCounts.forEach(sumFunction); var textRadius = (3 * srsOutsideRadius + srsInsideRadius) / 4; var textX = Math.cos((barStartAngle + parseInt(srsLvl) + 0.875) * arcWidth) * textRadius + canvasCenter.x; var textY = Math.sin((barStartAngle + parseInt(srsLvl) + 0.875) * arcWidth) * textRadius + canvasCenter.y; var resultDimensions = drawCenteredText(ctx, textX, textY, leechCount, leechFont); createHighlightHotspot(canvasCenter.x, canvasCenter.y, srsOutsideRadius, srsInsideRadius, ((barStartAngle + parseInt(srsLvl))) * arcWidth, ((barStartAngle + parseInt(srsLvl) + 1)) * arcWidth, srsDrawing[srsLvl].color(), null, resultDimensions.minX, resultDimensions.minY, resultDimensions.width, resultDimensions.height, textX, textY, leechCount, leechFont); } // then draw it for the details if visible and only if non-zero if (wkof.settings.db_cockpit.showSrsDetail) { for (var srsIndex in srsDetailSrs) { var stage = parseInt(srsIndex); for (srsLvl in srsDetailSrs[srsIndex]) { relatedCounts = filterBySRSCountOnAbbrev(countsBySRS, srsDetailSrs[srsIndex][srsLvl].srsAbbrev); if (relatedCounts[0].leech == 0) { continue; } textRadius = ((2 - stage) * srsOutsideDetailRadius + srsInsideDetailRadius) / (3 - stage); textX = Math.cos((barStartAngle + stage + (parseInt(srsLvl) + 1) * 0.25 * (stage + 1) - ((stage + 1) / 20)) * arcWidth) * textRadius + canvasCenter.x; textY = Math.sin((barStartAngle + stage + (parseInt(srsLvl) + 1) * 0.25 * (stage + 1) - ((stage + 1) / 20)) * arcWidth) * textRadius + canvasCenter.y; resultDimensions = drawCenteredText(ctx, textX, textY, relatedCounts[0].leech, leechFont); createHighlightHotspot(canvasCenter.x, canvasCenter.y, srsOutsideDetailRadius, srsInsideDetailRadius, (barStartAngle + parseInt(srsIndex) + parseInt(srsLvl) * 0.25 * (parseInt(srsIndex) + 1)) * arcWidth, (barStartAngle + parseInt(srsIndex) + (parseInt(srsLvl) + 1) * 0.25 * (parseInt(srsIndex) + 1)) * arcWidth, srsDetailSrs[srsIndex][srsLvl].color, srsDetailSrs[srsIndex][srsLvl].opacity, resultDimensions.minX, resultDimensions.minY, resultDimensions.width, resultDimensions.height, textX, textY, relatedCounts[0].leech, leechFont); } } } } // level by SRS function drawSRSProgressLevel(itemBreakdown) { var showSrsLevelBar = parseInt(wkof.settings.db_cockpit.showSrsLevelBar); switch (showSrsLevelBar) { case 0: // no display break; case 1: // level indications per SRS stage drawSRSProgressLevelBySrs(itemBreakdown); break; case 2: // level arrowheads drawSRSProgressLevelDials(itemBreakdown); break; } } function drawSRSProgressLevelBySrs(itemBreakdown) { var ctx = $('#speedometer')[0].getContext("2d"); var barStartAngle = srsStartAngle[parseInt(wkof.settings.db_cockpit.srsOrientation)]; // level indications per SRS stage drawSRSArc(ctx, canvasCenter.x, canvasCenter.y, srsOutsideLevelRadius, srsInsideLevelRadius, barStartAngle * arcWidth, (barStartAngle - 1) * arcWidth, srsLevelColors.bgColor); var lvlIndex = 1; var totalCount = 0; var sumFunction = function (countItem) { totalCount += countItem.total; }; var fractionFinished = wkof.settings.db_cockpit.srsLevelThreshold / 100; for (var srsIndex = srsDrawing.length - 1; srsIndex >= 0; srsIndex--) { var srsLevel = srsDrawing[srsIndex]; FIND_LEVEL_LOOP: while (true) { var textX = Math.cos((barStartAngle + parseInt(srsIndex) + 0.5) * arcWidth) * srsLevelTextRadius + canvasCenter.x; var textY = Math.sin((barStartAngle + parseInt(srsIndex) + 0.5) * arcWidth) * srsLevelTextRadius + canvasCenter.y; if (!itemBreakdown.countByLevel[lvlIndex]) { // we've run out of levels, paint the last one and be done drawCenteredText(ctx, textX, textY, "L" + (lvlIndex - 1), helperFont); createHotspot(textX, textY, helperFont.fontSize, '0% to level ' + lvlIndex); break FIND_LEVEL_LOOP; } // select all elements of the countbySRS for the current level that have passed the current srs stage (note: bySRSArray is off by 1 due to locked count) var relatedCounts = itemBreakdown.countByLevel[lvlIndex].bySRSArray.slice(srsLevel.srsLevel + 1, itemBreakdown.countByLevel[lvlIndex].bySRSArray.length); totalCount = 0; relatedCounts.forEach(sumFunction); if (totalCount / itemBreakdown.countByLevel[lvlIndex].total >= fractionFinished) { // go up a level lvlIndex++; } else { // previous level was our max drawCenteredText(ctx, textX, textY, "L" + (lvlIndex - 1), helperFont); var tooltip = Math.floor(100 * totalCount / itemBreakdown.countByLevel[lvlIndex].total) + '% to level ' + lvlIndex + ' (' + totalCount + ' of ' + itemBreakdown.countByLevel[lvlIndex].total + ')'; createHotspot(textX, textY, helperFont.fontSize, tooltip); break FIND_LEVEL_LOOP; } } } } function getLevelCounts(remainingItems) { var levelCounts = { passed: 0, total: 0, radical: 0, kanji: 0, vocabulary: 0 }; levelCounts.passed = remainingItems; return levelCounts; } function createDialDivId(level) { return "ct_dial" + level; } function drawSRSProgressLevelDials(itemBreakdown) { var ctx = $('#speedometer')[0].getContext("2d"); var barStartAngle = srsStartAngle[parseInt(wkof.settings.db_cockpit.srsOrientation)]; // level arrowheads drawSRSArc(ctx, canvasCenter.x, canvasCenter.y, srsOutsideLevelRadius, srsInsideLevelRadius, barStartAngle * arcWidth, (barStartAngle - 1.5) * arcWidth, srsLevelColors.bgColor); if (wkof.settings.db_cockpit.showLevelGridLines) { // draw target dashes for (var srsLvl in srsDrawing) { var dashAngle = (barStartAngle + parseInt(srsLvl) + 0.5) * arcWidth; var startX = Math.cos(dashAngle) * (srsInsideRadius - dashLength * 3 / 4) + canvasCenter.x; var startY = Math.sin(dashAngle) * (srsInsideRadius - dashLength * 3 / 4) + canvasCenter.y; var endX = Math.cos(dashAngle) * (srsInsideRadius + dashLength / 4) + canvasCenter.x; var endY = Math.sin(dashAngle) * (srsInsideRadius + dashLength / 4) + canvasCenter.y; drawLine(ctx, { x: startX, y: startY }, { x: endX, y: endY }, dashFont); } } var previousDialScores = []; // get the minimum dial separation from the settings, but enforce 0-10% range var minimumSeparation = Math.min(3.6, Math.max(0, wkof.settings.db_cockpit.srsLevelSeparation * 0.36)); var levelCounts = getLevelCounts(0); var levelFunction = function (countItem) { levelCounts.total += countItem.total; levelCounts.radical += countItem.radical; levelCounts.kanji += countItem.kanji; levelCounts.vocabulary += countItem.vocabulary; } var totalCount = 0; var totalFunction = function (countItem) { totalCount += countItem.total; }; // first create a list of divs and cross link them (previous - next) var dialDivs = {}; var previousLevel = null; for (var lvl = 1; lvl < itemBreakdown.countByLevel.length; lvl++) { // check if the level requires a dial div var averageScore = itemBreakdown.countByLevel[lvl].progressScore / itemBreakdown.countByLevel[lvl].total; if ((averageScore == 0) || (averageScore == 36)) { // don't paint unstarted or completely burnt continue; } dialDivs[lvl] = { previousLevel: previousLevel, nextLevel: null }; if (dialDivs[previousLevel]) { dialDivs[previousLevel].nextLevel = lvl; } previousLevel = lvl; } LEVEL_LOOP: for (lvl = 1; lvl < itemBreakdown.countByLevel.length; lvl++) { // check if the level requires a dial averageScore = itemBreakdown.countByLevel[lvl].progressScore / itemBreakdown.countByLevel[lvl].total; if ((averageScore == 0) || (averageScore == 36)) { // don't paint unstarted or completely burnt continue LEVEL_LOOP; } // create the dial div var levelData = { level: lvl, score: (100 * averageScore / 36).toFixed(1) }; var relatedCounts = itemBreakdown.countByLevel[lvl].bySRSArray.slice(0, 2); relatedCounts.forEach(levelFunction); var nextSrsCount = getLevelCounts(levelCounts.passed - levelCounts.total); levelData.unstarted = levelCounts; levelCounts = nextSrsCount; for (srsLvl in srsDrawing) { relatedCounts = filterBySRSCountOnAbbrev(itemBreakdown.countByLevel[lvl].bySRSArray, srsDrawing[srsLvl].srsAbbrev); relatedCounts.forEach(levelFunction); nextSrsCount = getLevelCounts(levelCounts.passed - levelCounts.total); levelCounts.passed = (100 * (levelCounts.passed + itemBreakdown.countByLevel[lvl].total) / itemBreakdown.countByLevel[lvl].total).toFixed(0); levelData[srsDrawing[srsLvl].srsAbbrev] = levelCounts; levelCounts = nextSrsCount; } // init for next level loop levelCounts = getLevelCounts(0); createDialDiv(levelData, dialDivs[lvl]); // check if this info needs a dial for (var previousDialScore in previousDialScores) { if (Math.abs(previousDialScores[previousDialScore] - averageScore) < minimumSeparation) { // don't paint if too close to previous dials continue LEVEL_LOOP; } } previousDialScores.push(averageScore); // calculate angle at which to draw var angle = arcWidth * (0.125 * averageScore + barStartAngle); var lvlTextX = Math.cos(angle) * levelProgressTextRadius + canvasCenter.x; var lvlTextY = Math.sin(angle) * levelProgressTextRadius + canvasCenter.y; // draw the dial and add the hover and click hotspots drawCenteredText(ctx, lvlTextX, lvlTextY, lvl, levelProgressFont); drawArrowHead(ctx, srsInsideRadius, angle, levelProgressFont.color, levelProgressFont.fontSize); createHotspot(lvlTextX, lvlTextY, levelProgressFont.fontSize, "L" + lvl + " progress " + (100 * averageScore / 36).toFixed(1) + "%"); addDialHotspot(lvlTextX, lvlTextY, levelProgressFont.fontSize, levelData); } } function createLockedCountDiv() { if (!$('#lockedCount').length) { var lockedCount = document.createElement("div"); lockedCount.id = "lockedCount"; lockedCount.className = "db_cockpit_locked"; $('#dbc_gas').append(lockedCount); $('#lockedCount').append( '<span id="lockedCountSpan"></span>' + '<span class="title">Locked</span>' + '<div class="db_cockpit_tooltipBreakdownText" style="top:60px">' + '<div class="srs-logo apprentice"></div>' + '<div class="tt_bd_totals"><ul>' + '<li>rad:<span>0</span></li>' + '<li>kan:<span>0</span></li>' + '<li>voc:<span>0</span></li>' + '</ul></div>' + '</div>' ); } if (!$('#fuelgauge').length) { var fuelgaugeDiv = document.createElement("div"); fuelgaugeDiv.id = "fuelgaugeDiv"; fuelgaugeDiv.className = "db_cockpit_fuelgauge"; $('#dbc_gas').append(fuelgaugeDiv); var fuelgauge = document.createElement("canvas"); fuelgauge.id = "fuelgauge"; fuelgauge.width = 120; fuelgauge.height = 100; $('#fuelgaugeDiv').append(fuelgauge); $('#fuelgaugeDiv').append( '<div class="db_cockpit_tooltipText">' + '<table><tr>' + '<td style="width:80px">radicals</td><td style="align:right;">0</td>' + '</tr><tr>' + '<td style="width:80px">kanji</td><td>0</td>' + '</tr><tr>' + '<td style="width:80px">vocabulary</td><td>0</td>' + '</tr>' + '<table>' + '</div>' ); } } function createLockedCount(itemBreakdown) { // createLockedCountDiv(); var showSrsLocked = parseInt(wkof.settings.db_cockpit.showSrsLocked); switch (showSrsLocked) { case 1: $('#lockedCountSpan').html(lockedCount.total); $('#lockedCount .tt_bd_totals li:first-child span').html(lockedCount.radical); $('#lockedCount .tt_bd_totals li:nth-child(2) span').html(lockedCount.kanji); $('#lockedCount .tt_bd_totals li:last-child span').html(lockedCount.vocabulary); break; default: // case 2: drawFuelGauge(itemBreakdown); $('#fuelgaugeDiv table tr:first-child td:last-child').html(lockedCount.radical); $('#fuelgaugeDiv table tr:nth-child(2) td:last-child').html(lockedCount.kanji); $('#fuelgaugeDiv table tr:last-child td:last-child').html(lockedCount.vocabulary); break; } } var FUELGAUGE_SIZE = { width: 120, height: 100 }; var FUELGAUGE_DIAL = { x: FUELGAUGE_SIZE.width / 2 - 5, y: FUELGAUGE_SIZE.height / 2 + 1, radius: FUELGAUGE_SIZE.height / 2 - 10, centerRadius: 5, angleFull: 10 * Math.PI / 6, angleEmpty: 13 * Math.PI / 6 }; var FUELGAUGE_ICON = { x: 34, y: 22, size: 16 }; var FUELGAUGE_NR = { x: 51, y: 74, font: { color: '#ffffff', fontStyle: "bold 20px Arial, sans-serif", fontSize: calculateFontSize("bold 20px Arial, sans-serif") } } function drawFuelGauge(itemBreakdown) { emptyFuelGauge(); var ctx = $('#fuelgauge')[0].getContext("2d"); ctx.save(); // dial for locked items var totalItems = lockedCount.total + itemBreakdown.countBySRS.total - filterBySRSCountOnAbbrev(itemBreakdown.countBySRS.bySRSArray, "Loc")[0].total; var dialAngle = FUELGAUGE_DIAL.angleEmpty - (Math.abs(FUELGAUGE_DIAL.angleFull - FUELGAUGE_DIAL.angleEmpty) * lockedCount.total / totalItems); var dialLeftLocation = { x: Math.cos(dialAngle - Math.PI / 180) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.x, y: Math.sin(dialAngle - Math.PI / 180) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.y }; var dialRightLocation = { x: Math.cos(dialAngle + Math.PI / 180) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.x, y: Math.sin(dialAngle + Math.PI / 180) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.y }; var leftAttach = { x: Math.cos(dialAngle - Math.PI / 2) * FUELGAUGE_DIAL.centerRadius + FUELGAUGE_DIAL.x, y: Math.sin(dialAngle - Math.PI / 2) * FUELGAUGE_DIAL.centerRadius + FUELGAUGE_DIAL.y }; var rightAttach = { x: Math.cos(dialAngle + Math.PI / 2) * FUELGAUGE_DIAL.centerRadius + FUELGAUGE_DIAL.x, y: Math.sin(dialAngle + Math.PI / 2) * FUELGAUGE_DIAL.centerRadius + FUELGAUGE_DIAL.y }; var ctxGradient = ctx.createRadialGradient( FUELGAUGE_DIAL.x, FUELGAUGE_DIAL.y, 5, FUELGAUGE_DIAL.x, FUELGAUGE_DIAL.y, FUELGAUGE_DIAL.radius - 5 ); ctxGradient.addColorStop(0, "#882d9e"); ctxGradient.addColorStop(1, "#aa38c6"); ctx.fillStyle = ctxGradient; ctx.beginPath(); ctx.moveTo(leftAttach.x, leftAttach.y); ctx.lineTo(dialLeftLocation.x, dialLeftLocation.y); ctx.lineTo(dialRightLocation.x, dialRightLocation.y); ctx.lineTo(rightAttach.x, rightAttach.y); ctx.closePath(); ctx.fill(); // dial center to cover the nasty end of the dial var centerGradient = ctx.createRadialGradient( FUELGAUGE_DIAL.x - 2, FUELGAUGE_DIAL.y - 2, 0, FUELGAUGE_DIAL.x - 2, FUELGAUGE_DIAL.y - 2, FUELGAUGE_DIAL.centerRadius + 3 ); centerGradient.addColorStop(0, "#999999"); centerGradient.addColorStop(1, "#666666"); ctx.fillStyle = centerGradient; ctx.beginPath(); ctx.arc(FUELGAUGE_DIAL.x, FUELGAUGE_DIAL.y, FUELGAUGE_DIAL.centerRadius, 0, 2 * Math.PI); ctx.fill(); ctx.restore(); // add the locked count drawCenteredText(ctx, FUELGAUGE_NR.x, FUELGAUGE_NR.y, lockedCount.total, FUELGAUGE_NR.font); } function emptyFuelGauge() { var ctx = $('#fuelgauge')[0].getContext("2d"); ctx.clearRect(0, 0, FUELGAUGE_SIZE.width, FUELGAUGE_SIZE.height); ctx.save(); // background ctx.fillStyle = '#bbbbbb'; ctx.beginPath(); ctx.arc(FUELGAUGE_SIZE.width / 2, FUELGAUGE_SIZE.height / 2, FUELGAUGE_SIZE.height / 2 - 2, 0, 2 * Math.PI); ctx.fill(); // rim ctx.strokeStyle = '#666666'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(FUELGAUGE_SIZE.width / 2, FUELGAUGE_SIZE.height / 2, FUELGAUGE_SIZE.height / 2 - 2, 0, 2 * Math.PI); ctx.stroke(); // scale ctx.strokeStyle = '#666666'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(FUELGAUGE_DIAL.x, FUELGAUGE_DIAL.y, FUELGAUGE_DIAL.radius, FUELGAUGE_DIAL.angleFull, FUELGAUGE_DIAL.angleEmpty); ctx.stroke(); // indicator markers // full ctx.beginPath(); ctx.moveTo(Math.cos(FUELGAUGE_DIAL.angleFull) * (FUELGAUGE_DIAL.radius - 5) + FUELGAUGE_DIAL.x, Math.sin(FUELGAUGE_DIAL.angleFull) * (FUELGAUGE_DIAL.radius - 5) + FUELGAUGE_DIAL.y); ctx.lineTo(Math.cos(FUELGAUGE_DIAL.angleFull) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.x, Math.sin(FUELGAUGE_DIAL.angleFull) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.y); ctx.stroke(); // 3/4 ctx.beginPath(); ctx.moveTo(Math.cos((3 * FUELGAUGE_DIAL.angleFull + FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius - 3) + FUELGAUGE_DIAL.x, Math.sin((3 * FUELGAUGE_DIAL.angleFull + FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius - 3) + FUELGAUGE_DIAL.y); ctx.lineTo(Math.cos((3 * FUELGAUGE_DIAL.angleFull + FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius + 0) + FUELGAUGE_DIAL.x, Math.sin((3 * FUELGAUGE_DIAL.angleFull + FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius + 0) + FUELGAUGE_DIAL.y); ctx.stroke(); // 1/2 ctx.beginPath(); ctx.moveTo(Math.cos(23 * Math.PI / 12) * (FUELGAUGE_DIAL.radius - 3) + FUELGAUGE_DIAL.x, Math.sin(23 * Math.PI / 12) * (FUELGAUGE_DIAL.radius - 3) + FUELGAUGE_DIAL.y); ctx.lineTo(Math.cos(23 * Math.PI / 12) * (FUELGAUGE_DIAL.radius + 3) + FUELGAUGE_DIAL.x, Math.sin(23 * Math.PI / 12) * (FUELGAUGE_DIAL.radius + 3) + FUELGAUGE_DIAL.y); ctx.stroke(); // 1/4 ctx.beginPath(); ctx.moveTo(Math.cos((FUELGAUGE_DIAL.angleFull + 3 * FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius - 3) + FUELGAUGE_DIAL.x, Math.sin((FUELGAUGE_DIAL.angleFull + 3 * FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius - 3) + FUELGAUGE_DIAL.y); ctx.lineTo(Math.cos((FUELGAUGE_DIAL.angleFull + 3 * FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius + 0) + FUELGAUGE_DIAL.x, Math.sin((FUELGAUGE_DIAL.angleFull + 3 * FUELGAUGE_DIAL.angleEmpty) / 4) * (FUELGAUGE_DIAL.radius + 0) + FUELGAUGE_DIAL.y); ctx.stroke(); // empty ctx.beginPath(); ctx.moveTo(Math.cos(FUELGAUGE_DIAL.angleEmpty) * (FUELGAUGE_DIAL.radius - 5) + FUELGAUGE_DIAL.x, Math.sin(FUELGAUGE_DIAL.angleEmpty) * (FUELGAUGE_DIAL.radius - 5) + FUELGAUGE_DIAL.y); ctx.lineTo(Math.cos(FUELGAUGE_DIAL.angleEmpty) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.x, Math.sin(FUELGAUGE_DIAL.angleEmpty) * (FUELGAUGE_DIAL.radius + 5) + FUELGAUGE_DIAL.y); ctx.stroke(); // indicators var indicatorFont = { color: '#666666', fontStyle: "10px Arial, sans-serif", fontSize: 10 }; drawCenteredText(ctx, Math.cos(FUELGAUGE_DIAL.angleFull - Math.PI / 12) * FUELGAUGE_DIAL.radius + FUELGAUGE_DIAL.x, Math.sin(FUELGAUGE_DIAL.angleFull - Math.PI / 12) * FUELGAUGE_DIAL.radius + FUELGAUGE_DIAL.y, "F", indicatorFont); drawCenteredText(ctx, Math.cos(FUELGAUGE_DIAL.angleEmpty + Math.PI / 12) * FUELGAUGE_DIAL.radius + FUELGAUGE_DIAL.x, Math.sin(FUELGAUGE_DIAL.angleEmpty + Math.PI / 12) * FUELGAUGE_DIAL.radius + FUELGAUGE_DIAL.y, "E", indicatorFont); // icon ctx.fillStyle = '#888888'; ctx.strokeStyle = '#666666'; // head ctx.beginPath(); ctx.moveTo(41, 22); ctx.lineTo(50, 22); ctx.lineTo(50, 28); ctx.lineTo(41, 28); ctx.lineTo(41, 22); ctx.stroke(); // body ctx.fillRect(42, 28, 8, 9); // fuel line ctx.beginPath(); ctx.moveTo(49, 31); ctx.lineTo(50, 31); ctx.lineTo(51, 32); ctx.lineTo(51, 35); ctx.lineTo(52, 35); ctx.lineTo(53, 34); ctx.lineTo(53, 26); ctx.lineTo(52, 26); ctx.lineTo(52, 25); ctx.lineTo(50, 25); ctx.stroke(); ctx.restore(); } function createTotalProgressDiv() { if (!$('#totalProgressBar').length) { var totalProgressBar = document.createElement("div"); totalProgressBar.id = "totalProgressBar"; totalProgressBar.className = 'hidden'; $('#dbc_bar60').append(totalProgressBar); $('#totalProgressBar').css("z-index", "0"); } } function createTotalProgressIndication(itemBreakdown) { createTotalProgressDiv(); populateTotalProgressIndication(itemBreakdown); } function populateTotalProgressIndication(itemBreakdown) { // emtpy the bar $('#totalProgressBar').html(''); // sum all locked items, all items up to the current level and subtract the current level locked items var allItems = lockedCount.total + itemBreakdown.countBySRS.total - itemBreakdown.countBySRS.bySRSArray[0].total; // unstarted is locked and init (ready for lessons) var totalUnstarted = lockedCount.total + itemBreakdown.countBySRS.bySRSArray[1].total; var totalCount = 0; var cumulativePercentage = 0; var sumFunction = function (countItem) { totalCount += countItem.total; }; var zindex = 7; var previousName; // burn -> apprentice only if not reversed if (!wkof.settings.db_cockpit.reverseTotalProgress) { for (var srsLvl = srsDrawing.length - 1; srsLvl >= 0; srsLvl--) { var relatedCounts = filterBySRSCountOnAbbrev(itemBreakdown.countBySRS.bySRSArray, srsDrawing[srsLvl].srsAbbrev); totalCount = 0; relatedCounts.forEach(sumFunction); var percentage = (100 * totalCount / allItems).toFixed(4); createSrsProgressBar(srsDrawing[srsLvl].srsName, srsDrawing[srsLvl].color(), srsDrawing[srsLvl].color().progressText, percentage, cumulativePercentage, zindex--, previousName); cumulativePercentage += parseFloat(percentage); previousName = srsDrawing[srsLvl].srsName; } // and add the srs-unstaged part if (totalUnstarted > 0) { // draw it as 'the rest' to prevent white horizontal line due to rounding differences var remainingPercentage = 100 - cumulativePercentage; createSrsProgressBar("Locked/Lessons", { start: "#545454", end: "#545454" }, "#efefef", remainingPercentage, cumulativePercentage, zindex--, previousName); } } // apprentice -> burn only if reversed if (wkof.settings.db_cockpit.reverseTotalProgress) { // the srs-unstaged part var unstartedPercentage = (100 * totalUnstarted / allItems).toFixed(4); createSrsProgressBar("Locked/Lessons", { start: "#545454", end: "#545454" }, "#efefef", unstartedPercentage, cumulativePercentage, zindex--, previousName); cumulativePercentage += parseFloat(unstartedPercentage); previousName = "Locked/Lessons"; for (srsLvl = 0; srsLvl < srsDrawing.length - 1; srsLvl++) { relatedCounts = filterBySRSCountOnAbbrev(itemBreakdown.countBySRS.bySRSArray, srsDrawing[srsLvl].srsAbbrev); totalCount = 0; relatedCounts.forEach(sumFunction); percentage = (100 * totalCount / allItems).toFixed(4); createSrsProgressBar(srsDrawing[srsLvl].srsName, srsDrawing[srsLvl].color(), srsDrawing[srsLvl].color().progressText, percentage, cumulativePercentage, zindex--, previousName); cumulativePercentage += parseFloat(percentage); previousName = srsDrawing[srsLvl].srsName; } // and add the burnt part as 'the rest' srsLvl = srsDrawing.length - 1; relatedCounts = filterBySRSCountOnAbbrev(itemBreakdown.countBySRS.bySRSArray, srsDrawing[srsLvl].srsAbbrev); totalCount = 0; relatedCounts.forEach(sumFunction); if (totalCount > 0) { remainingPercentage = 100 - cumulativePercentage; // draw it as 'the rest' to prevent white horizontal line due to rounding differences createSrsProgressBar(srsDrawing[srsLvl].srsName, srsDrawing[srsLvl].color(), srsDrawing[srsLvl].color().progressText, remainingPercentage, cumulativePercentage, zindex--, previousName); } } } var timeout = {}; function createSrsProgressBar(name, color, altTextColor, percentage, offsetLeft, zindex, previousName) { // bloody pass as string, give me the numbers var nrPercentage = parseFloat(percentage); var nrOffset = parseFloat(offsetLeft); var totalWidth = ((nrPercentage + nrOffset)); var idName = name.slice(0, 3); var textColor = '#efefef'; if (altTextColor != undefined) { textColor = altTextColor; } $('#totalProgressBar').append( '<div id="tpb_' + idName + '" title="' + name + ': ' + nrPercentage.toFixed(2) + '%" style="width: ' + totalWidth + '%; background-image: linear-gradient(' + color.start + ', ' + color.end + '); color: ' + textColor + '; margin-left: 0; z-index: ' + zindex + ';"><span style="margin-left: ' + (totalWidth == 0 ? 0 : (100 * nrOffset / totalWidth)) + '%; align: center;" displayvalues=":' + nrPercentage.toFixed(2) + '%_:' + nrPercentage.toFixed(1) + '%_0: ">$nbsp;</span></div>'); $('#cockpitPanel')[0].addEventListener('dashboard-cockpit-loaded', e => window.db_cockpit_calcAndDisplayTotalProgressBarPercentage('tpb_' + idName, previousName ? 'tpb_' + previousName.slice(0, 3) : null)); timeout[idName] = false; window.addEventListener('resize', function () { clearTimeout(timeout['' + idName]); timeout['' + idName] = setTimeout(() => window.db_cockpit_displayTotalProgressBarPercentage('tpb_' + idName, previousName ? 'tpb_' + previousName.slice(0, 3) : null), 250); }); } function calcAndDisplayTotalProgressBarPercentage(divId, blockingDivId) { var tpbSpan = $('#' + divId + ' span')[0]; var original = tpbSpan.getAttribute("displayvalues"); var sizeValues = tpbSpan.getAttribute("displayvalues").split("_").map(stringValue => stringValue.split(":")); if (sizeValues[0][0] == '') { tpbSpan.innerHTML = sizeValues[0][1]; sizeValues[0][0] = tpbSpan.getBoundingClientRect().width; tpbSpan.innerHTML = sizeValues[1][1]; sizeValues[1][0] = tpbSpan.getBoundingClientRect().width; tpbSpan.setAttribute("displayvalues", sizeValues.map(sizeValue => sizeValue.join(":")).join("_")); } window.db_cockpit_displayTotalProgressBarPercentage(divId, blockingDivId); } window.db_cockpit_calcAndDisplayTotalProgressBarPercentage = calcAndDisplayTotalProgressBarPercentage; function displayTotalProgressBarPercentage(divId, blockingDivId) { var tpbDiv = $('#' + divId)[0]; var tbpSpan = $('#' + divId + ' span')[0]; var sizeValues = tbpSpan.getAttribute("displayvalues").split("_").map(stringValue => stringValue.split(":")); if (sizeValues[0][0] == '') { // not ready to display return; } var subtractSize = 0; if (blockingDivId) { var tpbBlockingDiv = $('#' + blockingDivId); subtractSize = $('#' + blockingDivId)[0].getBoundingClientRect().width; } var availableSize = $('#' + divId)[0].getBoundingClientRect().width - subtractSize; availableSize = Math.max(availableSize, 0); for (var i = 0; i < sizeValues.length; i++) { if (sizeValues[i][0] <= availableSize) { tbpSpan.innerHTML = sizeValues[i][1]; return; } } } window.db_cockpit_displayTotalProgressBarPercentage = displayTotalProgressBarPercentage; function displayGrid(allPassed) { resetGridDefaults(allPassed); structureGrid(allPassed); showOrHidePanels(allPassed); } function resetGridDefaults(allPassed) { // default layout: // -------------------|------------------|------------------|------------------- // | dbc_gas | dbc_core | dbc_cruise | // |------------------- -------------------| // | dbc_currentLevel | | dbc_forecast | // --------------------------------------|------------------- - // | dbc_bar60 | | // -------------------|------------------|------------------- - // | | // |------------------- // GRID CELL PLACEMENTS // $('#dbc_bar60').css('grid-row-start', '4'); // $('#dbc_core').css('grid-row-start', '1'); // $('#dbc_currentLevel').css('grid-row-start', '2'); // $('#dbc_currentLevel').css('grid-row-end', '3'); // $('#dbc_forecast').css('grid-row-start', '2'); // $('#dbc_forecast').css('grid-row-end', '5'); // $('#dbc_bar60').css('grid-column-start', '1'); // $('#dbc_bar60').css('grid-column-end', 'span 3'); // $('#dbc_bar60').css('grid-row-start', '3'); // GRID CELL VISIBILITY $('#dbc_gas').removeClass('hidden'); $('#dbc_cruise').removeClass('hidden'); // GRID SIZING AND RESIZING $('#cockpitPanel').css('grid-template-columns', 'auto ' + getCanvasGridWidth() + 'px ' + getCanvasGridWidth() + 'px ' + BASE_COLUMN_WIDTH + 'px'); $('#cockpitPanel').css('min-width', ((getLeftColumnWidth(allPassed) + 30 + getCanvasSize() + BASE_COLUMN_WIDTH))); // left column, 3xmargin(10), canvas and forecast (335) // MOVE CELL CONTENTS var reviewButtonBlock = $('#reviewButtonBlock').detach(); $('#dbc_cruise').append(reviewButtonBlock); if (isRSPresent()) { var reviewSummaryDiv = $('#reviewSummaryDiv').detach(); $('#dbc_cruise').append(reviewSummaryDiv); } // MARGINS AND STYLING $('#dbc_core').css('margin-top', ''); $('#dbc_bar60').css('margin-top', ''); reviewButtonBlock.css("float", "left"); reviewButtonBlock.css("margin-left", "0px"); reviewButtonBlock.css("width", "100%"); $('#dbc_forecast').css('min-height', ''); $('.wk-panel--review-forecast').height("56.5%"); if (moveExtraStudy()) { var recentMistakes = $('.wk-panel--recent-mistakes'); recentMistakes.css("margin-bottom", "10px"); var extraStudy = $('.wk-panel--extra-study'); extraStudy.detach(); extraStudy.css('max-width', ''); extraStudy.css('margin-top', ''); extraStudy.css('margin-bottom', ''); var extraRecentStudy = $('#extraRecentStudy'); extraRecentStudy.append(extraStudy); } } function structureGrid(allPassed) { var tpIndicator = parseInt(wkof.settings.db_cockpit.showTotalProgress); switch (tpIndicator) { case 0: // not visible // place in center beneath arc $('#dbc_bar60').css('grid-column-end', 'span 2'); $('#dbc_bar60').css('grid-column-start', '2'); $('#dbc_bar60').css('grid-row-start', '3'); // and adjust size col heights $('#dbc_currentLevel').css('grid-row-end', '4'); $('#dbc_forecast').css('grid-row-end', '4'); break; case 1: // long bar with extended review forecast // is the default break; case 2: // short bar // place in center beneath arc $('#dbc_bar60').css('grid-column-end', 'span 2'); $('#dbc_bar60').css('grid-column-start', '2'); $('#dbc_bar60').css('grid-row-start', '3'); // and adjust size col heights $('#dbc_currentLevel').css('grid-row-end', '4'); break; case 3: // long bar with extended level progress panel // shrink col height $('#dbc_forecast').css('grid-row-end', '3'); // shift bar $('#dbc_bar60').css('grid-column-start', '2'); // and adjust size col heights $('#dbc_currentLevel').css('grid-row-end', '5'); break; case 4: // full bar over whole screen width // shrink col height $('#dbc_forecast').css('grid-row-end', '3'); // extend bar $('#dbc_bar60').css('grid-column-end', 'span 4'); break; case 5: // top bar above the arc createTotalProgressDiv(); // shift bar out of the way $('#dbc_bar60').css('grid-row-start', '4'); // move arc down $('#dbc_core').css('grid-row-start', '2'); $('#dbc_core').css('margin-top', '-50px'); // position bar $('#dbc_bar60').css('grid-column-end', 'span 2'); $('#dbc_bar60').css('grid-column-start', '2'); $('#dbc_bar60').css('grid-row-start', '1'); $('#dbc_bar60').css('margin-top', '4px'); // and adjust col height $('#dbc_currentLevel').css('grid-row-end', '5'); break; } var applyRightGridWidth = BASE_COLUMN_WIDTH + 10; var applyLeftGridWidth = getLeftColumnWidth(allPassed) + 10; if (!isRightGridVisible(allPassed)) { if (wkof.settings.db_cockpit.showMainButtons) { // 1. shift reviewsbutton to left of the speedometer var reviewButtonBlock = $('#reviewButtonBlock').detach(); $('#dbc_gas').prepend(reviewButtonBlock); reviewButtonBlock.css("float", "right"); reviewButtonBlock.css("margin-left", "10px"); } if (isRSVisible()) { var reviewSummaryDiv = $('#reviewSummaryDiv').detach(); $('#dbc_gas').prepend(reviewSummaryDiv); $('#reviewSummaryTile').css("margin-left", "10px"); } // 2. restructure column applyRightGridWidth = 0; // also occupy the review forecast space so we fill out until the margin switch (tpIndicator) { case 1: // long bar with extended review forecast $('#dbc_bar60').css('grid-column-end', 'span 4'); break; case 2: // short bar $('#dbc_bar60').css('grid-column-end', 'span 3'); break; case 5: // top bar above the arc $('#dbc_bar60').css('grid-column-end', 'span 3'); break; } } else if (!isTopRightCellVisible(allPassed)) { $('#dbc_cruise').addClass('hidden'); $('#dbc_forecast').css('grid-row-start', '1'); } if (!isLeftGridVisible(allPassed)) { // 1. restructure column applyLeftGridWidth = 0; // 2. hide dbc_gas because of the min width configurations $('#dbc_gas').addClass('hidden'); // also occupy the level progress space so we fill out until the margin switch (tpIndicator) { case 2: // short bar $('#dbc_bar60').css('grid-column-start', '1'); $('#dbc_bar60').css('grid-column-end', 'span 3'); break; case 3: // long bar with extended level progress panel $('#dbc_bar60').css('grid-column-start', '1'); $('#dbc_bar60').css('grid-column-end', 'span 4'); break; case 5: // top bar above the arc $('#dbc_bar60').css('grid-column-start', '1'); $('#dbc_bar60').css('grid-column-end', 'span 3'); break; } } else if (!isTopLeftCellVisible(allPassed)) { $('#dbc_gas').addClass('hidden'); $('#dbc_currentLevel').css('grid-row-start', '1'); } // restructure columns based on visibility $('#cockpitPanel').css('grid-template-columns', (applyLeftGridWidth > 0 ? 'auto ' : '0px ') + getCanvasGridWidth() + 'px ' + getCanvasGridWidth() + 'px ' + (applyRightGridWidth > 0 ? applyRightGridWidth - 10 : 0) + 'px'); $('#cockpitPanel').css('min-width', ((applyLeftGridWidth + 10 + getCanvasSize() + applyRightGridWidth))); // left col, 2xmargin(10) and canvas var extraRecentStudy = $('#extraRecentStudy'); if (moveExtraStudy()) { // place the new extra study panel var locationSetting = parseInt(getExtraStudyLocationSetting()); var forecast = $('.wk-panel--review-forecast'); var extraStudy = $('.wk-panel--extra-study'); switch (locationSetting) { case 1: // default, above dashboard progress panel extraRecentStudy.detach(); extraStudy.css('margin-bottom', '10px'); $('#dbc_currentLevel').prepend(extraRecentStudy); break; case 2: // below main arc extraRecentStudy.detach(); extraStudy.css('max-width', (getCanvasGridWidth() * 2 + 10) + 'px'); $('#dbc_core').append(extraRecentStudy); break; case 3: // above review forecast extraRecentStudy.detach(); extraStudy.css('margin-bottom', '10px'); forecast.height("267px"); $('#dbc_forecast').prepend(extraRecentStudy); if (wkof.settings.db_cockpit.showForecast) { $('#dbc_forecast').css('min-height', ((327 + extraStudy.outerHeight())) + 'px'); } break; case 4: // below review forecast extraRecentStudy.detach(); forecast.height("267px"); $('#dbc_forecast').append(extraRecentStudy); if (wkof.settings.db_cockpit.showForecast) { $('#dbc_forecast').css('min-height', ((327 + extraStudy.outerHeight())) + 'px'); } break; case 5: // above main arc extraRecentStudy.detach(); extraStudy.css('margin-bottom', '10px'); extraStudy.css('max-width', (getCanvasGridWidth() * 2 + 10) + 'px'); $('#dbc_core').css('margin-top', ''); $('#dbc_core').prepend(extraRecentStudy); break; case 6: // below dashboard progress panel extraRecentStudy.detach(); extraStudy.css('margin-top', '10px'); $('#dbc_currentLevel').append(extraRecentStudy); break; case 7: // center top. This position DOES NOT EXIST in the cockpit itself, but only as a translation from ESM extraRecentStudy.detach(); extraStudy.css('margin-bottom', '10px'); extraStudy.css('max-width', (getCanvasGridWidth() * 2 + 10) + 'px'); // if total progress bar setting is top, we put extra study above that if (wkof.settings.db_cockpit.showTotalProgress == '5') { $('#dbc_bar60').prepend(extraRecentStudy); var shiftHeight = $('.wk-panel--extra-study').outerHeight() + $('#totalProgressBar').outerHeight() - 100 + 20; $('#dbc_core').css('margin-top', shiftHeight + 'px'); } // else above the arc (same as option 5) else { $('#dbc_core').prepend(extraRecentStudy); } break; case 8: // same as 1, but above recent mistakes extraRecentStudy.detach(); $('#dbc_currentLevel').prepend(extraRecentStudy); extraStudy.detach(); extraStudy.css('margin-bottom', '10px'); extraRecentStudy.prepend(extraStudy); break; } } else { // still take care of recent mistakes, put it above level progress extraRecentStudy.detach(); $('#dbc_currentLevel').prepend(extraRecentStudy); } } function showOrHidePanels(allPassed) { applyVisibility((wkof.settings.db_cockpit.showTotalProgress != '0'), $('#totalProgressBar')); applyVisibility(wkof.settings.db_cockpit.showForecast, $('.wk-panel--review-forecast')); applyVisibility(wkof.settings.db_cockpit.showMainButtons, $('#reviewButtonBlock')); applyVisibility(wkof.settings.db_cockpit.showReviewSummary, $('#reviewSummaryDiv')); applyVisibility(showLessonsButton(), $('.dashboard__lessons-and-reviews-section')); // applyVisibility(showLockedCount('1'), $('#lockedCount')); // applyVisibility(showLockedCount('2'), $('#fuelgaugeDiv')); applyVisibility(showLevelProgress(allPassed), $('.wk-panel--level-progress')); // place the new extra study panel if it exists and is not governed by the Extra study mover script if (moveExtraStudy()) { var locationSetting = parseInt(getExtraStudyLocationSetting()); applyVisibility((locationSetting != 0), $('#extraRecentStudy')); } // 4 bottom panels, hide or show applyVisibility(wkof.settings.db_cockpit.showRecentUnlocks, $('.wk-panel--dashboard-list-unlocks')); applyVisibility(wkof.settings.db_cockpit.showCriticalItems, $('.wk-panel--dashboard-list-critical')); applyVisibility(wkof.settings.db_cockpit.showRecentBurns, $('.wk-panel--dashboard-list-burned')); applyVisibility(wkof.settings.db_cockpit.showForumTopics, $('.dashboard__community-banner')); } function applyVisibility(setting, panel) { if (setting) { panel.removeClass('hidden'); } else { panel.addClass('hidden'); } } // Adds the script's CSS to the page function add_css() { $('head').append( `<style id="db_cockpit_CSS"> #cockpitPanel { display: grid; grid-template-columns: auto 19.5% 19.5% 29%; grid-template-rows: 240px auto; grid-gap: 10px 10px; margin-bottom: 10px; } #dbc_gas { grid-column-start: 1; grid-column-end: 2; grid-row-start: 1; grid-row-end: span 2; } #dbc_currentLevel { grid-column-start: 1; grid-column-end: span 2; grid-row-start: 3; grid-row-end: 4; } #dbc_core { grid-column-start: 2; grid-column-end: span 2; grid-row-start: 1; grid-row-end: span 2; aligh: center; margin: 0 auto; } #dbc_bar60 { grid-column-start: 1; grid-column-end: span 3; grid-row-start: 4; grid-row-end: span 1; } #dbc_cruise { grid-column-start: 4; grid-column-end: 5; grid-row-start: 1; grid-row-end: span 2; } #dbc_forecast { grid-column-start: 3; grid-column-end: span 2; grid-row-start: 3; grid-row-end: span 2; } .progression { display: block; } .db_cockpit_locked { height: 70px; width: 120px; position: relative; float: left; font: bold 24px Arial, sans-serif; color: #fff; background-color: #aaa; padding-top: 30px; text-align: center; } .db_cockpit_locked .db_cockpit_tooltipBreakdownText { top: -60px; left: -10px; } .db_cockpit_locked .title { font-size: 14px; font-weight: normal; color: rgba(0,0,0,0.3); text-shadow: none; position: absolute; bottom: 0; left: 0; width: 100%; padding-bottom: 20px; margin-bottom: 2.5px; } .db_cockpit_fuelgauge { height: 100px; width: 120px; float: left; } .db_cockpit_tooltips { height: 0px; } .db_cockpit_tooltipText { visibility: hidden; border: 1px solid #555; background-color: #f8f8db; color: #555; font: 12px Segoe UI; text-align: left; padding: 3px 7px; position: absolute; z-index: 1; } .db_cockpit_fuelgauge .db_cockpit_tooltipText { top: -80px; left: 95px; position: relative; } .db_cockpit_fuelgauge .db_cockpit_tooltipText tr td:last-child { text-align: right; } .db_cockpit_tooltipBreakdownText { visibility: hidden; border: 3px solid rgba(75,75,75,0.8); border-radius: 5px; background-color: #333; width: 150px; color: rgba(255,255,255,0.7); font: 14px Arial, sans-serif; text-align: left; padding: 0; position: absolute; z-index: 3; display: grid; grid-template-columns: 50% 50%; grid-template-rows: 81px auto; grid-gap: 0px; } .db_cockpit_tooltipBreakdownText .srs-logo { grid-column-start: 1; grid-column-end: 2; grid-row-start: 1; grid-row-end: 2; } .db_cockpit_tooltipBreakdownText .tt_bd_totals { grid-column-start: 2; grid-column-end: 3; grid-row-start: 1; grid-row-end: 2; } .db_cockpit_tooltipBreakdownText ul { list-style-type:none; margin: 0 0 0 0; } .db_cockpit_tooltipBreakdownText li:first-child { background-image: linear-gradient(to bottom, #0af, #0093dd); } .db_cockpit_tooltipBreakdownText li:nth-child(2) { background-image: linear-gradient(to bottom, #f0a, #dd0093); } .db_cockpit_tooltipBreakdownText li:nth-child(3) { background-image: linear-gradient(to bottom, #a0f, #9300dd); } .db_cockpit_tooltipBreakdownText li { line-height: 27px; padding-left: 5px; padding-right: 5px; } .db_cockpit_tooltipBreakdownText li span { font-weight: bold; float: right; } .db_cockpit_tooltipDialText { font: 12px Arial, sans-serif; visibility: hidden; border-radius: 5px; background-color: #333; color: #aaa; text-align: right; padding: 0; position: absolute; z-index: 2; } .db_cockpit_tooltipDialText table { margin: 5px; } .db_cockpit_ctd_summary { text-align: center; } .db_cockpit_tooltipDialText table tr td { width: 40px; } .db_cockpit_ctd_level { font: bold 36px Arial, sans-serif; } .db_cockpit_ctd_link { font: 12px Arial, sans-serif; vertical-align: middle; } .db_cockpit_ctd_clickable { cursor: pointer; } .db_cockpit_ctd_score { font: bold 16px Arial, sans-serif } .db_cockpit_ctd_header { font-weight: bold; } .db_cockpit_ctd_category { font-weight: bold; text-align: left; } .db_cockpit_tooltipText_visible { visibility: visible; } .db_cockpit_clicktipText_visible { visibility: visible; } .db_cockpit_locked:hover .db_cockpit_tooltipBreakdownText { visibility: visible; } .db_cockpit_fuelgauge:hover .db_cockpit_tooltipText { visibility: visible; } #totalProgressBar { height: 30px; box-shadow: 0 0 2pt 2pt #BBBBBB; position: relative; width: 99.99%; } #totalProgressBar div { height: 30px; position: absolute; text-align: center; vertical-align: middle; line-height: 30px; color: #efefef; } </style>`); } //-----------------------------------------------------------------------------------------------------------------------------------------------------// //------------------------------------------------------------------MENU AND SETTINGS------------------------------------------------------------------// //-----------------------------------------------------------------------------------------------------------------------------------------------------// function consoleLog(obj) { $.each(obj, function (key, element) { console.log('key: ' + key + ', value: ' + element); }); } // Load settings and set defaults function load_settings() { var defaults = { srsOrientation: '0', srsSize: '0', goldenBurn: true, presetLevelColor: '0', levelNrInside: '#dd0093', levelNrOutside: '#0093dd', showSrsLocked: '2', showMainButtons: true, lvl60Lessons: false, lvl60Progress: false, showSrsDetail: false, showLeechCount: false, leechThreshold: 1, leechMinWrong: 1, leechDisplay: '0', showSrsLevelBar: '1', srsLevelThreshold: 90, srsLevelSeparation: 2, showLevelGridLines: false, showTotalProgress: '1', reverseTotalProgress: false, showForecast: true, showRecentUnlocks: false, showCriticalItems: true, showRecentBurns: true, showForumTopics: true, showExtraStudy: '1', showReviewSummary: true }; return wkof.Settings.load('db_cockpit', defaults); } // Installs the options button in the menu function install_menu() { var config = { name: 'db_cockpit_settings', submenu: 'Settings', title: 'Dashboard Cockpit', on_click: open_settings }; wkof.Menu.insert_script_link(config); } // Create the options function open_settings(items) { var config = { script_id: 'db_cockpit', title: 'Dashboard Cockpit Settings', on_save: fetch_and_update, content: { tabs: { type: 'tabset', content: { srsProgressPage: { type: 'page', label: 'SRS Progress', hover_tip: 'Settings for the SRS Progress display', content: { srsStyle_group: { type: 'group', label: 'Display Style', content: { srsOrientation: { type: 'dropdown', label: 'Orientation', hover_tip: 'Tilts the SRS progress overview', default: '0', content: { 0: 'left', 1: 'down' } }, srsSize: { type: 'dropdown', label: 'Size', hover_tip: 'Size of the SRS progress arc', default: '0', content: { 0: 'large', 1: 'medium', 2: 'small' } }, goldenBurn: { type: 'checkbox', label: 'Golden Burn', hover_tip: 'Display golden Burn background', default: true }, presetLevelColor: { type: 'dropdown', label: 'Level number display', hover_tip: 'Display method for the center level number', default: '0', content: { 0: 'default (pale pink-blue)', 1: 'pale with selected colors', 2: 'bright with selected colors' } }, levelNrInside: { type: 'color', label: 'Level center color', hover_tip: 'Color of the center of the level number', default: '#dd0093' }, levelNrOutside: { type: 'color', label: 'Level outside color', hover_tip: 'Color the level number fades into on the edges', default: '#0093dd' } } }, srsLocked_group: { type: 'group', label: 'Locked items', content: { showSrsLocked: { type: 'dropdown', label: 'Locked Count', hover_tip: 'Display the Locked count', default: '2', content: { 0: 'none', 1: 'Tile', 2: 'Fuel Gauge (car)' } } } }, lessonsReviews_group: { type: 'group', label: 'Lessons and Reviews buttons', content: { showMainButtons: { type: 'checkbox', label: 'Show lessons/reviews buttons', hover_tip: 'Provided for users running scripts that display alternate buttons. Cockpit does not move the buttons elsewhere!', default: true } } }, level60_group: { type: 'group', label: 'Level 60 options', content: { lvl60Lessons: { type: 'checkbox', label: 'Auto hide lessons section', hover_tip: 'Hides the locked count and lessons button if neither has items left', default: false }, lvl60Progress: { type: 'checkbox', label: 'Auto hide level progress', hover_tip: 'Hides the level progress panel when locked count and lessons are zero, and all items have reached Guru at least once (passed)', default: false } } } } }, srsPluginsPage: { type: 'page', label: 'Progress Details', hover_tip: 'Features that provide more details', content: { srsDetail_group: { type: 'group', label: 'SRS Details', content: { showSrsDetail: { type: 'checkbox', label: 'Show SRS Detail', hover_tip: 'Display the Apprentice and Guru detail splits', default: false }, showLeechCount: { type: 'checkbox', label: 'Show Leech Count', hover_tip: 'Displays counts of the identified leeches per stage', default: false }, leechThreshold: { type: 'number', label: 'Leech threshold', hover_tip: 'Higher threshold considers fewer items as leeches (1-4)', default: 1 }, leechMinWrong: { type: 'number', label: 'Leech min. incorrect attempts', hover_tip: 'Consider only items with at least this many incorrect answers (1-10)', default: 1 }, leechDisplay: { type: 'dropdown', label: 'Brightness of Leech display', hover_tip: 'Select the desired brightness', default: '0', content: { 0: 'dull', 1: 'bright on hover', 2: 'bright', 3: 'inverted ellipse' } }, } }, srsLevel_group: { type: 'group', label: 'Level by SRS', content: { showSrsLevelBar: { type: 'dropdown', label: 'Display Level by SRS', hover_tip: 'Display user level SRS progression', default: '0', content: { 0: 'none', 1: 'Level by SRS stage', 2: 'Level dials' } }, srsLevelThreshold: { type: 'number', label: 'Level by SRS threshold', hover_tip: 'Percentage to consider level done', default: 90 }, srsLevelSeparation: { type: 'number', label: 'Level dials minimum separation', hover_tip: 'Only display dials that are this far apart (0-10)', default: 2 }, showLevelGridLines: { type: 'checkbox', label: 'Level dials gridlines', hover_tip: 'Display indicators for the middle of the SRS levels', default: false } } }, srsTotalProgress_group: { type: 'group', label: 'Total Progress Bar', content: { showTotalProgress: { type: 'dropdown', label: 'Display Total Progress Bar', hover_tip: 'Total progress bar display style', default: '1', content: { 0: 'none', 1: 'Long bar (extend under level progress panel)', 2: 'Short bar (center only)', 3: 'Long bar (extend under review forecast)', 4: 'Full bar (full width)', 5: 'Top bar (center only)' } }, reverseTotalProgress: { type: 'checkbox', label: 'Reverse order', hover_tip: 'Display locked->burn (true) or burn->locked (false)', default: false } } }, } }, panelPage: { type: 'page', label: 'Panels', hover_tip: 'Show or Hide Panels', content: { showForecast: { type: 'checkbox', label: 'Review forecast', hover_tip: 'Display or hide this panel', default: true }, showRecentUnlocks: { type: 'checkbox', label: 'New unlocks in the last 30 days', hover_tip: 'Display or hide this panel', default: false }, showCriticalItems: { type: 'checkbox', label: 'Critical condition items', hover_tip: 'Display or hide this panel', default: true }, showRecentBurns: { type: 'checkbox', label: 'Burned items in the last 30 days', hover_tip: 'Display or hide this panel', default: true }, showForumTopics: { type: 'checkbox', label: 'Community banner', hover_tip: 'Display or hide this panel', default: true }, showExtraStudy: getShowExtraStudySettingObject(), showReviewSummary: { type: 'checkbox', label: 'Review Summary Tile', hover_tip: 'Display or hide this panel', default: true } } } } } } } var dialog = new wkof.Settings(config); dialog.open(); } function getShowExtraStudySettingObject() { if (isESMPresent()) { return { type: 'html', label: 'Extra Study', hover_tip: 'Location of Extra Study panel', html: '<div class="right"><label style="text-align: left; margin: 5px; color: grey;">Governed by Extra Study Mover</label></div>', wrapper: 'row' } } else { return { type: 'dropdown', label: 'Extra Study', hover_tip: 'Location of Extra Study panel', default: '1', content: { 0: 'none', 1: 'Above level progress', 6: 'Below level progress', 5: 'Above SRS progress arc', 2: 'Below SRS progress arc', 3: 'Above review forecast', 4: 'Below review forecast' } } } } })();