|
|
@@ -1,4 +1,4 @@
|
|
|
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
+import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
|
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
|
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
} from "./lib/recorder";
|
|
|
import "./App.css";
|
|
|
import {
|
|
|
+ BackwardOutlined,
|
|
|
BankOutlined,
|
|
|
CheckCircleOutlined,
|
|
|
CloseOutlined,
|
|
|
@@ -62,7 +63,6 @@ import {
|
|
|
PictureOutlined,
|
|
|
PlayCircleFilled,
|
|
|
PlusOutlined,
|
|
|
- RedoOutlined,
|
|
|
StopOutlined,
|
|
|
VideoCameraOutlined,
|
|
|
} from "@ant-design/icons";
|
|
|
@@ -93,6 +93,7 @@ const STATUS_DIALOG_KIND: Record<StatusKind, "info" | "warning" | "error"> = {
|
|
|
/** 工具栏右侧的尺寸预设(统一横屏:宽 × 高) */
|
|
|
const SIZE_PRESETS = [
|
|
|
{ label: "1280×720", w: 1280, h: 720 },
|
|
|
+ { label: "720x1280", w: 720, h: 1280 },
|
|
|
{ label: "1920×1080", w: 1920, h: 1080 },
|
|
|
{ label: "1024×768", w: 1024, h: 768 }
|
|
|
];
|
|
|
@@ -126,6 +127,7 @@ function App() {
|
|
|
// 当前激活的任务 id(仅用于左栏视觉高亮)
|
|
|
const [activeId, setActiveId] = useState<string | null>(null);
|
|
|
|
|
|
+
|
|
|
// 待处理任务(status=0)的真实列表,来自 sqlite
|
|
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
|
|
|
|
@@ -142,7 +144,7 @@ function App() {
|
|
|
const [assets, setAssets] = useState<TaskAssets | null>(null);
|
|
|
|
|
|
/**
|
|
|
- * landing 流转门控:true 表示已加载到最终 (Visit Site) URL,可截图 / 录制。
|
|
|
+ * landing 流转门控:true 表示已加载到最终 (Site) URL,可截图 / 录制。
|
|
|
* - handleSelectTask 触发 navigate 时强制 false
|
|
|
* - 监听 EVT_TASK_PAGE_READY 后变 true
|
|
|
* - 监听 EVT_TASK_PAGE_TIMEOUT 时保持 false,弹窗让用户「重试 / 取消」
|
|
|
@@ -169,10 +171,7 @@ function App() {
|
|
|
|
|
|
// activeId 的最新值快照,供 listen 闭包中读取(避免在每个 effect 上加 activeId 依赖
|
|
|
// 导致 listen 频繁重订)
|
|
|
- const activeIdRef = useRef<string | null>(activeId);
|
|
|
- useEffect(() => {
|
|
|
- activeIdRef.current = activeId;
|
|
|
- }, [activeId]);
|
|
|
+
|
|
|
|
|
|
// 底部状态栏文案与左侧色块;showStatus 同时弹原生对话框 + 写状态栏
|
|
|
const [statusColor, setStatusColor] = useState("#888");
|
|
|
@@ -185,10 +184,10 @@ function App() {
|
|
|
* 对话框为非阻塞触发(fire-and-forget),失败仅打日志兜底
|
|
|
*/
|
|
|
const showStatus = useCallback(
|
|
|
- (kind: StatusKind, title: string, description?: string) => {
|
|
|
+ (kind: StatusKind, title: string, description?: string, dlg?: boolean) => {
|
|
|
setStatusColor(STATUS_COLOR[kind]);
|
|
|
setStatusText(title);
|
|
|
- void message(description ? `${title}\n${description}` : title, {
|
|
|
+ dlg !== false && void message(description ? `${title}\n${description}` : title, {
|
|
|
title: "提示",
|
|
|
kind: STATUS_DIALOG_KIND[kind],
|
|
|
}).catch((e) => console.error("显示对话框失败:", e));
|
|
|
@@ -207,6 +206,9 @@ function App() {
|
|
|
try {
|
|
|
const list = await listPendingTasks();
|
|
|
setTasks(list);
|
|
|
+ if (!activeId && list.length > 0) {
|
|
|
+ handleSelectTask({ key: list[0].id });
|
|
|
+ }
|
|
|
return list;
|
|
|
} catch (e) {
|
|
|
console.error("加载任务列表失败:", e);
|
|
|
@@ -247,6 +249,7 @@ function App() {
|
|
|
if (!t) {
|
|
|
return;
|
|
|
}
|
|
|
+ showStatus("success", "任务开始", "任务开始", false);
|
|
|
setActiveId(t.id);
|
|
|
// 新一轮 landing 流转开始:先把按钮门控锁住,等 Rust 端发 task-page-ready 再放开
|
|
|
setPageReady(false);
|
|
|
@@ -307,13 +310,11 @@ function App() {
|
|
|
}, [showStatus, reloadTasks]);
|
|
|
|
|
|
/**
|
|
|
- * 监听 landing 四个事件:
|
|
|
+ * 监听 landing 三个事件:
|
|
|
* - task-tags-extracted:中间页抓到标签 → 写库 + reload
|
|
|
- * - task-site-url-found:landing 命中 Visit Site 链接的瞬间 → 立即写 site_url + reload
|
|
|
- * (此时还没 navigate,更不用说 Finished;即便后续 redirect/加载失败,site_url 也已落库)
|
|
|
- * - task-page-ready:跳到最终 URL 加载完成 → 解锁按钮 + 状态栏提示
|
|
|
+ * - task-page-ready:跳到最终 URL 完成 → 解锁按钮 + 状态栏提示
|
|
|
* - task-page-timeout:未找到 Visit Site → ask() 弹「重试 / 取消」
|
|
|
- * 全部按 activeIdRef 过滤,避免给已切走的旧任务弹无效提示(写库不过滤,所有命中任务都要落库)。
|
|
|
+ * 全部按 activeIdRef 过滤,避免给已切走的旧任务弹无效提示。
|
|
|
*/
|
|
|
useEffect(() => {
|
|
|
let unlistenTags: UnlistenFn | null = null;
|
|
|
@@ -329,8 +330,8 @@ function App() {
|
|
|
const { taskId, tags } = e.payload;
|
|
|
try {
|
|
|
await updateTaskTags(taskId, tags);
|
|
|
- await reloadTasks();
|
|
|
- showStatus("success", "已提取标签", tags || "(空)");
|
|
|
+ // await reloadTasks();
|
|
|
+ showStatus("success", "已提取标签", tags || "(空)", false);
|
|
|
} catch (err) {
|
|
|
console.error("写入 tags 失败:", err);
|
|
|
showStatus("error", "写入标签失败", String(err));
|
|
|
@@ -355,7 +356,7 @@ function App() {
|
|
|
// 只对当前激活任务生效,避免切走后还把按钮放开
|
|
|
if (activeIdRef.current !== taskId) return;
|
|
|
setPageReady(true);
|
|
|
- showStatus("success", "页面就绪", "已加载到最终 URL,可截图 / 录制");
|
|
|
+ showStatus("success", "页面就绪", "已加载到最终 URL,可截图 / 录制", false);
|
|
|
});
|
|
|
const u4 = await listen<TaskPageTimeoutPayload>(
|
|
|
EVT_TASK_PAGE_TIMEOUT,
|
|
|
@@ -364,7 +365,7 @@ function App() {
|
|
|
if (activeIdRef.current !== taskId) return;
|
|
|
// 弹原生 ask 对话框:确认 = 重试;取消 = 保持禁用
|
|
|
const retry = await ask(`${reason}\n\n是否刷新重试?`, {
|
|
|
- title: "未找到 Visit Site 链接",
|
|
|
+ title: "未找到 网站 链接",
|
|
|
kind: "warning",
|
|
|
okLabel: "重试",
|
|
|
cancelLabel: "取消",
|
|
|
@@ -407,7 +408,7 @@ function App() {
|
|
|
if (!activeId) return;
|
|
|
try {
|
|
|
await markTaskDone(activeId);
|
|
|
- showStatus("success", "任务已完成", `任务 ${activeId} 已从列表移除`);
|
|
|
+ showStatus("success", "任务已完成", `任务 ${activeId} 已从列表移除`, false);
|
|
|
setActiveId(null);
|
|
|
const list = await reloadTasks();
|
|
|
// 若已无任务,自动再拉起一次导入窗口(用户可继续粘贴)
|
|
|
@@ -438,27 +439,18 @@ function App() {
|
|
|
let disposed = false;
|
|
|
|
|
|
(async () => {
|
|
|
- const u1 = await listen<ScreenshotFinishedPayload>(
|
|
|
- EVT_SCREENSHOT_FINISHED,
|
|
|
- async (e) => {
|
|
|
- const { taskId, path, auto } = e.payload;
|
|
|
- showStatus(
|
|
|
- "success",
|
|
|
- auto ? "自动截图完成" : "截图完成",
|
|
|
- `任务 ${taskId} → ${path}`,
|
|
|
- );
|
|
|
- // 当前选中即此任务时刷新 assets,让预览图按钮立即变可点
|
|
|
- if (activeIdRef.current === taskId) {
|
|
|
- void refreshAssets(taskId);
|
|
|
- }
|
|
|
- // 把截图 basename 落库;失败仅打日志,不影响用户后续操作
|
|
|
- try {
|
|
|
- await updateTaskPic(taskId, basename(path));
|
|
|
- } catch (err) {
|
|
|
- console.error("写入 pic 失败:", err);
|
|
|
- }
|
|
|
- },
|
|
|
- );
|
|
|
+ const u1 = await listen<ScreenshotFinishedPayload>(EVT_SCREENSHOT_FINISHED, (e) => {
|
|
|
+ const { taskId, path, auto } = e.payload;
|
|
|
+ showStatus(
|
|
|
+ "success",
|
|
|
+ auto ? "自动截图完成" : "截图完成",
|
|
|
+ `任务 ${taskId} → ${path}`,
|
|
|
+ );
|
|
|
+ // 当前选中即此任务时刷新 assets,让预览图按钮立即变可点
|
|
|
+ if (activeIdRef.current === taskId) {
|
|
|
+ void refreshAssets(taskId);
|
|
|
+ }
|
|
|
+ });
|
|
|
const u2 = await listen<ScreenshotFailedPayload>(EVT_SCREENSHOT_FAILED, (e) => {
|
|
|
const { taskId, auto, error } = e.payload;
|
|
|
showStatus(
|
|
|
@@ -497,22 +489,13 @@ function App() {
|
|
|
let disposed = false;
|
|
|
|
|
|
(async () => {
|
|
|
- const u1 = await listen<RecordingFinishedPayload>(
|
|
|
- EVT_RECORDING_FINISHED,
|
|
|
- async (e) => {
|
|
|
- const { taskId, path } = e.payload;
|
|
|
- showStatus("success", "录制完成", `任务 ${taskId} → ${path}`);
|
|
|
- if (activeIdRef.current === taskId) {
|
|
|
- void refreshAssets(taskId);
|
|
|
- }
|
|
|
- // 把录制 mp4 basename 落库;失败仅打日志
|
|
|
- try {
|
|
|
- await updateTaskVideo(taskId, basename(path));
|
|
|
- } catch (err) {
|
|
|
- console.error("写入 video 失败:", err);
|
|
|
- }
|
|
|
- },
|
|
|
- );
|
|
|
+ const u1 = await listen<RecordingFinishedPayload>(EVT_RECORDING_FINISHED, (e) => {
|
|
|
+ const { taskId, path } = e.payload;
|
|
|
+ showStatus("success", "录制完成", `任务 ${taskId} → ${path}`);
|
|
|
+ if (activeIdRef.current === taskId) {
|
|
|
+ void refreshAssets(taskId);
|
|
|
+ }
|
|
|
+ });
|
|
|
const u2 = await listen<RecordingFailedPayload>(EVT_RECORDING_FAILED, (e) => {
|
|
|
const { taskId, error } = e.payload;
|
|
|
showStatus("error", "录制失败", `任务 ${taskId}:${error}`);
|
|
|
@@ -747,26 +730,40 @@ function App() {
|
|
|
data-tauri-drag-region
|
|
|
>
|
|
|
<div className="flex h-full flex-row items-center" data-tauri-drag-region>
|
|
|
- <Space size={8}>
|
|
|
+ <Space size={4}>
|
|
|
<Button
|
|
|
className="app-close"
|
|
|
icon={<CloseOutlined />}
|
|
|
- size="large"
|
|
|
+ // size="large"
|
|
|
type="text"
|
|
|
data-tauri-drag-region="no-drag"
|
|
|
onClick={handleAppClose}
|
|
|
/>
|
|
|
- <Button icon={<BankOutlined />} disabled size="large" type="text" />
|
|
|
<Button
|
|
|
className="app-minimize"
|
|
|
icon={<MinusOutlined />}
|
|
|
- size="large"
|
|
|
+ // size="large"
|
|
|
type="text"
|
|
|
data-tauri-drag-region="no-drag"
|
|
|
onClick={handleAppMinimize}
|
|
|
/>
|
|
|
</Space>
|
|
|
- <div className="w-32" />
|
|
|
+ <div className="w-8" />
|
|
|
+ <Button
|
|
|
+ icon={<LeftOutlined />}
|
|
|
+ // size="large"
|
|
|
+ type="text"
|
|
|
+ data-tauri-drag-region="no-drag"
|
|
|
+ onClick={handleBack}
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ icon={<RedoOutlined />}
|
|
|
+ // size="large"
|
|
|
+ type="text"
|
|
|
+ data-tauri-drag-region="no-drag"
|
|
|
+ onClick={handleRefresh}
|
|
|
+ />
|
|
|
+
|
|
|
<Tooltip
|
|
|
title={
|
|
|
!activeId
|
|
|
@@ -898,7 +895,6 @@ function App() {
|
|
|
) : (
|
|
|
<>
|
|
|
<InputNumber
|
|
|
-
|
|
|
min={200}
|
|
|
max={3840}
|
|
|
value={contentSize.w}
|
|
|
@@ -958,8 +954,12 @@ function App() {
|
|
|
<Layout className="flex flex-row">
|
|
|
<Content className="w-full m-0 p-1 flex-1" />
|
|
|
<div className="w-full h-6 flex items-center px-2 text-gray-3">
|
|
|
+
|
|
|
+ <div className="flex-1">任务:{activeId} {isWorking && <LoadingOutlined />}</div>
|
|
|
+ <div className="flex-1 overflow-hidden">{assets?.site_url}</div>
|
|
|
+ <div className="flex-1 overflow-hidden">{assets?.tags}</div>
|
|
|
+ <div className="flex-1 text-right">{statusText}</div>
|
|
|
<div className="bg-gray-6 rounded-full w-3 h-3 m-2" style={{ backgroundColor: statusColor }} />
|
|
|
- <div className="flex-1">{statusText}</div>
|
|
|
</div>
|
|
|
</Layout>
|
|
|
</Layout>
|