Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,14 @@ function App() {
// 自动下载函数
const startAutoDownload = useCallback(
async (updateResult: NonNullable<Awaited<ReturnType<typeof checkAndPrepareDownload>>>) => {
if (!updateResult.downloadUrl || downloadStartedRef.current) return;
// 在检查完成后读取最新偏好,检查期间关闭开关也不应开始下载。
if (
!useAppStore.getState().autoDownloadUpdates ||
!updateResult.downloadUrl ||
downloadStartedRef.current
) {
return;
}

downloadStartedRef.current = true;
setDownloadStatus('downloading');
Expand Down Expand Up @@ -752,8 +759,9 @@ function App() {
if (config.instances.length > 0) {
importConfig(config);
} else {
// 无实例时单独恢复定时检查偏好
// 无实例时单独恢复更新偏好
const state = useAppStore.getState();
state.setAutoDownloadUpdates(config.settings.autoDownloadUpdates !== false);
Comment thread
MistEO marked this conversation as resolved.
Resolved
state.setScheduledUpdateCheckEnabled(config.settings.scheduledUpdateCheckEnabled === true);
state.setScheduledUpdateCheckTime(
config.settings.scheduledUpdateCheckTime ?? defaultScheduledUpdateCheckTime,
Expand Down
23 changes: 9 additions & 14 deletions src/components/UpdatePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
X,
Download,
ChevronRight,
Maximize2,
AlertCircle,
RefreshCw,
AlertTriangle,
} from 'lucide-react';
import { X, Download, ChevronRight, Maximize2, AlertCircle, AlertTriangle } from 'lucide-react';
import { useAppStore, type DownloadProgress } from '@/stores/appStore';
import { simpleMarkdownToHtml } from '@/services/contentResolver';
import {
Expand Down Expand Up @@ -362,12 +354,15 @@ export function UpdatePanel({ onClose, anchorRef }: UpdatePanelProps) {
/>
)}

{/* 等待下载(有链接但未开始) */}
{/* 有链接时允许用户明确开始下载 */}
{updateInfo.downloadUrl && downloadStatus === 'idle' && (
<div className="flex items-center gap-2 text-xs text-text-muted">
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
<span>{t('mirrorChyan.preparingDownload')}</span>
</div>
<button
onClick={() => startDownload()}
className="flex items-center gap-2 px-3 py-2 text-sm bg-accent text-white hover:bg-accent-hover rounded-lg transition-colors"
>
<Download className="w-4 h-4" />
{t('mirrorChyan.downloadNow')}
</button>
)}
Comment thread
MistEO marked this conversation as resolved.
Resolved
Comment thread
MistEO marked this conversation as resolved.
Resolved
</div>
</div>
Expand Down
52 changes: 46 additions & 6 deletions src/components/settings/UpdateSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
downloadUpdate,
getUpdateSavePath,
cancelDownload,
savePendingUpdateInfo,
MIRRORCHYAN_ERROR_CODES,
isDebugVersion,
} from '@/services/updateService';
Expand All @@ -44,6 +45,8 @@ export function UpdateSection() {
mirrorChyanSettings,
setMirrorChyanCdk,
setMirrorChyanChannel,
autoDownloadUpdates,
setAutoDownloadUpdates,
scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime,
setScheduledUpdateCheckEnabled,
Expand Down Expand Up @@ -159,6 +162,16 @@ export function UpdateSection() {
// 使用实际保存路径(可能与请求路径不同,如果从 302 重定向检测到正确文件名)
setDownloadSavePath(result.actualSavePath);
setDownloadStatus('completed');
savePendingUpdateInfo({
versionName: info.versionName,
releaseNote: info.releaseNote,
channel: info.channel,
downloadSavePath: result.actualSavePath,
fileSize: info.fileSize,
updateType: info.updateType,
downloadSource: info.downloadSource,
timestamp: Date.now(),
});
} else {
setDownloadStatus('failed');
}
Expand Down Expand Up @@ -236,6 +249,15 @@ export function UpdateSection() {

if (result) {
setUpdateInfo(result);
// 切换正在进行的下载时继续;否则遵守最新的自动下载偏好。
if (
result.hasUpdate &&
result.downloadUrl &&
!isDownloadingFromGitHub &&
!useAppStore.getState().autoDownloadUpdates
) {
return;
}
if (result.hasUpdate && result.downloadUrl && result.downloadSource === 'mirrorchyan') {
addDebugLog(`已切换到 Mirror酱 下载: ${result.versionName}`);
await startDownload(result);
Expand Down Expand Up @@ -350,7 +372,9 @@ export function UpdateSection() {
addDebugLog(
`下载来源: ${result.downloadSource === 'github' ? 'GitHub' : 'Mirror酱 CDN'}`,
);
startDownload(result);
if (useAppStore.getState().autoDownloadUpdates) {
startDownload(result);
}
} else {
addDebugLog('无可用下载链接');
}
Expand Down Expand Up @@ -422,6 +446,19 @@ export function UpdateSection() {
</div>
</div>

{/* 自动下载更新 */}
<div className="pt-4 border-t border-border flex items-center justify-between gap-3">
<div>
<div className="font-medium text-text-primary">
{t('settings.autoDownloadUpdates')}
</div>
<p className="mt-1 text-xs text-text-muted">
{t('settings.autoDownloadUpdatesHint')}
</p>
</div>
<SwitchButton value={autoDownloadUpdates} onChange={setAutoDownloadUpdates} />
</div>

{/* 定时检查更新 */}
<div className="pt-4 border-t border-border space-y-3">
<div className="flex items-center justify-between gap-3">
Expand Down Expand Up @@ -652,12 +689,15 @@ export function UpdateSection() {
/>
)}

{/* 等待下载 */}
{/* 有链接时允许用户明确开始下载 */}
{updateInfo.downloadUrl && downloadStatus === 'idle' && (
<div className="flex items-center gap-2 text-xs text-text-muted">
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
<span>{t('mirrorChyan.preparingDownload')}</span>
</div>
<button
onClick={() => startDownload()}
Comment thread
MistEO marked this conversation as resolved.
Resolved
className="flex items-center gap-2 px-3 py-2 text-sm bg-accent text-white hover:bg-accent-hover rounded-lg transition-colors"
>
<Download className="w-4 h-4" />
{t('mirrorChyan.downloadNow')}
</button>
)}
</div>
)}
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export default {
appearance: 'Appearance',
hotkeys: 'Hotkeys',
general: 'General',
autoDownloadUpdates: 'Download updates automatically',
autoDownloadUpdatesHint:
'When off, new versions are still checked and announced. You can download them manually.',
scheduledUpdateCheck: 'Scheduled Update Checks',
scheduledUpdateCheckTime: 'Daily Check Time (Local Time)',
taskSettings: 'Task Settings',
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export default {
appearance: '外観',
hotkeys: 'ショートカットキー',
general: '一般',
autoDownloadUpdates: '更新を自動でダウンロード',
autoDownloadUpdatesHint:
'オフでも新しいバージョンの確認と通知は行われ、手動でダウンロードできます。',
scheduledUpdateCheck: '更新の定時確認',
scheduledUpdateCheckTime: '毎日の確認時刻(現地時間)',
taskSettings: 'タスク設定',
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/ko-KR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export default {
appearance: '외관',
hotkeys: '단축키',
general: '일반',
autoDownloadUpdates: '업데이트 자동 다운로드',
autoDownloadUpdatesHint:
'꺼도 새 버전을 확인하고 알림을 표시하며, 수동으로 다운로드할 수 있습니다.',
scheduledUpdateCheck: '예약 업데이트 확인',
scheduledUpdateCheckTime: '매일 확인 시간 (현지 시간)',
taskSettings: '작업 설정',
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export default {
appearance: '外观',
hotkeys: '快捷键',
general: '通用',
autoDownloadUpdates: '自动下载更新',
autoDownloadUpdatesHint: '关闭后仍会检查并提示新版本,可手动下载。',
scheduledUpdateCheck: '定时检查更新',
scheduledUpdateCheckTime: '每日检查时间(本地时间)',
taskSettings: '任务设置',
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export default {
appearance: '外觀',
hotkeys: '快捷鍵',
general: '一般',
autoDownloadUpdates: '自動下載更新',
autoDownloadUpdatesHint: '關閉後仍會檢查並提示新版本,可手動下載。',
scheduledUpdateCheck: '定時檢查更新',
scheduledUpdateCheckTime: '每日檢查時間(本地時間)',
taskSettings: '任務設定',
Expand Down
6 changes: 6 additions & 0 deletions src/services/configService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ export async function loadConfig(basePath: string, projectName?: string): Promis
} catch {
// 浏览器环境加载失败是正常的
}

// 纯前端预览没有后端或静态配置时,恢复本地保存的设置(也可能还没有实例)。
const storageConfig = loadConfigFromStorage(projectName);
if (isValidMxuConfig(storageConfig)) {
return storageConfig;
}
}

return defaultConfig;
Expand Down
7 changes: 7 additions & 0 deletions src/stores/appStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1507,6 +1507,7 @@ export const useAppStore = create<AppState>()(
confirmBeforeDelete: config.settings.confirmBeforeDelete ?? false,
maxLogsPerInstance: config.settings.maxLogsPerInstance ?? DEFAULT_MAX_LOGS_PER_INSTANCE,
autoClearLogsOnLaunch: config.settings.autoClearLogsOnLaunch ?? false,
autoDownloadUpdates: config.settings.autoDownloadUpdates !== false,
scheduledUpdateCheckEnabled: config.settings.scheduledUpdateCheckEnabled === true,
scheduledUpdateCheckTime: normalizeScheduledUpdateCheckTime(
config.settings.scheduledUpdateCheckTime,
Expand Down Expand Up @@ -2032,6 +2033,10 @@ export const useAppStore = create<AppState>()(
mirrorChyanSettings: { ...state.mirrorChyanSettings, channel },
})),

// 自动下载更新
autoDownloadUpdates: true,
setAutoDownloadUpdates: (enabled) => set({ autoDownloadUpdates: enabled }),

// 定时检查更新
scheduledUpdateCheckEnabled: false,
scheduledUpdateCheckTime: defaultScheduledUpdateCheckTime,
Expand Down Expand Up @@ -2512,6 +2517,7 @@ function generateConfig(): MxuConfig {
confirmBeforeDelete: state.confirmBeforeDelete,
maxLogsPerInstance: state.maxLogsPerInstance,
autoClearLogsOnLaunch: state.autoClearLogsOnLaunch,
autoDownloadUpdates: state.autoDownloadUpdates,
Comment thread
MistEO marked this conversation as resolved.
Resolved
scheduledUpdateCheckEnabled: state.scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime: state.scheduledUpdateCheckTime,
helpImproveSoftware: state.helpImproveSoftware,
Expand Down Expand Up @@ -2608,6 +2614,7 @@ useAppStore.subscribe(
confirmBeforeDelete: state.confirmBeforeDelete,
maxLogsPerInstance: state.maxLogsPerInstance,
autoClearLogsOnLaunch: state.autoClearLogsOnLaunch,
autoDownloadUpdates: state.autoDownloadUpdates,
helpImproveSoftware: state.helpImproveSoftware,
mirrorChyanSettings: state.mirrorChyanSettings,
scheduledUpdateCheckEnabled: state.scheduledUpdateCheckEnabled,
Expand Down
4 changes: 4 additions & 0 deletions src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ export interface AppState {
setMirrorChyanCdk: (cdk: string) => void;
setMirrorChyanChannel: (channel: UpdateChannel) => void;

// 自动下载更新
autoDownloadUpdates: boolean;
setAutoDownloadUpdates: (enabled: boolean) => void;

// 定时检查更新
scheduledUpdateCheckEnabled: boolean;
scheduledUpdateCheckTime: string;
Expand Down
2 changes: 2 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export interface AppSettings {
/** 每个实例最多保留的日志条数(超出自动丢弃最旧的) */
maxLogsPerInstance?: number;
autoClearLogsOnLaunch?: boolean;
autoDownloadUpdates?: boolean; // 自动下载已发现的更新,默认开启
scheduledUpdateCheckEnabled?: boolean; // 是否每天定时检查更新,默认关闭
scheduledUpdateCheckTime?: string; // 每天检查更新的本地时间(HH:mm),默认 08:00
windowSize?: WindowSize;
Expand Down Expand Up @@ -255,6 +256,7 @@ export const defaultConfig: MxuConfig = {
confirmBeforeDelete: false,
maxLogsPerInstance: DEFAULT_MAX_LOGS_PER_INSTANCE,
autoClearLogsOnLaunch: false,
autoDownloadUpdates: true,
scheduledUpdateCheckEnabled: false,
scheduledUpdateCheckTime: defaultScheduledUpdateCheckTime,
windowSize: defaultWindowSize,
Expand Down
6 changes: 3 additions & 3 deletions tests/autoUpdateCheck.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ test('startup and scheduled responses are discarded when their update parameters
}
});

test('replacing the loaded project or beginning a download invalidates an in-flight result', async () => {
for (const change of ['project', 'download'] as const) {
test('replacing the loaded project or starting/completing a download invalidates an in-flight result', async () => {
for (const change of ['project', 'downloading', 'completed'] as const) {
const check = createCheck();
const request = runAutoUpdateCheck(check.project, check.dependencies);
check.setState(
change === 'project'
? { projectInterface: { ...check.project, version: 'v2.0.0' } }
: { downloadStatus: 'downloading' },
: { downloadStatus: change },
);
check.resolve(update);
await request;
Expand Down
Loading
Loading