feat(web): show build info and theme menu [CI]
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/dev-image Pipeline was successful

This commit is contained in:
justme committed 2026-10-12 16:40:50 +08:00
1 parent 98a5e67f82
commit 89cd92f7ee
10 files changed
+80 -3

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
declare const __WEBDROP_BUILD_INFO__: {
readonly frontendVersion: string;
readonly commit: string;
readonly tag: string;
readonly builtAt: string;
};
+26 -1
View File
@@ -6,13 +6,38 @@
animation: page-in 0.21s ease-out;
}
.appearance {
position: fixed;
z-index: 30;
top: max(var(--space-4), env(safe-area-inset-top));
right: max(var(--space-4), env(safe-area-inset-right));
}
.buildInfo {
position: fixed;
z-index: 1;
right: max(var(--space-4), env(safe-area-inset-right));
bottom: max(var(--space-3), env(safe-area-inset-bottom));
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-1) var(--space-3);
max-width: calc(100vw - var(--space-6));
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.5;
text-align: right;
pointer-events: none;
}
.main {
display: flex;
flex-direction: column;
gap: var(--space-4);
max-width: 860px;
margin: 0 auto;
padding: var(--space-6) var(--space-4);
padding: var(--space-6) var(--space-4) calc(var(--space-6) + 32px);
}
.head {
+10
View File
@@ -1,5 +1,6 @@
import { lazy, Suspense, useCallback, useState } from 'react';
import { channelPath, navigate, sharePath } from '@/app/navigation';
import { buildInfo } from '@/lib/buildInfo';
import type { ChannelInfo, Share } from '@/core-bridge/types';
import type { ClipboardPayload } from '@/lib/clipboard';
import { useGlobalPaste } from '@/lib/useGlobalPaste';
@@ -8,6 +9,7 @@ import { useShares } from '@/store/shares';
import { solveCreationCaptcha } from '@/core-bridge/captcha';
import { errorMessage } from '@/core-bridge/errors';
import { Button } from '@/ui/Button';
import { ThemeMenu } from '@/ui/ThemeMenu';
import { ChannelList } from './ChannelList';
import { CreateSheet } from './CreateSheet';
import { QuickShare } from './QuickShare';
@@ -58,6 +60,7 @@ export function Home() {
return (
<div className={styles.page}>
<div className={styles.appearance}><ThemeMenu /></div>
<main className={styles.main}>
<header className={styles.head}>
<h1 className={styles.logo}>WebDrop</h1>
@@ -95,6 +98,13 @@ export function Home() {
) : null}
</main>
<aside className={styles.buildInfo} aria-label="构建信息">
<span>前端 v{buildInfo.frontendVersion}</span>
<span>tag {buildInfo.tag && buildInfo.tag !== 'unknown' ? buildInfo.tag : '未标记'}</span>
<span>commit {buildInfo.commit}</span>
<time dateTime={buildInfo.builtAt}>{buildInfo.builtAt} UTC</time>
</aside>
{createOpen ? <CreateSheet onClose={() => setCreateOpen(false)} onCreated={open} /> : null}
<Suspense fallback={null}>
{scanOpen ? <ScanSheet onClose={() => setScanOpen(false)} onChannel={open} onShare={open} /> : null}
+1
View File
@@ -0,0 +1 @@
export const buildInfo = __WEBDROP_BUILD_INFO__;
+21
View File
@@ -1,9 +1,30 @@
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vitest/config';
const webRoot = fileURLToPath(new URL('.', import.meta.url));
const webPackage = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')) as { version: string };
function gitValue(...args: string[]): string {
try {
return execFileSync('git', args, { cwd: webRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || 'unknown';
} catch {
return 'unknown';
}
}
const buildInfo = {
frontendVersion: webPackage.version,
commit: process.env.WEBDROP_BUILD_COMMIT ?? gitValue('rev-parse', '--short=12', 'HEAD'),
tag: process.env.WEBDROP_BUILD_TAG ?? gitValue('describe', '--tags', '--exact-match', 'HEAD'),
builtAt: process.env.WEBDROP_BUILD_TIME ?? new Date().toISOString(),
};
export default defineConfig({
plugins: [react()],
define: { __WEBDROP_BUILD_INFO__: JSON.stringify(buildInfo) },
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),