diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialog.scss b/src/web-ui/src/app/components/AboutDialog/AboutDialog.scss index 6417e7b75c..92b2801624 100644 --- a/src/web-ui/src/app/components/AboutDialog/AboutDialog.scss +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialog.scss @@ -227,109 +227,172 @@ } } -// ==================== Dependencies section ==================== +// ==================== Dependencies section animated entry ==================== .bitfun-about-dialog__dependencies-section { - margin-bottom: 16px; animation: about-section-enter 0.4s ease-out 0.25s both; } -.bitfun-about-dialog__dependencies-toggle { - width: 100%; +@keyframes about-list-expand { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +// ==================== Footer ==================== + +.bitfun-about-dialog__footer { + flex-shrink: 0; + padding: 16px 24px 20px; + text-align: center; + position: relative; + animation: about-section-enter 0.4s ease-out 0.3s both; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 24px; + right: 24px; + height: 1px; + background: linear-gradient(90deg, transparent 0%, var(--border-subtle) 50%, transparent 100%); + } +} + +.bitfun-about-dialog__links { display: flex; align-items: center; - justify-content: space-between; - padding: 10px 14px; - background: transparent; - border: 1px dashed var(--border-subtle); - border-radius: 8px; - color: var(--color-text-secondary); - font-size: 12px; - font-weight: 500; + justify-content: center; + gap: 8px; + margin-bottom: 10px; +} + +.bitfun-about-dialog__link { + font-size: 11px; + color: var(--color-text-muted); + text-decoration: none; cursor: pointer; - transition: all 0.25s ease; - + transition: color 0.2s ease; + background: none; + border: none; + padding: 0; + font-family: inherit; + &:hover { - background: var(--element-bg-subtle); - border-color: var(--border-base); - border-style: solid; - color: var(--color-text-primary); - - svg { - color: var(--color-accent-500); - } - } - - span { - display: flex; - align-items: center; - gap: 8px; + color: var(--color-accent-400); + text-decoration: underline; } +} - svg { - color: var(--color-text-muted); - transition: all 0.2s ease; - } +.bitfun-about-dialog__link-sep { + font-size: 11px; + color: var(--border-subtle); +} + +// ==================== Sub-dialogs (open source / user agreement) ==================== + +.bitfun-about-dialog__sub-content { + padding: 20px 24px 24px; + display: flex; + flex-direction: column; + gap: 20px; + max-height: min(520px, calc(100vh - 200px)); + overflow-y: auto; +} + +.bitfun-about-dialog__sub-desc { + font-size: 12px; + color: var(--color-text-secondary); + line-height: 1.6; + margin: 0; +} + +// ==================== Category section ==================== + +.bitfun-about-dialog__sub-category { + display: flex; + flex-direction: column; + gap: 8px; +} + +.bitfun-about-dialog__sub-category-header { + display: flex; + align-items: center; + gap: 10px; + padding: 0 2px; +} + +.bitfun-about-dialog__sub-category-title { + font-size: 13px; + font-weight: 600; + color: var(--color-text-primary); + margin: 0; } -.bitfun-about-dialog__dependencies-count { +.bitfun-about-dialog__sub-category-count { display: inline-flex; align-items: center; justify-content: center; - min-width: 20px; - height: 18px; - padding: 0 6px; - background: rgba(99, 102, 241, 0.1); - color: var(--color-accent-400); - font-size: 10px; + min-width: 18px; + height: 16px; + padding: 0 5px; + font-size: 9px; font-weight: 600; - border-radius: 9px; + border-radius: 8px; + letter-spacing: 0.02em; } -.bitfun-about-dialog__dependencies-list { - margin-top: 8px; - padding: 4px; - background: transparent; - border: 1px solid var(--border-subtle); - border-radius: 8px; - animation: about-list-expand 0.25s ease-out; +.bitfun-about-dialog__sub-category-count--frontend { + background: rgba(59, 130, 246, 0.12); + color: #60A5FA; } -@keyframes about-list-expand { - from { - opacity: 0; - transform: translateY(-4px); - } - to { - opacity: 1; - transform: translateY(0); - } +.bitfun-about-dialog__sub-category-count--backend { + background: rgba(52, 211, 153, 0.12); + color: #34D399; } +// ==================== Dependencies list grid ==================== + .bitfun-about-dialog__dependencies-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 4px; + gap: 6px; } .bitfun-about-dialog__dependency-item { display: flex; align-items: center; gap: 10px; - padding: 8px 10px; - background: transparent; - border: 1px solid transparent; - border-radius: 6px; + padding: 9px 12px; + background: linear-gradient(135deg, + rgba(255, 255, 255, 0.04) 0%, + rgba(255, 255, 255, 0.02) 100% + ); + border: 1px solid var(--border-subtle); + border-radius: 8px; transition: all 0.2s ease; min-width: 0; - + &:hover { - background: var(--element-bg-subtle); - border-color: var(--border-subtle); - + background: linear-gradient(135deg, + rgba(59, 130, 246, 0.08) 0%, + rgba(139, 92, 246, 0.05) 100% + ); + border-color: rgba(59, 130, 246, 0.25); + transform: translateY(-1px); + box-shadow: + 0 4px 12px rgba(96, 165, 250, 0.1), + 0 2px 4px rgba(0, 0, 0, 0.1); + .bitfun-about-dialog__dependency-icon { - background: rgba(99, 102, 241, 0.1); - + background: rgba(59, 130, 246, 0.15); + svg { color: var(--color-accent-500); } @@ -339,19 +402,19 @@ .bitfun-about-dialog__dependency-icon { flex-shrink: 0; - width: 26px; - height: 26px; + width: 28px; + height: 28px; display: flex; align-items: center; justify-content: center; - background: var(--element-bg-subtle); + background: rgba(255, 255, 255, 0.06); border-radius: 6px; transition: all 0.2s ease; svg { - width: 12px; - height: 12px; - color: var(--color-text-secondary); + width: 13px; + height: 13px; + color: var(--color-text-muted); transition: color 0.2s ease; } } @@ -372,6 +435,17 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + background: none; + border: none; + padding: 0; + cursor: pointer; + text-align: left; + transition: color 0.2s ease; + + &:hover { + color: var(--color-accent-400); + text-decoration: underline; + } } .bitfun-about-dialog__dependency-license { @@ -382,54 +456,93 @@ .bitfun-about-dialog__dependency-tag { display: inline-flex; align-items: center; - padding: 1px 5px; + padding: 2px 6px; font-size: 8px; font-weight: 600; - border-radius: 3px; + border-radius: 4px; text-transform: uppercase; letter-spacing: 0.3px; margin-left: auto; flex-shrink: 0; - - // Frontend tag - blue + &--frontend { - background: rgba(59, 130, 246, 0.12); + background: rgba(59, 130, 246, 0.1); color: #60A5FA; - border: 1px solid rgba(59, 130, 246, 0.2); + border: 1px solid rgba(59, 130, 246, 0.15); } - - // Backend tag - green + &--backend { - background: rgba(34, 197, 94, 0.12); - color: #4ADE80; - border: 1px solid rgba(34, 197, 94, 0.2); + background: rgba(52, 211, 153, 0.1); + color: #34D399; + border: 1px solid rgba(52, 211, 153, 0.15); } - - // General tag - purple - &--general { - background: rgba(168, 85, 247, 0.12); - color: #C084FC; - border: 1px solid rgba(168, 85, 247, 0.2); +} + +// ==================== User agreement card sections ==================== + +.bitfun-about-dialog__sub-card { + display: flex; + flex-direction: column; + gap: 8px; + padding: 14px 16px; + background: linear-gradient(135deg, + rgba(255, 255, 255, 0.03) 0%, + rgba(255, 255, 255, 0.01) 100% + ); + border: 1px solid var(--border-subtle); + border-left: 2px solid var(--color-accent-400); + border-radius: 8px; + transition: all 0.2s ease; + + &:hover { + background: linear-gradient(135deg, + rgba(59, 130, 246, 0.06) 0%, + rgba(139, 92, 246, 0.03) 100% + ); + border-color: rgba(59, 130, 246, 0.2); + border-left-color: var(--color-accent-500); + box-shadow: + 0 4px 12px rgba(96, 165, 250, 0.08), + 0 2px 4px rgba(0, 0, 0, 0.08); } } -// ==================== Footer ==================== +.bitfun-about-dialog__sub-card-heading { + font-size: 13px; + font-weight: 600; + color: var(--color-text-primary); + margin: 0; + display: flex; + align-items: center; + gap: 8px; +} -.bitfun-about-dialog__footer { - flex-shrink: 0; - padding: 16px 24px 20px; +.bitfun-about-dialog__sub-card-text { + font-size: 12px; + color: var(--color-text-secondary); + line-height: 1.7; + margin: 0; +} + +.bitfun-about-dialog__sub-footnote { + font-size: 11px; + color: var(--color-text-muted); + margin: 0; + padding-top: 12px; + border-top: 1px solid var(--border-subtle); text-align: center; - position: relative; - animation: about-section-enter 0.4s ease-out 0.3s both; - - &::before { - content: ''; - position: absolute; - top: 0; - left: 24px; - right: 24px; - height: 1px; - background: linear-gradient(90deg, transparent 0%, var(--border-subtle) 50%, transparent 100%); +} + +// ==================== Responsive ==================== + +@media (max-width: 580px) { + .bitfun-about-dialog__sub-content { + padding: 16px 16px 20px; + max-height: min(460px, calc(100vh - 160px)); + } + + .bitfun-about-dialog__dependencies-grid { + grid-template-columns: 1fr; } } @@ -468,10 +581,6 @@ padding: 0 16px; } - .bitfun-about-dialog__dependencies-grid { - grid-template-columns: 1fr; - } - .bitfun-about-dialog__footer { padding: 14px 16px 16px; diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx index 05e7833390..624739fd74 100644 --- a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx @@ -7,7 +7,8 @@ import React, { useState } from 'react'; import { useI18n } from '@/infrastructure/i18n'; import { Tooltip, Modal } from '@/component-library'; -import { Copy, Check } from 'lucide-react'; +import { Copy, Check, Package } from 'lucide-react'; +import { systemAPI } from '@/infrastructure/api'; import { getAboutInfo, formatVersion, @@ -18,6 +19,76 @@ import './AboutDialog.scss'; const log = createLogger('AboutDialog'); +interface Dependency { + name: string; + url: string; + license: string; + category: 'frontend' | 'backend'; +} + +const dependencies: Dependency[] = [ + // Frontend (TypeScript / JS) + { name: 'React', url: 'https://www.npmjs.com/package/react', license: 'MIT', category: 'frontend' }, + { name: 'React DOM', url: 'https://www.npmjs.com/package/react-dom', license: 'MIT', category: 'frontend' }, + { name: 'Zustand', url: 'https://www.npmjs.com/package/zustand', license: 'MIT', category: 'frontend' }, + { name: 'Immer', url: 'https://www.npmjs.com/package/immer', license: 'MIT', category: 'frontend' }, + { name: 'i18next', url: 'https://www.npmjs.com/package/i18next', license: 'MIT', category: 'frontend' }, + { name: 'react-i18next', url: 'https://www.npmjs.com/package/react-i18next', license: 'MIT', category: 'frontend' }, + { name: 'lucide-react', url: 'https://www.npmjs.com/package/lucide-react', license: 'ISC', category: 'frontend' }, + { name: '@tauri-apps/api', url: 'https://www.npmjs.com/package/@tauri-apps/api', license: 'Apache-2.0', category: 'frontend' }, + { name: '@tauri-apps/plugin-opener', url: 'https://www.npmjs.com/package/@tauri-apps/plugin-opener', license: 'Apache-2.0', category: 'frontend' }, + { name: '@tauri-apps/plugin-dialog', url: 'https://www.npmjs.com/package/@tauri-apps/plugin-dialog', license: 'Apache-2.0', category: 'frontend' }, + { name: '@tanstack/react-virtual', url: 'https://www.npmjs.com/package/@tanstack/react-virtual', license: 'MIT', category: 'frontend' }, + { name: 'Monaco Editor', url: 'https://www.npmjs.com/package/monaco-editor', license: 'MIT', category: 'frontend' }, + { name: '@monaco-editor/react', url: 'https://www.npmjs.com/package/@monaco-editor/react', license: 'MIT', category: 'frontend' }, + { name: 'TipTap', url: 'https://www.npmjs.com/package/@tiptap/react', license: 'MIT', category: 'frontend' }, + { name: 'react-markdown', url: 'https://www.npmjs.com/package/react-markdown', license: 'MIT', category: 'frontend' }, + { name: 'react-syntax-highlighter', url: 'https://www.npmjs.com/package/react-syntax-highlighter', license: 'MIT', category: 'frontend' }, + { name: 'react-virtuoso', url: 'https://www.npmjs.com/package/react-virtuoso', license: 'MIT', category: 'frontend' }, + { name: 'xterm.js', url: 'https://www.npmjs.com/package/@xterm/xterm', license: 'MIT', category: 'frontend' }, + { name: 'Mermaid', url: 'https://www.npmjs.com/package/mermaid', license: 'MIT', category: 'frontend' }, + { name: 'KaTeX', url: 'https://www.npmjs.com/package/katex', license: 'MIT', category: 'frontend' }, + { name: 'highlight.js', url: 'https://www.npmjs.com/package/highlight.js', license: 'BSD-3-Clause', category: 'frontend' }, + { name: 'PrismJS', url: 'https://www.npmjs.com/package/prismjs', license: 'MIT', category: 'frontend' }, + { name: 'diff', url: 'https://www.npmjs.com/package/diff', license: 'BSD-3-Clause', category: 'frontend' }, + { name: 'morphdom', url: 'https://www.npmjs.com/package/morphdom', license: 'MIT', category: 'frontend' }, + { name: 'html-to-image', url: 'https://www.npmjs.com/package/html-to-image', license: 'MIT', category: 'frontend' }, + { name: 'qrcode.react', url: 'https://www.npmjs.com/package/qrcode.react', license: 'MIT', category: 'frontend' }, + { name: 'Vite', url: 'https://www.npmjs.com/package/vite', license: 'MIT', category: 'frontend' }, + { name: 'TypeScript', url: 'https://www.npmjs.com/package/typescript', license: 'Apache-2.0', category: 'frontend' }, + // Backend (Rust) + { name: 'Tokio', url: 'https://crates.io/crates/tokio', license: 'MIT', category: 'backend' }, + { name: 'Serde', url: 'https://crates.io/crates/serde', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Reqwest', url: 'https://crates.io/crates/reqwest', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Axum', url: 'https://crates.io/crates/axum', license: 'MIT', category: 'backend' }, + { name: 'Tauri', url: 'https://crates.io/crates/tauri', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'git2 (libgit2)', url: 'https://crates.io/crates/git2', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Chrono', url: 'https://crates.io/crates/chrono', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'UUID', url: 'https://crates.io/crates/uuid', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Regex', url: 'https://crates.io/crates/regex', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Anyhow', url: 'https://crates.io/crates/anyhow', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Thiserror', url: 'https://crates.io/crates/thiserror', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Futures', url: 'https://crates.io/crates/futures', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Image', url: 'https://crates.io/crates/image', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Zip', url: 'https://crates.io/crates/zip', license: 'MIT', category: 'backend' }, + { name: 'DashMap', url: 'https://crates.io/crates/dashmap', license: 'MIT', category: 'backend' }, + { name: 'IndexMap', url: 'https://crates.io/crates/indexmap', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'tower-http', url: 'https://crates.io/crates/tower-http', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'tokio-tungstenite', url: 'https://crates.io/crates/tokio-tungstenite', license: 'MIT', category: 'backend' }, + { name: 'Clap', url: 'https://crates.io/crates/clap', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Similar', url: 'https://crates.io/crates/similar', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Notifly', url: 'https://crates.io/crates/notify', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'Fluent', url: 'https://crates.io/crates/fluent-bundle', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'AES-GCM', url: 'https://crates.io/crates/aes-gcm', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'X25519-Dalek', url: 'https://crates.io/crates/x25519-dalek', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'SHA2', url: 'https://crates.io/crates/sha2', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'russh', url: 'https://crates.io/crates/russh', license: 'MIT', category: 'backend' }, + { name: 'Ratatui', url: 'https://crates.io/crates/ratatui', license: 'MIT', category: 'backend' }, + { name: 'pulldown-cmark', url: 'https://crates.io/crates/pulldown-cmark', license: 'MIT', category: 'backend' }, + { name: 'base64', url: 'https://crates.io/crates/base64', license: 'Apache-2.0 OR MIT', category: 'backend' }, + { name: 'parking_lot', url: 'https://crates.io/crates/parking_lot', license: 'Apache-2.0 OR MIT', category: 'backend' }, +]; + interface AboutDialogProps { /** Whether visible */ isOpen: boolean; @@ -31,6 +102,7 @@ export const AboutDialog: React.FC = ({ }) => { const { t } = useI18n('common'); const [copiedItem, setCopiedItem] = useState(null); + const [subDialog, setSubDialog] = useState<'openSource' | 'userAgreement' | null>(null); const aboutInfo = getAboutInfo(); const { version, license } = aboutInfo; @@ -46,6 +118,7 @@ export const AboutDialog: React.FC = ({ }; return ( + <> = ({ {/* Footer */}
+
+ + · + +

{license.text}

{t('about.copyright')} @@ -117,6 +207,135 @@ export const AboutDialog: React.FC = ({

+ + {/* Open Source Software dialog */} + setSubDialog(null)} + title={t('about.openSource')} + showCloseButton={true} + size="medium" + > +
+

+ {t('about.openSourceDesc')} +

+ +
+
+
+

Frontend

+ + {dependencies.filter(d => d.category === 'frontend').length} + +
+
+ {dependencies.filter(d => d.category === 'frontend').map((dep) => ( +
+
+ +
+
+ + + {dep.license} + +
+ + FE + +
+ ))} +
+
+
+ +
+
+
+

Backend

+ + {dependencies.filter(d => d.category === 'backend').length} + +
+
+ {dependencies.filter(d => d.category === 'backend').map((dep) => ( +
+
+ +
+
+ + + {dep.license} + +
+ + BE + +
+ ))} +
+
+
+ +

+ {t('about.openSourceFootnote')} +

+
+
+ + {/* User Agreement dialog */} + setSubDialog(null)} + title={t('about.userAgreement')} + showCloseButton={true} + size="medium" + > +
+
+

+ 1. 服务使用 +

+

+ 用户在使用 BitFun 服务时应遵守相关法律法规。本软件仅供合法用途使用,不得用于任何非法或未经授权的活动。 +

+
+
+

+ 2. 免责声明 +

+

+ 本软件按"现状"提供,不提供任何明示或暗示的保证。在适用法律允许的最大范围内,开发者不承担任何损害赔偿的责任。使用风险由用户自行承担。 +

+
+
+

+ 3. 隐私政策 +

+

+ 我们重视你的隐私。本软件可能会收集必要的使用数据以改善服务质量。详细的隐私政策请参阅官方网站。 +

+
+

+ 完整协议内容将在后续版本中完善。 +

+
+
+ ); }; diff --git a/src/web-ui/src/locales/en-US/common.json b/src/web-ui/src/locales/en-US/common.json index f06f39b19a..67f545cbb4 100644 --- a/src/web-ui/src/locales/en-US/common.json +++ b/src/web-ui/src/locales/en-US/common.json @@ -484,7 +484,11 @@ "buildDate": "Build Date", "commit": "Commit", "branch": "Branch", - "copyright": "© 2025 BitFun. All rights reserved." + "copyright": "© 2025 BitFun. All rights reserved.", + "openSource": "Open Source Software", + "openSourceDesc": "BitFun uses the following open source components:", + "openSourceFootnote": "Click a component name to open its official page for more information.", + "userAgreement": "User Agreement" }, "actions": { "confirm": "Confirm", diff --git a/src/web-ui/src/locales/zh-CN/common.json b/src/web-ui/src/locales/zh-CN/common.json index c61824a406..9b0e22184f 100644 --- a/src/web-ui/src/locales/zh-CN/common.json +++ b/src/web-ui/src/locales/zh-CN/common.json @@ -484,7 +484,11 @@ "buildDate": "构建日期", "commit": "提交", "branch": "分支", - "copyright": "© 2025 BitFun. All rights reserved." + "copyright": "© 2025 BitFun. All rights reserved.", + "openSource": "开源软件", + "openSourceDesc": "BitFun 使用了以下开源组件:", + "openSourceFootnote": "点击组件名称可打开其官方页面获取更多信息。", + "userAgreement": "用户协议" }, "actions": { "confirm": "确认", diff --git a/src/web-ui/src/locales/zh-TW/common.json b/src/web-ui/src/locales/zh-TW/common.json index 44c4444fba..28502c7245 100644 --- a/src/web-ui/src/locales/zh-TW/common.json +++ b/src/web-ui/src/locales/zh-TW/common.json @@ -484,7 +484,11 @@ "buildDate": "構建日期", "commit": "提交", "branch": "分支", - "copyright": "© 2025 BitFun. All rights reserved." + "copyright": "© 2025 BitFun. All rights reserved.", + "openSource": "開源軟件", + "openSourceDesc": "BitFun 使用了以下開源組件:", + "openSourceFootnote": "點擊組件名稱可打開其官方頁面獲取更多資訊。", + "userAgreement": "用戶協議" }, "actions": { "confirm": "確認",