support search node
This commit is contained in:
1 parent
7dbea171bb
commit
82d1d89311
6 files changed
+112
-8
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"main": "dist-electron/main/index.js",
|
"main": "dist-electron/main/index.js",
|
||||||
"description": "行为树编辑器",
|
"description": "行为树编辑器",
|
||||||
"author": "zhandouxiaojiji & codetypes",
|
"author": "zhandouxiaojiji & codetypes",
|
||||||
|
|||||||
+104
-2
@@ -10,17 +10,19 @@ import * as b3util from "@/misc/b3util";
|
|||||||
import { message } from "@/misc/hooks";
|
import { message } from "@/misc/hooks";
|
||||||
import { Hotkey, isHotkeyPressed, isMacos, useHotkeys } from "@/misc/keys";
|
import { Hotkey, isHotkeyPressed, isMacos, useHotkeys } from "@/misc/keys";
|
||||||
import Path from "@/misc/path";
|
import Path from "@/misc/path";
|
||||||
|
import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined } from "@ant-design/icons";
|
||||||
import G6, { G6GraphEvent, Item, TreeGraph } from "@antv/g6";
|
import G6, { G6GraphEvent, Item, TreeGraph } from "@antv/g6";
|
||||||
import { dialog } from "@electron/remote";
|
import { dialog } from "@electron/remote";
|
||||||
import { useSize } from "ahooks";
|
import { useSize } from "ahooks";
|
||||||
import { Dropdown, Flex, FlexProps, MenuProps } from "antd";
|
import { Button, Dropdown, Flex, FlexProps, Input, MenuProps } from "antd";
|
||||||
import { clipboard } from "electron";
|
import { clipboard } from "electron";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useRef } from "react";
|
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FiDelete } from "react-icons/fi";
|
import { FiDelete } from "react-icons/fi";
|
||||||
import { IoMdReturnLeft } from "react-icons/io";
|
import { IoMdReturnLeft } from "react-icons/io";
|
||||||
import { mergeRefs } from "react-merge-refs";
|
import { mergeRefs } from "react-merge-refs";
|
||||||
|
import { useDebounceCallback } from "usehooks-ts";
|
||||||
import "./register-node";
|
import "./register-node";
|
||||||
|
|
||||||
export interface EditorProps extends React.HTMLAttributes<HTMLElement> {
|
export interface EditorProps extends React.HTMLAttributes<HTMLElement> {
|
||||||
@@ -36,11 +38,27 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
open: useWorkspace((state) => state.open),
|
open: useWorkspace((state) => state.open),
|
||||||
workdir: useWorkspace((state) => state.workdir),
|
workdir: useWorkspace((state) => state.workdir),
|
||||||
};
|
};
|
||||||
|
const [results, setResults] = useState<string[]>([]);
|
||||||
|
const [resultIndex, setResultIndex] = useState(0);
|
||||||
|
const [showingSearch, setShowingSearch] = useState(false);
|
||||||
const graphRef = useRef(null);
|
const graphRef = useRef(null);
|
||||||
const sizeRef = useRef(null);
|
const sizeRef = useRef(null);
|
||||||
const editorSize = useSize(sizeRef);
|
const editorSize = useSize(sizeRef);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onSearchChange = useDebounceCallback((filter: string) => {
|
||||||
|
const ids = filterNodes(filter, findDataById("1"));
|
||||||
|
setResults(ids);
|
||||||
|
setResultIndex(0);
|
||||||
|
if (ids.length > 0) {
|
||||||
|
editor.graph.focusItem(ids[0]);
|
||||||
|
selectNode(ids[0]);
|
||||||
|
}
|
||||||
|
if (filter.length == 0) {
|
||||||
|
restoreViewport();
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
if (!editor.unsave) {
|
if (!editor.unsave) {
|
||||||
editor.unsave = true;
|
editor.unsave = true;
|
||||||
@@ -93,6 +111,17 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
return editor.graph.findDataById(id) as TreeGraphData;
|
return editor.graph.findDataById(id) as TreeGraphData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterNodes = (str: string, node: TreeGraphData | null, ids?: string[]): string[] => {
|
||||||
|
ids ||= [];
|
||||||
|
if (node && str) {
|
||||||
|
if (node.name.toLocaleLowerCase().indexOf(str) >= 0) {
|
||||||
|
ids.push(node.id);
|
||||||
|
}
|
||||||
|
node.children?.forEach((child: TreeGraphData) => filterNodes(str, child, ids));
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
const isAncestor = (ancestor: TreeGraphData, node: TreeGraphData): boolean => {
|
const isAncestor = (ancestor: TreeGraphData, node: TreeGraphData): boolean => {
|
||||||
if (ancestor.id === node.parent) {
|
if (ancestor.id === node.parent) {
|
||||||
return true;
|
return true;
|
||||||
@@ -761,6 +790,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
Hotkey.Delete,
|
Hotkey.Delete,
|
||||||
Hotkey.Enter,
|
Hotkey.Enter,
|
||||||
Hotkey.Backspace,
|
Hotkey.Backspace,
|
||||||
|
Hotkey.SearchNode,
|
||||||
],
|
],
|
||||||
(event) => {
|
(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -778,6 +808,8 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
createNode();
|
createNode();
|
||||||
} else if (isHotkeyPressed(Hotkey.Delete) || isHotkeyPressed(Hotkey.Backspace)) {
|
} else if (isHotkeyPressed(Hotkey.Delete) || isHotkeyPressed(Hotkey.Backspace)) {
|
||||||
deleteNode();
|
deleteNode();
|
||||||
|
} else if (isHotkeyPressed(Hotkey.SearchNode)) {
|
||||||
|
setShowingSearch(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -967,6 +999,76 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{ maxWidth: "inherit", maxHeight: "inherit" }}
|
style={{ maxWidth: "inherit", maxHeight: "inherit" }}
|
||||||
>
|
>
|
||||||
|
{showingSearch && (
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
marginTop: "10px",
|
||||||
|
marginLeft: "10px",
|
||||||
|
backgroundColor: "#161b22",
|
||||||
|
padding: "4px 10px 4px 10px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
borderLeft: "4px solid #f78166",
|
||||||
|
boxShadow: "0 0 8px 2px #0000005c",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
allowClear
|
||||||
|
size="small"
|
||||||
|
style={{ borderRadius: "2px" }}
|
||||||
|
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<div style={{ padding: "0 10px 0 5px" }}>
|
||||||
|
{results.length ? `${resultIndex + 1}/${results.length}` : ""}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowDownOutlined />}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: "30px" }}
|
||||||
|
disabled={results.length == 0}
|
||||||
|
onClick={() => {
|
||||||
|
if (results.length > 0) {
|
||||||
|
const idx = (resultIndex + 1) % results.length;
|
||||||
|
setResultIndex(idx);
|
||||||
|
editor.graph.focusItem(results[idx]);
|
||||||
|
selectNode(results[idx]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowUpOutlined />}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: "30px" }}
|
||||||
|
disabled={results.length == 0}
|
||||||
|
onClick={() => {
|
||||||
|
if (results.length > 0) {
|
||||||
|
const idx = (resultIndex + results.length - 1) % results.length;
|
||||||
|
setResultIndex(idx);
|
||||||
|
console.log(idx, results[idx]);
|
||||||
|
editor.graph.focusItem(results[idx]);
|
||||||
|
selectNode(results[idx]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: "30px" }}
|
||||||
|
onClick={() => {
|
||||||
|
setShowingSearch(false);
|
||||||
|
setResults([]);
|
||||||
|
setResultIndex(0);
|
||||||
|
keysRef.current?.focus();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
<Dropdown menu={{ items: menuItems, onClick }} trigger={["contextMenu"]}>
|
<Dropdown menu={{ items: menuItems, onClick }} trigger={["contextMenu"]}>
|
||||||
<div tabIndex={-1} style={{ width: "100%", height: "100%" }} ref={graphRef} />
|
<div tabIndex={-1} style={{ width: "100%", height: "100%" }} ref={graphRef} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const Workspace: FC = () => {
|
|||||||
const { width = 0, height = 0 } = useWindowSize();
|
const { width = 0, height = 0 } = useWindowSize();
|
||||||
|
|
||||||
const keysRef = useHotkeys<HTMLDivElement>(
|
const keysRef = useHotkeys<HTMLDivElement>(
|
||||||
[Hotkey.Save, Hotkey.CloseEditor, Hotkey.Search, Hotkey.Build],
|
[Hotkey.Save, Hotkey.CloseEditor, Hotkey.SearchTree, Hotkey.Build],
|
||||||
(event) => {
|
(event) => {
|
||||||
if (isHotkeyPressed(Hotkey.Save)) {
|
if (isHotkeyPressed(Hotkey.Save)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -58,7 +58,7 @@ export const Workspace: FC = () => {
|
|||||||
workspace.close(workspace.editing.path);
|
workspace.close(workspace.editing.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isHotkeyPressed(Hotkey.Search)) {
|
} else if (isHotkeyPressed(Hotkey.SearchTree)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
workspace.onShowingSearch(true);
|
workspace.onShowingSearch(true);
|
||||||
} else if (isHotkeyPressed(Hotkey.Build)) {
|
} else if (isHotkeyPressed(Hotkey.Build)) {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type EditEvent =
|
|||||||
| "rename"
|
| "rename"
|
||||||
| "updateTree"
|
| "updateTree"
|
||||||
| "updateNode"
|
| "updateNode"
|
||||||
|
| "searchNode"
|
||||||
| "editSubtree"
|
| "editSubtree"
|
||||||
| "saveAsSubtree";
|
| "saveAsSubtree";
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -57,7 +57,8 @@ export const Hotkey = {
|
|||||||
Redo: isMacos ? hotkey("shift+ctrl+z") : hotkey("ctrl+y"),
|
Redo: isMacos ? hotkey("shift+ctrl+z") : hotkey("ctrl+y"),
|
||||||
Replace: hotkey("shift+ctrl+v"),
|
Replace: hotkey("shift+ctrl+v"),
|
||||||
Save: hotkey("ctrl+s"),
|
Save: hotkey("ctrl+s"),
|
||||||
Search: hotkey("ctrl+p"),
|
SearchTree: hotkey("ctrl+p"),
|
||||||
|
SearchNode: hotkey("ctrl+f"),
|
||||||
Undo: hotkey("ctrl+z"),
|
Undo: hotkey("ctrl+z"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user