|
@@ -63,6 +63,7 @@ import {
|
|
|
StopOutlined,
|
|
StopOutlined,
|
|
|
VideoCameraOutlined,
|
|
VideoCameraOutlined,
|
|
|
} from "@ant-design/icons";
|
|
} from "@ant-design/icons";
|
|
|
|
|
+import { asyncSleep } from "./lib/utils";
|
|
|
|
|
|
|
|
const { Header, Content, Sider } = Layout;
|
|
const { Header, Content, Sider } = Layout;
|
|
|
|
|
|
|
@@ -96,14 +97,15 @@ const SIZE_PRESETS = [
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
-/** 从绝对/相对路径里取最后一段文件名(同时兼容 Windows `\` 与 POSIX `/`) */
|
|
|
|
|
-function basename(p: string): string {
|
|
|
|
|
- const parts = p.split(/[\\/]/);
|
|
|
|
|
- return parts[parts.length - 1] || "";
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
/** 点条目:把 url 加载到子 webview,并把 task_id 一并下发(用于截图命名 + 自动截图回调) */
|
|
/** 点条目:把 url 加载到子 webview,并把 task_id 一并下发(用于截图命名 + 自动截图回调) */
|
|
|
async function loadInWebview(taskId: string, url: string) {
|
|
async function loadInWebview(taskId: string, url: string) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await invoke("clean_webview");
|
|
|
|
|
+ await asyncSleep(200);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
try {
|
|
try {
|
|
|
await invoke("navigate_webview", { taskId, url });
|
|
await invoke("navigate_webview", { taskId, url });
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
@@ -111,14 +113,6 @@ async function loadInWebview(taskId: string, url: string) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** 点条目右侧图标:调系统浏览器打开 */
|
|
|
|
|
-// async function openInBrowser(url: string) {
|
|
|
|
|
-// try {
|
|
|
|
|
-// await openUrl(url);
|
|
|
|
|
-// } catch (e) {
|
|
|
|
|
-// console.error("打开系统浏览器失败:", e);
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
function App() {
|
|
|
// 当前激活的任务 id(仅用于左栏视觉高亮)
|
|
// 当前激活的任务 id(仅用于左栏视觉高亮)
|
|
@@ -154,23 +148,10 @@ function App() {
|
|
|
*/
|
|
*/
|
|
|
const [pageReady, setPageReady] = useState(false);
|
|
const [pageReady, setPageReady] = useState(false);
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 截图进行中标志。覆盖两个来源:
|
|
|
|
|
- * - 手动截图:handleManualCapture 点下时置 true
|
|
|
|
|
- * - 自动截图:page-ready 触发后置 true(Rust 会在 1.5s 后跑 CDP,紧跟着的
|
|
|
|
|
- * screenshot-finished/failed 事件回 false)
|
|
|
|
|
- * screenshot-finished 与 screenshot-failed 都会重置回 false,保证不会卡死。
|
|
|
|
|
- */
|
|
|
|
|
- const [isCapturing, setIsCapturing] = useState(false);
|
|
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 派生「忙碌」状态,给底部状态栏 loading 图标 + 后退/刷新按钮的 disabled 用:
|
|
|
|
|
- * - 选了任务但还没就绪(landing 在查 URL / tags)→ true
|
|
|
|
|
- * - 截图进行中 → true
|
|
|
|
|
- * - 未选任务或已就绪且无截图任务 → false
|
|
|
|
|
- */
|
|
|
|
|
- const isLoading = activeId !== null && !pageReady;
|
|
|
|
|
- const isWorking = isLoading || isCapturing;
|
|
|
|
|
|
|
+ const [isWorking, setIsWorking] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ const [isLoading, setIsLoading] = useState(false);
|
|
|
|
|
|
|
|
// activeId 的最新值快照,供 listen 闭包中读取(避免在每个 effect 上加 activeId 依赖
|
|
// activeId 的最新值快照,供 listen 闭包中读取(避免在每个 effect 上加 activeId 依赖
|
|
|
// 导致 listen 频繁重订)
|
|
// 导致 listen 频繁重订)
|
|
@@ -209,9 +190,12 @@ function App() {
|
|
|
try {
|
|
try {
|
|
|
const list = await listPendingTasks();
|
|
const list = await listPendingTasks();
|
|
|
setTasks(list);
|
|
setTasks(list);
|
|
|
|
|
+
|
|
|
if (!activeId && list.length > 0) {
|
|
if (!activeId && list.length > 0) {
|
|
|
|
|
+ await asyncSleep(500);
|
|
|
handleSelectTask({ key: list[0].id });
|
|
handleSelectTask({ key: list[0].id });
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
return list;
|
|
return list;
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.error("加载任务列表失败:", e);
|
|
console.error("加载任务列表失败:", e);
|
|
@@ -247,18 +231,24 @@ function App() {
|
|
|
}
|
|
}
|
|
|
}, [showStatus]);
|
|
}, [showStatus]);
|
|
|
|
|
|
|
|
- function handleSelectTask({ key }: { key: string }) {
|
|
|
|
|
|
|
+ const handleSelectTask = useCallback(({ key }: { key: string }) => {
|
|
|
const t = tasks.find((v) => v.id == key);
|
|
const t = tasks.find((v) => v.id == key);
|
|
|
if (!t) {
|
|
if (!t) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
showStatus("success", "任务开始", "任务开始", false);
|
|
showStatus("success", "任务开始", "任务开始", false);
|
|
|
|
|
+ setIsWorking(true);
|
|
|
setActiveId(t.id);
|
|
setActiveId(t.id);
|
|
|
// 新一轮 landing 流转开始:先把按钮门控锁住,等 Rust 端发 task-page-ready 再放开
|
|
// 新一轮 landing 流转开始:先把按钮门控锁住,等 Rust 端发 task-page-ready 再放开
|
|
|
setPageReady(false);
|
|
setPageReady(false);
|
|
|
void loadInWebview(t.id, t.url);
|
|
void loadInWebview(t.id, t.url);
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ }, [tasks, showStatus, setIsWorking, setActiveId, setPageReady]);
|
|
|
|
|
+ const handleStopTask = useCallback(() => {
|
|
|
|
|
+ setActiveId(null);
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ setIsWorking(false);
|
|
|
|
|
+ invoke("clean_webview");
|
|
|
|
|
+ }, []);
|
|
|
/** 重新查询指定任务的产物文件信息(截图 + mp4) */
|
|
/** 重新查询指定任务的产物文件信息(截图 + mp4) */
|
|
|
const refreshAssets = useCallback(async (taskId: string | null) => {
|
|
const refreshAssets = useCallback(async (taskId: string | null) => {
|
|
|
if (!taskId) {
|
|
if (!taskId) {
|
|
@@ -287,11 +277,11 @@ function App() {
|
|
|
const pending = await countPendingTasks().catch(() => 0);
|
|
const pending = await countPendingTasks().catch(() => 0);
|
|
|
const list = await reloadTasks();
|
|
const list = await reloadTasks();
|
|
|
if (pending === 0 && list.length === 0) {
|
|
if (pending === 0 && list.length === 0) {
|
|
|
- setTimeout(() =>
|
|
|
|
|
- openImportWindow(), 500);
|
|
|
|
|
|
|
+ await asyncSleep(500);
|
|
|
|
|
+ openImportWindow();
|
|
|
}
|
|
}
|
|
|
})();
|
|
})();
|
|
|
- // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
|
|
+
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
// 监听导入窗口发出的 tasks-imported 事件,刷新主列表
|
|
// 监听导入窗口发出的 tasks-imported 事件,刷新主列表
|
|
@@ -324,6 +314,8 @@ function App() {
|
|
|
let unlistenSite: UnlistenFn | null = null;
|
|
let unlistenSite: UnlistenFn | null = null;
|
|
|
let unlistenReady: UnlistenFn | null = null;
|
|
let unlistenReady: UnlistenFn | null = null;
|
|
|
let unlistenTimeout: UnlistenFn | null = null;
|
|
let unlistenTimeout: UnlistenFn | null = null;
|
|
|
|
|
+ let unlistenPageStarted: UnlistenFn | null = null;
|
|
|
|
|
+ let unlistenPageLoaded: UnlistenFn | null = null;
|
|
|
let disposed = false;
|
|
let disposed = false;
|
|
|
|
|
|
|
|
(async () => {
|
|
(async () => {
|
|
@@ -331,6 +323,12 @@ function App() {
|
|
|
EVT_TASK_TAGS_EXTRACTED,
|
|
EVT_TASK_TAGS_EXTRACTED,
|
|
|
async (e) => {
|
|
async (e) => {
|
|
|
const { taskId, tags } = e.payload;
|
|
const { taskId, tags } = e.payload;
|
|
|
|
|
+ setAssets((assets) => {
|
|
|
|
|
+ if (assets?.task_id !== taskId) {
|
|
|
|
|
+ return assets;
|
|
|
|
|
+ }
|
|
|
|
|
+ return { ...assets, tags };
|
|
|
|
|
+ });
|
|
|
try {
|
|
try {
|
|
|
await updateTaskTags(taskId, tags);
|
|
await updateTaskTags(taskId, tags);
|
|
|
// await reloadTasks();
|
|
// await reloadTasks();
|
|
@@ -346,9 +344,15 @@ function App() {
|
|
|
async (e) => {
|
|
async (e) => {
|
|
|
const { taskId, url } = e.payload;
|
|
const { taskId, url } = e.payload;
|
|
|
if (!url) return;
|
|
if (!url) return;
|
|
|
|
|
+ setAssets((assets) => {
|
|
|
|
|
+ if (assets?.task_id !== taskId) {
|
|
|
|
|
+ return assets;
|
|
|
|
|
+ }
|
|
|
|
|
+ return { ...assets, site_url: url };
|
|
|
|
|
+ });
|
|
|
try {
|
|
try {
|
|
|
await updateTaskSiteUrl(taskId, url);
|
|
await updateTaskSiteUrl(taskId, url);
|
|
|
- await reloadTasks();
|
|
|
|
|
|
|
+ // await reloadTasks();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error("写入 site_url 失败:", err);
|
|
console.error("写入 site_url 失败:", err);
|
|
|
}
|
|
}
|
|
@@ -359,6 +363,7 @@ function App() {
|
|
|
// 只对当前激活任务生效,避免切走后还把按钮放开
|
|
// 只对当前激活任务生效,避免切走后还把按钮放开
|
|
|
if (activeIdRef.current !== taskId) return;
|
|
if (activeIdRef.current !== taskId) return;
|
|
|
setPageReady(true);
|
|
setPageReady(true);
|
|
|
|
|
+ setIsWorking(false);
|
|
|
showStatus("success", "页面就绪", "已加载到最终 URL,可截图 / 录制", false);
|
|
showStatus("success", "页面就绪", "已加载到最终 URL,可截图 / 录制", false);
|
|
|
});
|
|
});
|
|
|
const u4 = await listen<TaskPageTimeoutPayload>(
|
|
const u4 = await listen<TaskPageTimeoutPayload>(
|
|
@@ -378,22 +383,37 @@ function App() {
|
|
|
setPageReady(false);
|
|
setPageReady(false);
|
|
|
await loadInWebview(taskId, url);
|
|
await loadInWebview(taskId, url);
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ setActiveId(null);
|
|
|
|
|
+
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ setIsWorking(false);
|
|
|
// 用户取消:保持按钮禁用,写状态栏告知
|
|
// 用户取消:保持按钮禁用,写状态栏告知
|
|
|
- showStatus("warning", "已取消重试", "重新点击任务可继续尝试");
|
|
|
|
|
|
|
+ showStatus("warning", "已取消任务", "重新点击任务可继续尝试");
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
|
|
+ const u5 = await listen("page_started", () => {
|
|
|
|
|
+ setIsLoading(true);
|
|
|
|
|
+ })
|
|
|
|
|
+ const u6 = await listen("page_loaded", () => {
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
if (disposed) {
|
|
if (disposed) {
|
|
|
u1();
|
|
u1();
|
|
|
u2();
|
|
u2();
|
|
|
u3();
|
|
u3();
|
|
|
u4();
|
|
u4();
|
|
|
|
|
+ u5();
|
|
|
|
|
+ u6();
|
|
|
} else {
|
|
} else {
|
|
|
unlistenTags = u1;
|
|
unlistenTags = u1;
|
|
|
unlistenSite = u2;
|
|
unlistenSite = u2;
|
|
|
unlistenReady = u3;
|
|
unlistenReady = u3;
|
|
|
unlistenTimeout = u4;
|
|
unlistenTimeout = u4;
|
|
|
|
|
+ unlistenPageStarted = u5;
|
|
|
|
|
+ unlistenPageLoaded = u6;
|
|
|
}
|
|
}
|
|
|
})();
|
|
})();
|
|
|
|
|
|
|
@@ -403,8 +423,10 @@ function App() {
|
|
|
unlistenSite?.();
|
|
unlistenSite?.();
|
|
|
unlistenReady?.();
|
|
unlistenReady?.();
|
|
|
unlistenTimeout?.();
|
|
unlistenTimeout?.();
|
|
|
|
|
+ unlistenPageStarted?.();
|
|
|
|
|
+ unlistenPageLoaded?.();
|
|
|
};
|
|
};
|
|
|
- }, [reloadTasks, showStatus]);
|
|
|
|
|
|
|
+ }, [showStatus, setAssets]);
|
|
|
|
|
|
|
|
/** 工具栏 "完成" 按钮:把当前激活任务标记为已完成(status=1)→ 刷新 → 清空选中 */
|
|
/** 工具栏 "完成" 按钮:把当前激活任务标记为已完成(status=1)→ 刷新 → 清空选中 */
|
|
|
const handleCompleteTask = useCallback(async () => {
|
|
const handleCompleteTask = useCallback(async () => {
|
|
@@ -413,11 +435,16 @@ function App() {
|
|
|
await markTaskDone(activeId);
|
|
await markTaskDone(activeId);
|
|
|
showStatus("success", "任务已完成", `任务 ${activeId} 已从列表移除`, false);
|
|
showStatus("success", "任务已完成", `任务 ${activeId} 已从列表移除`, false);
|
|
|
setActiveId(null);
|
|
setActiveId(null);
|
|
|
|
|
+
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ setIsWorking(false);
|
|
|
|
|
+ await asyncSleep(500);
|
|
|
const list = await reloadTasks();
|
|
const list = await reloadTasks();
|
|
|
// 若已无任务,自动再拉起一次导入窗口(用户可继续粘贴)
|
|
// 若已无任务,自动再拉起一次导入窗口(用户可继续粘贴)
|
|
|
if (list.length === 0) {
|
|
if (list.length === 0) {
|
|
|
await openImportWindow();
|
|
await openImportWindow();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
showStatus("error", "标记完成失败", String(e));
|
|
showStatus("error", "标记完成失败", String(e));
|
|
|
}
|
|
}
|
|
@@ -447,7 +474,7 @@ function App() {
|
|
|
showStatus(
|
|
showStatus(
|
|
|
"success",
|
|
"success",
|
|
|
auto ? "自动截图完成" : "截图完成",
|
|
auto ? "自动截图完成" : "截图完成",
|
|
|
- `任务 ${taskId} → ${path}`,
|
|
|
|
|
|
|
+ `任务 ${taskId} → ${path}`, false
|
|
|
);
|
|
);
|
|
|
// 当前选中即此任务时刷新 assets,让预览图按钮立即变可点
|
|
// 当前选中即此任务时刷新 assets,让预览图按钮立即变可点
|
|
|
if (activeIdRef.current === taskId) {
|
|
if (activeIdRef.current === taskId) {
|
|
@@ -492,11 +519,13 @@ function App() {
|
|
|
let disposed = false;
|
|
let disposed = false;
|
|
|
|
|
|
|
|
(async () => {
|
|
(async () => {
|
|
|
- const u1 = await listen<RecordingFinishedPayload>(EVT_RECORDING_FINISHED, (e) => {
|
|
|
|
|
|
|
+ const u1 = await listen<RecordingFinishedPayload>(EVT_RECORDING_FINISHED, async (e) => {
|
|
|
const { taskId, path } = e.payload;
|
|
const { taskId, path } = e.payload;
|
|
|
- showStatus("success", "录制完成", `任务 ${taskId} → ${path}`);
|
|
|
|
|
|
|
+ showStatus("success", "录制完成", `任务 ${taskId} → ${path}`, false);
|
|
|
if (activeIdRef.current === taskId) {
|
|
if (activeIdRef.current === taskId) {
|
|
|
- void refreshAssets(taskId);
|
|
|
|
|
|
|
+ await refreshAssets(taskId);
|
|
|
|
|
+ await asyncSleep(100);
|
|
|
|
|
+ handleCompleteTask();
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
const u2 = await listen<RecordingFailedPayload>(EVT_RECORDING_FAILED, (e) => {
|
|
const u2 = await listen<RecordingFailedPayload>(EVT_RECORDING_FAILED, (e) => {
|
|
@@ -564,6 +593,7 @@ function App() {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
await startRecording(activeId, contentSize.w, contentSize.h);
|
|
await startRecording(activeId, contentSize.w, contentSize.h);
|
|
|
|
|
+
|
|
|
} else if (action === "stop") {
|
|
} else if (action === "stop") {
|
|
|
if (recordState === "recording") {
|
|
if (recordState === "recording") {
|
|
|
await stopRecording();
|
|
await stopRecording();
|
|
@@ -737,7 +767,7 @@ function App() {
|
|
|
* 触发系统级拖窗,比手写 startDragging 更稳定(Windows 尤其如此)。 */}
|
|
* 触发系统级拖窗,比手写 startDragging 更稳定(Windows 尤其如此)。 */}
|
|
|
<Header
|
|
<Header
|
|
|
className="app-title h-8 select-none"
|
|
className="app-title h-8 select-none"
|
|
|
- style={{ paddingLeft: 8 }}
|
|
|
|
|
|
|
+ style={{ paddingLeft: 8, paddingRight: 8 }}
|
|
|
data-tauri-drag-region
|
|
data-tauri-drag-region
|
|
|
>
|
|
>
|
|
|
<div className="flex h-full flex-row items-center" data-tauri-drag-region>
|
|
<div className="flex h-full flex-row items-center" data-tauri-drag-region>
|
|
@@ -762,19 +792,16 @@ function App() {
|
|
|
<div className="w-8" />
|
|
<div className="w-8" />
|
|
|
<Button
|
|
<Button
|
|
|
icon={<LeftOutlined />}
|
|
icon={<LeftOutlined />}
|
|
|
- // size="large"
|
|
|
|
|
- type="text"
|
|
|
|
|
data-tauri-drag-region="no-drag"
|
|
data-tauri-drag-region="no-drag"
|
|
|
onClick={handleBack}
|
|
onClick={handleBack}
|
|
|
/>
|
|
/>
|
|
|
<Button
|
|
<Button
|
|
|
icon={<RedoOutlined />}
|
|
icon={<RedoOutlined />}
|
|
|
- // size="large"
|
|
|
|
|
- type="text"
|
|
|
|
|
|
|
+
|
|
|
data-tauri-drag-region="no-drag"
|
|
data-tauri-drag-region="no-drag"
|
|
|
onClick={handleRefresh}
|
|
onClick={handleRefresh}
|
|
|
/>
|
|
/>
|
|
|
-
|
|
|
|
|
|
|
+ <Divider orientation="vertical" />
|
|
|
<Tooltip
|
|
<Tooltip
|
|
|
title={
|
|
title={
|
|
|
!activeId
|
|
!activeId
|
|
@@ -881,6 +908,9 @@ function App() {
|
|
|
onClick={() => void handlePreviewVideo()}
|
|
onClick={() => void handlePreviewVideo()}
|
|
|
/>
|
|
/>
|
|
|
</Tooltip>
|
|
</Tooltip>
|
|
|
|
|
+ {isWorking && <Tooltip title="停止当前任务" placement="right">
|
|
|
|
|
+ <Button icon={<CloseOutlined color="red" style={{ color: 'red' }} />} color="red" onClick={handleStopTask} />
|
|
|
|
|
+ </Tooltip>}
|
|
|
<div className="flex-1">
|
|
<div className="flex-1">
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
@@ -951,10 +981,11 @@ function App() {
|
|
|
defaultSelectedKeys={[activeId || '']}
|
|
defaultSelectedKeys={[activeId || '']}
|
|
|
selectedKeys={[activeId || '']}
|
|
selectedKeys={[activeId || '']}
|
|
|
style={{ borderInlineEnd: 0, height: '100%' }}
|
|
style={{ borderInlineEnd: 0, height: '100%' }}
|
|
|
- onClick={handleSelectTask}
|
|
|
|
|
|
|
+
|
|
|
|
|
+ onClick={isWorking ? undefined : handleSelectTask}
|
|
|
|
|
|
|
|
>
|
|
>
|
|
|
- {tasks.map((t) => <Menu.Item icon={<LinkOutlined />} key={t.id}>
|
|
|
|
|
|
|
+ {tasks.map((t) => <Menu.Item icon={isWorking && t.id == activeId ? <LoadingOutlined /> : <LinkOutlined />} key={t.id}>
|
|
|
{t.id}
|
|
{t.id}
|
|
|
</Menu.Item>
|
|
</Menu.Item>
|
|
|
)}
|
|
)}
|
|
@@ -966,11 +997,11 @@ function App() {
|
|
|
<Content className="w-full m-0 p-1 flex-1" />
|
|
<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="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 || activeTaskRef.current?.url}</div>
|
|
|
|
|
- <div className="flex-1 overflow-hidden">{assets?.tags}</div>
|
|
|
|
|
|
|
+ <div className="flex-1">任务:{activeId} {isLoading && <LoadingOutlined />}</div>
|
|
|
|
|
+ <div className="flex-1 truncate">{assets?.site_url || activeTaskRef.current?.url}</div>
|
|
|
|
|
+ <div className="flex-1 truncate">{assets?.tags}</div>
|
|
|
<div className="flex-1 text-right">{statusText}</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="bg-gray-6 rounded-full w-3 h-3 m-2" style={{ backgroundColor: isWorking ? '#ff0' : statusColor }} />
|
|
|
</div>
|
|
</div>
|
|
|
</Layout>
|
|
</Layout>
|
|
|
</Layout>
|
|
</Layout>
|