mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
fix(timetable): reopen colour picker
+ some quick fixes to dropdowns again
This commit is contained in:
+4
-1
@@ -6,6 +6,8 @@ import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
|
||||
// UI and theme management
|
||||
import pageState from "@/pageState.js?url";
|
||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||
import { installSeqtaMenuColourPatch } from "@/seqta/utils/patchSeqtaMenuUpdateColours";
|
||||
|
||||
// Stylesheets
|
||||
import injectedCSS from "@/css/injected.scss?inline";
|
||||
@@ -15,6 +17,7 @@ export async function main() {
|
||||
try {
|
||||
if (settingsState.onoff) {
|
||||
injectPageState();
|
||||
installSeqtaMenuColourPatch();
|
||||
|
||||
// Rather permanent FIX for bug! -> this is a hack to get the injected.css file to have HMR in development mode as this import system is currently broken with crxjs
|
||||
if (import.meta.env.MODE === "development") {
|
||||
@@ -35,6 +38,6 @@ export async function main() {
|
||||
|
||||
function injectPageState() {
|
||||
const mainScript = document.createElement("script");
|
||||
mainScript.src = browser.runtime.getURL(pageState);
|
||||
mainScript.src = resolveExtensionAssetUrl(pageState);
|
||||
document.head.appendChild(mainScript);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
import { getAdaptiveColour } from "@/seqta/utils/adaptiveThemeColour";
|
||||
import { getCustomThemeAdaptiveCssVariableBindings } from "@/seqta/ui/colors/customThemeAdaptiveBindings";
|
||||
|
||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||
import darkLogo from "@/resources/icons/betterseqta-light-full.png";
|
||||
import lightLogo from "@/resources/icons/betterseqta-dark-full.png";
|
||||
|
||||
const ADAPTIVE_THEME_TRANSITION_MS = 400;
|
||||
const LOGO_STYLE_ID = "bsplus-logo-style";
|
||||
|
||||
let colorTransitionRafId: number | null = null;
|
||||
let lastInterpolatedHex: string | null = null;
|
||||
@@ -84,6 +86,23 @@ function cancelColorTransition() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Chromium does not always resolve extension URLs inside CSS variables on ::before. */
|
||||
function applyBetterseqtaLogoBackground(isDark: boolean) {
|
||||
const url = resolveExtensionAssetUrl(isDark ? darkLogo : lightLogo);
|
||||
let styleEl = document.getElementById(LOGO_STYLE_ID);
|
||||
if (!styleEl) {
|
||||
styleEl = document.createElement("style");
|
||||
styleEl.id = LOGO_STYLE_ID;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
styleEl.textContent = `
|
||||
body.student #menu > ul::before,
|
||||
#title::before {
|
||||
background-image: url("${url}") !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function getRepresentativeRgbChannels(s: string): { r: number; g: number; b: number } | null {
|
||||
const parsedHex = parseRepresentativeHex(s);
|
||||
if (!parsedHex) return null;
|
||||
@@ -115,11 +134,11 @@ function applyColorsWith(selectedColor: string) {
|
||||
let modeProps = {};
|
||||
modeProps = settingsState.DarkMode
|
||||
? {
|
||||
"--betterseqta-logo": `url(${browser.runtime.getURL(darkLogo)})`,
|
||||
"--betterseqta-logo": `url(${resolveExtensionAssetUrl(darkLogo)})`,
|
||||
}
|
||||
: {
|
||||
"--better-pale": lightenAndPaleColor(selectedColor),
|
||||
"--betterseqta-logo": `url(${browser.runtime.getURL(lightLogo)})`,
|
||||
"--betterseqta-logo": `url(${resolveExtensionAssetUrl(lightLogo)})`,
|
||||
};
|
||||
|
||||
if (settingsState.DarkMode) {
|
||||
@@ -129,6 +148,8 @@ function applyColorsWith(selectedColor: string) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
|
||||
applyBetterseqtaLogoBackground(settingsState.DarkMode);
|
||||
|
||||
// Dynamic properties, always applied
|
||||
const rgbThreshold = GetThresholdOfColor(selectedColor);
|
||||
const isBright = rgbThreshold > 210;
|
||||
|
||||
@@ -106,7 +106,8 @@ function buildFontOverrideCss(family: string): string {
|
||||
|
||||
.iconFamily,
|
||||
.iconFamily *,
|
||||
button.uiButton.timetable-zoom.iconFamily,
|
||||
button.timetable-zoom.iconFamily,
|
||||
button.bsplus-timetable-control.iconFamily,
|
||||
[class~="iconFamily"],
|
||||
[class~="iconFamily"] * {
|
||||
font-family: "IconFamily" !important;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* SEQTA Learn bug (vanilla too): MainMenu.updateColours uses
|
||||
* `.each(function (item) { this.options... }).bind(this)` — the bind is on
|
||||
* `.each()`'s return value, not the callback. Saving a timetable subject colour
|
||||
* sends `menu.update.colours` and throws.
|
||||
*
|
||||
* Also: ColourChooser (SlidePane + Modaliser) can leave a full-screen
|
||||
* uiSlidePane / empty modaliser-container that blocks timetable clicks.
|
||||
*
|
||||
* Must run in the PAGE JavaScript context — inject via web_accessible script URL.
|
||||
*/
|
||||
|
||||
import browser from "webextension-polyfill";
|
||||
import patchScript from "@/seqta/utils/seqtaMenuColourPatch.js?url";
|
||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||
|
||||
const PAGE_PATCH_LOADER_ID = "bsplus-seqta-menu-colour-patch-loader";
|
||||
|
||||
/** Remove empty or hidden modaliser shells left after colour dialog teardown. */
|
||||
export function dismissStaleModaliserContainers(): number {
|
||||
let removed = 0;
|
||||
for (const container of document.querySelectorAll(".modaliser-container")) {
|
||||
const modal = container.querySelector(".modaliser");
|
||||
const empty = !modal || modal.childElementCount === 0;
|
||||
const hidden = !container.classList.contains("visible");
|
||||
if (empty || hidden) {
|
||||
container.remove();
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/** Remove stuck SEQTA colour chooser slide panes that intercept timetable clicks. */
|
||||
export function dismissStaleColourSlidePanes(
|
||||
forceColourChooser = false,
|
||||
): number {
|
||||
let removed = 0;
|
||||
for (const pane of document.querySelectorAll(".uiSlidePane")) {
|
||||
const isColourChooser = pane.querySelector(".pane.colourChooser");
|
||||
if (isColourChooser) {
|
||||
pane.remove();
|
||||
removed++;
|
||||
continue;
|
||||
}
|
||||
if (!forceColourChooser && pane.classList.contains("shown")) continue;
|
||||
if (!pane.classList.contains("shown")) {
|
||||
pane.remove();
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
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 };
|
||||
}
|
||||
|
||||
export function installSeqtaMenuColourPatch(): void {
|
||||
if (document.getElementById(PAGE_PATCH_LOADER_ID)) return;
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.id = PAGE_PATCH_LOADER_ID;
|
||||
script.src = resolveExtensionAssetUrl(patchScript);
|
||||
script.addEventListener("load", () => script.remove());
|
||||
(document.documentElement || document.head).appendChild(script);
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
/**
|
||||
* PAGE context only — patches SEQTA menu.update.colours and removes stuck colour-dialog
|
||||
* layers (uiSlidePane + modaliser) that block timetable entry clicks after a colour save.
|
||||
*/
|
||||
(function () {
|
||||
if (window.__bsplusMenuColoursPatched) return;
|
||||
|
||||
var LOG = "[BetterSEQTA+] timetable colour:";
|
||||
var MENU_UPDATE_COLOURS = "menu.update.colours";
|
||||
var SUBJECT_COLOUR_PREF_PREFIX = "timetable.subject.colour.";
|
||||
var TUTOR_COLOUR_PREF_PREFIX = "timetable.tutor.";
|
||||
|
||||
function log(event, detail) {
|
||||
if (detail !== undefined) {
|
||||
console.info(LOG, event, detail);
|
||||
} else {
|
||||
console.info(LOG, event);
|
||||
}
|
||||
}
|
||||
|
||||
function isTesStylingEnabled() {
|
||||
var logoStyle = document.getElementById("logo-style");
|
||||
return logoStyle && logoStyle.textContent.indexOf("tesSeqta") !== -1;
|
||||
}
|
||||
|
||||
function countOverlayState() {
|
||||
return {
|
||||
slidePanes: document.querySelectorAll(".uiSlidePane").length,
|
||||
slidePanesShown: document.querySelectorAll(".uiSlidePane.shown").length,
|
||||
colourChoosers: document.querySelectorAll(
|
||||
".uiSlidePane .pane.colourChooser",
|
||||
).length,
|
||||
modalisers: document.querySelectorAll(".modaliser-container").length,
|
||||
modalisersVisible: document.querySelectorAll(
|
||||
".modaliser-container.visible",
|
||||
).length,
|
||||
quickbarsVisible: document.querySelectorAll(
|
||||
".timetablepage .quickbar.visible",
|
||||
).length,
|
||||
};
|
||||
}
|
||||
|
||||
function applyMenuSubjectColours() {
|
||||
if (!window.user) return;
|
||||
var defaultColour = isTesStylingEnabled() ? "#2b3547" : "#dddddd";
|
||||
var items = document.querySelectorAll("#menu li[data-colour]");
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
var prefName = item.getAttribute("data-colour");
|
||||
if (!prefName) continue;
|
||||
var pref = window.user.getPreference(prefName);
|
||||
var colour = (pref && pref.value) || defaultColour;
|
||||
item.style.setProperty("--item-colour", colour);
|
||||
}
|
||||
}
|
||||
|
||||
function dismissStaleModaliserContainers() {
|
||||
var removed = 0;
|
||||
var containers = document.querySelectorAll(".modaliser-container");
|
||||
for (var i = 0; i < containers.length; i++) {
|
||||
var container = containers[i];
|
||||
var modal = container.querySelector(".modaliser");
|
||||
var empty = !modal || modal.childElementCount === 0;
|
||||
var hidden = !container.classList.contains("visible");
|
||||
if (empty || hidden) {
|
||||
container.remove();
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
function dismissStaleColourSlidePanes(forceColourChooser) {
|
||||
var removed = 0;
|
||||
var panes = document.querySelectorAll(".uiSlidePane");
|
||||
for (var i = 0; i < panes.length; i++) {
|
||||
var pane = panes[i];
|
||||
var isColourChooser = pane.querySelector(".pane.colourChooser");
|
||||
if (isColourChooser) {
|
||||
pane.remove();
|
||||
removed++;
|
||||
continue;
|
||||
}
|
||||
if (!forceColourChooser && pane.classList.contains("shown")) continue;
|
||||
if (!pane.classList.contains("shown")) {
|
||||
pane.remove();
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
function dismissStaleColourDialogs(forceColourChooser) {
|
||||
var slideRemoved = dismissStaleColourSlidePanes(forceColourChooser);
|
||||
var modalRemoved = dismissStaleModaliserContainers();
|
||||
document.body.classList.remove("clr-open");
|
||||
document.documentElement.classList.remove("clr-open");
|
||||
return {
|
||||
slideRemoved: slideRemoved,
|
||||
modalRemoved: modalRemoved,
|
||||
overlays: countOverlayState(),
|
||||
};
|
||||
}
|
||||
|
||||
function reconcileStuckQuickbars(reason) {
|
||||
var fixed = 0;
|
||||
var quickbars = document.querySelectorAll(".timetablepage .quickbar.visible");
|
||||
for (var i = 0; i < quickbars.length; i++) {
|
||||
var qb = quickbars[i];
|
||||
var wrapper = qb.querySelector(".wrapper");
|
||||
if (!wrapper || !wrapper.childElementCount) {
|
||||
qb.classList.remove("visible");
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
if (fixed > 0) {
|
||||
log("cleared stuck quickbar shell (" + reason + ")", { fixed: fixed });
|
||||
try {
|
||||
window.msg.send("calendar.quickbar.hide");
|
||||
} catch (err) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return fixed;
|
||||
}
|
||||
|
||||
function findEntryElement(calendarId, code) {
|
||||
if (calendarId) {
|
||||
var byCalendar = document.querySelector(
|
||||
".timetablepage .entry[data-calendarid=\"" + calendarId + "\"]",
|
||||
);
|
||||
if (byCalendar) return byCalendar;
|
||||
}
|
||||
if (code) {
|
||||
var entries = document.querySelectorAll(".timetablepage .entry.class");
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = entries[i];
|
||||
var titleEl = entry.querySelector(".title");
|
||||
var title =
|
||||
titleEl && titleEl.textContent ? titleEl.textContent.trim() : "";
|
||||
if (title && title.indexOf(code) !== -1) return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeQuickbarOpenContext(contents) {
|
||||
if (!contents) return contents;
|
||||
|
||||
reconcileStuckQuickbars("before-open");
|
||||
|
||||
var element = contents.element;
|
||||
var calendarId =
|
||||
(element && element.getAttribute && element.getAttribute("data-calendarid")) ||
|
||||
(contents.data && contents.data.calendarid);
|
||||
var code = contents.data && contents.data.code;
|
||||
var connected =
|
||||
element &&
|
||||
element.isConnected &&
|
||||
typeof document.contains === "function" &&
|
||||
document.contains(element);
|
||||
|
||||
if (!connected) {
|
||||
var replacement = findEntryElement(calendarId, code);
|
||||
if (replacement) {
|
||||
contents.element = replacement;
|
||||
log("replaced detached quickbar entry element", {
|
||||
calendarId: calendarId,
|
||||
code: code,
|
||||
});
|
||||
} else {
|
||||
log("quickbar entry element detached, no replacement found", {
|
||||
calendarId: calendarId,
|
||||
code: code,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
function logQuickbarOpenResult(phase) {
|
||||
var qb = document.querySelector(".timetablepage .quickbar.visible");
|
||||
var wrapper = qb && qb.querySelector(".wrapper");
|
||||
log("quickbar open result (" + phase + ")", {
|
||||
visible: !!qb,
|
||||
hasWrapper: !!wrapper,
|
||||
wrapperChildren: wrapper ? wrapper.childElementCount : 0,
|
||||
overlays: countOverlayState(),
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleColourDialogCleanup(reason) {
|
||||
var delays = [0, 100, 300, 600];
|
||||
for (var i = 0; i < delays.length; i++) {
|
||||
(function (delay) {
|
||||
setTimeout(function () {
|
||||
var result = dismissStaleColourDialogs(true);
|
||||
if (
|
||||
result.slideRemoved > 0 ||
|
||||
result.modalRemoved > 0 ||
|
||||
delay === 0
|
||||
) {
|
||||
log("cleanup (" + reason + ", +" + delay + "ms)", result);
|
||||
}
|
||||
}, delay);
|
||||
})(delays[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function isSubjectOrTutorColourPref(handle) {
|
||||
return (
|
||||
typeof handle === "string" &&
|
||||
(handle.indexOf(SUBJECT_COLOUR_PREF_PREFIX) === 0 ||
|
||||
handle.indexOf(TUTOR_COLOUR_PREF_PREFIX) === 0)
|
||||
);
|
||||
}
|
||||
|
||||
function runMenuColourUpdate() {
|
||||
log("menu.update.colours intercepted", countOverlayState());
|
||||
try {
|
||||
applyMenuSubjectColours();
|
||||
} catch (err) {
|
||||
console.error("[BetterSEQTA+] menu.update.colours failed:", err);
|
||||
}
|
||||
scheduleColourDialogCleanup("menu.update.colours");
|
||||
reconcileStuckQuickbars("after-colour-save");
|
||||
}
|
||||
|
||||
function fixedMenuColourHandler() {
|
||||
runMenuColourUpdate();
|
||||
}
|
||||
|
||||
function neutralizeBrokenMenuColourListeners(msg) {
|
||||
var listeners = msg.listeners && msg.listeners[MENU_UPDATE_COLOURS];
|
||||
if (!listeners) return;
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i]) {
|
||||
listeners[i].fn = fixedMenuColourHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function patchMsg(msg) {
|
||||
if (!msg || msg.__bsplusPatched) return;
|
||||
|
||||
var originalSend = msg.send.bind(msg);
|
||||
msg.send = function (handle, contents, suppressLogs, noRecord) {
|
||||
if (handle === MENU_UPDATE_COLOURS) {
|
||||
runMenuColourUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSubjectOrTutorColourPref(handle)) {
|
||||
log("colour pref save detected", {
|
||||
pref: handle,
|
||||
colour: contents,
|
||||
overlays: countOverlayState(),
|
||||
});
|
||||
var prefResult = originalSend(
|
||||
handle,
|
||||
contents,
|
||||
suppressLogs,
|
||||
noRecord,
|
||||
);
|
||||
scheduleColourDialogCleanup("pref:" + handle);
|
||||
reconcileStuckQuickbars("after-colour-save");
|
||||
return prefResult;
|
||||
}
|
||||
|
||||
if (handle === "calendar.quickbar.class") {
|
||||
var openContext = normalizeQuickbarOpenContext(contents);
|
||||
var label =
|
||||
openContext &&
|
||||
openContext.data &&
|
||||
(openContext.data.description || openContext.data.code);
|
||||
log("quickbar open msg.send", {
|
||||
subject: label,
|
||||
elementConnected:
|
||||
openContext &&
|
||||
openContext.element &&
|
||||
openContext.element.isConnected,
|
||||
overlays: countOverlayState(),
|
||||
});
|
||||
var openResult;
|
||||
try {
|
||||
openResult = originalSend(
|
||||
handle,
|
||||
openContext,
|
||||
suppressLogs,
|
||||
noRecord,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("[BetterSEQTA+] quickbar open failed:", err);
|
||||
throw err;
|
||||
}
|
||||
setTimeout(function () {
|
||||
logQuickbarOpenResult("+50ms");
|
||||
}, 50);
|
||||
setTimeout(function () {
|
||||
logQuickbarOpenResult("+200ms");
|
||||
}, 200);
|
||||
return openResult;
|
||||
}
|
||||
|
||||
if (handle === "calendar.quickbar.hide") {
|
||||
log("quickbar hide msg.send", countOverlayState());
|
||||
return originalSend(handle, contents, suppressLogs, noRecord);
|
||||
}
|
||||
|
||||
return originalSend(handle, contents, suppressLogs, noRecord);
|
||||
};
|
||||
|
||||
var originalRegister = msg.register.bind(msg);
|
||||
msg.register = function (handle, callback, clear, ignoreHistory) {
|
||||
if (handle === MENU_UPDATE_COLOURS) {
|
||||
return originalRegister(
|
||||
handle,
|
||||
fixedMenuColourHandler,
|
||||
clear,
|
||||
ignoreHistory,
|
||||
);
|
||||
}
|
||||
if (handle === "calendar.quickbar.class") {
|
||||
return originalRegister(
|
||||
handle,
|
||||
function (context) {
|
||||
log("quickbar class handler invoked", {
|
||||
elementConnected:
|
||||
context &&
|
||||
context.element &&
|
||||
context.element.isConnected,
|
||||
subject:
|
||||
context &&
|
||||
context.data &&
|
||||
(context.data.description || context.data.code),
|
||||
});
|
||||
return callback(context);
|
||||
},
|
||||
clear,
|
||||
ignoreHistory,
|
||||
);
|
||||
}
|
||||
return originalRegister(handle, callback, clear, ignoreHistory);
|
||||
};
|
||||
|
||||
neutralizeBrokenMenuColourListeners(msg);
|
||||
msg.__bsplusPatched = true;
|
||||
}
|
||||
|
||||
function tryPatch() {
|
||||
if (window.__bsplusMenuColoursPatched) return true;
|
||||
if (!window.msg || !window.msg.send) return false;
|
||||
patchMsg(window.msg);
|
||||
window.__bsplusMenuColoursPatched = true;
|
||||
log("patch active");
|
||||
return true;
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
"click",
|
||||
function (event) {
|
||||
var target = event.target;
|
||||
if (!target || !target.closest) return;
|
||||
var entry = target.closest(".timetablepage .entry");
|
||||
if (!entry) return;
|
||||
|
||||
var before = countOverlayState();
|
||||
var cleanup = dismissStaleColourDialogs(false);
|
||||
var calendarId = entry.getAttribute("data-calendarid");
|
||||
var instance = entry.getAttribute("data-instance");
|
||||
var titleEl = entry.querySelector(".title");
|
||||
var title = titleEl && titleEl.textContent ? titleEl.textContent.trim() : "";
|
||||
log("entry click (capture)", {
|
||||
calendarId: calendarId,
|
||||
instance: instance,
|
||||
title: title,
|
||||
before: before,
|
||||
cleanup: cleanup,
|
||||
});
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
if (!tryPatch()) {
|
||||
var interval = setInterval(function () {
|
||||
if (tryPatch()) {
|
||||
clearInterval(interval);
|
||||
} else if (window.msg) {
|
||||
neutralizeBrokenMenuColourListeners(window.msg);
|
||||
}
|
||||
}, 25);
|
||||
setTimeout(function () {
|
||||
clearInterval(interval);
|
||||
}, 120000);
|
||||
}
|
||||
})();
|
||||
@@ -1,12 +1,45 @@
|
||||
/**
|
||||
* SEQTA timetable uses Coloris for subject colours. Extension CSS previously
|
||||
* unset Coloris ::after animations, which left the picker unable to reopen.
|
||||
* This module clears stuck `clr-open` / hidden picker state after each use.
|
||||
* SEQTA timetable colour picker (Coloris) recovery and click-blocker cleanup.
|
||||
* Subject colour saves trigger SEQTA's broken menu.update.colours handler — see
|
||||
* patchSeqtaMenuUpdateColours.ts.
|
||||
*/
|
||||
|
||||
let attached = false;
|
||||
import { dismissStaleColourDialogs } from "@/seqta/utils/patchSeqtaMenuUpdateColours";
|
||||
|
||||
export function resetStuckColorisPicker(): void {
|
||||
let attached = false;
|
||||
let dismissTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const DISMISS_DELAY_MS = 100;
|
||||
|
||||
function scheduleDismiss(): void {
|
||||
if (dismissTimer !== null) clearTimeout(dismissTimer);
|
||||
dismissTimer = setTimeout(() => {
|
||||
dismissTimer = null;
|
||||
dismissTimetableUiBlockers();
|
||||
}, DISMISS_DELAY_MS);
|
||||
}
|
||||
|
||||
/** Hide colour-picker / modal layers that intercept clicks after a colour save. */
|
||||
export function dismissTimetableUiBlockers(): {
|
||||
slideRemoved: number;
|
||||
modalRemoved: number;
|
||||
} {
|
||||
document.body.style.removeProperty("overflow");
|
||||
|
||||
for (const picker of document.querySelectorAll(".clr-picker")) {
|
||||
picker.classList.remove("clr-open");
|
||||
if (picker instanceof HTMLElement) {
|
||||
picker.style.display = "none";
|
||||
picker.style.pointerEvents = "none";
|
||||
picker.style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
return dismissStaleColourDialogs();
|
||||
}
|
||||
|
||||
/** Clear inline styles that can prevent Coloris from reopening. */
|
||||
export function prepareColorisPickerOpen(): void {
|
||||
document.body.classList.remove("clr-open");
|
||||
document.documentElement.classList.remove("clr-open");
|
||||
|
||||
@@ -18,29 +51,44 @@ export function resetStuckColorisPicker(): void {
|
||||
picker.style.removeProperty("visibility");
|
||||
}
|
||||
}
|
||||
|
||||
for (const field of document.querySelectorAll(".clr-field")) {
|
||||
field.classList.remove("clr-open");
|
||||
}
|
||||
}
|
||||
|
||||
export function attachTimetableColorisRecovery(): void {
|
||||
if (attached) return;
|
||||
attached = true;
|
||||
|
||||
const afterColorisEvent = () => {
|
||||
requestAnimationFrame(() => resetStuckColorisPicker());
|
||||
};
|
||||
|
||||
document.addEventListener("coloris:pick", afterColorisEvent);
|
||||
document.addEventListener("coloris:close", afterColorisEvent);
|
||||
document.addEventListener("coloris:close", scheduleDismiss);
|
||||
document.addEventListener("coloris:pick", scheduleDismiss);
|
||||
|
||||
document.addEventListener(
|
||||
"click",
|
||||
(event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (!target.closest(".timetablepage [title='Choose a colour']")) return;
|
||||
resetStuckColorisPicker();
|
||||
if (!target.closest(".timetablepage")) return;
|
||||
|
||||
if (target.closest("[title='Choose a colour']")) {
|
||||
if (dismissTimer !== null) {
|
||||
clearTimeout(dismissTimer);
|
||||
dismissTimer = null;
|
||||
}
|
||||
prepareColorisPickerOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target.closest(".entry")) return;
|
||||
|
||||
const pickerOpen =
|
||||
document.body.classList.contains("clr-open") &&
|
||||
document.querySelector(".clr-picker.clr-open");
|
||||
if (!pickerOpen) {
|
||||
const result = dismissTimetableUiBlockers();
|
||||
if (result.slideRemoved > 0 || result.modalRemoved > 0) {
|
||||
console.info(
|
||||
"[BetterSEQTA+] timetable colour: content-script cleanup",
|
||||
result,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user