- {#each [
- {
+ {#if activeSection === "account"}
+ {@render Setting({
title: "Connect Mobile App",
description: "Link your SEQTA session to DesQTA — the modern desktop and mobile app for SEQTA Learn",
id: 0,
Component: ConnectMobileApp,
props: {}
- },
- {
- title: "Edit Sidebar Layout",
- description: "Reorder pages on the sidebar",
- id: 5,
- Component: Button,
- props: {
- onClick: () => browser.runtime.sendMessage({ type: 'currentTab', info: 'EditSidebar' }),
- text: "Edit"
- }
- },
- {
- title: "Custom Theme Colour",
- description: "Customise the overall theme colour of SEQTA Learn",
- id: 4,
- Component: PickerSwatch,
- props: {
- onClick: showColourPicker
- }
- },
- {
- title: "Interface Font",
- description: "Choose the typeface used across SEQTA Learn",
- id: 16,
- Component: Button,
- props: {
- onClick: showFontPicker,
- text: "Change"
- }
- },
- {
- title: "Icon Only Sidebar",
- description: "Show only icons in the sidebar for a compact layout",
- id: 14,
- Component: Switch,
- props: {
- state: $settingsState.iconOnlySidebar ?? false,
- onChange: (isOn: boolean) => settingsState.iconOnlySidebar = isOn
- }
- },
- {
- title: "Animations",
- description: "Enable animations on certain pages",
- id: 6,
- Component: Switch,
- props: {
- state: $settingsState.animations,
- onChange: (isOn: boolean) => settingsState.animations = isOn
- }
- },
- {
- title: "12 Hour Time",
- description: "Prefer 12 hour time format for SEQTA",
- id: 9,
- Component: Switch,
- props: {
- state: $settingsState.timeFormat === "12",
- onChange: (isOn: boolean) => settingsState.timeFormat = isOn ? "12" : "24"
- }
- },
- {
- title: "Transparency Effects",
- description: "Enable transparency effects on certain elements, such as blur (May impact battery life)",
- id: 1,
- Component: Switch,
- props: {
- state: $settingsState.transparencyEffects,
- onChange: (isOn: boolean) => settingsState.transparencyEffects = isOn
- }
- },
- {
- title: "Default Page",
- description: "Choose which page loads first when you open SEQTA",
- id: 10,
- Component: Select,
- props: {
- value: $settingsState.defaultPage ?? "home",
- onChange: (value: string) => (settingsState.defaultPage = value),
- options: [
- { value: "home", label: "Home" },
- { value: "dashboard", label: "Dashboard" },
- { value: "timetable", label: "Timetable" },
- { value: "welcome", label: "Welcome" },
- { value: "messages", label: "Messages" },
- { value: "documents", label: "Documents" },
- { value: "reports", label: "Reports" },
- ],
+ })}
+
+
+
+
+
+
BetterSEQTA Cloud
+
Account & sync
+
+
+
+
+
+ {#if cloudState.isLoggedIn}
+
+ showDisclaimer(onConfirm, onCancel, "Restore from cloud?", "This will replace your local settings with the cloud backup. Continue?")} />
+
+ {/if}
+
+
+ {/if}
+
+ {#if activeSection === "general"}
+ {#each [
+ ...(!isEngage
+ ? [
+ {
+ title: "Edit Sidebar Layout",
+ description: "Reorder pages on the sidebar",
+ id: 5,
+ Component: Button,
+ props: {
+ onClick: () => browser.runtime.sendMessage({ type: 'currentTab', info: 'EditSidebar' }),
+ text: "Edit"
+ }
+ },
+ {
+ title: "Icon Only Sidebar",
+ description: "Show only icons in the sidebar for a compact layout",
+ id: 14,
+ Component: Switch,
+ props: {
+ state: $settingsState.iconOnlySidebar ?? false,
+ onChange: (isOn: boolean) => settingsState.iconOnlySidebar = isOn
+ }
+ },
+ ]
+ : []),
+ {
+ title: "Animations",
+ description: "Enable animations on certain pages",
+ id: 6,
+ Component: Switch,
+ props: {
+ state: $settingsState.animations,
+ onChange: (isOn: boolean) => settingsState.animations = isOn
+ }
},
- },
- {
- title: "News Feed Source",
- description: "Choose the sources for your news feed",
- id: 11,
- Component: Select,
- props: {
- value: $settingsState.newsSource,
- onChange: (value: string) => settingsState.newsSource = value,
- options: [
- { value: "australia", label: "Australia" },
- { value: "usa", label: "USA" },
- { value: "uk", label: "UK" },
- { value: "taiwan", label: "Taiwan" },
- { value: "hong_kong", label: "Hong Kong" },
- { value: "panama", label: "Panama" },
- { value: "canada", label: "Canada" },
- { value: "singapore", label: "Singapore" },
- { value: "japan", label: "Japan" },
- { value: "netherlands", label: "Netherlands" }
- ]
+ {
+ title: "12 Hour Time",
+ description: "Prefer 12 hour time format for SEQTA",
+ id: 9,
+ Component: Switch,
+ props: {
+ state: $settingsState.timeFormat === "12",
+ onChange: (isOn: boolean) => settingsState.timeFormat = isOn ? "12" : "24"
+ }
+ },
+ {
+ title: "Transparency Effects",
+ description: "Enable transparency effects on certain elements, such as blur (May impact battery life)",
+ id: 1,
+ Component: Switch,
+ props: {
+ state: $settingsState.transparencyEffects,
+ onChange: (isOn: boolean) => settingsState.transparencyEffects = isOn
+ }
+ },
+ {
+ title: "Default Page",
+ description: "Choose which page loads first when you open SEQTA",
+ id: 10,
+ Component: Select,
+ props: {
+ value: $settingsState.defaultPage ?? "home",
+ onChange: (value: string) => (settingsState.defaultPage = value),
+ options: isEngage
+ ? [
+ { value: "home", label: "Home" },
+ { value: "dashboard", label: "Dashboard" },
+ { value: "timetable", label: "Timetable" },
+ { value: "messages", label: "Messages" },
+ { value: "documents", label: "Documents" },
+ { value: "reports", label: "Reports" },
+ ]
+ : [
+ { value: "home", label: "Home" },
+ { value: "dashboard", label: "Dashboard" },
+ { value: "timetable", label: "Timetable" },
+ { value: "welcome", label: "Welcome" },
+ { value: "messages", label: "Messages" },
+ { value: "documents", label: "Documents" },
+ { value: "reports", label: "Reports" },
+ ],
+ },
+ },
+ ...(!isEngage
+ ? [
+ {
+ title: "News Feed Source",
+ description: "Choose the sources for your news feed",
+ id: 11,
+ Component: Select,
+ props: {
+ value: $settingsState.newsSource,
+ onChange: (value: string) => settingsState.newsSource = value,
+ options: [
+ { value: "australia", label: "Australia" },
+ { value: "usa", label: "USA" },
+ { value: "uk", label: "UK" },
+ { value: "taiwan", label: "Taiwan" },
+ { value: "hong_kong", label: "Hong Kong" },
+ { value: "panama", label: "Panama" },
+ { value: "canada", label: "Canada" },
+ { value: "singapore", label: "Singapore" },
+ { value: "japan", label: "Japan" },
+ { value: "netherlands", label: "Netherlands" },
+ ],
+ },
+ },
+ ]
+ : []),
+ ] as option (option.id)}
+ {@render Setting(option)}
+ {/each}
+ {/if}
- }
- }
- ] as option}
- {@render Setting(option)}
- {/each}
+ {#if activeSection === "appearance"}
+ {#each [
+ {
+ title: "Custom Theme Colour",
+ description: "Customise the overall theme colour of SEQTA Learn",
+ id: 4,
+ Component: PickerSwatch,
+ props: {
+ onClick: showColourPicker
+ }
+ },
+ {
+ title: "Interface Font",
+ description: "Choose the typeface used across SEQTA Learn",
+ id: 16,
+ Component: Button,
+ props: {
+ onClick: showFontPicker,
+ text: "Change"
+ }
+ },
+ ] as option (option.id)}
+ {@render Setting(option)}
+ {/each}
+
+
+
+
+
Adaptive Theme Colour
+
Change the theme colour based on the current class (e.g. when viewing a course or assessments page)
+
+
+ settingsState.adaptiveThemeColour = isOn}
+ />
+
+
+ {#if $settingsState.adaptiveThemeColour}
+
+
+
Soft Gradient
+
Use a soft gradient instead of a solid colour when viewing a class
+
+
+ settingsState.adaptiveThemeGradient = isOn}
+ />
+
+
+
+
+
Smooth colour transition
+
Ease between class/subject colours when navigating instead of switching instantly
+
+
+ settingsState.adaptiveThemeColourTransition = isOn}
+ />
+
+
+ {/if}
+
+
+ {/if}
+
+ {#if activeSection === "home"}
-
+
-
Home Page Assessments
-
Limit upcoming assessments shown on the home page by subject
+
Home Page Assessments
+
Limit upcoming assessments shown on the home page by subject
-
+
-
Include Past Assessments
-
Show past-due assessments from the upcoming list, matching the Assessments page
+
Include Past Assessments
+
Show past-due assessments from the upcoming list, matching the Assessments page
-
+
-
Maximum Subjects
-
Number of subjects to include, ordered by soonest due date
+
Maximum Subjects
+
Number of subjects to include, ordered by soonest due date
-
+
-
Maximum Assessments per Subject
-
Assessments shown for each included subject
+
Maximum Assessments per Subject
+
Assessments shown for each included subject
+ {/if}
-
-
-
-
-
Adaptive Theme Colour
-
Change the theme colour based on the current class (e.g. when viewing a course or assessments page)
-
-
- settingsState.adaptiveThemeColour = isOn}
- />
-
-
- {#if $settingsState.adaptiveThemeColour}
-
-
-
Soft Gradient
-
Use a soft gradient instead of a solid colour when viewing a class
-
-
- settingsState.adaptiveThemeGradient = isOn}
- />
-
-
-
-
-
Smooth colour transition
-
Ease between class/subject colours when navigating instead of switching instantly
-
-
- settingsState.adaptiveThemeColourTransition = isOn}
- />
-
-
- {/if}
-
-
-
- {#each pluginSettings as plugin}
+ {#each pluginSettings as plugin (plugin.pluginId)}
+ {#if activePluginId === plugin.pluginId}
{#if (plugin as any).disableToggle}
-
+
-
+
Enable {plugin.name}
{#if plugin.beta}
@@ -407,7 +466,7 @@
{/if}
- {plugin.description}
+
{plugin.description}
{#if key !== 'enabled' && !(key === 'useCloudPfp' && !cloudState.isLoggedIn)}
-
+
-
{setting.title || key}
-
{setting.description || ''}
+
{setting.title || key}
+
{setting.description || ''}
{#if setting.type === 'boolean'}
@@ -507,29 +566,10 @@
})}
{/if}
+ {/if}
{/each}
-
-
-
-
-
BetterSEQTA Cloud
-
Account & sync
-
-
-
-
-
- {#if cloudState.isLoggedIn}
-
- showDisclaimer(onConfirm, onCancel, "Restore from cloud?", "This will replace your local settings with the cloud backup. Continue?")} />
-
- {/if}
-
-
-
-
-
+ {#if activeSection === "advanced"}
{@render Setting({
title: "BetterSEQTA+",
description: "Enables BetterSEQTA+ features",
@@ -543,19 +583,19 @@
{#if $settingsState.devMode}
-
+
-
Developer Mode
-
Enables developer mode, allowing you to test new features and changes.
+
Developer Mode
+
Enables developer mode, allowing you to test new features and changes.
settingsState.devMode = isOn} />
-
+
-
Verbose logging
-
Show diagnostic console output (indexer, theme manager, timetable colour patch, etc.)
+
Verbose logging
+
Show diagnostic console output (indexer, theme manager, timetable colour patch, etc.)
-
+
-
Sensitive Hider
-
Replace sensitive content with mock data
+
Sensitive Hider
+
Replace sensitive content with mock data
-
+
-
Mock Notices
-
Use fake notice data on homepage instead of real data
+
Mock Notices
+
Use fake notice data on homepage instead of real data
-
+
-
Show Privacy Notification
-
Show the privacy notification popup on next page load
+
Show Privacy Notification
+
Show the privacy notification popup on next page load
-
+
-
Show Theme of the Month
-
Fetch and show the current month's popup now (ignores dismissed state)
+
Show Theme of the Month
+
Fetch and show the current month's popup now (ignores dismissed state)
-
+
-
Export cloud settings JSON
-
Download the same payload as cloud sync (OAuth tokens stripped). For debugging and server testing.
+
Export cloud settings JSON
+
Download the same payload as cloud sync (OAuth tokens stripped). For debugging and server testing.
@@ -635,8 +675,8 @@
-
API Base URL (session only)
-
Override the content API host for this browser session. Cleared on restart. Affects themes, theme of the month, and other server-driven content.
+
API Base URL (session only)
+
Override the content API host for this browser session. Cleared on restart. Affects themes, theme of the month, and other server-driven content.
{#if devApiBaseActive}
Override active: {devApiBaseActive}
@@ -659,8 +699,8 @@
-
GitHub latest version override
-
Pretend a newer GitHub release exists to test the update badge. Only applies when dev mode is on.
+
GitHub latest version override
+
Pretend a newer GitHub release exists to test the update badge. Only applies when dev mode is on.
{/if}
+ {/if}
diff --git a/src/plugins/monofile.ts b/src/plugins/monofile.ts
index 5e12c330..334aff51 100644
--- a/src/plugins/monofile.ts
+++ b/src/plugins/monofile.ts
@@ -832,12 +832,17 @@ export function AppendElementsToDisabledPage() {
margin: 6px;
}
.outside-container {
- top: 48px !important;
+ position: fixed !important;
+ inset: 0 !important;
+ top: 0 !important;
+ width: 100% !important;
+ height: 100% !important;
+ z-index: 200000 !important;
}
#ExtensionPopup {
- border-radius: 1rem;
- box-shadow: 0px 0px 20px -2px rgba(0, 0, 0, 0.6);
- transform-origin: 70% 0;
+ border-radius: 0;
+ box-shadow: none;
+ transform-origin: center center;
}
`;
document.head.append(settingsStyle);
diff --git a/src/seqta/ui/AddBetterSEQTAElements.ts b/src/seqta/ui/AddBetterSEQTAElements.ts
index cc3e79d5..85b34b57 100644
--- a/src/seqta/ui/AddBetterSEQTAElements.ts
+++ b/src/seqta/ui/AddBetterSEQTAElements.ts
@@ -440,22 +440,56 @@ async function addEngageUserInfo() {
});
}
-async function setupEngageSettingsButton() {
- try {
- const notificationsWrapper = await waitForElm(
- "#content > div.connectedNotificationsWrapper > div",
- );
- const parent = notificationsWrapper.parentElement!;
+async function mountEngageToolbarControls(parent?: Element) {
+ // React can remount the notifications chrome — recreate missing controls.
+ if (!document.getElementById("LightDarkModeButton")) {
await addDarkLightToggle(parent);
- await createSettingsButton(parent);
- setupSettingsButton();
- attachNotificationsPanelAnimation();
- } catch {
- await addDarkLightToggle();
- await createSettingsButton();
- setupSettingsButton();
- attachNotificationsPanelAnimation();
}
+ if (!document.getElementById("AddedSettings")) {
+ await createSettingsButton(parent);
+ }
+ setupSettingsButton();
+ attachNotificationsPanelAnimation();
+}
+
+async function setupEngageSettingsButton() {
+ let mounting = false;
+
+ const tryMount = async () => {
+ if (mounting || document.getElementById("AddedSettings")) return;
+ mounting = true;
+ try {
+ try {
+ const notificationsWrapper = await waitForElm(
+ "#content > div.connectedNotificationsWrapper > div",
+ true,
+ 100,
+ 40,
+ );
+ const parent = notificationsWrapper.parentElement!;
+ await mountEngageToolbarControls(parent);
+ } catch {
+ await mountEngageToolbarControls();
+ }
+ } finally {
+ mounting = false;
+ }
+ };
+
+ await tryMount();
+
+ // Re-inject if Engage's React shell wipes the toolbar controls.
+ const content = document.getElementById("content");
+ if (!content || (content as HTMLElement).dataset.bsplusEngageSettingsWatch === "1") {
+ return;
+ }
+ (content as HTMLElement).dataset.bsplusEngageSettingsWatch = "1";
+
+ const observer = new MutationObserver(() => {
+ if (document.getElementById("AddedSettings")) return;
+ void tryMount();
+ });
+ observer.observe(content, { childList: true, subtree: true });
}
function GetLightDarkModeString() {
diff --git a/src/seqta/ui/SettingsResizer.ts b/src/seqta/ui/SettingsResizer.ts
index 4b1ad856..e6e6415f 100644
--- a/src/seqta/ui/SettingsResizer.ts
+++ b/src/seqta/ui/SettingsResizer.ts
@@ -1,7 +1,7 @@
import debounce from "@/seqta/utils/debounce";
/**
- * Automatically resizes the popup to fit the screen, checks on resize but is debounced to prevent intense utilisation.
+ * Keeps the settings overlay covering the viewport.
*/
export class SettingsResizer {
constructor() {
@@ -17,10 +17,10 @@ export class SettingsResizer {
const iframePopup = document.getElementById("ExtensionPopup");
if (!iframePopup) return;
- const viewportHeight = window.innerHeight;
- const rawIdeal = viewportHeight - 80 - 15; // room below top chrome
- const idealHeight = Math.min(Math.max(rawIdeal, 280), 600);
-
- iframePopup.style.height = `${idealHeight}px`;
+ iframePopup.style.inset = "0";
+ iframePopup.style.top = "0";
+ iframePopup.style.right = "0";
+ iframePopup.style.width = "100%";
+ iframePopup.style.height = "100%";
}
}
diff --git a/src/seqta/ui/renderStore.ts b/src/seqta/ui/renderStore.ts
index 6520646d..9d2d7b04 100644
--- a/src/seqta/ui/renderStore.ts
+++ b/src/seqta/ui/renderStore.ts
@@ -12,10 +12,10 @@ export async function renderStore() {
import("@/interface/pages/store.svelte"),
]);
- const container = document.querySelector("#container");
- if (!container) {
- throw new Error("Container not found");
- }
+ const container =
+ document.querySelector("#container") ??
+ document.getElementById("content") ??
+ document.body;
document.getElementById("store")?.remove();
diff --git a/src/seqta/utils/Adders/AddExtensionSettings.ts b/src/seqta/utils/Adders/AddExtensionSettings.ts
index d031561d..a73e54ee 100644
--- a/src/seqta/utils/Adders/AddExtensionSettings.ts
+++ b/src/seqta/utils/Adders/AddExtensionSettings.ts
@@ -13,12 +13,18 @@ function extensionOutsideClickHandler(extensionPopup: HTMLElement) {
if (!SettingsClicked) return;
if (!(event.target as HTMLElement).closest("#AddedSettings")) {
+ // Clicks inside the shadow tree retarget to the host — keep open.
if (event.target == extensionPopup) return;
changeSettingsClicked(closeExtensionPopup());
}
};
}
+/**
+ * Mount the settings host on `document.body` so `position: fixed` covers the
+ * viewport on both SEQTA Learn and SEQTA Engage (Engage often lacks `#container`
+ * or wraps the app in stacking contexts that clip in-app overlays).
+ */
export function addExtensionSettings() {
if (document.getElementById("ExtensionPopup")) return;
@@ -26,15 +32,12 @@ export function addExtensionSettings() {
extensionPopup.classList.add("outside-container", "hide");
extensionPopup.id = "ExtensionPopup";
- const extensionContainer =
- document.querySelector("#container") ?? document.getElementById("container");
- const mountParent = extensionContainer ?? document.body;
- mountParent.appendChild(extensionPopup);
+ document.body.appendChild(extensionPopup);
new SettingsResizer();
const handler = extensionOutsideClickHandler(extensionPopup);
- (extensionContainer ?? document.body).addEventListener("click", handler, false);
+ document.body.addEventListener("click", handler, false);
}
async function loadSettingsUi(extensionPopup: HTMLElement): Promise
{
diff --git a/src/seqta/utils/Closers/closeExtensionPopup.ts b/src/seqta/utils/Closers/closeExtensionPopup.ts
index a5cdb59e..c8d16a38 100644
--- a/src/seqta/utils/Closers/closeExtensionPopup.ts
+++ b/src/seqta/utils/Closers/closeExtensionPopup.ts
@@ -14,7 +14,6 @@ export const closeExtensionPopup = (extensionPopup?: HTMLElement) => {
animate(1, 0, {
onUpdate: (progress) => {
extensionPopup.style.opacity = Math.max(0, progress).toString();
- extensionPopup.style.transform = `scale(${Math.max(0, progress)})`;
},
type: "spring",
stiffness: 520,
@@ -22,7 +21,6 @@ export const closeExtensionPopup = (extensionPopup?: HTMLElement) => {
});
} else {
extensionPopup.style.opacity = "0";
- extensionPopup.style.transform = "scale(0)";
}
settingsPopup.triggerClose();
diff --git a/src/seqta/utils/setupSettingsButton.ts b/src/seqta/utils/setupSettingsButton.ts
index 4c9aa7d8..6f87eb52 100644
--- a/src/seqta/utils/setupSettingsButton.ts
+++ b/src/seqta/utils/setupSettingsButton.ts
@@ -10,22 +10,34 @@ import { delay } from "./delay";
export function setupSettingsButton() {
const AddedSettings = document.getElementById("AddedSettings");
- const extensionPopup = document.getElementById("ExtensionPopup");
- if (!AddedSettings || !extensionPopup) return;
+ if (!AddedSettings) return;
+
+ // Avoid stacking duplicate listeners if Engage remounts the toolbar.
+ if (AddedSettings.dataset.bsplusSettingsBound === "1") return;
+ AddedSettings.dataset.bsplusSettingsBound = "1";
AddedSettings.addEventListener("click", async () => {
+ // Re-query each click — Engage SPA navigations can recreate the host.
+ let extensionPopup = document.getElementById("ExtensionPopup");
+ if (!extensionPopup) {
+ const { addExtensionSettings } = await import("./Adders/AddExtensionSettings");
+ addExtensionSettings();
+ extensionPopup = document.getElementById("ExtensionPopup");
+ }
+ if (!extensionPopup) return;
+
if (SettingsClicked) {
- closeExtensionPopup(extensionPopup as HTMLElement);
+ closeExtensionPopup(extensionPopup);
} else {
await renderSettingsIfNeeded();
await delay(30);
-
+
+ extensionPopup.style.transform = "none";
if (settingsState.animations) {
animate(0, 1, {
onUpdate: (progress) => {
extensionPopup.style.opacity = progress.toString();
- extensionPopup.style.transform = `scale(${progress})`;
},
type: "spring",
stiffness: 280,
@@ -33,9 +45,7 @@ export function setupSettingsButton() {
});
} else {
extensionPopup.style.opacity = "1";
- extensionPopup.style.transform = "scale(1)";
- extensionPopup.style.transition =
- "opacity 0s linear, transform 0s linear";
+ extensionPopup.style.transition = "opacity 0s linear";
}
extensionPopup.classList.remove("hide");
changeSettingsClicked(true);