cdrop — 跨 OS 剪贴板与文件传输服务
Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。 - 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。 - 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。 - 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。 - 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。 架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。 本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import "@mantine/core/styles.css";
|
||||
import "@mantine/dropzone/styles.css";
|
||||
import "./styles/index.css";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import {
|
||||
MantineProvider,
|
||||
createTheme,
|
||||
type MantineColorsTuple,
|
||||
} from "@mantine/core";
|
||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||
import { ToastViewport } from "./ui/feedback";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import { useAppStore, type ThemeMode } from "./store";
|
||||
import { startCjkAutospace } from "./utils/cjkAutospace";
|
||||
import { initDesktopMenuBridge, loadDesktopSettings } from "./net/desktop";
|
||||
|
||||
// Mantine 需要 10 阶静态色,硬编码值与 tokens.semantic.css 的 --accent-* 阶
|
||||
// 严格对齐(Dracula Purple 谱)。组件颜色仍读 var(--accent),这里仅满足
|
||||
// Mantine 内部 primaryColor 索引。
|
||||
//
|
||||
// primaryShade = { light: 7, dark: 4 }:
|
||||
// - light 取 [7] = #644AC9(Alucard Purple)
|
||||
// - dark 取 [4] = #AA99FF(Dracula Base BrightBlue,Purple 的官方亮版,
|
||||
// 在深色背景上对比度 6.7:1,作链接/选中文字可读性达 AAA Large)
|
||||
const accentTuple: MantineColorsTuple = [
|
||||
"#F3F0FF",
|
||||
"#E4DEFF",
|
||||
"#C9BEFF",
|
||||
"#AC9BFE",
|
||||
"#AA99FF",
|
||||
"#9580FF",
|
||||
"#7458EE",
|
||||
"#644AC9",
|
||||
"#523CA8",
|
||||
"#3F2D85",
|
||||
];
|
||||
|
||||
const theme = createTheme({
|
||||
primaryColor: "accent",
|
||||
primaryShade: { light: 7, dark: 4 },
|
||||
defaultRadius: "md",
|
||||
radius: { sm: "6px", md: "10px", lg: "14px" },
|
||||
fontFamily: "var(--font-sans)",
|
||||
fontFamilyMonospace: "var(--font-mono)",
|
||||
headings: { fontFamily: "var(--font-sans)" },
|
||||
colors: { accent: accentTuple },
|
||||
});
|
||||
|
||||
const router = createRouter({ routeTree, defaultPreload: "intent" });
|
||||
|
||||
// 桌面端:菜单栏「设置」→ 路由到 /settings(浏览器里为 no-op)。
|
||||
initDesktopMenuBridge(() => { void router.navigate({ to: "/settings" }); });
|
||||
// 桌面端:启动即加载本地配置,把剪贴板同步开关应用到门控(浏览器里为 no-op)。
|
||||
void loadDesktopSettings();
|
||||
|
||||
// Cmd+,(macOS)/ Ctrl+,(Windows·Linux)→ 打开设置,桌面与浏览器通用。桌面端
|
||||
// 原生菜单未占用 ⌘,,故按键会落到 WebView 由此处理;未登录时 /settings 守卫会
|
||||
// 自行重定向,无需额外判断。
|
||||
window.addEventListener("keydown", (e) =>
|
||||
{
|
||||
if ((e.metaKey || e.ctrlKey) && !e.altKey && e.key === ",")
|
||||
{
|
||||
e.preventDefault();
|
||||
void router.navigate({ to: "/settings" });
|
||||
}
|
||||
});
|
||||
|
||||
declare module "@tanstack/react-router"
|
||||
{
|
||||
interface Register
|
||||
{
|
||||
router: typeof router;
|
||||
}
|
||||
}
|
||||
|
||||
// system 模式:移除 data-theme attribute,让 tokens.semantic.css 中的
|
||||
// @media (prefers-color-scheme: dark) 接管;light / dark 时写死 attribute。
|
||||
function applyTheme(mode: ThemeMode): void
|
||||
{
|
||||
const root = document.documentElement;
|
||||
if (mode === "system") { root.removeAttribute("data-theme"); }
|
||||
else { root.setAttribute("data-theme", mode); }
|
||||
}
|
||||
|
||||
// 切语言时给 RouterProvider 换 key 强制全树重挂载,让所有 t() 调用以新 locale
|
||||
// 重新求值;t() 仍是纯函数,避免改造每个调用点。这样比 location.reload() 安全
|
||||
// (内存里的 refresh_token 不丢)。
|
||||
function App()
|
||||
{
|
||||
const locale = useAppStore((s) => s.locale);
|
||||
const theme$ = useAppStore((s) => s.theme);
|
||||
|
||||
useEffect(() => { applyTheme(theme$); }, [ theme$ ]);
|
||||
|
||||
// Mantine 自身也维护 colorScheme(决定 [data-mantine-color-scheme])。
|
||||
// light / dark 用 forceColorScheme 锁死;system 时不传 force,让其
|
||||
// 内部跟随 prefers-color-scheme。store.theme 作为单一来源同步双方。
|
||||
const force = theme$ === "system" ? undefined : theme$;
|
||||
|
||||
return (
|
||||
<MantineProvider
|
||||
theme={theme}
|
||||
defaultColorScheme="auto"
|
||||
forceColorScheme={force}
|
||||
>
|
||||
<ToastViewport />
|
||||
<RouterProvider key={locale} router={router} />
|
||||
</MantineProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// 首屏在 React 挂载前先应用一次主题,避免 FOUC。桌面壳的登录态已在 store 初始化
|
||||
// 时从注入的 window.__CDROP_BOOT__ 同步水合(store/helpers.ts),无需异步等待。
|
||||
applyTheme(useAppStore.getState().theme);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
// CJK 自动间距 shim:在 React 完成首次挂载后启动。MutationObserver 后续
|
||||
// 增量处理 React 重渲染产生的新文本节点。放在 createRoot 之后,首帧绘制
|
||||
// 完会触发一次 runShim() 处理已挂载的子树。
|
||||
requestAnimationFrame(() => { startCjkAutospace(); });
|
||||
Reference in New Issue
Block a user