feat: 前台页头加明暗切换,刷新图标换成闪电
- settings.site.actions 新增 theme(默认开),后台「操作工具」可关 - 前台页头:月亮/太阳按钮,点击写回 site.theme,前后台同步; 未登录时不显示(设置接口需要会话) - 刷新图标 refresh_3_line -> lightning_line,刷新期间仍旋转
This commit is contained in:
@@ -188,6 +188,9 @@ impl Default for BrandSettings {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ActionSettings {
|
pub struct ActionSettings {
|
||||||
|
/// 明暗切换图标
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub theme: bool,
|
||||||
/// 刷新图标(重扫本地项目并重拉内容库)
|
/// 刷新图标(重扫本地项目并重拉内容库)
|
||||||
pub refresh: bool,
|
pub refresh: bool,
|
||||||
/// 站点设置图标(仅登录后出现)
|
/// 站点设置图标(仅登录后出现)
|
||||||
@@ -199,6 +202,7 @@ pub struct ActionSettings {
|
|||||||
impl Default for ActionSettings {
|
impl Default for ActionSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
theme: true,
|
||||||
refresh: true,
|
refresh: true,
|
||||||
settings: true,
|
settings: true,
|
||||||
session: true,
|
session: true,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
SearchField,
|
SearchField,
|
||||||
Space,
|
Space,
|
||||||
Toaster,
|
Toaster,
|
||||||
|
toast,
|
||||||
type MenuItem,
|
type MenuItem,
|
||||||
} from '@stash/ui'
|
} from '@stash/ui'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
@@ -26,6 +27,7 @@ import { useProjects } from './useProjects'
|
|||||||
import { fetchSession, logout, type SessionState } from './auth'
|
import { fetchSession, logout, type SessionState } from './auth'
|
||||||
import {
|
import {
|
||||||
fetchSettings,
|
fetchSettings,
|
||||||
|
saveSettings,
|
||||||
type SettingsDocument,
|
type SettingsDocument,
|
||||||
type SiteSettings,
|
type SiteSettings,
|
||||||
} from './settings'
|
} from './settings'
|
||||||
@@ -287,7 +289,7 @@ export default function AppNext() {
|
|||||||
favicon: '',
|
favicon: '',
|
||||||
theme: 'auto',
|
theme: 'auto',
|
||||||
brand: { name: 'STASH', mark: 'S', logo: '' },
|
brand: { name: 'STASH', mark: 'S', logo: '' },
|
||||||
actions: { refresh: true, settings: true, session: true },
|
actions: { theme: true, refresh: true, settings: true, session: true },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +300,27 @@ export default function AppNext() {
|
|||||||
document.documentElement.dataset.scheme = site.site.theme
|
document.documentElement.dataset.scheme = site.site.theme
|
||||||
}, [site.site.theme])
|
}, [site.site.theme])
|
||||||
|
|
||||||
|
/** 当前实际是深色吗(auto 时按系统偏好算) */
|
||||||
|
const isDark =
|
||||||
|
site.site.theme === 'dark' ||
|
||||||
|
(site.site.theme === 'auto' &&
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
|
||||||
|
/** 页头的明暗切换:写回站点设置,前后台一起变;只有登录后才会显示 */
|
||||||
|
const toggleTheme = async () => {
|
||||||
|
if (settings === null) return
|
||||||
|
const next = isDark ? 'light' : 'dark'
|
||||||
|
const result = await saveSettings(settings.version, {
|
||||||
|
...settings.settings,
|
||||||
|
site: { ...settings.settings.site, theme: next },
|
||||||
|
})
|
||||||
|
if (result.ok) {
|
||||||
|
setSettings(result.document)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toast.error(result.error)
|
||||||
|
}
|
||||||
|
|
||||||
// 站点级设置:浏览器标签标题与 favicon(空值回落到内置)
|
// 站点级设置:浏览器标签标题与 favicon(空值回落到内置)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const title = site.site.title.trim()
|
const title = site.site.title.trim()
|
||||||
@@ -648,6 +671,8 @@ export default function AppNext() {
|
|||||||
authenticated={session?.authenticated === true}
|
authenticated={session?.authenticated === true}
|
||||||
username={session?.username ?? undefined}
|
username={session?.username ?? undefined}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
|
dark={isDark}
|
||||||
|
onToggleTheme={() => void toggleTheme()}
|
||||||
onRefresh={() => {
|
onRefresh={() => {
|
||||||
refresh()
|
refresh()
|
||||||
load()
|
load()
|
||||||
|
|||||||
@@ -1935,6 +1935,16 @@ function HeaderPanel({
|
|||||||
<Text size="sm" strong>
|
<Text size="sm" strong>
|
||||||
操作工具
|
操作工具
|
||||||
</Text>
|
</Text>
|
||||||
|
<div className="sidebar-toggle-row">
|
||||||
|
<Text>明暗切换</Text>
|
||||||
|
<Switch
|
||||||
|
checked={site.actions.theme}
|
||||||
|
onChange={(next) =>
|
||||||
|
onChangeSite({ ...site, actions: { ...site.actions, theme: next } })
|
||||||
|
}
|
||||||
|
label="页头显示明暗切换按钮"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="sidebar-toggle-row">
|
<div className="sidebar-toggle-row">
|
||||||
<Text>刷新</Text>
|
<Text>刷新</Text>
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ type SiteHeaderProps = {
|
|||||||
username?: string
|
username?: string
|
||||||
/** 刷新中:刷新按钮转圈 */
|
/** 刷新中:刷新按钮转圈 */
|
||||||
refreshing?: boolean
|
refreshing?: boolean
|
||||||
|
/** 当前是否为深色,决定明暗按钮的图标与文案 */
|
||||||
|
dark?: boolean
|
||||||
onRefresh: () => void
|
onRefresh: () => void
|
||||||
|
onToggleTheme: () => void
|
||||||
onLogin: () => void
|
onLogin: () => void
|
||||||
onLogout: () => void
|
onLogout: () => void
|
||||||
onSettings: () => void
|
onSettings: () => void
|
||||||
@@ -52,7 +55,9 @@ export function SiteHeader({
|
|||||||
authenticated,
|
authenticated,
|
||||||
username,
|
username,
|
||||||
refreshing = false,
|
refreshing = false,
|
||||||
|
dark = false,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
|
onToggleTheme,
|
||||||
onLogin,
|
onLogin,
|
||||||
onLogout,
|
onLogout,
|
||||||
onSettings,
|
onSettings,
|
||||||
@@ -84,10 +89,17 @@ export function SiteHeader({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="header-actions">
|
<div className="header-actions">
|
||||||
|
{site.actions.theme && (
|
||||||
|
<IconButton
|
||||||
|
label={dark ? '切换到浅色' : '切换到深色'}
|
||||||
|
icon={<Icon name={dark ? 'sun_line' : 'moon_line'} />}
|
||||||
|
onClick={onToggleTheme}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{site.actions.refresh && (
|
{site.actions.refresh && (
|
||||||
<IconButton
|
<IconButton
|
||||||
label="刷新(重扫项目并重拉内容)"
|
label="刷新(重扫项目并重拉内容)"
|
||||||
icon={<Icon name="refresh_3_line" />}
|
icon={<Icon name="lightning_line" />}
|
||||||
spinning={refreshing}
|
spinning={refreshing}
|
||||||
onClick={onRefresh}
|
onClick={onRefresh}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ export type SiteMetaSettings = {
|
|||||||
logo: string
|
logo: string
|
||||||
}
|
}
|
||||||
actions: {
|
actions: {
|
||||||
|
/** 明暗切换 */
|
||||||
|
theme: boolean
|
||||||
/** 刷新图标(重扫本地项目并重拉内容库) */
|
/** 刷新图标(重扫本地项目并重拉内容库) */
|
||||||
refresh: boolean
|
refresh: boolean
|
||||||
/** 站点设置图标 */
|
/** 站点设置图标 */
|
||||||
@@ -143,7 +145,7 @@ export function defaultSettings(): SiteSettings {
|
|||||||
favicon: '',
|
favicon: '',
|
||||||
theme: 'auto',
|
theme: 'auto',
|
||||||
brand: { name: 'STASH', mark: 'S', logo: '' },
|
brand: { name: 'STASH', mark: 'S', logo: '' },
|
||||||
actions: { refresh: true, settings: true, session: true },
|
actions: { theme: true, refresh: true, settings: true, session: true },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user