Skip to content
Merged
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
10 changes: 9 additions & 1 deletion src/components/InstallConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
FallbackUpdateError,
isExecutableInstaller,
} from '@/services/updateService';
import { ReleaseNotes, DownloadProgressBar } from './UpdateInfoCard';
import { ReleaseNotes, DownloadProgressBar, useOpenUpdateSettings } from './UpdateInfoCard';
import { loggers } from '@/utils/logger';

export function InstallConfirmModal() {
Expand Down Expand Up @@ -134,6 +134,13 @@ export function InstallConfirmModal() {
setJustUpdatedInfo,
]);

// 跳转到设置页的更新分区去配置 CDK
const openUpdateSettings = useOpenUpdateSettings();
const handleOpenUpdateSettings = useCallback(() => {
openUpdateSettings();
setShowInstallConfirmModal(false); // 否则弹窗会盖住设置页
}, [openUpdateSettings, setShowInstallConfirmModal]);

// 用于追踪是否已触发自动安装,避免重复执行
const autoInstallTriggered = useRef(false);
// 用于追踪是否已触发自动重启,避免重复执行
Expand Down Expand Up @@ -398,6 +405,7 @@ export function InstallConfirmModal() {
fileSize={updateInfo.fileSize}
downloadSource={updateInfo.downloadSource}
showActions={false}
onSlowDownloadHintClick={handleOpenUpdateSettings}
/>
</div>
)}
Expand Down
17 changes: 17 additions & 0 deletions src/components/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ export function SettingsPage({ onClose }: SettingsPageProps) {
confirmBeforeDelete,
interfaceTranslations,
language,
settingsTargetSection,
setSettingsTargetSection,
} = useAppStore();

// 自定义强调色编辑状态
Expand Down Expand Up @@ -299,6 +301,21 @@ export function SettingsPage({ onClose }: SettingsPageProps) {
setDrawerOpen(false);
}, []);

// 由外部(如更新气泡里的加速下载入口)指定的目标分区,进入设置页后自动滚过去
useEffect(() => {
if (!settingsTargetSection) return;

const target = settingsTargetSection;
setActiveSection(target);
// 等一帧让页面切换后的布局稳定,否则 offsetTop 可能还没到位。
// 清空必须放在回调里:若提前清空,依赖变化会让 cleanup 在这一帧结束前取消掉 rAF。
const raf = requestAnimationFrame(() => {
scrollToSection(target);
setSettingsTargetSection(null);
});
return () => cancelAnimationFrame(raf);
}, [settingsTargetSection, setSettingsTargetSection, scrollToSection]);

// 监听滚动,更新当前高亮的 section
useEffect(() => {
const container = scrollContainerRef.current;
Expand Down
61 changes: 59 additions & 2 deletions src/components/UpdateInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { ChevronRight, RefreshCw, PackageCheck } from 'lucide-react';
import type { DownloadProgress, DownloadStatus } from '@/stores/appStore';
import { ChevronRight, RefreshCw, PackageCheck, Globe } from 'lucide-react';
import {
useAppStore,
SLOW_DOWNLOAD_DURATION_MS,
type DownloadProgress,
type DownloadStatus,
} from '@/stores/appStore';
import { simpleMarkdownToHtml } from '@/services/contentResolver';
import clsx from 'clsx';

Expand Down Expand Up @@ -137,6 +143,51 @@ export function ReleaseNotes({
);
}

/**
* 跳转到设置页的更新分区。
* 调用方通常还需要自行关闭当前的气泡/弹窗,否则会盖住设置页。
*/
export function useOpenUpdateSettings() {
const setSettingsTargetSection = useAppStore((s) => s.setSettingsTargetSection);
const setCurrentPage = useAppStore((s) => s.setCurrentPage);

return useCallback(() => {
setSettingsTargetSection('update');
setCurrentPage('settings');
}, [setSettingsTargetSection, setCurrentPage]);
}

/**
* 慢速下载引导入口。仅在「下载中 + 未填 CDK + 速度持续偏低」时出现,
* 其余情况自行返回 null,因此调用方无需再做条件判断。
*/
function SlowDownloadHint({ onClick }: { onClick: () => void }) {
const { t } = useTranslation();
const mirrorChyanRid = useAppStore((s) => s.projectInterface?.mirrorchyan_rid);
const cdk = useAppStore((s) => s.mirrorChyanSettings.cdk);
const downloadStatus = useAppStore((s) => s.downloadStatus);
const slowDownloadSince = useAppStore((s) => s.slowDownloadSince);

const hasCdk = !!cdk && cdk.trim() !== '';

// 没有 mirrorchyan_rid 时设置页不存在更新分区,跳过去是空的
if (!mirrorChyanRid || downloadStatus !== 'downloading' || hasCdk) return null;
if (slowDownloadSince === null || Date.now() - slowDownloadSince < SLOW_DOWNLOAD_DURATION_MS) {
return null;
}
Comment thread
zmdyy0318 marked this conversation as resolved.

return (
<button
onClick={onClick}
className="w-full flex items-center gap-2 px-2.5 py-2 rounded-lg bg-accent/10 text-accent text-xs hover:bg-accent/15 transition-colors"
>
<Globe className="w-3.5 h-3.5 shrink-0" />
<span className="flex-1 text-left">{t('mirrorChyan.slowDownloadHint')}</span>
<ChevronRight className="w-3.5 h-3.5 shrink-0" />
</button>
);
}

interface DownloadProgressBarProps {
downloadStatus: DownloadStatus;
downloadProgress: DownloadProgress | null;
Expand All @@ -150,6 +201,8 @@ interface DownloadProgressBarProps {
showActions?: boolean;
/** 进度条背景色类名 */
progressBgClass?: string;
/** 慢速下载引导入口的点击行为;不传则不显示该入口 */
onSlowDownloadHintClick?: () => void;
}

/** 下载进度组件 */
Expand All @@ -162,6 +215,7 @@ export function DownloadProgressBar({
onRetryClick,
showActions = true,
progressBgClass = 'bg-bg-tertiary',
onSlowDownloadHintClick,
}: DownloadProgressBarProps) {
const { t } = useTranslation();

Expand Down Expand Up @@ -231,6 +285,9 @@ export function DownloadProgressBar({
: t('mirrorChyan.downloadFromMirrorChyan')}
</div>
)}

{/* 慢速下载引导 */}
{onSlowDownloadHintClick && <SlowDownloadHint onClick={onSlowDownloadHintClick} />}
</div>
);
}
Expand Down
10 changes: 9 additions & 1 deletion src/components/UpdatePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
savePendingUpdateInfo,
} from '@/services/updateService';
import { proxySettingsForUpdateDownload } from '@/services/proxyService';
import { DownloadProgressBar } from './UpdateInfoCard';
import { DownloadProgressBar, useOpenUpdateSettings } from './UpdateInfoCard';
import clsx from 'clsx';
import { loggers } from '@/utils/logger';

Expand Down Expand Up @@ -124,6 +124,13 @@ export function UpdatePanel({ onClose, anchorRef }: UpdatePanelProps) {
useAppStore.getState().setInstallStatus('installing');
}, [setShowInstallConfirmModal, onClose]);

// 跳转到设置页的更新分区去配置 CDK
const openUpdateSettings = useOpenUpdateSettings();
const handleOpenUpdateSettings = useCallback(() => {
openUpdateSettings();
onClose(); // 关闭气泡
}, [openUpdateSettings, onClose]);

// 计算面板位置
useEffect(() => {
if (anchorRef.current) {
Expand Down Expand Up @@ -351,6 +358,7 @@ export function UpdatePanel({ onClose, anchorRef }: UpdatePanelProps) {
resetDownloadState();
startDownload();
}}
onSlowDownloadHintClick={handleOpenUpdateSettings}
/>
)}

Expand Down
10 changes: 9 additions & 1 deletion src/components/settings/UpdateSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import {
Download,
Expand Down Expand Up @@ -61,6 +61,7 @@ export function UpdateSection() {
} = useAppStore();

const [showCdk, setShowCdk] = useState(false);
const cdkInputRef = useRef<HTMLInputElement>(null);
const [proxyInput, setProxyInput] = useState(proxySettings?.url || '');
const [proxyError, setProxyError] = useState(false);
const [checkFailed, setCheckFailed] = useState(false);
Expand Down Expand Up @@ -268,6 +269,11 @@ export function UpdateSection() {
setInstallStatus('installing');
}, [setShowInstallConfirmModal, setInstallStatus]);

// 慢速下载引导:本身已在更新分区内,把 CDK 输入框滚进视野即可,不抢焦点
const handleScrollToCdkInput = useCallback(() => {
cdkInputRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, []);

// 获取错误码对应的翻译文本
const errorText = useMemo(() => {
if (!updateInfo?.errorCode) return null;
Expand Down Expand Up @@ -424,6 +430,7 @@ export function UpdateSection() {
</div>
<div className="relative">
<input
ref={cdkInputRef}
type={showCdk ? 'text' : 'password'}
value={mirrorChyanSettings.cdk}
onChange={(e) => handleCdkChange(e.target.value)}
Expand Down Expand Up @@ -607,6 +614,7 @@ export function UpdateSection() {
startDownload();
}}
progressBgClass="bg-bg-secondary"
onSlowDownloadHintClick={handleScrollToCdkInput}
/>
)}

Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,7 @@ export default {
' is an independent third-party accelerated download service that requires a paid subscription, not a fee charged by "{{projectName}}". Its operating costs are covered by subscription revenue, with a portion supporting project developers. Subscribe for high-speed downloads while supporting ongoing development. Without a CDK, downloads will fall back to GitHub. If that fails, please configure a network proxy.',
getCdk: 'No CDKey? Subscribe Now',
cdkHint: 'Please check if your CDK is correct or has expired',
slowDownloadHint: 'Other channels',
checkUpdate: 'Check for Updates',
checking: 'Checking...',
upToDate: 'You are up to date ({{version}})',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ export default {
' は独立したサードパーティの高速ダウンロードサービスで、有料サブスクリプションが必要です。これは「{{projectName}}」の料金ではありません。運営費はサブスクリプション収入で賄われ、一部は開発者に還元されます。CDK を購読して高速ダウンロードをお楽しみください。CDK を入力しない場合、GitHub からダウンロードします。失敗した場合は、ネットワークプロキシを設定してください。',
getCdk: 'CDKをお持ちでない方はこちら',
cdkHint: 'CDK が正しいか、または有効期限が切れていないか確認してください',
slowDownloadHint: '他の入手先',
checkUpdate: '更新を確認',
checking: '確認中...',
upToDate: '最新バージョンです ({{version}})',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ko-KR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ export default {
'는 독립적인 서드파티 고속 다운로드 서비스이며 유료 구독이 필요합니다. 이것은 "{{projectName}}"의 요금이 아닙니다. 운영비는 구독 수익으로 충당되며 일부는 개발자에게 환원됩니다. CDK를 구독하여 고속 다운로드를 즐기세요. CDK가 없으면 GitHub에서 다운로드됩니다. 실패하면 네트워크 프록시를 설정하세요.',
getCdk: 'CDK가 없으신가요? 지금 구독하세요',
cdkHint: 'CDK가 올바른지 또는 만료되지 않았는지 확인하세요',
slowDownloadHint: '다른 채널',
checkUpdate: '업데이트 확인',
checking: '확인 중...',
upToDate: '최신 버전입니다 ({{version}})',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ export default {
' 是独立的第三方加速下载服务,需要付费使用,并非「{{projectName}}」收费。其运营成本由订阅收入支撑,部分收益将回馈项目开发者。欢迎订阅 CDK 享受高速下载,同时支持项目持续开发。未填写 CDK 时将自动通过 GitHub 下载,若失败请尝试配置网络代理。',
getCdk: '没有CDK?立即订阅',
cdkHint: '请检查您的 CDK 是否正确或已过期',
slowDownloadHint: '其他渠道',
checkUpdate: '检查更新',
checking: '正在检查...',
upToDate: '当前已是最新版本 ({{version}})',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ export default {
' 是獨立的第三方加速下載服務,需要付費使用,並非「{{projectName}}」收費。其營運成本由訂閱收入支撐,部分收益將回饋專案開發者。歡迎訂閱 CDK 享受高速下載,同時支援專案持續開發。未填寫 CDK 時將自動透過 GitHub 下載,若失敗請嘗試設定網路代理。',
getCdk: '沒有CDK?立即訂閱',
cdkHint: '請檢查您的 CDK 是否正確或已過期',
slowDownloadHint: '其他管道',
checkUpdate: '檢查更新',
checking: '正在檢查...',
upToDate: '目前已是最新版本 ({{version}})',
Expand Down
13 changes: 9 additions & 4 deletions src/services/updateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,9 +733,14 @@ interface DownloadUpdateOptions {
// 当前下载的保存路径,用于取消时清理临时文件
let currentDownloadPath: string | null = null;

// 进度事件数据(包含 session_id 用于区分不同下载任务)
interface DownloadProgressEventPayload extends DownloadProgress {
// 进度事件数据。Rust 侧 DownloadProgressEvent 走 serde 默认命名,
// 字段是 snake_case,不能直接套用前端 camelCase 的 DownloadProgress
interface DownloadProgressEventPayload {
session_id: number;
downloaded_size: number;
total_size: number;
speed: number;
progress: number;
}

/**
Expand Down Expand Up @@ -809,8 +814,8 @@ export async function downloadUpdate(
// 如果已被取消,忽略进度更新
if (downloadCancelled) return;
onProgress({
downloadedSize: event.payload.downloadedSize,
totalSize: event.payload.totalSize,
downloadedSize: event.payload.downloaded_size,
totalSize: event.payload.total_size,
speed: event.payload.speed,
progress: event.payload.progress,
});
Expand Down
25 changes: 23 additions & 2 deletions src/stores/appStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ import { cacheTaskEnabledForController } from '@/utils/taskControllerCache';
// 从独立模块导入类型和辅助函数
import type { AppState, LogEntry, TaskRunStatus } from './types';

/** 低于该速度(字节/秒)视为慢速下载 */
const SLOW_DOWNLOAD_SPEED_BPS = 1024 * 1024;

/** 慢速需要持续这么久才认定为「确实慢」,避免下载刚开始时误报 */
export const SLOW_DOWNLOAD_DURATION_MS = 5000;

/**
* 规范化定时策略:仅保留 times(分钟精度)字段,丢弃旧版整点 hours 字段。
* 不做新旧数据迁移——旧配置中基于 hours 的时间点不会被转换为 times,加载后时间点为空,需用户重新配置。
Expand Down Expand Up @@ -351,6 +357,9 @@ export const useAppStore = create<AppState>()(
currentPage: 'main',
setCurrentPage: (page) => set({ currentPage: page }),

settingsTargetSection: null,
setSettingsTargetSection: (section) => set({ settingsTargetSection: section }),

// 调试选项(不落盘,每次启动默认关闭)
saveDraw: false,
setSaveDraw: async (enabled) => {
Expand Down Expand Up @@ -2036,14 +2045,26 @@ export const useAppStore = create<AppState>()(
downloadStatus: 'idle',
downloadProgress: null,
downloadSavePath: null,
setDownloadStatus: (status) => set({ downloadStatus: status }),
setDownloadProgress: (progress) => set({ downloadProgress: progress }),
slowDownloadSince: null,
// 每次状态变化都重置慢速计时:'downloading' 表示新一轮下载开始,其余状态表示下载已结束
setDownloadStatus: (status) => set({ downloadStatus: status, slowDownloadSince: null }),
// 要求 downloadedSize > 0 才起算:下载开始前先写入的那条 speed 为 0 的占位进度,
// 以及 Rust 建连/重定向期间(进度事件还没开始推送)都不应计入慢速时长
setDownloadProgress: (progress) =>
set((state) => ({
downloadProgress: progress,
slowDownloadSince:
progress && progress.downloadedSize > 0 && progress.speed < SLOW_DOWNLOAD_SPEED_BPS
? (state.slowDownloadSince ?? Date.now())
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
: null,
})),
setDownloadSavePath: (path) => set({ downloadSavePath: path }),
resetDownloadState: () =>
set({
downloadStatus: 'idle',
downloadProgress: null,
downloadSavePath: null,
slowDownloadSince: null,
}),

// 安装状态
Expand Down
6 changes: 6 additions & 0 deletions src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ export interface AppState {
currentPage: PageView;
setCurrentPage: (page: PageView) => void;

/** 进入设置页后需要自动滚动到的分区 id(如 'update'),由 SettingsPage 消费后清空 */
settingsTargetSection: string | null;
setSettingsTargetSection: (section: string | null) => void;

// 调试选项(不落盘,每次启动默认关闭)
saveDraw: boolean;
setSaveDraw: (enabled: boolean) => void;
Expand Down Expand Up @@ -456,6 +460,8 @@ export interface AppState {
downloadStatus: DownloadStatus;
downloadProgress: DownloadProgress | null;
downloadSavePath: string | null;
/** 下载速度持续低于阈值的起始时间戳,速度回升或下载结束时置为 null */
slowDownloadSince: number | null;
setDownloadStatus: (status: DownloadStatus) => void;
setDownloadProgress: (progress: DownloadProgress | null) => void;
setDownloadSavePath: (path: string | null) => void;
Expand Down