diff --git a/package-lock.json b/package-lock.json index 0c6fc145..4a4a4e73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@sentry/react": "^7.68.0", "@sentry/tracing": "^7.68.0", "antd": "^4.24.13", + "fuse.js": "^6.6.2", "kbar": "^0.1.0-beta.43", "lodash.debounce": "^4.0.8", "mlg-converter": "^0.9.0", diff --git a/package.json b/package.json index 044940ab..653b4aff 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@sentry/react": "^7.68.0", "@sentry/tracing": "^7.68.0", "antd": "^4.24.13", + "fuse.js": "^6.6.2", "kbar": "^0.1.0-beta.43", "lodash.debounce": "^4.0.8", "mlg-converter": "^0.9.0", diff --git a/src/pages/Logs.tsx b/src/pages/Logs.tsx index 3f813498..77485c39 100644 --- a/src/pages/Logs.tsx +++ b/src/pages/Logs.tsx @@ -12,6 +12,7 @@ import { Badge, Typography, Grid, + Input, } from 'antd'; import { FileTextOutlined, EditOutlined, GlobalOutlined } from '@ant-design/icons'; import { CheckboxValueType } from 'antd/es/checkbox/Group'; @@ -19,7 +20,6 @@ import { connect } from 'react-redux'; import { Result as ParserResult } from 'mlg-converter/dist/types'; import PerfectScrollbar from 'react-perfect-scrollbar'; import { OutputChannel, Logs as LogsType, DatalogEntry } from '@hyper-tuner/types'; - import LogParserWorker from '../workers/logParserWorker?worker'; import LogCanvas, { SelectedField } from '../components/Logs/LogCanvas'; import store from '../store'; @@ -35,11 +35,29 @@ import { removeFilenameSuffix } from '../pocketbase'; import { isAbortedRequest } from '../utils/error'; import { WorkerOutput } from '../workers/logParserWorker'; import { collapsedSidebarWidth, sidebarWidth } from '../components/Tune/SideBar'; +import Fuse from 'fuse.js'; +import debounce from 'lodash.debounce'; const { Content } = Layout; const edgeUnknown = 'Unknown'; const minCanvasHeightInner = 500; const badgeStyle = { backgroundColor: Colors.TEXT }; +const fieldsSectionStyle = { height: 'calc(50vh - 175px)' }; +const searchInputStyle = { + width: 'auto', + position: 'sticky' as const, + top: 0, + marginBottom: 10, +}; +const fuseOptions = { + shouldSort: true, + includeMatches: false, + includeScore: false, + ignoreLocation: false, + findAllMatches: false, + threshold: 0.4, + keys: ['label'], // TODO: handle expression +}; const mapStateToProps = (state: AppState) => ({ ui: state.ui, @@ -153,6 +171,18 @@ const Logs = ({ }, [config?.datalog, findOutputChannel, isConfigReady], ); + const [foundFields1, setFoundFields1] = useState([]); + const [foundFields2, setFoundFields2] = useState([]); + const fuse = new Fuse(fields, fuseOptions); + + const debounceSearch1 = debounce(async (searchText: string) => { + const result = fuse.search(searchText); + setFoundFields1(result.length > 0 ? result.map((item) => item.item) : fields); + }, 300); + const debounceSearch2 = debounce(async (searchText: string) => { + const result = fuse.search(searchText); + setFoundFields2(result.length > 0 ? result.map((item) => item.item) : fields); + }, 300); useEffect(() => { const worker = new LogParserWorker(); @@ -263,7 +293,10 @@ const Logs = ({ } if (config?.outputChannels) { - setFields(Object.values(config.datalog)); + const fields = Object.values(config.datalog); + setFields(fields); + setFoundFields1(fields); + setFoundFields2(fields); } calculateCanvasSize(); @@ -302,11 +335,24 @@ const Logs = ({ key: 'fields', children: ( <> -
+ debounceSearch1(target.value)} + style={searchInputStyle} + placeholder='Search fields...' + allowClear + /> +
{fields.map((field) => ( - +