|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
|
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
|
|
import { openUrl, revealItemInDir } from "@tauri-apps/plugin-opener";
|
|
|
-import { Button, Divider, InputNumber, notification, Tooltip } from "antd";
|
|
|
+import { Button, ConfigProvider, Divider, InputNumber, Layout, Menu, notification, Space, theme, Tooltip } from "antd";
|
|
|
import { mockTasks, type Task } from "./mocks/tasks";
|
|
|
import {
|
|
|
EVT_RECORDING_FAILED,
|
|
|
@@ -28,15 +28,21 @@ import {
|
|
|
} from "./lib/recorder";
|
|
|
import "./App.css";
|
|
|
import {
|
|
|
+ BankOutlined,
|
|
|
+ CloseOutlined,
|
|
|
FileImageOutlined,
|
|
|
FolderOpenOutlined,
|
|
|
+ LinkOutlined,
|
|
|
LoadingOutlined,
|
|
|
+ MinusOutlined,
|
|
|
PictureOutlined,
|
|
|
PlayCircleFilled,
|
|
|
StopOutlined,
|
|
|
VideoCameraOutlined,
|
|
|
} from "@ant-design/icons";
|
|
|
|
|
|
+const { Header, Content, Footer, Sider } = Layout;
|
|
|
+
|
|
|
/**
|
|
|
* 与 Rust 端常量保持一致(src-tauri/src/lib.rs):
|
|
|
* LEFT_PANEL_WIDTH = 180
|
|
|
@@ -96,7 +102,11 @@ function App() {
|
|
|
// antd 6 notification 必须用 hook + contextHolder 才能正确取到主题
|
|
|
const [notifyApi, notifyContext] = notification.useNotification();
|
|
|
|
|
|
- function handleSelectTask(t: Task) {
|
|
|
+ function handleSelectTask(key: string) {
|
|
|
+ const t = mockTasks.find((v) => v.id == key);
|
|
|
+ if (!t) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
setActiveId(t.id);
|
|
|
void loadInWebview(t.id, t.url);
|
|
|
}
|
|
|
@@ -405,217 +415,406 @@ function App() {
|
|
|
}, []);
|
|
|
|
|
|
// 录制状态机(RecordState 类型已抽到 src/types/ipc.ts),录制功能下一阶段接入
|
|
|
-
|
|
|
+ const [collapsed, setCollapsed] = useState(false);
|
|
|
+ const {
|
|
|
+ token: { colorBgContainer },
|
|
|
+ } = theme.useToken();
|
|
|
return (
|
|
|
- <div className="flex h-screen w-screen overflow-hidden select-none">
|
|
|
- {/* antd notification 的渲染容器 —— 必须挂在树中 */}
|
|
|
- {notifyContext}
|
|
|
- {/* ===== 左栏:任务列表(180px) ===== */}
|
|
|
- <aside className="w-[180px] shrink-0 border-r border-gray-4 bg-gray-2 h-full flex flex-col">
|
|
|
- <div className="px-3 py-2 text-xs text-gray-7 border-b border-gray-4 select-none">
|
|
|
- 任务列表
|
|
|
- </div>
|
|
|
- <div className="flex-1 overflow-y-auto">
|
|
|
- <ul>
|
|
|
- {mockTasks.map((t) => {
|
|
|
- const active = activeId === t.id;
|
|
|
- return (
|
|
|
- <li
|
|
|
- key={t.id}
|
|
|
- className={
|
|
|
- "flex items-center justify-between px-3 py-2 text-sm cursor-pointer transition-colors " +
|
|
|
- (active
|
|
|
- ? "bg-primary-1 text-primary-7"
|
|
|
- : "hover:bg-gray-3 text-gray-10")
|
|
|
- }
|
|
|
- onClick={() => handleSelectTask(t)}
|
|
|
+ <Layout className="m-0 p-0 h-full overflow-hidden">
|
|
|
+ <Header className="app-title h-8 select-none" style={{ paddingLeft: 8 }} >
|
|
|
+ <div className="flex h-full flex-row items-center">
|
|
|
+ <Space size={8}>
|
|
|
+ <Button className="app-close" icon={<CloseOutlined />} size="large" />
|
|
|
+ <Button icon={<BankOutlined />} disabled size="large" />
|
|
|
+ <Button className="app-minimize" icon={<MinusOutlined />} size="large" />
|
|
|
+ </Space>
|
|
|
+ <div className="w-32" />
|
|
|
+ <Tooltip title={activeId ? "截图整页(覆盖之前的自动截图)" : "请先选择任务"} placement="top">
|
|
|
+ <Button
|
|
|
+
|
|
|
+ shape="circle"
|
|
|
+ icon={<PictureOutlined />}
|
|
|
+ disabled={!activeId}
|
|
|
+ onClick={() => void handleManualCapture()}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ <Divider orientation="vertical" />
|
|
|
+ {/* 开始按钮:仅 idle + 已选任务时可点 */}
|
|
|
+ <Tooltip title={startConfig.tip} placement="top">
|
|
|
+ <Button
|
|
|
+
|
|
|
+ shape="circle"
|
|
|
+ icon={startConfig.icon}
|
|
|
+ disabled={startDisabled}
|
|
|
+ onClick={() => void handleStartRecord()}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ {/* 停止按钮:仅 recording 时可用 */}
|
|
|
+ <Tooltip
|
|
|
+ title={
|
|
|
+ stopDisabled
|
|
|
+ ? "无进行中的录制"
|
|
|
+ : "停止录制并落盘 mp4(F11)"
|
|
|
+ }
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+
|
|
|
+ shape="circle"
|
|
|
+ icon={<StopOutlined />}
|
|
|
+ disabled={stopDisabled}
|
|
|
+ onClick={() => void handleStopRecord()}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ <Divider orientation="vertical" />
|
|
|
+ {/* 打开文件夹:reveal mp4 → png → 兜底 screenshots 目录 */}
|
|
|
+ <Tooltip
|
|
|
+ title={
|
|
|
+ !activeId
|
|
|
+ ? "请先选择任务"
|
|
|
+ : assets?.recording_exists
|
|
|
+ ? "在文件管理器中显示录制视频"
|
|
|
+ : assets?.screenshot_exists
|
|
|
+ ? "在文件管理器中显示截图"
|
|
|
+ : "打开截图目录"
|
|
|
+ }
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+
|
|
|
+ shape="circle"
|
|
|
+ icon={<FolderOpenOutlined />}
|
|
|
+ disabled={!assets}
|
|
|
+ onClick={() => void handleOpenFolder()}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ {/* 预览截图:仅当 png 存在 */}
|
|
|
+ <Tooltip
|
|
|
+ title={
|
|
|
+ !assets?.screenshot_exists
|
|
|
+ ? "暂无截图可预览"
|
|
|
+ : "在新窗口预览截图"
|
|
|
+ }
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+
|
|
|
+ shape="circle"
|
|
|
+ icon={<FileImageOutlined />}
|
|
|
+ disabled={!assets?.screenshot_exists}
|
|
|
+ onClick={() => void handlePreviewImage()}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ {/* 预览视频:仅当 mp4 存在 */}
|
|
|
+ <Tooltip
|
|
|
+ title={
|
|
|
+ !assets?.recording_exists
|
|
|
+ ? "暂无录制视频可预览"
|
|
|
+ : "在新窗口预览录制视频"
|
|
|
+ }
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+
|
|
|
+ shape="circle"
|
|
|
+ icon={<VideoCameraOutlined />}
|
|
|
+ disabled={!assets?.recording_exists}
|
|
|
+ onClick={() => void handlePreviewVideo()}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ <div className="flex-1">
|
|
|
+ {notifyContext}
|
|
|
+ </div>
|
|
|
+ {!customMode ? (
|
|
|
+ <>
|
|
|
+ {SIZE_PRESETS.map((p) => (
|
|
|
+ <Button
|
|
|
+ key={p.label}
|
|
|
+
|
|
|
+ disabled={contentSize.w == p.w && contentSize.h == p.h}
|
|
|
+ onClick={() => applyWorkAreaSize(p.w, p.h)}
|
|
|
>
|
|
|
- <span className="truncate">任务 {t.id}</span>
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- title="在系统浏览器打开"
|
|
|
- className="ml-2 inline-flex items-center justify-center w-6 h-6 rounded-sm hover:bg-gray-4 text-gray-7 hover:text-primary-6"
|
|
|
- onClick={(e) => {
|
|
|
- e.stopPropagation();
|
|
|
- void openInBrowser(t.url);
|
|
|
- }}
|
|
|
- >
|
|
|
- {/* 外链图标(lucide external-link 同款 path,不引入新依赖) */}
|
|
|
- <svg
|
|
|
- width="14"
|
|
|
- height="14"
|
|
|
- viewBox="0 0 24 24"
|
|
|
- fill="none"
|
|
|
- stroke="currentColor"
|
|
|
- strokeWidth="2"
|
|
|
- strokeLinecap="round"
|
|
|
- strokeLinejoin="round"
|
|
|
- >
|
|
|
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
|
- <polyline points="15 3 21 3 21 9" />
|
|
|
- <line x1="10" y1="14" x2="21" y2="3" />
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </li>
|
|
|
- );
|
|
|
- })}
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- </aside>
|
|
|
-
|
|
|
- {/* ===== 右侧主区 ===== */}
|
|
|
- <main className="flex-1 flex flex-col min-w-0">
|
|
|
- {/* 工具栏(48px) */}
|
|
|
- <header className="h-12 shrink-0 flex items-center justify-between gap-2 px-3 border-b border-gray-4 bg-gray-1">
|
|
|
- {/* 左侧:操作按钮区 */}
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <Tooltip title={activeId ? "截图整页(覆盖之前的自动截图)" : "请先选择任务"} placement="top">
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- icon={<PictureOutlined />}
|
|
|
- disabled={!activeId}
|
|
|
- onClick={() => void handleManualCapture()}
|
|
|
- />
|
|
|
- </Tooltip>
|
|
|
- <Divider type="vertical" />
|
|
|
- {/* 开始按钮:仅 idle + 已选任务时可点 */}
|
|
|
- <Tooltip title={startConfig.tip} placement="top">
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- icon={startConfig.icon}
|
|
|
- disabled={startDisabled}
|
|
|
- onClick={() => void handleStartRecord()}
|
|
|
- />
|
|
|
- </Tooltip>
|
|
|
- {/* 停止按钮:仅 recording 时可用 */}
|
|
|
- <Tooltip
|
|
|
- title={
|
|
|
- stopDisabled
|
|
|
- ? "无进行中的录制"
|
|
|
- : "停止录制并落盘 mp4(F11)"
|
|
|
- }
|
|
|
- placement="top"
|
|
|
- >
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- icon={<StopOutlined />}
|
|
|
- disabled={stopDisabled}
|
|
|
- onClick={() => void handleStopRecord()}
|
|
|
- />
|
|
|
- </Tooltip>
|
|
|
- <Divider type="vertical" />
|
|
|
- {/* 打开文件夹:reveal mp4 → png → 兜底 screenshots 目录 */}
|
|
|
- <Tooltip
|
|
|
- title={
|
|
|
- !activeId
|
|
|
- ? "请先选择任务"
|
|
|
- : assets?.recording_exists
|
|
|
- ? "在文件管理器中显示录制视频"
|
|
|
- : assets?.screenshot_exists
|
|
|
- ? "在文件管理器中显示截图"
|
|
|
- : "打开截图目录"
|
|
|
- }
|
|
|
- placement="top"
|
|
|
- >
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- icon={<FolderOpenOutlined />}
|
|
|
- disabled={!assets}
|
|
|
- onClick={() => void handleOpenFolder()}
|
|
|
+ {p.label}
|
|
|
+ </Button>
|
|
|
+ ))}
|
|
|
+ <Button onClick={() => setCustomMode(true)}>
|
|
|
+ 自定义
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <InputNumber
|
|
|
+
|
|
|
+ min={200}
|
|
|
+ max={3840}
|
|
|
+ value={contentSize.w}
|
|
|
+ onChange={(v) => setContentSize({ w: v || 0, h: contentSize.h })}
|
|
|
+ style={{ width: 80 }}
|
|
|
/>
|
|
|
- </Tooltip>
|
|
|
- {/* 预览截图:仅当 png 存在 */}
|
|
|
- <Tooltip
|
|
|
- title={
|
|
|
- !assets?.screenshot_exists
|
|
|
- ? "暂无截图可预览"
|
|
|
- : "在新窗口预览截图"
|
|
|
- }
|
|
|
- placement="top"
|
|
|
- >
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- icon={<FileImageOutlined />}
|
|
|
- disabled={!assets?.screenshot_exists}
|
|
|
- onClick={() => void handlePreviewImage()}
|
|
|
+ <span className="text-gray-7 select-none">×</span>
|
|
|
+ <InputNumber
|
|
|
+
|
|
|
+ min={200}
|
|
|
+ max={2160}
|
|
|
+ value={contentSize.h}
|
|
|
+ onChange={(v) => setContentSize({ w: contentSize.w, h: v || 0 })}
|
|
|
+ style={{ width: 80 }}
|
|
|
/>
|
|
|
- </Tooltip>
|
|
|
- {/* 预览视频:仅当 mp4 存在 */}
|
|
|
- <Tooltip
|
|
|
- title={
|
|
|
- !assets?.recording_exists
|
|
|
- ? "暂无录制视频可预览"
|
|
|
- : "在新窗口预览录制视频"
|
|
|
- }
|
|
|
- placement="top"
|
|
|
- >
|
|
|
<Button
|
|
|
- size="small"
|
|
|
- icon={<VideoCameraOutlined />}
|
|
|
- disabled={!assets?.recording_exists}
|
|
|
- onClick={() => void handlePreviewVideo()}
|
|
|
- />
|
|
|
- </Tooltip>
|
|
|
- </div>
|
|
|
|
|
|
- {/* 右侧:尺寸预设 / 自定义 */}
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- {!customMode ? (
|
|
|
- <>
|
|
|
- {SIZE_PRESETS.map((p) => (
|
|
|
- <Button
|
|
|
- key={p.label}
|
|
|
- size="small"
|
|
|
- disabled={contentSize.w == p.w && contentSize.h == p.h}
|
|
|
- onClick={() => applyWorkAreaSize(p.w, p.h)}
|
|
|
- >
|
|
|
- {p.label}
|
|
|
- </Button>
|
|
|
- ))}
|
|
|
- <Button size="small" onClick={() => setCustomMode(true)}>
|
|
|
- 自定义
|
|
|
- </Button>
|
|
|
- </>
|
|
|
- ) : (
|
|
|
- <>
|
|
|
- <InputNumber
|
|
|
- size="small"
|
|
|
- min={200}
|
|
|
- max={3840}
|
|
|
- value={contentSize.w}
|
|
|
- onChange={(v) => setContentSize({ w: v || 0, h: contentSize.h })}
|
|
|
- style={{ width: 80 }}
|
|
|
- />
|
|
|
- <span className="text-gray-7 select-none">×</span>
|
|
|
- <InputNumber
|
|
|
- size="small"
|
|
|
- min={200}
|
|
|
- max={2160}
|
|
|
- value={contentSize.h}
|
|
|
- onChange={(v) => setContentSize({ w: contentSize.w, h: v || 0 })}
|
|
|
- style={{ width: 80 }}
|
|
|
- />
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="primary"
|
|
|
- onClick={() => applyWorkAreaSize(contentSize.w, contentSize.h)}
|
|
|
- >
|
|
|
- 应用
|
|
|
- </Button>
|
|
|
- <Button size="small" onClick={() => setCustomMode(false)}>
|
|
|
- 取消
|
|
|
- </Button>
|
|
|
- </>
|
|
|
+ type="primary"
|
|
|
+ onClick={() => applyWorkAreaSize(contentSize.w, contentSize.h)}
|
|
|
+ >
|
|
|
+ 应用
|
|
|
+ </Button>
|
|
|
+ <Button onClick={() => setCustomMode(false)}>
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </Header>
|
|
|
+ <Layout>
|
|
|
+ <Sider className="select-none" collapsible collapsed={collapsed} onCollapse={(c) => setCollapsed(c)} width={180} style={{ background: colorBgContainer }}>
|
|
|
+ <Menu
|
|
|
+ mode="inline"
|
|
|
+ defaultSelectedKeys={[activeId || '']}
|
|
|
+ selectedKeys={[activeId || '']}
|
|
|
+ style={{ height: '100%', borderInlineEnd: 0 }}
|
|
|
+ onClick={({ key }) => handleSelectTask(key)}
|
|
|
+
|
|
|
+ >
|
|
|
+ {mockTasks.map((t) => <Menu.Item icon={<LinkOutlined />} key={t.id}>
|
|
|
+ {t.id}
|
|
|
+ </Menu.Item>
|
|
|
)}
|
|
|
- </div>
|
|
|
- </header>
|
|
|
-
|
|
|
- {/* 工作区占位:实际由 Rust child webview 覆盖在此区域之上 */}
|
|
|
- <section className="flex-1 bg-gray-7" />
|
|
|
- <section className="h-10 bg-gray-5 broder border-gray-7" />
|
|
|
- </main>
|
|
|
- </div>
|
|
|
+ </Menu>
|
|
|
+ </Sider>
|
|
|
+ <Layout>
|
|
|
+ <Content className="m-0 p-1 h-full w-full bg-indigo-950" />
|
|
|
+ </Layout>
|
|
|
+ </Layout>
|
|
|
+ </Layout>
|
|
|
);
|
|
|
+ // <div data-tauri-decorum-tb className="flex h-screen w-screen overflow-hidden select-none">
|
|
|
+ // {/* antd notification 的渲染容器 —— 必须挂在树中 */}
|
|
|
+ // {notifyContext}
|
|
|
+ // {/* ===== 左栏:任务列表(180px) ===== */}
|
|
|
+ // <aside data-tauri-drag-region className="w-[180px] shrink-0 border-r border-gray-4 bg-gray-2 h-full flex flex-col">
|
|
|
+ // <div className="px-3 py-2 text-xs text-gray-7 border-b border-gray-4 select-none">
|
|
|
+ // 任务列表
|
|
|
+ // </div>
|
|
|
+ // <div className="flex-1 overflow-y-auto">
|
|
|
+ // <ul>
|
|
|
+ // {mockTasks.map((t) => {
|
|
|
+ // const active = activeId === t.id;
|
|
|
+ // return (
|
|
|
+ // <li
|
|
|
+ // key={t.id}
|
|
|
+ // className={
|
|
|
+ // "flex items-center justify-between px-3 py-2 text-sm cursor-pointer transition-colors " +
|
|
|
+ // (active
|
|
|
+ // ? "bg-primary-1 text-primary-7"
|
|
|
+ // : "hover:bg-gray-3 text-gray-10")
|
|
|
+ // }
|
|
|
+ // onClick={() => handleSelectTask(t)}
|
|
|
+ // >
|
|
|
+ // <span className="truncate">任务 {t.id}</span>
|
|
|
+ // <button
|
|
|
+ // type="button"
|
|
|
+ // title="在系统浏览器打开"
|
|
|
+ // className="ml-2 inline-flex items-center justify-center w-6 h-6 rounded-sm hover:bg-gray-4 text-gray-7 hover:text-primary-6"
|
|
|
+ // onClick={(e) => {
|
|
|
+ // e.stopPropagation();
|
|
|
+ // void openInBrowser(t.url);
|
|
|
+ // }}
|
|
|
+ // >
|
|
|
+ // {/* 外链图标(lucide external-link 同款 path,不引入新依赖) */}
|
|
|
+ // <svg
|
|
|
+ // width="14"
|
|
|
+ // height="14"
|
|
|
+ // viewBox="0 0 24 24"
|
|
|
+ // fill="none"
|
|
|
+ // stroke="currentColor"
|
|
|
+ // strokeWidth="2"
|
|
|
+ // strokeLinecap="round"
|
|
|
+ // strokeLinejoin="round"
|
|
|
+ // >
|
|
|
+ // <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
|
+ // <polyline points="15 3 21 3 21 9" />
|
|
|
+ // <line x1="10" y1="14" x2="21" y2="3" />
|
|
|
+ // </svg>
|
|
|
+ // </button>
|
|
|
+ // </li>
|
|
|
+ // );
|
|
|
+ // })}
|
|
|
+ // </ul>
|
|
|
+ // </div>
|
|
|
+ // </aside>
|
|
|
+
|
|
|
+ // {/* ===== 右侧主区 ===== */}
|
|
|
+ // <main className="flex-1 flex flex-col min-w-0">
|
|
|
+ // {/* 工具栏(48px) */}
|
|
|
+ // <header className="h-12 shrink-0 flex items-center justify-between gap-2 px-3 border-b border-gray-4 bg-gray-1">
|
|
|
+ // {/* 左侧:操作按钮区 */}
|
|
|
+ // <div className="flex items-center gap-2">
|
|
|
+ // <Tooltip title={activeId ? "截图整页(覆盖之前的自动截图)" : "请先选择任务"} placement="top">
|
|
|
+ // <Button
|
|
|
+ //
|
|
|
+ // icon={<PictureOutlined />}
|
|
|
+ // disabled={!activeId}
|
|
|
+ // onClick={() => void handleManualCapture()}
|
|
|
+ // />
|
|
|
+ // </Tooltip>
|
|
|
+ // <Divider type="vertical" />
|
|
|
+ // {/* 开始按钮:仅 idle + 已选任务时可点 */}
|
|
|
+ // <Tooltip title={startConfig.tip} placement="top">
|
|
|
+ // <Button
|
|
|
+ //
|
|
|
+ // icon={startConfig.icon}
|
|
|
+ // disabled={startDisabled}
|
|
|
+ // onClick={() => void handleStartRecord()}
|
|
|
+ // />
|
|
|
+ // </Tooltip>
|
|
|
+ // {/* 停止按钮:仅 recording 时可用 */}
|
|
|
+ // <Tooltip
|
|
|
+ // title={
|
|
|
+ // stopDisabled
|
|
|
+ // ? "无进行中的录制"
|
|
|
+ // : "停止录制并落盘 mp4(F11)"
|
|
|
+ // }
|
|
|
+ // placement="top"
|
|
|
+ // >
|
|
|
+ // <Button
|
|
|
+ //
|
|
|
+ // icon={<StopOutlined />}
|
|
|
+ // disabled={stopDisabled}
|
|
|
+ // onClick={() => void handleStopRecord()}
|
|
|
+ // />
|
|
|
+ // </Tooltip>
|
|
|
+ // <Divider type="vertical" />
|
|
|
+ // {/* 打开文件夹:reveal mp4 → png → 兜底 screenshots 目录 */}
|
|
|
+ // <Tooltip
|
|
|
+ // title={
|
|
|
+ // !activeId
|
|
|
+ // ? "请先选择任务"
|
|
|
+ // : assets?.recording_exists
|
|
|
+ // ? "在文件管理器中显示录制视频"
|
|
|
+ // : assets?.screenshot_exists
|
|
|
+ // ? "在文件管理器中显示截图"
|
|
|
+ // : "打开截图目录"
|
|
|
+ // }
|
|
|
+ // placement="top"
|
|
|
+ // >
|
|
|
+ // <Button
|
|
|
+ //
|
|
|
+ // icon={<FolderOpenOutlined />}
|
|
|
+ // disabled={!assets}
|
|
|
+ // onClick={() => void handleOpenFolder()}
|
|
|
+ // />
|
|
|
+ // </Tooltip>
|
|
|
+ // {/* 预览截图:仅当 png 存在 */}
|
|
|
+ // <Tooltip
|
|
|
+ // title={
|
|
|
+ // !assets?.screenshot_exists
|
|
|
+ // ? "暂无截图可预览"
|
|
|
+ // : "在新窗口预览截图"
|
|
|
+ // }
|
|
|
+ // placement="top"
|
|
|
+ // >
|
|
|
+ // <Button
|
|
|
+ //
|
|
|
+ // icon={<FileImageOutlined />}
|
|
|
+ // disabled={!assets?.screenshot_exists}
|
|
|
+ // onClick={() => void handlePreviewImage()}
|
|
|
+ // />
|
|
|
+ // </Tooltip>
|
|
|
+ // {/* 预览视频:仅当 mp4 存在 */}
|
|
|
+ // <Tooltip
|
|
|
+ // title={
|
|
|
+ // !assets?.recording_exists
|
|
|
+ // ? "暂无录制视频可预览"
|
|
|
+ // : "在新窗口预览录制视频"
|
|
|
+ // }
|
|
|
+ // placement="top"
|
|
|
+ // >
|
|
|
+ // <Button
|
|
|
+ //
|
|
|
+ // icon={<VideoCameraOutlined />}
|
|
|
+ // disabled={!assets?.recording_exists}
|
|
|
+ // onClick={() => void handlePreviewVideo()}
|
|
|
+ // />
|
|
|
+ // </Tooltip>
|
|
|
+ // </div>
|
|
|
+
|
|
|
+ // {/* 右侧:尺寸预设 / 自定义 */}
|
|
|
+ // <div className="flex items-center gap-2">
|
|
|
+ // {!customMode ? (
|
|
|
+ // <>
|
|
|
+ // {SIZE_PRESETS.map((p) => (
|
|
|
+ // <Button
|
|
|
+ // key={p.label}
|
|
|
+ //
|
|
|
+ // disabled={contentSize.w == p.w && contentSize.h == p.h}
|
|
|
+ // onClick={() => applyWorkAreaSize(p.w, p.h)}
|
|
|
+ // >
|
|
|
+ // {p.label}
|
|
|
+ // </Button>
|
|
|
+ // ))}
|
|
|
+ // <Button onClick={() => setCustomMode(true)}>
|
|
|
+ // 自定义
|
|
|
+ // </Button>
|
|
|
+ // </>
|
|
|
+ // ) : (
|
|
|
+ // <>
|
|
|
+ // <InputNumber
|
|
|
+ //
|
|
|
+ // min={200}
|
|
|
+ // max={3840}
|
|
|
+ // value={contentSize.w}
|
|
|
+ // onChange={(v) => setContentSize({ w: v || 0, h: contentSize.h })}
|
|
|
+ // style={{ width: 80 }}
|
|
|
+ // />
|
|
|
+ // <span className="text-gray-7 select-none">×</span>
|
|
|
+ // <InputNumber
|
|
|
+ //
|
|
|
+ // min={200}
|
|
|
+ // max={2160}
|
|
|
+ // value={contentSize.h}
|
|
|
+ // onChange={(v) => setContentSize({ w: contentSize.w, h: v || 0 })}
|
|
|
+ // style={{ width: 80 }}
|
|
|
+ // />
|
|
|
+ // <Button
|
|
|
+ //
|
|
|
+ // type="primary"
|
|
|
+ // onClick={() => applyWorkAreaSize(contentSize.w, contentSize.h)}
|
|
|
+ // >
|
|
|
+ // 应用
|
|
|
+ // </Button>
|
|
|
+ // <Button onClick={() => setCustomMode(false)}>
|
|
|
+ // 取消
|
|
|
+ // </Button>
|
|
|
+ // </>
|
|
|
+ // )}
|
|
|
+ // </div>
|
|
|
+ // </header>
|
|
|
+
|
|
|
+ // {/* 工作区占位:实际由 Rust child webview 覆盖在此区域之上 */}
|
|
|
+ // <section className="flex-1 bg-gray-7" />
|
|
|
+ // <section className="h-6 bg-gray-5 broder border-gray-7" />
|
|
|
+ // </main>
|
|
|
+ // </div>
|
|
|
+ // );
|
|
|
}
|
|
|
|
|
|
-export default App;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+export default function Main() {
|
|
|
+ return <ConfigProvider theme={{
|
|
|
+ // 1. Use dark algorithm alone
|
|
|
+ algorithm: [theme.darkAlgorithm, theme.compactAlgorithm]
|
|
|
+ }}>
|
|
|
+ <App />
|
|
|
+ </ ConfigProvider>
|
|
|
+};
|