From 928f05ec8e23c00ecff45a7917f7bd924adf095f Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Fri, 24 Jul 2026 10:08:03 +0930 Subject: [PATCH] feat: new settings popup --- src/css/injected.scss | 8 + src/css/injected/popup.scss | 26 +- .../components/themes/ThemeSelector.svelte | 3 +- src/interface/pages/settings.svelte | 342 +++++++++--- src/interface/pages/settings/general.svelte | 501 ++++++++++-------- src/plugins/monofile.ts | 13 +- src/seqta/ui/AddBetterSEQTAElements.ts | 62 ++- src/seqta/ui/SettingsResizer.ts | 12 +- src/seqta/ui/renderStore.ts | 8 +- .../utils/Adders/AddExtensionSettings.ts | 13 +- .../utils/Closers/closeExtensionPopup.ts | 2 - src/seqta/utils/setupSettingsButton.ts | 26 +- 12 files changed, 662 insertions(+), 354 deletions(-) diff --git a/src/css/injected.scss b/src/css/injected.scss index fd6a59ae..71195bed 100644 --- a/src/css/injected.scss +++ b/src/css/injected.scss @@ -274,6 +274,14 @@ select option { justify-content: center; width: 35px !important; } + +/* Engage toolbar: keep settings/theme buttons above React chrome */ +.connectedNotificationsWrapper .addedButton, +.connectedNotificationsWrapper #AddedSettings, +.connectedNotificationsWrapper #LightDarkModeButton { + z-index: 50 !important; + pointer-events: auto !important; +} [style="background-color: rgb(255, 255, 255);"] { color: black; } diff --git a/src/css/injected/popup.scss b/src/css/injected/popup.scss index 5ecf0385..e012349f 100644 --- a/src/css/injected/popup.scss +++ b/src/css/injected/popup.scss @@ -14,11 +14,12 @@ .outside-container { margin: 0; overflow: hidden; - position: absolute; - right: 10px; - top: 80px; - height: 590px; - z-index: 100; + position: fixed; + inset: 0; + width: 100%; + height: 100%; + /* Above Engage React chrome / MUI layers; below rare fullscreen media overlays */ + z-index: 200000; transition-duration: 100ms; } @@ -31,12 +32,11 @@ } #ExtensionPopup { - border-radius: 1rem; - filter: drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.6)); - transform-origin: 70% 0; - will-change: opacity, transform; - transform: translateZ(0); // promotes GPU rendering - transition: - opacity 0.05s, - transform 0.05s; + border-radius: 0; + filter: none; + /* Avoid transform on the host — it makes shadow `position: fixed` children + size to the host instead of the viewport on Engage stacking contexts. */ + transform-origin: center center; + will-change: opacity; + transition: opacity 0.05s; } diff --git a/src/interface/components/themes/ThemeSelector.svelte b/src/interface/components/themes/ThemeSelector.svelte index 1599bbae..8723139e 100644 --- a/src/interface/components/themes/ThemeSelector.svelte +++ b/src/interface/components/themes/ThemeSelector.svelte @@ -128,7 +128,8 @@ const openStorePage = async () => { const { OpenStorePage } = await import('@/seqta/ui/renderStore') - OpenStorePage() + closeExtensionPopup() + await OpenStorePage() } const openThemeCreator = async (themeId?: string) => { diff --git a/src/interface/pages/settings.svelte b/src/interface/pages/settings.svelte index 7b3d0c25..23abc356 100644 --- a/src/interface/pages/settings.svelte +++ b/src/interface/pages/settings.svelte @@ -1,5 +1,4 @@ -
-
selectSection(item.id)} + class="w-full px-3 py-2 text-left text-base rounded-lg transition-all duration-200 + {activePage === 'settings' && activeSection === item.id + ? 'bg-zinc-200/80 dark:bg-zinc-700/80 text-zinc-900 dark:text-white font-medium' + : 'text-zinc-600 dark:text-zinc-300 hover:bg-zinc-200/50 dark:hover:bg-zinc-700/40 hover:text-zinc-900 dark:hover:text-white'}" > + {item.label} + +{/snippet} + +{#snippet settingsShell()} +
+
Light logo +
+ {#each pages as page (page.id)} + + {/each} +
+ {#if !standalone} -
+
{#if ghReleaseUpdateEnabled} -
+
{#if ghReleaseUpdate?.available} {/if}

{#if ghReleaseChannelLabel} - {ghReleaseChannelLabel} — do not upload to extension stores. + {ghReleaseChannelLabel} — do not upload to stores. {:else} - GitHub release build — do not upload to extension stores. + GitHub build — do not upload to stores. {/if}

{/if} -
- - + -
{/if}
-
- + +
+ + +
+
+

+ {sectionTitle} +

+
+
+ {#if activePage === "settings"} + + {:else if activePage === "shortcuts"} + + {:else} + + {/if} +
+
+{/snippet} - {#if showColourPicker && ColourPickerComponent} - { - showColourPicker = false; - }} - /> - {/if} +{#if standalone} +
+ {@render settingsShell()} +
+{:else} + +
+ {@render settingsShell()} +
+
+{/if} + +{#if showColourPicker && ColourPickerComponent} + { + showColourPicker = false; + }} + /> +{/if} + +{#if showCloudPanel} + { + showCloudPanel = false; + }} + /> +{/if} {#if showFontPicker} void; showFontPicker: () => void; showDisclaimer: (onConfirm: () => void, onCancel: () => void, title?: string, message?: string) => void; showCloudPanel: () => void; + activeSection?: string; }>(); + const activePluginId = $derived( + activeSection.startsWith("plugin:") + ? activeSection.slice("plugin:".length) + : null, + ); + async function exportCloudSettingsJsonToFile() { const payload = await getSnapshotForUpload(); const blob = new Blob([JSON.stringify(payload, null, 2)], { @@ -155,153 +164,244 @@ {#snippet Setting({ title, description, Component, props }: SettingsList) } -
-
-

{title}

-

{description}

+
+
+

{title}

+

{description}

-
+
{/snippet}
- {#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

+ {/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.