mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
feat: search bar in settings & update changelog
This commit is contained in:
@@ -61,6 +61,7 @@
|
||||
let selectedBackgroundCategory = $state("All");
|
||||
let backgroundCategories = $state<string[]>([]);
|
||||
let storeSearchTerm = $state("");
|
||||
let settingsSearch = $state("");
|
||||
|
||||
let showDisclaimerModal = $state(false);
|
||||
let disclaimerCallbacks = $state<{ onConfirm: () => void; onCancel: () => void } | null>(null);
|
||||
@@ -142,6 +143,7 @@
|
||||
);
|
||||
|
||||
const sectionTitle = $derived.by(() => {
|
||||
if (activePage === "settings" && settingsSearch.trim()) return "Search results";
|
||||
if (activePage === "themes") return "Themes";
|
||||
if (activePage === "backgrounds") return "Backgrounds";
|
||||
return [...userNav, ...appNav].find((item) => item.id === activeSection)?.label ?? "Settings";
|
||||
@@ -251,6 +253,7 @@
|
||||
|
||||
const selectNavItem = (id: string) => {
|
||||
if (activePage === "settings") {
|
||||
settingsSearch = "";
|
||||
activeSection = id;
|
||||
return;
|
||||
}
|
||||
@@ -281,6 +284,7 @@
|
||||
const applyDestination = (destination: SettingsDestination) => {
|
||||
activePage = destination.page;
|
||||
if (destination.page === "settings" && destination.section) {
|
||||
settingsSearch = "";
|
||||
activeSection = destination.section;
|
||||
} else if (destination.page === "themes" && destination.view) {
|
||||
activeThemeView = destination.view === "store" ? "theme-store" : "theme-settings";
|
||||
@@ -373,6 +377,32 @@
|
||||
: 'w-[260px] px-4 py-5'}"
|
||||
aria-label="Settings categories"
|
||||
>
|
||||
{#if activePage === "settings"}
|
||||
<label class="relative mb-4 block shrink-0">
|
||||
<span class="sr-only">Search settings</span>
|
||||
<svg
|
||||
class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-zinc-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m21 21-4.35-4.35m1.35-5.65a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"
|
||||
/>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search settings"
|
||||
bind:value={settingsSearch}
|
||||
class="h-10 w-full rounded-lg bg-zinc-200/70 pl-9 pr-3 text-sm text-zinc-900 placeholder:text-zinc-400 transition-colors duration-200 focus:bg-white focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:bg-zinc-800/80 dark:text-white dark:placeholder:text-zinc-500 dark:focus:bg-zinc-800 dark:focus:ring-offset-zinc-900"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<SidebarNav
|
||||
groups={navGroups}
|
||||
selectedId={selectedNavId}
|
||||
@@ -455,7 +485,7 @@
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 px-4 pb-8 overflow-y-auto no-scrollbar">
|
||||
{#if activePage === "settings"}
|
||||
{#if activeSection === "shortcuts"}
|
||||
{#if activeSection === "shortcuts" && !settingsSearch.trim()}
|
||||
<Shortcuts />
|
||||
{:else}
|
||||
<Settings
|
||||
@@ -463,8 +493,12 @@
|
||||
showFontPicker={openFontPicker}
|
||||
{showDisclaimer}
|
||||
showCloudPanel={openCloudPanel}
|
||||
{activeSection}
|
||||
activeSection={settingsSearch.trim() ? "all" : activeSection}
|
||||
searchQuery={settingsSearch}
|
||||
/>
|
||||
{#if settingsSearch.trim()}
|
||||
<Shortcuts searchQuery={settingsSearch} />
|
||||
{/if}
|
||||
{/if}
|
||||
{:else if activePage === "themes"}
|
||||
<Theme section="themes" />
|
||||
|
||||
@@ -136,12 +136,13 @@
|
||||
void loadPluginSettings();
|
||||
});
|
||||
|
||||
const { showColourPicker, showFontPicker, showDisclaimer, showCloudPanel, activeSection = "general" } = $props<{
|
||||
const { showColourPicker, showFontPicker, showDisclaimer, showCloudPanel, activeSection = "general", searchQuery = "" } = $props<{
|
||||
showColourPicker: () => void;
|
||||
showFontPicker: () => void;
|
||||
showDisclaimer: (onConfirm: () => void, onCancel: () => void, title?: string, message?: string) => void;
|
||||
showCloudPanel: () => void;
|
||||
activeSection?: string;
|
||||
searchQuery?: string;
|
||||
}>();
|
||||
|
||||
/** Map each plugin into the settings sidebar category that fits its purpose. */
|
||||
@@ -167,6 +168,13 @@
|
||||
activeSection === "all" ||
|
||||
(pluginSectionById[pluginId] ?? "features") === activeSection;
|
||||
|
||||
const matchesSearch = (...parts: Array<string | undefined>) => {
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
return !q || parts.some((part) => part?.toLowerCase().includes(q));
|
||||
};
|
||||
|
||||
const pluginHit = (plugin: Plugin) =>
|
||||
matchesSearch(plugin.name, plugin.description, ...Object.values(plugin.settings).flatMap((s) => [s.title, s.description]));
|
||||
|
||||
async function exportCloudSettingsJsonToFile() {
|
||||
const payload = await getSnapshotForUpload();
|
||||
@@ -183,6 +191,7 @@
|
||||
</script>
|
||||
|
||||
{#snippet Setting({ title, description, Component, props }: SettingsList) }
|
||||
{#if matchesSearch(title, description)}
|
||||
<div class="flex justify-between items-center px-5 py-5">
|
||||
<div class="pr-5">
|
||||
<h2 class="text-xl font-bold">{title}</h2>
|
||||
@@ -192,6 +201,7 @@
|
||||
<Component {...props} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div class="flex flex-col divide-y divide-zinc-100 dark:divide-zinc-700">
|
||||
@@ -204,6 +214,7 @@
|
||||
props: {}
|
||||
})}
|
||||
|
||||
{#if matchesSearch("BetterSEQTA Cloud", "Account & sync")}
|
||||
<div class="border-none">
|
||||
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
|
||||
<div class="flex justify-between items-center px-5 py-4">
|
||||
@@ -223,6 +234,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if showsSection("general")}
|
||||
{#each [
|
||||
@@ -354,12 +366,13 @@
|
||||
{@render Setting(option)}
|
||||
{/each}
|
||||
|
||||
{#if !isEngage}
|
||||
{#if !isEngage && matchesSearch("Sidebar Style", "Item Size", "Corner Radius", "Active Indicator", "Sidebar Width", "Transparency Effects", "Blur Strength")}
|
||||
<div class="border-none">
|
||||
<SidebarAppearance />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if matchesSearch("Adaptive Theme Colour", "Soft Gradient", "Smooth colour transition")}
|
||||
<div class="border-none">
|
||||
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
|
||||
<div class="flex justify-between items-center px-5 py-4">
|
||||
@@ -403,8 +416,10 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if showsSection("general")}
|
||||
{#if matchesSearch("Home Page Assessments", "Include Past Assessments", "Maximum Subjects", "Maximum Assessments per Subject")}
|
||||
<div class="border-none">
|
||||
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
|
||||
<div class="flex justify-between items-center px-5 py-4">
|
||||
@@ -464,9 +479,10 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#each pluginSettings as plugin (plugin.pluginId)}
|
||||
{#if pluginBelongsInSection(plugin.pluginId)}
|
||||
{#if pluginBelongsInSection(plugin.pluginId) && pluginHit(plugin)}
|
||||
<div class="border-none">
|
||||
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40 {!(plugin as any).disableToggle && Object.keys(plugin.settings).length === 0 ? 'hidden' : ''}">
|
||||
<!-- Always show enable toggle if disableToggle is true -->
|
||||
@@ -596,7 +612,18 @@
|
||||
}
|
||||
})}
|
||||
|
||||
{#if $settingsState.devMode}
|
||||
{#if $settingsState.devMode && matchesSearch(
|
||||
"Developer Mode",
|
||||
"Verbose logging",
|
||||
"Delay loading screen",
|
||||
"Sensitive Hider",
|
||||
"Mock Notices",
|
||||
"Show Privacy Notification",
|
||||
"Show Theme of the Month",
|
||||
"Export cloud settings JSON",
|
||||
"API Base URL",
|
||||
"GitHub latest version override",
|
||||
)}
|
||||
<div class="flex-col p-1 my-1 bg-gradient-to-br from-white rounded-xl border shadow-sm to-zinc-100 border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
|
||||
<div class="flex justify-between items-center px-5 py-4">
|
||||
<div class="pr-4">
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
import { onMount } from 'svelte';
|
||||
import Shortcuts from "@/seqta/content/links.json"
|
||||
|
||||
let { searchQuery = "" } = $props<{ searchQuery?: string }>();
|
||||
const q = $derived(searchQuery.trim().toLowerCase());
|
||||
|
||||
let isLoaded = $state(false);
|
||||
let fileInput = $state<HTMLInputElement | null>(null);
|
||||
|
||||
@@ -106,6 +109,7 @@
|
||||
|
||||
<div class="flex flex-col pt-4 divide-y divide-zinc-100 dark:divide-zinc-700">
|
||||
{#if isLoaded}
|
||||
{#if !q}
|
||||
<div>
|
||||
<MotionDiv
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
@@ -207,9 +211,11 @@
|
||||
</button>
|
||||
</MotionDiv>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Custom Shortcuts Section -->
|
||||
{#each ($settingsState.customshortcuts ?? []) as shortcut, index}
|
||||
{#if !q || shortcut.name.toLowerCase().includes(q)}
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
{shortcut.name}
|
||||
<button aria-label="Delete Shortcut" onclick={() => deleteCustomShortcut(index)}>
|
||||
@@ -218,9 +224,11 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#each Object.entries(Shortcuts) as shortcut}
|
||||
{#if !q || shortcut[1].DisplayName?.toLowerCase().includes(q) || shortcut[0].toLowerCase().includes(q)}
|
||||
<div class="flex justify-between items-center px-4 py-3">
|
||||
<div class="pr-4">
|
||||
<!-- Use DisplayName if it exists, otherwise use the key (shortcut[0]) as a fallback -->
|
||||
@@ -228,6 +236,7 @@
|
||||
</div>
|
||||
<Switch state={$settingsState.shortcuts.find(s => s.name === shortcut[0])?.enabled ?? false} onChange={() => switchChange(shortcut[0])} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="p-4 text-center">
|
||||
|
||||
@@ -15,6 +15,7 @@ export const WHATS_NEW_CHANGELOG: WhatsNewRelease[] = [
|
||||
"Removed white background around SEQTA Learn mobile App icons in settings.",
|
||||
"Re-added the kitten back to the 404 Error pages.",
|
||||
"Improved the sidebar to be more stable and performant.",
|
||||
"Fixed Analytics page not respecting no animations setting. (#474)",
|
||||
"Fixed dropdown contrast and readability in settings and across SEQTA pages.",
|
||||
"Fixed Analytics sidebar item not hiding when toggled off in Edit Sidebar.",
|
||||
"Fixed timetable subject colour picker not reopening after closing (#221).",
|
||||
|
||||
Reference in New Issue
Block a user