From 3bac45032dc3668ab4f66840831aa36c8bd5232c Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Sun, 19 Jul 2026 08:58:31 +0930 Subject: [PATCH] refactor: further trim PR #458 debloat across patches, notices, Select, music, archive, search Co-authored-by: Cursor --- src/interface/components/Select.svelte | 215 ++++----- .../animatedBackground/backgroundLayers.ts | 16 +- .../built-in/animatedBackground/index.ts | 13 +- src/plugins/built-in/backgroundMusic/index.ts | 104 ++--- .../built-in/backgroundMusic/styles.css | 10 - .../built-in/globalSearch/src/indexing/db.ts | 5 +- .../globalSearch/src/indexing/indexer.ts | 64 +-- .../globalSearch/src/indexing/utils.ts | 9 +- .../src/search/dedupeIndexItems.ts | 89 ++-- .../built-in/gradeAnalytics/core/index.ts | 6 +- .../built-in/notificationCollector/archive.ts | 169 ++------ .../built-in/notificationCollector/index.ts | 55 +-- .../injectArchivedNotifications.ts | 59 +-- .../built-in/notificationCollector/styles.css | 13 +- src/seqta/utils/Openers/OpenMenuOptions.ts | 17 +- src/seqta/utils/menuItemVisibility.ts | 8 - src/seqta/utils/notices/noticeHomeUi.ts | 408 ++++++++---------- src/seqta/utils/notices/noticeLabelFilters.ts | 44 +- .../utils/patchSeqtaMenuUpdateColours.ts | 53 +-- src/seqta/utils/seqtaMenuColourPatch.js | 54 +-- src/seqta/utils/themeImagePagePatch.js | 10 +- 21 files changed, 497 insertions(+), 924 deletions(-) diff --git a/src/interface/components/Select.svelte b/src/interface/components/Select.svelte index e88ad70d..228f8e60 100644 --- a/src/interface/components/Select.svelte +++ b/src/interface/components/Select.svelte @@ -9,21 +9,7 @@ let isOpen = $state(false); let activeIndex = $state(0); - let root: HTMLDivElement | undefined = $state(); - let trigger: HTMLButtonElement | undefined = $state(); - let listbox: HTMLDivElement | undefined = $state(); - - const selectedLabel = $derived( - options.find((option) => option.value === value)?.label ?? value, - ); - - const activeDescendantId = $derived( - isOpen && options[activeIndex] ? optionId(options[activeIndex].value) : undefined, - ); - - function optionId(optionValue: string): string { - return `${listboxId}-option-${optionValue}`; - } + let trigger = $state(); function openMenu(preferredIndex?: number) { isOpen = true; @@ -41,62 +27,59 @@ closeMenu(); } - function moveActive(delta: number) { - if (!options.length) return; - activeIndex = (activeIndex + delta + options.length) % options.length; - } + function onKeydown(event: KeyboardEvent, inListbox = false) { + const { key } = event; - function handleKeydown(event: KeyboardEvent, inListbox = false) { - switch (event.key) { - case "ArrowDown": - case "ArrowUp": { - event.preventDefault(); - const delta = event.key === "ArrowDown" ? 1 : -1; - if (isOpen || inListbox) moveActive(delta); - else openMenu(); - break; + if (key === "ArrowDown" || key === "ArrowUp") { + event.preventDefault(); + const count = options.length; + if (!count) return; + if (isOpen || inListbox) { + activeIndex = (activeIndex + (key === "ArrowDown" ? 1 : -1) + count) % count; + } else { + openMenu(); } - case "Enter": - case " ": - event.preventDefault(); - if (isOpen) { - const option = options[activeIndex]; - if (option) selectValue(option.value); - } else { - openMenu(); - } - break; - case "Escape": - if (isOpen) { - event.preventDefault(); - closeMenu(); - } - break; - case "Home": - if (inListbox) { - event.preventDefault(); - activeIndex = 0; - } - break; - case "End": - if (inListbox) { - event.preventDefault(); - activeIndex = Math.max(0, options.length - 1); - } - break; - case "Tab": - if (inListbox) closeMenu(false); - break; + return; + } + + if (key === "Enter" || key === " ") { + event.preventDefault(); + if (isOpen) { + const option = options[activeIndex]; + if (option) selectValue(option.value); + } else { + openMenu(); + } + return; + } + + if (key === "Escape" && isOpen) { + event.preventDefault(); + closeMenu(); + return; + } + + if (!inListbox) return; + + if (key === "Home") { + event.preventDefault(); + activeIndex = 0; + } else if (key === "End") { + event.preventDefault(); + activeIndex = Math.max(0, options.length - 1); + } else if (key === "Tab") { + closeMenu(false); } } $effect(() => { if (!isOpen) return; - queueMicrotask(() => listbox?.focus()); + queueMicrotask(() => document.getElementById(listboxId)?.focus()); + const wrapper = trigger?.parentElement; const onPointerDown = (event: PointerEvent) => { - if (root && event.composedPath().includes(root)) return; + if (wrapper && event.composedPath().includes(wrapper)) return; closeMenu(false); }; @@ -105,46 +88,47 @@ }); -
+
{#if isOpen}
handleKeydown(event, true)} + aria-activedescendant={options[activeIndex] ? `${listboxId}-opt-${activeIndex}` : undefined} + onkeydown={(event) => onKeydown(event, true)} > {#each options as option, index (option.value)} ' + : ''; + return `
+
+
+ ${notice.label_title || "General"} + ${notice.staff} +
+ ${closeBtn} +
+

${notice.title}

+
${body}
+
`; +} + +function modalTargetSize(sourceWidth: number, contentHeight: number) { + const vw = window.innerWidth; + const vh = window.innerHeight; + const scrollY = Math.round(window.scrollY); + const width = Math.round(Math.min(Math.max(sourceWidth, 800), vw - 40)); + const height = Math.round(Math.min(Math.max(contentHeight + 32, 200), vh * 0.9)); + return { + width, + height, + left: Math.round((vw - width) / 2), + top: Math.round((vh - height) / 2) + scrollY, + scrollX: Math.round(window.scrollX), + scrollY, + }; +} + +function measureNoticeHeight( + notice: { title: string; staff: string; label_title?: string }, + body: string, + targetWidth: number, +): number { + const measure = document.createElement("div"); + measure.style.cssText = `position:absolute;left:-9999px;width:${targetWidth}px;visibility:hidden`; + measure.innerHTML = noticeCardHtml(notice, undefined, body, { + wrapperClass: "notice-modal-state", + wrapperStyle: + "position:relative;width:100%;padding:16px;border:1px solid rgba(255,255,255,0.1)", + }); + document.body.appendChild(measure); + const height = measure.firstElementChild!.getBoundingClientRect().height; + measure.remove(); + return height; +} + +function showSourceElement(el: HTMLElement) { + el.style.opacity = "1"; + el.style.transform = ""; +} + +function elementScale(el: HTMLElement) { + const transform = getComputedStyle(el).transform; + if (!transform || transform === "none") return { x: 1, y: 1 }; + const match = transform.match(/matrix.*\((.+)\)/); + if (!match) return { x: 1, y: 1 }; + const values = match[1].split(", "); + return { x: parseFloat(values[0]), y: parseFloat(values[3]) }; +} + export function processNoticeColor(colour: unknown): string | undefined { if (typeof colour !== "string") return undefined; const rgb = GetThresholdOfColor(colour); - if (rgb < 100 && settingsState.DarkMode) { - return undefined; - } + if (rgb < 100 && settingsState.DarkMode) return undefined; return colour; } @@ -29,35 +126,13 @@ export function appendNoticeEmptyState(container: HTMLElement, message: string) } function createNoticeElement(notice: any, colour: string | undefined): Node { - const textPreview = - notice.contents - .replace(/<[^>]*>/g, "") - .replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "") - .replace(/\s+/g, " ") - .trim() - .substring(0, 150) + (notice.contents.length > 150 ? "..." : ""); - - const noticeId = `notice-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - - const htmlContent = ` -
-
-
- - ${notice.label_title || "General"} - - ${notice.staff} -
- -
-

${notice.title}

-
${textPreview}
-
`; - + const htmlContent = noticeCardHtml(notice, colour, noticePreview(notice.contents), { + wrapperStyle: + "position: relative; background: var(--background-primary); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);", + hideClose: true, + }); const element = stringToHTML(htmlContent).firstChild as HTMLElement; - element.addEventListener("click", () => - openNoticeModal(notice, colour, element), - ); + element.addEventListener("click", () => openNoticeModal(notice, colour, element)); return element; } @@ -66,10 +141,7 @@ export function openNoticeModal( colour: string | undefined, sourceElement: HTMLElement, ) { - const cleanContent = notice.contents - .replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "") - .replace(/ +/, " "); - + const cleanContent = noticeBody(notice.contents); document.getElementById("notice-modal")?.remove(); const sourceRect = sourceElement.getBoundingClientRect(); @@ -80,84 +152,37 @@ export function openNoticeModal( let sourceWidth = sourceRect.width; let sourceHeight = sourceRect.height; - const modalHtml = ` -
-
+ const modalHtml = `
+
-
-
-
- - ${notice.label_title || "General"} - - ${notice.staff} -
- -
-

${notice.title}

-
${cleanContent}
-
+ ${noticeCardHtml(notice, colour, cleanContent)}
`; const modal = stringToHTML(modalHtml).firstChild as HTMLElement; - const transitionContainer = modal.querySelector( - ".notice-modal-transition", - ) as HTMLElement; - const unifiedContent = modal.querySelector( - ".notice-unified-content", - ) as HTMLElement; + const transitionContainer = modal.querySelector(".notice-modal-transition") as HTMLElement; + const unifiedContent = modal.querySelector(".notice-unified-content") as HTMLElement; const closeBtn = modal.querySelector(".notice-close-btn") as HTMLElement; - document.body.appendChild(modal); sourceElement.setAttribute("data-transitioning", "true"); sourceElement.style.opacity = "0"; sourceElement.style.transform = "scale(0.95)"; - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - let targetWidth = Math.round( - Math.min(Math.max(sourceWidth, 800), viewportWidth - 40), + const initialWidth = Math.round( + Math.min(Math.max(sourceWidth, 800), window.innerWidth - 40), ); + const measuredHeight = measureNoticeHeight(notice, cleanContent, initialWidth); + let { width: targetWidth, height: targetHeight, left: targetLeft, top: targetTop } = + modalTargetSize(sourceWidth, measuredHeight); - const tempMeasureDiv = document.createElement("div"); - tempMeasureDiv.style.position = "absolute"; - tempMeasureDiv.style.left = "-9999px"; - tempMeasureDiv.style.width = targetWidth + "px"; - tempMeasureDiv.style.visibility = "hidden"; - tempMeasureDiv.innerHTML = ` -
-
-
- ${notice.label_title || "General"} - ${notice.staff} -
- -
-

${notice.title}

-
${cleanContent}
-
- `; - document.body.appendChild(tempMeasureDiv); - const measuredHeight = - tempMeasureDiv.firstElementChild!.getBoundingClientRect().height; - document.body.removeChild(tempMeasureDiv); - - let targetHeight = Math.round( - Math.min(Math.max(measuredHeight + 32, 200), viewportHeight * 0.9), - ); - let targetLeft = Math.round((viewportWidth - targetWidth) / 2); - let targetTop = Math.round((viewportHeight - targetHeight) / 2) + scrollY; + const applyTargetLayout = () => { + transitionContainer.style.left = `${Math.round(targetLeft + scrollX)}px`; + transitionContainer.style.top = `${Math.round(targetTop)}px`; + transitionContainer.style.width = `${Math.round(targetWidth)}px`; + transitionContainer.style.height = `${Math.round(targetHeight)}px`; + }; const closeModal = () => { window.removeEventListener("resize", handleResize); @@ -165,8 +190,7 @@ export function openNoticeModal( if (!settingsState.animations) { modal.remove(); - sourceElement.style.opacity = "1"; - sourceElement.style.transform = ""; + showSourceElement(sourceElement); sourceElement.removeAttribute("data-transitioning"); return; } @@ -179,146 +203,65 @@ export function openNoticeModal( }, { duration: 0.2 }, ); - - animate( - transitionContainer, - { opacity: [1, 0] }, - { duration: 0.2, delay: 0.3 }, - ); - - sourceElement.style.opacity = "1"; - sourceElement.style.transform = ""; - + animate(transitionContainer, { opacity: [1, 0] }, { duration: 0.2, delay: 0.3 }); + showSourceElement(sourceElement); modal.style.pointerEvents = "none"; - - animate( - transitionContainer, - { - left: [targetLeft + scrollX, sourceLeft + scrollX], - top: [targetTop, sourceTop + scrollY], - width: [targetWidth, sourceWidth], - height: [targetHeight, sourceHeight], - scale: [1, 1], - }, - { - duration: 0.35, - type: "spring", - stiffness: 400, - damping: 35, - }, - ).finished.then(async () => { + animate(transitionContainer, { + left: [targetLeft + scrollX, sourceLeft + scrollX], + top: [targetTop, sourceTop + scrollY], + width: [targetWidth, sourceWidth], + height: [targetHeight, sourceHeight], + }, SPRING_CLOSE).finished.then(() => { modal.remove(); sourceElement.removeAttribute("data-transitioning"); }); }; - closeBtn?.addEventListener("click", closeModal); - modal?.addEventListener("click", (e) => { - if (e.target === modal) { - closeModal(); - } + closeBtn.addEventListener("click", closeModal); + modal.addEventListener("click", (e) => { + if (e.target === modal) closeModal(); }); const handleEscape = (e: KeyboardEvent) => { - if (e.key === "Escape") { - closeModal(); - document.removeEventListener("keydown", handleEscape); - window.removeEventListener("resize", handleResize); - } + if (e.key === "Escape") closeModal(); }; document.addEventListener("keydown", handleEscape); const handleResize = () => { - const newSourceRect = sourceElement.getBoundingClientRect(); - const newScrollY = Math.round(window.scrollY); - const newScrollX = Math.round(window.scrollX); + const rect = sourceElement.getBoundingClientRect(); + scrollY = Math.round(window.scrollY); + scrollX = Math.round(window.scrollX); + const scale = elementScale(sourceElement); + sourceWidth = rect.width / scale.x; + sourceHeight = rect.height / scale.y; + sourceLeft = rect.left - (sourceWidth - rect.width) / 2; + sourceTop = rect.top - (sourceHeight - rect.height) / 2; - const computedStyle = getComputedStyle(sourceElement); - const transform = computedStyle.transform; - let scaleX = 1, - scaleY = 1; - - if (transform && transform !== "none") { - const matrix = transform.match(/matrix.*\((.+)\)/); - if (matrix) { - const values = matrix[1].split(", "); - scaleX = parseFloat(values[0]); - scaleY = parseFloat(values[3]); - } - } - - const newSourceWidth = newSourceRect.width / scaleX; - const newSourceHeight = newSourceRect.height / scaleY; - - const deltaX = (newSourceWidth - newSourceRect.width) / 2; - const deltaY = (newSourceHeight - newSourceRect.height) / 2; - - const newSourceLeft = newSourceRect.left - deltaX; - const newSourceTop = newSourceRect.top - deltaY; - - const newViewportWidth = window.innerWidth; - const newViewportHeight = window.innerHeight; - const newTargetWidth = Math.round( - Math.min(Math.max(newSourceWidth, 800), newViewportWidth - 40), + const next = modalTargetSize( + sourceWidth, + unifiedContent.getBoundingClientRect().height, ); - const currentHeight = unifiedContent.getBoundingClientRect().height; - const newTargetHeight = Math.round( - Math.min(Math.max(currentHeight + 32, 200), newViewportHeight * 0.9), - ); - const newTargetLeft = Math.round((newViewportWidth - newTargetWidth) / 2); - const newTargetTop = - Math.round((newViewportHeight - newTargetHeight) / 2) + newScrollY; - - transitionContainer.style.left = - Math.round(newTargetLeft + newScrollX) + "px"; - transitionContainer.style.top = Math.round(newTargetTop) + "px"; - transitionContainer.style.width = Math.round(newTargetWidth) + "px"; - transitionContainer.style.height = Math.round(newTargetHeight) + "px"; - - sourceLeft = newSourceLeft; - sourceTop = newSourceTop; - sourceWidth = newSourceWidth; - sourceHeight = newSourceHeight; - targetLeft = newTargetLeft; - targetTop = newTargetTop; - targetWidth = newTargetWidth; - targetHeight = newTargetHeight; - scrollY = newScrollY; - scrollX = newScrollX; + targetLeft = next.left; + targetTop = next.top; + targetWidth = next.width; + targetHeight = next.height; + applyTargetLayout(); }; window.addEventListener("resize", handleResize); + unifiedContent.classList.replace("notice-card-state", "notice-modal-state"); if (settingsState.animations) { animate(modal, { opacity: [0, 1] }, { duration: 0.2 }); - - animate( - transitionContainer, - { - left: [sourceLeft + scrollX, targetLeft + scrollX], - top: [sourceTop + scrollY, targetTop], - width: [sourceWidth, targetWidth], - height: [sourceHeight, targetHeight], - scale: [1, 1], - }, - { - duration: 0.5, - type: "spring", - stiffness: 280, - damping: 24, - }, - ); - - unifiedContent.classList.remove("notice-card-state"); - unifiedContent.classList.add("notice-modal-state"); + animate(transitionContainer, { + left: [sourceLeft + scrollX, targetLeft + scrollX], + top: [sourceTop + scrollY, targetTop], + width: [sourceWidth, targetWidth], + height: [sourceHeight, targetHeight], + }, SPRING_OPEN); } else { modal.style.opacity = "1"; - transitionContainer.style.left = Math.round(targetLeft + scrollX) + "px"; - transitionContainer.style.top = Math.round(targetTop) + "px"; - transitionContainer.style.width = Math.round(targetWidth) + "px"; - transitionContainer.style.height = Math.round(targetHeight) + "px"; - unifiedContent.classList.remove("notice-card-state"); - unifiedContent.classList.add("notice-modal-state"); + applyTargetLayout(); } } @@ -341,22 +284,19 @@ export function renderNoticesIntoContainer( } const fragment = document.createDocumentFragment(); - - notices.forEach((notice: any) => { - const shouldInclude = - settingsState.mockNotices || noticeMatchesLabelFilter(notice, labelTokens); - - if (shouldInclude) { - const colour = processNoticeColor(notice.colour); - fragment.appendChild(createNoticeElement(notice, colour)); + for (const notice of notices) { + if ( + settingsState.mockNotices || + noticeMatchesLabelFilter(notice, labelTokens) + ) { + fragment.appendChild(createNoticeElement(notice, processNoticeColor(notice.colour))); } - }); + } - if (fragment.childNodes.length === 0) { + if (!fragment.childNodes.length) { appendNoticeEmptyState(noticeContainer, emptyMessage); return; } - noticeContainer.appendChild(fragment); } @@ -372,8 +312,9 @@ export async function fetchNoticesForDate( container.innerHTML = ""; } + let data: { payload?: unknown }; try { - const data = settingsState.mockNotices + data = settingsState.mockNotices ? getMockNotices() : await ( await fetch(noticesUrl, { @@ -383,11 +324,10 @@ export async function fetchNoticesForDate( body: JSON.stringify({ date }), }) ).json(); - - renderNoticesIntoContainer(containerId, data, labelTokens); } catch { - renderNoticesIntoContainer(containerId, { payload: [] }, labelTokens); + data = { payload: [] }; } + renderNoticesIntoContainer(containerId, data, labelTokens); } export type SetupNoticesSectionOptions = { @@ -405,9 +345,7 @@ export function setupNoticesSection(options: SetupNoticesSectionOptions): () => ? (document.querySelector(options.dateInput) as HTMLInputElement | null) : options.dateInput; - if (dateControl) { - dateControl.value = options.initialDate; - } + if (dateControl) dateControl.value = options.initialDate; const debouncedInputChange = debounce((e: Event) => { void fetchNoticesForDate( diff --git a/src/seqta/utils/notices/noticeLabelFilters.ts b/src/seqta/utils/notices/noticeLabelFilters.ts index 26818293..447a611f 100644 --- a/src/seqta/utils/notices/noticeLabelFilters.ts +++ b/src/seqta/utils/notices/noticeLabelFilters.ts @@ -1,14 +1,13 @@ type PrefEntry = { name?: string; value?: unknown }; +const NOTICES_FILTER = "notices.filters"; +const JSON_HEADERS = { "Content-Type": "application/json; charset=utf-8" }; + /** Parse `notices.filters` pref (space-separated label IDs). */ export function parseNoticesFilterPref(prefsPayload: unknown): string[] { if (!Array.isArray(prefsPayload)) return []; - const values = (prefsPayload as PrefEntry[]) - .filter((item) => item?.name === "notices.filters") - .map((item) => item?.value) - .filter((v): v is string => typeof v === "string"); - if (values.length === 0) return []; - return String(values[0]).split(" ").filter(Boolean); + const raw = (prefsPayload as PrefEntry[]).find((item) => item?.name === NOTICES_FILTER)?.value; + return typeof raw === "string" ? raw.split(" ").filter(Boolean) : []; } /** Label IDs from `load/notices` with `{ mode: "labels" }`. */ @@ -16,13 +15,12 @@ export async function fetchNoticeLabelIds(noticesUrl: string): Promise try { const res = await fetch(noticesUrl, { method: "POST", - headers: { "Content-Type": "application/json; charset=utf-8" }, + headers: JSON_HEADERS, credentials: "include", body: JSON.stringify({ mode: "labels" }), }); if (!res.ok) return []; - const json = (await res.json()) as { payload?: Array<{ id?: number }> }; - const payload = json?.payload; + const payload = ((await res.json()) as { payload?: Array<{ id?: number }> })?.payload; if (!Array.isArray(payload)) return []; return payload .map((entry) => entry?.id) @@ -39,26 +37,16 @@ export async function resolveNoticeFilterTokens( noticesUrl: string, ): Promise { const fromPref = parseNoticesFilterPref(prefsPayload); - if (fromPref.length > 0) return fromPref; - return await fetchNoticeLabelIds(noticesUrl); + return fromPref.length > 0 ? fromPref : await fetchNoticeLabelIds(noticesUrl); } export function normalizeNoticeLabelId(label: unknown): string | null { - if (typeof label === "number" && !Number.isNaN(label)) { - return String(label); - } - if (typeof label === "string" && label.trim()) { - return label.trim(); - } - if (label && typeof label === "object") { - const obj = label as Record; - if (typeof obj.id === "number" && !Number.isNaN(obj.id)) { - return String(obj.id); - } - if (typeof obj.id === "string" && obj.id.trim()) { - return obj.id.trim(); - } - } + if (typeof label === "number" && !Number.isNaN(label)) return String(label); + if (typeof label === "string" && label.trim()) return label.trim(); + const id = + label && typeof label === "object" ? (label as { id?: unknown }).id : undefined; + if (typeof id === "number" && !Number.isNaN(id)) return String(id); + if (typeof id === "string" && id.trim()) return id.trim(); return null; } @@ -68,6 +56,6 @@ export function noticeMatchesLabelFilter( ): boolean { if (filterTokens.length === 0) return true; const id = normalizeNoticeLabelId(notice?.label); - if (id !== null && filterTokens.includes(id)) return true; - return filterTokens.includes(JSON.stringify(notice?.label)); + return (id !== null && filterTokens.includes(id)) || + filterTokens.includes(JSON.stringify(notice?.label)); } diff --git a/src/seqta/utils/patchSeqtaMenuUpdateColours.ts b/src/seqta/utils/patchSeqtaMenuUpdateColours.ts index ace24fbe..ec5c7cf4 100644 --- a/src/seqta/utils/patchSeqtaMenuUpdateColours.ts +++ b/src/seqta/utils/patchSeqtaMenuUpdateColours.ts @@ -8,44 +8,36 @@ import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl"; import { verboseInfo } from "@/utils/verboseLog"; const PAGE_PATCH_LOADER_ID = "bsplus-seqta-menu-colour-patch-loader"; +let colorisRecoveryAttached = false; +let dismissTimer: ReturnType | null = null; -export function dismissStaleModaliserContainers(): number { - let removed = 0; - for (const container of document.querySelectorAll(".modaliser-container")) { - const modal = container.querySelector(".modaliser"); - if (!modal?.childElementCount || !container.classList.contains("visible")) { - container.remove(); - removed++; - } - } - return removed; -} - -export function dismissStaleColourSlidePanes( - forceColourChooser = false, -): number { - let removed = 0; +function dismissStaleColourDialogs(forceColourChooser = false): { + slideRemoved: number; + modalRemoved: number; +} { + let slideRemoved = 0; for (const pane of document.querySelectorAll(".uiSlidePane")) { if (pane.querySelector(".pane.colourChooser")) { pane.remove(); - removed++; + slideRemoved++; continue; } if (!forceColourChooser && pane.classList.contains("shown")) continue; if (!pane.classList.contains("shown")) { pane.remove(); - removed++; + slideRemoved++; + } + } + + let modalRemoved = 0; + for (const container of document.querySelectorAll(".modaliser-container")) { + const modal = container.querySelector(".modaliser"); + if (!modal?.childElementCount || !container.classList.contains("visible")) { + container.remove(); + modalRemoved++; } } - return removed; -} -export function dismissStaleColourDialogs(forceColourChooser = false): { - slideRemoved: number; - modalRemoved: number; -} { - const slideRemoved = dismissStaleColourSlidePanes(forceColourChooser); - const modalRemoved = dismissStaleModaliserContainers(); document.body.classList.remove("clr-open"); document.documentElement.classList.remove("clr-open"); return { slideRemoved, modalRemoved }; @@ -69,8 +61,7 @@ function setClrPickerState(reset: boolean): void { } } -/** Hide colour-picker / modal layers that intercept clicks after a colour save. */ -export function dismissTimetableUiBlockers(): { +function dismissTimetableUiBlockers(): { slideRemoved: number; modalRemoved: number; } { @@ -79,14 +70,10 @@ export function dismissTimetableUiBlockers(): { return dismissStaleColourDialogs(); } -/** Clear inline styles that can prevent Coloris from reopening. */ -export function prepareColorisPickerOpen(): void { +function prepareColorisPickerOpen(): void { setClrPickerState(true); } -let colorisRecoveryAttached = false; -let dismissTimer: ReturnType | null = null; - export function attachTimetableColorisRecovery(): void { if (colorisRecoveryAttached) return; colorisRecoveryAttached = true; diff --git a/src/seqta/utils/seqtaMenuColourPatch.js b/src/seqta/utils/seqtaMenuColourPatch.js index 271dde2c..0f96405b 100644 --- a/src/seqta/utils/seqtaMenuColourPatch.js +++ b/src/seqta/utils/seqtaMenuColourPatch.js @@ -7,62 +7,54 @@ var MENU_UPDATE_COLOURS = "menu.update.colours"; var SUBJECT_PREFIX = "timetable.subject.colour."; var TUTOR_PREFIX = "timetable.tutor."; + var CLEANUP_FOLLOWUP_MS = 300; + var cleanupFollowup = null; function isTesStyling() { var el = document.getElementById("logo-style"); return el && el.textContent.indexOf("tesSeqta") !== -1; } - function dismissModalisers() { - var n = 0; - var containers = document.querySelectorAll(".modaliser-container"); - for (var i = 0; i < containers.length; i++) { - var c = containers[i]; - var m = c.querySelector(".modaliser"); - if (!m || !m.childElementCount || !c.classList.contains("visible")) { - c.remove(); - n++; - } - } - return n; - } - - function dismissSlidePanes(forceColour) { - var n = 0; + function dismissStaleDialogs(forceColour) { + var slide = 0; var panes = document.querySelectorAll(".uiSlidePane"); for (var i = 0; i < panes.length; i++) { var p = panes[i]; if (p.querySelector(".pane.colourChooser")) { p.remove(); - n++; + slide++; continue; } if (!forceColour && p.classList.contains("shown")) continue; if (!p.classList.contains("shown")) { p.remove(); - n++; + slide++; + } + } + + var modal = 0; + var containers = document.querySelectorAll(".modaliser-container"); + for (var j = 0; j < containers.length; j++) { + var c = containers[j]; + var m = c.querySelector(".modaliser"); + if (!m || !m.childElementCount || !c.classList.contains("visible")) { + c.remove(); + modal++; } } - return n; - } - function dismissStaleDialogs(forceColour) { - var slide = dismissSlidePanes(forceColour); - var modal = dismissModalisers(); document.body.classList.remove("clr-open"); document.documentElement.classList.remove("clr-open"); return { slideRemoved: slide, modalRemoved: modal }; } function scheduleCleanup() { - var delays = [0, 100, 300, 600]; - for (var i = 0; i < delays.length; i++) { - (function (d) { - setTimeout(function () { - dismissStaleDialogs(true); - }, d); - })(delays[i]); - } + dismissStaleDialogs(true); + if (cleanupFollowup) clearTimeout(cleanupFollowup); + cleanupFollowup = setTimeout(function () { + cleanupFollowup = null; + dismissStaleDialogs(true); + }, CLEANUP_FOLLOWUP_MS); } function applyMenuColours() { diff --git a/src/seqta/utils/themeImagePagePatch.js b/src/seqta/utils/themeImagePagePatch.js index 51c5f184..899e984e 100644 --- a/src/seqta/utils/themeImagePagePatch.js +++ b/src/seqta/utils/themeImagePagePatch.js @@ -14,7 +14,6 @@ var THEME_STYLE_ID = "custom-theme"; var PREVIEW_STYLE_ID = "custom-theme-preview"; var urlCache = {}; - var cssState = { custom: "", preview: "" }; var headObserver = null; function log(event, detail) { @@ -77,8 +76,6 @@ function clearAll() { releaseCachedUrls(); - cssState.custom = ""; - cssState.preview = ""; setStyleText(IMAGES_STYLE_ID, ""); document.getElementById(THEME_STYLE_ID)?.remove(); document.getElementById(PREVIEW_STYLE_ID)?.remove(); @@ -128,18 +125,15 @@ if (payload.images !== undefined) applyThemeImages(payload.images); if (payload.customCss !== undefined) { - cssState.custom = payload.customCss || ""; - setStyleText(THEME_STYLE_ID, cssState.custom, true); + setStyleText(THEME_STYLE_ID, payload.customCss || "", true); log("custom css applied"); } if (payload.previewCss !== undefined) { - cssState.preview = payload.previewCss || ""; - setStyleText(PREVIEW_STYLE_ID, cssState.preview); + setStyleText(PREVIEW_STYLE_ID, payload.previewCss || ""); } if (payload.clearPreview) { - cssState.preview = ""; setStyleText(PREVIEW_STYLE_ID, ""); } } catch (e) {