From 8cab61a9c5791d1c277bfa2e5b35c780ae58dd0a Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:52:36 +0200 Subject: [PATCH] v1.0.8 --- CHANGELOG.md | 14 +++++++ package.json | 4 +- src/components/common/Logo.tsx | 52 ++++---------------------- src/components/forms/FieldWidget.tsx | 4 +- src/components/layout/MainContent.tsx | 4 +- src/components/layout/Sidebar.tsx | 13 ++----- src/components/layout/TopBar.tsx | 7 ++-- src/components/lists/DynamicList.tsx | 25 +++++++++++-- src/lib/lastVisited.ts | 53 +++++++++++++++++++++++++++ src/lib/logoCache.ts | 40 ++++++++++++++++++++ src/main.tsx | 3 ++ src/pages/AdminPanel.tsx | 7 ++-- vite.config.ts | 2 + 13 files changed, 156 insertions(+), 72 deletions(-) create mode 100644 src/lib/lastVisited.ts create mode 100644 src/lib/logoCache.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5a354..c4ae4ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [1.0.8] - 2026-07-31 + +### Added +- Remember the last visited page when switching sections (credits @LinkPhoenix). + +### Changed +- Enum list filters with many options render as a searchable combobox (credits @LinkPhoenix). +- Object cells display the variant label as a badge instead of the raw type name (credits @LinkPhoenix). +- Empty date pickers open on the current date and time (credits @LinkPhoenix). + +### Fixed +- Custom logos no longer flash when navigating between pages. +- Landing no longer flashes "Select a view" before redirecting to the default page. + ## [1.0.7] - 2026-07-30 ### Added diff --git a/package.json b/package.json index ad85676..77f2371 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stalwart-webui", "private": true, - "version": "1.0.7", + "version": "1.0.8", "description": "Stalwart WebUI", "type": "module", "scripts": { @@ -68,4 +68,4 @@ "vite": "^8.2.0", "vitest": "^4.1.10" } -} +} \ No newline at end of file diff --git a/src/components/common/Logo.tsx b/src/components/common/Logo.tsx index f974f44..128b0c3 100644 --- a/src/components/common/Logo.tsx +++ b/src/components/common/Logo.tsx @@ -4,9 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -import { useState, useEffect } from 'react'; +import { useEffect, useSyncExternalStore } from 'react'; import { useTranslation } from 'react-i18next'; -import { getApiBaseUrl } from '@/services/api'; +import { getLogoState, loadLogoOnce, subscribeToLogo } from '@/lib/logoCache'; export function DefaultLogo() { const { t } = useTranslation(); @@ -31,54 +31,18 @@ export function DefaultLogo() { export default function Logo() { const { t } = useTranslation(); - const [logoUrl, setLogoUrl] = useState(null); - const [failed, setFailed] = useState(false); + const logo = useSyncExternalStore(subscribeToLogo, getLogoState, getLogoState); useEffect(() => { - const controller = new AbortController(); - - async function fetchLogo() { - try { - const response = await fetch(`${getApiBaseUrl()}/logo`, { - signal: controller.signal, - }); - const contentType = response.headers.get('content-type') ?? ''; - - if (response.ok && contentType.startsWith('image/')) { - const blob = await response.blob(); - if (!controller.signal.aborted) { - const url = URL.createObjectURL(blob); - setLogoUrl(url); - } - } else { - if (!controller.signal.aborted) setFailed(true); - } - } catch { - if (!controller.signal.aborted) setFailed(true); - } - } - - fetchLogo(); - - return () => { - controller.abort(); - }; + loadLogoOnce(); }, []); - useEffect(() => { - return () => { - if (logoUrl) { - URL.revokeObjectURL(logoUrl); - } - }; - }, [logoUrl]); - - if (logoUrl && !failed) { - return {t('logo.alt',; + if (logo.status === 'custom') { + return {t('logo.alt',; } - if (!failed) { - return