background work + bugfixes

daily backgrounds + some smaller fixes and some UI changes
This commit is contained in:
Guy Sandler 2026-08-12 19:06:52 -07:00
parent 25ff281bf9
commit 2c65b5ab17
5 changed files with 505 additions and 118 deletions

View File

@ -254,7 +254,16 @@
<span class="option-name" data-i18n="better_sidebar">Better Sidebar</span> <span class="option-name" data-i18n="better_sidebar">Better Sidebar</span>
</div> </div>
<div class="sub-options"> <div class="sub-options">
<div style="margin-top: 5px"> <div class="option" id="remlogo" style="margin-top:2px">
<input type="radio" id="off" name="remlogo">
<input type="radio" id="on" name="remlogo">
<div class="slider">
<div class="sliderknob"></div>
<div class="sliderbg"></div>
</div>
<span class="option-name" data-i18n="remlogo">Remove sidebar logo</span>
</div>
<div style="margin-top: 5px" id="sidebarScaleSub">
<span class="sub-text">Sidebar scale: </span><span id="sidebarScaleValue"></span><span class="sub-text">%</span> <span class="sub-text">Sidebar scale: </span><span id="sidebarScaleValue"></span><span class="sub-text">%</span>
<input type="range" min="70" max="150" id="sidebarScaleSlider"> <input type="range" min="70" max="150" id="sidebarScaleSlider">
</div> </div>
@ -303,14 +312,6 @@
</div> </div>
<span class="option-name" data-i18n="full_width">Full Width Fix</span> <span class="option-name" data-i18n="full_width">Full Width Fix</span>
</div> </div>
<div class="option" id="remlogo">
<input type="radio" id="off" name="remlogo">
<input type="radio" id="on" name="remlogo">
<div class="slider">
<div class="sliderknob"></div>
<div class="sliderbg"></div>
</div><span class="option-name" data-i18n="remlogo">Remove sidebar logo</span>
</div>
<div class="option" id="hide_feedback"> <div class="option" id="hide_feedback">
<input type="radio" id="off" name="hide_feedback"> <input type="radio" id="off" name="hide_feedback">
<input type="radio" id="on" name="hide_feedback"> <input type="radio" id="on" name="hide_feedback">
@ -941,16 +942,30 @@
<div class="custom-font option-container"> <div class="custom-font option-container">
<h3 class="header-small">Custom Background</h3> <h3 class="header-small">Custom Background</h3>
<p>Pick a preset or use your own image url.</p> <p>Pick a preset or use your own image url.</p>
<div class="background-preset-grid" id="background-presets"></div> <div id="customBackgroundManualControls">
<div class="background-scale-row"> <div class="background-preset-grid" id="background-presets"></div>
<div style="display:flex;justify-content:space-between;gap:10px;align-items:center;margin-top:10px;"> <div class="background-scale-row">
<span class="sub-text" style="font-weight:600; color:#e2e2e2;">Image scale</span> <div style="display:flex;justify-content:space-between;gap:10px;align-items:center;margin-top:10px;">
<span class="sub-text" id="customBackgroundScaleValue">100%</span> <span class="sub-text" style="font-weight:600; color:#e2e2e2;">Image scale</span>
<span class="sub-text" id="customBackgroundScaleValue">100%</span>
</div>
<input type="range" min="50" max="200" step="1" id="customBackgroundScale">
</div> </div>
<input type="range" min="50" max="200" step="1" id="customBackgroundScale"> <input style="width:85%;" class="card-input" id="customBackgroundLink" placeholder="https://...">
<button id="clearCustomBackground" class="big-button" style="margin-top: 8px;">Clear Background</button>
</div>
<div class="sub-option" style="margin-top:10px;">
<input type="checkbox" id="customBackgroundDaily" name="customBackgroundDaily">
<label for="customBackgroundDaily" class="sub-text">Daily Random Image</label>
</div>
<div class="sub-option" style="margin-top:6px;">
<input type="checkbox" id="customBackgroundNasaDaily" name="customBackgroundNasaDaily">
<label for="customBackgroundNasaDaily" class="sub-text">NASA Astronomy Picture of the Day</label>
</div>
<div class="sub-option" style="margin-top:8px;">
<input type="checkbox" id="fitImageToScreen" name="fitImageToScreen">
<label for="fitImageToScreen" class="sub-text">Fit image to screen</label>
</div> </div>
<input style="width:85%;" class="card-input" id="customBackgroundLink" placeholder="https://...">
<button id="clearCustomBackground" class="big-button" style="margin-top: 8px;">Clear Background</button>
</div> </div>
</div> </div>

View File

@ -104,6 +104,10 @@ chrome.runtime.onInstalled.addListener(function () {
"customCardStyles": false, "customCardStyles": false,
"customBackgroundLink": "", "customBackgroundLink": "",
"customBackgroundScale": 100, "customBackgroundScale": 100,
"customBackgroundDaily": false,
"customBackgroundNasaDaily": false,
"nasaInfoOverlay": false,
"fitImageToScreen": false,
} }
}; };
@ -122,6 +126,11 @@ chrome.runtime.onInstalled.addListener(function () {
newLocalOptions[option] = default_options["local"][option]; newLocalOptions[option] = default_options["local"][option];
}) })
// migrate old setting name
if (sync["nasaFitToScreen"] !== undefined && sync["fitImageToScreen"] === undefined) {
newSyncOptions["fitImageToScreen"] = sync["nasaFitToScreen"];
}
if (Object.keys(newLocalOptions).length > 0) { if (Object.keys(newLocalOptions).length > 0) {
chrome.storage.local.set(newLocalOptions); chrome.storage.local.set(newLocalOptions);
} }
@ -139,4 +148,90 @@ chrome.runtime.onInstalled.addListener(function () {
}); });
}); });
// The NASA APOD API with the demo key is limited to 30 requests/hour and 50/day.
// All calls are serialized through this worker and gated against those limits.
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message?.type === "getNasaBackground") {
getNasaBackground().then(sendResponse);
return true;
}
});
let nasaRequestQueue = Promise.resolve();
function queuedNasaTask(task) {
const run = nasaRequestQueue.then(task, task);
nasaRequestQueue = run.catch(() => {});
return run;
}
async function getNasaBackground() {
return queuedNasaTask(async () => {
const date = new Date();
for (let i = 0; i < 7; i++) {
const dateStr = date.toISOString().slice(0, 10);
const cacheKey = `nasa_apod_${dateStr}`;
const metadataKey = `nasa_apod_meta_${dateStr}`;
const cached = await chrome.storage.local.get([cacheKey, metadataKey]);
if (cached[cacheKey]) return cached[cacheKey];
// Don't re-probe dates the API already told us don't exist
const missingKey = `nasa_apod_missing_${dateStr}`;
const missing = await chrome.storage.local.get(missingKey);
if (missing[missingKey]) {
date.setDate(date.getDate() - 1);
continue;
}
const data = await callNasaApi(dateStr);
if (data === "ratelimited" || data === null) return null;
if (data === "missing") {
await chrome.storage.local.set({ [missingKey]: true });
date.setDate(date.getDate() - 1);
continue;
}
const url = data.thumbnail_url || data.hdurl || data.url;
if (!url) return null;
const result = { url, scale: 100, date: dateStr };
await chrome.storage.local.set({ [cacheKey]: result });
await chrome.storage.local.set({ [metadataKey]: { title: data.title || "", date: data.date || dateStr, copyright: data.copyright || "", explanation: data.explanation || "" } });
return result;
}
return null;
});
}
async function callNasaApi(dateStr) {
const now = Date.now();
const { nasa_api_calls = [] } = await chrome.storage.local.get("nasa_api_calls");
const lastHour = nasa_api_calls.filter(t => now - t < 3600 * 1000);
const lastDay = nasa_api_calls.filter(t => now - t < 24 * 3600 * 1000);
if (lastHour.length >= 30 || lastDay.length >= 50) {
console.warn("[CanvasRefined] NASA API rate limit reached, skipping request");
return "ratelimited";
}
let response;
try {
response = await fetch(`https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&thumbs=true&date=${dateStr}`);
} catch (error) {
console.error("[CanvasRefined] Failed to fetch NASA APOD:", error);
return null;
}
// The request was made, so it counts against the quota
await chrome.storage.local.set({ nasa_api_calls: [...lastDay, now] });
if (response.status === 429) return "ratelimited";
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
if (errorData.code === 404 && (errorData.msg || "").toLowerCase().includes("no data available")) {
return "missing";
}
return null;
}
return await response.json().catch(() => null);
}
// chrome.runtime.setUninstallURL("https://diditupe.dev/canvasrefined/goodbye"); // chrome.runtime.setUninstallURL("https://diditupe.dev/canvasrefined/goodbye");

View File

@ -30,6 +30,10 @@ function isConversationsPage() {
return /^\/conversations(?:\/|$)/.test(current_page); return /^\/conversations(?:\/|$)/.test(current_page);
} }
function isAccountsPage() {
return /^\/accounts(?:\/|$)/.test(current_page);
}
function isProfilePage() { function isProfilePage() {
return /^\/profile(?:\/|$)/.test(current_page); return /^\/profile(?:\/|$)/.test(current_page);
} }
@ -58,72 +62,6 @@ function addSubmissionPageButton() {
}, true); }, true);
} }
let sequenceFooterObserver = null;
function isAssignmentPage() {
return /^\/courses\/\d+\/assignments(?:\/\d+)?(?:\/|$)/.test(current_page);
}
function removeSequenceFooter() {
if (!isAssignmentPage()) return false;
const sequenceFooter = document.getElementById("sequence_footer");
if (!sequenceFooter) return false;
sequenceFooter.remove();
return true;
}
function watchSequenceFooter() {
if (!isAssignmentPage()) return;
if (removeSequenceFooter()) return;
if (sequenceFooterObserver) return;
sequenceFooterObserver = new MutationObserver(() => {
if (removeSequenceFooter() && sequenceFooterObserver) {
sequenceFooterObserver.disconnect();
sequenceFooterObserver = null;
}
});
sequenceFooterObserver.observe(document.documentElement, { childList: true, subtree: true });
setTimeout(() => {
if (sequenceFooterObserver) {
sequenceFooterObserver.disconnect();
sequenceFooterObserver = null;
}
}, 10000);
}
function ensureSubmissionPageButton() {
const assignmentLink = getSubmissionAssignmentLink();
if (!assignmentLink) return false;
const content = document.getElementById("content");
if (!content) return false;
if (content.querySelector("#canvasrefined-assignment-return")) return true;
addSubmissionPageButton();
return Boolean(content.querySelector("#canvasrefined-assignment-return"));
}
function watchSubmissionPageButton() {
if (!getSubmissionAssignmentLink()) return;
if (ensureSubmissionPageButton()) return;
if (submissionPageButtonObserver) return;
submissionPageButtonObserver = new MutationObserver(() => {
if (ensureSubmissionPageButton() && submissionPageButtonObserver) {
submissionPageButtonObserver.disconnect();
submissionPageButtonObserver = null;
}
});
submissionPageButtonObserver.observe(document.documentElement, { childList: true, subtree: true });
setTimeout(() => {
if (submissionPageButtonObserver) {
submissionPageButtonObserver.disconnect();
submissionPageButtonObserver = null;
}
}, 10000);
}
function addProfileLogoutPageButton() { function addProfileLogoutPageButton() {
if (!isProfilePage()) return; if (!isProfilePage()) return;
const content = document.getElementById("content"); const content = document.getElementById("content");
@ -168,6 +106,194 @@ function watchProfileLogoutPageButton() {
}, 10000); }, 10000);
} }
function ensureSubmissionPageButton() {
const assignmentLink = getSubmissionAssignmentLink();
if (!assignmentLink) return false;
const content = document.getElementById("content");
if (!content) return false;
if (content.querySelector("#canvasrefined-assignment-return")) return true;
addSubmissionPageButton();
return Boolean(content.querySelector("#canvasrefined-assignment-return"));
}
function watchSequenceFooter() {
if (!isAssignmentPage()) return;
if (removeSequenceFooter()) return;
if (sequenceFooterObserver) return;
sequenceFooterObserver = new MutationObserver(() => {
if (removeSequenceFooter() && sequenceFooterObserver) {
sequenceFooterObserver.disconnect();
sequenceFooterObserver = null;
}
});
sequenceFooterObserver.observe(document.documentElement, { childList: true, subtree: true });
setTimeout(() => {
if (sequenceFooterObserver) {
sequenceFooterObserver.disconnect();
sequenceFooterObserver = null;
}
}, 10000);
}
function watchSubmissionPageButton() {
if (!getSubmissionAssignmentLink()) return;
if (ensureSubmissionPageButton()) return;
if (submissionPageButtonObserver) return;
submissionPageButtonObserver = new MutationObserver(() => {
if (ensureSubmissionPageButton() && submissionPageButtonObserver) {
submissionPageButtonObserver.disconnect();
submissionPageButtonObserver = null;
}
});
submissionPageButtonObserver.observe(document.documentElement, { childList: true, subtree: true });
setTimeout(() => {
if (submissionPageButtonObserver) {
submissionPageButtonObserver.disconnect();
submissionPageButtonObserver = null;
}
}, 10000);
}
async function getActiveCustomBackground() {
const syncOpts = await chrome.storage.sync.get([
"customBackgroundDaily",
"customBackgroundNasaDaily",
"customBackgroundLink",
"customBackgroundScale",
]);
console.log("[CanvasRefined] getActiveCustomBackground:", syncOpts);
if (syncOpts.customBackgroundNasaDaily === true) {
console.log("[CanvasRefined] Using NASA APOD");
return await getNasaDailyBackground();
}
if (syncOpts.customBackgroundDaily === true) {
console.log("[CanvasRefined] Using Wikimedia Featured");
const dailyPreset = await getDailyBackgroundPreset();
if (dailyPreset) {
console.log("[CanvasRefined] Wikimedia URL:", dailyPreset.url);
return {
url: dailyPreset.url,
scale: dailyPreset.scale,
};
}
console.log("[CanvasRefined] Wikimedia returned null");
}
if (syncOpts.customBackgroundLink && syncOpts.customBackgroundLink !== "") {
console.log("[CanvasRefined] Using custom link");
return {
url: syncOpts.customBackgroundLink,
scale: syncOpts.customBackgroundScale || 100,
};
}
console.log("[CanvasRefined] No custom background");
return null;
}
async function getDailyBackgroundPreset() {
const today = new Date();
const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
const cacheKey = `picsum_daily_${dateStr}`;
const cached = await chrome.storage.local.get(cacheKey);
if (cached[cacheKey]) return cached[cacheKey];
const url = `https://picsum.photos/seed/${dateStr}/1920/1080`;
const result = { url, scale: 100 };
await chrome.storage.local.set({ [cacheKey]: result });
return result;
}
async function getNasaDailyBackground() {
try {
return await chrome.runtime.sendMessage({ type: "getNasaBackground" });
} catch (error) {
console.error("[CanvasRefined] Failed to fetch NASA APOD:", error);
return null;
}
}
let nasaInfoOverlayEl = null;
function isDashboardPage() {
return !!document.querySelector("#DashboardCard_Container");
}
function createNasaInfoOverlay() {
if (options.customBackgroundNasaDaily !== true) return;
if (nasaInfoOverlayEl || !isDashboardPage()) return;
const contentMain = document.querySelector("#content.ic-Layout-contentMain, .ic-Layout-contentMain");
if (!contentMain) return;
if (getComputedStyle(contentMain).position === "static") {
contentMain.style.position = "relative";
}
nasaInfoOverlayEl = document.createElement("div");
nasaInfoOverlayEl.id = "canvasrefined-nasa-info-overlay";
nasaInfoOverlayEl.style.cssText = "position:absolute;right:24px;bottom:24px;z-index:9999;";
nasaInfoOverlayEl.innerHTML = `
<div id="nasa-info-icon" style="display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;background:rgba(30,30,30,0.85);border:1px solid rgba(255,255,255,0.15);cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.4);">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#e2e2e2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</div>
<div id="nasa-info-panel" style="display:none;position:absolute;bottom:calc(100% + 10px);right:0;background:#1e1e1e;border:1px solid #3c3c3c;border-radius:8px;padding:14px 18px;width:340px;max-width:calc(100vw - 40px);box-shadow:0 4px 16px rgba(0,0,0,0.4);">
<div id="nasa-info-title" style="font-weight:600;font-size:14px;margin-bottom:4px;color:#f5f5f5;"></div>
<div id="nasa-info-date" style="font-size:12px;color:#ababab;margin-bottom:4px;"></div>
<div id="nasa-info-credit" style="font-size:12px;color:#dfa581;margin-bottom:8px;"></div>
<div id="nasa-info-explanation" style="font-size:12px;color:#e2e2e2;line-height:1.5;max-height:200px;overflow-y:auto;white-space:pre-wrap;"></div>
</div>
`;
const icon = nasaInfoOverlayEl.querySelector("#nasa-info-icon");
const panel = nasaInfoOverlayEl.querySelector("#nasa-info-panel");
const showPanel = async () => {
const dateStr = new Date().toISOString().slice(0, 10);
const cacheKey = `nasa_apod_${dateStr}`;
const cached = await chrome.storage.local.get(cacheKey);
const metaDate = cached[cacheKey]?.date || dateStr;
const metadataKey = `nasa_apod_meta_${metaDate}`;
const metadata = await chrome.storage.local.get(metadataKey);
const meta = metadata[metadataKey];
if (meta) {
document.getElementById("nasa-info-title").textContent = meta.title || "";
document.getElementById("nasa-info-date").textContent = `Date: ${meta.date}`;
document.getElementById("nasa-info-credit").textContent = meta.copyright ? `Credit: ${meta.copyright}` : "";
document.getElementById("nasa-info-explanation").textContent = meta.explanation || "No description available.";
panel.style.display = "block";
}
};
const hidePanel = () => {
panel.style.display = "none";
};
icon.addEventListener("mouseenter", showPanel);
icon.addEventListener("mouseleave", hidePanel);
panel.addEventListener("mouseenter", showPanel);
panel.addEventListener("mouseleave", hidePanel);
contentMain.appendChild(nasaInfoOverlayEl);
}
function removeNasaInfoOverlay() {
if (nasaInfoOverlayEl) {
nasaInfoOverlayEl.remove();
nasaInfoOverlayEl = null;
}
}
function getSidebarStateMode(mode = getSidebarLayoutMode()) { function getSidebarStateMode(mode = getSidebarLayoutMode()) {
return mode === "course" ? "course" : "dashboard"; return mode === "course" ? "course" : "dashboard";
} }
@ -348,6 +474,7 @@ async function reminderWatch() {
} }
function updateReminders() { function updateReminders() {
if (!assignments || typeof assignments.then !== "function") return;
const fiveDays = 1000 * 60 * 60 * 24 * 5; const fiveDays = 1000 * 60 * 60 * 24 * 5;
const now = (new Date()).getTime(); const now = (new Date()).getTime();
const list = []; const list = [];
@ -438,6 +565,17 @@ function isDomainCanvasPage() {
} }
function startExtension() { function startExtension() {
// Remove footer robustly - run first so a crash below can't block it
const removeFooter = () => {
const footer = document.querySelector('footer#footer.ic-app-footer, footer#footer');
if (footer) footer.remove();
};
removeFooter();
const footerObserver = new MutationObserver(() => {
removeFooter();
});
footerObserver.observe(document.documentElement, { childList: true, subtree: true });
toggleDarkMode(); toggleDarkMode();
chrome.storage.sync.get(["better_sidebar", "sidebar_scale"], result => { chrome.storage.sync.get(["better_sidebar", "sidebar_scale"], result => {
@ -463,7 +601,6 @@ function startExtension() {
//getClassAverages(); //getClassAverages();
setTimeout(() => document.getElementById("footer")?.remove(), 800);
setTimeout(() => runDarkModeFixer(false), 800); setTimeout(() => runDarkModeFixer(false), 800);
setTimeout(() => runDarkModeFixer(false), 4500); setTimeout(() => runDarkModeFixer(false), 4500);
}); });
@ -567,6 +704,21 @@ function applyOptionsChanges(changes) {
applyAestheticChanges(); applyAestheticChanges();
break; break;
case "customBackgroundScale": case "customBackgroundScale":
applyCustomBackground();
break;
case "customBackgroundDaily":
applyCustomBackground();
removeNasaInfoOverlay();
break;
case "customBackgroundNasaDaily":
applyCustomBackground();
if (options.customBackgroundNasaDaily === true) {
createNasaInfoOverlay();
} else {
removeNasaInfoOverlay();
}
break;
case "fitImageToScreen":
applyCustomBackground(); applyCustomBackground();
break; break;
// case "show_updates": // case "show_updates":
@ -674,22 +826,31 @@ function ensureBetterSidebar() {
setupBetterSidebar(getSidebarLayoutMode()); setupBetterSidebar(getSidebarLayoutMode());
} }
function applyCustomBackground() { async function applyCustomBackground() {
// let style = document.querySelector("#DashboardCard_Container") // let style = document.querySelector("#DashboardCard_Container")
let style = document.querySelector("#canvasrefined-background") || document.createElement('style'); let style = document.querySelector("#canvasrefined-background") || document.createElement('style');
style.id = "canvasrefined-background"; style.id = "canvasrefined-background";
if (options.customBackgroundLink && options.customBackgroundLink !== "") { const activeBackground = await getActiveCustomBackground();
const backgroundScale = Number(options.customBackgroundScale) || 100; console.log("[CanvasRefined] activeBackground:", activeBackground);
style.textContent = ` if (!activeBackground) {
if (style.isConnected) style.remove();
return;
}
const backgroundScale = Number(activeBackground.scale) || 100;
const backgroundUrl = JSON.stringify(activeBackground.url);
const fitToScreen = options.fitImageToScreen === true;
console.log("[CanvasRefined] Applying background:", activeBackground.url, "fitToScreen:", fitToScreen);
style.textContent = `
#wrapper { #wrapper {
background-image: url('${options.customBackgroundLink}') !important; background-image: url(${backgroundUrl}) !important;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-position: center center !important; background-position: center center !important;
background-attachment: fixed !important; background-attachment: fixed !important;
} }
@media (orientation: landscape) { @media (orientation: landscape) {
#wrapper { background-size: ${backgroundScale}% auto !important; } #wrapper { background-size: ${fitToScreen ? 'cover' : backgroundScale + '% auto'} !important; }
} }
@media (orientation: portrait) { @media (orientation: portrait) {
#wrapper { background-size: cover !important; } #wrapper { background-size: cover !important; }
@ -698,6 +859,20 @@ function applyCustomBackground() {
background: none !important; background: none !important;
/* backdrop-filter: blur(10px) !important; */ /* backdrop-filter: blur(10px) !important; */
border-radius: 5px; border-radius: 5px;
padding-left: 20px !important;
}
#dashboard_header_container {
margin-left: -35px !important;
margin-right: -35px !important;
box-sizing: border-box !important;
background-color: color-mix(in srgb, var(--bcbackground-0), transparent 20%) !important;
border: 1px solid color-mix(in srgb, var(--bcborders) 60%, transparent) !important;
border-radius: 10px !important;
position: sticky !important;
top: 0 !important;
z-index: 1000 !important;
backdrop-filter: blur(8px) saturate(120%) !important;
-webkit-backdrop-filter: blur(8px) saturate(120%) !important;
} }
#right-side-wrapper { #right-side-wrapper {
// backdrop-filter: blur(10px) !important; // backdrop-filter: blur(10px) !important;
@ -837,8 +1012,7 @@ function applyCustomBackground() {
tr.student_assignment.assignment_graded.editable > * { tr.student_assignment.assignment_graded.editable > * {
border:none!important border:none!important
}`; }`;
// TODO: liquid glass? // TODO: liquid glass?
}
document.documentElement.appendChild(style); document.documentElement.appendChild(style);
} }
@ -903,6 +1077,7 @@ function checkDashboardReady() {
loadDashboardNotes(); loadDashboardNotes();
setupGPACalc(); setupGPACalc();
showUpdateMsg(); showUpdateMsg();
createNasaInfoOverlay();
} }
const rightSide = document.querySelector("#right-side"); const rightSide = document.querySelector("#right-side");
@ -942,7 +1117,7 @@ function recieveMessage(request, sender, sendResponse) {
case ("getcolors"): sendResponse(getCardColors()); break; case ("getcolors"): sendResponse(getCardColors()); break;
case ("inspect"): sendResponse(inspectDarkMode(true)); break; case ("inspect"): sendResponse(inspectDarkMode(true)); break;
case ("fixdm"): sendResponse(runDarkModeFixer(true)); break; case ("fixdm"): sendResponse(runDarkModeFixer(true)); break;
case ("updateBackground"): clearCustomBackground(); sendResponse(true); break; case ("updateBackground"): applyCustomBackground(); sendResponse(true); break;
default: sendResponse(true); default: sendResponse(true);
} }
} }
@ -2442,8 +2617,13 @@ async function setupBetterSidebar(mode = getSidebarLayoutMode()) {
const contentMain = document.querySelector(".ic-Layout-contentMain"); const contentMain = document.querySelector(".ic-Layout-contentMain");
contentMain?.style.setProperty("flex", "1 1 auto"); contentMain?.style.setProperty("flex", "1 1 auto");
contentMain?.style.setProperty("min-width", "0"); contentMain?.style.setProperty("min-width", "0");
const notRightSide = document.getElementById("not_right_side");
if (notRightSide && isAccountsPage()) {
notRightSide.style.setProperty("width", "100%");
notRightSide.style.setProperty("max-width", "100%");
notRightSide.style.setProperty("min-width", "0");
}
if (layoutMode === "course" && leftSide) { if (layoutMode === "course" && leftSide) {
const notRightSide = document.getElementById("not_right_side");
const rightSideWrapper = document.getElementById("right-side-wrapper"); const rightSideWrapper = document.getElementById("right-side-wrapper");
const sectionTabs = document.getElementById("section-tabs"); const sectionTabs = document.getElementById("section-tabs");
leftSide.style.setProperty("padding-top", "0", "important"); leftSide.style.setProperty("padding-top", "0", "important");
@ -3790,7 +3970,7 @@ function setupGPACalc() {
grades?.then(result => { grades?.then(result => {
const sortableContainer = document.querySelector(".ic-DashboardCard__box__container"); const sortableContainer = document.querySelector(".ic-DashboardCard__box__container");
const dashboardContainer = document.querySelector("#DashboardCard_Container"); const dashboardContainer = sortableContainer || document.querySelector("#DashboardCard_Container");
if (!dashboardContainer) return; if (!dashboardContainer) return;
let container2 = document.querySelector(".canvasrefined-gpa-card"); let container2 = document.querySelector(".canvasrefined-gpa-card");
@ -3850,17 +4030,15 @@ function setupGPACalc() {
if (cumulative) cumulative.style.display = options.gpa_calc_cumulative ? "block" : "none"; if (cumulative) cumulative.style.display = options.gpa_calc_cumulative ? "block" : "none";
const shouldPrepend = options.gpa_calc_prepend === true; const shouldPrepend = options.gpa_calc_prepend === true;
const firstCard = shouldPrepend ? container : container2; if (shouldPrepend) {
const secondCard = shouldPrepend ? container2 : container; if (dashboardContainer.children[0] !== container || dashboardContainer.children[1] !== container2) {
dashboardContainer.insertBefore(container, dashboardContainer.firstChild);
if (firstCard.parentElement !== dashboardContainer) { dashboardContainer.insertBefore(container2, container.nextSibling);
dashboardContainer.prepend(firstCard); }
} } else {
if (secondCard.parentElement !== dashboardContainer) { if (dashboardContainer.lastElementChild !== container || container2.nextElementSibling !== container) {
if (shouldPrepend) { dashboardContainer.appendChild(container2);
dashboardContainer.prepend(secondCard); dashboardContainer.appendChild(container);
} else {
dashboardContainer.appendChild(secondCard);
} }
} }
} }
@ -4013,23 +4191,39 @@ function changeFullWidth() {
function changeGradientCards() { function changeGradientCards() {
if (options.gradient_cards === true) { if (options.gradient_cards === true) {
let cardheads = document.querySelectorAll('.ic-DashboardCard__header_hero'); let cardheads = document.querySelectorAll('.ic-DashboardCard__header_hero');
let cardcss = document.querySelector("#gradientcss") || document.createElement('style');
cardcss.id = "gradientcss";
cardcss.textContent = "";
document.documentElement.appendChild(cardcss);
// Only create + append the style once; never re-append an already-
// attached element, since appending to <html> triggers the
// MutationObserver in checkDashboardReady() and re-runs this function.
let cardcss = document.querySelector("#gradientcss");
if (!cardcss) {
cardcss = document.createElement('style');
cardcss.id = "gradientcss";
document.documentElement.appendChild(cardcss);
}
// Build the full CSS into a string first, then only touch the DOM
// if the content actually changed. This keeps #gradientcss from being
// cleared/rewritten on every observer tick.
let css = "";
for (let i = 0; i < cardheads.length; i++) { for (let i = 0; i < cardheads.length; i++) {
let colorone = cardheads[i].style.backgroundColor.split(','); let colorone = cardheads[i].style.backgroundColor.split(',');
let [r, g, b] = [parseInt(colorone[0].split('(')[1]), parseInt(colorone[1]), parseInt(colorone[2])]; let [r, g, b] = [parseInt(colorone[0].split('(')[1]), parseInt(colorone[1]), parseInt(colorone[2])];
let [h, s, l] = [rgbToHsl(r, g, b)[0], rgbToHsl(r, g, b)[1], rgbToHsl(r, g, b)[2]]; let [h, s, l] = [rgbToHsl(r, g, b)[0], rgbToHsl(r, g, b)[1], rgbToHsl(r, g, b)[2]];
let degree = ((h % 60) / 60) >= .66 ? 30 : ((h % 60) / 60) <= .33 ? -30 : 15; let degree = ((h % 60) / 60) >= .66 ? 30 : ((h % 60) / 60) <= .33 ? -30 : 15;
let newh = h > 300 ? (360 - (h + 65)) + (65 + degree) : h + 65 + degree; let newh = h > 300 ? (360 - (h + 65)) + (65 + degree) : h + 65 + degree;
cardcss.textContent += ".ic-DashboardCard:nth-of-type(" + (i + 1) + ") .ic-DashboardCard__header_hero{background: linear-gradient(115deg, hsl(" + h + "deg," + s + "%," + l + "%) 5%, hsl(" + newh + "deg," + s + "%," + l + "%) 100%)!important}"; css += ".ic-DashboardCard:nth-of-type(" + (i + 1) + ") .ic-DashboardCard__header_hero{background: linear-gradient(115deg, hsl(" + h + "deg," + s + "%," + l + "%) 5%, hsl(" + newh + "deg," + s + "%," + l + "%) 100%)!important}";
}
if (cardcss.textContent !== css) {
cardcss.textContent = css;
} }
} else { } else {
let cardcss = document.querySelector("#gradientcss"); let cardcss = document.querySelector("#gradientcss");
if (cardcss) cardcss.textContent = ""; if (cardcss && cardcss.textContent !== "") {
cardcss.textContent = "";
}
} }
} }

View File

@ -29,6 +29,9 @@ const syncedSubOptions = [
"cardHeight", "cardHeight",
"customBackgroundLink", "customBackgroundLink",
"customBackgroundScale", "customBackgroundScale",
"customBackgroundDaily",
"customBackgroundNasaDaily",
"fitImageToScreen",
"sidebar_scale", "sidebar_scale",
]; ];
const localSwitches = []; const localSwitches = [];
@ -133,6 +136,9 @@ const defaultOptions = {
"customCardStyles": false, "customCardStyles": false,
"customBackgroundLink": "", "customBackgroundLink": "",
"customBackgroundScale": 100, "customBackgroundScale": 100,
"customBackgroundDaily": false,
"customBackgroundNasaDaily": false,
"fitImageToScreen": false,
} }
}; };
@ -315,9 +321,41 @@ function setupCustomBackgroundScale(initial) {
}); });
} }
function getDailyBackgroundPreset() {
if (typeof backgroundPresets === "undefined" || !Array.isArray(backgroundPresets) || backgroundPresets.length === 0) {
return null;
}
const today = new Date();
const dayNumber = Math.floor(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()) / 86400000);
return backgroundPresets[Math.abs(dayNumber) % backgroundPresets.length];
}
function syncCustomBackgroundDailyState(isDaily) {
const manualControls = document.getElementById("customBackgroundManualControls");
if (manualControls) {
manualControls.style.opacity = isDaily ? "0.45" : "1";
manualControls.style.pointerEvents = isDaily ? "none" : "auto";
manualControls.style.filter = isDaily ? "grayscale(1)" : "none";
}
["customBackgroundLink", "customBackgroundScale", "clearCustomBackground"].forEach(id => {
const el = document.getElementById(id);
if (el) el.disabled = isDaily;
});
document.querySelectorAll(".background-preset-card").forEach(button => {
button.disabled = isDaily;
});
renderBackgroundPresetSelection();
}
function renderBackgroundPresetSelection() { function renderBackgroundPresetSelection() {
const currentLink = document.querySelector("#customBackgroundLink")?.value || ""; const isDaily = document.querySelector("#customBackgroundDaily")?.checked === true || document.querySelector("#customBackgroundNasaDaily")?.checked === true;
const currentScale = String(document.querySelector("#customBackgroundScale")?.value || "100"); const dailyPreset = isDaily ? getDailyBackgroundPreset() : null;
const currentLink = isDaily ? (dailyPreset?.url || "") : (document.querySelector("#customBackgroundLink")?.value || "");
const currentScale = isDaily ? String(dailyPreset?.scale || "100") : String(document.querySelector("#customBackgroundScale")?.value || "100");
document.querySelectorAll(".background-preset-card").forEach(button => { document.querySelectorAll(".background-preset-card").forEach(button => {
const matchesLink = button.dataset.backgroundUrl === currentLink; const matchesLink = button.dataset.backgroundUrl === currentLink;
const matchesScale = button.dataset.backgroundScale === currentScale; const matchesScale = button.dataset.backgroundScale === currentScale;
@ -349,6 +387,29 @@ function displayBackgroundPresets() {
}); });
}); });
renderBackgroundPresetSelection(); renderBackgroundPresetSelection();
syncCustomBackgroundDailyState(document.querySelector("#customBackgroundDaily")?.checked === true || document.querySelector("#customBackgroundNasaDaily")?.checked === true);
}
function toggleBetterSidebarSubOptions(betterSidebarOn) {
const remlogoEl = document.getElementById("remlogo");
if (remlogoEl) {
remlogoEl.style.display = betterSidebarOn ? "none" : "flex";
}
const sidebarScaleEl = document.getElementById("sidebarScaleSub");
if (sidebarScaleEl) {
sidebarScaleEl.style.display = betterSidebarOn ? "block" : "none";
}
}
// When the Better Todo List is on, its own "Hide Recent Feedback" sub-option
// (todo_hide_feedback) replaces the standalone "Hide recent feedback" toggle,
// so hide the big #hide_feedback toggle to avoid showing two controls for the
// same thing. Mirrors toggleBetterSidebarSubOptions.
function toggleBetterTodoSubOptions(betterTodoOn) {
const hideFeedbackEl = document.getElementById("hide_feedback");
if (hideFeedbackEl) {
hideFeedbackEl.style.display = betterTodoOn ? "none" : "flex";
}
} }
function setup() { function setup() {
@ -376,6 +437,9 @@ function setup() {
"grade_hover", "grade_hover",
// "hide_completed", // "hide_completed",
"hover_preview", "hover_preview",
"customBackgroundDaily",
"customBackgroundNasaDaily",
"fitImageToScreen",
// "scheduledReminder", // "scheduledReminder",
"customCardStyles", "customCardStyles",
], ],
@ -493,8 +557,16 @@ function setup() {
if (option === "auto_dark") { if (option === "auto_dark") {
toggleDarkModeDisable(status); toggleDarkModeDisable(status);
} }
if (option === "better_sidebar") {
toggleBetterSidebarSubOptions(status);
}
if (option === "better_todo") {
toggleBetterTodoSubOptions(status);
}
}); });
}); });
toggleBetterSidebarSubOptions(sync["better_sidebar"] === true);
toggleBetterTodoSubOptions(sync["better_todo"] === true);
}); });
chrome.storage.sync.get(menu.checkboxes, sync => { chrome.storage.sync.get(menu.checkboxes, sync => {
@ -503,11 +575,21 @@ function setup() {
if (!checkbox) {console.log(option); return;} if (!checkbox) {console.log(option); return;}
checkbox.addEventListener("change", function (e) { checkbox.addEventListener("change", function (e) {
let status = this.checked; let status = this.checked;
chrome.storage.sync.set(JSON.parse(`{"${option}": ${status}}`)); if (option === "customBackgroundDaily" && status) {
document.querySelector("#customBackgroundNasaDaily").checked = false;
chrome.storage.sync.set({ "customBackgroundDaily": true, "customBackgroundNasaDaily": false });
} else if (option === "customBackgroundNasaDaily" && status) {
document.querySelector("#customBackgroundDaily").checked = false;
chrome.storage.sync.set({ "customBackgroundNasaDaily": true, "customBackgroundDaily": false });
} else {
chrome.storage.sync.set(JSON.parse(`{"${option}": ${status}}`));
}
syncCustomBackgroundDailyState(document.querySelector("#customBackgroundDaily")?.checked === true || document.querySelector("#customBackgroundNasaDaily")?.checked === true);
}); });
const value = sync[option] !== undefined ? sync[option] : defaultOptions.sync[option]; const value = sync[option] !== undefined ? sync[option] : defaultOptions.sync[option];
document.querySelector("#" + option).checked = value; document.querySelector("#" + option).checked = value;
}); });
syncCustomBackgroundDailyState(sync.customBackgroundDaily === true || sync.customBackgroundNasaDaily === true);
/* /*
document.querySelector('#autodark_start').value = result.auto_dark_start["hour"] + ":" + result.auto_dark_start["minute"]; document.querySelector('#autodark_start').value = result.auto_dark_start["hour"] + ":" + result.auto_dark_start["minute"];
document.querySelector('#autodark_end').value = result.auto_dark_end["hour"] + ":" + result.auto_dark_end["minute"]; document.querySelector('#autodark_end').value = result.auto_dark_end["hour"] + ":" + result.auto_dark_end["minute"];
@ -662,7 +744,7 @@ function setup() {
final = { ...final, ...(await getExport(storage, ["custom_styles"])) }; final = { ...final, ...(await getExport(storage, ["custom_styles"])) };
break; break;
case "export-background": case "export-background":
final = { ...final, ...(await getExport(storage, ["customBackgroundLink", "customBackgroundScale"])) }; final = { ...final, ...(await getExport(storage, ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "fitImageToScreen"])) };
break; break;
} }
} }
@ -1270,6 +1352,7 @@ function saveCurrentTheme() {
"cardHeight": current["cardHeight"], "cardHeight": current["cardHeight"],
"customBackgroundLink": current["customBackgroundLink"], "customBackgroundLink": current["customBackgroundLink"],
"customBackgroundScale": current["customBackgroundScale"], "customBackgroundScale": current["customBackgroundScale"],
"customBackgroundDaily": current["customBackgroundDaily"],
} }
const now = new Date(); const now = new Date();
local["saved_themes"][now.getTime()] = trimmed; local["saved_themes"][now.getTime()] = trimmed;

View File

@ -25,7 +25,7 @@
"content_scripts": [ "content_scripts": [
{ {
"matches": ["https://*/*"], "matches": ["https://*/*"],
"js": ["css/darkmodecss.js", "js/content.js"], "js": ["css/darkmodecss.js", "js/backgrounds.js", "js/content.js"],
"css": ["css/content.css"], "css": ["css/content.css"],
"run_at": "document_start" "run_at": "document_start"
} }