reveal file or node def in explorer
This commit is contained in:
1 parent
eba675b0ab
commit
d595ce4a3a
2 files changed
+311
-240
No files matched your search
+305
-236
@@ -22,6 +22,8 @@ import Path from "../misc/path";
|
|||||||
|
|
||||||
const { DirectoryTree } = Tree;
|
const { DirectoryTree } = Tree;
|
||||||
|
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
|
||||||
type MenuInfo = Parameters<Exclude<MenuProps["onClick"], undefined>>[0];
|
type MenuInfo = Parameters<Exclude<MenuProps["onClick"], undefined>>[0];
|
||||||
type MenuEvent =
|
type MenuEvent =
|
||||||
| "open"
|
| "open"
|
||||||
@@ -273,6 +275,280 @@ const createFolderContextMenu = (copiedPath: string) => {
|
|||||||
return arr;
|
return arr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createNodeTree = (rootName: string, nodeDefs: b3util.NodeDefs) => {
|
||||||
|
const workspace = useWorkspace.getState();
|
||||||
|
const data: NodeTreeType = {
|
||||||
|
title: i18n.t("nodeDefinition"),
|
||||||
|
path: rootName,
|
||||||
|
icon: (
|
||||||
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
|
<PiTreeStructureFill size={19} />
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
children: [],
|
||||||
|
style: {
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: "13px",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
nodeDefs.forEach((nodeDef) => {
|
||||||
|
(nodeDef.group || [""]).forEach((g) => {
|
||||||
|
const typeGroup = !g ? nodeDef.type : `${nodeDef.type} (${g})`;
|
||||||
|
let catalog = data.children?.find((nt) => nt.title === typeGroup);
|
||||||
|
if (!catalog) {
|
||||||
|
const type = getNodeType(nodeDef);
|
||||||
|
catalog = {
|
||||||
|
title: typeGroup,
|
||||||
|
path: `nodeTree.catalog.${typeGroup}`,
|
||||||
|
children: [],
|
||||||
|
icon: (
|
||||||
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
|
<img
|
||||||
|
className="b3-node-icon"
|
||||||
|
style={{ width: "13px", height: "13px", color: "white" }}
|
||||||
|
src={`./icons/${type}.svg`}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
data.children?.push(catalog);
|
||||||
|
}
|
||||||
|
catalog.children?.push({
|
||||||
|
title: `${nodeDef.name}(${nodeDef.desc})`,
|
||||||
|
isLeaf: true,
|
||||||
|
def: nodeDef,
|
||||||
|
path: `${nodeDef.name}(${g})`,
|
||||||
|
icon: (
|
||||||
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
|
{nodeDef.icon ? (
|
||||||
|
<img
|
||||||
|
className="b3-node-icon"
|
||||||
|
style={{ width: "13px", height: "13px", color: "white" }}
|
||||||
|
src={`file:///${workspace.workdir}/${nodeDef.icon}`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BsBoxFill style={{ width: "12px", height: "12px", color: "white" }} />
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
data.children?.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
data.children?.forEach((child) => child.children?.sort((a, b) => a.title.localeCompare(b.title)));
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isElementInViewport = (element: Element, container: Element) => {
|
||||||
|
const elementRect = element.getBoundingClientRect();
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
return elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollIntoView = (path: string) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
const element = document.querySelector(`[title="${path}"]`);
|
||||||
|
const container = element?.closest(".b3-overflow") || element?.closest(".b3-explorer");
|
||||||
|
if (element && container && !isElementInViewport(element, container)) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: "auto",
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertReplaceFile = (path: string, newPath: string) => {
|
||||||
|
const workspace = useWorkspace.getState();
|
||||||
|
const alert = modal.confirm({
|
||||||
|
centered: true,
|
||||||
|
content: (
|
||||||
|
<Flex vertical gap="middle">
|
||||||
|
<div>
|
||||||
|
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
||||||
|
</div>
|
||||||
|
<div>{i18n.t("explorer.replaceFile", { name: Path.basename(newPath) })}</div>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
footer: (
|
||||||
|
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
||||||
|
<Flex vertical gap="6px">
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
onClick={() => {
|
||||||
|
workspace.close(newPath);
|
||||||
|
renameFile(path, newPath);
|
||||||
|
alert.destroy();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("replace")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
alert.destroy();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const doMoveFile = (path: string, newPath: string) => {
|
||||||
|
const workspace = useWorkspace.getState();
|
||||||
|
const title = Path.basename(path);
|
||||||
|
const destDir = Path.dirname(newPath);
|
||||||
|
const doMove = () => {
|
||||||
|
fs.renameSync(path, newPath);
|
||||||
|
for (const editor of workspace.editors) {
|
||||||
|
if (editor.path.startsWith(path)) {
|
||||||
|
editor.dispatch("rename", destDir + "/" + Path.basename(editor.path));
|
||||||
|
}
|
||||||
|
console.log("editor move", editor.path === newPath, editor.path, newPath);
|
||||||
|
if (editor.path.startsWith(newPath)) {
|
||||||
|
console.log("editor reload", editor.path === newPath, editor.path, newPath);
|
||||||
|
editor.dispatch("refresh");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!fs.existsSync(newPath)) {
|
||||||
|
doMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alert = modal.confirm({
|
||||||
|
centered: true,
|
||||||
|
content: (
|
||||||
|
<Flex vertical gap="middle">
|
||||||
|
<div>
|
||||||
|
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
||||||
|
</div>
|
||||||
|
<div>{i18n.t("explorer.replaceFile", { name: title })}</div>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
footer: (
|
||||||
|
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
||||||
|
<Flex vertical gap="6px">
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
onClick={() => {
|
||||||
|
console.log("close file", newPath);
|
||||||
|
workspace.close(path);
|
||||||
|
doMove();
|
||||||
|
alert.destroy();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("replace")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
alert.destroy();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertDeleteFile = (path: string) => {
|
||||||
|
const workspace = useWorkspace.getState();
|
||||||
|
const title = Path.basename(path);
|
||||||
|
const alert = modal.confirm({
|
||||||
|
centered: true,
|
||||||
|
content: (
|
||||||
|
<Flex vertical gap="middle">
|
||||||
|
<div>
|
||||||
|
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
||||||
|
</div>
|
||||||
|
<div>{i18n.t("explorer.deleteFile", { name: title })}</div>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
footer: (
|
||||||
|
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
||||||
|
<Flex vertical gap="6px">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
alert.destroy();
|
||||||
|
if (path === workspace.editing?.path) {
|
||||||
|
workspace.close(path);
|
||||||
|
}
|
||||||
|
ipcRenderer.invoke("trashItem", path);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("moveToTrash")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
alert.destroy();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<div style={{ fontSize: "11px", textAlign: "center" }}>
|
||||||
|
{i18n.t("explorer.restoreFileInfo")}
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertDeleteFolder = (path: string) => {
|
||||||
|
const workspace = useWorkspace.getState();
|
||||||
|
const title = Path.basename(path);
|
||||||
|
const alert = modal.confirm({
|
||||||
|
centered: true,
|
||||||
|
content: (
|
||||||
|
<Flex vertical gap="middle">
|
||||||
|
<div>
|
||||||
|
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
||||||
|
</div>
|
||||||
|
<div>{i18n.t("explorer.deleteFolder", { name: title })}</div>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
footer: (
|
||||||
|
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
||||||
|
<Flex vertical gap="6px">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
workspace.editors.forEach((editor) => {
|
||||||
|
if (editor.path.startsWith(path + "/")) {
|
||||||
|
workspace.close(editor.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ipcRenderer.invoke("trashItem", path);
|
||||||
|
alert.destroy();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("moveToTrash")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
alert.destroy();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<div style={{ fontSize: "11px", textAlign: "center" }}>
|
||||||
|
{i18n.t("explorer.restoreFileInfo")}
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const Explorer: FC = () => {
|
export const Explorer: FC = () => {
|
||||||
const workspace = useWorkspace(
|
const workspace = useWorkspace(
|
||||||
useShallow((state) => ({
|
useShallow((state) => ({
|
||||||
@@ -300,6 +576,7 @@ export const Explorer: FC = () => {
|
|||||||
const [selectedNodedefKeys, setSelectedNodedefKeys] = useState<string[]>([]);
|
const [selectedNodedefKeys, setSelectedNodedefKeys] = useState<string[]>([]);
|
||||||
const [expandedNodedefKeys, setExpandedNodedefKeys] = useState<React.Key[]>([rootNodedefName]);
|
const [expandedNodedefKeys, setExpandedNodedefKeys] = useState<React.Key[]>([rootNodedefName]);
|
||||||
|
|
||||||
|
// set the icon of the root node
|
||||||
if (workspace.fileTree) {
|
if (workspace.fileTree) {
|
||||||
workspace.fileTree.icon = (
|
workspace.fileTree.icon = (
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
@@ -308,71 +585,10 @@ export const Explorer: FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeTree = useMemo(() => {
|
const nodeTree = useMemo(
|
||||||
const data: NodeTreeType = {
|
() => createNodeTree(rootNodedefName, workspace.nodeDefs),
|
||||||
title: t("nodeDefinition"),
|
[t, workspace.nodeDefs]
|
||||||
path: rootNodedefName,
|
);
|
||||||
icon: (
|
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
|
||||||
<PiTreeStructureFill size={19} />
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
children: [],
|
|
||||||
style: {
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontSize: "13px",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
workspace.nodeDefs.forEach((nodeDef) => {
|
|
||||||
(nodeDef.group || [""]).forEach((g) => {
|
|
||||||
const typeGroup = !g ? nodeDef.type : `${nodeDef.type} (${g})`;
|
|
||||||
let catalog = data.children?.find((nt) => nt.title === typeGroup);
|
|
||||||
if (!catalog) {
|
|
||||||
const type = getNodeType(nodeDef);
|
|
||||||
catalog = {
|
|
||||||
title: typeGroup,
|
|
||||||
path: `nodeTree.catalog.${typeGroup}`,
|
|
||||||
children: [],
|
|
||||||
icon: (
|
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
|
||||||
<img
|
|
||||||
className="b3-node-icon"
|
|
||||||
style={{ width: "13px", height: "13px", color: "white" }}
|
|
||||||
src={`./icons/${type}.svg`}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
data.children?.push(catalog);
|
|
||||||
}
|
|
||||||
catalog.children?.push({
|
|
||||||
title: `${nodeDef.name}(${nodeDef.desc})`,
|
|
||||||
isLeaf: true,
|
|
||||||
def: nodeDef,
|
|
||||||
path: `${nodeDef.name}(${g})`,
|
|
||||||
icon: nodeDef.icon ? (
|
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
|
||||||
<img
|
|
||||||
className="b3-node-icon"
|
|
||||||
key={catalog.title}
|
|
||||||
style={{ width: "13px", height: "13px", color: "white" }}
|
|
||||||
src={`file:///${workspace.workdir}/${nodeDef.icon}`}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
) : (
|
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
|
||||||
<BsBoxFill style={{ width: "12px", height: "12px", color: "white" }} />{" "}
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
data.children?.sort((a, b) => a.title.localeCompare(b.title));
|
|
||||||
data.children?.forEach((child) =>
|
|
||||||
child.children?.sort((a, b) => a.title.localeCompare(b.title))
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}, [t, workspace.nodeDefs]);
|
|
||||||
|
|
||||||
// expand the selected tree
|
// expand the selected tree
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -384,6 +600,9 @@ export const Explorer: FC = () => {
|
|||||||
keys.push(k);
|
keys.push(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!selectedNodedefKeys.includes(workspace.editing.path)) {
|
||||||
|
scrollIntoView(workspace.editing.path);
|
||||||
|
}
|
||||||
setExpandedKeys(keys);
|
setExpandedKeys(keys);
|
||||||
setSelectedKeys([workspace.editing.path]);
|
setSelectedKeys([workspace.editing.path]);
|
||||||
}
|
}
|
||||||
@@ -402,6 +621,9 @@ export const Explorer: FC = () => {
|
|||||||
keys.push(k);
|
keys.push(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!workspace.editingNodeDef.path) {
|
||||||
|
scrollIntoView(path);
|
||||||
|
}
|
||||||
setExpandedNodedefKeys(keys);
|
setExpandedNodedefKeys(keys);
|
||||||
setSelectedNodedefKeys([path]);
|
setSelectedNodedefKeys([path]);
|
||||||
}
|
}
|
||||||
@@ -537,41 +759,7 @@ export const Explorer: FC = () => {
|
|||||||
if (node.path === newPath) {
|
if (node.path === newPath) {
|
||||||
dispatch("duplicate", node);
|
dispatch("duplicate", node);
|
||||||
} else {
|
} else {
|
||||||
const alert = modal.confirm({
|
alertReplaceFile(node.path, newPath);
|
||||||
centered: true,
|
|
||||||
content: (
|
|
||||||
<Flex vertical gap="middle">
|
|
||||||
<div>
|
|
||||||
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
|
||||||
</div>
|
|
||||||
<div>{t("explorer.replaceFile", { name: Path.basename(copyFile) })}</div>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
footer: (
|
|
||||||
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
|
||||||
<Flex vertical gap="6px">
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
onClick={() => {
|
|
||||||
workspace.close(newPath);
|
|
||||||
renameFile(node.path, newPath);
|
|
||||||
alert.destroy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("replace")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
alert.destroy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fs.copyFileSync(copyFile, newPath);
|
fs.copyFileSync(copyFile, newPath);
|
||||||
@@ -604,87 +792,9 @@ export const Explorer: FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (node.isLeaf) {
|
if (node.isLeaf) {
|
||||||
const alert = modal.confirm({
|
alertDeleteFile(node.path);
|
||||||
centered: true,
|
|
||||||
content: (
|
|
||||||
<Flex vertical gap="middle">
|
|
||||||
<div>
|
|
||||||
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
|
||||||
</div>
|
|
||||||
<div>{t("explorer.deleteFile", { name: node.title })}</div>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
footer: (
|
|
||||||
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
|
||||||
<Flex vertical gap="6px">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
alert.destroy();
|
|
||||||
if (node.path === workspace.editing?.path) {
|
|
||||||
workspace.close(node.path);
|
|
||||||
}
|
|
||||||
ipcRenderer.invoke("trashItem", node.path);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("moveToTrash")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
alert.destroy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
<div style={{ fontSize: "11px", textAlign: "center" }}>
|
|
||||||
{t("explorer.restoreFileInfo")}
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const alert = modal.confirm({
|
alertDeleteFolder(node.path);
|
||||||
centered: true,
|
|
||||||
content: (
|
|
||||||
<Flex vertical gap="middle">
|
|
||||||
<div>
|
|
||||||
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
|
||||||
</div>
|
|
||||||
<div>{t("explorer.deleteFolder", { name: node.title })}</div>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
footer: (
|
|
||||||
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
|
||||||
<Flex vertical gap="6px">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
workspace.editors.forEach((editor) => {
|
|
||||||
if (editor.path.startsWith(node.path + "/")) {
|
|
||||||
workspace.close(editor.path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ipcRenderer.invoke("trashItem", node.path);
|
|
||||||
alert.destroy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("moveToTrash")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
alert.destroy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
<div style={{ fontSize: "11px", textAlign: "center" }}>
|
|
||||||
{t("explorer.restoreFileInfo")}
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -695,59 +805,7 @@ export const Explorer: FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newPath = destDir + "/" + Path.basename(node.path);
|
const newPath = destDir + "/" + Path.basename(node.path);
|
||||||
const doMove = () => {
|
doMoveFile(node.path, newPath);
|
||||||
fs.renameSync(node.path, newPath);
|
|
||||||
for (const editor of workspace.editors) {
|
|
||||||
if (editor.path.startsWith(node.path)) {
|
|
||||||
editor.dispatch("rename", destDir + "/" + Path.basename(editor.path));
|
|
||||||
}
|
|
||||||
console.log("editor move", editor.path === newPath, editor.path, newPath);
|
|
||||||
if (editor.path.startsWith(newPath)) {
|
|
||||||
console.log("editor reload", editor.path === newPath, editor.path, newPath);
|
|
||||||
editor.dispatch("refresh");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (fs.existsSync(newPath)) {
|
|
||||||
const alert = modal.confirm({
|
|
||||||
centered: true,
|
|
||||||
content: (
|
|
||||||
<Flex vertical gap="middle">
|
|
||||||
<div>
|
|
||||||
<FaExclamationTriangle style={{ fontSize: "60px", color: "#FADB14" }} />
|
|
||||||
</div>
|
|
||||||
<div>{t("explorer.replaceFile", { name: Path.basename(node.path) })}</div>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
footer: (
|
|
||||||
<Flex vertical gap="middle" style={{ paddingTop: "30px" }}>
|
|
||||||
<Flex vertical gap="6px">
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
onClick={() => {
|
|
||||||
console.log("close file", newPath);
|
|
||||||
workspace.close(node.path);
|
|
||||||
doMove();
|
|
||||||
alert.destroy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("replace")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
alert.destroy();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
doMove();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("move file:", error);
|
console.error("move file:", error);
|
||||||
}
|
}
|
||||||
@@ -801,7 +859,7 @@ export const Explorer: FC = () => {
|
|||||||
<DirectoryTree
|
<DirectoryTree
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
treeData={workspace.fileTree ? [workspace.fileTree] : []}
|
treeData={workspace.fileTree ? [workspace.fileTree] : []}
|
||||||
fieldNames={{ key: "path" }}
|
fieldNames={{ key: "path", title: "path" }}
|
||||||
expandedKeys={expandedKeys}
|
expandedKeys={expandedKeys}
|
||||||
selectedKeys={selectedKeys}
|
selectedKeys={selectedKeys}
|
||||||
onExpand={(keys) => {
|
onExpand={(keys) => {
|
||||||
@@ -818,6 +876,18 @@ export const Explorer: FC = () => {
|
|||||||
if (node && !node.editing) {
|
if (node && !node.editing) {
|
||||||
dispatch("open", node);
|
dispatch("open", node);
|
||||||
setSelectedKeys([node.path]);
|
setSelectedKeys([node.path]);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = document.querySelector(`[title="${node.title}"]`);
|
||||||
|
const container =
|
||||||
|
element?.closest(".b3-overflow") || element?.closest(".b3-explorer");
|
||||||
|
if (element && container && !isElementInViewport(element, container)) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onDrop={(info) => {
|
onDrop={(info) => {
|
||||||
@@ -898,7 +968,7 @@ export const Explorer: FC = () => {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
<DirectoryTree
|
<DirectoryTree
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
fieldNames={{ key: "path" }}
|
fieldNames={{ key: "path", title: "path" }}
|
||||||
treeData={[nodeTree]}
|
treeData={[nodeTree]}
|
||||||
expandedKeys={expandedNodedefKeys}
|
expandedKeys={expandedNodedefKeys}
|
||||||
selectedKeys={selectedNodedefKeys}
|
selectedKeys={selectedNodedefKeys}
|
||||||
@@ -928,7 +998,6 @@ export const Explorer: FC = () => {
|
|||||||
path: node.path,
|
path: node.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.info("select node:", node.path, node.def);
|
|
||||||
setSelectedNodedefKeys([node.path]);
|
setSelectedNodedefKeys([node.path]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -634,10 +634,11 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
if (!fileMeta) {
|
if (!fileMeta) {
|
||||||
fileMeta = { path: fileNode.path };
|
fileMeta = { path: fileNode.path };
|
||||||
allFiles.set(path, fileMeta);
|
allFiles.set(path, fileMeta);
|
||||||
|
console.log("add file meta:", path);
|
||||||
} else {
|
} else {
|
||||||
fileMeta.path = fileNode.path;
|
fileMeta.path = fileNode.path;
|
||||||
}
|
}
|
||||||
fileMeta.exists = true;
|
fileMeta.exists = fs.existsSync(fileNode.path);
|
||||||
if (fileMeta.desc === undefined) {
|
if (fileMeta.desc === undefined) {
|
||||||
const file = readJson(fileNode.path) as TreeModel;
|
const file = readJson(fileNode.path) as TreeModel;
|
||||||
fileMeta.desc = file.desc ?? "";
|
fileMeta.desc = file.desc ?? "";
|
||||||
@@ -650,11 +651,12 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
allFiles.forEach((file, key) => {
|
allFiles.forEach((file, key) => {
|
||||||
if (!file.exists) {
|
if (!file.exists) {
|
||||||
allFiles.delete(key);
|
allFiles.delete(key);
|
||||||
delete b3util.files[key];
|
|
||||||
updated = true;
|
updated = true;
|
||||||
|
console.log("delete file meta:", key);
|
||||||
|
delete b3util.files[key];
|
||||||
|
} else {
|
||||||
|
b3util.files[key] = fs.statSync(file.path).mtimeMs;
|
||||||
}
|
}
|
||||||
const modified = fs.statSync(file.path).mtimeMs;
|
|
||||||
b3util.files[key] = modified;
|
|
||||||
});
|
});
|
||||||
set({ allFiles });
|
set({ allFiles });
|
||||||
if (updated) {
|
if (updated) {
|
||||||
|
|||||||
Reference in new issue
Block a user