mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 02:36:01 -10:00
feat(studio): opening a project whose folder is gone asks, in the list, whether to remove it from the list, instead of reporting the system's sentence
This commit is contained in:
1 parent
1ff0525a34
commit
0a43322267
3 files changed
+69
-4
No files matched your search
@@ -68,6 +68,7 @@ export const SESSION: Messages = {
|
||||
"session.expand_all_projects": { zh: "展开所有项目", en: "Expand all projects" },
|
||||
"session.export_session": { zh: "导出会话", en: "Export session" },
|
||||
"session.extra_folders_for_this_project": { zh: "此项目的额外文件夹", en: "Extra folders for this project" },
|
||||
"session.folder_gone_remove_from_list": { zh: "「{name}」的文件夹已经不在磁盘上了,要把它从列表里移除吗?", en: "The folder of “{name}” is no longer on disk. Remove it from the list?" },
|
||||
"session.folder_is_gone": { zh: "文件夹已不在磁盘上", en: "Folder is gone" },
|
||||
"session.folder_path_to_add": { zh: "要添加的文件夹路径", en: "Folder path to add" },
|
||||
"session.folders_are_managed_in_the": { zh: "在左栏管理文件夹:底部添加,展开后可新建会话。会话归属于创建它的文件夹,不会移动到其他位置。", en: "Folders are managed in the left column: add at the bottom, expand to start a session. A session belongs to the folder it was created in and does not move." },
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createPortal } from "react-dom";
|
||||
import { t } from "../i18n";
|
||||
import type { HubPort, RuntimeView, TreeSession, TreeWorkspace } from "../port/hub";
|
||||
import { workspaceContains } from "../port/hub";
|
||||
import { HttpError } from "../port/http_error";
|
||||
import type { Adder } from "./addws";
|
||||
import { pinToViewport } from "./place";
|
||||
import { useRailQuery } from "./railsearch";
|
||||
@@ -94,6 +95,11 @@ const rowLabel = (session: TreeSession) =>
|
||||
function WorkspacesView({ hub, tree, treeRead, answered = null, runtimes, active, folded, reload, retry, paging = NO_PAGING, previewLimit = Infinity, onFold, onToggleAll, onRecent, allFolded, canFold, opening = "", onOpen, onFocus, onClose, liveIds, listedIds = liveIds, runs, scope = "all", pinned = new Set(), onPin = () => {}, onPause = () => {}, onBranch, onArchive = async () => {}, onRename, onError, adder, children, hasOtherRows }: Props) {
|
||||
const [busy, setBusy] = useState("");
|
||||
const [confirm, setConfirm] = useState("");
|
||||
// The project whose folder an open just found gone: the question is asked about it, not about a removal chosen from the menu.
|
||||
const [gone, setGone] = useState("");
|
||||
const failIn = (ws: TreeWorkspace, e: unknown) => {
|
||||
if (e instanceof HttpError && e.reason?.code === "workspace.folder_missing") { setGone(ws.root); setConfirm(ws.root); } else onError(e);
|
||||
};
|
||||
const [selection, setSelection] = useState<Set<string> | null>(null);
|
||||
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
||||
// The row a Delete key asked about. The question replaces that row, so focus
|
||||
@@ -190,7 +196,7 @@ function WorkspacesView({ hub, tree, treeRead, answered = null, runtimes, active
|
||||
try {
|
||||
await onOpen({ root: session.workspaceRoot || ws.root, sessionPath: session.path });
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
failIn(ws, e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -199,7 +205,7 @@ function WorkspacesView({ hub, tree, treeRead, answered = null, runtimes, active
|
||||
try {
|
||||
await onOpen({ root: ws.root });
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
failIn(ws, e);
|
||||
} finally {
|
||||
setBusy("");
|
||||
}
|
||||
@@ -222,10 +228,12 @@ function WorkspacesView({ hub, tree, treeRead, answered = null, runtimes, active
|
||||
|
||||
const dropWorkspace = async (ws: TreeWorkspace) => {
|
||||
if (confirm !== ws.root) {
|
||||
setGone("");
|
||||
setConfirm(ws.root);
|
||||
return;
|
||||
}
|
||||
setConfirm("");
|
||||
setGone("");
|
||||
try {
|
||||
// Closed here for the same reason dropSession does it: the kernel will not
|
||||
// pull a folder out from under a pane that is writing, and leaving that to
|
||||
@@ -381,12 +389,12 @@ function WorkspacesView({ hub, tree, treeRead, answered = null, runtimes, active
|
||||
<div className="wsnode" key={ws.root} data-current={panesOf(ws.root).includes(active) ? "" : undefined} data-missing={ws.missing ? "" : undefined}>
|
||||
{confirm === ws.root ? (
|
||||
<Confirm
|
||||
what={t("session.remove_name_from_the_list", { name: ws.name })}
|
||||
what={gone === ws.root ? t("session.folder_gone_remove_from_list", { name: ws.name }) : t("session.remove_name_from_the_list", { name: ws.name })}
|
||||
hint={removeHint(doomed.length, busyPanes)}
|
||||
go={t("common.remove")}
|
||||
danger={busyPanes > 0}
|
||||
onGo={() => void dropWorkspace(ws)}
|
||||
onCancel={() => setConfirm("")}
|
||||
onCancel={() => { setConfirm(""); setGone(""); }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
||||
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import "./testkit";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { MockHub } from "../port/mock_hub";
|
||||
import { HttpError } from "../port/http_error";
|
||||
import { NO_PAGING } from "./treewindow";
|
||||
|
||||
beforeEach(() => localStorage.clear());
|
||||
afterEach(() => { cleanup(); vi.restoreAllMocks(); localStorage.clear(); });
|
||||
|
||||
function draw(onOpen: (req: { root?: string; sessionPath?: string }) => Promise<unknown>, hub = new MockHub(), onError = vi.fn()) {
|
||||
const noop = vi.fn();
|
||||
render(<Sidebar hub={hub} collapsed={false} navShown treeRead
|
||||
tree={[{ root: "/gone/alpha", name: "alpha", remembered: true,
|
||||
sessions: [{ path: "/gone/alpha/a.jsonl", name: "a", title: "first" }] }]}
|
||||
runtimes={[]} runs={{}} active="" activeWorkspace={undefined} liveIds={() => []}
|
||||
pinned={new Set()} onPin={noop} remotes={null} remoteTrees={{}}
|
||||
reloadRemotes={async () => {}} reloadRemoteTrees={async () => {}} readRemoteTree={async () => {}}
|
||||
reloadTree={async () => {}} paging={NO_PAGING} adder={{ busy: false, add: noop, addPath: noop } as never}
|
||||
onOpen={onOpen as never} onOpenRemote={async () => {}} onFocusPane={noop} onClosePanes={async () => {}}
|
||||
onPause={noop} onArchive={async () => {}} onRename={noop} onCollapse={noop}
|
||||
wallet="" onSettings={noop} onFiles={noop} onView={noop}
|
||||
onFeedback={noop} feedbackUnread={0} onFind={noop} onError={onError} />);
|
||||
return { hub, onError };
|
||||
}
|
||||
|
||||
const missing = () => new HttpError(400, "stat /gone/alpha: no such file or directory", { code: "workspace.folder_missing" });
|
||||
|
||||
it("asks, in Chinese, whether to remove a project whose folder is gone, instead of printing the system's sentence", async () => {
|
||||
const { onError } = draw(async () => { throw missing(); });
|
||||
await userEvent.click(screen.getByText("first"));
|
||||
expect(await screen.findByText("「alpha」的文件夹已经不在磁盘上了,要把它从列表里移除吗?")).toBeTruthy();
|
||||
expect(onError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes the project only when told to", async () => {
|
||||
const hub = new MockHub();
|
||||
const remove = vi.spyOn(hub, "removeWorkspace");
|
||||
draw(async () => { throw missing(); }, hub);
|
||||
await userEvent.click(screen.getByText("first"));
|
||||
await userEvent.click(await screen.findByRole("button", { name: "取消" }));
|
||||
expect(remove).not.toHaveBeenCalled();
|
||||
await userEvent.click(screen.getByText("first"));
|
||||
await userEvent.click(await screen.findByRole("button", { name: "移除" }));
|
||||
await waitFor(() => expect(remove).toHaveBeenCalledWith("/gone/alpha"));
|
||||
});
|
||||
|
||||
it("keeps reporting every other failure as an error", async () => {
|
||||
const { onError } = draw(async () => { throw new HttpError(500, "boom", { code: "session.open_failed" }); });
|
||||
await userEvent.click(screen.getByText("first"));
|
||||
await waitFor(() => expect(onError).toHaveBeenCalled());
|
||||
expect(screen.queryByText(/文件夹已经不在磁盘上/)).toBeNull();
|
||||
});
|
||||
Reference in new issue
Block a user