mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
feat: use web transitions api for themes
This commit is contained in:
@@ -12,6 +12,13 @@
|
||||
font-family: Rubik, sans-serif !important;
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none;
|
||||
mix-blend-mode: normal;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
let isDragging = $state(false);
|
||||
let tempTheme = $state(null);
|
||||
|
||||
const handleThemeClick = async (theme: CustomTheme) => {
|
||||
const handleThemeClick = async (theme: CustomTheme, e: MouseEvent) => {
|
||||
if (isEditMode) return;
|
||||
if (theme.id === themes?.selectedTheme) {
|
||||
themeManager.setTransitionPoint(e.clientX, e.clientY);
|
||||
await themeManager.disableTheme();
|
||||
themes.selectedTheme = '';
|
||||
} else {
|
||||
themeManager.setTransitionPoint(e.clientX, e.clientY);
|
||||
await themeManager.setTheme(theme.id);
|
||||
if (!themes) return;
|
||||
themes.selectedTheme = theme.id;
|
||||
@@ -127,7 +129,7 @@
|
||||
{#each themes.themes as theme (theme.id)}
|
||||
<button
|
||||
class="relative group w-full aspect-theme flex justify-center items-center rounded-xl transition ring dark:ring-white ring-zinc-300 {theme.id === themes.selectedTheme ? 'dark:ring-2 ring-4' : 'ring-0'}"
|
||||
onclick={() => handleThemeClick(theme)}
|
||||
onclick={(e) => handleThemeClick(theme, e)}
|
||||
>
|
||||
{#if isEditMode}
|
||||
<div
|
||||
|
||||
@@ -25,6 +25,7 @@ export class ThemeManager {
|
||||
private originalPreviewColor: string | null = null;
|
||||
private originalPreviewTheme: boolean | null = null;
|
||||
private imageUrlCache: Map<string, string> = new Map();
|
||||
private lastTransitionPoint: { x: number; y: number } = { x: 0, y: 0 };
|
||||
|
||||
private constructor() {
|
||||
console.debug("[ThemeManager] Initializing...");
|
||||
@@ -65,6 +66,61 @@ export class ThemeManager {
|
||||
return settingsState.selectedTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the last transition point based on a click or event
|
||||
*/
|
||||
public setTransitionPoint(x: number, y: number): void {
|
||||
this.lastTransitionPoint = { x, y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a view transition animation
|
||||
*/
|
||||
private async applyViewTransition(callback: () => void): Promise<void> {
|
||||
if (
|
||||
!document.startViewTransition ||
|
||||
!settingsState.animations ||
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
) {
|
||||
// Just run the callback without animation if transitions not supported
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use last known transition point or fallback to center
|
||||
const x = this.lastTransitionPoint.x || window.innerWidth / 2;
|
||||
const y = this.lastTransitionPoint.y || window.innerHeight / 2;
|
||||
const right = window.innerWidth - x;
|
||||
const bottom = window.innerHeight - y;
|
||||
|
||||
const maxRadius = Math.hypot(
|
||||
Math.max(x, right),
|
||||
Math.max(y, bottom),
|
||||
);
|
||||
|
||||
await document.startViewTransition(() => {
|
||||
callback();
|
||||
}).ready;
|
||||
|
||||
try {
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${maxRadius}px at ${x}px ${y}px)`,
|
||||
],
|
||||
},
|
||||
{
|
||||
duration: 400,
|
||||
easing: 'ease-in-out',
|
||||
pseudoElement: '::view-transition-new(root)',
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("[ThemeManager] View Transition animation error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the current theme without deleting it
|
||||
*/
|
||||
@@ -107,7 +163,7 @@ export class ThemeManager {
|
||||
"[ThemeManager] Found selected theme, restoring:",
|
||||
settingsState.selectedTheme,
|
||||
);
|
||||
await this.setTheme(settingsState.selectedTheme);
|
||||
await this.setTheme(settingsState.selectedTheme, false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[ThemeManager] Error during initialization:", error);
|
||||
@@ -131,7 +187,7 @@ export class ThemeManager {
|
||||
/**
|
||||
* Set and apply a theme by ID
|
||||
*/
|
||||
public async setTheme(themeId: string): Promise<void> {
|
||||
public async setTheme(themeId: string, applyViewTransition: boolean = true): Promise<void> {
|
||||
console.debug("[ThemeManager] Setting theme:", themeId);
|
||||
try {
|
||||
const theme = (await localforage.getItem(themeId)) as CustomTheme;
|
||||
@@ -147,17 +203,32 @@ export class ThemeManager {
|
||||
settingsState.originalDarkMode = settingsState.DarkMode;
|
||||
}
|
||||
|
||||
// Use view transition for the theme change
|
||||
if (applyViewTransition) {
|
||||
await this.applyViewTransition(async () => {
|
||||
// Remove current theme if exists
|
||||
if (this.currentTheme) {
|
||||
console.debug("[ThemeManager] Removing current theme");
|
||||
|
||||
await this.removeTheme(this.currentTheme);
|
||||
await this.removeThemeWithoutTransition(this.currentTheme);
|
||||
}
|
||||
|
||||
// Apply new theme
|
||||
await this.applyTheme(theme);
|
||||
this.currentTheme = theme;
|
||||
settingsState.selectedTheme = themeId;
|
||||
});
|
||||
} else {
|
||||
// Remove current theme if exists
|
||||
if (this.currentTheme) {
|
||||
console.debug("[ThemeManager] Removing current theme");
|
||||
await this.removeThemeWithoutTransition(this.currentTheme);
|
||||
}
|
||||
|
||||
// Apply new theme
|
||||
await this.applyTheme(theme);
|
||||
this.currentTheme = theme;
|
||||
settingsState.selectedTheme = themeId;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[ThemeManager] Error setting theme:", error);
|
||||
}
|
||||
@@ -213,11 +284,28 @@ export class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove theme and restore original settings
|
||||
* Remove theme and restore original settings with view transition
|
||||
*/
|
||||
private async removeTheme(
|
||||
theme: CustomTheme,
|
||||
clearSelectedTheme: boolean = true,
|
||||
): Promise<void> {
|
||||
console.debug("[ThemeManager] Removing theme with transition:", theme.name);
|
||||
try {
|
||||
await this.applyViewTransition(async () => {
|
||||
await this.removeThemeWithoutTransition(theme, clearSelectedTheme);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[ThemeManager] Error removing theme with transition:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove theme without applying view transition animation
|
||||
*/
|
||||
private async removeThemeWithoutTransition(
|
||||
theme: CustomTheme,
|
||||
clearSelectedTheme: boolean = true,
|
||||
): Promise<void> {
|
||||
console.debug("[ThemeManager] Removing theme:", theme.name);
|
||||
try {
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function AddBetterSEQTAElements() {
|
||||
setupSettingsButton();
|
||||
}
|
||||
|
||||
function createHomeButton(fragment: DocumentFragment, menuList: HTMLElement) {
|
||||
function createHomeButton(fragment: DocumentFragment, _: HTMLElement) {
|
||||
const container = document.getElementById("content")!;
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("titlebar");
|
||||
@@ -265,13 +265,14 @@ function GetLightDarkModeString() {
|
||||
|
||||
async function addDarkLightToggle() {
|
||||
const tooltipString = GetLightDarkModeString();
|
||||
const svgContent = settingsState.DarkMode
|
||||
? /* html */ `<defs><clipPath id="__lottie_element_80"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_80)"><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -4,-2.2100000381469727 -4,0 C-4,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>`
|
||||
: /* html */ `<defs><clipPath id="__lottie_element_263"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_263)"><g style="display: block;" transform="matrix(1.5,0,0,1.5,7,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -1.2920000553131104,-2.2100000381469727 -1.2920000553131104,0 C-1.2920000553131104,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(-1,0,0,-1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>`;
|
||||
const SUN_ICON_SVG = /* html */ `<defs><clipPath id="__lottie_element_80"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_80)"><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -4,-2.2100000381469727 -4,0 C-4,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>`;
|
||||
const MOON_ICON_SVG = /* html */ `<defs><clipPath id="__lottie_element_263"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_263)"><g style="display: block;" transform="matrix(1.5,0,0,1.5,7,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -1.2920000553131104,-2.2100000381469727 -1.2920000553131104,0 C-1.2920000553131104,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(-1,0,0,-1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>`;
|
||||
|
||||
const initialSvgContent = settingsState.DarkMode ? SUN_ICON_SVG : MOON_ICON_SVG;
|
||||
|
||||
const LightDarkModeButton = stringToHTML(/* html */ `
|
||||
<button class="addedButton DarkLightButton tooltip" id="LightDarkModeButton">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">${svgContent}</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">${initialSvgContent}</svg>
|
||||
<div class="tooltiptext topmenutooltip" id="darklighttooliptext">${tooltipString}</div>
|
||||
</button>
|
||||
`);
|
||||
@@ -281,9 +282,9 @@ async function addDarkLightToggle() {
|
||||
|
||||
updateAllColors();
|
||||
|
||||
document
|
||||
.getElementById("LightDarkModeButton")!
|
||||
.addEventListener("click", async () => {
|
||||
const lightDarkModeButtonElement = document.getElementById("LightDarkModeButton")!;
|
||||
|
||||
lightDarkModeButtonElement.addEventListener("click", async () => {
|
||||
const darklightText = document.getElementById("darklighttooliptext");
|
||||
|
||||
if (
|
||||
@@ -291,11 +292,19 @@ async function addDarkLightToggle() {
|
||||
settingsState.selectedTheme
|
||||
) {
|
||||
darklightText!.innerText = "Locked by current theme";
|
||||
|
||||
await delay(1000);
|
||||
|
||||
darklightText!.innerText = GetLightDarkModeString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!document.startViewTransition || !settingsState.animations || window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||||
settingsState.DarkMode = !settingsState.DarkMode;
|
||||
updateAllColors();
|
||||
|
||||
const newSvgContent = settingsState.DarkMode ? SUN_ICON_SVG : MOON_ICON_SVG;
|
||||
const svgElement = lightDarkModeButtonElement.querySelector("svg");
|
||||
if (svgElement) svgElement.innerHTML = newSvgContent;
|
||||
darklightText!.innerText = GetLightDarkModeString();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -303,6 +312,10 @@ async function addDarkLightToggle() {
|
||||
|
||||
updateAllColors();
|
||||
|
||||
const newSvgContent = settingsState.DarkMode ? SUN_ICON_SVG : MOON_ICON_SVG;
|
||||
const svgElement = lightDarkModeButtonElement.querySelector("svg");
|
||||
if (svgElement) svgElement.innerHTML = newSvgContent;
|
||||
|
||||
darklightText!.innerText = GetLightDarkModeString();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user