support search file desc

This commit is contained in:
codetypes committed 2024-05-08 00:09:18 +08:00
1 parent 6d00be9d3b
commit b693855012
13 files changed
+154 -40

No files matched your search

+5
View File
@@ -171,3 +171,8 @@ ipcMain.handle("trashItem", (_, arg: string) => {
arg = arg.replace(/\//g, path.sep); arg = arg.replace(/\//g, path.sep);
shell.trashItem(arg).catch((e) => console.error(e)); shell.trashItem(arg).catch((e) => console.error(e));
}); });
ipcMain.handle("showItemInFolder", (_, arg: string) => {
arg = arg.replace(/\//g, path.sep);
shell.showItemInFolder(arg);
});
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "behavior3editor", "name": "behavior3editor",
"version": "1.2.0", "version": "1.3.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "behavior3editor", "name": "behavior3editor",
"version": "1.2.0", "version": "1.3.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "behavior3editor", "name": "behavior3editor",
"version": "1.2.0", "version": "1.3.0",
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",
"description": "行为树编辑器", "description": "行为树编辑器",
"author": "zhandouxiaojiji & codetypes", "author": "zhandouxiaojiji & codetypes",
-1
View File
@@ -77,7 +77,6 @@
"regnode.subtree": "Subtree: ", "regnode.subtree": "Subtree: ",
"node.args": "Const arguments", "node.args": "Const arguments",
"node.codePlaceholder": "a.b * 3 > 2",
"node.debug": "Debug", "node.debug": "Debug",
"node.disabled": "Disabled", "node.disabled": "Disabled",
"node.deleteRootNodeDenied": "Unable to delete the root node", "node.deleteRootNodeDenied": "Unable to delete the root node",
-1
View File
@@ -77,7 +77,6 @@
"regnode.subtree": "子树:", "regnode.subtree": "子树:",
"node.args": "常量参数", "node.args": "常量参数",
"node.codePlaceholder": "a.b * 3 > 2",
"node.debug": "调试", "node.debug": "调试",
"node.disabled": "禁用", "node.disabled": "禁用",
"node.deleteRootNodeDenied": "不能删除根节点", "node.deleteRootNodeDenied": "不能删除根节点",
+26 -3
View File
@@ -1,5 +1,28 @@
{ {
"isRelative": true, "files": [
"nodeConfPath": "node-config.json", {
"workdir": "workdir" "path": "workdir/hero.json",
"desc": "英雄测试AI"
},
{
"path": "workdir/monster.json",
"desc": "怪物测试AI"
},
{
"path": "workdir/sub/subtree1.json",
"desc": "子树1"
},
{
"path": "workdir/sub/subtree2.json",
"desc": "子树2"
},
{
"path": "workdir/subtree1.json",
"desc": "子树1"
},
{
"path": "workdir/subtree2.json",
"desc": "子树2"
}
]
} }
+5 -3
View File
@@ -131,6 +131,8 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
onEditingTree: useWorkspace((state) => state.onEditingTree), onEditingTree: useWorkspace((state) => state.onEditingTree),
open: useWorkspace((state) => state.open), open: useWorkspace((state) => state.open),
workdir: useWorkspace((state) => state.workdir), workdir: useWorkspace((state) => state.workdir),
relative: useWorkspace((state) => state.relative),
updateFileMeta: useWorkspace((state) => state.updateFileMeta),
}; };
const searchInputRef = useRef<InputRef>(null); const searchInputRef = useRef<InputRef>(null);
@@ -605,8 +607,8 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
desc: editor.desc, desc: editor.desc,
} as TreeModel; } as TreeModel;
fs.writeFileSync(path, JSON.stringify(treeModel, null, 2)); fs.writeFileSync(path, JSON.stringify(treeModel, null, 2));
workspace.updateFileMeta(editor);
editor.unsave = false; editor.unsave = false;
editor.data = b3util.createTreeData(root); editor.data = b3util.createTreeData(root);
editor.autoId = b3util.refreshTreeDataId(editor.data); editor.autoId = b3util.refreshTreeDataId(editor.data);
editor.graph.changeData(editor.data); editor.graph.changeData(editor.data);
@@ -664,7 +666,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
desc: data.desc, desc: data.desc,
} as TreeModel; } as TreeModel;
fs.writeFileSync(subpath, JSON.stringify(subtreeModel, null, 2)); fs.writeFileSync(subpath, JSON.stringify(subtreeModel, null, 2));
data.path = Path.relative(workspace.workdir, subpath).replaceAll(Path.sep, "/"); data.path = workspace.relative(subpath);
reload(); reload();
pushHistory(); pushHistory();
onChange(); onChange();
@@ -858,7 +860,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
{ {
id: editor.autoId++, id: editor.autoId++,
name: "unknow", name: "unknow",
path: Path.relative(workspace.workdir, exploreFile).replaceAll(Path.sep, "/"), path: workspace.relative(exploreFile),
}, },
dstData.id dstData.id
); );
+1 -1
View File
@@ -551,7 +551,7 @@ export const Explorer: FC = () => {
break; break;
} }
case "revealFile": case "revealFile":
shell.showItemInFolder(node.path.replaceAll("/", Path.sep)); ipcRenderer.invoke("showItemInFolder", node.path);
break; break;
case "rename": { case "rename": {
node.editing = true; node.editing = true;
+8 -7
View File
@@ -9,7 +9,9 @@ import { FC, useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
interface OptionType extends DefaultOptionType {} interface OptionType extends DefaultOptionType {
value: string;
}
const TreeInspector: FC = () => { const TreeInspector: FC = () => {
const workspace = { const workspace = {
@@ -70,6 +72,7 @@ const NodeInspector: FC = () => {
onEditingNode: useWorkspace((state) => state.onEditingNode), onEditingNode: useWorkspace((state) => state.onEditingNode),
allFiles: useWorkspace((state) => state.allFiles), allFiles: useWorkspace((state) => state.allFiles),
fileTree: useWorkspace((state) => state.fileTree), fileTree: useWorkspace((state) => state.fileTree),
relative: useWorkspace((state) => state.relative),
}; };
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -133,14 +136,15 @@ const NodeInspector: FC = () => {
// auto complete for subtree // auto complete for subtree
const subtreeOptions = useMemo(() => { const subtreeOptions = useMemo(() => {
const options: OptionType[] = []; const options: OptionType[] = [];
workspace.allFiles.forEach((path) => { workspace.allFiles.forEach((file) => {
const value = Path.relative(workspace.fileTree!.path, path).replaceAll(Path.sep, "/"); const value = workspace.relative(file.path);
const desc = ""; //fileNode.desc ? `(${fileNode.desc})` : ""; const desc = ""; //fileNode.desc ? `(${fileNode.desc})` : "";
options.push({ options.push({
label: `${value}${desc}`, label: `${value}${desc}`,
value: value, value: value,
}); });
}); });
options.sort((a, b) => a.value.localeCompare(b.value));
return options; return options;
}, [workspace.allFiles, workspace.fileTree]); }, [workspace.allFiles, workspace.fileTree]);
@@ -328,9 +332,7 @@ const NodeInspector: FC = () => {
)} )}
{type === "float" && <InputNumber disabled={disabled} onBlur={form.submit} />} {type === "float" && <InputNumber disabled={disabled} onBlur={form.submit} />}
{type === "boolean" && <Switch disabled={disabled} onChange={form.submit} />} {type === "boolean" && <Switch disabled={disabled} onChange={form.submit} />}
{type === "code" && ( {type === "code" && <Input disabled={disabled} onBlur={form.submit} />}
<Input onBlur={form.submit} placeholder={t("node.codePlaceholder")} />
)}
{type === "enum" && ( {type === "enum" && (
<Select disabled={disabled} onBlur={form.submit} onChange={form.submit}> <Select disabled={disabled} onBlur={form.submit} onChange={form.submit}>
{v.options?.map((value) => { {v.options?.map((value) => {
@@ -475,7 +477,6 @@ const NodeDefInspector: FC = () => {
</Divider> </Divider>
{data.args.map((v, i) => { {data.args.map((v, i) => {
const required = v.type.indexOf("?") == -1; const required = v.type.indexOf("?") == -1;
const type = v.type.replace("?", "") as NodeArgType;
return ( return (
<Form.Item <Form.Item
name={`args.${i}.type`} name={`args.${i}.type`}
-2
View File
@@ -41,12 +41,10 @@ export const Menu: FC<LayoutProps> = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [trigger, setTrigger] = useState<DropDownProps["trigger"]>(["click"]); const [trigger, setTrigger] = useState<DropDownProps["trigger"]>(["click"]);
const workspace = { const workspace = {
allFiles: useWorkspace((state) => state.allFiles),
batchProject: useWorkspace((state) => state.batchProject), batchProject: useWorkspace((state) => state.batchProject),
buildProject: useWorkspace((state) => state.buildProject), buildProject: useWorkspace((state) => state.buildProject),
createProject: useWorkspace((state) => state.createProject), createProject: useWorkspace((state) => state.createProject),
editing: useWorkspace((state) => state.editing), editing: useWorkspace((state) => state.editing),
editors: useWorkspace((state) => state.editors),
init: useWorkspace((state) => state.init), init: useWorkspace((state) => state.init),
open: useWorkspace((state) => state.open), open: useWorkspace((state) => state.open),
openProject: useWorkspace((state) => state.openProject), openProject: useWorkspace((state) => state.openProject),
+13 -6
View File
@@ -20,23 +20,28 @@ export const TitleBar: FC<LayoutProps> = () => {
onShowingSearch: useWorkspace((state) => state.onShowingSearch), onShowingSearch: useWorkspace((state) => state.onShowingSearch),
open: useWorkspace((state) => state.open), open: useWorkspace((state) => state.open),
name: useWorkspace((state) => state.path), name: useWorkspace((state) => state.path),
relative: useWorkspace((state) => state.relative),
}; };
const searchOptions = useMemo(() => { const searchOptions = useMemo(() => {
const options: OptionType[] = []; const options: OptionType[] = [];
workspace.allFiles.forEach((path) => { workspace.allFiles.forEach((file) => {
const value = Path.relative(workspace.fileTree!.path, path).replaceAll(Path.sep, "/"); const value = workspace.relative(file.path);
const desc = file.desc ?? "";
options.push({ options.push({
label: ( label: (
<div> <div>
{Path.basename(value)} {Path.basename(value)}
<span> </span> <span> </span>
<span style={{ color: "gray", fontSize: "12px" }}>{Path.dirname(value)}</span> <span style={{ color: "gray", fontSize: "12px" }}>
{Path.dirname(value)} {desc}
</span>
</div> </div>
), ),
value: value, value: `${value.toLocaleLowerCase()} ${desc.toLocaleLowerCase()}`,
path: path, path: file.path,
}); });
}); });
options.sort((a, b) => a.path.localeCompare(b.path));
return options; return options;
}, [workspace.allFiles, workspace.fileTree]); }, [workspace.allFiles, workspace.fileTree]);
return ( return (
@@ -101,7 +106,9 @@ export const TitleBar: FC<LayoutProps> = () => {
workspace.open(option.path); workspace.open(option.path);
} }
}} }}
filterOption={(input, option) => (option?.value ?? "").includes(input)} filterOption={(input, option) =>
(option?.value ?? "").includes(input.toLocaleLowerCase())
}
filterSort={(optionA, optionB) => filterSort={(optionA, optionB) =>
(optionA?.value ?? "") (optionA?.value ?? "")
.toLowerCase() .toLowerCase()
+92 -12
View File
@@ -2,7 +2,7 @@ import { NodeDef, NodeModel, TreeGraphData, TreeModel, unknownNodeDef } from "@/
import * as b3util from "@/misc/b3util"; import * as b3util from "@/misc/b3util";
import { message } from "@/misc/hooks"; import { message } from "@/misc/hooks";
import i18n from "@/misc/i18n"; import i18n from "@/misc/i18n";
import { readJson } from "@/misc/util"; import { readJson, writeJson } from "@/misc/util";
import Path from "@/misc/path"; import Path from "@/misc/path";
import { Matrix, TreeGraph } from "@antv/g6"; import { Matrix, TreeGraph } from "@antv/g6";
import { BrowserWindow, dialog } from "@electron/remote"; import { BrowserWindow, dialog } from "@electron/remote";
@@ -61,9 +61,9 @@ export class EditorStore {
constructor(path: string) { constructor(path: string) {
this.path = path; this.path = path;
const file = readJson(path); const file = readJson(path) as TreeModel;
this.data = b3util.createTreeData(file.root); this.data = b3util.createTreeData(file.root);
this.desc = file.desc; this.desc = file.desc ?? "";
this.name = file.name || path.slice(0, -5); this.name = file.name || path.slice(0, -5);
this.autoId = b3util.refreshTreeDataId(this.data); this.autoId = b3util.refreshTreeDataId(this.data);
this.historyStack.push(b3util.createNode(this.data)); this.historyStack.push(b3util.createNode(this.data));
@@ -95,6 +95,16 @@ export type EditTree = {
data: TreeModel; data: TreeModel;
}; };
export type FileMeta = {
path: string;
desc?: string;
exists?: boolean;
};
interface WorkspaceModel {
files?: { path: string; desc: string }[];
}
export type WorkspaceStore = { export type WorkspaceStore = {
init: (project: string) => void; init: (project: string) => void;
createProject: () => void; createProject: () => void;
@@ -105,7 +115,11 @@ export type WorkspaceStore = {
workdir: string; workdir: string;
path: string; path: string;
allFiles: string[]; loadWorkspace: () => void;
saveWorkspace: () => void;
updateFileMeta: (editor: EditorStore) => void;
allFiles: Map<string, FileMeta>;
fileTree?: FileTreeType; fileTree?: FileTreeType;
editors: EditorStore[]; editors: EditorStore[];
editing?: EditorStore; editing?: EditorStore;
@@ -117,6 +131,7 @@ export type WorkspaceStore = {
edit: (path: string) => void; edit: (path: string) => void;
close: (path: string) => void; close: (path: string) => void;
find: (path: string) => EditorStore | undefined; find: (path: string) => EditorStore | undefined;
relative: (path: string) => string;
save: () => void; save: () => void;
saveAs: () => void; saveAs: () => void;
@@ -189,7 +204,7 @@ const saveFile = (editor?: EditorStore) => {
}; };
export const useWorkspace = create<WorkspaceStore>((set, get) => ({ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
allFiles: [], allFiles: new Map(),
fileTree: undefined, fileTree: undefined,
editors: [], editors: [],
workdir: "", workdir: "",
@@ -201,6 +216,7 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
try { try {
workspace.workdir = Path.dirname(path).replaceAll(Path.sep, "/"); workspace.workdir = Path.dirname(path).replaceAll(Path.sep, "/");
workspace.path = path; workspace.path = path;
workspace.loadWorkspace();
workspace.loadTrees(); workspace.loadTrees();
workspace.loadNodeDefs(); workspace.loadNodeDefs();
workspace.watch(); workspace.watch();
@@ -257,8 +273,8 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
} }
try { try {
let hasError = false; let hasError = false;
for (const path of workspace.allFiles) { for (const [path] of workspace.allFiles) {
const buildpath = buildDir + "/" + Path.relative(workspace.workdir, path); const buildpath = buildDir + "/" + workspace.relative(path);
console.log("build:", buildpath); console.log("build:", buildpath);
const treeModel = b3util.createBuildData(path); const treeModel = b3util.createBuildData(path);
if (!b3util.checkNodeData(treeModel?.root)) { if (!b3util.checkNodeData(treeModel?.root)) {
@@ -290,8 +306,8 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
console.log("run script", scriptPath); console.log("run script", scriptPath);
const str = fs.readFileSync(scriptPath, "utf8"); const str = fs.readFileSync(scriptPath, "utf8");
const batch = eval(str) as BatchScript; const batch = eval(str) as BatchScript;
workspace.allFiles.forEach((path) => { workspace.allFiles.forEach((file) => {
const treeStr = fs.readFileSync(path, "utf8"); const treeStr = fs.readFileSync(file.path, "utf8");
let tree: TreeModel | undefined = JSON.parse(treeStr); let tree: TreeModel | undefined = JSON.parse(treeStr);
if (batch.processTree && tree) { if (batch.processTree && tree) {
tree = batch.processTree(tree); tree = batch.processTree(tree);
@@ -304,7 +320,7 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
processNode(tree.root); processNode(tree.root);
} }
if (tree) { if (tree) {
fs.writeFileSync(path, JSON.stringify(tree)); fs.writeFileSync(file.path, JSON.stringify(tree));
} }
}); });
} catch (error) { } catch (error) {
@@ -313,6 +329,40 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
} }
}, },
loadWorkspace: () => {
const workspace = get();
const data = readJson(workspace.path) as WorkspaceModel;
data.files?.forEach((file) => {
workspace.allFiles.set(file.path, { path: file.path, desc: file.desc, exists: false });
});
},
saveWorkspace: () => {
const workspace = get();
const data: WorkspaceModel = {
files: [],
};
workspace.allFiles.forEach((file) => {
data.files?.push({
path: workspace.relative(file.path),
desc: file.desc ?? "",
});
});
data.files?.sort((a, b) => a.path.localeCompare(b.path));
writeJson(workspace.path, data);
},
updateFileMeta: (editor) => {
const workspace = get();
const path = workspace.relative(editor.path);
const file = workspace.allFiles.get(path);
if (file && file.desc !== editor.desc) {
file.desc = editor.desc;
set({ allFiles: new Map(workspace.allFiles) });
workspace.saveWorkspace();
}
},
isShowingSearch: false, isShowingSearch: false,
onShowingSearch: (isShowingSearch) => { onShowingSearch: (isShowingSearch) => {
set({ isShowingSearch }); set({ isShowingSearch });
@@ -326,6 +376,7 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
editor = new EditorStore(path); editor = new EditorStore(path);
workspace.editors.push(editor); workspace.editors.push(editor);
set({ editors: workspace.editors }); set({ editors: workspace.editors });
workspace.updateFileMeta(editor);
workspace.edit(editor.path); workspace.edit(editor.path);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@@ -384,6 +435,11 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
return workspace.editors.find((v) => v.path === path); return workspace.editors.find((v) => v.path === path);
}, },
relative: (path: string) => {
const workspace = get();
return Path.relative(workspace.workdir, path).replaceAll(Path.sep, "/");
},
save: () => { save: () => {
const workspace = get(); const workspace = get();
saveFile(workspace.editing); saveFile(workspace.editing);
@@ -431,15 +487,39 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
}; };
set({ fileTree: data }); set({ fileTree: data });
const allFiles: string[] = []; const allFiles = workspace.allFiles;
let updated = false;
allFiles.forEach((file) => (file.exists = false));
const collect = (fileNode?: FileTreeType) => { const collect = (fileNode?: FileTreeType) => {
if (fileNode?.isLeaf) { if (fileNode?.isLeaf) {
allFiles.push(fileNode.path); const path = workspace.relative(fileNode.path);
let fileMeta = allFiles.get(path);
if (!fileMeta) {
fileMeta = { path: fileNode.path };
allFiles.set(fileNode.path, fileMeta);
} else {
fileMeta.path = fileNode.path;
}
fileMeta.exists = true;
if (fileMeta.desc === undefined) {
const file = readJson(fileNode.path) as TreeModel;
fileMeta.desc = file.desc ?? "";
updated = true;
}
} }
fileNode?.children?.forEach((child) => collect(child)); fileNode?.children?.forEach((child) => collect(child));
}; };
collect(data); collect(data);
allFiles.forEach((file, key) => {
if (!file.exists) {
allFiles.delete(key);
updated = true;
}
});
set({ allFiles }); set({ allFiles });
if (updated) {
workspace.saveWorkspace();
}
}, },
loadNodeDefs: () => { loadNodeDefs: () => {
+1 -1
View File
@@ -12,7 +12,7 @@ export interface NodeArg {
name: string; name: string;
type: string; type: string;
desc: string; desc: string;
default?: string; default?: unknown;
options?: NodeArgOption[]; options?: NodeArgOption[];
} }
export interface NodeDef { export interface NodeDef {