diff --git a/package-lock.json b/package-lock.json index 7590aa9..9bf5649 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "behavior3editor", - "version": "1.1.2", + "version": "1.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "behavior3editor", - "version": "1.1.2", + "version": "1.1.3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 411038a..f0b5159 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "behavior3editor", - "version": "1.1.2", + "version": "1.1.3", "main": "dist-electron/main/index.js", "description": "行为树编辑器", "author": "zhandouxiaojiji & codetypes", diff --git a/src/components/editor.tsx b/src/components/editor.tsx index 93dfbd7..c79d6b4 100644 --- a/src/components/editor.tsx +++ b/src/components/editor.tsx @@ -10,17 +10,19 @@ import * as b3util from "@/misc/b3util"; import { message } from "@/misc/hooks"; import { Hotkey, isHotkeyPressed, isMacos, useHotkeys } from "@/misc/keys"; import Path from "@/misc/path"; +import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined } from "@ant-design/icons"; import G6, { G6GraphEvent, Item, TreeGraph } from "@antv/g6"; import { dialog } from "@electron/remote"; 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 * 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 { FiDelete } from "react-icons/fi"; import { IoMdReturnLeft } from "react-icons/io"; import { mergeRefs } from "react-merge-refs"; +import { useDebounceCallback } from "usehooks-ts"; import "./register-node"; export interface EditorProps extends React.HTMLAttributes { @@ -36,11 +38,27 @@ export const Editor: FC = ({ onUpdate: updateState, data: editor, . open: useWorkspace((state) => state.open), workdir: useWorkspace((state) => state.workdir), }; + const [results, setResults] = useState([]); + const [resultIndex, setResultIndex] = useState(0); + const [showingSearch, setShowingSearch] = useState(false); const graphRef = useRef(null); const sizeRef = useRef(null); const editorSize = useSize(sizeRef); 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 = () => { if (!editor.unsave) { editor.unsave = true; @@ -93,6 +111,17 @@ export const Editor: FC = ({ onUpdate: updateState, data: editor, . 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 => { if (ancestor.id === node.parent) { return true; @@ -761,6 +790,7 @@ export const Editor: FC = ({ onUpdate: updateState, data: editor, . Hotkey.Delete, Hotkey.Enter, Hotkey.Backspace, + Hotkey.SearchNode, ], (event) => { event.preventDefault(); @@ -778,6 +808,8 @@ export const Editor: FC = ({ onUpdate: updateState, data: editor, . createNode(); } else if (isHotkeyPressed(Hotkey.Delete) || isHotkeyPressed(Hotkey.Backspace)) { deleteNode(); + } else if (isHotkeyPressed(Hotkey.SearchNode)) { + setShowingSearch(true); } } ); @@ -967,6 +999,76 @@ export const Editor: FC = ({ onUpdate: updateState, data: editor, . tabIndex={-1} style={{ maxWidth: "inherit", maxHeight: "inherit" }} > + {showingSearch && ( + + onSearchChange(e.currentTarget.value)} + /> +
+ {results.length ? `${resultIndex + 1}/${results.length}` : ""} +
+