diff --git a/tdx-enhanced.js b/tdx-enhanced.js index 70b3ddf..ee19568 100644 --- a/tdx-enhanced.js +++ b/tdx-enhanced.js @@ -1,7 +1,7 @@ // ==UserScript== // @name tdx-enhanced // @namespace purdue-it -// @version 2025-05-29-570 +// @version 2025-05-29-600 // @description enhanced tdx coloring & formatting. follows system color scheme. // @author Purdue STEM IT - it@purdue.edu // @match https://service.purdue.edu/TDWorkManagement* @@ -84,6 +84,351 @@ /* BEGIN FUNCTIONS */ + //better solution + //start + + class Color { + constructor(r, g, b) { + this.set(r, g, b); + } + + toString() { + return `rgb(${Math.round( + this.r + )}, ${Math.round(this.g)}, ${Math.round(this.b)})`; + } + + set(r, g, b) { + this.r = this.clamp(r); + this.g = this.clamp(g); + this.b = this.clamp(b); + } + + hueRotate(angle = 0) { + angle = (angle / 180) * Math.PI; + const sin = Math.sin(angle); + const cos = Math.cos(angle); + + this.multiply([ + 0.213 + cos * 0.787 - sin * 0.213, + 0.715 - cos * 0.715 - sin * 0.715, + 0.072 - cos * 0.072 + sin * 0.928, + 0.213 - cos * 0.213 + sin * 0.143, + 0.715 + cos * 0.285 + sin * 0.14, + 0.072 - cos * 0.072 - sin * 0.283, + 0.213 - cos * 0.213 - sin * 0.787, + 0.715 - cos * 0.715 + sin * 0.715, + 0.072 + cos * 0.928 + sin * 0.072 + ]); + } + + grayscale(value = 1) { + this.multiply([ + 0.2126 + 0.7874 * (1 - value), + 0.7152 - 0.7152 * (1 - value), + 0.0722 - 0.0722 * (1 - value), + 0.2126 - 0.2126 * (1 - value), + 0.7152 + 0.2848 * (1 - value), + 0.0722 - 0.0722 * (1 - value), + 0.2126 - 0.2126 * (1 - value), + 0.7152 - 0.7152 * (1 - value), + 0.0722 + 0.9278 * (1 - value) + ]); + } + + sepia(value = 1) { + this.multiply([ + 0.393 + 0.607 * (1 - value), + 0.769 - 0.769 * (1 - value), + 0.189 - 0.189 * (1 - value), + 0.349 - 0.349 * (1 - value), + 0.686 + 0.314 * (1 - value), + 0.168 - 0.168 * (1 - value), + 0.272 - 0.272 * (1 - value), + 0.534 - 0.534 * (1 - value), + 0.131 + 0.869 * (1 - value) + ]); + } + + saturate(value = 1) { + this.multiply([ + 0.213 + 0.787 * value, + 0.715 - 0.715 * value, + 0.072 - 0.072 * value, + 0.213 - 0.213 * value, + 0.715 + 0.285 * value, + 0.072 - 0.072 * value, + 0.213 - 0.213 * value, + 0.715 - 0.715 * value, + 0.072 + 0.928 * value + ]); + } + + multiply(matrix) { + const newR = this.clamp( + this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2] + ); + const newG = this.clamp( + this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5] + ); + const newB = this.clamp( + this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8] + ); + this.r = newR; + this.g = newG; + this.b = newB; + } + + brightness(value = 1) { + this.linear(value); + } + contrast(value = 1) { + this.linear(value, -(0.5 * value) + 0.5); + } + + linear(slope = 1, intercept = 0) { + this.r = this.clamp(this.r * slope + intercept * 255); + this.g = this.clamp(this.g * slope + intercept * 255); + this.b = this.clamp(this.b * slope + intercept * 255); + } + + invert(value = 1) { + this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255); + this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255); + this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255); + } + + hsl() { + // Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA. + const r = this.r / 255; + const g = this.g / 255; + const b = this.b / 255; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + let h, + s, + l = (max + min) / 2; + + if (max === min) { + h = s = 0; + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + + case g: + h = (b - r) / d + 2; + break; + + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return { + h: h * 100, + s: s * 100, + l: l * 100 + }; + } + + clamp(value) { + if (value > 255) { + value = 255; + } else if (value < 0) { + value = 0; + } + return value; + } + } + + class Solver { + constructor(target, baseColor) { + this.target = target; + this.targetHSL = target.hsl(); + this.reusedColor = new Color(0, 0, 0); + } + + solve() { + const result = this.solveNarrow(this.solveWide()); + return { + values: result.values, + loss: result.loss, + filter: this.css(result.values) + }; + } + + solveWide() { + const A = 5; + const c = 15; + const a = [60, 180, 18000, 600, 1.2, 1.2]; + + let best = { loss: Infinity }; + for (let i = 0; best.loss > 25 && i < 3; i++) { + const initial = [50, 20, 3750, 50, 100, 100]; + const result = this.spsa(A, a, c, initial, 1000); + if (result.loss < best.loss) { + best = result; + } + } + return best; + } + + solveNarrow(wide) { + const A = wide.loss; + const c = 2; + const A1 = A + 1; + const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1]; + return this.spsa(A, a, c, wide.values, 500); + } + + spsa(A, a, c, values, iters) { + const alpha = 1; + const gamma = 0.16666666666666666; + + let best = null; + let bestLoss = Infinity; + const deltas = new Array(6); + const highArgs = new Array(6); + const lowArgs = new Array(6); + + for (let k = 0; k < iters; k++) { + const ck = c / Math.pow(k + 1, gamma); + for (let i = 0; i < 6; i++) { + deltas[i] = Math.random() > 0.5 ? 1 : -1; + highArgs[i] = values[i] + ck * deltas[i]; + lowArgs[i] = values[i] - ck * deltas[i]; + } + + const lossDiff = this.loss(highArgs) - this.loss(lowArgs); + + for (let i = 0; i < 6; i++) { + const g = (lossDiff / (2 * ck)) * deltas[i]; + const ak = a[i] / Math.pow(A + k + 1, alpha); + values[i] = fix(values[i] - ak * g, i); + } + + const loss = this.loss(values); + if (loss < bestLoss) { + best = values.slice(0); + bestLoss = loss; + } + } + return { values: best, loss: bestLoss }; + + function fix(value, idx) { + let max = 100; + if (idx === 2 /* saturate */) { + max = 7500; + } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) { + max = 200; + } + + if (idx === 3 /* hue-rotate */) { + if (value > max) { + value %= max; + } else if (value < 0) { + value = max + (value % max); + } + } else if (value < 0) { + value = 0; + } else if (value > max) { + value = max; + } + return value; + } + } + + loss(filters) { + // Argument is array of percentages. + const color = this.reusedColor; + color.set(0, 0, 0); + + color.invert(filters[0] / 100); + color.sepia(filters[1] / 100); + color.saturate(filters[2] / 100); + color.hueRotate(filters[3] * 3.6); + color.brightness(filters[4] / 100); + color.contrast(filters[5] / 100); + + const colorHSL = color.hsl(); + return ( + Math.abs(color.r - this.target.r) + + Math.abs(color.g - this.target.g) + + Math.abs(color.b - this.target.b) + + Math.abs(colorHSL.h - this.targetHSL.h) + + Math.abs(colorHSL.s - this.targetHSL.s) + + Math.abs(colorHSL.l - this.targetHSL.l) + ); + } + + css(filters) { + function fmt(idx, multiplier = 1) { + return Math.round(filters[idx] * multiplier); + } + + return { + invert: `${fmt(0)}%`, + sepia: `${fmt(1)}%`, + saturate: `${fmt(2)}%`, + hue_rotate: `${fmt(3, 3.6)}deg`, + brightness: `${fmt(4)}%`, + contrast: `${fmt(5)}%` + } + + //return `filter: invert(${fmt( + // 0 + //)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`; + } + } + + function hexToRgb(hex) { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, (m, r, g, b) => { + return r + r + g + g + b + b; + }); + + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? [ + parseInt(result[1], 16), + parseInt(result[2], 16), + parseInt(result[3], 16) + ] + : null; + } + + function getFilterForColor(rgbColor) { + let bestResult = null; + let iterationCount = 0; // Initialize a counter for iterations + + for (let i = 0; i < 200; i++) { + iterationCount++; // Increment the counter for each iteration + const color = new Color(rgbColor[0], rgbColor[1], rgbColor[2]); + const solver = new Solver(color); + const result = solver.solve(); + + if (!bestResult || result.loss < bestResult.loss) { + bestResult = result; + if (bestResult.loss <= 0.01) { + // exit early if good result is found + break; + } + } + } + + console.log("Total iterations: ", iterationCount); // Log the total iterations + return bestResult; + } + + //end + function updateHeading(mutation) { let headings = mutation.querySelectorAll(".tdx-control-bar__title") @@ -330,10 +675,21 @@ let path = document.location.pathname; //inject styles into search bar - [...document.querySelectorAll("#globalSearchBar, #input_ticketSearch33, .tdx-leftnav__filter-bar")].forEach(searchBar=>{ + [...document.querySelectorAll("tdx-search-bar")].forEach(searchBar=>{ + //console.log("SEARCH BAR:",searchBar) let shadow = searchBar.shadowRoot if (shadow) { - injectOtherStyles(shadow) + + injectOtherStyles(shadow); + + //check nested shadows + [...shadow.querySelectorAll("tdx-close-x")].forEach(nested=>{ + let nestedShadow = nested.shadowRoot + //console.log("NESTED:",nestedShadow) + if (nestedShadow) { + injectOtherStyles(nestedShadow) + } + }) } }) @@ -755,10 +1111,10 @@ let frame = element.contentWindow.document; //attempt color coded tables on non-desktop pages - let tables = frame.querySelectorAll("table"); + /*let tables = frame.querySelectorAll("table"); [...tables].forEach(table=>{ parseTable(table.parentElement) - }) + })*/ //console.log("Found iFrame:",frame) @@ -771,6 +1127,7 @@ } } else { if (!element.querySelector("#customStyles")) { + setCssFilters(s) element.appendChild(s) } } @@ -862,12 +1219,38 @@ let bg = `var(--${col}-${value}-bg)` style.setProperty(`--${col}-col-primary`, accent) style.setProperty(`--${col}-bg-primary`, `var(--${col}-bg-default)`) + } + break } } } + function setCssFilters(element=null) { + let style = element ? element.style : document.documentElement.style + //quick test for filter generation + let mode = getColorMode().split("Mode")[0] + let priCol = window.getComputedStyle(document.body).getPropertyValue("--dark-col-primary") + let rgb = hexToRgb(priCol) + //let filters = getFilterForColor(rgb) + + const color = new Color(rgb[0], rgb[1], rgb[2]); + const solver = new Solver(color); + const result = solver.solve(); + let filters = result.filter + + console.log("STYLES:",priCol) + console.log("FILTERS:",priCol,rgb,filters) + + style.setProperty("--filter-brightness",filters.brightness) + style.setProperty("--filter-contrast",filters.contrast) + style.setProperty("--filter-hue-rotate",filters.hue_rotate) + style.setProperty("--filter-invert",filters.invert) + style.setProperty("--filter-saturate",filters.saturate) + style.setProperty("--filter-sepia",filters.sepia) +} + /* function toggleColorMode() { let mode = getColorMode() == "darkMode" ? "lightMode" : "darkMode" @@ -1195,6 +1578,7 @@ console.log("Settings update!",data) localStorage.setItem("userSettings",JSON.stringify(data)) settings("apply") + setCssFilters() } } } @@ -1240,7 +1624,6 @@ settings("apply") parseTicket() checkPath() - parseOtherElements() addEventListener("storage", (event) => { //console.log("Storage event:",event) @@ -1282,6 +1665,13 @@ --dark-green-accent: #559170; --light-green-accent: var(--dark-green-accent); + --filter-invert: 0; + --filter-sepia: 0; + --filter-saturate: 0; + --filter-hue-rotate: 0; + --filter-brightness: 0; + --filter-contrast: 0; + --dark-bg-0: color-mix(in srgb,var(--dark-bg-primary),#fff 5%); --dark-bg-1: color-mix(in srgb,var(--dark-bg-primary),#fff 12%); --dark-bg-2: color-mix(in srgb,var(--dark-bg-primary),#fff 20%); @@ -1378,6 +1768,8 @@ --col-modified: var(--dark-col-modified); --shadow-1: var(--light-shadow-1); --shadow-2: var(--light-shadow-2); + --filter-brightness: var(--light-filter-brightness); + --filter-invert: var(--light-filter-invert); } } @@ -1419,6 +1811,8 @@ --col-modified: var(--dark-col-modified); --shadow-1: var(--dark-shadow-1); --shadow-2: var(--dark-shadow-2); + --filter-brightness: var(--dark-filter-brightness); + --filter-invert: var(--dark-filter-invert); } } @@ -1459,6 +1853,8 @@ --col-modified: var(--dark-col-modified); --shadow-1: var(--dark-shadow-1); --shadow-2: var(--dark-shadow-2); + --filter-brightness: var(--dark-filter-brightness); + --filter-invert: var(--dark-filter-invert); } :root.lightMode { @@ -1498,6 +1894,8 @@ --col-modified: var(--dark-col-modified); --shadow-1: var(--light-shadow-1); --shadow-2: var(--light-shadow-2); + --filter-brightness: var(--light-filter-brightness); + --filter-invert: var(--light-filter-invert); } /* Body */ @@ -1526,7 +1924,17 @@ body { } .tdx-overlay { - background-color: var(--bg-5) !important; + background-color: color-mix(in srgb,var(--col-400),#00000000 25%) !important; +} + +.tdx-overlay__item { + background-color: var(--bg-0) !important; + color: var(--col-400) !important; +} + +.tdx-overlay__item:hover, .tdx-overlay__item:focus-visible { + background-color: var(--col-400) !important; + color: var(--col-100) !important; } .tdx-navigation-tab-bar { @@ -1724,6 +2132,14 @@ h1, h2, h3, h4, h5 { background-color: var(--col-200) !important; } +.tdx-navigation-tabs__home-tab:hover { + background-color: var(--col-200) !important; +} + +.tdx-navigation-tabs__home-tab--active:hover { + background-color: var(--bg-0) !important; +} + .tdx-dropdown--headline { color: var(--txt-1) !important; } @@ -1784,6 +2200,10 @@ h1, h2, h3, h4, h5 { color: var(--txt-1) !important; } +.tdx-dropdown--dark:hover, .tdx-dropdown--dark:focus-visible { + background-color: var(--col-300) !important; +} + .tdx-searchbar__textbox:not(.tdx-searchbar__textbox--dark) { background-color: var(--bg-0) !important; color: var(--txt-1) !important; @@ -1793,8 +2213,17 @@ h1, h2, h3, h4, h5 { background-color: var(--col-200) !important; } +.tdx-searchbar__textbox:focus, .tdx-searchbar__textbox:active, .tdx-searchbar__textbox:focus-within { + border-color: var(--col-400) !important; +} + .tdx-searchbar__textbox::placeholder { - color: var(--txt-4) !important; + color: var(--col-400) !important; +} + +.tdx-searchbar__textbox--dark:hover:not(:focus)::placeholder, .tdx-searchbar:hover .tdx-searchbar__textbox--dark:not(:focus)::placeholder, +.tdx-searchbar__textbox:hover:not(:focus)::placeholder, .tdx-searchbar:hover .tdx-searchbar__textbox:not(:focus)::placeholder { + color: var(--col-400) !important; } input[type=checkbox], input[type=radio] { @@ -2615,7 +3044,7 @@ thead, tbody, tfoot, tr, td, th { td.k-table-td { background-color: var(--bg-1); -} +} tr.k-alt td.k-table-td { background-color: var(--bg-0) @@ -2645,21 +3074,26 @@ tr.k-alt td.k-table-td { background-color: var(--bg-0) !important; } -.k-pager-md .k-pager-numbers-wrap .k-button.k-pager-nav { - background-color: var(--bg-2) !important; -} - -.k-pager-md .k-pager-numbers-wrap .k-button.k-pager-nav { +button.k-pager-nav.k-button.k-button-flat.k-button-flat-base.k-icon-button.k-button-md { background-color: var(--bg-0) !important; - color: var(--bg-3) !important; + color: var(--col-400) !important; } .k-pager-md .k-pager-numbers-wrap .k-button.k-pager-nav.k-disabled, button.k-pager-nav.k-button.k-button-flat.k-button-flat-base.k-icon-button.k-pager-last.k-button-md { background-color: var(--bg-0) !important; + color: var(--col-400) !important; +} + +button.k-pager-nav.k-button.k-button-flat.k-button-flat-base.k-icon-button.k-pager-last.k-button-md.k-disabled, +button.k-pager-nav.k-button.k-button-flat.k-button-flat-base.k-icon-button.k-button-md.k-disabled { color: var(--bg-2) !important; } +.k-pager-md .k-pager-numbers-wrap .k-button.k-pager-nav:hover { + color: var(--col-600) !important; +} + #lookupIframeWrapper { background-color: var(--bg-0); } @@ -2686,12 +3120,25 @@ button.k-pager-nav.k-button.k-button-flat.k-button-flat-base.k-icon-button.k-pag } button.k-button.k-button-md.k-button-flat.k-button-flat-primary.k-selected { - background-color: var(--col-300) !important; + background-color: var(--col-100) !important; } button.k-selected { background-color: var(--col-1) !important; - color: var(--txt-1) !important; + color: var(--col-400) !important; +} + +.k-pager-md .k-pager-numbers-wrap .k-button { + color: var(--col-400) !important; +} + +button.k-button.k-button-md.k-button-flat.k-button-flat-primary:hover { + color: var(--col-500); + background-color: var(--col-100)!important; +} + +.k-pager-md .k-pager-numbers-wrap .k-button.k-pager-nav { + background-color: var(--bg-0) !important; } .tdx-pagination-bar { @@ -3111,10 +3558,6 @@ select.cke_dialog_ui_input_select { color: var(--col-500); } -.tdx-icon--home-on { - filter: grayscale(1) brightness(3); -} - .tdx-lookup-button-group a, .tdx-lookup-button-group button, .input-btn-group a, .input-btn-group button, div.select2-container.select2-allowclear .select2-choice abbr { color: var(--col-400); @@ -3124,15 +3567,22 @@ div.select2-container.select2-allowclear .select2-choice abbr { color: var(--col-500); } -.tdx-icon--filter-purple { - filter: brightness(5) !important; - color: var(--txt-1); +.tdx-icon__encircled--active { + background-color: var(--col-200) !important; +} + +.tdx-icon__encircled--active:hover { + background-color: var(--col-300) !important; } -.tdx-icon__encircled { +.tdx-icon__encircled:not(.tdx-icon__encircled--active) { background-color: var(--bg-0) !important; } +.tdx-icon__encircled:not(.tdx-icon__encircled--active):hover { + background-color: var(--col-200) !important; +} + .tdx-user-icon, .tdx-multiuser-icon { background-color: var(--col-0) !important; color: var(--txt-5) !important; @@ -3154,9 +3604,50 @@ div.select2-container.select2-allowclear .select2-choice abbr { color: var(--col-500) !important; } +.k-table-th[data-role=columnsorter] .k-link:not(:has(>.k-sort-icon)):after { + color: var(--col-400) !important; +} + +.k-table-th[data-role=columnsorter] .k-link:not(:has(>.k-sort-icon)):hover:after { + color: var(--col-500) !important; +} + +/* Icons + Filters */ +.tdx-dropdown__menu-icon, +.tdx-dropdown--headline:after, +.tdx-dropdown:after, +.tdx-icon--search, +.tdx-icon { + filter: brightness(0) invert(var(--filter-invert)) sepia(var(--filter-sepia)) saturate(var(--filter-saturate)) hue-rotate(var(--filter-hue-rotate)) brightness(var(--filter-brightness)) contrast(var(--filter-contrast)); +} + +/* Same but with !important */ +.tdx-icon__encircled--active .tdx-icon, +.tdx-icon--filter-purple, +.tdx-navigation-tabs__tab--active .tdx-close-x, +.tdx-icon--new-browser, +.tdx-close-x, button.tdx-close-x, +.tdx-action-menu__item--primary:after, +.tdx-action-menu-dropdown:after, +.tdx-icon.tdx-icon--hover-darken, .tdx-icon--hover-darken>.tdx-icon +{ + filter: brightness(0) invert(var(--filter-invert)) sepia(var(--filter-sepia)) saturate(var(--filter-saturate)) hue-rotate(var(--filter-hue-rotate)) brightness(var(--filter-brightness)) contrast(var(--filter-contrast)) !important; +} + +/* Hover + !important */ +.tdx-icon__encircled:hover .tdx-icon, +.tdx-close-x:hover, button.tdx-close-x:hover, +.tdx-icon.tdx-icon--hover-darken:hover, .tdx-icon.tdx-icon--hover-darken:focus, .tdx-icon--hover-darken>.tdx-icon:hover, .tdx-icon--hover-darken>.tdx-icon:focus, +.tdx-leftnav__item-expander:hover .tdx-icon, .tdx-leftnav__item-expander:focus .tdx-icon +{ + filter: brightness(0) invert(var(--filter-invert)) sepia(var(--filter-sepia)) saturate(var(--filter-saturate)) hue-rotate(var(--filter-hue-rotate)) brightness(var(--filter-brightness)) contrast(var(--filter-contrast)) brightness(125%) !important; +} + ` //const customStyles = GM_getResourceText("IMPORTED_CSS"); GM_addStyle(customStyles); + setCssFilters() + parseOtherElements() })(); \ No newline at end of file