mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
feat: refine settings, stores, and message folders
This commit is contained in:
+2
-38
@@ -162,40 +162,6 @@ select option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#store {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
z-index: 9998;
|
|
||||||
animation: fadeIn 500ms forwards;
|
|
||||||
animation-delay: 200ms;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
&.hide {
|
|
||||||
animation: fadeOut 500ms forwards;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeOut {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .resizeBar {
|
.dark .resizeBar {
|
||||||
background-color: rgb(28 28 30);
|
background-color: rgb(28 28 30);
|
||||||
}
|
}
|
||||||
@@ -1817,12 +1783,10 @@ html.transparencyEffects
|
|||||||
-webkit-box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3);
|
-webkit-box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3);
|
||||||
box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
/* Not named `.border` — Tailwind’s utility of that name is injected into the
|
/* `.border` is also a public hook used by existing themes. */
|
||||||
page via content-script CSS in production and would paint a 1px outline. */
|
.home-container .border {
|
||||||
.bsplus-rounded {
|
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
border-width: 0;
|
|
||||||
}
|
}
|
||||||
.shortcut-container h2 {
|
.shortcut-container h2 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { onclick, label = "Close" } = $props<{
|
||||||
|
onclick: () => void;
|
||||||
|
label?: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
{onclick}
|
||||||
|
class="flex h-10 w-10 shrink-0 items-center justify-center text-zinc-500 transition-[color,transform] duration-150 hover:text-zinc-950 active:scale-[0.96] focus:outline-none dark:text-zinc-400 dark:hover:text-white"
|
||||||
|
aria-label={label}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path d="M6 6l12 12M18 6 6 18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Select from "@/interface/components/Select.svelte";
|
import Select from "@/interface/components/Select.svelte";
|
||||||
import Slider from "@/interface/components/Slider.svelte";
|
import Slider from "@/interface/components/Slider.svelte";
|
||||||
|
import Switch from "@/interface/components/Switch.svelte";
|
||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
import {
|
import {
|
||||||
DEFAULT_SIDEBAR_BLUR,
|
DEFAULT_SIDEBAR_BLUR,
|
||||||
@@ -187,16 +188,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" class:disabled={!transparencyOn}>
|
<div class="row">
|
||||||
|
<div class="copy">
|
||||||
|
<h3 class="row-title">Transparency Effects</h3>
|
||||||
|
<p class="row-desc">Use glass effects on supported surfaces (may impact battery life)</p>
|
||||||
|
</div>
|
||||||
|
<div class="control toggle">
|
||||||
|
<Switch
|
||||||
|
state={transparencyOn}
|
||||||
|
onChange={(value) => (settingsState.transparencyEffects = value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if transparencyOn}
|
||||||
|
<div class="row">
|
||||||
<div class="copy">
|
<div class="copy">
|
||||||
<h3 class="row-title">Blur Strength</h3>
|
<h3 class="row-title">Blur Strength</h3>
|
||||||
<p class="row-desc">
|
<p class="row-desc">Glass blur on the sidebar ({blur}px)</p>
|
||||||
{#if transparencyOn}
|
|
||||||
Glass blur on the sidebar ({blur}px)
|
|
||||||
{:else}
|
|
||||||
Enable Transparency Effects to use blur
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control slider">
|
<div class="control slider">
|
||||||
<Slider
|
<Slider
|
||||||
@@ -208,6 +217,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -538,10 +548,6 @@
|
|||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.disabled {
|
|
||||||
opacity: 0.55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy {
|
.copy {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@@ -574,4 +580,9 @@
|
|||||||
min-width: 10rem;
|
min-width: 10rem;
|
||||||
width: 11rem;
|
width: 11rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control.toggle {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
import SidebarNavItem from "./SidebarNavItem.svelte";
|
||||||
|
|
||||||
|
type NavGroup = {
|
||||||
|
label?: string;
|
||||||
|
items: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
divided?: boolean;
|
||||||
|
nested?: boolean;
|
||||||
|
expanded?: boolean;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
let { groups, selectedId, onselect } = $props<{
|
||||||
|
groups: NavGroup[];
|
||||||
|
selectedId: string;
|
||||||
|
onselect: (id: string) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let trackEl = $state<HTMLElement | null>(null);
|
||||||
|
let indicatorX = $state(0);
|
||||||
|
let indicatorY = $state(0);
|
||||||
|
let indicatorW = $state(0);
|
||||||
|
let indicatorH = $state(40);
|
||||||
|
let indicatorReady = $state(false);
|
||||||
|
|
||||||
|
const updateIndicator = () => {
|
||||||
|
if (!trackEl) return;
|
||||||
|
const item = trackEl.querySelector<HTMLElement>(`[data-nav-section="${selectedId}"]`);
|
||||||
|
if (!item) return;
|
||||||
|
const trackRect = trackEl.getBoundingClientRect();
|
||||||
|
const itemRect = item.getBoundingClientRect();
|
||||||
|
indicatorX = itemRect.left - trackRect.left;
|
||||||
|
indicatorY = itemRect.top - trackRect.top + trackEl.scrollTop;
|
||||||
|
indicatorW = itemRect.width;
|
||||||
|
indicatorH = itemRect.height;
|
||||||
|
indicatorReady = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
selectedId;
|
||||||
|
groups;
|
||||||
|
trackEl;
|
||||||
|
queueMicrotask(updateIndicator);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="relative flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto no-scrollbar"
|
||||||
|
bind:this={trackEl}
|
||||||
|
>
|
||||||
|
{#if indicatorReady}
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute left-0 top-0 z-0 rounded-lg bg-zinc-200/90 dark:bg-zinc-700/90"
|
||||||
|
style="transform: translate({indicatorX}px, {indicatorY}px); width: {indicatorW}px; height: {indicatorH}px; transition: {$settingsState.animations
|
||||||
|
? 'transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), width 0.28s cubic-bezier(0.22, 1, 0.36, 1), height 0.28s cubic-bezier(0.22, 1, 0.36, 1)'
|
||||||
|
: 'none'};"
|
||||||
|
></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each groups as group}
|
||||||
|
<div class="relative flex flex-col gap-1">
|
||||||
|
{#if group.label}
|
||||||
|
<p
|
||||||
|
class="relative z-10 mb-1.5 px-3 text-sm font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-500"
|
||||||
|
>
|
||||||
|
{group.label}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{#each group.items as item (item.id)}
|
||||||
|
{#if item.divided}
|
||||||
|
<div class="my-2 border-t border-zinc-200 dark:border-zinc-700 {item.nested ? 'ml-4' : ''}"></div>
|
||||||
|
{/if}
|
||||||
|
<SidebarNavItem
|
||||||
|
label={item.label}
|
||||||
|
active={selectedId === item.id}
|
||||||
|
nested={item.nested}
|
||||||
|
emphasized={item.expanded}
|
||||||
|
trackId={item.id}
|
||||||
|
onclick={() => onselect(item.id)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
label,
|
||||||
|
active = false,
|
||||||
|
nested = false,
|
||||||
|
emphasized = false,
|
||||||
|
trackId,
|
||||||
|
onclick,
|
||||||
|
} = $props<{
|
||||||
|
label: string;
|
||||||
|
active?: boolean;
|
||||||
|
nested?: boolean;
|
||||||
|
emphasized?: boolean;
|
||||||
|
trackId?: string;
|
||||||
|
onclick: () => void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-nav-section={trackId}
|
||||||
|
{onclick}
|
||||||
|
class="relative z-10 rounded-lg text-left transition-[color,background-color] duration-200
|
||||||
|
{nested
|
||||||
|
? 'ml-4 w-[calc(100%-1rem)] px-3 py-2 text-base'
|
||||||
|
: 'w-full px-3 py-2.5 text-lg'}
|
||||||
|
{active || emphasized
|
||||||
|
? 'font-medium text-zinc-900 dark:text-white'
|
||||||
|
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-300 dark:hover:text-white'}"
|
||||||
|
aria-current={active ? "page" : undefined}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.tab-width {
|
|
||||||
width: var(--tab-width);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MotionDiv from './MotionDiv.svelte';
|
import MotionDiv from './MotionDiv.svelte';
|
||||||
import './TabbedContainer.css';
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let { tabs, activeTab = $bindable(0) } = $props<{ tabs: { title: string, Content: any, props?: any }[]; activeTab?: number }>();
|
let { tabs, activeTab = $bindable(0) } = $props<{ tabs: { title: string, Content: any, props?: any }[]; activeTab?: number }>();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
.bsplus-font-picker-overlay {
|
.bsplus-font-picker-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 50000;
|
z-index: 200001;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -8,11 +8,14 @@
|
|||||||
const themeManager = ThemeManager.getInstance();
|
const themeManager = ThemeManager.getInstance();
|
||||||
|
|
||||||
type Background = { id: string; category: string; type: string; lowResUrl: string; highResUrl: string; name: string; description: string; featured?: boolean };
|
type Background = { id: string; category: string; type: string; lowResUrl: string; highResUrl: string; name: string; description: string; featured?: boolean };
|
||||||
let { searchTerm } = $props<{ searchTerm: string }>();
|
let { searchTerm, selectedCategory, onCategoriesChange } = $props<{
|
||||||
|
searchTerm: string;
|
||||||
|
selectedCategory: string;
|
||||||
|
onCategoriesChange: (categories: string[]) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
// Existing states
|
// Existing states
|
||||||
let backgrounds = $state<Background[]>([]);
|
let backgrounds = $state<Background[]>([]);
|
||||||
let selectedCategory = $state<string>('All');
|
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
let selectedBackground = $state<string | null>(null);
|
let selectedBackground = $state<string | null>(null);
|
||||||
let isLoading = $state<boolean>(true);
|
let isLoading = $state<boolean>(true);
|
||||||
@@ -33,6 +36,7 @@
|
|||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
backgrounds = data.backgrounds;
|
backgrounds = data.backgrounds;
|
||||||
|
onCategoriesChange([...new Set(backgrounds.map((background) => background.category))]);
|
||||||
debugInfo = `Loaded ${backgrounds.length} backgrounds`;
|
debugInfo = `Loaded ${backgrounds.length} backgrounds`;
|
||||||
await loadSavedBackgrounds();
|
await loadSavedBackgrounds();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -97,7 +101,6 @@
|
|||||||
})());
|
})());
|
||||||
|
|
||||||
let categories = $derived([...new Set(backgrounds.map(bg => bg.category))]);
|
let categories = $derived([...new Set(backgrounds.map(bg => bg.category))]);
|
||||||
|
|
||||||
// Background management functions
|
// Background management functions
|
||||||
async function saveBackgroundFromUrl(url: string, id: string, fileType: string): Promise<void> {
|
async function saveBackgroundFromUrl(url: string, id: string, fileType: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -162,41 +165,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full">
|
<div class="h-full overflow-auto">
|
||||||
<!-- Sidebar -->
|
|
||||||
<div class="p-4 w-64 h-full border-r border-zinc-200 dark:border-zinc-700">
|
|
||||||
<div class="mb-8">
|
|
||||||
<h2 class="mb-4 text-lg font-semibold">Categories</h2>
|
|
||||||
<nav class="space-y-2">
|
|
||||||
<button
|
|
||||||
class={`w-full px-4 py-2 text-left bg-transparent rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800 transition ${selectedCategory === 'All' ? 'bg-blue-100 dark:bg-zinc-800' : ''}`}
|
|
||||||
onclick={() => selectedCategory = 'All'}
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class={`w-full px-4 py-2 text-left bg-transparent rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800 transition ${selectedCategory === 'Featured' ? 'bg-blue-100 dark:bg-zinc-800' : ''}`}
|
|
||||||
onclick={() => selectedCategory = 'Featured'}
|
|
||||||
>
|
|
||||||
Featured
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="my-2 border-b border-zinc-200 dark:border-zinc-700"></div>
|
|
||||||
|
|
||||||
{#each categories as category}
|
|
||||||
<button
|
|
||||||
class={`w-full px-4 py-2 text-left bg-transparent rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800 transition ${selectedCategory === category ? 'bg-blue-100 dark:bg-zinc-800' : ''}`}
|
|
||||||
onclick={() => selectedCategory = category}
|
|
||||||
>
|
|
||||||
{category}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div class="overflow-auto flex-1">
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="sticky top-0 z-10 p-4 border-b bg-[#F1F1F3] dark:bg-zinc-900 dark:border-zinc-700">
|
<div class="sticky top-0 z-10 p-4 border-b bg-[#F1F1F3] dark:bg-zinc-900 dark:border-zinc-700">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
@@ -295,7 +264,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if settingsState.devMode}
|
{#if settingsState.devMode}
|
||||||
<div class="p-4 mt-8 rounded bg-zinc-100 dark:bg-zinc-800">
|
<div class="p-4 mt-8 rounded bg-zinc-100 dark:bg-zinc-800">
|
||||||
@@ -307,4 +275,3 @@
|
|||||||
<p>Selected Category: {selectedCategory}</p>
|
<p>Selected Category: {selectedCategory}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={handleButtonClick}
|
onclick={handleButtonClick}
|
||||||
class="flex items-center gap-2 px-3 py-1.5 text-[0.75rem] rounded-lg shadow-2xl border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 dark:text-white transition-colors duration-200"
|
class="flex h-12 items-center gap-2 rounded-full bg-zinc-100/80 px-4 text-sm font-medium text-zinc-700 transition-colors duration-150 hover:bg-zinc-200 hover:text-zinc-950 focus:outline-none dark:bg-zinc-900/50 dark:text-zinc-200 dark:hover:bg-zinc-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{#if cloudState.isLoggedIn}
|
{#if cloudState.isLoggedIn}
|
||||||
{#if cloudState.user?.pfpUrl}
|
{#if cloudState.user?.pfpUrl}
|
||||||
@@ -85,14 +85,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<span
|
<span
|
||||||
class={alwaysShowUserName
|
class={alwaysShowUserName
|
||||||
? "inline max-w-[10rem] truncate text-[0.75rem]"
|
? "inline max-w-[10rem] truncate"
|
||||||
: "hidden max-w-24 truncate sm:inline text-[0.75rem]"}
|
: "hidden max-w-24 truncate sm:inline"}
|
||||||
>
|
>
|
||||||
{cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"}
|
{cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"}
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-sm font-IconFamily" aria-hidden="true">{'\ued53'}</span>
|
<span class="text-sm font-IconFamily" aria-hidden="true">{'\ued53'}</span>
|
||||||
<span class="text-[0.75rem]">Sign in</span>
|
<span>Sign in</span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -34,16 +34,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if slides.length > 0}
|
{#if slides.length > 0}
|
||||||
<div class="relative w-full overflow-clip rounded-xl transition-opacity" transition:fade>
|
<div class="relative w-full overflow-clip rounded-xl ring-1 ring-black/10 transition-opacity dark:ring-white/10" transition:fade>
|
||||||
<div
|
<div
|
||||||
class="w-full aspect-[5/1] max-h-[500px]"
|
class="w-full overflow-hidden"
|
||||||
use:emblaCarouselSvelte={{ options, plugins }}
|
use:emblaCarouselSvelte={{ options, plugins }}
|
||||||
onemblaInit={onInit}
|
onemblaInit={onInit}
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
{#each slides as slide (slide.imageUrl + slide.title + (slide.subtitle ?? ''))}
|
{#each slides as slide (slide.imageUrl + slide.title + (slide.subtitle ?? ''))}
|
||||||
<div
|
<div
|
||||||
class="relative flex-[0_0_100%] cursor-pointer rounded-xl overflow-clip"
|
class="cover-slide relative isolate min-w-0 flex-[0_0_100%] cursor-pointer overflow-hidden"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
onkeydown={(e) => {
|
onkeydown={(e) => {
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
}}
|
}}
|
||||||
onclick={() => setDisplayTheme(slide.openTheme)}
|
onclick={() => setDisplayTheme(slide.openTheme)}
|
||||||
>
|
>
|
||||||
<img src={slide.imageUrl} alt="" class="object-cover w-full h-full" />
|
<img src={slide.imageUrl} alt="" class="absolute inset-0 z-0 h-full w-full object-cover" />
|
||||||
{#if slide.badgeFeatured === true}
|
{#if slide.badgeFeatured === true}
|
||||||
<div class="absolute top-4 left-4 z-[2] pointer-events-none">
|
<div class="absolute top-4 left-4 z-[2] pointer-events-none">
|
||||||
<span
|
<span
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="absolute bottom-0 left-0 p-8 z-[1]">
|
<div class="absolute bottom-0 left-0 z-[2] p-8">
|
||||||
<h2 class="text-4xl font-bold text-white">{slide.title}</h2>
|
<h2 class="text-4xl font-bold text-white">{slide.title}</h2>
|
||||||
{#if slide.subtitle}
|
{#if slide.subtitle}
|
||||||
<p class="text-lg font-medium text-white/95 mt-1 line-clamp-2">{slide.subtitle}</p>
|
<p class="text-lg font-medium text-white/95 mt-1 line-clamp-2">{slide.subtitle}</p>
|
||||||
@@ -81,7 +81,6 @@
|
|||||||
<p class="text-lg text-white line-clamp-3">{slide.openTheme.description}</p>
|
<p class="text-lg text-white line-clamp-3">{slide.openTheme.description}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-0 left-0 w-full h-1/2 to-transparent bg-linear-to-t from-black/80"></div>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -111,3 +110,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cover-slide {
|
||||||
|
aspect-ratio: 3 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-slide::after {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to top,
|
||||||
|
rgb(0 0 0 / 46%) 0%,
|
||||||
|
rgb(0 0 0 / 42.5%) 18%,
|
||||||
|
rgb(0 0 0 / 34%) 36%,
|
||||||
|
rgb(0 0 0 / 21%) 54%,
|
||||||
|
rgb(0 0 0 / 8%) 72%,
|
||||||
|
transparent 88%
|
||||||
|
);
|
||||||
|
content: '';
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,77 +1,120 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import logo from '@/resources/icons/betterseqta-dark-full.png';
|
import darkLogo from "@/resources/icons/betterseqta-dark-full.png";
|
||||||
import logoDark from '@/resources/icons/betterseqta-light-full.png';
|
import lightLogo from "@/resources/icons/betterseqta-light-full.png";
|
||||||
import browser from 'webextension-polyfill';
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
import CloudHeader from './CloudHeader.svelte';
|
import MotionDiv from "../MotionDiv.svelte";
|
||||||
|
import PlainCloseButton from "../PlainCloseButton.svelte";
|
||||||
|
import CloudHeader from "./CloudHeader.svelte";
|
||||||
|
|
||||||
const handleCloseStore = () => {
|
type SettingsPage = "settings" | "themes" | "backgrounds";
|
||||||
void import('@/seqta/ui/renderStore').then((module) => module.closeStore());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Props
|
let {
|
||||||
let { searchTerm, setSearchTerm, darkMode, activeTab, setActiveTab } = $props<{
|
searchTerm,
|
||||||
searchTerm: string,
|
setSearchTerm,
|
||||||
setSearchTerm: (term: string) => void,
|
activePage,
|
||||||
darkMode: boolean,
|
setActivePage,
|
||||||
activeTab: string,
|
showStoreTools,
|
||||||
setActiveTab: (tab: string) => void
|
onLogoClick,
|
||||||
|
onClose,
|
||||||
|
} = $props<{
|
||||||
|
searchTerm: string;
|
||||||
|
setSearchTerm: (term: string) => void;
|
||||||
|
activePage: SettingsPage;
|
||||||
|
setActivePage: (page: SettingsPage) => void;
|
||||||
|
showStoreTools: boolean;
|
||||||
|
onLogoClick: () => void;
|
||||||
|
onClose: () => void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// Clear search input function
|
const tabs: { id: SettingsPage; title: string }[] = [
|
||||||
const clearSearch = () => {
|
{ id: "settings", title: "Settings" },
|
||||||
setSearchTerm('');
|
{ id: "themes", title: "Themes" },
|
||||||
};
|
{ id: "backgrounds", title: "Backgrounds" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const activeIndex = $derived(tabs.findIndex((tab) => tab.id === activePage));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="fixed top-0 z-50 w-full h-[4.25rem] bg-white border-b shadow-md border-b-white/10 dark:bg-zinc-950/90 backdrop-blur-xl dark:text-white">
|
<header
|
||||||
<div class="flex justify-between items-center px-4 py-1">
|
class="flex shrink-0 items-center gap-4 border-b border-zinc-200/60 px-5 py-4 dark:border-zinc-700/50"
|
||||||
<div class="flex gap-4 place-items-center cursor-pointer" onkeydown={(e) => { if (e.key === 'Enter') clearSearch() }} onclick={clearSearch} role="button" tabindex="0">
|
|
||||||
<img src={browser.runtime.getURL(logo)} class="h-14 {darkMode ? 'hidden' : ''}" alt="Logo" />
|
|
||||||
<img src={browser.runtime.getURL(logoDark)} class="h-14 {darkMode ? '' : 'hidden'}" alt="Dark Logo" />
|
|
||||||
|
|
||||||
<div class="w-[1px] h-10 my-auto bg-zinc-400 dark:bg-zinc-600"></div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="px-4 py-2 font-semibold text-lg transition-colors duration-200 {activeTab === 'themes' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-600 dark:text-gray-300 hover:text-blue-500 dark:hover:text-blue-400'}"
|
|
||||||
onclick={() => setActiveTab('themes')}
|
|
||||||
>
|
>
|
||||||
Themes
|
<button type="button" class="hidden shrink-0 lg:block" onclick={onLogoClick}>
|
||||||
|
<img
|
||||||
|
src={resolveExtensionAssetUrl(darkLogo)}
|
||||||
|
class="h-11 w-52 object-cover dark:hidden"
|
||||||
|
alt="BetterSEQTA+"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={resolveExtensionAssetUrl(lightLogo)}
|
||||||
|
class="hidden h-11 w-52 object-cover dark:block"
|
||||||
|
alt="BetterSEQTA+"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
class="px-4 py-2 font-semibold text-lg transition-colors duration-200 {activeTab === 'backgrounds' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-600 dark:text-gray-300 hover:text-blue-500 dark:hover:text-blue-400'}"
|
<div
|
||||||
onclick={() => setActiveTab('backgrounds')}
|
class="h-12 min-w-[14rem] flex-1 rounded-full bg-zinc-100/80 p-1 dark:bg-zinc-900/50"
|
||||||
|
role="tablist"
|
||||||
|
aria-label="Settings pages"
|
||||||
>
|
>
|
||||||
Backgrounds
|
<div class="relative flex">
|
||||||
|
<MotionDiv
|
||||||
|
class="absolute inset-y-0 left-0 z-0 w-1/3 rounded-full bg-white shadow-sm dark:bg-zinc-700"
|
||||||
|
animate={{ x: `${activeIndex * 100}%` }}
|
||||||
|
transition={{ type: "spring", stiffness: 250, damping: 25 }}
|
||||||
|
/>
|
||||||
|
{#each tabs as tab (tab.id)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={activePage === tab.id}
|
||||||
|
onclick={() => setActivePage(tab.id)}
|
||||||
|
class="relative z-10 h-10 flex-1 rounded-full px-4 text-base transition-colors duration-200
|
||||||
|
{activePage === tab.id
|
||||||
|
? 'font-semibold text-zinc-900 dark:text-white'
|
||||||
|
: 'text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white'}"
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
</button>
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex shrink-0 items-center gap-2">
|
||||||
|
{#if showStoreTools}
|
||||||
|
<label class="relative hidden w-40 md:block xl:w-56">
|
||||||
|
<span class="sr-only">Search {activePage}</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={activePage === "themes" ? "Search themes" : "Search backgrounds"}
|
||||||
|
value={searchTerm}
|
||||||
|
oninput={(event) => setSearchTerm(event.currentTarget.value)}
|
||||||
|
class="store-search h-12 w-full rounded-full bg-zinc-100/80 pl-9 pr-4 text-sm text-zinc-900 transition-colors duration-150 placeholder:text-zinc-400 focus:bg-zinc-200/70 dark:bg-zinc-900/50 dark:text-white dark:focus:bg-zinc-700"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<CloudHeader />
|
<CloudHeader />
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<div class="flex relative gap-2">
|
<PlainCloseButton onclick={onClose} label="Close settings" />
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search themes..."
|
|
||||||
value={searchTerm}
|
|
||||||
oninput={(e: any) => setSearchTerm(e.target.value)}
|
|
||||||
class="px-4 py-2 pl-10 text-lg transition bg-gray-100/80 rounded-lg ring-0 focus:bg-gray-100/0 dark:focus:bg-zinc-700/50 focus:ring-[1px] ring-zinc-200 dark:ring-zinc-600 dark:bg-zinc-700/80 dark:text-gray-100 focus:outline-none focus:border-transparent" />
|
|
||||||
<svg
|
|
||||||
class="absolute left-3 top-1/2 w-5 h-5 text-gray-400 transform -translate-y-1/2 dark:text-gray-200"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor">
|
|
||||||
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<!-- Close Button -->
|
|
||||||
<button
|
|
||||||
onclick={handleCloseStore}
|
|
||||||
class="p-1 px-3"
|
|
||||||
>
|
|
||||||
<span class="text-2xl font-IconFamily"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.store-search,
|
||||||
|
.store-search:focus,
|
||||||
|
.store-search:focus-visible {
|
||||||
|
appearance: none;
|
||||||
|
border: 0 !important;
|
||||||
|
outline: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
masterGridDisplayDownloadCount,
|
masterGridDisplayDownloadCount,
|
||||||
gridCardPreviewImageUrls,
|
gridCardPreviewImageUrls,
|
||||||
} from '@/interface/utils/themeStoreFlavours'
|
} from '@/interface/utils/themeStoreFlavours'
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import emblaCarouselSvelte from 'embla-carousel-svelte';
|
import emblaCarouselSvelte from 'embla-carousel-svelte';
|
||||||
import Autoplay from 'embla-carousel-autoplay';
|
import Autoplay from 'embla-carousel-autoplay';
|
||||||
let { theme, onClick, toggleFavorite, isLoggedIn, onRequestSignIn, allStoreThemeRows } = $props<{
|
let { theme, onClick, toggleFavorite, isLoggedIn, onRequestSignIn, allStoreThemeRows } = $props<{
|
||||||
@@ -49,21 +47,8 @@
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
let menuOpen = $state(false);
|
|
||||||
let menuRef: HTMLDivElement;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const closeMenu = (e: MouseEvent) => {
|
|
||||||
if (menuOpen && menuRef && !menuRef.contains(e.target as Node)) {
|
|
||||||
menuOpen = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener('click', closeMenu);
|
|
||||||
return () => document.removeEventListener('click', closeMenu);
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleCardClick(e: MouseEvent) {
|
function handleCardClick(e: MouseEvent) {
|
||||||
if ((e.target as HTMLElement).closest('[data-theme-menu]')) return;
|
if ((e.target as HTMLElement).closest('[data-theme-favorite]')) return;
|
||||||
onClick();
|
onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +59,6 @@
|
|||||||
} else {
|
} else {
|
||||||
onRequestSignIn?.();
|
onRequestSignIn?.();
|
||||||
}
|
}
|
||||||
menuOpen = false;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -86,8 +70,7 @@
|
|||||||
onclick={handleCardClick}
|
onclick={handleCardClick}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bg-gray-50 w-full transition-all duration-500 ease-out relative group flex flex-col rounded-xl overflow-clip border hover:scale-105 hover:shadow-2xl dark:hover:shadow-white/[0.1] dark:hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto"
|
class="theme-card isolate bg-gray-50 w-full transition-[transform,box-shadow,border-color] duration-300 ease-out relative group flex flex-col rounded-xl overflow-clip border hover:scale-[1.02] hover:shadow-xl dark:hover:shadow-black/60 dark:bg-zinc-800 dark:border-white/[0.1] h-auto"
|
||||||
transition:fade
|
|
||||||
>
|
>
|
||||||
{#if theme.featured === true}
|
{#if theme.featured === true}
|
||||||
<div class="absolute top-2 left-2 z-20 pointer-events-none">
|
<div class="absolute top-2 left-2 z-20 pointer-events-none">
|
||||||
@@ -102,29 +85,21 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- Menu dropdown -->
|
|
||||||
<div class="absolute top-2 right-2 z-20" data-theme-menu bind:this={menuRef}>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex justify-center items-center w-8 h-8 rounded-lg bg-black/40 hover:bg-black/60 text-white transition-all"
|
data-theme-favorite
|
||||||
onclick={(e) => { e.stopPropagation(); menuOpen = !menuOpen; }}
|
class="pointer-events-none absolute right-2 top-2 z-20 flex h-10 w-10 scAle-[0.25] items-center justify-center rounded-full bg-black/50 text-white opacity-0 blur-[4px] transition-[opacity,transform,filter,background-color] duration-200 ease-[cubic-bezier(0.2,0,0,1)] hover:bg-black/70 active:scale-[0.96] group-hover:pointer-events-auto group-hover:scale-100 group-hover:opacity-100 group-hover:blur-0 focus-visible:pointer-events-auto focus-visible:scale-100 focus-visible:opacity-100 focus-visible:blur-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
||||||
aria-label="Theme options"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" class="w-5 h-5">
|
|
||||||
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{#if menuOpen}
|
|
||||||
<div
|
|
||||||
class="absolute right-0 top-full mt-1 py-1 min-w-[140px] rounded-lg bg-white dark:bg-zinc-800 shadow-lg border border-zinc-200 dark:border-zinc-700"
|
|
||||||
role="menu"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex gap-2 items-center w-full px-3 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700"
|
|
||||||
role="menuitem"
|
|
||||||
onclick={handleFavoriteClick}
|
onclick={handleFavoriteClick}
|
||||||
title={isLoggedIn ? (theme.is_favorited ? 'Remove from favorites' : 'Add to favorites') : 'Sign in to favorite themes'}
|
aria-label={isLoggedIn
|
||||||
|
? theme.is_favorited
|
||||||
|
? 'Remove from favourites'
|
||||||
|
: 'Add to favourites'
|
||||||
|
: 'Sign in to favourite themes'}
|
||||||
|
title={isLoggedIn
|
||||||
|
? theme.is_favorited
|
||||||
|
? 'Remove from favourites'
|
||||||
|
: 'Add to favourites'
|
||||||
|
: 'Sign in to favourite themes'}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -132,15 +107,12 @@
|
|||||||
fill={theme.is_favorited ? 'currentColor' : 'none'}
|
fill={theme.is_favorited ? 'currentColor' : 'none'}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
class="w-5 h-5 {theme.is_favorited ? 'text-red-500' : ''}"
|
class="h-5 w-5 {theme.is_favorited ? 'text-red-500' : ''}"
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 0 1 6.364 0L12 7.636l1.318-1.318a4.5 4.5 0 0 1 6.364 6.364L12 20.364l-7.682-7.682a4.5 4.5 0 0 1 0-6.364Z" />
|
||||||
</svg>
|
</svg>
|
||||||
{theme.is_favorited ? 'Favorited' : 'Favorite'}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="absolute bottom-1 left-3 right-3 z-10 mb-1 flex flex-col gap-0.5">
|
<div class="absolute bottom-1 left-3 right-3 z-10 mb-1 flex flex-col gap-0.5">
|
||||||
<span class="text-xl font-bold text-white drop-shadow-md">{theme.name}</span>
|
<span class="text-xl font-bold text-white drop-shadow-md">{theme.name}</span>
|
||||||
{#if theme.author}
|
{#if theme.author}
|
||||||
@@ -161,7 +133,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='absolute bottom-0 z-0 w-full h-3/4 bg-linear-to-t to-transparent from-black/80'></div>
|
|
||||||
{#if gridRotatorUrls.length === 0}
|
{#if gridRotatorUrls.length === 0}
|
||||||
<div class="relative w-full h-48 overflow-hidden rounded-md bg-zinc-200 dark:bg-zinc-700" aria-hidden="true"></div>
|
<div class="relative w-full h-48 overflow-hidden rounded-md bg-zinc-200 dark:bg-zinc-700" aria-hidden="true"></div>
|
||||||
{:else if !allowSlideAutoplay || gridRotatorUrls.length === 1}
|
{:else if !allowSlideAutoplay || gridRotatorUrls.length === 1}
|
||||||
@@ -199,3 +170,22 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.theme-card::after {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to top,
|
||||||
|
rgb(0 0 0 / 92%) 0%,
|
||||||
|
rgb(0 0 0 / 85%) 18%,
|
||||||
|
rgb(0 0 0 / 68%) 36%,
|
||||||
|
rgb(0 0 0 / 42%) 54%,
|
||||||
|
rgb(0 0 0 / 16%) 72%,
|
||||||
|
transparent 88%
|
||||||
|
);
|
||||||
|
content: '';
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative" >
|
<div class="relative" >
|
||||||
<div class="grid grid-cols-1 gap-4 py-12 mx-auto sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 py-6 mx-auto sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{#each filteredThemes as theme (theme.id)}
|
{#each filteredThemes as theme (theme.id)}
|
||||||
<ThemeCard
|
<ThemeCard
|
||||||
{theme}
|
{theme}
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
{#if filteredThemes.length !== 0}
|
{#if filteredThemes.length !== 0}
|
||||||
<a href="https://docs.betterseqta.org/theme-creation/" class="block relative z-0 hover:z-20 w-full cursor-pointer">
|
<a href="https://docs.betterseqta.org/theme-creation/" class="block relative z-0 hover:z-20 w-full cursor-pointer">
|
||||||
<div class="bg-zinc-50 h-48 w-full transition-all duration-500 ease-out relative overflow-clip rounded-xl border group group/card flex flex-col justify-center items-center hover:scale-105 hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1]">
|
<div class="bg-zinc-50 h-48 w-full transition-[transform,box-shadow,border-color] duration-300 ease-out relative overflow-clip rounded-xl border group group/card flex flex-col justify-center items-center hover:scale-[1.02] hover:shadow-xl dark:hover:shadow-black/60 dark:bg-zinc-800 dark:border-white/[0.1]">
|
||||||
<div class="text-2xl font-IconFamily">{'\uecb3'}</div>
|
<div class="text-2xl font-IconFamily">{'\uecb3'}</div>
|
||||||
<div class="text-xl font-bold text-center transition-all duration-500 dark:text-white">
|
<div class="text-xl font-bold text-center transition-all duration-500 dark:text-white">
|
||||||
Got a Theme Idea?
|
Got a Theme Idea?
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
const themeManager = ThemeManager.getInstance();
|
const themeManager = ThemeManager.getInstance();
|
||||||
|
|
||||||
let themes = $state<ThemeList | null>(null);
|
let themes = $state<ThemeList | null>(null);
|
||||||
let { isEditMode } = $props<{ isEditMode: boolean }>();
|
let { isEditMode, showNavigation = true } = $props<{
|
||||||
|
isEditMode: boolean;
|
||||||
|
showNavigation?: boolean;
|
||||||
|
}>();
|
||||||
let isDragging = $state(false);
|
let isDragging = $state(false);
|
||||||
let tempTheme = $state(null);
|
let tempTheme = $state(null);
|
||||||
let favoriteStatus = $state<Record<string, boolean>>({});
|
let favoriteStatus = $state<Record<string, boolean>>({});
|
||||||
@@ -128,7 +131,6 @@
|
|||||||
|
|
||||||
const openStorePage = async () => {
|
const openStorePage = async () => {
|
||||||
const { OpenStorePage } = await import('@/seqta/ui/renderStore')
|
const { OpenStorePage } = await import('@/seqta/ui/renderStore')
|
||||||
closeExtensionPopup()
|
|
||||||
await OpenStorePage()
|
await OpenStorePage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +168,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="pt-5 mb-1 w-full"
|
class="pt-5 mb-1 w-full max-w-lg mx-auto"
|
||||||
role="list"
|
role="list"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
ondragover={handleDragOver}
|
ondragover={handleDragOver}
|
||||||
@@ -188,7 +190,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="pb-2 text-lg font-bold">Themes</h2>
|
|
||||||
<div class="flex flex-col gap-2 px-2">
|
<div class="flex flex-col gap-2 px-2">
|
||||||
{#if themes}
|
{#if themes}
|
||||||
{#each themes.themes as theme (theme.id)}
|
{#each themes.themes as theme (theme.id)}
|
||||||
@@ -267,10 +268,38 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if themes && themes.themes.length > 0}
|
{#if themes && themes.themes.length === 0 && !tempTheme}
|
||||||
<div id="divider" class="w-full h-[1px] my-2 bg-zinc-100 dark:bg-zinc-600"></div>
|
<div class="flex min-h-56 flex-col items-center justify-center px-4 py-10 text-center">
|
||||||
|
<h3 class="text-xl font-semibold text-zinc-900 dark:text-white" style="text-wrap: balance">
|
||||||
|
No themes installed
|
||||||
|
</h3>
|
||||||
|
<p class="mt-2 max-w-md text-base text-zinc-500 dark:text-zinc-400" style="text-wrap: pretty">
|
||||||
|
Install a theme from the store or create one of your own.
|
||||||
|
</p>
|
||||||
|
<div class="mt-6 grid w-full max-w-md grid-cols-1 gap-2 sm:grid-cols-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => void openStorePage()}
|
||||||
|
class="flex h-11 items-center justify-center gap-2 rounded-lg bg-zinc-200 px-4 font-medium text-zinc-900 transition-[background-color,color,transform] duration-150 hover:bg-zinc-300 active:scale-[0.96] focus:outline-none focus:ring-2 focus:ring-zinc-400 dark:bg-zinc-700 dark:text-white dark:hover:bg-zinc-600"
|
||||||
|
>
|
||||||
|
<span class="font-IconFamily text-lg" aria-hidden="true"></span>
|
||||||
|
Install a theme
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => void openThemeCreator()}
|
||||||
|
class="flex h-11 items-center justify-center gap-2 rounded-lg bg-zinc-200 px-4 font-medium text-zinc-900 transition-[background-color,color,transform] duration-150 hover:bg-zinc-300 active:scale-[0.96] focus:outline-none focus:ring-2 focus:ring-zinc-400 dark:bg-zinc-700 dark:text-white dark:hover:bg-zinc-600"
|
||||||
|
>
|
||||||
|
<span class="font-IconFamily text-lg" aria-hidden="true"></span>
|
||||||
|
Create a theme
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if showNavigation && themes && themes.themes.length > 0}
|
||||||
|
<div id="divider" class="w-full h-[1px] my-2 bg-zinc-100 dark:bg-zinc-600"></div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick={() => void openStorePage()}
|
onclick={() => void openStorePage()}
|
||||||
class="flex justify-center items-center w-full rounded-xl transition aspect-theme bg-zinc-100 dark:bg-zinc-900 dark:text-white"
|
class="flex justify-center items-center w-full rounded-xl transition aspect-theme bg-zinc-100 dark:bg-zinc-900 dark:text-white"
|
||||||
@@ -286,6 +315,7 @@
|
|||||||
<span class="text-xl font-IconFamily"></span>
|
<span class="text-xl font-IconFamily"></span>
|
||||||
<span class="ml-2">Create your own</span>
|
<span class="ml-2">Create your own</span>
|
||||||
</button>
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import "./index.css";
|
|
||||||
import Settings from "./pages/settings.svelte";
|
import Settings from "./pages/settings.svelte";
|
||||||
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
||||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ export default function renderSvelte(
|
|||||||
props: Record<string, any> = {},
|
props: Record<string, any> = {},
|
||||||
shadowStyle: ShadowStyleVariant = "settings",
|
shadowStyle: ShadowStyleVariant = "settings",
|
||||||
) {
|
) {
|
||||||
|
if (mountPoint instanceof ShadowRoot || props.standalone === true) {
|
||||||
|
const styleElement = document.createElement("style");
|
||||||
|
styleElement.textContent = shadowStyles[shadowStyle];
|
||||||
|
(mountPoint instanceof ShadowRoot ? mountPoint : document.head).appendChild(
|
||||||
|
styleElement,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const app = mount(Component, {
|
const app = mount(Component, {
|
||||||
target: mountPoint,
|
target: mountPoint,
|
||||||
props: {
|
props: {
|
||||||
@@ -24,11 +32,5 @@ export default function renderSvelte(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mountPoint instanceof ShadowRoot) {
|
|
||||||
const styleElement = document.createElement("style");
|
|
||||||
styleElement.textContent = shadowStyles[shadowStyle];
|
|
||||||
mountPoint.appendChild(styleElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
+258
-228
@@ -2,7 +2,11 @@
|
|||||||
import Settings from "./settings/general.svelte";
|
import Settings from "./settings/general.svelte";
|
||||||
import Shortcuts from "./settings/shortcuts.svelte";
|
import Shortcuts from "./settings/shortcuts.svelte";
|
||||||
import Theme from "./settings/theme.svelte";
|
import Theme from "./settings/theme.svelte";
|
||||||
import browser from "webextension-polyfill";
|
import Store from "./store.svelte";
|
||||||
|
import TabbedContainer from "../components/TabbedContainer.svelte";
|
||||||
|
import darkLogo from "@/resources/icons/betterseqta-dark-full.png";
|
||||||
|
import lightLogo from "@/resources/icons/betterseqta-light-full.png";
|
||||||
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
|
|
||||||
import { standalone as StandaloneStore } from "../utils/standalone.svelte";
|
import { standalone as StandaloneStore } from "../utils/standalone.svelte";
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
@@ -17,8 +21,15 @@
|
|||||||
import CloudPanel from "../components/CloudPanel.svelte";
|
import CloudPanel from "../components/CloudPanel.svelte";
|
||||||
import DisclaimerModal from "../components/DisclaimerModal.svelte";
|
import DisclaimerModal from "../components/DisclaimerModal.svelte";
|
||||||
import FeedbackModal from "../components/FeedbackModal.svelte";
|
import FeedbackModal from "../components/FeedbackModal.svelte";
|
||||||
|
import SidebarNav from "../components/SidebarNav.svelte";
|
||||||
|
import StoreHeader from "../components/store/Header.svelte";
|
||||||
import { settingsPopup } from "@/seqta/utils/settingsPopup";
|
import { settingsPopup } from "@/seqta/utils/settingsPopup";
|
||||||
import { consumeOpenFeedbackRequest } from "@/seqta/utils/feedback/client";
|
import { consumeOpenFeedbackRequest } from "@/seqta/utils/feedback/client";
|
||||||
|
import {
|
||||||
|
consumeSettingsDestination,
|
||||||
|
SETTINGS_NAVIGATION_EVENT,
|
||||||
|
type SettingsDestination,
|
||||||
|
} from "@/seqta/utils/settingsNavigation";
|
||||||
import {
|
import {
|
||||||
checkGithubReleaseUpdate,
|
checkGithubReleaseUpdate,
|
||||||
dismissNightlyUpdate,
|
dismissNightlyUpdate,
|
||||||
@@ -26,46 +37,30 @@
|
|||||||
isGhReleaseUpdateCheckEnabled,
|
isGhReleaseUpdateCheckEnabled,
|
||||||
type GhReleaseUpdateInfo,
|
type GhReleaseUpdateInfo,
|
||||||
} from "@/utils/githubReleaseUpdate";
|
} from "@/utils/githubReleaseUpdate";
|
||||||
type PageId = "settings" | "shortcuts" | "themes";
|
type PageId = "settings" | "themes" | "backgrounds";
|
||||||
|
type StoreTab = "themes" | "backgrounds";
|
||||||
|
type ThemeView = "theme-settings" | "theme-store";
|
||||||
|
type BackgroundView = "background-settings" | "background-store";
|
||||||
|
|
||||||
type NavItem = {
|
type NavItem = {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
divided?: boolean;
|
||||||
|
nested?: boolean;
|
||||||
|
expanded?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BACKGROUND_CATEGORY_PREFIX = "background-category:";
|
||||||
|
|
||||||
let devModeSequence = "";
|
let devModeSequence = "";
|
||||||
|
let compactActiveTab = $state(0);
|
||||||
let activePage = $state<PageId>("settings");
|
let activePage = $state<PageId>("settings");
|
||||||
let activeSection = $state("general");
|
let activeSection = $state("general");
|
||||||
let navTrackEl = $state<HTMLElement | null>(null);
|
let activeThemeView = $state<ThemeView>("theme-settings");
|
||||||
let indicatorY = $state(0);
|
let activeBackgroundView = $state<BackgroundView>("background-settings");
|
||||||
let indicatorH = $state(40);
|
let selectedBackgroundCategory = $state("All");
|
||||||
let indicatorReady = $state(false);
|
let backgroundCategories = $state<string[]>([]);
|
||||||
|
let storeSearchTerm = $state("");
|
||||||
const updateNavIndicator = () => {
|
|
||||||
if (!navTrackEl || activePage !== "settings") {
|
|
||||||
indicatorReady = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const btn = navTrackEl.querySelector<HTMLElement>(
|
|
||||||
`[data-nav-section="${activeSection}"]`,
|
|
||||||
);
|
|
||||||
if (!btn) {
|
|
||||||
indicatorReady = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const trackRect = navTrackEl.getBoundingClientRect();
|
|
||||||
const btnRect = btn.getBoundingClientRect();
|
|
||||||
indicatorY = btnRect.top - trackRect.top + navTrackEl.scrollTop;
|
|
||||||
indicatorH = btnRect.height;
|
|
||||||
indicatorReady = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
activeSection;
|
|
||||||
activePage;
|
|
||||||
navTrackEl;
|
|
||||||
queueMicrotask(updateNavIndicator);
|
|
||||||
});
|
|
||||||
|
|
||||||
let showDisclaimerModal = $state(false);
|
let showDisclaimerModal = $state(false);
|
||||||
let disclaimerCallbacks = $state<{ onConfirm: () => void; onCancel: () => void } | null>(null);
|
let disclaimerCallbacks = $state<{ onConfirm: () => void; onCancel: () => void } | null>(null);
|
||||||
@@ -75,12 +70,6 @@
|
|||||||
const ghReleaseChannelLabel = getInstalledGhReleaseChannelLabel();
|
const ghReleaseChannelLabel = getInstalledGhReleaseChannelLabel();
|
||||||
let ghReleaseUpdate = $state<GhReleaseUpdateInfo | null>(null);
|
let ghReleaseUpdate = $state<GhReleaseUpdateInfo | null>(null);
|
||||||
|
|
||||||
const pages: { id: PageId; title: string }[] = [
|
|
||||||
{ id: "settings", title: "Settings" },
|
|
||||||
{ id: "shortcuts", title: "Shortcuts" },
|
|
||||||
{ id: "themes", title: "Themes" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const userNav: NavItem[] = [
|
const userNav: NavItem[] = [
|
||||||
{ id: "account", label: "My Account" },
|
{ id: "account", label: "My Account" },
|
||||||
{ id: "general", label: "General" },
|
{ id: "general", label: "General" },
|
||||||
@@ -92,15 +81,77 @@
|
|||||||
{ id: "assessments", label: "Assessments" },
|
{ id: "assessments", label: "Assessments" },
|
||||||
{ id: "features", label: "Features" },
|
{ id: "features", label: "Features" },
|
||||||
{ id: "advanced", label: "Advanced" },
|
{ id: "advanced", label: "Advanced" },
|
||||||
|
{ id: "shortcuts", label: "Shortcuts" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const sectionTitle = $derived.by(() => {
|
const settingsNavGroups = [
|
||||||
if (activePage === "shortcuts") return "Shortcuts";
|
{ label: "User Settings", items: userNav },
|
||||||
if (activePage === "themes") return "Themes";
|
{ label: "App Settings", items: appNav },
|
||||||
const all = [...userNav, ...appNav];
|
];
|
||||||
return all.find((item) => item.id === activeSection)?.label ?? "Settings";
|
|
||||||
|
const themeNavGroups = [
|
||||||
|
{
|
||||||
|
label: "Themes",
|
||||||
|
items: [
|
||||||
|
{ id: "theme-settings", label: "Theme settings" },
|
||||||
|
{ id: "create-theme", label: "Create theme" },
|
||||||
|
{ id: "theme-store", label: "Store" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const backgroundNavGroups = $derived.by(() => {
|
||||||
|
const storeOpen = activeBackgroundView === "background-store";
|
||||||
|
const categoryItems: NavItem[] = storeOpen
|
||||||
|
? ["All", "Featured", ...backgroundCategories].map((category, index) => ({
|
||||||
|
id: `${BACKGROUND_CATEGORY_PREFIX}${category}`,
|
||||||
|
label: category,
|
||||||
|
nested: true,
|
||||||
|
divided: index === 2,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: "Backgrounds",
|
||||||
|
items: [
|
||||||
|
{ id: "background-settings", label: "Background settings" },
|
||||||
|
{ id: "background-store", label: "Store", expanded: storeOpen },
|
||||||
|
...categoryItems,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const navGroups = $derived(
|
||||||
|
activePage === "settings"
|
||||||
|
? settingsNavGroups
|
||||||
|
: activePage === "themes"
|
||||||
|
? themeNavGroups
|
||||||
|
: backgroundNavGroups,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedNavId = $derived(
|
||||||
|
activePage === "settings"
|
||||||
|
? activeSection
|
||||||
|
: activePage === "themes"
|
||||||
|
? activeThemeView
|
||||||
|
: activeBackgroundView === "background-store"
|
||||||
|
? `${BACKGROUND_CATEGORY_PREFIX}${selectedBackgroundCategory}`
|
||||||
|
: activeBackgroundView,
|
||||||
|
);
|
||||||
|
|
||||||
|
const sectionTitle = $derived.by(() => {
|
||||||
|
if (activePage === "themes") return "Themes";
|
||||||
|
if (activePage === "backgrounds") return "Backgrounds";
|
||||||
|
return [...userNav, ...appNav].find((item) => item.id === activeSection)?.label ?? "Settings";
|
||||||
|
});
|
||||||
|
|
||||||
|
const isStoreView = $derived(
|
||||||
|
(activePage === "themes" && activeThemeView === "theme-store") ||
|
||||||
|
(activePage === "backgrounds" && activeBackgroundView === "background-store"),
|
||||||
|
);
|
||||||
|
|
||||||
const openGhRelease = () => {
|
const openGhRelease = () => {
|
||||||
const url =
|
const url =
|
||||||
ghReleaseUpdate?.url ?? "https://github.com/BetterSEQTA/BetterSEQTA-Plus/releases";
|
ghReleaseUpdate?.url ?? "https://github.com/BetterSEQTA/BetterSEQTA-Plus/releases";
|
||||||
@@ -198,15 +249,52 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectPage = (page: PageId) => {
|
const selectNavItem = (id: string) => {
|
||||||
activePage = page;
|
if (activePage === "settings") {
|
||||||
|
activeSection = id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activePage === "themes") {
|
||||||
|
if (id === "create-theme") {
|
||||||
|
void openThemeCreator();
|
||||||
|
} else {
|
||||||
|
activeThemeView = id as ThemeView;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(BACKGROUND_CATEGORY_PREFIX)) {
|
||||||
|
activeBackgroundView = "background-store";
|
||||||
|
selectedBackgroundCategory = id.slice(BACKGROUND_CATEGORY_PREFIX.length);
|
||||||
|
} else {
|
||||||
|
activeBackgroundView = id as BackgroundView;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectSection = (id: string) => {
|
const openThemeCreator = async () => {
|
||||||
activeSection = id;
|
const { OpenThemeCreator } = await import("@/plugins/built-in/themes/ThemeCreator");
|
||||||
activePage = "settings";
|
OpenThemeCreator();
|
||||||
|
closeExtensionPopup();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const applyDestination = (destination: SettingsDestination) => {
|
||||||
|
activePage = destination.page;
|
||||||
|
if (destination.page === "settings" && destination.section) {
|
||||||
|
activeSection = destination.section;
|
||||||
|
} else if (destination.page === "themes" && destination.view) {
|
||||||
|
activeThemeView = destination.view === "store" ? "theme-store" : "theme-settings";
|
||||||
|
} else if (destination.page === "backgrounds" && destination.view) {
|
||||||
|
activeBackgroundView = destination.view === "store" ? "background-store" : "background-settings";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const utilityActions = [
|
||||||
|
{ label: "About", icon: "\ueb73", onclick: openAbout },
|
||||||
|
{ label: "What's new", icon: "\ue929", onclick: openChangelog },
|
||||||
|
{ label: "Privacy", icon: "\uecba", onclick: openPrivacyStatement },
|
||||||
|
] as const;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
settingsPopup.addListener(closePopupsOnSettingsClose);
|
settingsPopup.addListener(closePopupsOnSettingsClose);
|
||||||
|
|
||||||
@@ -231,6 +319,14 @@
|
|||||||
};
|
};
|
||||||
window.addEventListener("bsplus:open-feedback", onOpenFeedback);
|
window.addEventListener("bsplus:open-feedback", onOpenFeedback);
|
||||||
|
|
||||||
|
const onNavigateSettings = (event: Event) => {
|
||||||
|
applyDestination((event as CustomEvent<SettingsDestination>).detail);
|
||||||
|
consumeSettingsDestination();
|
||||||
|
};
|
||||||
|
window.addEventListener(SETTINGS_NAVIGATION_EVENT, onNavigateSettings);
|
||||||
|
const pendingDestination = consumeSettingsDestination();
|
||||||
|
if (pendingDestination) applyDestination(pendingDestination);
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape" && !standalone) {
|
if (event.key === "Escape" && !standalone) {
|
||||||
closeExtensionPopup();
|
closeExtensionPopup();
|
||||||
@@ -241,6 +337,7 @@
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("keydown", onKeyDown);
|
window.removeEventListener("keydown", onKeyDown);
|
||||||
window.removeEventListener("bsplus:open-feedback", onOpenFeedback);
|
window.removeEventListener("bsplus:open-feedback", onOpenFeedback);
|
||||||
|
window.removeEventListener(SETTINGS_NAVIGATION_EVENT, onNavigateSettings);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -249,126 +346,21 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet navButton(item: NavItem)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-nav-section={item.id}
|
|
||||||
onclick={() => selectSection(item.id)}
|
|
||||||
class="relative z-10 w-full px-3 py-2.5 text-left text-lg rounded-lg transition-colors duration-200
|
|
||||||
{activePage === 'settings' && activeSection === item.id
|
|
||||||
? 'text-zinc-900 dark:text-white font-medium'
|
|
||||||
: 'text-zinc-600 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-white'}"
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</button>
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
{#snippet settingsShell()}
|
{#snippet settingsShell()}
|
||||||
<div
|
<div
|
||||||
class="flex flex-col h-full min-h-0 overflow-hidden bg-white dark:bg-zinc-800 dark:text-white text-[18px] {standalone
|
class="flex flex-col h-full min-h-0 overflow-hidden bg-white dark:bg-zinc-800 dark:text-white text-[18px] {standalone
|
||||||
? ''
|
? ''
|
||||||
: 'rounded-xl shadow-2xl border border-zinc-200/60 dark:border-zinc-700/60'}"
|
: 'rounded-xl shadow-2xl border border-zinc-200/60 dark:border-zinc-700/60'}"
|
||||||
>
|
>
|
||||||
<!-- Top bar: logo + page selectors + actions -->
|
<StoreHeader
|
||||||
<div
|
searchTerm={storeSearchTerm}
|
||||||
class="flex shrink-0 items-center gap-4 px-5 py-4 border-b border-zinc-200/60 dark:border-zinc-700/50"
|
setSearchTerm={(term) => (storeSearchTerm = term)}
|
||||||
>
|
{activePage}
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
setActivePage={(page) => (activePage = page)}
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
showStoreTools={isStoreView}
|
||||||
<img
|
onLogoClick={handleDevModeToggle}
|
||||||
src={browser.runtime.getURL("resources/icons/betterseqta-dark-full.png")}
|
onClose={handleClose}
|
||||||
class="h-9 w-auto dark:hidden shrink-0 cursor-pointer"
|
|
||||||
alt="BetterSEQTA+"
|
|
||||||
onclick={handleDevModeToggle}
|
|
||||||
/>
|
/>
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<img
|
|
||||||
src={browser.runtime.getURL("resources/icons/betterseqta-light-full.png")}
|
|
||||||
class="hidden h-9 w-auto dark:block shrink-0 cursor-pointer"
|
|
||||||
alt="BetterSEQTA+"
|
|
||||||
onclick={handleDevModeToggle}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="flex flex-1 items-center justify-center gap-1 p-1.5 rounded-full bg-zinc-100/80 dark:bg-zinc-900/50"
|
|
||||||
role="tablist"
|
|
||||||
aria-label="Settings pages"
|
|
||||||
>
|
|
||||||
{#each pages as page (page.id)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
role="tab"
|
|
||||||
aria-selected={activePage === page.id}
|
|
||||||
onclick={() => selectPage(page.id)}
|
|
||||||
class="flex-1 px-4 py-2.5 text-lg rounded-full transition-all duration-200
|
|
||||||
{activePage === page.id
|
|
||||||
? 'bg-white dark:bg-zinc-700 text-zinc-900 dark:text-white font-semibold shadow-sm'
|
|
||||||
: 'text-zinc-500 dark:text-zinc-400 hover:text-zinc-800 dark:hover:text-zinc-200'}"
|
|
||||||
>
|
|
||||||
{page.title}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !standalone}
|
|
||||||
<div class="flex items-center gap-1 shrink-0">
|
|
||||||
{#if ghReleaseUpdateEnabled}
|
|
||||||
<div class="flex flex-col items-end gap-0.5 max-w-[8.5rem] mr-0.5">
|
|
||||||
{#if ghReleaseUpdate?.available}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={openGhRelease}
|
|
||||||
class="px-1.5 py-0.5 text-[10px] font-semibold leading-tight text-white rounded-full bg-amber-500 hover:bg-amber-600 dark:bg-amber-600 dark:hover:bg-amber-500 transition-colors duration-200"
|
|
||||||
title="Open GitHub release"
|
|
||||||
>
|
|
||||||
Update — {ghReleaseUpdate.label}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
<p class="text-[9px] leading-tight text-right text-zinc-500 dark:text-zinc-400">
|
|
||||||
{#if ghReleaseChannelLabel}
|
|
||||||
{ghReleaseChannelLabel} — do not upload to stores.
|
|
||||||
{:else}
|
|
||||||
GitHub build — do not upload to stores.
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={openAbout}
|
|
||||||
class="flex justify-center items-center w-9 h-9 text-xl rounded-lg font-IconFamily bg-zinc-100 dark:bg-zinc-700 transition-all duration-200 hover:scale-105 active:scale-95 focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:focus:ring-offset-zinc-800"
|
|
||||||
aria-label="About"
|
|
||||||
>
|
|
||||||

|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={openChangelog}
|
|
||||||
class="flex justify-center items-center w-9 h-9 text-xl rounded-lg font-IconFamily bg-zinc-100 dark:bg-zinc-700 transition-all duration-200 hover:scale-105 active:scale-95 focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:focus:ring-offset-zinc-800"
|
|
||||||
aria-label="Changelog"
|
|
||||||
>
|
|
||||||

|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={openPrivacyStatement}
|
|
||||||
class="flex justify-center items-center w-9 h-9 text-xl rounded-lg font-IconFamily bg-zinc-100 dark:bg-zinc-700 transition-all duration-200 hover:scale-105 active:scale-95 focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:focus:ring-offset-zinc-800"
|
|
||||||
aria-label="Privacy Statement"
|
|
||||||
>
|
|
||||||

|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={handleClose}
|
|
||||||
class="flex justify-center items-center w-9 h-9 text-xl rounded-lg font-IconFamily bg-zinc-100 dark:bg-zinc-700 transition-all duration-200 hover:scale-105 active:scale-95 focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:focus:ring-offset-zinc-800"
|
|
||||||
aria-label="Close settings"
|
|
||||||
>
|
|
||||||

|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Body: left nav + content -->
|
<!-- Body: left nav + content -->
|
||||||
<div class="flex flex-1 min-h-0 overflow-hidden">
|
<div class="flex flex-1 min-h-0 overflow-hidden">
|
||||||
@@ -378,79 +370,47 @@
|
|||||||
: 'w-[260px] px-4 py-5'}"
|
: 'w-[260px] px-4 py-5'}"
|
||||||
aria-label="Settings categories"
|
aria-label="Settings categories"
|
||||||
>
|
>
|
||||||
<div class="relative flex flex-col flex-1 min-h-0 gap-5 overflow-y-auto no-scrollbar" bind:this={navTrackEl}>
|
<SidebarNav
|
||||||
{#if activePage === "settings" && indicatorReady}
|
groups={navGroups}
|
||||||
<div
|
selectedId={selectedNavId}
|
||||||
class="absolute left-0 right-0 top-0 z-0 rounded-lg bg-zinc-200/90 dark:bg-zinc-700/90 pointer-events-none"
|
onselect={selectNavItem}
|
||||||
style="transform: translateY({indicatorY}px); height: {indicatorH}px; transition: {$settingsState.animations
|
/>
|
||||||
? 'transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), height 0.28s cubic-bezier(0.22, 1, 0.36, 1)'
|
|
||||||
: 'none'};"
|
<div class="mt-4 flex shrink-0 flex-col gap-1 border-t border-zinc-200/60 pt-4 dark:border-zinc-700/50">
|
||||||
></div>
|
{#if ghReleaseUpdateEnabled}
|
||||||
|
<div class="px-3 pb-2">
|
||||||
|
{#if ghReleaseUpdate?.available}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={openGhRelease}
|
||||||
|
class="mb-1 px-1.5 py-0.5 text-[10px] font-semibold leading-tight text-white rounded-full bg-amber-500 hover:bg-amber-600 dark:bg-amber-600 dark:hover:bg-amber-500 transition-colors duration-200"
|
||||||
|
title="Open GitHub release"
|
||||||
|
>
|
||||||
|
Update — {ghReleaseUpdate.label}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<p class="text-[9px] leading-tight text-zinc-500 dark:text-zinc-400">
|
||||||
|
{ghReleaseChannelLabel ?? "GitHub build"} — do not upload to stores.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if activePage === "settings"}
|
{#each utilityActions as action (action.label)}
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<p
|
|
||||||
class="relative z-10 px-3 mb-1.5 text-sm font-semibold tracking-wider uppercase text-zinc-400 dark:text-zinc-500"
|
|
||||||
>
|
|
||||||
User Settings
|
|
||||||
</p>
|
|
||||||
{#each userNav as item (item.id)}
|
|
||||||
{@render navButton(item)}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<p
|
|
||||||
class="relative z-10 px-3 mb-1.5 text-sm font-semibold tracking-wider uppercase text-zinc-400 dark:text-zinc-500"
|
|
||||||
>
|
|
||||||
App Settings
|
|
||||||
</p>
|
|
||||||
{#each appNav as item (item.id)}
|
|
||||||
{@render navButton(item)}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else if activePage === "shortcuts"}
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<p
|
|
||||||
class="px-3 mb-1.5 text-sm font-semibold tracking-wider uppercase text-zinc-400 dark:text-zinc-500"
|
|
||||||
>
|
|
||||||
Shortcuts
|
|
||||||
</p>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="w-full px-3 py-2.5 text-left text-lg rounded-lg font-medium bg-zinc-200/80 dark:bg-zinc-700/80 text-zinc-900 dark:text-white"
|
onclick={action.onclick}
|
||||||
|
class="flex h-10 w-full items-center gap-3 rounded-lg px-3 text-left text-[16px] font-medium text-zinc-600 transition-[color,background-color,transform] duration-150 hover:bg-zinc-200/70 hover:text-zinc-950 active:scale-[0.96] focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:text-zinc-300 dark:hover:bg-zinc-700/70 dark:hover:text-white dark:focus:ring-offset-zinc-900"
|
||||||
>
|
>
|
||||||
Shortcuts
|
<span class="w-5 text-center font-IconFamily text-[18px]" aria-hidden="true">{action.icon}</span>
|
||||||
|
<span>{action.label}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
{/each}
|
||||||
{:else}
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<p
|
|
||||||
class="px-3 mb-1.5 text-sm font-semibold tracking-wider uppercase text-zinc-400 dark:text-zinc-500"
|
|
||||||
>
|
|
||||||
Themes
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="w-full px-3 py-2.5 text-left text-lg rounded-lg font-medium bg-zinc-200/80 dark:bg-zinc-700/80 text-zinc-900 dark:text-white"
|
|
||||||
>
|
|
||||||
Themes
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => openFeedback()}
|
onclick={() => openFeedback()}
|
||||||
class="shrink-0 mt-4 w-full px-3 py-2.5 text-left text-[18px] font-medium rounded-lg transition-all duration-200
|
class="mt-2 flex w-full items-center gap-3 rounded-lg bg-zinc-200/70 px-3 py-2.5 text-left text-[16px] font-medium text-zinc-700 transition-[color,background-color,transform] duration-150 hover:bg-zinc-300/80 active:scale-[0.96] focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:bg-zinc-800/80 dark:text-zinc-200 dark:hover:bg-zinc-700 dark:focus:ring-offset-zinc-900"
|
||||||
text-zinc-700 dark:text-zinc-200
|
|
||||||
bg-zinc-200/70 dark:bg-zinc-800/80
|
|
||||||
hover:bg-zinc-300/80 dark:hover:bg-zinc-700
|
|
||||||
hover:scale-[1.02] active:scale-95
|
|
||||||
focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:focus:ring-offset-zinc-900"
|
|
||||||
>
|
>
|
||||||
<span class="flex items-center gap-2">
|
|
||||||
<svg
|
<svg
|
||||||
class="w-5 h-5 shrink-0"
|
class="w-5 h-5 shrink-0"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -464,10 +424,26 @@
|
|||||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Send us feedback!</span>
|
<span>Send us feedback!</span>
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{#if isStoreView}
|
||||||
|
<div class="min-w-0 min-h-0 flex-1">
|
||||||
|
<Store
|
||||||
|
activeTab={activePage as StoreTab}
|
||||||
|
searchTerm={storeSearchTerm}
|
||||||
|
{selectedBackgroundCategory}
|
||||||
|
setActiveTab={(tab) => {
|
||||||
|
activePage = tab;
|
||||||
|
if (tab === "themes") activeThemeView = "theme-store";
|
||||||
|
else activeBackgroundView = "background-store";
|
||||||
|
}}
|
||||||
|
setSearchTerm={(term) => (storeSearchTerm = term)}
|
||||||
|
setBackgroundCategories={(categories) => (backgroundCategories = categories)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<div class="flex flex-col flex-1 min-w-0 min-h-0">
|
<div class="flex flex-col flex-1 min-w-0 min-h-0">
|
||||||
<div class="shrink-0 px-6 pt-5 pb-3">
|
<div class="shrink-0 px-6 pt-5 pb-3">
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-zinc-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-zinc-900 dark:text-white">
|
||||||
@@ -476,6 +452,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-h-0 px-4 pb-8 overflow-y-auto no-scrollbar">
|
<div class="flex-1 min-h-0 px-4 pb-8 overflow-y-auto no-scrollbar">
|
||||||
{#if activePage === "settings"}
|
{#if activePage === "settings"}
|
||||||
|
{#if activeSection === "shortcuts"}
|
||||||
|
<Shortcuts />
|
||||||
|
{:else}
|
||||||
<Settings
|
<Settings
|
||||||
showColourPicker={openColourPicker}
|
showColourPicker={openColourPicker}
|
||||||
showFontPicker={openFontPicker}
|
showFontPicker={openFontPicker}
|
||||||
@@ -483,13 +462,63 @@
|
|||||||
showCloudPanel={openCloudPanel}
|
showCloudPanel={openCloudPanel}
|
||||||
{activeSection}
|
{activeSection}
|
||||||
/>
|
/>
|
||||||
{:else if activePage === "shortcuts"}
|
{/if}
|
||||||
<Shortcuts />
|
{:else if activePage === "themes"}
|
||||||
|
<Theme section="themes" />
|
||||||
{:else}
|
{:else}
|
||||||
<Theme />
|
<Theme section="backgrounds" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet compactSettings()}
|
||||||
|
<div
|
||||||
|
class="flex h-full min-h-0 flex-col gap-2 overflow-hidden bg-white dark:bg-zinc-800 dark:text-white"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="grid shrink-0 place-items-center border-b border-zinc-200/40 dark:border-zinc-700/40"
|
||||||
|
>
|
||||||
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<img
|
||||||
|
src={resolveExtensionAssetUrl(darkLogo)}
|
||||||
|
class="w-4/5 dark:hidden"
|
||||||
|
alt="BetterSEQTA+"
|
||||||
|
onclick={handleDevModeToggle}
|
||||||
|
/>
|
||||||
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<img
|
||||||
|
src={resolveExtensionAssetUrl(lightLogo)}
|
||||||
|
class="hidden w-4/5 dark:block"
|
||||||
|
alt="BetterSEQTA+"
|
||||||
|
onclick={handleDevModeToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-h-0 flex-1 overflow-hidden">
|
||||||
|
<TabbedContainer
|
||||||
|
bind:activeTab={compactActiveTab}
|
||||||
|
tabs={[
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
Content: Settings,
|
||||||
|
props: {
|
||||||
|
showColourPicker: openColourPicker,
|
||||||
|
showFontPicker: openFontPicker,
|
||||||
|
showDisclaimer,
|
||||||
|
showCloudPanel: openCloudPanel,
|
||||||
|
activeSection: "all",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: "Shortcuts", Content: Shortcuts },
|
||||||
|
{ title: "Themes", Content: Theme },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -500,7 +529,7 @@
|
|||||||
? 'dark'
|
? 'dark'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
{@render settingsShell()}
|
{@render compactSettings()}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
@@ -520,6 +549,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="relative z-10 w-[min(1180px,96vw)] h-[min(860px,92vh)] no-scrollbar overflow-clip"
|
class="relative z-10 w-[min(1180px,96vw)] h-[min(860px,92vh)] no-scrollbar overflow-clip"
|
||||||
|
data-settings-panel
|
||||||
>
|
>
|
||||||
{@render settingsShell()}
|
{@render settingsShell()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -160,7 +160,11 @@
|
|||||||
notificationCollector: "features",
|
notificationCollector: "features",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showsSection = (section: string) =>
|
||||||
|
activeSection === "all" || activeSection === section;
|
||||||
|
|
||||||
const pluginBelongsInSection = (pluginId: string) =>
|
const pluginBelongsInSection = (pluginId: string) =>
|
||||||
|
activeSection === "all" ||
|
||||||
(pluginSectionById[pluginId] ?? "features") === activeSection;
|
(pluginSectionById[pluginId] ?? "features") === activeSection;
|
||||||
|
|
||||||
|
|
||||||
@@ -191,7 +195,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<div class="flex flex-col divide-y divide-zinc-100 dark:divide-zinc-700">
|
<div class="flex flex-col divide-y divide-zinc-100 dark:divide-zinc-700">
|
||||||
{#if activeSection === "account"}
|
{#if showsSection("account")}
|
||||||
{@render Setting({
|
{@render Setting({
|
||||||
title: "Connect Mobile App",
|
title: "Connect Mobile App",
|
||||||
description: "Link your SEQTA session to DesQTA — the modern desktop and mobile app for SEQTA Learn",
|
description: "Link your SEQTA session to DesQTA — the modern desktop and mobile app for SEQTA Learn",
|
||||||
@@ -220,7 +224,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if activeSection === "general"}
|
{#if showsSection("general")}
|
||||||
{#each [
|
{#each [
|
||||||
...(!isEngage
|
...(!isEngage
|
||||||
? [
|
? [
|
||||||
@@ -266,16 +270,6 @@
|
|||||||
onChange: (isOn: boolean) => settingsState.timeFormat = isOn ? "12" : "24"
|
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",
|
title: "Default Page",
|
||||||
description: "Choose which page loads first when you open SEQTA",
|
description: "Choose which page loads first when you open SEQTA",
|
||||||
@@ -335,7 +329,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if activeSection === "appearance"}
|
{#if showsSection("appearance")}
|
||||||
{#each [
|
{#each [
|
||||||
{
|
{
|
||||||
title: "Custom Theme Colour",
|
title: "Custom Theme Colour",
|
||||||
@@ -410,7 +404,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if activeSection === "general"}
|
{#if showsSection("general")}
|
||||||
<div class="border-none">
|
<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="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">
|
<div class="flex justify-between items-center px-5 py-4">
|
||||||
@@ -590,7 +584,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if activeSection === "advanced"}
|
{#if showsSection("advanced")}
|
||||||
{@render Setting({
|
{@render Setting({
|
||||||
title: "BetterSEQTA+",
|
title: "BetterSEQTA+",
|
||||||
description: "Enables BetterSEQTA+ features",
|
description: "Enables BetterSEQTA+ features",
|
||||||
|
|||||||
@@ -3,21 +3,32 @@
|
|||||||
import ThemeSelector from "@/interface/components/themes/ThemeSelector.svelte"
|
import ThemeSelector from "@/interface/components/themes/ThemeSelector.svelte"
|
||||||
import { standalone } from "@/interface/utils/standalone.svelte"
|
import { standalone } from "@/interface/utils/standalone.svelte"
|
||||||
|
|
||||||
|
let { section = "all" } = $props<{
|
||||||
|
section?: "all" | "themes" | "backgrounds";
|
||||||
|
}>();
|
||||||
|
|
||||||
// backgrounds
|
// backgrounds
|
||||||
let selectedBackground = $state<string | null>(null);
|
let selectedBackground = $state<string | null>(null);
|
||||||
let selectNoBackground = $state<() => void>(() => { });
|
let selectNoBackground = $state<() => void>(() => { });
|
||||||
|
|
||||||
let clearTheme = $derived(selectedBackground !== null);
|
let clearTheme = $derived(selectedBackground !== null);
|
||||||
let editMode = $state<boolean>(false);
|
let editMode = $state<boolean>(false);
|
||||||
|
let showBackgrounds = $derived(section !== "themes");
|
||||||
|
let showThemes = $derived(section !== "backgrounds");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="py-4">
|
<div class="py-4">
|
||||||
{#if !standalone.standalone}
|
{#if !standalone.standalone}
|
||||||
|
{#if showBackgrounds}
|
||||||
<button
|
<button
|
||||||
onclick={() => selectNoBackground()}
|
onclick={() => selectNoBackground()}
|
||||||
class="w-full px-4 py-3 mb-4 text-base dark:text-white transition rounded-xl bg-zinc-200 dark:bg-zinc-700/50">
|
disabled={section === "backgrounds" && !clearTheme}
|
||||||
{ clearTheme ? 'Clear Theme' : 'Select a Theme' }
|
class="w-full px-4 py-3 mb-4 text-base dark:text-white transition rounded-xl bg-zinc-200 dark:bg-zinc-700/50 disabled:opacity-50">
|
||||||
|
{section === "all"
|
||||||
|
? clearTheme ? 'Clear Theme' : 'Select a Theme'
|
||||||
|
: clearTheme ? 'Clear Background' : 'No Background Selected'}
|
||||||
</button>
|
</button>
|
||||||
|
{/if}
|
||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<button
|
<button
|
||||||
onclick={() => editMode = !editMode}
|
onclick={() => editMode = !editMode}
|
||||||
@@ -26,8 +37,12 @@
|
|||||||
<span class="font-IconFamily">{editMode ? '\ue9e4' : '\uec38'}</span>
|
<span class="font-IconFamily">{editMode ? '\ue9e4' : '\uec38'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if showBackgrounds}
|
||||||
<BackgroundSelector isEditMode={editMode} bind:selectedBackground={selectedBackground} bind:selectNoBackground={selectNoBackground} />
|
<BackgroundSelector isEditMode={editMode} bind:selectedBackground={selectedBackground} bind:selectNoBackground={selectNoBackground} />
|
||||||
<ThemeSelector isEditMode={editMode} />
|
{/if}
|
||||||
|
{#if showThemes}
|
||||||
|
<ThemeSelector isEditMode={editMode} showNavigation={section === "all"} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-center items-center w-full h-full">
|
<div class="flex justify-center items-center w-full h-full">
|
||||||
|
|||||||
@@ -5,12 +5,10 @@
|
|||||||
import CoverSwiper from '../components/store/CoverSwiper.svelte';
|
import CoverSwiper from '../components/store/CoverSwiper.svelte';
|
||||||
import ThemeGrid from '../components/store/ThemeGrid.svelte';
|
import ThemeGrid from '../components/store/ThemeGrid.svelte';
|
||||||
import SkeletonLoader from '../components/SkeletonLoader.svelte';
|
import SkeletonLoader from '../components/SkeletonLoader.svelte';
|
||||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState'
|
|
||||||
import type { Theme } from '../types/Theme'
|
import type { Theme } from '../types/Theme'
|
||||||
import { visibleStoreThemes, buildCoverSlidesForThemes, normalizeStoreTheme } from '@/interface/utils/themeStoreFlavours'
|
import { visibleStoreThemes, buildCoverSlidesForThemes, normalizeStoreTheme } from '@/interface/utils/themeStoreFlavours'
|
||||||
import browser from 'webextension-polyfill'
|
import browser from 'webextension-polyfill'
|
||||||
import ThemeModal from '../components/store/ThemeModal.svelte'
|
import ThemeModal from '../components/store/ThemeModal.svelte'
|
||||||
import Header from '../components/store/Header.svelte'
|
|
||||||
import { themeUpdates } from '../hooks/ThemeUpdates'
|
import { themeUpdates } from '../hooks/ThemeUpdates'
|
||||||
import { ThemeManager } from '@/plugins/built-in/themes/theme-manager'
|
import { ThemeManager } from '@/plugins/built-in/themes/theme-manager'
|
||||||
|
|
||||||
@@ -21,6 +19,22 @@
|
|||||||
import { consumePendingHighlightThemeId } from '@/seqta/utils/openThemeStoreWithHighlight'
|
import { consumePendingHighlightThemeId } from '@/seqta/utils/openThemeStoreWithHighlight'
|
||||||
|
|
||||||
const themeManager = ThemeManager.getInstance();
|
const themeManager = ThemeManager.getInstance();
|
||||||
|
type StoreTab = 'themes' | 'backgrounds';
|
||||||
|
let {
|
||||||
|
activeTab,
|
||||||
|
searchTerm,
|
||||||
|
selectedBackgroundCategory,
|
||||||
|
setActiveTab,
|
||||||
|
setSearchTerm,
|
||||||
|
setBackgroundCategories,
|
||||||
|
} = $props<{
|
||||||
|
activeTab: StoreTab;
|
||||||
|
searchTerm: string;
|
||||||
|
selectedBackgroundCategory: string;
|
||||||
|
setActiveTab: (tab: StoreTab) => void;
|
||||||
|
setSearchTerm: (term: string) => void;
|
||||||
|
setBackgroundCategories: (categories: string[]) => void;
|
||||||
|
}>();
|
||||||
let cloudLoggedIn = $state(cloudAuth.state.isLoggedIn);
|
let cloudLoggedIn = $state(cloudAuth.state.isLoggedIn);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -29,7 +43,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// State variables
|
// State variables
|
||||||
let searchTerm = $state('');
|
|
||||||
let themes = $state<Theme[]>([]);
|
let themes = $state<Theme[]>([]);
|
||||||
|
|
||||||
/** Grid/search/cover: hides flat-listed slaves when API sends them */
|
/** Grid/search/cover: hides flat-listed slaves when API sends them */
|
||||||
@@ -38,10 +51,8 @@
|
|||||||
/** Cover marquee slides (master + flavour imagery for top masters) */
|
/** Cover marquee slides (master + flavour imagery for top masters) */
|
||||||
let coverSlides = $derived(buildCoverSlidesForThemes(listThemes.slice(0, 3)));
|
let coverSlides = $derived(buildCoverSlidesForThemes(listThemes.slice(0, 3)));
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let darkMode = $state(false);
|
|
||||||
let displayTheme = $state<Theme | null>(null);
|
let displayTheme = $state<Theme | null>(null);
|
||||||
let currentThemes = $state<string[]>([]);
|
let currentThemes = $state<string[]>([]);
|
||||||
let activeTab = $state('themes');
|
|
||||||
|
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
let fetchAttempt = $state(0);
|
let fetchAttempt = $state(0);
|
||||||
@@ -69,14 +80,6 @@
|
|||||||
displayTheme = theme;
|
displayTheme = theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setSearchTerm = (term: string) => {
|
|
||||||
searchTerm = term;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setActiveTab = (tab: string) => {
|
|
||||||
activeTab = tab;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Featured themes first; within each group, newest by `created_at` (API: Unix seconds). */
|
/** Featured themes first; within each group, newest by `created_at` (API: Unix seconds). */
|
||||||
function compareStoreThemes(a: Theme, b: Theme): number {
|
function compareStoreThemes(a: Theme, b: Theme): number {
|
||||||
const fa = a.featured === true ? 1 : 0;
|
const fa = a.featured === true ? 1 : 0;
|
||||||
@@ -155,8 +158,8 @@
|
|||||||
const match = themes.find((t) => t.id === themeId)
|
const match = themes.find((t) => t.id === themeId)
|
||||||
?? themes.find((t) => t.flavours?.some((f) => f.id === themeId));
|
?? themes.find((t) => t.flavours?.some((f) => f.id === themeId));
|
||||||
if (match) {
|
if (match) {
|
||||||
activeTab = 'themes';
|
setActiveTab('themes');
|
||||||
searchTerm = '';
|
setSearchTerm('');
|
||||||
displayTheme = match;
|
displayTheme = match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,9 +178,6 @@
|
|||||||
await fetchThemes();
|
await fetchThemes();
|
||||||
await fetchCurrentThemes();
|
await fetchCurrentThemes();
|
||||||
|
|
||||||
darkMode = (await browser.storage.local.get('DarkMode')).DarkMode === 'true';
|
|
||||||
darkMode = $settingsState.DarkMode;
|
|
||||||
|
|
||||||
const pending = consumePendingHighlightThemeId();
|
const pending = consumePendingHighlightThemeId();
|
||||||
if (pending) focusThemeById(pending);
|
if (pending) focusThemeById(pending);
|
||||||
|
|
||||||
@@ -242,11 +242,9 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-screen h-screen bg-white {darkMode ? 'dark' : ''}">
|
<div class="relative flex h-full min-h-0 flex-col overflow-hidden text-zinc-900 dark:text-white">
|
||||||
<div class="h-full overflow-y-scroll bg-zinc-200/50 dark:bg-zinc-900 dark:text-white pt-[4.25rem]">
|
<main class="min-h-0 flex-1 overflow-y-auto bg-zinc-50/80 dark:bg-zinc-900/40">
|
||||||
<Header {searchTerm} {setSearchTerm} {darkMode} {activeTab} {setActiveTab} />
|
<div class={activeTab === 'backgrounds' ? 'h-full' : 'px-6 py-6 md:px-8 lg:px-10'}>
|
||||||
|
|
||||||
<div class={`px-12 h-full ${activeTab === 'backgrounds' ? 'pt-0' : 'pt-6 md:px-24 lg:px-48'}`}>
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="grid grid-cols-1 gap-4 py-12 mx-auto sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 py-12 mx-auto sm:grid-cols-2 lg:grid-cols-3">
|
||||||
@@ -263,7 +261,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="mt-6 px-4 py-2 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700"
|
class="mt-6 rounded-lg bg-zinc-900 px-4 py-2 font-medium text-white transition-colors duration-200 hover:bg-zinc-700 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-white"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
loading = true;
|
loading = true;
|
||||||
error = null;
|
error = null;
|
||||||
@@ -312,11 +310,15 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if activeTab === 'backgrounds'}
|
{:else if activeTab === 'backgrounds'}
|
||||||
<Backgrounds {searchTerm} />
|
<Backgrounds
|
||||||
|
{searchTerm}
|
||||||
|
selectedCategory={selectedBackgroundCategory}
|
||||||
|
onCategoriesChange={setBackgroundCategories}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
{#if showSignInOverlay}
|
{#if showSignInOverlay}
|
||||||
<SignInToFavoriteModal onClose={() => (showSignInOverlay = false)} />
|
<SignInToFavoriteModal onClose={() => (showSignInOverlay = false)} />
|
||||||
|
|||||||
@@ -75,12 +75,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-left: 8px;
|
margin: 0 48px 0 8px;
|
||||||
margin-right: 48px;
|
line-height: 1;
|
||||||
height: 100%;
|
|
||||||
margin-bottom: 0;
|
|
||||||
line-height: 32px;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateX(100%);
|
transform: translateX(300%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ const messageFoldersPlugin = {
|
|||||||
|
|
||||||
let activeFolderId: string | null = null;
|
let activeFolderId: string | null = null;
|
||||||
let messageListObserver: MutationObserver | null = null;
|
let messageListObserver: MutationObserver | null = null;
|
||||||
|
let observedMessageList: Element | null = null;
|
||||||
let sidebarObserver: MutationObserver | null = null;
|
let sidebarObserver: MutationObserver | null = null;
|
||||||
let actionsObserver: MutationObserver | null = null;
|
let actionsObserver: MutationObserver | null = null;
|
||||||
let openDropdown: HTMLElement | null = null;
|
let openDropdown: HTMLElement | null = null;
|
||||||
@@ -218,9 +219,6 @@ const messageFoldersPlugin = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMessageInAnyCustomFolder = (messageId: string): boolean =>
|
|
||||||
getAssignedFolderIds(messageId, getAssignments()).length > 0;
|
|
||||||
|
|
||||||
const shouldShowBadgesInList = (): boolean => {
|
const shouldShowBadgesInList = (): boolean => {
|
||||||
return api.settings.showTagsInAllMessages || activeFolderId !== null;
|
return api.settings.showTagsInAllMessages || activeFolderId !== null;
|
||||||
};
|
};
|
||||||
@@ -837,20 +835,9 @@ const messageFoldersPlugin = {
|
|||||||
const messageItems = getMessageListItems();
|
const messageItems = getMessageListItems();
|
||||||
const moreBtn = document.querySelector("[class*='MessageList__MessageList___'] ol > button");
|
const moreBtn = document.querySelector("[class*='MessageList__MessageList___'] ol > button");
|
||||||
if (activeFolderId === null) {
|
if (activeFolderId === null) {
|
||||||
if (api.settings.hideFolderedMessagesInAll) {
|
|
||||||
for (const li of messageItems) {
|
|
||||||
const msgId = li.getAttribute("data-message");
|
|
||||||
if (msgId && isMessageInAnyCustomFolder(msgId)) {
|
|
||||||
li.classList.add("bsplus-folder-hidden");
|
|
||||||
} else {
|
|
||||||
li.classList.remove("bsplus-folder-hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const li of messageItems) {
|
for (const li of messageItems) {
|
||||||
li.classList.remove("bsplus-folder-hidden");
|
li.classList.remove("bsplus-folder-hidden");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (moreBtn) (moreBtn as HTMLElement).classList.remove("bsplus-folder-hidden");
|
if (moreBtn) (moreBtn as HTMLElement).classList.remove("bsplus-folder-hidden");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -868,14 +855,29 @@ const messageFoldersPlugin = {
|
|||||||
|
|
||||||
const setupMessageListObserver = () => {
|
const setupMessageListObserver = () => {
|
||||||
const messageList = document.querySelector("[class*='MessageList__MessageList___'] ol");
|
const messageList = document.querySelector("[class*='MessageList__MessageList___'] ol");
|
||||||
if (!messageList || messageListObserver) return;
|
if (!messageList || messageList === observedMessageList) return;
|
||||||
messageListObserver = new MutationObserver(() => {
|
messageListObserver?.disconnect();
|
||||||
|
observedMessageList = messageList;
|
||||||
|
messageListObserver = new MutationObserver((mutations) => {
|
||||||
|
const messageRowsChanged = mutations.some((mutation) =>
|
||||||
|
mutation.type === "childList"
|
||||||
|
? mutation.target === messageList
|
||||||
|
: mutation.target instanceof Element &&
|
||||||
|
mutation.target.parentElement === messageList &&
|
||||||
|
mutation.target.matches("li[data-message]"),
|
||||||
|
);
|
||||||
|
if (!messageRowsChanged) return;
|
||||||
applyBadges();
|
applyBadges();
|
||||||
applyFolderFilter();
|
applyFolderFilter();
|
||||||
attachDragListeners();
|
attachDragListeners();
|
||||||
attachContextMenuListeners();
|
attachContextMenuListeners();
|
||||||
});
|
});
|
||||||
messageListObserver.observe(messageList, { childList: true, subtree: false });
|
messageListObserver.observe(messageList, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["class", "data-message"],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachContextMenuListeners = () => {
|
const attachContextMenuListeners = () => {
|
||||||
@@ -925,6 +927,7 @@ const messageFoldersPlugin = {
|
|||||||
renderSidebarFolders();
|
renderSidebarFolders();
|
||||||
attachNativeSidebarListeners();
|
attachNativeSidebarListeners();
|
||||||
}
|
}
|
||||||
|
setupMessageListObserver();
|
||||||
});
|
});
|
||||||
sidebarObserver.observe(sidebar, { childList: true, subtree: true });
|
sidebarObserver.observe(sidebar, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
@@ -937,12 +940,6 @@ const messageFoldersPlugin = {
|
|||||||
applyBadges();
|
applyBadges();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
unregisters.push(
|
|
||||||
api.settings.onChange("hideFolderedMessagesInAll", () => {
|
|
||||||
applyFolderFilter();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
for (const u of unregisters) u.unregister();
|
for (const u of unregisters) u.unregister();
|
||||||
messageListObserver?.disconnect();
|
messageListObserver?.disconnect();
|
||||||
|
|||||||
@@ -9,12 +9,6 @@ const settings = defineSettings({
|
|||||||
description:
|
description:
|
||||||
"When off, folder tags are not shown on the message list until you select a folder.",
|
"When off, folder tags are not shown on the message list until you select a folder.",
|
||||||
}),
|
}),
|
||||||
hideFolderedMessagesInAll: booleanSetting({
|
|
||||||
default: true,
|
|
||||||
title: "Hide foldered messages in All Messages",
|
|
||||||
description:
|
|
||||||
"When on, messages assigned to a custom folder are hidden from the inbox until you open that folder.",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineLazyPlugin({
|
export default defineLazyPlugin({
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export const SYNCABLE_PLUGIN_SETTING_DEFAULTS: Record<
|
|||||||
"background-music": { volume: 0.5, pauseOnHidden: true },
|
"background-music": { volume: 0.5, pauseOnHidden: true },
|
||||||
messageFolders: {
|
messageFolders: {
|
||||||
showTagsInAllMessages: true,
|
showTagsInAllMessages: true,
|
||||||
hideFolderedMessagesInAll: true,
|
|
||||||
},
|
},
|
||||||
"enhanced-navigation": { autoScrollOnClick: false },
|
"enhanced-navigation": { autoScrollOnClick: false },
|
||||||
"global-search": {
|
"global-search": {
|
||||||
|
|||||||
@@ -1,39 +1,9 @@
|
|||||||
import { unmount } from "svelte";
|
|
||||||
|
|
||||||
let remove: () => void;
|
|
||||||
|
|
||||||
export async function OpenStorePage(): Promise<void> {
|
export async function OpenStorePage(): Promise<void> {
|
||||||
remove = await renderStore();
|
const [{ requestSettingsDestination }, { openSettingsPopup }] =
|
||||||
}
|
await Promise.all([
|
||||||
|
import("@/seqta/utils/settingsNavigation"),
|
||||||
export async function renderStore() {
|
import("@/seqta/utils/setupSettingsButton"),
|
||||||
const [{ default: renderSvelte }, { default: Store }] = await Promise.all([
|
|
||||||
import("@/interface/main"),
|
|
||||||
import("@/interface/pages/store.svelte"),
|
|
||||||
]);
|
]);
|
||||||
|
requestSettingsDestination({ page: "themes", view: "store" });
|
||||||
const container =
|
await openSettingsPopup();
|
||||||
document.querySelector("#container") ??
|
|
||||||
document.getElementById("content") ??
|
|
||||||
document.body;
|
|
||||||
|
|
||||||
document.getElementById("store")?.remove();
|
|
||||||
|
|
||||||
const child = document.createElement("div");
|
|
||||||
child.id = "store";
|
|
||||||
container.appendChild(child);
|
|
||||||
|
|
||||||
const shadow = child.attachShadow({ mode: "open" });
|
|
||||||
const app = renderSvelte(Store, shadow);
|
|
||||||
|
|
||||||
return () => unmount(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeStore() {
|
|
||||||
document.getElementById("store")!.classList.add("hide");
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
remove();
|
|
||||||
document.getElementById("store")!.remove();
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,12 @@ async function loadSettingsUi(extensionPopup: HTMLElement): Promise<void> {
|
|||||||
const mount = () => renderSvelte(Settings, shadow);
|
const mount = () => renderSvelte(Settings, shadow);
|
||||||
|
|
||||||
if ("requestIdleCallback" in window) {
|
if ("requestIdleCallback" in window) {
|
||||||
requestIdleCallback(mount);
|
await new Promise<void>((resolve) => {
|
||||||
|
requestIdleCallback(() => {
|
||||||
|
mount();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
mount();
|
mount();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,23 @@ export const closeExtensionPopup = (extensionPopup?: HTMLElement) => {
|
|||||||
|
|
||||||
extensionPopup.classList.add("hide");
|
extensionPopup.classList.add("hide");
|
||||||
if (settingsState.animations) {
|
if (settingsState.animations) {
|
||||||
|
const panel = extensionPopup.shadowRoot?.querySelector<HTMLElement>(
|
||||||
|
"[data-settings-panel]",
|
||||||
|
);
|
||||||
animate(1, 0, {
|
animate(1, 0, {
|
||||||
onUpdate: (progress) => {
|
onUpdate: (progress) => {
|
||||||
extensionPopup.style.opacity = Math.max(0, progress).toString();
|
extensionPopup.style.opacity = Math.max(0, progress).toString();
|
||||||
},
|
},
|
||||||
type: "spring",
|
duration: 0.18,
|
||||||
stiffness: 520,
|
ease: "easeIn",
|
||||||
damping: 20,
|
|
||||||
});
|
});
|
||||||
|
if (panel) {
|
||||||
|
animate(
|
||||||
|
panel,
|
||||||
|
{ scale: [1, 0], opacity: [1, 0] },
|
||||||
|
{ duration: 0.18, ease: "easeIn" },
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
extensionPopup.style.opacity = "0";
|
extensionPopup.style.opacity = "0";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,10 +311,10 @@ export async function loadEngageHomePage(): Promise<void> {
|
|||||||
const engageHomeBody = stringToHTML(/* html */ `
|
const engageHomeBody = stringToHTML(/* html */ `
|
||||||
<div class="home-root" id="engage-home-root">
|
<div class="home-root" id="engage-home-root">
|
||||||
<div class="home-container" id="engage-home-container">
|
<div class="home-container" id="engage-home-container">
|
||||||
<div class="bsplus-rounded shortcut-container">
|
<div class="border shortcut-container">
|
||||||
<div class="bsplus-rounded shortcuts" id="shortcuts"></div>
|
<div class="border shortcuts" id="shortcuts"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bsplus-rounded timetable-container">
|
<div class="border timetable-container">
|
||||||
<div class="home-subtitle">
|
<div class="home-subtitle">
|
||||||
<div class="engage-timetable-title-cluster">
|
<div class="engage-timetable-title-cluster">
|
||||||
<h2 id="engage-home-lesson-subtitle">Today's Lessons</h2>
|
<h2 id="engage-home-lesson-subtitle">Today's Lessons</h2>
|
||||||
@@ -331,7 +331,7 @@ export async function loadEngageHomePage(): Promise<void> {
|
|||||||
</div>
|
</div>
|
||||||
<div class="day-container loading" id="engage-day-container"></div>
|
<div class="day-container loading" id="engage-day-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bsplus-rounded notices-container">
|
<div class="border notices-container">
|
||||||
<div style="display: flex; justify-content: space-between">
|
<div style="display: flex; justify-content: space-between">
|
||||||
<h2 class="home-subtitle">Notices</h2>
|
<h2 class="home-subtitle">Notices</h2>
|
||||||
<input type="date" id="engage-notices-date" />
|
<input type="date" id="engage-notices-date" />
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ export async function loadHomePage() {
|
|||||||
|
|
||||||
const skeletonStructure = stringToHTML(/* html */ `
|
const skeletonStructure = stringToHTML(/* html */ `
|
||||||
<div class="home-container" id="home-container">
|
<div class="home-container" id="home-container">
|
||||||
<div class="bsplus-rounded shortcut-container">
|
<div class="border shortcut-container">
|
||||||
<div class="bsplus-rounded shortcuts" id="shortcuts"></div>
|
<div class="border shortcuts" id="shortcuts"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bsplus-rounded timetable-container">
|
<div class="border timetable-container">
|
||||||
<div class="home-subtitle">
|
<div class="home-subtitle">
|
||||||
<h2 id="home-lesson-subtitle">Today's Lessons</h2>
|
<h2 id="home-lesson-subtitle">Today's Lessons</h2>
|
||||||
<div class="timetable-arrows">
|
<div class="timetable-arrows">
|
||||||
@@ -80,7 +80,7 @@ export async function loadHomePage() {
|
|||||||
<div class="day-container loading" id="day-container">
|
<div class="day-container loading" id="day-container">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bsplus-rounded upcoming-container">
|
<div class="border upcoming-container">
|
||||||
<div class="upcoming-title">
|
<div class="upcoming-title">
|
||||||
<h2 class="home-subtitle">Upcoming Assessments</h2>
|
<h2 class="home-subtitle">Upcoming Assessments</h2>
|
||||||
<div class="upcoming-filters" id="upcoming-filters"></div>
|
<div class="upcoming-filters" id="upcoming-filters"></div>
|
||||||
@@ -88,7 +88,7 @@ export async function loadHomePage() {
|
|||||||
<div class="upcoming-items loading" id="upcoming-items">
|
<div class="upcoming-items loading" id="upcoming-items">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bsplus-rounded notices-container">
|
<div class="border notices-container">
|
||||||
<div style="display: flex; justify-content: space-between">
|
<div style="display: flex; justify-content: space-between">
|
||||||
<h2 class="home-subtitle">Notices</h2>
|
<h2 class="home-subtitle">Notices</h2>
|
||||||
<input type="date" />
|
<input type="date" />
|
||||||
|
|||||||
@@ -11,15 +11,9 @@ export function consumePendingHighlightThemeId(): string | null {
|
|||||||
|
|
||||||
export async function openThemeStoreWithHighlight(themeId: string): Promise<void> {
|
export async function openThemeStoreWithHighlight(themeId: string): Promise<void> {
|
||||||
pendingHighlightThemeId = themeId;
|
pendingHighlightThemeId = themeId;
|
||||||
|
const { OpenStorePage } = await import("@/seqta/ui/renderStore");
|
||||||
const existing = document.getElementById("store");
|
await OpenStorePage();
|
||||||
if (existing) {
|
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("bsplus:highlight-theme", { detail: { themeId } }),
|
new CustomEvent("bsplus:highlight-theme", { detail: { themeId } }),
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { OpenStorePage } = await import("@/seqta/ui/renderStore");
|
|
||||||
await OpenStorePage();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export type SettingsDestination =
|
||||||
|
| { page: "settings"; section?: string }
|
||||||
|
| { page: "themes" | "backgrounds"; view?: "settings" | "store" };
|
||||||
|
|
||||||
|
export const SETTINGS_NAVIGATION_EVENT = "bsplus:navigate-settings";
|
||||||
|
|
||||||
|
let pendingDestination: SettingsDestination | null = null;
|
||||||
|
|
||||||
|
export function requestSettingsDestination(destination: SettingsDestination): void {
|
||||||
|
pendingDestination = destination;
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent(SETTINGS_NAVIGATION_EVENT, { detail: destination }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function consumeSettingsDestination(): SettingsDestination | null {
|
||||||
|
const destination = pendingDestination;
|
||||||
|
pendingDestination = null;
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
@@ -17,7 +17,18 @@ export function setupSettingsButton() {
|
|||||||
AddedSettings.dataset.bsplusSettingsBound = "1";
|
AddedSettings.dataset.bsplusSettingsBound = "1";
|
||||||
|
|
||||||
AddedSettings.addEventListener("click", async () => {
|
AddedSettings.addEventListener("click", async () => {
|
||||||
// Re-query each click — Engage SPA navigations can recreate the host.
|
if (SettingsClicked) {
|
||||||
|
closeExtensionPopup();
|
||||||
|
} else {
|
||||||
|
await openSettingsPopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openSettingsPopup(): Promise<void> {
|
||||||
|
if (SettingsClicked && document.getElementById("ExtensionPopup")) return;
|
||||||
|
|
||||||
|
// Re-query each time — Engage SPA navigations can recreate the host.
|
||||||
let extensionPopup = document.getElementById("ExtensionPopup");
|
let extensionPopup = document.getElementById("ExtensionPopup");
|
||||||
if (!extensionPopup) {
|
if (!extensionPopup) {
|
||||||
const { addExtensionSettings } = await import("./Adders/AddExtensionSettings");
|
const { addExtensionSettings } = await import("./Adders/AddExtensionSettings");
|
||||||
@@ -26,14 +37,13 @@ export function setupSettingsButton() {
|
|||||||
}
|
}
|
||||||
if (!extensionPopup) return;
|
if (!extensionPopup) return;
|
||||||
|
|
||||||
if (SettingsClicked) {
|
|
||||||
closeExtensionPopup(extensionPopup);
|
|
||||||
} else {
|
|
||||||
await renderSettingsIfNeeded();
|
await renderSettingsIfNeeded();
|
||||||
|
|
||||||
await delay(30);
|
await delay(30);
|
||||||
|
|
||||||
extensionPopup.style.transform = "none";
|
extensionPopup.style.transform = "none";
|
||||||
|
const panel = extensionPopup.shadowRoot?.querySelector<HTMLElement>(
|
||||||
|
"[data-settings-panel]",
|
||||||
|
);
|
||||||
if (settingsState.animations) {
|
if (settingsState.animations) {
|
||||||
animate(0, 1, {
|
animate(0, 1, {
|
||||||
onUpdate: (progress) => {
|
onUpdate: (progress) => {
|
||||||
@@ -43,12 +53,21 @@ export function setupSettingsButton() {
|
|||||||
stiffness: 280,
|
stiffness: 280,
|
||||||
damping: 20,
|
damping: 20,
|
||||||
});
|
});
|
||||||
|
if (panel) {
|
||||||
|
animate(
|
||||||
|
panel,
|
||||||
|
{ scale: [0, 1], opacity: [0, 1] },
|
||||||
|
{ type: "spring", stiffness: 330, damping: 30 },
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
extensionPopup.style.opacity = "1";
|
extensionPopup.style.opacity = "1";
|
||||||
extensionPopup.style.transition = "opacity 0s linear";
|
extensionPopup.style.transition = "opacity 0s linear";
|
||||||
|
if (panel) {
|
||||||
|
panel.style.scale = "1";
|
||||||
|
panel.style.opacity = "1";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
extensionPopup.classList.remove("hide");
|
extensionPopup.classList.remove("hide");
|
||||||
changeSettingsClicked(true);
|
changeSettingsClicked(true);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user