feat(console): 第5按钮·更新公告——dock最右侧新增铃铛按钮(反选切换),大磁片页:当前版本/检查更新/自动检查开关/新版本横幅+忽略此版本/公告正文(changelog.ts);更新检查核心抽到updateCheck.ts(键与设置面板复用)

This commit is contained in:
Aisland
2026-09-02 00:18:04 +08:00
parent d8b209ec92
commit d808cdf749
6 changed files with 439 additions and 12 deletions
+53 -1
View File
@@ -1,5 +1,7 @@
import { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react'
import { parentPathOf } from './pathutil'
import { CHANGELOG_V030 } from './changelog'
import { LOCAL_VERSION, checkUpdate, getAutoCheck, readCache, setAutoCheck as storeAutoCheck, setSkipVersion, type UpdateInfo, type UpdateStatus } from './updateCheck'
import { Terminal } from 'xterm'
import MarkdownIt from 'markdown-it'
@@ -1415,6 +1417,11 @@ function ConsolePane() {
const [photoGrid, setPhotoGridState] = useState<boolean>(() => splitEnv?.console?.getPhotoGrid?.() ?? true)
const [gridOpacity, setGridOpacityState] = useState<number>(() => splitEnv?.console?.getGridOpacity?.() ?? 8)
const [cardBlur, setCardBlurState] = useState<number>(() => splitEnv?.console?.getCardBlur?.() ?? 0)
const [annOpen, setAnnOpen] = useState(false)
const [updStatus, setUpdStatus] = useState<UpdateStatus>(() => readCache().status)
const [updInfo, setUpdInfo] = useState<UpdateInfo | null>(() => readCache().info)
const [autoCheckOn, setAutoCheckOn] = useState<boolean>(() => getAutoCheck())
const updBusyRef = useRef(false)
const photoUrlRef = useRef<string>('')
const [openMenu, setOpenMenu] = useState<string | null>(null)
const [bgEdit, setBgEdit] = useState<'plain' | 'glow' | 'photo' | null>(null)
@@ -1514,6 +1521,27 @@ function ConsolePane() {
setCardBlurState(v)
splitEnv?.console?.setCardBlur?.(v)
}
const runUpdateCheck = async (force: boolean) => {
if (updBusyRef.current) return
updBusyRef.current = true
setUpdStatus('checking')
try {
const r = await checkUpdate(force)
setUpdStatus(r.status)
setUpdInfo(r.info)
} finally {
updBusyRef.current = false
}
}
const onSkipVersion = () => {
if (updInfo) { setSkipVersion(updInfo.latest); setUpdInfo(null); setUpdStatus('uptodate') }
}
const onAutoCheckToggle = () => {
const next = !autoCheckOn
setAutoCheckOn(next)
storeAutoCheck(next)
if (next) void runUpdateCheck(false)
}
/** 在媒体库中选中某条 */
const selectPhoto = (p: { id: string; kind: 'photo' | 'video'; url: string }) => {
setPhotoIdLocal(p.id)
@@ -1605,6 +1633,8 @@ function ConsolePane() {
useEffect(() => {
splitEnv?.console?.refreshPreviews?.()
}, [])
// 自动检查更新(节流一天;手动按钮始终 force)
useEffect(() => { if (getAutoCheck()) void runUpdateCheck(false) }, [])
// 背景照片库:IndexedDB 异步加载记录 → 对象 URL;无记录则空
useEffect(() => {
let alive = true
@@ -1684,6 +1714,26 @@ function ConsolePane() {
{bg === 'glow' && (<><i className="dsh-wt_blob dsh-wt_blob1" /><i className="dsh-wt_blob dsh-wt_blob2" /><i className="dsh-wt_blob dsh-wt_blob3" /><i className="dsh-wt_blob dsh-wt_blob4" /></>)}</span>
{openMenu !== null && <div className="dsh-wt_dropMask" onClick={() => setOpenMenu(null)} />}
<div className="dsh-wt_consoleScroll">
{annOpen ? (
<div className="dsh-wt_announce">
<div className="dsh-wt_announceHead">
<span className="dsh-wt_announceVer">{T('annot.curVer')} v{LOCAL_VERSION}</span>
<button type="button" className="dsh-wt_announceBtn" disabled={updStatus === 'checking'} onClick={() => runUpdateCheck(true)}>{updStatus === 'checking' ? T('annot.checking') : T('annot.checkNow')}</button>
<span className="dsh-wt_announceAuto">{T('annot.autoCheck')}</span>
<button type="button" className={'dsh-wt_switch' + (autoCheckOn ? ' dsh-wt_switchOn' : '')} aria-pressed={autoCheckOn} aria-label={T('annot.autoCheck')} onClick={onAutoCheckToggle}><span className="dsh-wt_switchKnob" /></button>
</div>
{updInfo && (
<div className="dsh-wt_announceUpdate">
<span className="dsh-wt_announceNewVer">{T('annot.newVer')} v{updInfo.latest}</span>
<button type="button" className="dsh-wt_gridToggle dsh-wt_gridToggleOn" onClick={() => { try { window.open(updInfo.url, '_blank') } catch {} }}>{T('annot.gotoRelease')}</button>
<button type="button" className="dsh-wt_announceSkip" onClick={onSkipVersion}>{T('annot.skipVer')}</button>
</div>
)}
{updStatus === 'failed' && <div className="dsh-wt_announceStatus">{T('annot.checkFail')}</div>}
{updStatus === 'uptodate' && !updInfo && <div className="dsh-wt_announceStatus">{T('annot.latest')}</div>}
<div className="dsh-wt_announceBody">{CHANGELOG_V030}</div>
</div>
) : (
<div ref={gridRef} className="dsh-wt_consoleGrid" style={{ ['--wt-cols' as any]: cols }}>
{cards.map((c) => (
<div
@@ -1729,7 +1779,8 @@ function ConsolePane() {
<span className="dsh-wt_consoleAddLabel">{T('console.addProject')}</span>
</div>
{cards.length === 0 && <div className="dsh-wt_consoleEmpty">{T('console.empty')}</div>}
</div>
</div>
)}
</div>
<div className="dsh-wt_consoleDockWrap">
<div className="dsh-wt_consoleDock">
@@ -1737,6 +1788,7 @@ function ConsolePane() {
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'shape' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.shapeLabel')} aria-label={T('console.shapeLabel')} onClick={() => setOpenMenu(openMenu === 'shape' ? null : 'shape')}><svg width="20" height="20" viewBox="0 0 16 16" aria-hidden><rect x="2.2" y="2.2" width="7" height="7" rx="1.5" fill="none" stroke="currentColor" strokeWidth="0.9" /><circle cx="10.6" cy="10.6" r="3.8" fill="none" stroke="currentColor" strokeWidth="0.9" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'bg' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.bgLabel')} aria-label={T('console.bgLabel')} onClick={() => { setOpenMenu(openMenu === 'bg' ? null : 'bg'); setBgEdit(null) }}><svg width="18" height="18" viewBox="0 0 16 16" aria-hidden><rect x="2.2" y="3.2" width="11.6" height="9.6" rx="1.6" fill="none" stroke="currentColor" strokeWidth="0.9" /><circle cx="5.9" cy="6.7" r="1.05" fill="none" stroke="currentColor" strokeWidth="0.8" /><path d="M3.4 11.4l3-3 2.3 2.3 1.9-1.9 3 2.6" fill="none" stroke="currentColor" strokeWidth="0.9" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (openMenu === 'cols' ? ' dsh-wt_dockBtnOn' : '')} title={T('console.colsLabel')} aria-label={T('console.colsLabel')} onClick={() => setOpenMenu(openMenu === 'cols' ? null : 'cols')}><svg width="20" height="20" viewBox="0 0 16 16" aria-hidden><rect x="2.2" y="3.4" width="2.7" height="9.2" rx="0.9" fill="none" stroke="currentColor" strokeWidth="0.9" /><rect x="6.65" y="3.4" width="2.7" height="9.2" rx="0.9" fill="none" stroke="currentColor" strokeWidth="0.9" /><rect x="11.1" y="3.4" width="2.7" height="9.2" rx="0.9" fill="none" stroke="currentColor" strokeWidth="0.9" /></svg></button>
<button type="button" className={'dsh-wt_dockBtn' + (annOpen ? ' dsh-wt_dockBtnOn' : '')} title={T('annot.updateTitle')} aria-label={T('annot.updateTitle')} onClick={() => { setOpenMenu(null); setAnnOpen((v) => !v) }}><svg width="18" height="18" viewBox="0 0 16 16" aria-hidden><path d="M2.6 7c0-2.9 2.4-4.9 5.4-4.9s5.4 2 5.4 4.9v2.9l1.2 1.7H1.4l1.2-1.7z" fill="none" stroke="currentColor" strokeWidth="0.9" strokeLinejoin="round" /><path d="M6.2 12.9c.3.7 1 1.2 1.8 1.2s1.5-.5 1.8-1.2" fill="none" stroke="currentColor" strokeWidth="0.9" strokeLinecap="round" /></svg>{updInfo && <span className="dsh-wt_dockBadge" aria-hidden />}</button>
</div>
{openMenu === 'theme' && (
<div className="dsh-wt_drop">