update:原生确认框替换为自定义弹窗
This commit is contained in:
@@ -13,9 +13,10 @@ import {
|
||||
import { WelcomePanel } from "./WelcomePanel";
|
||||
import { PublishModal } from "./PublishModal";
|
||||
import { ScriptExplorer } from "./ScriptExplorer";
|
||||
import { ScriptsPendingConfirmDialog } from "./ScriptsPendingConfirm";
|
||||
import { TreeContextMenu } from "./TreeContextMenu";
|
||||
import { VersionReceiptModal } from "./VersionReceiptModal";
|
||||
|
||||
import { useScriptsPendingConfirm } from "./useScriptsPendingConfirm";
|
||||
import { getSessionCache, useScriptWorkspaceStore } from "./state/scriptWorkspaceStore";
|
||||
import { useUiStore } from "./state/uiStore";
|
||||
import { toast } from "sonner";
|
||||
@@ -75,6 +76,18 @@ export default function ScriptsPage() {
|
||||
const submitPublish = useScriptWorkspaceStore((s) => s.submitPublish);
|
||||
const refreshReadOnlyContent = useScriptWorkspaceStore((s) => s.refreshReadOnlyContent);
|
||||
|
||||
const {
|
||||
pendingConfirm,
|
||||
setPendingConfirm,
|
||||
requestCloseTab,
|
||||
handleConfirm,
|
||||
} = useScriptsPendingConfirm(scripts, {
|
||||
deleteScript,
|
||||
deleteDataResource,
|
||||
deleteDirectory,
|
||||
closeTab,
|
||||
});
|
||||
|
||||
// ui store
|
||||
const pushToast = (notice: { tone: "success" | "error" | "info"; message: string }) => {
|
||||
if (notice.tone === "error") toast.error(notice.message);
|
||||
@@ -206,27 +219,11 @@ export default function ScriptsPage() {
|
||||
};
|
||||
}, [contextMenu, closeContextMenu]);
|
||||
|
||||
// python editor handlers with scriptId forwarding
|
||||
const handleSetPythonEditorContent = (scriptId: string, value: string) => {
|
||||
setPythonEditorContent(scriptId, value);
|
||||
};
|
||||
const handleSavePythonEditor = (scriptId: string) => {
|
||||
void savePythonEditor(scriptId);
|
||||
};
|
||||
const handleExitPythonEditor = (scriptId: string) => {
|
||||
exitPythonEditor(scriptId);
|
||||
};
|
||||
const handleClosePythonTab = (scriptId: string) => {
|
||||
void closeTab(scriptId);
|
||||
};
|
||||
|
||||
// 5) handlers
|
||||
// 5) handlers
|
||||
const SCRIPT_EXTS = [".py", ".ipynb"];
|
||||
const DATA_EXTS = [".csv", ".xlsx", ".xls", ".tsv", ".json", ".parquet", ".txt"];
|
||||
|
||||
const matchesExt = (name: string, exts: string[]) =>
|
||||
exts.some((ext) => name.toLowerCase().endsWith(ext));
|
||||
|
||||
const handleUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(event.target.files ?? []);
|
||||
event.target.value = "";
|
||||
@@ -381,17 +378,17 @@ export default function ScriptsPage() {
|
||||
void endEditing();
|
||||
}
|
||||
}}
|
||||
onClose={(scriptId, event) => void closeTab(scriptId, event)}
|
||||
onClose={(scriptId, event) => void requestCloseTab(scriptId, event)}
|
||||
onSwitchTab={switchTab}
|
||||
onNewTab={() => openCreateDialog("")}
|
||||
onPublish={() => openPublishDialog(selected)}
|
||||
scripts={scripts}
|
||||
pythonEditorBuffers={pythonEditorBuffers}
|
||||
onOpenPythonEditor={() => void openPythonEditor(selected)}
|
||||
onSetPythonEditorContent={handleSetPythonEditorContent}
|
||||
onSavePythonEditor={handleSavePythonEditor}
|
||||
onExitPythonEditor={handleExitPythonEditor}
|
||||
onClosePythonTab={handleClosePythonTab}
|
||||
onSetPythonEditorContent={setPythonEditorContent}
|
||||
onSavePythonEditor={(scriptId) => void savePythonEditor(scriptId)}
|
||||
onExitPythonEditor={exitPythonEditor}
|
||||
onClosePythonTab={(scriptId) => void requestCloseTab(scriptId)}
|
||||
onInfo={(t) => pushToast(t)}
|
||||
/>
|
||||
) : (
|
||||
@@ -429,19 +426,35 @@ export default function ScriptsPage() {
|
||||
selectScript(scriptId);
|
||||
closeContextMenu();
|
||||
}}
|
||||
onRemoveScript={(s) => void deleteScript(s)}
|
||||
onRemoveScript={(s) => setPendingConfirm({ kind: "script", script: s })}
|
||||
onToggleLock={(s) => void toggleScriptLock(s)}
|
||||
onOpenCreateDialog={(parentPath, scriptType) =>
|
||||
openCreateDialog(parentPath, scriptType)}
|
||||
onOpenFolderDialog={(parentPath) => openFolderDialog(parentPath)}
|
||||
onChooseUpload={(parentPath) => triggerUpload(parentPath ?? "")}
|
||||
onRemoveDirectory={(p) => void deleteDirectory(p)}
|
||||
onRemoveDirectory={(p) =>
|
||||
setPendingConfirm({ kind: "directory", path: p })}
|
||||
onCopyResourcePath={(p) => void handleCopyResourcePath(p)}
|
||||
onPreviewResource={openResourcePreview}
|
||||
onRemoveResource={(id) => void deleteDataResource(id)}
|
||||
onRemoveResource={(id) => {
|
||||
const resource = dataResources.find((item) => item.resource_id === id);
|
||||
setPendingConfirm({
|
||||
kind: "resource",
|
||||
id,
|
||||
name: resource?.resource_name ?? id,
|
||||
});
|
||||
}}
|
||||
onClose={closeContextMenu}
|
||||
/>
|
||||
|
||||
<ScriptsPendingConfirmDialog
|
||||
pending={pendingConfirm}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setPendingConfirm(null);
|
||||
}}
|
||||
onConfirm={() => void handleConfirm()}
|
||||
/>
|
||||
|
||||
<DataResourcePreviewDialog
|
||||
target={resourcePreview}
|
||||
onClose={() => setResourcePreview(null)}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { ConfirmDialog } from "~/components/common/ConfirmDialog";
|
||||
import type { ScriptItem } from "~/services/api";
|
||||
|
||||
export type ScriptsPendingConfirm =
|
||||
| { kind: "script"; script: ScriptItem }
|
||||
| { kind: "resource"; id: string; name: string }
|
||||
| { kind: "directory"; path: string }
|
||||
| { kind: "close-tab"; id: string; name: string };
|
||||
|
||||
type ScriptsPendingConfirmDialogProps = {
|
||||
pending: ScriptsPendingConfirm | null;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onConfirm: () => void | Promise<void>;
|
||||
};
|
||||
|
||||
function copyFor(pending: ScriptsPendingConfirm) {
|
||||
switch (pending.kind) {
|
||||
case "script":
|
||||
return {
|
||||
title: "确定删除文件?",
|
||||
description: `确定删除文件"${pending.script.script_name}"吗?稳定版本会保留。`,
|
||||
confirmLabel: "删除",
|
||||
destructive: true,
|
||||
};
|
||||
case "resource":
|
||||
return {
|
||||
title: "确定删除数据资源?",
|
||||
description: `确定删除数据资源"${pending.name}"吗?稳定版本会保留。`,
|
||||
confirmLabel: "删除",
|
||||
destructive: true,
|
||||
};
|
||||
case "directory":
|
||||
return {
|
||||
title: "确定删除文件夹?",
|
||||
description: `确定递归删除文件夹"${pending.path}"及其内容吗?稳定版本会保留。`,
|
||||
confirmLabel: "删除",
|
||||
destructive: true,
|
||||
};
|
||||
case "close-tab":
|
||||
return {
|
||||
title: "确定关闭标签?",
|
||||
description: `当前脚本有未保存修改,确定关闭 "${pending.name}" 吗?`,
|
||||
confirmLabel: "关闭",
|
||||
destructive: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function ScriptsPendingConfirmDialog({
|
||||
pending,
|
||||
onOpenChange,
|
||||
onConfirm,
|
||||
}: ScriptsPendingConfirmDialogProps) {
|
||||
const copy = pending ? copyFor(pending) : null;
|
||||
return (
|
||||
<ConfirmDialog
|
||||
open={pending !== null}
|
||||
onOpenChange={onOpenChange}
|
||||
title={copy?.title ?? ""}
|
||||
description={copy?.description ?? ""}
|
||||
confirmLabel={copy?.confirmLabel}
|
||||
destructive={copy?.destructive}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -151,7 +151,10 @@ export function TreeContextMenu({
|
||||
className={dangerItemClass}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => onRemoveScript(contextMenu.script!)}
|
||||
onClick={() => {
|
||||
onRemoveScript(contextMenu.script!);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<X size={16} />
|
||||
删除文件
|
||||
@@ -204,7 +207,10 @@ export function TreeContextMenu({
|
||||
className={dangerItemClass}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => onRemoveDirectory(contextMenu.path)}
|
||||
onClick={() => {
|
||||
onRemoveDirectory(contextMenu.path);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<X size={16} />
|
||||
删除文件夹
|
||||
|
||||
@@ -164,7 +164,7 @@ export const createEditSessionSlice: StateCreator<
|
||||
const scriptId = active?.script_id;
|
||||
if (!active) {
|
||||
if (closeTabFlag && scriptId) {
|
||||
await get().closeTab(scriptId);
|
||||
await get().closeTab(scriptId, undefined, { discardDirty: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ export const createEditSessionSlice: StateCreator<
|
||||
applyEditSessionState((p) => set(p), null, null);
|
||||
if (scriptId) sessionCache.delete(scriptId);
|
||||
if (closeTabFlag && scriptId) {
|
||||
await get().closeTab(scriptId);
|
||||
await get().closeTab(scriptId, undefined, { discardDirty: true });
|
||||
}
|
||||
if (showToast) {
|
||||
pushToast("success", `${active.script_name} 的编辑锁已释放`);
|
||||
|
||||
@@ -215,9 +215,6 @@ export const createMutationsSlice: StateCreator<
|
||||
deleteDataResource: async (resourceId) => {
|
||||
const api = requireApi();
|
||||
useUiStore.getState().closeContextMenu();
|
||||
if (!window.confirm(`确定删除数据资源吗?稳定版本会保留。`)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.deleteResource(resourceId);
|
||||
set((state) => ({
|
||||
@@ -287,11 +284,6 @@ export const createMutationsSlice: StateCreator<
|
||||
deleteScript: async (script: ScriptItem) => {
|
||||
const api = requireApi();
|
||||
useUiStore.getState().closeContextMenu();
|
||||
if (
|
||||
!window.confirm(`确定删除文件"${script.script_name}"吗?稳定版本会保留。`)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const editSession = getEditSession();
|
||||
if (editSession?.script_id === script.script_id) {
|
||||
await get().endEditing(false, false);
|
||||
@@ -323,11 +315,6 @@ export const createMutationsSlice: StateCreator<
|
||||
deleteDirectory: async (path) => {
|
||||
const api = requireApi();
|
||||
useUiStore.getState().closeContextMenu();
|
||||
if (
|
||||
!window.confirm(`确定递归删除文件夹"${path}"及其内容吗?稳定版本会保留。`)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const activeScript = get().scripts.find(
|
||||
(item) => item.script_id === getEditSession()?.script_id,
|
||||
);
|
||||
|
||||
@@ -62,14 +62,11 @@ export const createSelectionSlice: StateCreator<
|
||||
}));
|
||||
},
|
||||
|
||||
closeTab: async (id, event) => {
|
||||
closeTab: async (id, event, options) => {
|
||||
event?.stopPropagation();
|
||||
const buffer = get().pythonEditorBuffers[id];
|
||||
if (buffer?.dirty && !buffer.saving) {
|
||||
const name =
|
||||
get().scripts.find((s) => s.script_id === id)?.script_name ?? "该脚本";
|
||||
const ok = window.confirm(`当前脚本有未保存修改,确定关闭 "${name}" 吗?`);
|
||||
if (!ok) return;
|
||||
if (buffer?.dirty && !buffer.saving && !options?.discardDirty) {
|
||||
return false;
|
||||
}
|
||||
if (buffer) {
|
||||
get().exitPythonEditor(id);
|
||||
@@ -91,6 +88,7 @@ export const createSelectionSlice: StateCreator<
|
||||
}
|
||||
return { openTabIds: newTabs, selectedId: nextSelected };
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
||||
switchTab: (id) => {
|
||||
|
||||
@@ -92,7 +92,8 @@ export type SelectionSliceActions = {
|
||||
closeTab: (
|
||||
id: string,
|
||||
event?: { stopPropagation: () => void },
|
||||
) => Promise<void>;
|
||||
options?: { discardDirty?: boolean },
|
||||
) => Promise<boolean>;
|
||||
switchTab: (id: string) => void;
|
||||
openPublishDialog: (script: ScriptItem) => void;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useState } from "react";
|
||||
import type { ScriptItem } from "~/services/api";
|
||||
import type { ScriptsPendingConfirm } from "./ScriptsPendingConfirm";
|
||||
|
||||
type DeleteActions = {
|
||||
deleteScript: (script: ScriptItem) => Promise<void>;
|
||||
deleteDataResource: (resourceId: string) => Promise<void>;
|
||||
deleteDirectory: (path: string) => Promise<void>;
|
||||
closeTab: (
|
||||
id: string,
|
||||
event?: { stopPropagation: () => void },
|
||||
options?: { discardDirty?: boolean },
|
||||
) => Promise<boolean>;
|
||||
};
|
||||
|
||||
export function useScriptsPendingConfirm(
|
||||
scripts: ScriptItem[],
|
||||
actions: DeleteActions,
|
||||
) {
|
||||
const [pendingConfirm, setPendingConfirm] =
|
||||
useState<ScriptsPendingConfirm | null>(null);
|
||||
|
||||
const requestCloseTab = async (
|
||||
scriptId: string,
|
||||
event?: { stopPropagation: () => void },
|
||||
) => {
|
||||
const closed = await actions.closeTab(scriptId, event);
|
||||
if (closed) return;
|
||||
const name =
|
||||
scripts.find((s) => s.script_id === scriptId)?.script_name ?? "该脚本";
|
||||
setPendingConfirm({ kind: "close-tab", id: scriptId, name });
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!pendingConfirm) return;
|
||||
const target = pendingConfirm;
|
||||
setPendingConfirm(null);
|
||||
if (target.kind === "script") await actions.deleteScript(target.script);
|
||||
else if (target.kind === "resource") {
|
||||
await actions.deleteDataResource(target.id);
|
||||
} else if (target.kind === "directory") {
|
||||
await actions.deleteDirectory(target.path);
|
||||
} else {
|
||||
await actions.closeTab(target.id, undefined, { discardDirty: true });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
pendingConfirm,
|
||||
setPendingConfirm,
|
||||
requestCloseTab,
|
||||
handleConfirm,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user