From 124452a493b2b8f59f6e203d9ba479251a3f1f5a Mon Sep 17 00:00:00 2001 From: Danylo Hotskivskyi Date: Thu, 21 Nov 2024 09:55:45 +0100 Subject: [PATCH] Add side by side comparison feature --- .eslintrc.cjs | 40 +- .gitignore | 3 +- .prettierignore | 24 +- README.md | 2 +- .../developer-documentation/components/App.md | 118 +- .../components/ContextMenu.md | 118 +- .../components/FlagsDialog.md | 118 +- .../components/LogView.md | 286 +-- .../components/Minimap.md | 176 +- .../components/StatesDialog.md | 118 +- .../getting-started.md | 66 +- .../types/LogEntryCharMaps.md | 18 +- .../types/LogViewState.md | 48 +- docs/user-documentation/getting-started.md | 46 +- media/style.css | 2 +- package-lock.json | 1967 ++++++++++------- package.json | 16 +- src/extension/DefaultJsonConverter.ts | 42 + src/extension/TracyAPI.ts | 32 + src/extension/api/ITracyAPI.ts | 5 + src/extension/api/README.md | 54 + src/extension/api/converter/TracyConverter.ts | 8 + src/extension/api/converter/TracyLogFile.ts | 8 + src/extension/extension.ts | 466 ++-- src/viewer/App.tsx | 1069 ++++----- src/viewer/components/LogView.tsx | 378 ++++ src/viewer/components/LogViewAndMinimap.tsx | 183 ++ src/viewer/components/SearchBar.tsx | 280 +++ src/viewer/components/TracyIconButton.tsx | 39 + .../components/dialogs/ExportDialog.tsx | 75 + src/viewer/components/dialogs/FlagsDialog.tsx | 292 +++ .../components/dialogs/SelectColDialog.tsx | 133 ++ .../components/dialogs/StatesDialog.tsx | 248 +++ .../components/dialogs/StructureDialog.tsx | 689 ++++++ .../dialogs/structures/ContextMenu.tsx | 122 + .../structures/StructureSettingsDropdown.tsx | 77 + .../dialogs/structures/StructureTable.tsx | 262 +++ .../components/minimap/MinimapHeader.tsx | 59 + src/viewer/components/minimap/MinimapView.tsx | 240 ++ src/viewer/constants.ts | 148 +- src/viewer/enums.ts | 30 + src/viewer/hooks/useColor.ts | 5 + src/viewer/hooks/useColumnSelection.ts | 24 + src/viewer/hooks/useLogFile.ts | 93 + src/viewer/hooks/useLogSearchManager.ts | 139 +- src/viewer/hooks/useMessage.ts | 19 + src/viewer/hooks/useRowProperty.ts | 33 +- src/viewer/hooks/useStructureEntryManager.ts | 406 ++-- .../useStructureRegularExpressionManager.ts | 618 +++--- src/viewer/hooks/useStyleManager.ts | 724 +++--- src/viewer/hooks/useTracyLogData.ts | 48 + src/viewer/hooks/useVsCode.ts | 27 + src/viewer/hooks/useWildcardManager.tsx | 914 ++++---- src/viewer/interfaces.ts | 140 ++ src/viewer/lib/ColorMappingHandler.ts | 63 + src/viewer/lib/LogFile.ts | 102 + src/viewer/lib/RowSelectionHandler.ts | 53 + src/viewer/lib/SideBySideAlignmentHandler.ts | 61 + src/viewer/lib/columns/CachingColumn.ts | 24 + src/viewer/lib/columns/LineNumberingColumn.ts | 15 + src/viewer/lib/columns/RelativeTimeColumn.ts | 95 + src/viewer/rules/FlagRule.tsx | 9 +- src/viewer/rules/Rule.tsx | 12 +- src/viewer/rules/StateBasedRule.tsx | 7 +- src/viewer/types.ts | 5 + 65 files changed, 7918 insertions(+), 3823 deletions(-) create mode 100644 src/extension/DefaultJsonConverter.ts create mode 100644 src/extension/TracyAPI.ts create mode 100644 src/extension/api/ITracyAPI.ts create mode 100644 src/extension/api/README.md create mode 100644 src/extension/api/converter/TracyConverter.ts create mode 100644 src/extension/api/converter/TracyLogFile.ts create mode 100644 src/viewer/components/LogView.tsx create mode 100644 src/viewer/components/LogViewAndMinimap.tsx create mode 100644 src/viewer/components/SearchBar.tsx create mode 100644 src/viewer/components/TracyIconButton.tsx create mode 100644 src/viewer/components/dialogs/ExportDialog.tsx create mode 100644 src/viewer/components/dialogs/FlagsDialog.tsx create mode 100644 src/viewer/components/dialogs/SelectColDialog.tsx create mode 100644 src/viewer/components/dialogs/StatesDialog.tsx create mode 100644 src/viewer/components/dialogs/StructureDialog.tsx create mode 100644 src/viewer/components/dialogs/structures/ContextMenu.tsx create mode 100644 src/viewer/components/dialogs/structures/StructureSettingsDropdown.tsx create mode 100644 src/viewer/components/dialogs/structures/StructureTable.tsx create mode 100644 src/viewer/components/minimap/MinimapHeader.tsx create mode 100644 src/viewer/components/minimap/MinimapView.tsx create mode 100644 src/viewer/enums.ts create mode 100644 src/viewer/hooks/useColor.ts create mode 100644 src/viewer/hooks/useColumnSelection.ts create mode 100644 src/viewer/hooks/useLogFile.ts create mode 100644 src/viewer/hooks/useMessage.ts create mode 100644 src/viewer/hooks/useTracyLogData.ts create mode 100644 src/viewer/hooks/useVsCode.ts create mode 100644 src/viewer/interfaces.ts create mode 100644 src/viewer/lib/ColorMappingHandler.ts create mode 100644 src/viewer/lib/LogFile.ts create mode 100644 src/viewer/lib/RowSelectionHandler.ts create mode 100644 src/viewer/lib/SideBySideAlignmentHandler.ts create mode 100644 src/viewer/lib/columns/CachingColumn.ts create mode 100644 src/viewer/lib/columns/LineNumberingColumn.ts create mode 100644 src/viewer/lib/columns/RelativeTimeColumn.ts create mode 100644 src/viewer/types.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 34f0070..710f833 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,21 +1,21 @@ -/* eslint-env node */ -module.exports = { - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', "plugin:react/recommended"], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', "react"], - root: true, - rules: { - "no-useless-escape": "off", - "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "variable", - "format": ["camelCase","PascalCase","UPPER_CASE"] - } - ], - 'react/jsx-uses-react': "error", - 'react/jsx-uses-vars': "error", - } +/* eslint-env node */ +module.exports = { + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', "plugin:react/recommended"], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', "react"], + root: true, + rules: { + "no-useless-escape": "off", + "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "variable", + "format": ["camelCase","PascalCase","UPPER_CASE"] + } + ], + 'react/jsx-uses-react': "error", + 'react/jsx-uses-vars': "error", + } }; \ No newline at end of file diff --git a/.gitignore b/.gitignore index d992089..b9a8ffe 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ out/ workspace/ *.vsix -examples/*.tracy.json \ No newline at end of file +examples/*.tracy.json +!log/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 8947633..de8e251 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,13 +1,13 @@ -node_modules/ -out/ -workspace/ -media/ -examples/ -.eslintrc.cjs -.github/ -.vscode/ -*.vsix -README.md -webpack.config.ts -tsconfig.json +node_modules/ +out/ +workspace/ +media/ +examples/ +.eslintrc.cjs +.github/ +.vscode/ +*.vsix +README.md +webpack.config.ts +tsconfig.json src/extension \ No newline at end of file diff --git a/README.md b/README.md index 40418c5..dfb2ac5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Tracy is a Visual Studio Code extension for the analysis of logs. It is meant as ## Installation -To install Tracy in Visual Studio Code: +To install Tracy in Visual Studio Code: test 1. Obtain the plugin `.vsix` file: - If you want to install the latest release, go to the [Latest release](https://github.com/TNO/vscode-tracy/releases/latest) and download the `vscode-tracy-X.X.X.vsix` file under *Assests*. - If you want to install a specific commit, click on the :heavy_check_mark: next to the commit -> *Details* -> *Summary* -> under *Artifacts*, *vscode-vsix* and extract the downloaded `vscode-vsix.zip`. diff --git a/docs/developer-documentation/components/App.md b/docs/developer-documentation/components/App.md index c01cacd..fe6695f 100644 --- a/docs/developer-documentation/components/App.md +++ b/docs/developer-documentation/components/App.md @@ -1,59 +1,59 @@ -# Component name -(Screenshot of component - Optional) - ---- -(Short description) - -## Relations to other components - -- **Parent:** ParentComponent -- **Children:** - - Child1 - - Child2 - -## Props - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `prop1` | `type1` | short description | -| `prop2` | `type2` | short description | -| `prop3` | `type3` | short description | - -## State - -| Name | Type | Initial Value | Description | -| ---- | ---- | ------------- | ----------- | -| `stateObj1` | `type1` | `init value` | short description | -| `stateObj2` | `type1` | `init value` | short description | -| `stateObj3` | `type1` | `init value` | short description | - -## Functions -### Component lifecycle functions -- ### `constructor(...)` - - **Params:** - - `props: Props` - - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. - - **Returns:** - - -- ### `shouldComponentUpdate(...)` - - **Params:** - - `nextProps: Readonly` - - `nextState: Readonly` - - `nextContext: any` - - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. - - **Returns:** `boolean` - -- ### `render()` - - **Description:** - - **Returns:** Div of type `JSX.Element` containing.... - -### Functionality-related functions -- ### `exampleFunctionWithNoParams()` - - **Description:** short description of what happens in the function. - - **Returns:** - - -- ### `exampleFunctionWithParams(...)` - - **Params:** - - `name: type` - - **Description:** short description of what happens in the function. - - **Returns:** - +# Component name +(Screenshot of component - Optional) + +--- +(Short description) + +## Relations to other components + +- **Parent:** ParentComponent +- **Children:** + - Child1 + - Child2 + +## Props + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `prop1` | `type1` | short description | +| `prop2` | `type2` | short description | +| `prop3` | `type3` | short description | + +## State + +| Name | Type | Initial Value | Description | +| ---- | ---- | ------------- | ----------- | +| `stateObj1` | `type1` | `init value` | short description | +| `stateObj2` | `type1` | `init value` | short description | +| `stateObj3` | `type1` | `init value` | short description | + +## Functions +### Component lifecycle functions +- ### `constructor(...)` + - **Params:** + - `props: Props` + - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. + - **Returns:** - + +- ### `shouldComponentUpdate(...)` + - **Params:** + - `nextProps: Readonly` + - `nextState: Readonly` + - `nextContext: any` + - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. + - **Returns:** `boolean` + +- ### `render()` + - **Description:** + - **Returns:** Div of type `JSX.Element` containing.... + +### Functionality-related functions +- ### `exampleFunctionWithNoParams()` + - **Description:** short description of what happens in the function. + - **Returns:** - + +- ### `exampleFunctionWithParams(...)` + - **Params:** + - `name: type` + - **Description:** short description of what happens in the function. + - **Returns:** - diff --git a/docs/developer-documentation/components/ContextMenu.md b/docs/developer-documentation/components/ContextMenu.md index c01cacd..fe6695f 100644 --- a/docs/developer-documentation/components/ContextMenu.md +++ b/docs/developer-documentation/components/ContextMenu.md @@ -1,59 +1,59 @@ -# Component name -(Screenshot of component - Optional) - ---- -(Short description) - -## Relations to other components - -- **Parent:** ParentComponent -- **Children:** - - Child1 - - Child2 - -## Props - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `prop1` | `type1` | short description | -| `prop2` | `type2` | short description | -| `prop3` | `type3` | short description | - -## State - -| Name | Type | Initial Value | Description | -| ---- | ---- | ------------- | ----------- | -| `stateObj1` | `type1` | `init value` | short description | -| `stateObj2` | `type1` | `init value` | short description | -| `stateObj3` | `type1` | `init value` | short description | - -## Functions -### Component lifecycle functions -- ### `constructor(...)` - - **Params:** - - `props: Props` - - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. - - **Returns:** - - -- ### `shouldComponentUpdate(...)` - - **Params:** - - `nextProps: Readonly` - - `nextState: Readonly` - - `nextContext: any` - - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. - - **Returns:** `boolean` - -- ### `render()` - - **Description:** - - **Returns:** Div of type `JSX.Element` containing.... - -### Functionality-related functions -- ### `exampleFunctionWithNoParams()` - - **Description:** short description of what happens in the function. - - **Returns:** - - -- ### `exampleFunctionWithParams(...)` - - **Params:** - - `name: type` - - **Description:** short description of what happens in the function. - - **Returns:** - +# Component name +(Screenshot of component - Optional) + +--- +(Short description) + +## Relations to other components + +- **Parent:** ParentComponent +- **Children:** + - Child1 + - Child2 + +## Props + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `prop1` | `type1` | short description | +| `prop2` | `type2` | short description | +| `prop3` | `type3` | short description | + +## State + +| Name | Type | Initial Value | Description | +| ---- | ---- | ------------- | ----------- | +| `stateObj1` | `type1` | `init value` | short description | +| `stateObj2` | `type1` | `init value` | short description | +| `stateObj3` | `type1` | `init value` | short description | + +## Functions +### Component lifecycle functions +- ### `constructor(...)` + - **Params:** + - `props: Props` + - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. + - **Returns:** - + +- ### `shouldComponentUpdate(...)` + - **Params:** + - `nextProps: Readonly` + - `nextState: Readonly` + - `nextContext: any` + - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. + - **Returns:** `boolean` + +- ### `render()` + - **Description:** + - **Returns:** Div of type `JSX.Element` containing.... + +### Functionality-related functions +- ### `exampleFunctionWithNoParams()` + - **Description:** short description of what happens in the function. + - **Returns:** - + +- ### `exampleFunctionWithParams(...)` + - **Params:** + - `name: type` + - **Description:** short description of what happens in the function. + - **Returns:** - diff --git a/docs/developer-documentation/components/FlagsDialog.md b/docs/developer-documentation/components/FlagsDialog.md index c01cacd..fe6695f 100644 --- a/docs/developer-documentation/components/FlagsDialog.md +++ b/docs/developer-documentation/components/FlagsDialog.md @@ -1,59 +1,59 @@ -# Component name -(Screenshot of component - Optional) - ---- -(Short description) - -## Relations to other components - -- **Parent:** ParentComponent -- **Children:** - - Child1 - - Child2 - -## Props - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `prop1` | `type1` | short description | -| `prop2` | `type2` | short description | -| `prop3` | `type3` | short description | - -## State - -| Name | Type | Initial Value | Description | -| ---- | ---- | ------------- | ----------- | -| `stateObj1` | `type1` | `init value` | short description | -| `stateObj2` | `type1` | `init value` | short description | -| `stateObj3` | `type1` | `init value` | short description | - -## Functions -### Component lifecycle functions -- ### `constructor(...)` - - **Params:** - - `props: Props` - - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. - - **Returns:** - - -- ### `shouldComponentUpdate(...)` - - **Params:** - - `nextProps: Readonly` - - `nextState: Readonly` - - `nextContext: any` - - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. - - **Returns:** `boolean` - -- ### `render()` - - **Description:** - - **Returns:** Div of type `JSX.Element` containing.... - -### Functionality-related functions -- ### `exampleFunctionWithNoParams()` - - **Description:** short description of what happens in the function. - - **Returns:** - - -- ### `exampleFunctionWithParams(...)` - - **Params:** - - `name: type` - - **Description:** short description of what happens in the function. - - **Returns:** - +# Component name +(Screenshot of component - Optional) + +--- +(Short description) + +## Relations to other components + +- **Parent:** ParentComponent +- **Children:** + - Child1 + - Child2 + +## Props + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `prop1` | `type1` | short description | +| `prop2` | `type2` | short description | +| `prop3` | `type3` | short description | + +## State + +| Name | Type | Initial Value | Description | +| ---- | ---- | ------------- | ----------- | +| `stateObj1` | `type1` | `init value` | short description | +| `stateObj2` | `type1` | `init value` | short description | +| `stateObj3` | `type1` | `init value` | short description | + +## Functions +### Component lifecycle functions +- ### `constructor(...)` + - **Params:** + - `props: Props` + - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. + - **Returns:** - + +- ### `shouldComponentUpdate(...)` + - **Params:** + - `nextProps: Readonly` + - `nextState: Readonly` + - `nextContext: any` + - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. + - **Returns:** `boolean` + +- ### `render()` + - **Description:** + - **Returns:** Div of type `JSX.Element` containing.... + +### Functionality-related functions +- ### `exampleFunctionWithNoParams()` + - **Description:** short description of what happens in the function. + - **Returns:** - + +- ### `exampleFunctionWithParams(...)` + - **Params:** + - `name: type` + - **Description:** short description of what happens in the function. + - **Returns:** - diff --git a/docs/developer-documentation/components/LogView.md b/docs/developer-documentation/components/LogView.md index 048ac5e..210290e 100644 --- a/docs/developer-documentation/components/LogView.md +++ b/docs/developer-documentation/components/LogView.md @@ -1,143 +1,143 @@ -# Component name -(Screenshot of component - Optional) - -![](../../figures/Logview.PNG) - ---- -This component contains the table that shows the content of the log. Each row shows one log record and each column shows the value of the corresponding column. - -## Relations to other components - -- **Parent:** App - -## Props - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `LogFile` | `LogFile` | it contains the content of the input log/logs | -| `onLogViewStateChanged` | `function` | A function to update logview state to the parent component | -| `onSelectedRowsChanged` | `function` | A function to pass the selected row to the parent component when it is changed | -| `onRowPropsChanged` | `function` | A function to pass the property of the row when it is changed | -| `forwardRef` | `React.RefObject` | A ref object for update the scrolling between log view and minimap | -| `coloredTable` | `boolean` | boolean for switching the colored table On/Off | -| `rowProperties` | `RowProperty[]` | A interface to keep all the row related properties | -| `currentStructureMatch` | `number[]` | The current selected structure | -| `structureMatches` | `number[][]` | All the matched structures | -| `structureMatchesLogRows` | `number[]` | The row number of all the matched structures | -| `collapsibleRows` | `{ [key: number]: Segment }` | A map for the segment annotation, the key is the number of row where the segment starts, the value is the interface Segment contains the start row, end row and the nested level | -| `clearSegmentation` | `function` | A function to delete all the segmentations | - -## State - -| Name | Type | Initial Value | Description | -| ---- | ---- | ------------- | ----------- | -| `state` | `LogViewState` | `undefined` | A state to keep several values for representing the log view | -| `columnWidth` | `{ [id: string]: number }` | `LOG_COLUMN_WIDTH_LOOKUP` | A map to keep the width of each column | -| `logFile` | `LogFile` | `this.props.logFile` | it contains the content of the input log/logs | -| `collapsed` | `{ [key: number]: boolean }` | `[]` | A map to keep tracking if the segmentation is collapsed | - -## Functions -### Component lifecycle functions -- ### `constructor(...)` - - **Params:** - - `props: Props` - - **Description:** bind the RefObject and the log view state; initialize the states; - - **Returns:** - - -- ### `componentDidMount(...)` - - **Description:** add event listener for window resize. - -- ### `componentDidUpdate(...)` - - **Params:** - - `nextProps: Readonly` - - `nextState: Readonly` - - `nextContext: any` - - **Description:** check whether the log file or currentStructureMatch is changed. If so, call the updateState() function. - - **Returns:** - - -- ### `renderColumn(...)` - - **Params:** - - `value: string` - - `columnIndex: number` - - `isHeader: boolean` - - `width: number` - - `colorMap: string` - - **Description:** This function returns the Div for a single column. - - **Returns:** Div of type `JSX.Element` - -- ### `renderRows()` - - **Description:** return the Div for all the visible rows and its segmentations. - - **Returns:** Div of type `JSX.Element` - -- ### `renderSegmentForRow(...)` - - **Params:** - - `r: number` - - `level: number` - - **Description:** This function returns the Div for the segmentation of a single row. - - **Returns:** Div of type `JSX.Element` - -- ### `renderHeader(...)` - - **Params:** - - `width: number` - - **Description:** This function returns the Div for the header. - - **Returns:** Div of type `JSX.Element` - -- ### `renderHeaderColumn(...)` - - **Params:** - - `value: string` - - `columnIndex: number` - - `isHeader: boolean` - - `width: number` - - **Description:** This function returns the Div for each column in the header. - - **Returns:** Div of type `JSX.Element` - -- ### `render()` - - **Description:** This function renders the log view. - - **Returns:** Div of type `JSX.Element` - -### Functionality-related functions - -- ### `collapseRows(...)` - - **Params:** - - `index: number` - - **Description:** This function updates the state `collapsed` - - **Returns:** A new map for `collapsed` - -- ### `updateState(...)` - - **Params:** - - `currentStructureMatchFirstRow: StructureMatchId` - - **Description:** This function updates the log view state - - **Returns:** - - -- ### `setColumnWidth(...)` - - **Params:** - - `name: string` - - `width: number` - - **Description:** This function updates the column width. - - **Returns:** - - -- ### `columnWidth(...)` - - **Params:** - - `name: string` - - **Description:** This function returns default width for column. - - **Returns:** - - -- ### `isLight(...)` - - **Params:** - - `color: string` - - **Description:** This function returns if the given color is light/dark. - - **Returns:** `boolean` - -- ### `getRGB(...)` - - **Params:** - - `level: number` - - **Description:** This function returns the corresponding color for the given level of Segmentation. - - **Returns:** RGB color - -- ### `getVisibleRows()` - - **Description:** This function returns the number of visiable rows. - - **Returns:** `number` - -- ### `deleteSegmentAnnotations()` - - **Description:** This function clears all the segmentations. - - **Returns:** - +# Component name +(Screenshot of component - Optional) + +![](../../figures/Logview.PNG) + +--- +This component contains the table that shows the content of the log. Each row shows one log record and each column shows the value of the corresponding column. + +## Relations to other components + +- **Parent:** App + +## Props + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `LogFile` | `LogFile` | it contains the content of the input log/logs | +| `onLogViewStateChanged` | `function` | A function to update logview state to the parent component | +| `onSelectedRowsChanged` | `function` | A function to pass the selected row to the parent component when it is changed | +| `onRowPropsChanged` | `function` | A function to pass the property of the row when it is changed | +| `forwardRef` | `React.RefObject` | A ref object for update the scrolling between log view and minimap | +| `coloredTable` | `boolean` | boolean for switching the colored table On/Off | +| `rowProperties` | `RowProperty[]` | A interface to keep all the row related properties | +| `currentStructureMatch` | `number[]` | The current selected structure | +| `structureMatches` | `number[][]` | All the matched structures | +| `structureMatchesLogRows` | `number[]` | The row number of all the matched structures | +| `collapsibleRows` | `{ [key: number]: Segment }` | A map for the segment annotation, the key is the number of row where the segment starts, the value is the interface Segment contains the start row, end row and the nested level | +| `clearSegmentation` | `function` | A function to delete all the segmentations | + +## State + +| Name | Type | Initial Value | Description | +| ---- | ---- | ------------- | ----------- | +| `state` | `LogViewState` | `undefined` | A state to keep several values for representing the log view | +| `columnWidth` | `{ [id: string]: number }` | `LOG_COLUMN_WIDTH_LOOKUP` | A map to keep the width of each column | +| `logFile` | `LogFile` | `this.props.logFile` | it contains the content of the input log/logs | +| `collapsed` | `{ [key: number]: boolean }` | `[]` | A map to keep tracking if the segmentation is collapsed | + +## Functions +### Component lifecycle functions +- ### `constructor(...)` + - **Params:** + - `props: Props` + - **Description:** bind the RefObject and the log view state; initialize the states; + - **Returns:** - + +- ### `componentDidMount(...)` + - **Description:** add event listener for window resize. + +- ### `componentDidUpdate(...)` + - **Params:** + - `nextProps: Readonly` + - `nextState: Readonly` + - `nextContext: any` + - **Description:** check whether the log file or currentStructureMatch is changed. If so, call the updateState() function. + - **Returns:** - + +- ### `renderColumn(...)` + - **Params:** + - `value: string` + - `columnIndex: number` + - `isHeader: boolean` + - `width: number` + - `colorMap: string` + - **Description:** This function returns the Div for a single column. + - **Returns:** Div of type `JSX.Element` + +- ### `renderRows()` + - **Description:** return the Div for all the visible rows and its segmentations. + - **Returns:** Div of type `JSX.Element` + +- ### `renderSegmentForRow(...)` + - **Params:** + - `r: number` + - `level: number` + - **Description:** This function returns the Div for the segmentation of a single row. + - **Returns:** Div of type `JSX.Element` + +- ### `renderHeader(...)` + - **Params:** + - `width: number` + - **Description:** This function returns the Div for the header. + - **Returns:** Div of type `JSX.Element` + +- ### `renderHeaderColumn(...)` + - **Params:** + - `value: string` + - `columnIndex: number` + - `isHeader: boolean` + - `width: number` + - **Description:** This function returns the Div for each column in the header. + - **Returns:** Div of type `JSX.Element` + +- ### `render()` + - **Description:** This function renders the log view. + - **Returns:** Div of type `JSX.Element` + +### Functionality-related functions + +- ### `collapseRows(...)` + - **Params:** + - `index: number` + - **Description:** This function updates the state `collapsed` + - **Returns:** A new map for `collapsed` + +- ### `updateState(...)` + - **Params:** + - `currentStructureMatchFirstRow: StructureMatchId` + - **Description:** This function updates the log view state + - **Returns:** - + +- ### `setColumnWidth(...)` + - **Params:** + - `name: string` + - `width: number` + - **Description:** This function updates the column width. + - **Returns:** - + +- ### `columnWidth(...)` + - **Params:** + - `name: string` + - **Description:** This function returns default width for column. + - **Returns:** - + +- ### `isLight(...)` + - **Params:** + - `color: string` + - **Description:** This function returns if the given color is light/dark. + - **Returns:** `boolean` + +- ### `getRGB(...)` + - **Params:** + - `level: number` + - **Description:** This function returns the corresponding color for the given level of Segmentation. + - **Returns:** RGB color + +- ### `getVisibleRows()` + - **Description:** This function returns the number of visiable rows. + - **Returns:** `number` + +- ### `deleteSegmentAnnotations()` + - **Description:** This function clears all the segmentations. + - **Returns:** - diff --git a/docs/developer-documentation/components/Minimap.md b/docs/developer-documentation/components/Minimap.md index 534f67a..5f9bf0d 100644 --- a/docs/developer-documentation/components/Minimap.md +++ b/docs/developer-documentation/components/Minimap.md @@ -1,88 +1,88 @@ -# Minimap View -(Screenshot of component - Optional) - -![](../../figures/minimap.PNG) - ---- -This component contains the colored map which visualizes the log contents according to the value in every cell. - -## Relations to other components - -- **Parent:** App - -## Props - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `logFile` | `LogFile` | it contains the content of the input log/logs | -| `logViewState` | `LogViewState` | A state to keep several values for representing the log view | -| `onLogViewStateChanged` | `function` | A function that update the log view state to other components | -| `forwardRef` | `React.RefObject` | A ref object for update the scrolling between log view and minimap | -| `rowProperties` | `RowProperty[]` | A interface to keep all the row related properties | - -## State - -| Name | Type | Initial Value | Description | -| ---- | ---- | ------------- | ----------- | -| `state` | `LogViewState` | `undefined` | A state to keep several values for representing the log view | -| `scale` | `number` | `1` | the scale of the Mini map. If it is 1, then minimap 1: 1 matches the log view. If it is 0, minimap matches all the log view. | -| `controlDown` | `boolean` | `false` | A state represents whether the ctrl button is pressed | - -## Functions -### Component lifecycle functions - -- ### `constructor(...)` - - **Params:** - - `props: Props` - - **Description:** initializes the minimap canvas and the states; bind handlers for wheel and click actions; - - **Returns:** - -- ### `componentDidMount(...)` - - **Description:** add three events listener for window resize, the ctrl key down and up. - -- ### `componentDidUpdate(...)` - - **Params:** - - `nextProps: Readonly` - - `nextState: Readonly` - - `nextContext: any` - - **Description:** check whether the logViewState, scale or the logFile is changed. If so, call the draw() function. - - **Returns:** - - -- ### `render()` - - **Description:** renders the minimap canvas - - **Returns:** div of type `JSX.Element` - -### Functionality-related functions - -- ### `draw()` - - **Description:** draw the minimap and the grey block. - - **Returns:** - - -- ### `handleClick(...)` - - **Params:** - - `e: React.MouseEvent` - - **Description:** A function to handle the click action on a random point on the canvas, the log view will jump to that point. - - **Returns:** - - -- ### `handleWheel(...)` - - **Params:** - - `e: React.WheelEvent` - - **Description:** A function to handle scroll action on the canvas. Depends on whether "ctrl" is pressed, it will either scroll the minimap or Zoom In/Out. - - **Returns:** - - -- ### `updateState(...)` - - **Params:** - - `scale: number` - - **Description:** A function to update the log view state when the scale is changed by zooming in/out the minimap. - - **Returns:** - - -- ### `controlDownListener(...)` - - **Params:** - - `e: any` - - **Description:** A function triggered by key down event of "ctrl", it will set the state controlDown to true. - - **Returns:** - - -- ### `controlUpListener(...)` - - **Params:** - - `e: any` - - **Description:** A function triggered by key up event of "ctrl", it will set the state controlDown to false. - - **Returns:** - +# Minimap View +(Screenshot of component - Optional) + +![](../../figures/minimap.PNG) + +--- +This component contains the colored map which visualizes the log contents according to the value in every cell. + +## Relations to other components + +- **Parent:** App + +## Props + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `logFile` | `LogFile` | it contains the content of the input log/logs | +| `logViewState` | `LogViewState` | A state to keep several values for representing the log view | +| `onLogViewStateChanged` | `function` | A function that update the log view state to other components | +| `forwardRef` | `React.RefObject` | A ref object for update the scrolling between log view and minimap | +| `rowProperties` | `RowProperty[]` | A interface to keep all the row related properties | + +## State + +| Name | Type | Initial Value | Description | +| ---- | ---- | ------------- | ----------- | +| `state` | `LogViewState` | `undefined` | A state to keep several values for representing the log view | +| `scale` | `number` | `1` | the scale of the Mini map. If it is 1, then minimap 1: 1 matches the log view. If it is 0, minimap matches all the log view. | +| `controlDown` | `boolean` | `false` | A state represents whether the ctrl button is pressed | + +## Functions +### Component lifecycle functions + +- ### `constructor(...)` + - **Params:** + - `props: Props` + - **Description:** initializes the minimap canvas and the states; bind handlers for wheel and click actions; + - **Returns:** - +- ### `componentDidMount(...)` + - **Description:** add three events listener for window resize, the ctrl key down and up. + +- ### `componentDidUpdate(...)` + - **Params:** + - `nextProps: Readonly` + - `nextState: Readonly` + - `nextContext: any` + - **Description:** check whether the logViewState, scale or the logFile is changed. If so, call the draw() function. + - **Returns:** - + +- ### `render()` + - **Description:** renders the minimap canvas + - **Returns:** div of type `JSX.Element` + +### Functionality-related functions + +- ### `draw()` + - **Description:** draw the minimap and the grey block. + - **Returns:** - + +- ### `handleClick(...)` + - **Params:** + - `e: React.MouseEvent` + - **Description:** A function to handle the click action on a random point on the canvas, the log view will jump to that point. + - **Returns:** - + +- ### `handleWheel(...)` + - **Params:** + - `e: React.WheelEvent` + - **Description:** A function to handle scroll action on the canvas. Depends on whether "ctrl" is pressed, it will either scroll the minimap or Zoom In/Out. + - **Returns:** - + +- ### `updateState(...)` + - **Params:** + - `scale: number` + - **Description:** A function to update the log view state when the scale is changed by zooming in/out the minimap. + - **Returns:** - + +- ### `controlDownListener(...)` + - **Params:** + - `e: any` + - **Description:** A function triggered by key down event of "ctrl", it will set the state controlDown to true. + - **Returns:** - + +- ### `controlUpListener(...)` + - **Params:** + - `e: any` + - **Description:** A function triggered by key up event of "ctrl", it will set the state controlDown to false. + - **Returns:** - diff --git a/docs/developer-documentation/components/StatesDialog.md b/docs/developer-documentation/components/StatesDialog.md index c01cacd..fe6695f 100644 --- a/docs/developer-documentation/components/StatesDialog.md +++ b/docs/developer-documentation/components/StatesDialog.md @@ -1,59 +1,59 @@ -# Component name -(Screenshot of component - Optional) - ---- -(Short description) - -## Relations to other components - -- **Parent:** ParentComponent -- **Children:** - - Child1 - - Child2 - -## Props - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `prop1` | `type1` | short description | -| `prop2` | `type2` | short description | -| `prop3` | `type3` | short description | - -## State - -| Name | Type | Initial Value | Description | -| ---- | ---- | ------------- | ----------- | -| `stateObj1` | `type1` | `init value` | short description | -| `stateObj2` | `type1` | `init value` | short description | -| `stateObj3` | `type1` | `init value` | short description | - -## Functions -### Component lifecycle functions -- ### `constructor(...)` - - **Params:** - - `props: Props` - - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. - - **Returns:** - - -- ### `shouldComponentUpdate(...)` - - **Params:** - - `nextProps: Readonly` - - `nextState: Readonly` - - `nextContext: any` - - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. - - **Returns:** `boolean` - -- ### `render()` - - **Description:** - - **Returns:** Div of type `JSX.Element` containing.... - -### Functionality-related functions -- ### `exampleFunctionWithNoParams()` - - **Description:** short description of what happens in the function. - - **Returns:** - - -- ### `exampleFunctionWithParams(...)` - - **Params:** - - `name: type` - - **Description:** short description of what happens in the function. - - **Returns:** - +# Component name +(Screenshot of component - Optional) + +--- +(Short description) + +## Relations to other components + +- **Parent:** ParentComponent +- **Children:** + - Child1 + - Child2 + +## Props + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `prop1` | `type1` | short description | +| `prop2` | `type2` | short description | +| `prop3` | `type3` | short description | + +## State + +| Name | Type | Initial Value | Description | +| ---- | ---- | ------------- | ----------- | +| `stateObj1` | `type1` | `init value` | short description | +| `stateObj2` | `type1` | `init value` | short description | +| `stateObj3` | `type1` | `init value` | short description | + +## Functions +### Component lifecycle functions +- ### `constructor(...)` + - **Params:** + - `props: Props` + - **Description:** Is invoked the first time the `StructureDialog` is opened. It constructs an array containing [StructureEntries](..\Types\StructureEntry.md) from the `logSelectedRows` props and updates the state accordingly. + - **Returns:** - + +- ### `shouldComponentUpdate(...)` + - **Params:** + - `nextProps: Readonly` + - `nextState: Readonly` + - `nextContext: any` + - **Description:** This function returns a `boolean` value that indicates whether or not rendering should be skipped. It returns `true` if ..., and ... otherwise. + - **Returns:** `boolean` + +- ### `render()` + - **Description:** + - **Returns:** Div of type `JSX.Element` containing.... + +### Functionality-related functions +- ### `exampleFunctionWithNoParams()` + - **Description:** short description of what happens in the function. + - **Returns:** - + +- ### `exampleFunctionWithParams(...)` + - **Params:** + - `name: type` + - **Description:** short description of what happens in the function. + - **Returns:** - diff --git a/docs/developer-documentation/getting-started.md b/docs/developer-documentation/getting-started.md index 3169ce4..72efded 100644 --- a/docs/developer-documentation/getting-started.md +++ b/docs/developer-documentation/getting-started.md @@ -1,33 +1,33 @@ -# Getting started - -## Prerequesites -- [Node.js](https://nodejs.org/en/) (version 18+ recommended) -- [Git](https://git-scm.com/) -- [Visual Studio Code](https://code.visualstudio.com/) - -## Useful links -- [Architecture](architecture.md) -- [Developer Guidelines](dev-guidlines.md) -- [Debugging](debugging.md) - -## Developing -To develop Tracy: - -1. Execute: - ```bash - # Clone the repository - git clone https://github.com/TNO/vscode-tracy.git - cd vscode-tracy - # Install dependencies - npm ci - # Open the repository in Visual Studio Code - code . - # Start the viewer in watch mode such that changes to src/viewer/* are applied on-the-fly - npm run watch-viewer - ``` -1. In Vistual Studio Code, go to *Run* (menu bar) -> *Start Debugging*. A new Visual Studio Code instance (*Extension Development Host*) will be started with Tracy installed. Open a `*.tracy.json` file to see the viewer. - - Changes made to `src/viewer/*` are applied on-the-fly as long as `npm run watch-viewer` from the previous step is running. You only need to close the viewer and re-open the `*.tracy.json` file. - - Changes made to `src/extension/*` are **NOT** applied on-the-fly, to apply them go to *Run* (menu bar) -> *Restart Debugging*. - -## Creating a new release -To create a new release, go to the [CI GitHub action](https://github.com/TNO/vscode-tracy/actions/workflows/ci.yml) -> *Run workflow* -> adjust type accordingly -> *Run workflow*. Wait till build completes and add the [release notes](https://github.com/TNO/vscode-tracy/releases/latest). +# Getting started + +## Prerequesites +- [Node.js](https://nodejs.org/en/) (version 18+ recommended) +- [Git](https://git-scm.com/) +- [Visual Studio Code](https://code.visualstudio.com/) + +## Useful links +- [Architecture](architecture.md) +- [Developer Guidelines](dev-guidlines.md) +- [Debugging](debugging.md) + +## Developing +To develop Tracy: + +1. Execute: + ```bash + # Clone the repository + git clone https://github.com/TNO/vscode-tracy.git + cd vscode-tracy + # Install dependencies + npm ci + # Open the repository in Visual Studio Code + code . + # Start the viewer in watch mode such that changes to src/viewer/* are applied on-the-fly + npm run watch-viewer + ``` +1. In Vistual Studio Code, go to *Run* (menu bar) -> *Start Debugging*. A new Visual Studio Code instance (*Extension Development Host*) will be started with Tracy installed. Open a `*.tracy.json` file to see the viewer. + - Changes made to `src/viewer/*` are applied on-the-fly as long as `npm run watch-viewer` from the previous step is running. You only need to close the viewer and re-open the `*.tracy.json` file. + - Changes made to `src/extension/*` are **NOT** applied on-the-fly, to apply them go to *Run* (menu bar) -> *Restart Debugging*. + +## Creating a new release +To create a new release, go to the [CI GitHub action](https://github.com/TNO/vscode-tracy/actions/workflows/ci.yml) -> *Run workflow* -> adjust type accordingly -> *Run workflow*. Wait till build completes and add the [release notes](https://github.com/TNO/vscode-tracy/releases/latest). diff --git a/docs/developer-documentation/types/LogEntryCharMaps.md b/docs/developer-documentation/types/LogEntryCharMaps.md index 2bd7e5d..9800772 100644 --- a/docs/developer-documentation/types/LogEntryCharMaps.md +++ b/docs/developer-documentation/types/LogEntryCharMaps.md @@ -1,10 +1,10 @@ -# Name - -```TS -interface LogEntryCharMaps { - firstCharIndexMap; - lastCharIndexMap; -} -``` - +# Name + +```TS +interface LogEntryCharMaps { + firstCharIndexMap; + lastCharIndexMap; +} +``` + This type is used to represent an object containing two JavaScript [`Map`(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). These maps store the first and last char indices of each log entry. These indices are used for the Segment Annotation and Structure Matching features and Search (with Regular Expressions). \ No newline at end of file diff --git a/docs/developer-documentation/types/LogViewState.md b/docs/developer-documentation/types/LogViewState.md index aea1d2e..156e20a 100644 --- a/docs/developer-documentation/types/LogViewState.md +++ b/docs/developer-documentation/types/LogViewState.md @@ -1,24 +1,24 @@ -# LogViewState ---- -```TS -interface LogViewState { - height: number, - start: number, - visibleItems: number, - startFloor: number, - endCeil: number, - scrollTop: number, - scrollLeft: number, - rowHeight: number -} -``` -This interface contains all the values for rendering the log view. - -- `height` - the height of the log view canvas, it is an integer (px). -- `start` - this value represents (approximately) where the current log view starts according to the entire log. The value is calculated by scrollTop/rowHeight, it is a float. -- `visibleItems` - the number of visible items in the current log view, mostly equal to (height / rowHeight), it is a float. -- `startFloor` - the number of the first item in the log view, rounds down the value of `start`, it is an integer. -- `endCeil` - the number of the last item in the log view, it is an integer. It is written, but not used. -- `scrollTop` - the number in pixels that shows how far the log content is scrolled vertically from the top, it is a float. -- `scrollLeft` - the number in pixels that shows how far the log content is scrolled horizontally from the left, it is a float. -- `rowHeight` - it is a constant, the value 28px. +# LogViewState +--- +```TS +interface LogViewState { + height: number, + start: number, + visibleItems: number, + startFloor: number, + endCeil: number, + scrollTop: number, + scrollLeft: number, + rowHeight: number +} +``` +This interface contains all the values for rendering the log view. + +- `height` - the height of the log view canvas, it is an integer (px). +- `start` - this value represents (approximately) where the current log view starts according to the entire log. The value is calculated by scrollTop/rowHeight, it is a float. +- `visibleItems` - the number of visible items in the current log view, mostly equal to (height / rowHeight), it is a float. +- `startFloor` - the number of the first item in the log view, rounds down the value of `start`, it is an integer. +- `endCeil` - the number of the last item in the log view, it is an integer. It is written, but not used. +- `scrollTop` - the number in pixels that shows how far the log content is scrolled vertically from the top, it is a float. +- `scrollLeft` - the number in pixels that shows how far the log content is scrolled horizontally from the left, it is a float. +- `rowHeight` - it is a constant, the value 28px. diff --git a/docs/user-documentation/getting-started.md b/docs/user-documentation/getting-started.md index 7a19492..31398b2 100644 --- a/docs/user-documentation/getting-started.md +++ b/docs/user-documentation/getting-started.md @@ -1,24 +1,24 @@ -# Getting Started - -## Installation -To install Tracy in Visual Studio Code: -1. Obtain the plugin `.vsix` file: - - If you want to install the latest release, go to the [Latest release](https://github.com/TNO/vscode-tracy/releases/latest) and download the `vscode-tracy-X.X.X.vsix` file under *Assests*. - - If you want to install a specific commit, click on the :heavy_check_mark: next to the commit -> *Details* -> *Summary* -> under *Artifacts*, *vscode-vsix* and extract the downloaded `vscode-vsix.zip`. -1. Open Visual Studio Code, in the side bar go to *Extensions* -> `···` (right top) -> *Install from VSIX...* -> open the downloaded `vscode-tracy-X.X.X.vsix`. -1. Tracy is now installed and will be used as the default viewer for all `*.tracy.json` files. You can download an `example.tracy.json` from [here](https://github.com/TNO/vscode-tracy/raw/main/examples/dummy.tracy.json.zip) (extract before opening). -1. If you want to upgrade Tracy in the future, repeat the instructions above. - -## User Guide - -### Log format - -Tracy assumes that a log is represented in JSON format. The log must be a list of JSON objects, with each object representing an event. Every event is assumed to have the same fields, with the first field being the timestamp of the event. Thus, the log can be viewed as a table where each row is an event and each column an event field, with the first column containing the timestamps. - -Files with extension `*.tracy.json` will be automatically opened in Tracy. - -### The minimap - -A prominent feature is the minimap, which allows navigation and analysis of a log by representing information in the form of glyphs (colored rectangles). In the minimap, each column of the log is represented as a column of glyphs. Every value in the log column maps to a glyph, in such a way that different values map to different colors. For timestamp values, nearby timestamps map to nearby colors, so that a gradual progress of time shows as a smooth gradient in the first minimap column. - +# Getting Started + +## Installation +To install Tracy in Visual Studio Code: +1. Obtain the plugin `.vsix` file: + - If you want to install the latest release, go to the [Latest release](https://github.com/TNO/vscode-tracy/releases/latest) and download the `vscode-tracy-X.X.X.vsix` file under *Assests*. + - If you want to install a specific commit, click on the :heavy_check_mark: next to the commit -> *Details* -> *Summary* -> under *Artifacts*, *vscode-vsix* and extract the downloaded `vscode-vsix.zip`. +1. Open Visual Studio Code, in the side bar go to *Extensions* -> `···` (right top) -> *Install from VSIX...* -> open the downloaded `vscode-tracy-X.X.X.vsix`. +1. Tracy is now installed and will be used as the default viewer for all `*.tracy.json` files. You can download an `example.tracy.json` from [here](https://github.com/TNO/vscode-tracy/raw/main/examples/dummy.tracy.json.zip) (extract before opening). +1. If you want to upgrade Tracy in the future, repeat the instructions above. + +## User Guide + +### Log format + +Tracy assumes that a log is represented in JSON format. The log must be a list of JSON objects, with each object representing an event. Every event is assumed to have the same fields, with the first field being the timestamp of the event. Thus, the log can be viewed as a table where each row is an event and each column an event field, with the first column containing the timestamps. + +Files with extension `*.tracy.json` will be automatically opened in Tracy. + +### The minimap + +A prominent feature is the minimap, which allows navigation and analysis of a log by representing information in the form of glyphs (colored rectangles). In the minimap, each column of the log is represented as a column of glyphs. Every value in the log column maps to a glyph, in such a way that different values map to different colors. For timestamp values, nearby timestamps map to nearby colors, so that a gradual progress of time shows as a smooth gradient in the first minimap column. + The minimap can be scaled (zoomed out and in) by holding the Control key and at the same time turning the mouse wheel, while the pointer is positioned over the minimap. The lines (rows) of the log that are visible, are indicated by a shaded area in the minimap. Scrolling the log is done with the scroll bar immediately to the left of the minimap, or by using the mouse wheel while the pointer is positioned over the log. \ No newline at end of file diff --git a/media/style.css b/media/style.css index 56c8daf..6d5bfb5 100644 --- a/media/style.css +++ b/media/style.css @@ -63,7 +63,7 @@ html { div.resizable-content { min-height: 30px; - min-width: 50px; + min-width: 20px; resize: horizontal; overflow: auto; } diff --git a/package-lock.json b/package-lock.json index 08b2965..9aaa6e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,9 @@ "react-resize-detector": "^8.0.4" }, "devDependencies": { + "@types/d3-array": "^3.2.1", + "@types/d3-scale": "^4.0.8", + "@types/d3-scale-chromatic": "^3.0.3", "@types/node": "^16.11.7", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", @@ -58,61 +61,62 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz", - "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", - "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", - "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -121,12 +125,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.17", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz", - "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.15", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -190,9 +194,9 @@ "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", - "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", "dependencies": { "@emotion/memoize": "^0.8.1" } @@ -203,14 +207,14 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", - "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", + "@emotion/serialize": "^1.1.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", "@emotion/utils": "^1.2.1", "@emotion/weak-memoize": "^0.3.1", @@ -226,9 +230,9 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", "dependencies": { "@emotion/hash": "^0.9.1", "@emotion/memoize": "^0.8.1", @@ -243,14 +247,14 @@ "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, "node_modules/@emotion/styled": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", - "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "version": "11.11.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", + "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.1", - "@emotion/serialize": "^1.1.2", + "@emotion/is-prop-valid": "^1.2.2", + "@emotion/serialize": "^1.1.4", "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", "@emotion/utils": "^1.2.1" }, @@ -303,18 +307,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -335,37 +339,37 @@ } }, "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@floating-ui/core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz", - "integrity": "sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "dependencies": { - "@floating-ui/utils": "^0.1.1" + "@floating-ui/utils": "^0.2.1" } }, "node_modules/@floating-ui/dom": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.2.tgz", - "integrity": "sha512-6ArmenS6qJEWmwzczWyhvrXRdI/rI78poBcW0h/456+onlabit+2G+QxHx5xTOX60NBJQXjsCLFbW2CmsXpUog==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.4.1", - "@floating-ui/utils": "^0.1.1" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dependencies": { - "@floating-ui/dom": "^1.5.1" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -373,18 +377,18 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz", - "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -405,51 +409,71 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -469,31 +493,37 @@ } }, "node_modules/@microsoft/fast-element": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.12.0.tgz", - "integrity": "sha512-gQutuDHPKNxUEcQ4pypZT4Wmrbapus+P9s3bR/SEOLsMbNqNoXigGImITygI5zhb+aA5rzflM6O8YWkmRbGkPA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.13.0.tgz", + "integrity": "sha512-iFhzKbbD0cFRo9cEzLS3Tdo9BYuatdxmCEKCpZs1Cro/93zNMpZ/Y9/Z7SknmW6fhDZbpBvtO8lLh9TFEcNVAQ==", "dev": true }, "node_modules/@microsoft/fast-foundation": { - "version": "2.49.1", - "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.49.1.tgz", - "integrity": "sha512-dSajlZeX+lkqjg4108XbIIhVLECgJTCG32bE8P6rNgo8XCPHVJBDiBejrF34lv5pO9Z2uGORZjeip/N0fPib+g==", + "version": "2.49.6", + "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.49.6.tgz", + "integrity": "sha512-DZVr+J/NIoskFC1Y6xnAowrMkdbf2d5o7UyWK6gW5AiQ6S386Ql8dw4KcC4kHaeE1yL2CKvweE79cj6ZhJhTvA==", "dev": true, "dependencies": { - "@microsoft/fast-element": "^1.12.0", + "@microsoft/fast-element": "^1.13.0", "@microsoft/fast-web-utilities": "^5.4.1", "tabbable": "^5.2.0", "tslib": "^1.13.0" } }, + "node_modules/@microsoft/fast-foundation/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@microsoft/fast-react-wrapper": { - "version": "0.1.48", - "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz", - "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.3.24.tgz", + "integrity": "sha512-sRnSBIKaO42p4mYoYR60spWVkg89wFxFAgQETIMazAm2TxtlsnsGszJnTwVhXq2Uz+XNiD8eKBkfzK5c/i6/Kw==", "dev": true, "dependencies": { - "@microsoft/fast-element": "^1.9.0", - "@microsoft/fast-foundation": "^2.41.1" + "@microsoft/fast-element": "^1.13.0", + "@microsoft/fast-foundation": "^2.49.6" }, "peerDependencies": { "react": ">=16.9.0" @@ -509,16 +539,16 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.15", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.15.tgz", - "integrity": "sha512-Xtom3YSdi0iwYPtyVRFUEGoRwi6IHWixPwifDKaK+4PkEPtUWMU5YOIJfTsmC59ri+dFvA3oBNSiTPUGGrklZw==", - "dependencies": { - "@babel/runtime": "^7.22.15", - "@floating-ui/react-dom": "^2.0.2", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.9", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@popperjs/core": "^2.11.8", - "clsx": "^2.0.0", + "clsx": "^2.1.0", "prop-types": "^15.8.1" }, "engines": { @@ -526,7 +556,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -540,20 +570,20 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.9.tgz", - "integrity": "sha512-JAU/R5hM3l2zP1Q4KnioDRhq5V3vZ4mmjEZ+TwARDb2xFhg3p59McacQuzkSu0sUHJnH9aJos36+hU5sPQBcFQ==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", + "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==", "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.0.tgz", - "integrity": "sha512-zHY6fOkaK7VfhWeyxO8MjO3IAjEYpYMXuqUhX7TkUZJ9+TSH/9dn4ClG4K2j6hdgBU5Yrq2Z/89Bo6BHHp7AdQ==", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", + "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", "dependencies": { - "@babel/runtime": "^7.23.5" + "@babel/runtime": "^7.23.9" }, "engines": { "node": ">=12.0.0" @@ -574,19 +604,19 @@ } }, "node_modules/@mui/material": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.9.tgz", - "integrity": "sha512-pbBy5kc5iUGXPxgbb+t+yEPvLK5nE3bPUb8WbAafJ8iZ40ZGui0xC4xiiIyzbVexzsLmyN7MaSo4LkxLmPKqUQ==", - "dependencies": { - "@babel/runtime": "^7.22.15", - "@mui/base": "5.0.0-beta.15", - "@mui/core-downloads-tracker": "^5.14.9", - "@mui/system": "^5.14.9", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.9", - "@types/react-transition-group": "^4.4.6", - "clsx": "^2.0.0", - "csstype": "^3.1.2", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", + "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.15", + "@mui/system": "^5.15.15", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -596,7 +626,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -618,12 +648,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.9.tgz", - "integrity": "sha512-0PzoUFqFXTXiNchhR7K4b7kZunasPOjx6Qf7AagCmfZDNASHedA0x6evHVhnST918x/AHY9xykYNKfB0Z4xMBg==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", + "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", "dependencies": { - "@babel/runtime": "^7.22.15", - "@mui/utils": "^5.14.9", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.14", "prop-types": "^15.8.1" }, "engines": { @@ -631,7 +661,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -644,22 +674,21 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.9.tgz", - "integrity": "sha512-LEQxLrW9oWvea33pge08+oyNeTz704jb6Nhe26xEJKojXWd34Rr327Zzx3dmo70AcS4h0b99vQjEpUzm6ASqUw==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", "dependencies": { - "@babel/runtime": "^7.22.15", + "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", - "prop-types": "^15.8.1", - "react": "^18.2.0" + "csstype": "^3.1.3", + "prop-types": "^15.8.1" }, "engines": { "node": ">=12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.4.1", @@ -676,17 +705,17 @@ } }, "node_modules/@mui/system": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.9.tgz", - "integrity": "sha512-Z00Wj590QXk5+SIxmxayBo7SWrao+y433LKGChneJxO4QcT/caSCeEWtyeoLs1Q8ys0zOzl2kkKee6n8TaKzhQ==", - "dependencies": { - "@babel/runtime": "^7.22.15", - "@mui/private-theming": "^5.14.9", - "@mui/styled-engine": "^5.14.9", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.9", - "clsx": "^2.0.0", - "csstype": "^3.1.2", + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", + "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.14", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -694,7 +723,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -715,11 +744,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -728,11 +757,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.9.tgz", - "integrity": "sha512-9ysB5e+RwS7ofn0n3nwAg1/3c81vBTmSvauD3EuK9LmqMzhmF//BFDaC44U4yITvB/0m1kWyDqg924Ll3VHCcg==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", + "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", "dependencies": { - "@babel/runtime": "^7.22.15", + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -741,12 +771,11 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -799,9 +828,9 @@ } }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true }, "node_modules/@tsconfig/node12": { @@ -822,10 +851,37 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", + "dev": true + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "dev": true + }, "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "version": "8.56.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", + "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", "dev": true, "dependencies": { "@types/estree": "*", @@ -833,9 +889,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -843,75 +899,69 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/node": { - "version": "16.18.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.50.tgz", - "integrity": "sha512-OiDU5xRgYTJ203v4cprTs0RwOCd5c5Zjv+K5P8KSqfiCsB1W3LcamTUMcnQarpq5kOYbhHfSOgIEJvdPyb5xyw==", + "version": "16.18.96", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz", + "integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==", "dev": true }, "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { - "version": "18.2.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", - "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "version": "18.2.79", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", + "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "version": "18.2.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz", + "integrity": "sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==", "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "dependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" - }, "node_modules/@types/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/vscode": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.82.0.tgz", - "integrity": "sha512-VSHV+VnpF8DEm8LNrn8OJ8VuUNcBzN3tMvKrNpbhhfuVjFm82+6v44AbDhLvVFgCzn6vs94EJNTp7w8S6+Q1Rw==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.88.0.tgz", + "integrity": "sha512-rWY+Bs6j/f1lvr8jqZTyp5arRMfovdxolcqGi+//+cPDOh8SBvzXH90e7BiSXct5HJ9HGW6jATchbRTpTJpEkw==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -1102,6 +1152,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vscode/codicons": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.32.tgz", @@ -1109,15 +1165,17 @@ "dev": true }, "node_modules/@vscode/vsce": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.21.0.tgz", - "integrity": "sha512-KuxYqScqUY/duJbkj9eE2tN2X/WJoGAy54hHtxT3ZBkM6IzrOg7H7CXGUPBxNlmqku2w/cAjOUSrgIHlzz0mbA==", + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.25.0.tgz", + "integrity": "sha512-VXMCGUaP6wKBadA7vFQdsksxkBAMoh4ecZgXBwauZMASAgnwYesHyLnqIyWYeRwjy2uEpitHvz/1w5ENnR30pg==", "dev": true, "dependencies": { - "azure-devops-node-api": "^11.0.1", + "azure-devops-node-api": "^12.5.0", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", "commander": "^6.2.1", + "form-data": "^4.0.0", "glob": "^7.0.6", "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", @@ -1139,30 +1197,31 @@ "vsce": "vsce" }, "engines": { - "node": ">= 14" + "node": ">= 16" }, "optionalDependencies": { "keytar": "^7.7.0" } }, "node_modules/@vscode/webview-ui-toolkit": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.2.2.tgz", - "integrity": "sha512-xIQoF4FC3Xh6d7KNKIoIezSiFWYFuf6gQMdDyKueKBFGeKwaHWEn+dY2g3makvvEsNMEDji/woEwvg9QSbuUsw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.4.0.tgz", + "integrity": "sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==", "dev": true, "dependencies": { - "@microsoft/fast-element": "^1.6.2", - "@microsoft/fast-foundation": "^2.38.0", - "@microsoft/fast-react-wrapper": "^0.1.18" + "@microsoft/fast-element": "^1.12.0", + "@microsoft/fast-foundation": "^2.49.4", + "@microsoft/fast-react-wrapper": "^0.3.22", + "tslib": "^2.6.2" }, "peerDependencies": { "react": ">=16.9.0" } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -1182,9 +1241,9 @@ "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -1205,15 +1264,15 @@ "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -1241,28 +1300,28 @@ "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -1270,24 +1329,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -1296,12 +1355,12 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -1362,9 +1421,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1392,9 +1451,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1458,28 +1517,32 @@ "dev": true }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -1498,6 +1561,26 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", @@ -1534,31 +1617,44 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.tosorted": { + "node_modules/array.prototype.toreversed": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", - "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -1568,20 +1664,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - } + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -1590,9 +1686,9 @@ } }, "node_modules/azure-devops-node-api": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", - "integrity": "sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==", + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", "dev": true, "dependencies": { "tunnel": "0.0.6", @@ -1681,9 +1777,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -1700,10 +1796,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -1753,13 +1849,19 @@ "dev": true }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1774,9 +1876,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001534", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", - "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", + "version": "1.0.30001611", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001611.tgz", + "integrity": "sha512-19NuN1/3PjA3QI8Eki55N8my4LzfkMCRLgCVfrl/slbSAchQfV0+GwjPrK3rq37As4UCLlM/DHajbKkAqbv92Q==", "dev": true, "funding": [ { @@ -1883,13 +1985,22 @@ } }, "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", "engines": { "node": ">=6" } }, + "node_modules/cockatiel": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.2.tgz", + "integrity": "sha512-5yARKww0dWyWg2/3xZeXgoxjHLwpVqFptj9Zy7qioJ6+/L0ARM184sgMUrQDjxw7ePJWlGhV998mKhzrxT0/Kg==", + "dev": true, + "engines": { + "node": ">=16" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1909,6 +2020,18 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -1965,19 +2088,19 @@ } }, "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">= 12.13.0" @@ -1987,7 +2110,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-select": { @@ -2031,9 +2163,9 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d3-array": { "version": "3.2.4", @@ -2094,9 +2226,9 @@ } }, "node_modules/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "dev": true, "dependencies": { "d3-color": "1 - 3", @@ -2130,6 +2262,57 @@ "node": ">=12" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2180,17 +2363,20 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", - "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -2210,10 +2396,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "dev": true, "optional": true, "engines": { @@ -2318,9 +2513,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.520", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.520.tgz", - "integrity": "sha512-Frfus2VpYADsrh1lB3v/ft/WVFlVzOIm+Q0p7U7VqHI6qr7NWHYKe+Wif3W50n7JAFoBsWVsoU0+qDks6WQ60g==", + "version": "1.4.740", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.740.tgz", + "integrity": "sha512-Yvg5i+iyv7Xm18BRdVPVm8lc7kgxM3r6iwqCH2zB7QZy1kZRNmd0Zqm0zcD9XoFREE5/5rwIuIAOT+/mzGcnZg==", "dev": true }, "node_modules/end-of-stream": { @@ -2334,9 +2529,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -2359,9 +2554,9 @@ } }, "node_modules/envinfo": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", - "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.12.0.tgz", + "integrity": "sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -2379,50 +2574,57 @@ } }, "node_modules/es-abstract": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", - "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.1", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-array-concat": "^1.0.0", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -2431,55 +2633,91 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", - "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz", + "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==", "dev": true, "dependencies": { - "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", + "es-abstract": "^1.23.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", + "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.0.1" + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", - "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -2500,9 +2738,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -2520,18 +2758,19 @@ } }, "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -2574,27 +2813,29 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "version": "7.34.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", + "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", + "es-iterator-helpers": "^1.0.17", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "string.prototype.matchall": "^4.0.10" }, "engines": { "node": ">=4" @@ -2616,12 +2857,12 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -2852,9 +3093,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2901,9 +3142,9 @@ } }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2963,24 +3204,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/for-each": { @@ -2992,10 +3242,24 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/framer-motion": { - "version": "10.16.4", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.4.tgz", - "integrity": "sha512-p9V9nGomS3m6/CALXqv6nFGMuFOxbWsmaOrdmhyQimMIlLl3LC7h7l86wge/Js/8cRu5ktutS/zlzgR7eBOtFA==", + "version": "10.18.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz", + "integrity": "sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==", "dependencies": { "tslib": "^2.4.0" }, @@ -3030,11 +3294,6 @@ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", "optional": true }, - "node_modules/framer-motion/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -3049,9 +3308,12 @@ "dev": true }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.6", @@ -3081,28 +3343,33 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -3157,9 +3424,9 @@ "dev": true }, "node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3230,17 +3497,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -3259,21 +3515,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -3295,12 +3551,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -3309,6 +3565,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3387,9 +3654,9 @@ "optional": true }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -3462,13 +3729,13 @@ "optional": true }, "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -3494,14 +3761,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3568,11 +3837,26 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, "dependencies": { - "has": "^1.0.3" + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3642,18 +3926,21 @@ } }, "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -3724,21 +4011,27 @@ } }, "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3775,12 +4068,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -3790,10 +4083,13 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3811,13 +4107,16 @@ } }, "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3936,9 +4235,9 @@ "dev": true }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsx-ast-utils": { @@ -3969,9 +4268,9 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -4276,9 +4575,9 @@ "dev": true }, "node_modules/node-abi": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", - "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", + "version": "3.59.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.59.0.tgz", + "integrity": "sha512-HyyfzvTLCE8b1SX2nWimlra8cibEsypcSu/Az4SXMhWhtuctkwAX7qsEYNjUOIoYtPV884oN3wtYTN+iZKBtvw==", "dev": true, "optional": true, "dependencies": { @@ -4296,9 +4595,9 @@ "optional": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/nth-check": { @@ -4322,9 +4621,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4340,13 +4639,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -4358,28 +4657,29 @@ } }, "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -4389,27 +4689,31 @@ } }, "node_modules/object.hasown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", - "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", "dev": true, "dependencies": { - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -4603,8 +4907,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -4682,10 +4985,19 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -4704,16 +5016,16 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -4723,9 +5035,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -4740,9 +5052,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -4770,9 +5082,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4789,9 +5101,9 @@ "dev": true }, "node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", "dev": true, "optional": true, "dependencies": { @@ -4866,21 +5178,21 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -5044,15 +5356,16 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", - "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -5064,19 +5377,20 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -5086,9 +5400,9 @@ } }, "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -5179,13 +5493,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -5217,23 +5531,26 @@ ] }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", "dev": true }, "node_modules/scheduler": { @@ -5263,9 +5580,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5278,23 +5595,41 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5334,14 +5669,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5412,9 +5751,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5450,34 +5789,41 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", - "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "set-function-name": "^2.0.0", - "side-channel": "^1.0.4" + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -5487,28 +5833,31 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5539,9 +5888,9 @@ } }, "node_modules/style-loader": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "dev": true, "engines": { "node": ">= 12.13.0" @@ -5627,9 +5976,9 @@ } }, "node_modules/terser": { - "version": "5.19.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", - "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", + "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -5645,16 +5994,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -5679,9 +6028,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -5701,15 +6050,12 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/to-fast-properties": { @@ -5733,15 +6079,16 @@ } }, "node_modules/ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" @@ -5809,6 +6156,15 @@ "node": ">=8" } }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/ts-loader/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5822,9 +6178,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -5865,10 +6221,9 @@ } }, "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -5885,6 +6240,12 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -5932,29 +6293,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5964,16 +6326,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5983,14 +6346,20 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6048,9 +6417,9 @@ "dev": true }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -6105,9 +6474,9 @@ "dev": true }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -6118,34 +6487,34 @@ } }, "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.16.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -6219,12 +6588,13 @@ } }, "node_modules/webpack-merge": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", - "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -6298,31 +6668,34 @@ } }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index c948bb7..e28086c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "Other" ], "activationEvents": [ - "onCustomEditor:tno.tracy" + "onStartupFinished" ], "main": "./out/extension/extension.js", "contributes": { @@ -25,9 +25,16 @@ "displayName": "Tracy", "selector": [ { - "filenamePattern": "*.tracy.json" + "filenamePattern": "*" } - ] + ], + "priority": "option" + } + ], + "commands": [ + { + "command": "tracy.openDocument", + "title": "Tracy: open current document with Tracy" } ] }, @@ -40,6 +47,9 @@ "watch-viewer": "webpack --mode=development --env development --progress --watch" }, "devDependencies": { + "@types/d3-array": "^3.2.1", + "@types/d3-scale": "^4.0.8", + "@types/d3-scale-chromatic": "^3.0.3", "@types/node": "^16.11.7", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", diff --git a/src/extension/DefaultJsonConverter.ts b/src/extension/DefaultJsonConverter.ts new file mode 100644 index 0000000..b5a906a --- /dev/null +++ b/src/extension/DefaultJsonConverter.ts @@ -0,0 +1,42 @@ +import fs from 'fs'; +import TracyConverter from "./api/converter/TracyConverter"; +import TracyLogFile from "./api/converter/TracyLogFile"; + +export default class DefaultJsonConverter implements TracyConverter { + name = 'DefaultJsonConverter'; + fileExtentions = ['json']; + + async parseLogFile(filePath: string): Promise { + const content = await fs.promises.readFile(filePath, "utf-8"); + return this.parseLog(filePath, content); + } + + async parseLog(filePath: string, content: string): Promise { + const json = JSON.parse(content); + const headers = this.getHeaders(json); + const rows = json.map((l: any) => headers.map((h) => l[h])); + let dateTimeIndex = this.searchForDateTimeColumn(headers); + + return { + filePath, + headers, + rows, + dateTimeIndex + } + } + + // Headers are all keys that are present in the first object (row) + private getHeaders(content: { [s: string]: string }[]) { + const firstRow = content[0] ?? {}; + const contentHeaders = Object.keys(firstRow); + return contentHeaders; + } + + private searchForDateTimeColumn(headers: string[]) { + const dateTimeIndex = headers.findIndex((h) => h.toLowerCase().includes('time')); + if (dateTimeIndex !== -1) { + return dateTimeIndex; + } + } + +} \ No newline at end of file diff --git a/src/extension/TracyAPI.ts b/src/extension/TracyAPI.ts new file mode 100644 index 0000000..b79fac5 --- /dev/null +++ b/src/extension/TracyAPI.ts @@ -0,0 +1,32 @@ +import ITracyAPI from "./api/ITracyAPI"; +import TracyConverter from "./api/converter/TracyConverter"; + +export default class TracyAPI implements ITracyAPI { + + private readonly customConverters: TracyConverter[] = []; + + constructor(...defaultConverters: TracyConverter[]) { + defaultConverters.forEach((e) => this.registerCustomConverter(e)); + } + + registerCustomConverter(converter: TracyConverter) { + this.customConverters.push(converter); + console.log(`Registered custom converter: ${converter.name} for file extentions: ${converter.fileExtentions}`); + } + + getConvertersForFile(filePath: string): TracyConverter[] { + const extention = filePath.split('.').pop(); + if (!extention) return []; + + return this.getConvertersForFileExtention(extention); + } + + getConvertersForFileExtention(extention: string): TracyConverter[] { + return this.customConverters.filter((c) => c.fileExtentions.some((e) => e === extention)); + } + + getSuportedFileExtentions(): string[] { + return this.customConverters.flatMap((e) => e.fileExtentions); + } + +} \ No newline at end of file diff --git a/src/extension/api/ITracyAPI.ts b/src/extension/api/ITracyAPI.ts new file mode 100644 index 0000000..1353f99 --- /dev/null +++ b/src/extension/api/ITracyAPI.ts @@ -0,0 +1,5 @@ +import TracyConverter from "./converter/TracyConverter"; + +export default interface ITracyAPI { + registerCustomConverter: (converter: TracyConverter) => void; +} \ No newline at end of file diff --git a/src/extension/api/README.md b/src/extension/api/README.md new file mode 100644 index 0000000..24ab7f5 --- /dev/null +++ b/src/extension/api/README.md @@ -0,0 +1,54 @@ +# Creating a custom converter +1. Add Tracy as a depencency to your extension `package.json` +``` JSON +"extensionDependencies": [ + "TNO.vscode-tracy" +] +``` +2. To use the Tracy API, copy this `api` folder to the extension project +3. Create a custom converter +``` Typescript +import TracyConverter from "./api/converter/TracyConverter"; +import TracyLogFile, { TracyLogData } from "./api/converter/TracyLogFile"; + +export default class MyCustomTracyConverter implements TracyConverter { + name = 'MyCustomTracyConverter'; + fileExtentions = ['csv']; + + async parseLogFile(filePath: string): Promise { + // Parse file at filePath + const content = 'file content'; + + return this.parseLog(filePath, content); + } + + async parseLog(filePath: string, content: string): Promise { + // Parse file from content string + + return { + filePath, + headers: [ + 'DateTime', + 'Text', + 'ThreadName', + 'EventOrigin' + ], + rows, + dateTimeIndex: 0 + } + } +} +``` +4. Register the custom converter +``` Typescript +export function activate(context: vscode.ExtensionContext) { + const exports = vscode.extensions.getExtension('TNO.vscode-tracy')?.exports; + if (!exports) { + vscode.window.showErrorMessage("Tracy is not installed! Could not register custom Tracy Converters."); + return; + } + + const tracyAPI = exports as ITracyAPI; + tracyAPI.registerCustomConverter(new MyCustomTracyConverter()); +} +``` \ No newline at end of file diff --git a/src/extension/api/converter/TracyConverter.ts b/src/extension/api/converter/TracyConverter.ts new file mode 100644 index 0000000..7f686f4 --- /dev/null +++ b/src/extension/api/converter/TracyConverter.ts @@ -0,0 +1,8 @@ +import TracyLogFile from "./TracyLogFile"; + +export default interface TracyConverter { + name: string; + fileExtentions: string[]; + parseLogFile: (filePath: string) => Promise; + parseLog: (filePath: string, content: string) => Promise; +} \ No newline at end of file diff --git a/src/extension/api/converter/TracyLogFile.ts b/src/extension/api/converter/TracyLogFile.ts new file mode 100644 index 0000000..cdaaef7 --- /dev/null +++ b/src/extension/api/converter/TracyLogFile.ts @@ -0,0 +1,8 @@ +export type TracyLogData = string | number | boolean | null | undefined; + +export default interface TracyLogFile { + filePath: string; + headers: string[]; + rows: TracyLogData[][]; + dateTimeIndex?: number; +} \ No newline at end of file diff --git a/src/extension/extension.ts b/src/extension/extension.ts index 7bf3bd9..83790cc 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -1,190 +1,278 @@ -import * as vscode from 'vscode'; -import fs from 'fs'; - -export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push(EditorProvider.register(context)); -} - -export class EditorProvider implements vscode.CustomTextEditorProvider { - - public static register(context: vscode.ExtensionContext): vscode.Disposable { - return vscode.window.registerCustomEditorProvider(EditorProvider.viewType, new EditorProvider(context)); - } - - private static readonly viewType = 'tno.tracy'; - - constructor( - private readonly context: vscode.ExtensionContext - ) { } - - public async resolveCustomTextEditor( - document: vscode.TextDocument, - webviewPanel: vscode.WebviewPanel, - _token: vscode.CancellationToken - ): Promise { - const rulesFile = `${document.fileName}.rules`; - const structureDefinitionFile = `${document.fileName}.structure`; - - // Setup initial content for the webview - webviewPanel.webview.options = { - enableScripts: true, - }; - webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview); - - function updateWebview(message_type: string) { - webviewPanel.webview.postMessage({ - type: message_type, - text: document.getText(), - rules: fs.existsSync(rulesFile) ? JSON.parse(fs.readFileSync(rulesFile, { encoding: 'utf8' })) : [], - }); - } - - // Hook up event handlers so that we can synchronize the webview with the text document. - // - // The text document acts as our model, so we have to sync change in the document to our - // editor and sync changes in the editor back to the document. - // - // Remember that a single text document can also be shared between multiple custom - // editors (this happens for example when you split a custom editor) - - const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => { - if (e.document.uri.toString() === document.uri.toString()) { - updateWebview('readFile'); - } - }); - - // Make sure we get rid of the listener when our editor is closed. - webviewPanel.onDidDispose(() => { - changeDocumentSubscription.dispose(); - }); - - // Receive message from the webview. - webviewPanel.webview.onDidReceiveMessage(e => { - - if (e.type === 'readFile') { - updateWebview('readFile'); - } else if (e.type === 'saveRules') { - fs.writeFileSync(rulesFile, JSON.stringify(e.rules)); - } - else if (e.type === 'saveStructureDefinition') { - - const options: vscode.SaveDialogOptions = { - title: 'Save Structure Definition', - defaultUri: vscode.Uri.joinPath(document.uri, structureDefinitionFile), - filters: { - 'Stucture files': ['structure'] - } - }; - vscode.window.showSaveDialog(options).then(fileUri => { - if (fileUri) { - fs.writeFileSync(fileUri.fsPath, e.structureDefinition); - } - }); - } - else if (e.type === 'loadStructureDefinition') { - const options: vscode.OpenDialogOptions = { - title: 'Load Structure Definition', - canSelectMany: false, - openLabel: 'Load', - filters: { - 'Stucture files': ['structure'] - } - }; - vscode.window.showOpenDialog(options).then(fileUri => { - - if (fileUri && fileUri[0]) { - const filePath = fileUri[0].fsPath; - - webviewPanel.webview.postMessage({ - type: 'loadedStructureDefinition', - structureDefinition: fs.existsSync(filePath) ? JSON.parse(fs.readFileSync(filePath, {encoding: 'utf8'})) : [] - }); - } - - }); - } - else if (e.type === 'exportData') { - let filename = document.fileName; - const splitItems = [".tracy", ".json", ".txt", ".csv", "_Tracy_export_"]; - splitItems.forEach(item => { filename = filename.split(item)[0]; }); - const tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds - const _date = new Date(Date.now() - tzoffset).toISOString().slice(0, 10).replace(/-/g, ""); - const _time = new Date(Date.now() - tzoffset).toISOString().slice(11, 19).replace(/:/g, ""); - const exportFile = `${filename}_Tracy_export_${_date}_${_time}.tracy.json`; - const options: vscode.SaveDialogOptions = { - title: 'Export Data', - defaultUri: vscode.Uri.joinPath(document.uri, exportFile), - filters: { - 'Tracy files': ['json'] - } - }; - vscode.window.showSaveDialog(options).then(fileUri => { - if (fileUri) { - fs.writeFileSync(fileUri.fsPath, JSON.stringify(e.data)); - } - }); - // fs.writeFileSync(exportFile, JSON.stringify(e.data)); - // webviewPanel.webview.postMessage({ - // type: "readExportPath", - // text: fileUri.fsPath, - // }); - } - }); - } - - /** - * Get the static html used for the editor webviews. - */ - private getHtmlForWebview(webview: vscode.Webview): string { - // Local path to script and css for the webview - const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'out', 'viewer', 'viewer.js')); - - const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'media', 'reset.css')); - - const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'media', 'vscode.css')); - - const styleUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'media', 'style.css')); - - // Use a nonce to whitelist which scripts can be run - const nonce = getNonce(); - - return /* html */` - - - - - - - - - - - - - - Tracy - - -
- - - `; - } -} - -function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; +import * as vscode from 'vscode'; +import fs from 'fs'; +import TracyAPI from './TracyAPI'; +import DefaultJsonConverter from './DefaultJsonConverter'; +import TracyConverter from './api/converter/TracyConverter'; + +const API = new TracyAPI(new DefaultJsonConverter()); +let chosenConverter: TracyConverter | undefined; + +export function activate(context: vscode.ExtensionContext) { + const disposable = vscode.commands.registerCommand('tracy.openDocument', async () => { + const editor = vscode.window.activeTextEditor; + if (editor) { + const converters = API.getConvertersForFile(editor.document.fileName); + if (converters.length === 1) { + chosenConverter = converters[0]; + } else { + const choice = await vscode.window.showQuickPick(converters.map((c) => c.name)); + if (!choice) { + return; + } + + const converter = converters.filter((c) => c.name == choice).pop(); + if (!converter) { + throw new Error('Chosen converter does not exist!'); + } + + chosenConverter = converter; + } + + vscode.commands.executeCommand('vscode.openWith', editor.document.uri, 'tno.tracy'); + } else { + vscode.window.showErrorMessage("Current document is not open in a text editor."); + } + }); + + context.subscriptions.push(disposable); + context.subscriptions.push(EditorProvider.register(context)); + return API; +} + +export class EditorProvider implements vscode.CustomTextEditorProvider { + + public static register(context: vscode.ExtensionContext): vscode.Disposable { + return vscode.window.registerCustomEditorProvider(EditorProvider.viewType, new EditorProvider(context)); + } + + private static readonly viewType = 'tno.tracy'; + + constructor( + private readonly context: vscode.ExtensionContext + ) { } + + public async resolveCustomTextEditor( + document: vscode.TextDocument, + webviewPanel: vscode.WebviewPanel, + _token: vscode.CancellationToken + ): Promise { + const rulesFile = `${document.fileName}.rules`; + const structureDefinitionFile = `${document.fileName}.structure`; + + // Setup initial content for the webview + webviewPanel.webview.options = { + enableScripts: true, + }; + webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview); + + function updateWebview(message_type: string) { + let converter: TracyConverter; + if (chosenConverter) { + converter = chosenConverter; + chosenConverter = undefined; + } else { + converter = API.getConvertersForFile(document.fileName)[0]; + if (!converter) { + vscode.window.showErrorMessage(`No converter found for: ${document.fileName}`); + return; + } + } + + converter.parseLog(document.fileName, document.getText()) + .then((logFile) => { + webviewPanel.webview.postMessage({ + type: message_type, + logFile, + rules: fs.existsSync(rulesFile) ? JSON.parse(fs.readFileSync(rulesFile, { encoding: 'utf8' })) : [], + }); + }).catch(console.log); + } + + // Hook up event handlers so that we can synchronize the webview with the text document. + // + // The text document acts as our model, so we have to sync change in the document to our + // editor and sync changes in the editor back to the document. + // + // Remember that a single text document can also be shared between multiple custom + // editors (this happens for example when you split a custom editor) + + const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => { + if (e.document.uri.toString() === document.uri.toString()) { + updateWebview('readFile'); + } + }); + + // Make sure we get rid of the listener when our editor is closed. + webviewPanel.onDidDispose(() => { + changeDocumentSubscription.dispose(); + }); + + // Receive message from the webview. + webviewPanel.webview.onDidReceiveMessage(e => { + if (e.type === 'readFile') { + updateWebview('readFile'); + if (e.comparisonFilePath) { + this.convertComparisonFile(e.comparisonFilePath, webviewPanel); + } + } else if (e.type === 'saveRules') { + fs.writeFileSync(rulesFile, JSON.stringify(e.rules)); + } else if (e.type === 'showCompareFileDialog') { + this.showCompareFileDialog(document, webviewPanel); + } + else if (e.type === 'saveStructureDefinition') { + + const options: vscode.SaveDialogOptions = { + title: 'Save Structure Definition', + defaultUri: vscode.Uri.joinPath(document.uri, structureDefinitionFile), + filters: { + 'Stucture files': ['structure'] + } + }; + vscode.window.showSaveDialog(options).then(fileUri => { + if (fileUri) { + fs.writeFileSync(fileUri.fsPath, e.structureDefinition); + } + }); + } + else if (e.type === 'loadStructureDefinition') { + const options: vscode.OpenDialogOptions = { + title: 'Load Structure Definition', + canSelectMany: false, + openLabel: 'Load', + filters: { + 'Stucture files': ['structure'] + } + }; + vscode.window.showOpenDialog(options).then(fileUri => { + + if (fileUri && fileUri[0]) { + const filePath = fileUri[0].fsPath; + + webviewPanel.webview.postMessage({ + type: 'loadedStructureDefinition', + structureDefinition: fs.existsSync(filePath) ? JSON.parse(fs.readFileSync(filePath, {encoding: 'utf8'})) : [] + }); + } + + }); + } + else if (e.type === 'exportData') { + let filename = document.fileName; + const splitItems = [".tracy", ".json", ".txt", ".csv", "_Tracy_export_"]; + splitItems.forEach(item => { filename = filename.split(item)[0]; }); + const tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds + const _date = new Date(Date.now() - tzoffset).toISOString().slice(0, 10).replace(/-/g, ""); + const _time = new Date(Date.now() - tzoffset).toISOString().slice(11, 19).replace(/:/g, ""); + const exportFile = `${filename}_Tracy_export_${_date}_${_time}.tracy.json`; + const options: vscode.SaveDialogOptions = { + title: 'Export Data', + defaultUri: vscode.Uri.joinPath(document.uri, exportFile), + filters: { + 'Tracy files': ['json'] + } + }; + vscode.window.showSaveDialog(options).then(fileUri => { + if (fileUri) { + fs.writeFileSync(fileUri.fsPath, JSON.stringify(e.data)); + } + }); + } + else if (e.type === 'showErrorMessage') { + vscode.window.showErrorMessage(e.message); + } + }); + } + + private showCompareFileDialog(document: vscode.TextDocument, webviewPanel: vscode.WebviewPanel) { + const options: vscode.OpenDialogOptions = { + title: 'Select Log File', + defaultUri: document.uri, + canSelectFolders: false, + canSelectMany: false, + filters: { + 'Tracy files': API.getSuportedFileExtentions() + } + }; + + vscode.window.showOpenDialog(options).then(fileUri => { + if (!fileUri) { + return; + } + + const path = fileUri[0].fsPath; + this.convertComparisonFile(path, webviewPanel); + }); + } + + private convertComparisonFile(path: string, webviewPanel: vscode.WebviewPanel) { + const converter = API.getConvertersForFile(path)[0]; + if (!converter) { + vscode.window.showErrorMessage(`No converter found for: ${path}`); + return; + } + + converter.parseLogFile(path) + .then((logFile) => { + webviewPanel.webview.postMessage({ + type: 'loadFileForComparison', + logFile + }); + }).catch(console.log); + } + + /** + * Get the static html used for the editor webviews. + */ + private getHtmlForWebview(webview: vscode.Webview): string { + // Local path to script and css for the webview + const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath( + this.context.extensionUri, 'out', 'viewer', 'viewer.js')); + + const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath( + this.context.extensionUri, 'media', 'reset.css')); + + const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath( + this.context.extensionUri, 'media', 'vscode.css')); + + const styleUri = webview.asWebviewUri(vscode.Uri.joinPath( + this.context.extensionUri, 'media', 'style.css')); + + // Use a nonce to whitelist which scripts can be run + const nonce = getNonce(); + + return /* html */` + + + + + + + + + + + + + + Tracy + + +
+ + + `; + } +} + +function getNonce() { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; } \ No newline at end of file diff --git a/src/viewer/App.tsx b/src/viewer/App.tsx index 2ad23ee..609b74c 100644 --- a/src/viewer/App.tsx +++ b/src/viewer/App.tsx @@ -1,479 +1,420 @@ import React from "react"; import Rule from "./rules/Rule"; -import LogFile from "./LogFile"; -import LogView from "./log/LogView"; -import MinimapView from "./minimap/MinimapView"; -import Tooltip from "@mui/material/Tooltip"; -import { LogViewState, StructureMatchId, RowProperty, Segment, LogEntryCharMaps, StructureDefinition } from "./types"; +import LogFile from "./lib/LogFile"; +import { ColumnSelection, LogViewState, VsCode } from "./interfaces"; import { - COLUMN_0_HEADER_STYLE, - COLUMN_2_HEADER_STYLE, - MINIMAP_COLUMN_WIDTH, - SelectedRowType, - StructureHeaderColumnType, - defaultAppState, -} from "./constants"; -import { - VSCodeButton, - VSCodeTextField, - VSCodeDropdown, - VSCodeOption, + VSCodeButton } from "@vscode/webview-ui-toolkit/react"; -import { - useGetCharIndicesForLogEntries, - useStructureRegularExpressionSearch, -} from "./hooks/useStructureRegularExpressionManager"; -import { returnSearchIndices } from "./hooks/useLogSearchManager"; -import { constructNewRowProperty } from "./hooks/useRowProperty"; -import StructureDialog from "./structures/StructureDialog"; -import StatesDialog from "./rules/Dialogs/StatesDialog"; -import FlagsDialog from "./rules/Dialogs/FlagsDialog"; -import ExportDialog from "./rules/Dialogs/ExportDialog"; -import MinimapHeader from "./minimap/MinimapHeader"; -import SelectColDialog from "./log/SelectColDialog"; - -interface Props { } -interface State { +import ExportDialog from "./components/dialogs/ExportDialog"; +import SelectColDialog from "./components/dialogs/SelectColDialog"; +import StructureDialog from "./components/dialogs/StructureDialog"; +import SearchBar from "./components/SearchBar"; +import TracyIconButton, { IconType } from "./components/TracyIconButton"; +import LogViewAndMinimap from "./components/LogViewAndMinimap"; +import SideBySideAlignmentHandler from "./lib/SideBySideAlignmentHandler"; +import ColorMappingHandler from "./lib/ColorMappingHandler"; +import StatesDialog from "./components/dialogs/StatesDialog"; +import FlagsDialog from "./components/dialogs/FlagsDialog"; +import MinimapHeader from "./components/minimap/MinimapHeader"; +import { constants } from "./constants"; +import { enums } from "./enums"; +import { isLoadFileForComparisonMessage, isReadExportPathMessage, isReadFileMessage } from "./hooks/useMessage"; +import { createEmptyLogFile, createLogFile, toExportData } from "./hooks/useLogFile"; +import { getVisibleColumnsMinimap } from "./hooks/useColumnSelection"; +import RelativeTimeColumn, { HEADER as RELATIVE_TIME_HEADER, handleSetStartingPoint } from "./lib/columns/RelativeTimeColumn"; +import { setVsState, storeAppState } from "./hooks/useVsCode"; + +export interface State { logFile: LogFile; - logViewState: LogViewState | undefined; rules: Rule[]; - showExportDialog: boolean; - showStatesDialog: boolean; - showStructureDialog: boolean; - showFlagsDialog: boolean; - showSelectDialog: boolean; + + // Comparison related + comparisonFile?: LogFile; + + // UI settings showMinimapHeader: boolean; - selectedColumns: boolean[]; - selectedColumnsMini: boolean[]; coloredTable: boolean; + selectedColumns: ColumnSelection; + currentDialog?: enums.DialogType; // Search related - reSearch: boolean; - wholeSearch: boolean; - caseSearch: boolean; filterSearch: boolean; - searchMatches: number[]; - currentSearchMatch: number | null; - currentSearchMatchIndex: number | null; - - // Structure related - logFileAsString: string; - loadedStructureDefinition: StructureDefinition | null; - logEntryCharIndexMaps: LogEntryCharMaps | null; - selectedLogRows: string[][]; - rowProperties: RowProperty[]; - lastSelectedRow: number | undefined; - structureMatches: number[][]; - currentStructureMatch: number[]; - currentStructureMatchIndex: StructureMatchId; - - //Collapsible Table - collapsibleRows: { [key: number]: Segment }; + searchMatches: number[][]; + comparisonSearchMatches?: number[][]; + currentSearchMatch: number[]; } - -let searchTimeoutId; -let searchText: string = ""; -let searchColumn: string = "All"; let exportPath: string = ""; -let logHeaderColumnTypes: StructureHeaderColumnType[] = []; -export default class App extends React.Component { +export default class App extends React.Component<{}, State> { // @ts-ignore - vscode = acquireVsCodeApi(); - previousSession = this.vscode.getState(); - child = React.createRef(); - constructor(props: Props) { + vscode = acquireVsCodeApi() as VsCode; + storedState = this.vscode.getState(); + + primaryViewRef = React.createRef(); + comparisonViewRef = React.createRef(); + sideBySideAlignmentHandler: SideBySideAlignmentHandler; + colorMappingHandler: ColorMappingHandler; + + constructor(props: any) { super(props); - this.updateSearchMatches = this.updateSearchMatches.bind(this); - - this.state = defaultAppState; - if (this.previousSession !== undefined) { - searchText = this.previousSession.searchText; - searchColumn = this.previousSession.searchColumn; - ["searchColumn", "searchText"].forEach(e => delete this.previousSession[e]); - const { showFlagsDialog, showStatesDialog, showStructureDialog, ...updatedState } = this.previousSession; - this.state = { ...this.state, ...updatedState } + + this.sideBySideAlignmentHandler = new SideBySideAlignmentHandler(this.primaryViewRef, this.comparisonViewRef); + this.colorMappingHandler = new ColorMappingHandler(); + + this.state = { + rules: [], + logFile: createEmptyLogFile(), + coloredTable: false, + showMinimapHeader: true, + selectedColumns: {}, + filterSearch: false, + searchMatches: [], + currentSearchMatch: [] + }; + + // Override default state with previous session state + let comparisonFilePath: string | undefined; + if (this.storedState !== undefined) { + const { comparisonFile, ui } = this.storedState; + this.state = { ...this.state, ...ui }; + comparisonFilePath = comparisonFile; } this.onMessage = this.onMessage.bind(this); + this.handlePrimaryLogViewStateChange = this.handlePrimaryLogViewStateChange.bind(this); + this.handleComparisonLogViewStateChange = this.handleComparisonLogViewStateChange.bind(this); + window.addEventListener("message", this.onMessage); document.addEventListener("contextmenu", (event) => { event.preventDefault(); }); - this.vscode.postMessage({ type: "readFile" }); + this.vscode.postMessage({ type: "readFile", comparisonFilePath }); } - componentDidUpdate(prevProps: Props, prevState: State) { + componentDidUpdate(prevProps: any, prevState: State) { if (this.state !== prevState) { - const { rules, logFile, logFileAsString, logEntryCharIndexMaps, ...updatedState } = this.state; - this.vscode.setState({ ...updatedState, searchText, searchColumn }) - } - if (this.state.logFile !== prevState.logFile || - this.state.collapsibleRows !== prevState.collapsibleRows) { - this.render(); + storeAppState(this.vscode, this.state); } } onMessage(event: MessageEvent) { - const message = event.data; - if (message.type === "readFile") { + const message: unknown = event.data; + if (isReadFileMessage(message)) { const rules = message.rules.map((r) => Rule.fromJSON(r)).filter((r) => r); - const lines = JSON.parse(message.text); - const logFileText = JSON.stringify(lines, null, 2); - const logEntryCharIndexMaps = useGetCharIndicesForLogEntries(logFileText); - let logFile = LogFile.create(lines, rules); - // if (this.previousSession) - // logFile.setSelectedColumns(this.previousSession.selectedColumns, this.previousSession.selectedColumnsMini); - this.extractHeaderColumnTypes(logFile); + const { dateTimeIndex } = message.logFile; + let logFile = createLogFile(message.logFile, rules); + if (dateTimeIndex !== undefined) { + logFile.registerCustomColumn(new RelativeTimeColumn(true, false)); + } + this.colorMappingHandler.computeAllColors(logFile, this.state.comparisonFile); + this.setState({ rules, - logFile, - logFileAsString: logFileText, - logEntryCharIndexMaps: logEntryCharIndexMaps, + logFile }); - - if (!this.previousSession) { - const newRowsProps = logFile.rows.map(() => - constructNewRowProperty(true, SelectedRowType.None), - ); - this.setState({ rowProperties: newRowsProps }); - } - else { - this.setState({ - showFlagsDialog: this.previousSession.showFlagsDialog, - showStatesDialog: this.previousSession.showStatesDialog, - showStructureDialog: this.previousSession.showStructureDialog, - }); - } - } else if (message.type === "loadedStructureDefinition") { - const loadedStructureDefinition = message.structureDefinition; - console.log("loadedStructureDefinition - in App", loadedStructureDefinition); - this.setState({loadedStructureDefinition}); } - else if (message.type === "readExportPath") { + else if (isReadExportPathMessage(message)) { exportPath = message.text; - this.setState({ showExportDialog: true }); + this.setState({ currentDialog: enums.DialogType.ExportDialog }); } - } - - extractHeaderColumnTypes(logFile: LogFile) { - logHeaderColumnTypes = []; - for (let h = 0; h < logFile.headers.length; h++) { - let headerType = StructureHeaderColumnType.Selected; - - const cleanHeader = logFile.headers[h].name.toLowerCase().replace(/[\u200B-\u200D\uFEFF]/g, ''); - if (cleanHeader === "timestamp") { - headerType = StructureHeaderColumnType.Unselected; - } - if (!logFile.contentHeaders.includes(logFile.headers[h].name)) { - headerType = StructureHeaderColumnType.Custom; + else if (isLoadFileForComparisonMessage(message)) { + const { headers, dateTimeIndex } = message.logFile; + let comparisonFile = createLogFile(message.logFile, this.state.rules); + + const selectedColumns = this.state.selectedColumns; + if (dateTimeIndex !== undefined) { + comparisonFile.registerCustomColumn(new RelativeTimeColumn()); + + selectedColumns[RELATIVE_TIME_HEADER] = {logView: true, miniMap: true}; + selectedColumns[headers[dateTimeIndex]] = {logView: true, miniMap: false}; } + comparisonFile.registerCustomColumn(new RelativeTimeColumn()); - logHeaderColumnTypes.push(headerType); + this.colorMappingHandler.computeAllColors(this.state.logFile, comparisonFile); + + this.setState({ comparisonFile }); } } - updateSearchField() { - clearTimeout(searchTimeoutId); - searchTimeoutId = setTimeout(this.updateSearchMatches, 1000); - } + handleAnnotationDialogClose(newRules: Rule[]) { + this.vscode.postMessage({ type: "saveRules", rules: newRules.map((r) => r.toJSON()) }); + + const oldRuleHeaders = this.state.rules.map((r) => r.column); + const newRuleHeaders = newRules.map((r) => r.column); + + const { logFile } = this.state; + + oldRuleHeaders.filter((r) => !newRuleHeaders.includes(r)) + .forEach((h) => logFile.unRegisterCustomColumn(h)); + + const updatedLogFile = this.state.logFile.updateLogFile(newRules); + this.colorMappingHandler.computeAllColors(updatedLogFile, this.state.comparisonFile); - clearSearchField() { - searchText = ""; - const newRowsProps = this.clearRowsTypes(); this.setState({ - filterSearch: false, - rowProperties: newRowsProps, - searchMatches: [], - currentSearchMatch: null, - currentSearchMatchIndex: null + rules: newRules, + logFile: updatedLogFile, + currentDialog: undefined, }); } - updateSearchMatches() { - if (searchText === "") - this.clearSearchField(); - else { - const colIndex = this.state.logFile.headers.findIndex((h) => h.name === searchColumn); - const searchMatches = returnSearchIndices( - this.state.logFile.rows, - colIndex, - searchText, - this.state.reSearch, - this.state.wholeSearch, - this.state.caseSearch, - ); - - const currentSearchMatch = searchMatches[0]; - const currentSearchMatchIndex = 0; - const [rowProperties, filterSearch] = this.updateVisibleSearchMatches(searchMatches, this.state.filterSearch); - - this.setState({ searchMatches, currentSearchMatch, currentSearchMatchIndex, rowProperties, filterSearch }); - } + handleSelectDialog(selectedColumns: ColumnSelection) { + this.setState({ + selectedColumns: selectedColumns, + currentDialog: undefined + }); } - updateVisibleSearchMatches(searchMatches: number[], filterSearch: boolean) { - let rowProperties; - if (!filterSearch) { - rowProperties = this.state.logFile.rows.map((row, index) => { - if (searchMatches.includes(index)) - return constructNewRowProperty(true, SelectedRowType.SearchResult); - else return constructNewRowProperty(true, SelectedRowType.None); - }); + handleSetRelativeTimeStartingPoint() { + let result = false; + + const primary = this.primaryViewRef.current; + if (primary) { + result = handleSetStartingPoint(primary) || result; } - else { - rowProperties = this.state.logFile.rows.map((row, index) => { - if (searchMatches.includes(index)) - return constructNewRowProperty(true, SelectedRowType.SearchResult); - else return constructNewRowProperty(false, SelectedRowType.None); - }); + const comparison = this.comparisonViewRef.current; + if (comparison) { + result = handleSetStartingPoint(comparison) || result; } - this.setState({ rowProperties, filterSearch }); - return [rowProperties, filterSearch]; - } - handleAnnotationDialog(newRules: Rule[], isClose: boolean) { - this.vscode.postMessage({ type: "saveRules", rules: newRules.map((r) => r.toJSON()) }); - if (isClose === true) - this.setState({ - rules: newRules, - logFile: this.state.logFile.updateLogFile(newRules, []), - showStatesDialog: false, - showFlagsDialog: false, - }); - else this.setState({ rules: newRules }); - } - - handleSelectDialog(selectedColumns: boolean[], selectedColumnsMini: boolean[], isClose: boolean) { - if (isClose === true) { - this.setState({ - selectedColumns: selectedColumns, - selectedColumnsMini: selectedColumnsMini, - logFile: this.state.logFile.setSelectedColumns(selectedColumns, selectedColumnsMini), - showSelectDialog: false, - }); + if (result) { + this.colorMappingHandler.computeColors(RELATIVE_TIME_HEADER, this.state.logFile, this.state.comparisonFile); + this.primaryViewRef.current?.forceUpdate(); + this.comparisonViewRef.current?.forceUpdate(); } - } - - handleStructureDialog(isClosing: boolean) { - if (isClosing === true) { - this.handleStructureUpdate(isClosing); - - } else { - const { logFile, rowProperties, showStructureDialog } = this.state; - - const selectedLogRows = logFile.rows.filter( - (v, i) => rowProperties[i].rowType === SelectedRowType.UserSelect, - ); - - if (selectedLogRows.length === 0) { - return; - } - - if (!showStructureDialog) { - this.extractHeaderColumnTypes(logFile); - this.setState({ showStructureDialog: true }); - } - - this.setState({ selectedLogRows: selectedLogRows }); + else { + this.vscode.postMessage({ type: "showErrorMessage", message: 'Select 1 starting point row'}); } } - handleSavingStuctureDefinition(structureDefinition: string) { - this.vscode.postMessage({ type: "saveStructureDefinition", structureDefinition: structureDefinition}); + handlePrimaryLogViewStateChange(trigger: enums.EventTrigger, state: LogViewState) { + this.sideBySideAlignmentHandler.handlePrimaryViewStateChange(trigger, state); + setVsState(this.vscode, { + primaryView: state + }); } - handleLoadingStructureDefinition() { - this.vscode.postMessage({ type: "loadStructureDefinition"}); + handleComparisonLogViewStateChange(trigger: enums.EventTrigger, state: LogViewState) { + this.sideBySideAlignmentHandler.handleComparisonViewStateChange(trigger, state); + setVsState(this.vscode, { + comparisonView: state + }); } - handleRowCollapse(rowIndex: number, isRendered: boolean) { - const newRowProps = this.state.rowProperties; - newRowProps[rowIndex].isRendered = isRendered; - this.setState({ rowProperties: newRowProps }); + exportData(exportIndices?: number[]) { + const logFile = this.state.logFile; + if (exportIndices?.length === 0 || this.state.filterSearch === false) + exportIndices = undefined; + + const exportData = toExportData(logFile, exportIndices); + this.vscode.postMessage({ type: "exportData", data: exportData }); } - handleRowSelect(rowProperties: RowProperty[], rowIndex: number) { - if (rowProperties[rowIndex].rowType !== SelectedRowType.UserSelect) - return SelectedRowType.UserSelect; - else if (this.state.searchMatches.includes(rowIndex)) - return SelectedRowType.SearchResult; - else - return SelectedRowType.None; + showCompareFileDialog() { + if (this.state.comparisonFile) { + this.setState({ comparisonFile: undefined}); + this.colorMappingHandler.computeAllColors(this.state.logFile, undefined); + } else { + this.vscode.postMessage({ + type: 'showCompareFileDialog' + }); + } } - handleSelectedLogRow(rowIndex: number, event: React.MouseEvent) { - if (event.ctrlKey) { - const newRowProps = this.state.rowProperties; - const { structureMatches, lastSelectedRow } = this.state; - - const structureMatchesLogRows = structureMatches.flat(1); - - if (!structureMatchesLogRows.includes(rowIndex)) { - if (event.shiftKey && rowIndex !== this.state.lastSelectedRow) { - // Shift click higher in the event log - if (lastSelectedRow !== undefined && lastSelectedRow < rowIndex) { - for (let i = lastSelectedRow + 1; i < rowIndex + 1; i++) { - newRowProps[i].rowType = this.handleRowSelect(newRowProps, rowIndex); - } - } - // Shift click lower in the event log - else if (lastSelectedRow !== undefined && lastSelectedRow > rowIndex) { - for (let i = rowIndex; i < lastSelectedRow + 1; i++) { - newRowProps[i].rowType = this.handleRowSelect(newRowProps, rowIndex); + renderDialog() { + switch(this.state.currentDialog) { + case enums.DialogType.ExportDialog: + return ( + this.setState({ currentDialog: undefined })} + /> + ); + case enums.DialogType.FlagsDialog: + return ( + this.handleAnnotationDialogClose(newRules)} + onReturn={() => {}} + /> + ); + case enums.DialogType.SelectDialog: + return ( + + this.handleSelectDialog(selectedColumns) } - } - } else { - newRowProps[rowIndex].rowType = this.handleRowSelect(newRowProps, rowIndex); + /> + ); + case enums.DialogType.StatesDialog: + return ( + this.handleAnnotationDialogClose(newRules)} + onReturn={() => {}} + /> + ); + case enums.DialogType.StructureDialog: + const selectedRows = this.primaryViewRef.current?.state.rowProperties.filter((r) => r.isSelected).map((r) => r.index) ?? []; + if (selectedRows.length === 0) { + this.vscode.postMessage({ type: "showErrorMessage", message: 'No rows selected'}); + this.state = { ...this.state, currentDialog: undefined }; + break; } - - this.setState({ rowProperties: newRowProps, lastSelectedRow: rowIndex }); - } + return ( +
+ this.setState({currentDialog: undefined, currentSearchMatch: [], searchMatches: [], comparisonSearchMatches: []})} + onSearchResults={(searchMatches: number[][], comparisonSearchMatches: number[][]) => this.setState({ searchMatches, currentSearchMatch: searchMatches[0] , comparisonSearchMatches})} + onSearchHighlight={(match: number[]) => this.setState({currentSearchMatch: match})} + /> +
+ ); } } - clearRowsTypes(): RowProperty[] { - const clearedSelectedRows = this.state.rowProperties.map(() => - constructNewRowProperty(true, SelectedRowType.None), - ); - return clearedSelectedRows; - } - - handleStructureUpdate(isClosing: boolean) { - const clearedSelectedRows = this.clearRowsTypes(); - - this.setState({ - showStructureDialog: !isClosing, - rowProperties: clearedSelectedRows, - loadedStructureDefinition: null, - structureMatches: [], - currentStructureMatchIndex: null, - currentStructureMatch: [], - logFile: this.state.logFile.updateLogFile(this.state.rules, []) - }); + renderHeaderButtons() { + return ( + <> + this.setState({ currentDialog: enums.DialogType.SelectDialog })} + > + Choose Columns + + this.exportData(this.state.searchMatches.flatMap((m) => m))} + > + Export + + this.showCompareFileDialog()} + > + {this.state.comparisonFile ? 'End Comparison' : 'Compare'} + +
+ this.setState({ currentDialog: enums.DialogType.StructureDialog })} + /> + this.setState({ currentDialog: enums.DialogType.FlagsDialog })} + /> + this.setState({ currentDialog: enums.DialogType.StatesDialog })} + /> + this.setState(({ coloredTable }) => ({ coloredTable: !coloredTable }))} + /> + { + this.state.logFile.dateTimeColumn() !== undefined && ( + this.handleSetRelativeTimeStartingPoint()} + /> + ) + } + { + this.state.comparisonFile && ( + this.sideBySideAlignmentHandler.toggleSynchonizedScrolling()} + /> + ) + } +
+ + ) } - handleStructureMatching(expression: string) { - const rowProperties = this.clearRowsTypes(); - const { logFileAsString, logEntryCharIndexMaps } = this.state; - let { currentStructureMatch, currentStructureMatchIndex } = this.state; - - const structureMatches = useStructureRegularExpressionSearch( - expression, - logFileAsString, - logEntryCharIndexMaps!, - ); - - if (structureMatches.length >= 1) { - currentStructureMatchIndex = 0; - currentStructureMatch = structureMatches[0]; + renderMinimapHeaders () { + if (this.state.comparisonFile) { + return ( + <> + + + + ) } else { - currentStructureMatchIndex = null; - currentStructureMatch = []; + return ( + + ) } - - this.setState({ - rowProperties, - structureMatches, - currentStructureMatch, - currentStructureMatchIndex, - logFile: this.state.logFile.updateLogFile(this.state.rules, structureMatches) - }); } - handleNavigation(isGoingForward: boolean, isStructureMatching: boolean) { - let matches, currentMatchIndex; - if (!isStructureMatching) { - matches = this.state.searchMatches; - currentMatchIndex = this.state.currentSearchMatchIndex; - } - else { - matches = this.state.structureMatches; - currentMatchIndex = this.state.currentStructureMatchIndex; - } - - if (currentMatchIndex !== null) { - if (isGoingForward) { - currentMatchIndex = - currentMatchIndex < matches.length - 1 - ? currentMatchIndex + 1 - : 0; - } else { - currentMatchIndex = - currentMatchIndex > 0 - ? currentMatchIndex - 1 - : matches.length - 1; - } - - if (!isStructureMatching) { - this.setState({ - currentSearchMatchIndex: currentMatchIndex, - currentSearchMatch: matches[currentMatchIndex] - }); - } - else { - this.setState({ - currentStructureMatch: matches[currentMatchIndex], - currentStructureMatchIndex: currentMatchIndex, - }); - } - } - } + renderHeader() { + const { logFile, selectedColumns, showMinimapHeader } = this.state; + const minimapHeadersPrimary = getVisibleColumnsMinimap(logFile, selectedColumns); + const minimapWidthPrimary = minimapHeadersPrimary.length * constants.MINIMAP_COLUMN_WIDTH; + const headerHight = showMinimapHeader ? "100px" : "30px"; - updateSegmentation(collapsibleRows: { [key: number]: Segment }) { - this.setState({ collapsibleRows }); + return ( + + ) } - switchBooleanState(name: string) { - switch (name) { - case "coloredTable": - this.setState(({ coloredTable }) => ({ coloredTable: !coloredTable })); - break; - case "reSearch": - this.setState(({ reSearch }) => ({ reSearch: !reSearch })); - this.updateSearchField(); - break; - case "wholeSearch": - this.setState(({ wholeSearch }) => ({ wholeSearch: !wholeSearch })); - this.updateSearchField(); - break; - case "caseSearch": - this.setState(({ caseSearch }) => ({ caseSearch: !caseSearch })); - this.updateSearchField(); - break; - case "filterSearch": - this.setState(({ filterSearch }) => ({ filterSearch: !filterSearch })); - break; + render() { + // logFile is initialized with an empty object + if (this.state.logFile.isEmpty()) { + return undefined; } - } - exportData(exportIndices: number[], structureExport: boolean) { - var exportObjects: Object[] = [] - const originalColumns = this.state.logFile.headers; - if (!structureExport) - if (exportIndices.length === 0 || this.state.filterSearch === false) - exportIndices = Array.from(Array(this.state.logFile.amountOfRows()).keys()) - for (var index of exportIndices) { - var rowObject = {}; - const row = this.state.logFile.rows[index] - for (var columnIndex = 0; columnIndex <= originalColumns.length - 1; columnIndex++) - rowObject[originalColumns[columnIndex].name] = row[columnIndex]; - exportObjects.push(rowObject) - } - this.vscode.postMessage({ type: "exportData", data: exportObjects }); - } + const headerHight = this.state.showMinimapHeader ? "100px" : "30px"; - render() { - const minimapCounter = this.state.logFile.selectedColumnsMini.filter(Boolean).length; - const minimapWidth = Math.max(6, minimapCounter) * MINIMAP_COLUMN_WIDTH; - const minimapHeight = this.state.showMinimapHeader ? "12%" : "5%"; - - const allColumns = [ - "All", - ...this.state.logFile.contentHeaders, - ...this.state.rules.map((i) => i.column), - ]; return (
{ boxSizing: "border-box", }} > - + { + this.renderHeader() + }
-
- + this.setState({ logViewState })} - forwardRef={this.child} - filterSearch={this.state.filterSearch} + initialLogViewState={this.storedState?.primaryView} + onLogViewStateChanged={this.handlePrimaryLogViewStateChange} + onMinimapVisibleItemsChanged={this.sideBySideAlignmentHandler.handlePrimaryMinimapScaleChange} + onRequestColors={this.colorMappingHandler.getPrimaryColors} + + // UI settings coloredTable={this.state.coloredTable} - rowProperties={this.state.rowProperties} - structureMatches={this.state.structureMatches} - currentStructureMatch={this.state.currentStructureMatch} + selectedColumns={this.state.selectedColumns} + alignMinimap="right" + + // Search related + searchMatches={this.state.searchMatches} + filterSearch={this.state.filterSearch} currentSearchMatch={this.state.currentSearchMatch} - onSelectedRowsChanged={(index, e) => this.handleSelectedLogRow(index, e)} - onRowPropsChanged={(index, isRendered) => this.handleRowCollapse(index, isRendered)} - collapsibleRows={this.state.collapsibleRows} - clearSegmentation={() => this.updateSegmentation({})} />
-
-
- Create a structure from selected rows} - placement="bottom" - arrow - > - this.handleStructureDialog(false)} - > - - - - Create/Modify Flag Annotations Columns} - placement="bottom" - arrow - > - this.setState({ showFlagsDialog: true })} - > - - - - Create/Modify State-Based Annotation Columns} - placement="bottom" - arrow - > - this.setState({ showStatesDialog: true })} - > - - - - this.switchBooleanState("coloredTable")} - > - - -
- {this.state.logViewState && ( - this.setState({ logViewState })} - forwardRef={this.child} - rowProperties={this.state.rowProperties} - /> - )} -
- {this.state.showExportDialog && ( - this.setState({ showExportDialog: false })} - /> - )} - {this.state.showStatesDialog && ( - this.handleAnnotationDialog(newRules, true)} - onReturn={(newRules) => this.handleAnnotationDialog(newRules, false)} - /> - )} - {this.state.showFlagsDialog && ( - this.handleAnnotationDialog(newRules, true)} - onReturn={(newRules) => this.handleAnnotationDialog(newRules, false)} - /> - )} - {this.state.showSelectDialog && ( - - this.handleSelectDialog(selectedColumns, selectedColumnsMini, true) - } - /> - )} -
-
- {this.state.showStructureDialog && ( - this.handleStructureDialog(true)} - onStructureUpdate={() => this.handleStructureUpdate(false)} - onMatchStructure={(expression) => this.handleStructureMatching(expression)} - onNavigateStructureMatches={(isGoingForward) => this.handleNavigation(isGoingForward, true)} - onStructureDefinitionSave={(structureDefinitionString) => this.handleSavingStuctureDefinition(structureDefinitionString)} - onStructureDefinitionLoad={() => {this.handleLoadingStructureDefinition()}} - onDefineSegment={(collapsibleRows) => this.updateSegmentation(collapsibleRows)} - onExportStructureMatches={() => this.exportData(this.state.structureMatches.flat(1), true)} - /> - )} + { + this.state.comparisonFile && ( +
+ +
+ ) + }
+ { + this.renderDialog() + }
); } diff --git a/src/viewer/components/LogView.tsx b/src/viewer/components/LogView.tsx new file mode 100644 index 0000000..def5892 --- /dev/null +++ b/src/viewer/components/LogView.tsx @@ -0,0 +1,378 @@ +import React from "react"; +import { + getHeaderColumnInnerStyle, + getHeaderColumnStyle, + getLogViewRowSelectionStyle +} from "../hooks/useStyleManager"; +import { ColumnProperty, ColumnSelection, LogViewState, RowProperty } from "../interfaces"; +import LogFile from "../lib/LogFile"; +import ReactResizeDetector from "react-resize-detector"; +import RowSelectionHandler from "../lib/RowSelectionHandler"; +import { logDataToString } from "../hooks/useTracyLogData"; +import { constants } from "../constants"; +import { enums } from "../enums"; +import { isColumnVisibleLogView } from "../hooks/useColumnSelection"; +import { types } from "../types"; +import { createDefaultRowProperty } from "../hooks/useRowProperty"; +import { isLight } from "../hooks/useColor"; + +interface Props { + logFile: LogFile; + columnSelection: ColumnSelection; + previousSessionLogView?: LogViewState; + forwardRef: React.RefObject; + filterSearch: boolean; + coloredTable: boolean; + rowProperties: RowProperty[]; + currentSearchMatch?: number; + onRequestColors: (column: string) => string[]; + onLogViewStateChanged: (trigger: enums.EventTrigger, state: LogViewState) => void; + onSelectedRowsChanged: (selection: number[]) => void; +} + +interface State { + state?: LogViewState; + logFile: LogFile; + columnWidth: { [id: string]: number }; + isLoadingSavedState: boolean; +} + +const HEADER_STYLE: React.CSSProperties = { + width: "100%", + height: constants.LOG_HEADER_HEIGHT, + position: "relative", + overflow: "hidden", + borderBottom: constants.BORDER, +}; + +const VIEWPORT_STYLE: React.CSSProperties = { position: "relative", flex: 1, overflow: "auto" }; + +export default class LogView extends React.Component { + private readonly viewport: React.RefObject; + private readonly rowSelectionHandler: RowSelectionHandler; + + constructor(props: Props) { + super(props); + this.viewport = this.props.forwardRef; + this.rowSelectionHandler = new RowSelectionHandler(props.onSelectedRowsChanged); + this.updateState = this.updateState.bind(this); + this.state = { + columnWidth: this.getInitialColumnWidth(props.logFile), + logFile: this.props.logFile, + isLoadingSavedState: false + }; + } + + componentDidMount(): void { + window.addEventListener("resize", () => this.updateState()); + + // Initial update needed to initialize LogViewState + this.updateState(); + this.loadState(); + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly): void { + const { logFile, currentSearchMatch, rowProperties } = this.props; + if (prevProps.logFile !== logFile) { + this.updateState(); + } + // Responsible for making sure the current search match is always visible in the log view + if (currentSearchMatch !== undefined && prevProps.currentSearchMatch !== currentSearchMatch && this.state.state) { + // Index in logView can be different to the index of in the log file if some rows are not rendered (e.g. when filter search is used) + const logViewIndex = rowProperties.filter((r) => r.isRendered).findIndex((r) => r.index === currentSearchMatch); + + const { start, end } = this.state.state; + // Check if search match is visible + if (logViewIndex < start || logViewIndex > end) { + this.updateState(logViewIndex); + } + } + if (this.viewport.current && this.props.previousSessionLogView && this.state.isLoadingSavedState) { + this.viewport.current.scrollTop = this.props.previousSessionLogView.scrollTop; + this.setState({isLoadingSavedState:false}); + } + if (prevProps.filterSearch !== this.props.filterSearch) { + this.updateState(this.state.state?.start); + } + // scrollTop has been changed by updateState with currentMatchFirstRow + if (this.viewport.current && this.state.state && this.state.state.scrollTop !== this.viewport.current.scrollTop) { + this.viewport.current.scrollTo({top: this.state.state.scrollTop}); + } + } + + private getInitialColumnWidth(logFile: LogFile) { + const customColumns = logFile.getCustomColumns(); + const widths = {}; + for (let i = 0; i < customColumns.length; i++) { + const column = customColumns[i]; + widths[column.header] = column.width ?? constants.LOG_DEFAULT_COLUMN_WIDTH; + } + + return widths; + } + + clearSelection = () => this.rowSelectionHandler.clearSelection(); + + renderRows(selectedColumns: ColumnProperty[]) { + // This method only renders the rows that are visible + if (!this.state.state) return; + const { + start, + end + } = this.state.state; + + let firstRender = Math.floor(start); + let lastRender = Math.ceil(end); + let extraPaddingTop = start < 0 ? (-start) : 0; + + const { + logFile, + rowProperties + } = this.props; + + const visibleRows = rowProperties.filter((r) => r.isRendered); + + const result: React.JSX.Element[] = []; + for (let r = firstRender; r <= lastRender; r++) { + const rowProperty = visibleRows[r] ?? createDefaultRowProperty(-1); + result.push( + this.rowSelectionHandler.handleLogRowClick(rowProperty.index, event)} + /> + ); + } + return result; + } + + loadState() { + if (!this.props.previousSessionLogView) return; + this.setState({ state: this.props.previousSessionLogView, isLoadingSavedState: true }); + this.props.onLogViewStateChanged(enums.EventTrigger.Initalisation, this.props.previousSessionLogView); + } + + handleScroll(scrollTop: number, scrollLeft: number) { + if (this.state.state?.scrollTop !== scrollTop || this.state.state?.scrollLeft !== scrollLeft) { + this.updateState(); + } + } + + updateState(currentMatchFirstRow?: number) { + if (!this.viewport.current) return; + + const logRows = this.props.logFile.amountOfRows(); + const height = this.viewport.current.clientHeight; + const maxVisibleItems = height / constants.LOG_ROW_HEIGHT; + const visibleItems = Math.min(logRows, maxVisibleItems); + + let scrollTop: number; // Number of pixels that the content is scrolled vertically + let start: number; // First visible item, can be decimal if an item is half shown + let trigger: enums.EventTrigger; + + // Set values based on currentMatchFirstRow + if (currentMatchFirstRow !== undefined) { + trigger = enums.EventTrigger.LogViewJump; + scrollTop = currentMatchFirstRow * constants.LOG_ROW_HEIGHT; + start = currentMatchFirstRow; + } + // Set values based on scrolling + else { + trigger = enums.EventTrigger.UserScroll; + scrollTop = this.viewport.current.scrollTop; + start = scrollTop / constants.LOG_ROW_HEIGHT; + } + + // Number of pixels that the content is scrolled horizontally + const scrollLeft = this.viewport.current.scrollLeft; + const end = start + maxVisibleItems - 1; + + const state: LogViewState = { + height, + scrollLeft, + scrollTop, + start, + end, + visibleItems + }; + + this.setState({ state }); + this.props.onLogViewStateChanged(trigger, state); + } + + setColumnWidth(name: string, width: number) { + //update the state for triggering the render + this.setState((prevState) => { + const columnWidth = { ...prevState.columnWidth }; + columnWidth[name] = width; + return { columnWidth }; + }); + } + + render() { + const selection = getSelection(); + + if (selection !== null) { + // empty unwanted text selection resulting from Shift-click + selection.empty(); + } + + const { logFile, columnSelection, rowProperties } = this.props; + + const selectedColumns: ColumnProperty[] = logFile.getAllHeaders() + .map((h, i) => ({ + index: i, + name: h, + isRendered: isColumnVisibleLogView(h, logFile, columnSelection), + width: this.state.columnWidth[h] ?? constants.LOG_DEFAULT_COLUMN_WIDTH, + colors: this.props.onRequestColors(h) + })) + .filter((h) => h.isRendered); + + const containerHeight = rowProperties.filter((r) => r.isRendered).length * constants.LOG_ROW_HEIGHT; + const containerWidth = + selectedColumns.length * constants.BORDER_SIZE + // All vertical deviders + selectedColumns.reduce((sum, c) => sum + c.width, + 0, + ); + + return ( +
+
this.setColumnWidth(header, width)} + /> +
this.handleScroll(e.currentTarget.scrollTop, e.currentTarget.scrollLeft)}> +
+ {this.renderRows(selectedColumns)} +
+
+
+ ); + } +} + +function Cell( + props: { + value: types.TracyLogData, + width: number, + colorMap?: string + } +) { + let color = "transparent"; + let fontColor = ""; + + if (props.colorMap) { + color = props.colorMap; + if (isLight(color)) { + fontColor = "#000000"; + } else { + fontColor = "#ffffff"; + } + } + + const columnHeaderStyle = getHeaderColumnStyle(props.width, constants.LOG_ROW_HEIGHT); + const columnHeaderInnerStyle = getHeaderColumnInnerStyle(constants.LOG_ROW_HEIGHT, false); + const colorStyle: React.CSSProperties = { backgroundColor: color, color: fontColor }; + const innerStyle = { ...columnHeaderInnerStyle, ...colorStyle }; + + return ( +
+
{logDataToString(props.value)}
+
+ ); +} + +function HeaderCell( + props: { + value: string, + width: number, + onWidthChange: (newWidth: number) => void + } +) { + const columnHeaderStyle = getHeaderColumnStyle(props.width, constants.LOG_HEADER_HEIGHT); + const columnHeaderInnerStyle = getHeaderColumnInnerStyle(constants.LOG_HEADER_HEIGHT, true); + + return ( + newWidth && props.onWidthChange(newWidth)} + > +
+
{props.value}
+
+
+ ); +} + +function Row( + props: { + viewIndex: number, + rowProperties: RowProperty, + selectedColumns: ColumnProperty[], + row: types.TracyLogData[], + coloredTable: boolean, + onClick: (event: React.MouseEvent) => void + } +) { + const rowStyle = getLogViewRowSelectionStyle(props.rowProperties, props.viewIndex); + + return ( +
+
+ { + props.selectedColumns.map((c, i) => ( + + )) + } +
+
+ ); +} + +function Header( + props: { + selectedColumns: ColumnProperty[], + width: number, + scrollLeft?: number, + onColumnWidthChange: (header: string, newWidth: number) => void + } +) { + const style: React.CSSProperties = { + width: props.width, + height: "100%", + position: "absolute", + left: props.scrollLeft ? props.scrollLeft * -1 : 0, + }; + return ( +
+
+ { + props.selectedColumns.map((c, i) => ( + props.onColumnWidthChange(c.name, newWidth)} + /> + )) + } +
+
+ ); +} \ No newline at end of file diff --git a/src/viewer/components/LogViewAndMinimap.tsx b/src/viewer/components/LogViewAndMinimap.tsx new file mode 100644 index 0000000..8b36c0a --- /dev/null +++ b/src/viewer/components/LogViewAndMinimap.tsx @@ -0,0 +1,183 @@ +import React from "react"; +import LogView from "./LogView"; +import MinimapView from "./minimap/MinimapView"; +import LogFile from "../lib/LogFile"; +import { LogViewState, RowProperty, ColumnSelection } from "../interfaces"; +import { constants } from "../constants"; +import { enums } from "../enums"; +import { createDefaultRowProperties } from "../hooks/useRowProperty"; +import { getVisibleColumnsMinimap } from "../hooks/useColumnSelection"; + +interface State { + logViewState?: LogViewState; + visibleItemsMinimap?: number; + rowProperties: RowProperty[]; +} + +interface Props { + logFile: LogFile; + initialLogViewState?: LogViewState; + onLogViewStateChanged?: (trigger: enums.EventTrigger, state: LogViewState) => void; + onMinimapVisibleItemsChanged?: (trigger: enums.EventTrigger, visibleItems: number) => void; + onRequestColors: (column: string) => string[]; + + // UI settings + coloredTable: boolean; + selectedColumns: ColumnSelection; + alignMinimap: 'left' | 'right'; + + // Search related + searchMatches: number[][]; + filterSearch: boolean; + currentSearchMatch: number[]; // A match can consist of multiple rows +} + +export default class LogViewAndMinimap extends React.Component { + private child = React.createRef(); + private logViewRef = React.createRef(); + + constructor(props: Props) { + super(props) + + this.handleLogViewStateChanged = this.handleLogViewStateChanged.bind(this); + this.handleSelectedRowsChanged = this.handleSelectedRowsChanged.bind(this); + + const rowProperties = createDefaultRowProperties(props.logFile.amountOfRows()); + this.state = { rowProperties, logViewState: props.initialLogViewState }; + } + + componentDidUpdate(prevProps: Props, prevState: State) { + const { searchMatches, filterSearch, currentSearchMatch } = this.props; + if (prevProps.searchMatches !== searchMatches + || prevProps.filterSearch !== filterSearch) { + + this.updateVisibleSearchMatches(searchMatches.flatMap((m) => m), filterSearch); + } + + if (prevProps.currentSearchMatch !== currentSearchMatch) { + this.updateHighlightedMatche(currentSearchMatch); + } + } + + setLogViewStart(start: number) { + this.logViewRef.current?.updateState(start); + } + + setMinimapVisibleItems(trigger: enums.EventTrigger, visibleItems: number) { + this.setState({visibleItemsMinimap: visibleItems}); + + if (this.props.onMinimapVisibleItemsChanged) { + this.props.onMinimapVisibleItemsChanged(trigger, visibleItems); + } + } + + clearSelection() { + this.logViewRef.current?.clearSelection(); + } + + updateVisibleSearchMatches(searchMatches: number[], filterSearch: boolean) { + const rowProperties: RowProperty[] = [... this.state.rowProperties]; + for (let i = 0; i < rowProperties.length; i++) { + const queried = searchMatches.includes(i); + rowProperties[i].isQueried = queried; + rowProperties[i].isRendered = !filterSearch || queried; + } + + this.setState({ rowProperties }); + } + + updateHighlightedMatche(match: number[]) { + const rowProperties: RowProperty[] = [... this.state.rowProperties]; + for (let i = 0; i < rowProperties.length; i++) { + rowProperties[i].isHighlighted = match.includes(i); + } + + this.setState({ rowProperties }); + } + + handleSelectedRowsChanged(selection: number[]) { + const rowProperties: RowProperty[] = [...this.state.rowProperties]; + for (let i = 0; i < rowProperties.length; i++) { + rowProperties[i].isSelected = selection.includes(i); + } + + this.setState({ rowProperties }) + } + + handleLogViewStateChanged(trigger: enums.EventTrigger, state: LogViewState) { + this.setState({ logViewState: state }); + + if (this.props.onLogViewStateChanged) { + this.props.onLogViewStateChanged(trigger, state); + } + } + + renderLogView() { + return ( +
+ +
+ ); + } + + renderMinimap() { + const minimapHeaders = getVisibleColumnsMinimap(this.props.logFile, this.props.selectedColumns); + const minimapWidth = minimapHeaders.length * constants.MINIMAP_COLUMN_WIDTH; + + return ( +
+
+ {this.state.logViewState && ( + this.setMinimapVisibleItems(enums.EventTrigger.UserScroll, items)} + /> + )} +
+ ); + } + + render() { + if (this.props.alignMinimap === 'left') { + return ( + <> + { this.renderMinimap() } + { this.renderLogView() } + + ) + } else { + return ( + <> + { this.renderLogView() } + { this.renderMinimap() } + + ) + } + } +} \ No newline at end of file diff --git a/src/viewer/components/SearchBar.tsx b/src/viewer/components/SearchBar.tsx new file mode 100644 index 0000000..03eee1c --- /dev/null +++ b/src/viewer/components/SearchBar.tsx @@ -0,0 +1,280 @@ +import { Tooltip } from "@mui/material"; +import { VSCodeDropdown, VSCodeOption, VSCodeTextField, VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import React from "react"; +import { returnSearchIndices } from "../hooks/useLogSearchManager"; +import LogFile from "../lib/LogFile"; +import { toDataGrid } from "../hooks/useLogFile"; + +interface Props { + logFile: LogFile; + comparisonLogFile?: LogFile; + initialState?: State; + onClear: () => void; + onSearchResults: (filterSearch: boolean, searchMatches: number[], comparisonSearchMatches?: number[]) => void; + onSearchHighlight: (resultIndex: number) => void; + onStateChanged?: (newState: State) => void; +} + +export interface State { + searchColumn: string; + searchText: string; + + // Search options + reSearch: boolean; // Regex search + wholeSearch: boolean; // Whole word search + caseSearch: boolean; // case sensitive + filterSearch: boolean; // Filter out none matching lines + + primarySearchMatches: number[]; + comparisonSearchMatches?: number[]; + currentSearchMatchIndex?: number; +} + +const ALL_COLUMNS: string = "All"; +const DEFAULT_SEARCH_STATE: State = { + searchColumn: ALL_COLUMNS, + searchText: '', + reSearch: false, + wholeSearch: false, + caseSearch: false, + filterSearch: false, + primarySearchMatches: [], + comparisonSearchMatches: [], + currentSearchMatchIndex: undefined +} + +export default class SearchBar extends React.Component { + constructor(props: Props) { + super(props); + this.updateSearchMatches = this.updateSearchMatches.bind(this); + + if (props.initialState) { + this.state = props.initialState; + this.props.onSearchResults(this.state.filterSearch, this.state.primarySearchMatches, this.state.comparisonSearchMatches); + } else { + this.state = DEFAULT_SEARCH_STATE; + } + } + + componentDidUpdate(prevProps: Props, prevState: State) { + const searchProperties = (state: State) => { + return { + searchColumn: state.searchColumn, + reSearch: state.reSearch, + wholeSearch: state.wholeSearch, + caseSearch: state.caseSearch + } + } + + const searchProps = searchProperties(this.state); + const prevSearchProps = searchProperties(prevState); + // This seems to be the only way to compare two objects in js + if (JSON.stringify(searchProps) !== JSON.stringify(prevSearchProps)) { + this.updateSearchMatches(); + } + + if (this.props.onStateChanged) { + this.props.onStateChanged(this.state); + } + } + + clearSearchField() { + this.setState({ + searchText: '', + filterSearch: false, + primarySearchMatches: [], + comparisonSearchMatches: [], + currentSearchMatchIndex: undefined + }); + this.props.onClear(); + } + + updateSearchMatches() { + // Empty search field + if (!this.state.searchText) { + this.clearSearchField(); + return; + } + + const { filterSearch } = this.state; + const { logFile, comparisonLogFile } = this.props; + + let primarySearchMatches = this.preformSearchQuery(logFile) ?? []; + let comparisonSearchMatches = comparisonLogFile ? this.preformSearchQuery(comparisonLogFile) : []; + + this.props.onSearchResults(filterSearch, primarySearchMatches, comparisonSearchMatches); + + this.setState({ + primarySearchMatches, + comparisonSearchMatches, + currentSearchMatchIndex: 0 + }); + } + + preformSearchQuery(logFile: LogFile): number[] | undefined { + const { searchColumn, searchText, reSearch, wholeSearch, caseSearch } = this.state; + + const searchAllColums = searchColumn === ALL_COLUMNS; + + if (searchAllColums) { + return returnSearchIndices( + toDataGrid(logFile), + searchText, + reSearch, + wholeSearch, + caseSearch, + ); + } else { + return returnSearchIndices( + logFile.values(searchColumn), + searchText, + reSearch, + wholeSearch, + caseSearch, + ); + } + } + + handleNavigation(isGoingForward: boolean) { + let matches = this.state.primarySearchMatches; + let currentMatchIndex = this.state.currentSearchMatchIndex; + + if (currentMatchIndex !== undefined) { + if (isGoingForward) { + currentMatchIndex = + currentMatchIndex < matches.length - 1 + ? currentMatchIndex + 1 + : 0; + } else { + currentMatchIndex = + currentMatchIndex > 0 + ? currentMatchIndex - 1 + : matches.length - 1; + } + + this.setState({ + currentSearchMatchIndex: currentMatchIndex + }); + this.props.onSearchHighlight(currentMatchIndex); + } + } + + setFilterSearch(enabled: boolean) { + this.setState({filterSearch: enabled}); + this.props.onSearchResults( + enabled, + this.state.primarySearchMatches, + this.state.comparisonSearchMatches + ); + } + + render() { + const selectionColumns = [...new Set([ + ALL_COLUMNS, + ...this.props.logFile.getAllHeaders(), + ...this.props.comparisonLogFile?.getAllHeaders() ?? [] + ])]; + + return ( + <> + this.setState({searchColumn: e.target.value})} + > + {selectionColumns.map((col, col_i) => ( + + {col} + + ))} + + {if (e.key === 'Enter') this.updateSearchMatches()}} + onInput={(e) => this.setState({searchText: e.target.value})} + > + Match Case} placement="bottom" arrow> + this.setState(({ caseSearch }) => ({ caseSearch: !caseSearch }))} + > + + Match Whole Word} placement="bottom" arrow> + this.setState(({ wholeSearch }) => ({ wholeSearch: !wholeSearch }))} + > + + Use Regular Expression} placement="bottom" arrow> + this.setState(({ reSearch }) => ({ reSearch: !reSearch }))} + > + + Clear} placement="bottom" arrow> + this.clearSearchField()} + > + + + {" "} + {this.state.primarySearchMatches.length === 0 + ? "No Results" + : `${this.state.currentSearchMatchIndex! + 1} of ${this.state.primarySearchMatches.length}` + } + this.handleNavigation(false)} + > + + + this.handleNavigation(true)} + > + + + this.setFilterSearch(!this.state.filterSearch) } + > + + + + ); + } +} diff --git a/src/viewer/components/TracyIconButton.tsx b/src/viewer/components/TracyIconButton.tsx new file mode 100644 index 0000000..95f79bf --- /dev/null +++ b/src/viewer/components/TracyIconButton.tsx @@ -0,0 +1,39 @@ +import { Tooltip } from "@mui/material"; +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import React from "react"; + +interface Props { + tooltip: string; + icon: IconType; + onClick: () => void; +} + +export enum IconType { + Tag = 'codicon codicon-tag', + SettingsGear = 'codicon codicon-settings-gear', + SymbolColor = 'codicon codicon-symbol-color', + ArrowUp = 'codicon codicon-arrow-up', + ArrowDown = 'codicon codicon-arrow-down', + Link = 'codicon codicon-link', + Calendar = 'codicon codicon-calendar', + ThreeBars = 'codicon codicon-three-bars' +} + +export default class TracyIconButton extends React.Component { + render() { + return ( + {this.props.tooltip}} + placement="bottom" + arrow={true} + > + + + + + ); + } +} \ No newline at end of file diff --git a/src/viewer/components/dialogs/ExportDialog.tsx b/src/viewer/components/dialogs/ExportDialog.tsx new file mode 100644 index 0000000..4a76153 --- /dev/null +++ b/src/viewer/components/dialogs/ExportDialog.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; + +interface Props { + filepath: string; + onClose: () => void; +} + +interface State { +} + +const BACKDROP_STYLE: React.CSSProperties = { + height: "100vh", + width: "100vw", + backgroundColor: "#00000030", + position: "absolute", + display: "flex", + justifyContent: "center", + alignItems: "center", +}; + +const DIALOG_STYLE: React.CSSProperties = { + height: "100px", + width: "600px", + padding: "5px", + display: "flex", + overflow: "auto", + zIndex: 100 +}; + +export default class ExportDialog extends React.Component { + constructor(props: Props) { + super(props); + } + + + render() { + return ( +
+
+
+
+

Data has been successfully exported to filepath:

+

{this.props.filepath}

+
+ + this.props.onClose()} + > + Ok + +
+
+
+ ); + } +} diff --git a/src/viewer/components/dialogs/FlagsDialog.tsx b/src/viewer/components/dialogs/FlagsDialog.tsx new file mode 100644 index 0000000..93a4341 --- /dev/null +++ b/src/viewer/components/dialogs/FlagsDialog.tsx @@ -0,0 +1,292 @@ +import React from "react"; +import LogFile from "../../lib/LogFile"; +import { + VSCodeButton, + VSCodeTextField, + VSCodeDropdown, + VSCodeOption, +} from "@vscode/webview-ui-toolkit/react"; +import Rule from "../../rules/Rule"; +import FlagRule from "../../rules/FlagRule"; +import StateBasedRule from "../../rules/StateBasedRule"; +import Table from "../../rules/Tables/Table"; + +interface Props { + onReturn: (rules: Rule[]) => void; + onClose: (rules: Rule[]) => void; + initialRules: Rule[]; + logFile: LogFile; +} + +interface State { + showEdit: boolean; + rules: Rule[]; + selectedRule: number; +} + +const BACKDROP_STYLE: React.CSSProperties = { + height: "100vh", + width: "100vw", + backgroundColor: "#00000030", + position: "absolute", + display: "flex", + justifyContent: "center", + alignItems: "center", +}; + +const DIALOG_STYLE: React.CSSProperties = { + height: "95%", + width: "95%", + padding: "10px", + display: "flex", + flexDirection: "column", + overflow: "auto", + zIndex: 100 +}; + +export default class FlagsDialog extends React.Component { + constructor(props: Props) { + super(props); + this.state = { showEdit: false, selectedRule: -1, rules: props.initialRules }; + } + + updateRule(rule: Rule, index: number) { + const rules = [...this.state.rules]; + if (rules[index].column != rule.column) { + for (let i = 0; i < rules.length; i++) { + if (rules[i].ruleType === "Flag rule") { + const updateRule = rules[i] as FlagRule; + const updatedFlags = updateRule.flags; + for (let j = 0; j < updatedFlags.length; j++) + for (let k = 0; k < updatedFlags[j].conditions.length; k++) + for (let l = 0; l < updatedFlags[j].conditions[k].length; l++) + if (updatedFlags[j].conditions[k][l].Column === rules[index].column) + updatedFlags[j].conditions[k][l].Column = rule.column; + updateRule.setFlags(updatedFlags); + rules[i] = updateRule; + } else if (rules[i].ruleType === "State based rule") { + const updatedRule = rules[i] as StateBasedRule; + const updatedStates = updatedRule.ruleStates; + for (let j = 0; j < updatedStates.length; j++) { + for (let k = 0; k < updatedStates[j].transitions.length; k++) { + for (let l = 0; l < updatedStates[j].transitions[k].conditions.length; l++) { + for (let m = 0; m < updatedStates[j].transitions[k].conditions[l].length; m++) { + if ( + updatedStates[j].transitions[k].conditions[l][m].Column === rules[index].column + ) + updatedStates[j].transitions[k].conditions[l][m].Column = rule.column; + } + } + } + } + updatedRule.setStates(updatedStates, updatedRule.initialStateIndex); + rules[i] = updatedRule; + } + } + } + rules[index] = rule; + this.setState({ rules }); + } + + updateFlagProperty(rule: Rule, property: string, new_value: string, index: number) { + const rules = [...this.state.rules]; + const flagRule = rule as FlagRule; + if (property === "defaultValue") rules[index] = flagRule.setDefault(new_value); + else if (property === "flagType") rules[index] = flagRule.setFlagType(new_value); + this.setState({ rules }); + } + + onDialogClick(isClose: boolean) { + const ruleIndex = this.state.selectedRule; + if (ruleIndex !== -1) { + const rule = FlagRule.cleanConditions(this.state.rules[ruleIndex].reset()); + this.updateRule(rule, ruleIndex); + } + this.setState({ selectedRule: -1, showEdit: false }, () => { + if (isClose === true) this.props.onClose(this.state.rules); + else this.props.onReturn(this.state.rules); + }); + } + + renderManage() { + const onAddAction = () => { + const newRule = new FlagRule( + `FlagRule${this.state.rules.filter((r) => r.ruleType === "Flag rule").length + 1}`, + "", + "", + "User Defined", + 0, + [], + ); + this.setState({ + rules: [...this.state.rules, newRule], + selectedRule: this.state.rules.length, + showEdit: true, + }); + }; + + const onEditAction = (tableIndex: number) => { + const index = this.state.rules.findIndex( + (x) => x === this.state.rules.filter((r) => r.ruleType === "Flag rule")[tableIndex], + ); + this.setState({ showEdit: true, selectedRule: index }); + }; + + const onDeleteAction = (tableIndex: number) => { + const index = this.state.rules.findIndex( + (x) => x === this.state.rules.filter((r) => r.ruleType === "Flag rule")[tableIndex], + ); + if (this.state.selectedRule === index) this.setState({ selectedRule: -1 }); + this.setState({ rules: this.state.rules.filter((r, i) => i !== index) }); + }; + + const tableRows = this.state.rules + .filter((r) => r.ruleType === "Flag rule") + .map((rule) => { + const flagRule = rule as FlagRule; + return [rule.column, flagRule.flagType, rule.description]; + }); + + return ( +
+ + + ); + } + + renderEdit() { + if (this.state.selectedRule === -1) return; + const ruleIndex = this.state.selectedRule; + const rule = this.state.rules[ruleIndex]; + const ruleAsFlag = rule as FlagRule; + const defaultValue = ruleAsFlag.defaultValue; + const flagType = ruleAsFlag.flagType; + const userColumns = this.state.rules + .map((r, i) => r.column) + .filter((name) => name != rule.column); + const keyWidth = "100px"; + const textFieldWidth = "250px"; + const rows = [ + [ + "Name", + + this.updateRule(rule.setColumn(e.target.value), ruleIndex) + } + />, + ], + [ + "Description", + this.updateRule(rule.setDescription(e.target.value), ruleIndex)} + />, + ], + [ + "Type", + 0} + style={{ width: textFieldWidth, marginBottom: "2px" }} + value={flagType} + key="Type" + onChange={(e) => this.updateFlagProperty(rule, "flagType", e.target.value, ruleIndex)} + > + + User Defined + + + Capture Match + + , + ], + [ + "Default Value", + this.updateFlagProperty(rule, "defaultValue", e.target.value, ruleIndex)} + />, + ], + ]; + + return ( +
+
+ {rule.renderEdit( + (newRule) => this.updateRule(newRule, ruleIndex), + keyWidth, + textFieldWidth, + userColumns, + this.props.logFile, + this.state.rules + )} + + ); + } + + render() { + return ( +
+
+
+ {!this.state.showEdit &&
Flag Annotation Columns
} + {this.state.showEdit &&
Edit Flag Annotation Column
} + {this.state.showEdit && ( + this.onDialogClick(false)} + > + + + + )} + this.onDialogClick(true)} + > + + +
+
+ {!this.state.showEdit && this.renderManage()} + {this.state.showEdit && this.renderEdit()} +
+
+
+ ); + } +} diff --git a/src/viewer/components/dialogs/SelectColDialog.tsx b/src/viewer/components/dialogs/SelectColDialog.tsx new file mode 100644 index 0000000..74aa9a9 --- /dev/null +++ b/src/viewer/components/dialogs/SelectColDialog.tsx @@ -0,0 +1,133 @@ +import React from "react"; +import LogFile from "../../lib/LogFile"; +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import { ColumnSelection } from "../../interfaces"; + +interface Props { + logFile: LogFile; + comparisonFile?: LogFile; + selectedColumns: ColumnSelection; + onClose: (selectedColumns: ColumnSelection) => void; +} + +interface State { + headers: string[]; + selectedColumns: ColumnSelection; +} + +const BACKDROP_STYLE: React.CSSProperties = { + width: "100vw", + backgroundColor: "#00000030", + position: "absolute", + padding: "10px", + zIndex: 100 +}; + +const DIALOG_STYLE: React.CSSProperties = { + height: "90", + width: "70%", + padding: "10px", + display: "flex", + flexDirection: "column", + alignItems: "start", + overflow: "auto", +}; +const innerStyle: React.CSSProperties = { + display: "flex", + height: "20px", + alignItems: "center", + justifyContent: "center", + flexDirection: "row", + paddingLeft: "2px", +}; + +export default class SelectColDialog extends React.Component { + constructor(props: Props) { + super(props); + const { logFile, comparisonFile, selectedColumns } = this.props; + + const toggleablePrimaryHeaders = logFile.getAllHeaders() + .filter((h) => logFile.customColumns[h]?.toggleable ?? true); + const toggleableComparisonHeaders = comparisonFile?.getAllHeaders() + .filter((h) => comparisonFile.customColumns[h]?.toggleable ?? true) ?? []; + + // Using a set to remove duplicate columns + const headers: string[] = [...new Set([ + ...toggleablePrimaryHeaders, + ...toggleableComparisonHeaders + ])]; + + this.state = { + selectedColumns, + headers + }; + } + + handleVisibilityChange(header: string, visibility: {logView?: boolean, miniMap?: boolean}) { + // Update a clone of the original, original is compared to new in MinimapView.componentDidUpdate() + const columns = {...this.state.selectedColumns}; + columns[header] = visibility; + this.setState({selectedColumns: columns}); + } + + render() { + return ( +
+
+
+
+
+
Table
+
Minimap
+
+ this.props.onClose(this.state.selectedColumns)}> + + +
+
+
+ { + this.state.headers.map((h, i) => ( + this.handleVisibilityChange(h, visibility)} + /> + )) + } +
+
+ ); + } +} + +function ColumnItem( + props: { + header: string, + visibility?: {logView?: boolean, miniMap?: boolean}, + onVisibilityChange: (visibility: {logView?: boolean, miniMap?: boolean}) => void + } +) { + return ( +
+
+
{props.header}
+
+ props.onVisibilityChange({logView: e.target.checked, miniMap: props.visibility?.miniMap})} + /> +
+
+ props.onVisibilityChange({logView: props.visibility?.logView, miniMap: e.target.checked})} + /> +
+
+
+ ); +} \ No newline at end of file diff --git a/src/viewer/components/dialogs/StatesDialog.tsx b/src/viewer/components/dialogs/StatesDialog.tsx new file mode 100644 index 0000000..7aa2d71 --- /dev/null +++ b/src/viewer/components/dialogs/StatesDialog.tsx @@ -0,0 +1,248 @@ +import React from "react"; +import LogFile from "../../lib/LogFile"; +import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; +import Rule from "../../rules/Rule"; +import FlagRule from "../../rules/FlagRule"; +import StateBasedRule from "../../rules/StateBasedRule"; +import Table from "../../rules/Tables/Table"; + +interface Props { + onReturn: (rules: Rule[]) => void; + onClose: (rules: Rule[]) => void; + initialRules: Rule[]; + logFile: LogFile; +} + +interface State { + showEdit: boolean; + rules: Rule[]; + selectedRule: number; +} + +const BACKDROP_STYLE: React.CSSProperties = { + height: "100vh", + width: "100vw", + backgroundColor: "#00000030", + position: "absolute", + display: "flex", + justifyContent: "center", + alignItems: "center", +}; + +const DIALOG_STYLE: React.CSSProperties = { + height: "95%", + width: "95%", + padding: "10px", + display: "flex", + flexDirection: "column", + overflow: "auto", + zIndex: 100 +}; + +export default class StatesDialog extends React.Component { + constructor(props: Props) { + super(props); + this.state = { showEdit: false, selectedRule: -1, rules: props.initialRules }; + } + + updateRule(rule: Rule, index: number) { + const rules = [...this.state.rules]; + if (rules[index].column != rule.column) { + for (let i = 0; i < rules.length; i++) { + if (rules[i].ruleType === "Flag rule") { + const updatedRule = rules[i] as FlagRule; + const updatedFlags = updatedRule.flags; + for (let j = 0; j < updatedFlags.length; j++) + for (let k = 0; k < updatedFlags[j].conditions.length; k++) + for (let l = 0; l < updatedFlags[j].conditions[k].length; l++) + if (updatedFlags[j].conditions[k][l].Column === rules[index].column) + updatedFlags[j].conditions[k][l].Column = rule.column; + updatedRule.setFlags(updatedFlags); + rules[i] = updatedRule; + } else if (rules[i].ruleType === "State based rule") { + const updatedRule = rules[i] as StateBasedRule; + const updatedStates = updatedRule.ruleStates; + for (let j = 0; j < updatedStates.length; j++) { + for (let k = 0; k < updatedStates[j].transitions.length; k++) { + for (let l = 0; l < updatedStates[j].transitions[k].conditions.length; l++) { + for (let m = 0; m < updatedStates[j].transitions[k].conditions[l].length; m++) { + if ( + updatedStates[j].transitions[k].conditions[l][m].Column === rules[index].column + ) + updatedStates[j].transitions[k].conditions[l][m].Column = rule.column; + } + } + } + } + updatedRule.setStates(updatedStates, updatedRule.initialStateIndex); + rules[i] = updatedRule; + } + } + } + rules[index] = rule; + this.setState({ rules }); + } + + onDialogClick(isClose: boolean) { + const ruleIndex = this.state.selectedRule; + if (ruleIndex !== -1) { + const rule = StateBasedRule.cleanConditions(this.state.rules[ruleIndex].reset()); + this.updateRule(rule, ruleIndex); + } + this.setState({ selectedRule: -1, showEdit: false }, () => { + if (isClose === true) this.props.onClose(this.state.rules); + else this.props.onReturn(this.state.rules); + }); + } + + renderManage() { + const onAddAction = () => { + const newRule = new StateBasedRule( + `StateRule${this.state.rules.filter((r) => r.ruleType === "State based rule").length + 1}`, + "", + 0, + 0, + 0, + [], + ); + this.setState({ + rules: [...this.state.rules, newRule], + selectedRule: this.state.rules.length, + showEdit: true, + }); + }; + + const onEditAction = (tableIndex: number) => { + const index = this.state.rules.findIndex( + (x) => x === this.state.rules.filter((r) => r.ruleType === "State based rule")[tableIndex], + ); + this.setState({ showEdit: true, selectedRule: index }); + }; + + const onDeleteAction = (tableIndex: number) => { + const index = this.state.rules.findIndex( + (x) => x === this.state.rules.filter((r) => r.ruleType === "State based rule")[tableIndex], + ); + if (this.state.selectedRule === index) this.setState({ selectedRule: -1 }); + this.setState({ rules: this.state.rules.filter((r, i) => i !== index) }); + }; + + return ( +
+
r.ruleType === "State based rule") + .map((rule) => [rule.column, rule.ruleType, rule.description])} + noRowsText={"No rules have been defined (click + to add)"} + onAddAction={onAddAction} + onEditAction={onEditAction} + onDeleteAction={onDeleteAction} + /> + + ); + } + + renderEdit() { + if (this.state.selectedRule === -1) return; + const ruleIndex = this.state.selectedRule; + const rule = this.state.rules[ruleIndex]; + const userColumns = this.state.rules + .map((r, i) => r.column) + .filter((name) => name != rule.column); + const keyWidth = "100px"; + const textFieldWidth = "250px"; + const rows = [ + [ + "Name", + + this.updateRule(rule.setColumn(e.target.value), ruleIndex) + } + />, + ], + [ + "Description", + this.updateRule(rule.setDescription(e.target.value), ruleIndex)} + />, + ], + ]; + + return ( +
+
+ {rule.renderEdit( + (newRule) => this.updateRule(newRule, ruleIndex), + keyWidth, + textFieldWidth, + userColumns, + this.props.logFile, + this.state.rules + )} + + ); + } + + render() { + return ( +
+
+
+ {!this.state.showEdit && ( +
State-Based Annotation Columns
+ )} + {this.state.showEdit && ( +
Edit State-Based Annotation Column
+ )} + {this.state.showEdit && ( + this.onDialogClick(false)} + > + + + )} + this.onDialogClick(true)} + > + + +
+
+ {!this.state.showEdit && this.renderManage()} + {this.state.showEdit && this.renderEdit()} +
+
+
+ ); + } +} diff --git a/src/viewer/components/dialogs/StructureDialog.tsx b/src/viewer/components/dialogs/StructureDialog.tsx new file mode 100644 index 0000000..bcf16fd --- /dev/null +++ b/src/viewer/components/dialogs/StructureDialog.tsx @@ -0,0 +1,689 @@ +import React from "react"; +import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip"; +import CloseIcon from "@mui/icons-material/Close"; +import IconButton from "@mui/material/IconButton"; +import StructureTable from "./structures/StructureTable"; +import { ContextMenuItem, LogEntryCharMaps, StructureEntry, Wildcard } from "../../interfaces"; +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import { useGetCharIndicesForLogEntries, useStructureQueryConstructor, useStructureRegularExpressionSearch } from "../../hooks/useStructureRegularExpressionManager"; +import { + constructStructureEntriesArray, + appendNewStructureEntries, + removeStructureEntryFromList, + toggleCellSelection, + toggleStructureLink, + removeLastStructureLink, + addWildcardToStructureEntry, + removeWildcardFromStructureEntry, + updateStructureEntriesAfterWildcardDeletion, +} from "../../hooks/useStructureEntryManager"; +import { + createWildcard, + getIndicesForWildcardFromDivId, + insertWildcardIntoCellsContents, + removeWildcardSubstitution, + removeWildcardSubstitutionsForStructureEntry, + getWildcardIndex, + removeWildcardFromCellContent, +} from "../../hooks/useWildcardManager"; +import { structureDialogBackdropStyle, structureDialogDialogStyle } from "../../hooks/useStyleManager"; +import isEqual from "react-fast-compare"; +import cloneDeep from "lodash/cloneDeep"; +import ContextMenu from "./structures/ContextMenu"; +import { styled } from "@mui/material/styles"; +import { enums } from "../../enums"; +import LogFile from "../../lib/LogFile"; +import { toStructureData } from "../../hooks/useLogFile"; +import { types } from "../../types"; + +interface Props { + logFile: LogFile; + comparisonLogFile?: LogFile; + selectedRows: number[]; + + onClose: () => void; + onSearchResults: (searchMatches: number[][], comparisonSearchMatches: number[][]) => void; + onSearchHighlight: (match: number[]) => void; +} + +interface LogFileProperties { + logFileAsString: string; + logEntryCharIndexMaps: LogEntryCharMaps; + selectedRows: types.TracyLogData[][] +} + +interface State { + wildcards: Wildcard[]; + structureEntries: StructureEntry[]; + isRemovingStructureEntries: boolean; + isLoadingStructureDefintion: boolean; + isStructureMatching: boolean; + logFileHeaders: string[]; + structureHeaderColumnsTypes: enums.StructureHeaderColumnType[]; + logFileProperties: LogFileProperties; + comparisonLogFileProperties?: LogFileProperties; + structureMatches: number[][]; + currentStructureMatchIndex?: number; +} + +export default class StructureDialog extends React.Component { + constructor(props: Props) { + super(props); + + const { logFile, comparisonLogFile } = props; + const logFileAsString = JSON.stringify(toStructureData(logFile), null, 2); + const logEntryCharIndexMaps = useGetCharIndicesForLogEntries(logFileAsString); + + let comparisonLogFileProperties: LogFileProperties | undefined; + if (comparisonLogFile) { + const comparisonLogFileAsString = JSON.stringify(toStructureData(comparisonLogFile), null, 2); + const comparisonLogEntryCharIndexMaps = useGetCharIndicesForLogEntries(comparisonLogFileAsString); + comparisonLogFileProperties = { + logFileAsString: comparisonLogFileAsString, + logEntryCharIndexMaps: comparisonLogEntryCharIndexMaps, + selectedRows: [] + } + } + + const headers = logFile.getAllHeaders(); + const headerColumnTypes: enums.StructureHeaderColumnType[] = []; + for (let header in headers) { + if (logFile.customColumns[header]) { + headerColumnTypes.push(enums.StructureHeaderColumnType.Custom); + } else { + headerColumnTypes.push(enums.StructureHeaderColumnType.Selected); + } + } + + const selectedRows = props.selectedRows + .map((i) => logFile.getRow(i)); + + let structureEntries = constructStructureEntriesArray(headerColumnTypes, selectedRows); + structureEntries = removeLastStructureLink(structureEntries); + + this.state = { + isRemovingStructureEntries: false, + isLoadingStructureDefintion: false, + isStructureMatching: false, + logFileHeaders: logFile.getAllHeaders(), + structureHeaderColumnsTypes: headerColumnTypes, + structureEntries: structureEntries, + wildcards: [], + logFileProperties: { + logFileAsString, + logEntryCharIndexMaps, + selectedRows + }, + comparisonLogFileProperties, + structureMatches: [] + }; + + //bind context for all functions used by the context and dropdown menus: + this.createWildcard = this.createWildcard.bind(this); + } + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + _nextContext: any, + ): boolean { + const isLoadingStructureDefinition = false; + const islogFileUpdating = !isEqual( + this.props.logFile, + nextProps.logFile, + ); + const isNumberOfMatchesUpdating = !isEqual( + this.state.structureMatches.length, + nextState.structureMatches.length, + ); + + const areHeaderColumnTypesUpdating = !isEqual( + this.state.structureHeaderColumnsTypes, + nextState.structureHeaderColumnsTypes, + ); + const areStateEntriesUpdating = !isEqual( + this.state.structureEntries, + nextState.structureEntries, + ); + const areWildcardsUpdating = !isEqual(this.state.wildcards, nextState.wildcards); + const isRemovingStructureEntriesUpdating = !isEqual( + this.state.isRemovingStructureEntries, + nextState.isRemovingStructureEntries, + ); + const isStructureMatchingUpdating = !isEqual( + this.state.isStructureMatching, + nextState.isStructureMatching, + ); + + if ( + isLoadingStructureDefinition || + islogFileUpdating || + isNumberOfMatchesUpdating || + areHeaderColumnTypesUpdating || + areStateEntriesUpdating || + areWildcardsUpdating || + isRemovingStructureEntriesUpdating || + isStructureMatchingUpdating + ) { + return true; + } + + return false; + } + + getContextMenuItems() { + const contextMenuItems: ContextMenuItem[] = []; + + const createWildcardItem: ContextMenuItem = { + text: "Create wildcard", + callback: () => this.createWildcard(), + }; + + contextMenuItems.push(createWildcardItem); + + if (this.state.wildcards.length > 0) { + this.state.wildcards.forEach((wc, index) => { + const useWildcardItem: ContextMenuItem = { + text: `Use wildcard ?${index + 1}`, + callback: () => this.useWildcard(index), + }; + + contextMenuItems.push(useWildcardItem); + }); + + const removeWildcardItem: ContextMenuItem = { + text: "Remove wildcard", + callback: (anchorDivId) => this.removeWildcard(anchorDivId), + }; + + contextMenuItems.push(removeWildcardItem); + } + + return contextMenuItems; + } + + removeStructureEntry(rowIndex: number) { + const { structureEntries, wildcards } = this.state; + const wildcardsCopy = cloneDeep(wildcards); + + const wildcardRemovalResults = removeWildcardSubstitutionsForStructureEntry( + wildcardsCopy, + rowIndex, + ); + const modifiedWildcards = wildcardRemovalResults.modifiedWildcards; + + const remainingEntries = removeStructureEntryFromList(structureEntries, rowIndex); + + wildcardRemovalResults.indicesOfWildcardsToBeRemoved.forEach((index) => { + updateStructureEntriesAfterWildcardDeletion(remainingEntries, modifiedWildcards, index); + }); + + if (remainingEntries.length === 0) { + this.props.onClose(); + } else { + this.setState({ + structureEntries: remainingEntries, + wildcards: modifiedWildcards, + isStructureMatching: false, + }); + } + } + + toggleIsRemovingStructureEntries() { + const isRemovingStructureEntries = this.state.isRemovingStructureEntries; + this.setState({ + isRemovingStructureEntries: !isRemovingStructureEntries, + }); + } + + toggleIsCellSelected( + structureEntryIndex: number, + cellIndex: number, + isCtrlPressed: boolean, + isShiftPressed: boolean, + ) { + if (isCtrlPressed) { + const { structureHeaderColumnsTypes, structureEntries } = this.state; + let structureEntriesCopy = cloneDeep(structureEntries); + + structureEntriesCopy = toggleCellSelection( + structureHeaderColumnsTypes, + structureEntriesCopy, + structureEntryIndex, + cellIndex, + isShiftPressed, + ); + + this.setState({ structureEntries: structureEntriesCopy }); + } + } + + toggleStructureLink(structureEntryIndex: number) { + let { structureEntries } = this.state; + const structureEntriesCopy = cloneDeep(structureEntries); + + structureEntries = toggleStructureLink(structureEntriesCopy, structureEntryIndex); + + this.setState({ structureEntries: structureEntries }); + } + + matchStructure() { + // pass list of wildcards and use those in regular expression construction + const structureRegExp = useStructureQueryConstructor( + this.state.logFileHeaders, + this.state.structureHeaderColumnsTypes, + this.state.structureEntries, + this.state.wildcards, + ); + + this.handleStructureMatching(structureRegExp) + this.setState({ isStructureMatching: true }); + } + + handleStructureMatching(expression: string) { + const primaryMatches = this.findStructureMatches(expression, this.state.logFileProperties); + const comparisonMatches = (this.state.comparisonLogFileProperties && this.findStructureMatches(expression, this.state.comparisonLogFileProperties)) ?? []; + let currentStructureMatchIndex: number | undefined; + let currentStructureMatch: number[]; + + if (primaryMatches.length >= 1) { + currentStructureMatchIndex = 0; + currentStructureMatch = primaryMatches[0]; + } else { + currentStructureMatchIndex = undefined; + currentStructureMatch = []; + } + + this.props.onSearchResults(primaryMatches, comparisonMatches); + + this.setState({ + structureMatches: primaryMatches, + currentStructureMatchIndex + }); + } + + findStructureMatches(expression: string, logFileProperties: LogFileProperties) { + const { logFileAsString, logEntryCharIndexMaps } = logFileProperties; + + return useStructureRegularExpressionSearch( + expression, + logFileAsString, + logEntryCharIndexMaps, + ); + } + + handleStructureNavigation(isGoingForward: boolean) { + const { structureMatches } = this.state; + const matchCount = structureMatches.length; + let currentMatchIndex = this.state.currentStructureMatchIndex; + + if (currentMatchIndex === undefined) { + return; + } + + currentMatchIndex += isGoingForward ? 1 : -1; + + if (currentMatchIndex === matchCount) { + currentMatchIndex = 0; + } + if (currentMatchIndex < 0) { + currentMatchIndex = matchCount -1; + } + + const match = structureMatches[currentMatchIndex]; + this.props.onSearchHighlight(match); + + this.setState({ + currentStructureMatchIndex: currentMatchIndex, + }, () => this.forceUpdate()); + } + + createWildcard() { + const selection = getSelection(); + const range = selection!.getRangeAt(0); + const startNode = range.startContainer; + const endNode = range.endContainer; + const startOffset = range.startOffset; + const endOffset = range.endOffset; + const parentDivId = (startNode.parentNode as Element).id; + + if (startNode.textContent === endNode.textContent && startOffset !== endOffset) { + const { structureEntries, wildcards } = this.state; + const structureEntriesCopy: StructureEntry[] = cloneDeep(structureEntries); + let wildcardsCopy: Wildcard[] = cloneDeep(wildcards); + + const indicesForWildcard = getIndicesForWildcardFromDivId(parentDivId); + + const entryIndex = +indicesForWildcard[1]; + const cellIndex = +indicesForWildcard[2]; + const contentsIndex = +indicesForWildcard[3]; + + const newWildcard = createWildcard(entryIndex, cellIndex, contentsIndex); + + wildcardsCopy.push(newWildcard); + + const wildcardIndex = wildcardsCopy.length - 1; + const modifiedStructureEntries = addWildcardToStructureEntry( + structureEntriesCopy, + entryIndex, + cellIndex, + wildcardIndex, + ); + + const insertionResults = insertWildcardIntoCellsContents( + structureEntriesCopy[entryIndex].row[cellIndex], + wildcardsCopy, + entryIndex, + cellIndex, + wildcardIndex, + contentsIndex, + startOffset, + endOffset, + ); + structureEntriesCopy[entryIndex].row[cellIndex] = insertionResults.cellContents; + wildcardsCopy = insertionResults.wildcards; + + wildcardsCopy[wildcardIndex].wildcardSubstitutions[0].contentsIndex = + insertionResults.insertedWildcardContentsIndex; + + this.setState({ + structureEntries: modifiedStructureEntries, + wildcards: wildcardsCopy, + }); + } + } + + useWildcard(wildcardIndex: number) { + const selection = getSelection(); + const range = selection!.getRangeAt(0); + const startNode = range.startContainer; + const endNode = range.endContainer; + const startOffset = range.startOffset; + const endOffset = range.endOffset; + const parentDivId = (startNode.parentNode as Element).id; + + if (startNode.textContent === endNode.textContent && startOffset !== endOffset) { + const { structureEntries, wildcards } = this.state; + const structureEntriesCopy: StructureEntry[] = cloneDeep(structureEntries); + let wildcardsCopy: Wildcard[] = cloneDeep(wildcards); + + const indicesForWildcard = getIndicesForWildcardFromDivId(parentDivId); + + const entryIndex = +indicesForWildcard[1]; + const cellIndex = +indicesForWildcard[2]; + const contentsIndex = +indicesForWildcard[3]; + + const modifiedStructureEntries = addWildcardToStructureEntry( + structureEntriesCopy, + entryIndex, + cellIndex, + wildcardIndex, + ); + + const insertionResults = insertWildcardIntoCellsContents( + structureEntriesCopy[entryIndex].row[cellIndex], + wildcardsCopy, + entryIndex, + cellIndex, + wildcardIndex, + contentsIndex, + startOffset, + endOffset, + ); + structureEntriesCopy[entryIndex].row[cellIndex] = insertionResults.cellContents; + wildcardsCopy = insertionResults.wildcards; + + const newWildcardSubstitution = { + entryIndex: entryIndex, + cellIndex: cellIndex, + contentsIndex: insertionResults.insertedWildcardContentsIndex, + }; + wildcardsCopy[wildcardIndex].wildcardSubstitutions.push(newWildcardSubstitution); + + this.setState({ + structureEntries: modifiedStructureEntries, + wildcards: wildcardsCopy, + }); + } + } + + removeWildcard(anchorDivId: string) { + const isAnchorDivWildcard = anchorDivId[0] === "w"; + + if (isAnchorDivWildcard) { + const indicesForWildcard = anchorDivId.split("-"); + const entryIndex = +indicesForWildcard[1]; + const cellIndex = +indicesForWildcard[2]; + const contentsIndex = +indicesForWildcard[3]; + + const { structureEntries, wildcards } = this.state; + const structureEntriesCopy: StructureEntry[] = cloneDeep(structureEntries); + let wildcardsCopy: Wildcard[] = cloneDeep(wildcards); + + const wildcardIndex = getWildcardIndex(wildcardsCopy, entryIndex, cellIndex, contentsIndex); + + const wildcardsUpdateResult = removeWildcardSubstitution( + wildcardsCopy, + wildcardIndex, + entryIndex, + cellIndex, + contentsIndex, + ); + wildcardsCopy = wildcardsUpdateResult.wildcards; + + let modifiedStructureEntries = removeWildcardFromStructureEntry( + structureEntriesCopy, + entryIndex, + cellIndex, + wildcardIndex, + ); + + const removalResults = removeWildcardFromCellContent( + structureEntriesCopy[entryIndex].row[cellIndex], + wildcardsCopy, + entryIndex, + cellIndex, + contentsIndex, + ); + structureEntriesCopy[entryIndex].row[cellIndex] = removalResults.cellContents; + + wildcardsCopy = removalResults.wildcards; + + if (wildcardsUpdateResult.isWildcardDeleted) { + modifiedStructureEntries = updateStructureEntriesAfterWildcardDeletion( + modifiedStructureEntries, + wildcardsCopy, + wildcardIndex, + ); + } + + this.setState({ + structureEntries: modifiedStructureEntries, + wildcards: wildcardsCopy, + isStructureMatching: false, + }); + } + } + + render() { + const { structureEntries, wildcards, isRemovingStructureEntries, isStructureMatching } = + this.state; + const structureEntriesCopy = cloneDeep(structureEntries); + const wildcardsCopy = cloneDeep(wildcards); + const contextMenuItems = this.getContextMenuItems(); + + const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => ( + + ))({ + [`& .${tooltipClasses.tooltip}`]: { + maxWidth: 900, + }, + }); + + const selection = getSelection(); + + if (selection !== null) { + // empty unwanted text selection resulting from Shift-click + selection.empty(); + } + + return ( +
+
+
+
Structure Matching
+
+ +

Help

+
    +
  • + Ignoring cells: Hold CTRL and click on a cell to ignore it or + stop ignoring it. Hold SHIFT+CTRL to ignore the cell and stop + ignoring all others, or ignore all other cells instead.{" "} +
  • +
  • + Constraining distance between structure rows: Change the constraint + on the distance between two rows by clicking on the link icon between them. + This icon is three horizontal dots by default. +
  • +
  • + Creating wildcards: Selecting a part of the text in a cell, right + click and select "Create wildcard" to create a new + wildcard. A wildcard can be used to abstract away any specific data. +
  • +
  • + Using wildcards: Selecting a part of the text in a cell, right click + and select "Use wildcard wildcard id". Any value could be + abstracted by the wildcard, but the value has to be the same in all places + where this wildcard is used. +
  • +
  • + Removing wildcards: Hover over a wildcard, right click and select + "Remove wildcard". If the wildcard is used in multiple + places, only the selected one will be removed. +
  • +
  • + Removing rows: Click on the Remove rows button on the bottom + right of the dialogue. A red cross will appear to the left of every row in + the structure, by clicking on a cross, the row will be removed from the + structure. Click theDone button afterwards. +
  • +
+ + } + sx={{ m: 1 }} + placement="right" + arrow + > + +
+ this.props.onClose()} + > + + +
+
+ + this.toggleIsCellSelected( + structureEntryIndex, + cellIndex, + isCtrlPressed, + isShiftPressed, + ) + } + onToggleStructureLink={(structureEntryIndex) => + this.toggleStructureLink(structureEntryIndex) + } + onStructureEntryRemoved={(structureEntryIndex) => + this.removeStructureEntry(structureEntryIndex) + } + /> + +
+ { + this.toggleIsRemovingStructureEntries(); + }} + > + {isRemovingStructureEntries ? "Done" : "Remove rows"} + + { + this.matchStructure(); + }} + disabled={isRemovingStructureEntries} + > + Search for Structure + + {isStructureMatching && ( + <> +
+ {" "} + {(this.state.currentStructureMatchIndex ?? -1) + 1}{" "} + of {this.state.structureMatches.length} +
+ {this.state.structureMatches.length > 1 && ( + <> + this.handleStructureNavigation(false)} + > + + + this.handleStructureNavigation(true)} + > + + + + )} + + )} +
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/viewer/components/dialogs/structures/ContextMenu.tsx b/src/viewer/components/dialogs/structures/ContextMenu.tsx new file mode 100644 index 0000000..509ee69 --- /dev/null +++ b/src/viewer/components/dialogs/structures/ContextMenu.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import { ContextMenuItem } from "../../../interfaces"; +import { getContextMenuItemStyle, getContextMenuStyle } from "../../../hooks/useStyleManager"; +import { motion } from "framer-motion"; + +interface Props { + parentDivId: string; + items: ContextMenuItem[]; +} + +interface State { + xPos: number; + yPos: number; + showMenu: boolean; + selectedItemIndex: number | null; + anchorDivId: string; +} + +export default class ContextMenu extends React.PureComponent { + parentDiv: HTMLElement | null; + + constructor(props: Props) { + super(props); + this.state = { + xPos: 0, + yPos: 0, + showMenu: false, + selectedItemIndex: null, + anchorDivId: "", + }; + } + + componentDidMount(): void { + this.parentDiv = document.getElementById(this.props.parentDivId); + if (this.parentDiv) { + this.parentDiv.addEventListener("click", this.handleClick); + this.parentDiv.addEventListener("contextmenu", this.handleContextMenu); + } + } + + componentWillUnmount(): void { + if (this.parentDiv) { + this.parentDiv.removeEventListener("click", this.handleClick); + this.parentDiv.removeEventListener("contextmenu", this.handleContextMenu); + } + } + + handleClick = () => { + if (this.state.selectedItemIndex !== null) { + this.props.items[this.state.selectedItemIndex].callback(this.state.anchorDivId); + } + + if (this.state.showMenu) this.setState({ showMenu: false, selectedItemIndex: null }); + }; + + handleContextMenu = (e) => { + e.preventDefault(); + const path = e.composedPath(); + + this.setState({ + xPos: e.pageX, + yPos: e.pageY, + showMenu: true, + anchorDivId: path[0].id, + }); + }; + + toggleSelectedOptionIndex(selectedOptionIndex: number) { + this.setState({ selectedItemIndex: selectedOptionIndex }); + } + + clearSelectedOptionIndex() { + this.setState({ selectedItemIndex: null }); + } + + renderMenuOptions() { + const { items, parentDivId } = this.props; + const result: any = []; + + for (let o = 0; o < items.length; o++) { + const isSelected = o === this.state.selectedItemIndex; + const contextMenuItemStyle = getContextMenuItemStyle(isSelected); + + result.push( +
this.toggleSelectedOptionIndex(o)} + > + {items[o].text} +
, + ); + } + + return result; + } + + render() { + const { items } = this.props; + const { showMenu, xPos, yPos } = this.state; + const contextMenuHeight = items.length * 28; + const contextMenuWidth = 120; + + const contextMenuStyle = getContextMenuStyle(contextMenuHeight, contextMenuWidth, xPos, yPos); + + if (showMenu) + return ( +
this.clearSelectedOptionIndex()}> + +
{this.renderMenuOptions()}
+
+
+ ); + else return null; + } +} \ No newline at end of file diff --git a/src/viewer/components/dialogs/structures/StructureSettingsDropdown.tsx b/src/viewer/components/dialogs/structures/StructureSettingsDropdown.tsx new file mode 100644 index 0000000..91ada7f --- /dev/null +++ b/src/viewer/components/dialogs/structures/StructureSettingsDropdown.tsx @@ -0,0 +1,77 @@ +import * as React from "react"; +import IconButton from "@mui/material/IconButton"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import ListItemText from "@mui/material/ListItemText"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import SaveIcon from "@mui/icons-material/Save"; +import FileOpenIcon from "@mui/icons-material/FileOpen"; +import SettingsIcon from "@mui/icons-material/Settings"; +import { MenuList } from "@mui/material"; + +interface StructureSettingsDropdownProps { + onStructureDefinitionSave: () => void; + onStructureDefinitionLoad: () => void; +} + +export const StructureSettingsDropdown: React.FunctionComponent = ({ + onStructureDefinitionSave, + onStructureDefinitionLoad, +}) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = (action: string) => { + if (action === "save") onStructureDefinitionSave(); + else if (action === "load") onStructureDefinitionLoad(); + + setAnchorEl(null); + }; + + return ( +
+ + + + handleClose("")} + MenuListProps={{ + "aria-labelledby": "basic-button", + sx: { py: 0 }, + }} + > + + handleClose("save")}> + + + + + Save Structure Definition + + + handleClose("load")}> + + + + + Load Structure Definition + + + + +
+ ); +}; \ No newline at end of file diff --git a/src/viewer/components/dialogs/structures/StructureTable.tsx b/src/viewer/components/dialogs/structures/StructureTable.tsx new file mode 100644 index 0000000..e3cebae --- /dev/null +++ b/src/viewer/components/dialogs/structures/StructureTable.tsx @@ -0,0 +1,262 @@ +import React, { ReactNode } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import Tooltip from "@mui/material/Tooltip"; +import { StructureEntry, Wildcard } from "../../../interfaces"; +import { constants } from "../../../constants"; +import { + getStructureTableColumnStyle, + getStructureTableHeaderStyle, + getHeaderColumnStyle, + getHeaderColumnInnerStyle, + getStructureTableCellSelectionStyle, + getStructureTableEntryIconStyle, + getStructureTableRowStyle, + getStructureTableLinkStyle, +} from "../../../hooks/useStyleManager"; +import { getReactElementsFromCellContents } from "../../../hooks/useWildcardManager"; +import isEqual from "react-fast-compare"; +import { enums } from "../../../enums"; + +interface Props { + headerColumns: string[]; + structureEntries: StructureEntry[]; + wildcards: Wildcard[]; + isRemovingStructureEntries: boolean; + onToggleStructureLink: (structureEntryIndex: number) => void; + onStructureEntryRemoved: (structureEntryIndex: number) => void; + onToggleIsCellSelected: ( + structureEntryIndex: number, + cellIndex: number, + isCtrlPressed: boolean, + isShiftPressed: boolean, + ) => void; +} + +interface State { + columnWidth: { [id: string]: number }; +} + +export default class StructureTable extends React.Component { + constructor(props: Props) { + super(props); + this.state = { columnWidth: {} }; + } + + shouldComponentUpdate( + nextProps: Readonly, + nextState: Readonly, + _nextContext: any, + ): boolean { + const areStructureEntriesUpdating = !isEqual( + this.props.structureEntries, + nextProps.structureEntries, + ); + const areWildcardsUpdating = !isEqual(this.props.wildcards, nextProps.wildcards); + const isRemovingStructureEntriesUpdating = !isEqual( + this.props.isRemovingStructureEntries, + nextProps.isRemovingStructureEntries, + ); + const isColumnWidthUpdating = !isEqual(this.state.columnWidth, nextState.columnWidth); + + if ( + areStructureEntriesUpdating || + areWildcardsUpdating || + isRemovingStructureEntriesUpdating || + isColumnWidthUpdating + ) { + return true; + } + + return false; + } + + setColumnWidth(name: string, width: number) { + //update the state for triggering the render + this.setState((prevState) => { + const columnWidth = { ...prevState.columnWidth }; + columnWidth[name] = width; + return { columnWidth }; + }); + } + + renderHeader(containerWidth: number) { + const style = getStructureTableHeaderStyle(containerWidth); + + return ( +
+
+ {this.props.headerColumns.filter(h => !h.startsWith("Structure")).map((h, i) => + this.renderHeaderColumn(h, i, constants.LOG_DEFAULT_COLUMN_WIDTH), + )} +
+
+ ); + } + + renderHeaderColumn(value: string, columnIndex: number, width: number) { + const height = constants.LOG_HEADER_HEIGHT; + const headerColumnStyle = getHeaderColumnStyle(width, height); + const headerColumnInnerStyle = getHeaderColumnInnerStyle(height, true); + return ( + this.setColumnWidth(value, width!)} + > +
+
{value}
+
+
+ ); + } + + renderColumn(rowIndex: number, cellIndex: number, width: number) { + const { structureEntries } = this.props; + const columnStyle = getStructureTableColumnStyle(width, cellIndex); + const columnInnerStyle = getStructureTableCellSelectionStyle( + structureEntries, + rowIndex, + cellIndex, + ); + + const allCellContents: ReactNode[] = []; + + structureEntries[rowIndex].row[cellIndex].forEach((contentsPart) => { + const contentPartDiv = getReactElementsFromCellContents( + rowIndex, + cellIndex, + contentsPart.contentsIndex, + contentsPart.wildcardIndex, + contentsPart.textValue, + ); + allCellContents.push(contentPartDiv); + }); + + return ( +
+
+ this.props.onToggleIsCellSelected(rowIndex, cellIndex, event.ctrlKey, event.shiftKey) + } + > + {allCellContents.map((value) => value)} +
+
+ ); + } + + renderRows(containerWidth: number, containerHeight: number) { + const newContainerWidth = containerWidth + constants.STRUCTURE_WIDTH; + const result: ReactNode[] = []; + const { structureEntries, isRemovingStructureEntries, onStructureEntryRemoved } = + this.props; + const headerColumns = this.props.headerColumns.filter(h => !h.startsWith("Structure")); + const structureEntryIconStyle = getStructureTableEntryIconStyle(isRemovingStructureEntries); + let structureLinkIndex = 0; + + for (let r = 0; r < structureEntries.length; r++) { + const rowStyle = getStructureTableRowStyle(r, structureLinkIndex); + + result.push( +
+ {!isRemovingStructureEntries && ( +
+ +
+ )} + {isRemovingStructureEntries && ( +
{ + onStructureEntryRemoved(r); + }} + > + +
+ )} + {headerColumns.map((h, c) => this.renderColumn(r, c, this.state.columnWidth[h]))} +
, + ); + + if (r !== structureEntries.length - 1) { + const structureLinkStyle = getStructureTableLinkStyle(r, structureLinkIndex); + + const structureLinkDistance = structureEntries[r].structureLink; + + result.push( +
this.props.onToggleStructureLink(r)} + > + {structureLinkDistance === enums.StructureLinkDistance.Max && ( + Allow maximal number of rows in-between} + placement="right" + arrow + > + + + )} + {structureLinkDistance === enums.StructureLinkDistance.None && ( + Disallow rows in-between} placement="right" arrow> + + + )} + {structureLinkDistance === enums.StructureLinkDistance.Min && ( + Allow minimal number of rows in-between} + placement="right" + arrow + > + + + )} +
, + ); + structureLinkIndex++; + } + } + + return ( +
+ {result} +
+ ); + } + + render() { + const numberOfRows = this.props.structureEntries.length; + const headerColumns = this.props.headerColumns.filter(h => !h.startsWith("Structure")); + const containerHeight = + numberOfRows * constants.LOG_ROW_HEIGHT + (numberOfRows - 1) * constants.STRUCTURE_LINK_HEIGHT; + const containerWidth = + headerColumns.length * constants.BORDER_SIZE + + headerColumns.reduce((partialSum: number, h) => partialSum + (this.state.columnWidth[h] ?? 0), 0); + + return ( +
+ {this.renderHeader(containerWidth)} + {this.renderRows(containerWidth, containerHeight)} +
+ ); + } +} \ No newline at end of file diff --git a/src/viewer/components/minimap/MinimapHeader.tsx b/src/viewer/components/minimap/MinimapHeader.tsx new file mode 100644 index 0000000..964f270 --- /dev/null +++ b/src/viewer/components/minimap/MinimapHeader.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { constants } from "../../constants"; +import LogFile from "../../lib/LogFile"; +import { getVisibleColumnsMinimap } from "../../hooks/useColumnSelection"; +import { ColumnSelection } from "../../interfaces"; + +export default function MinimapHeader( + props: { + logFile: LogFile, + selectedColumns: ColumnSelection, + borderRight: boolean + } +) { + const minimapHeaders = getVisibleColumnsMinimap(props.logFile, props.selectedColumns); + const minimapWidth = minimapHeaders.length * constants.MINIMAP_COLUMN_WIDTH; + + const style: any = { width: minimapWidth, ...constants.COLUMN_2_HEADER_STYLE }; + if (props.borderRight) { + style.borderRight = constants.BORDER; + } + + return ( +
+
+ { + minimapHeaders + .map((h, i) => ) + } +
+
+ ) +} + +function HeaderItem( + props: { + header: string + } +) { + const style: React.CSSProperties = { + whiteSpace: "nowrap", + width: constants.MINIMAP_COLUMN_WIDTH, + display: "inline-block", + }; + const innerStyle: React.CSSProperties = { + display: "flex", + height: "100%", + paddingLeft: "2px", + }; + return ( +
+
+ {props.header} +
+
+ ); +} \ No newline at end of file diff --git a/src/viewer/components/minimap/MinimapView.tsx b/src/viewer/components/minimap/MinimapView.tsx new file mode 100644 index 0000000..1a1e42b --- /dev/null +++ b/src/viewer/components/minimap/MinimapView.tsx @@ -0,0 +1,240 @@ +import React from "react"; +import LogFile from "../../lib/LogFile"; +import { ColumnSelection, LogViewState, RowProperty } from "../../interfaces"; +import { constants } from "../../constants"; +import { isColumnVisibleMinimap } from "../../hooks/useColumnSelection"; + +interface Props { + logFile: LogFile; + columnSelection: ColumnSelection; + logViewState: LogViewState; + visibleItems: number; + forwardRef: React.RefObject; + rowProperties: RowProperty[]; + onRequestColors: (column: string) => string[]; + onMinimapScaleChange(visibleItems: number): void; +} + +interface State { + controlDown: boolean; +} + +export default class MinimapView extends React.Component { + canvasRef: React.RefObject; + + constructor(props: Props) { + super(props); + this.canvasRef = React.createRef(); + this.handleWheel = this.handleWheel.bind(this); + this.handleClick = this.handleClick.bind(this); + this.state = { controlDown: false }; + } + + componentDidMount(): void { + window.addEventListener("resize", () => this.draw()); + window.addEventListener("keydown", (e) => this.controlDownListener(e)); + window.addEventListener("keyup", (e) => this.controlUpListener(e)); + this.draw(); + } + + componentDidUpdate(prevProps: Readonly, prevState: State): void { + if ( + prevProps.logViewState !== this.props.logViewState || + prevProps.visibleItems !== this.props.visibleItems || + prevProps.logFile !== this.props.logFile || + prevProps.columnSelection !== this.props.columnSelection + ) { + this.draw(); + } + } + + draw() { + // Clear and scale the canvas + const canvas = this.canvasRef.current; + if (!canvas || !this.props.logViewState) return; + canvas.height = canvas.clientHeight * window.devicePixelRatio; + canvas.width = canvas.clientWidth * window.devicePixelRatio; + const ctx = canvas.getContext("2d")!; + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Hide Minimap if search did not return any rows + if (this.props.logFile.amountOfRows() === 0) return; + + const { + logViewState, + visibleItems, + logFile, + columnSelection, + rowProperties + } = this.props; + const { + height, // Height of logView (pixels) + scrollTop, + } = logViewState; + const maxVisibleItems = rowProperties.filter((r) => r.isRendered).length; + const minVisibleItems = height / constants.LOG_ROW_HEIGHT; + const fileHeight = maxVisibleItems * constants.LOG_ROW_HEIGHT; + + // Set minimap scale to number between 0 & 1. (1 = minimap 1:1, 0.5 = minimap 1:2) + const scale = minVisibleItems / visibleItems; + ctx.scale(1, scale); + ctx.translate(0, scrollTop * -1); + + // Extra padding at the top is used when empty rows are displayed at the top of the logview + const extraPaddingTop = scrollTop < 0 ? (-scrollTop) : 0; + + // Compute center & end + const minimapEnd = scrollTop + height / scale; // End of last visible part + const minimapCenter = scrollTop + (minimapEnd - scrollTop) / 2; // Center of visible part + const logEnd = scrollTop + height + extraPaddingTop; // End of last visible part + const logCenter = scrollTop + (logEnd - scrollTop) / 2; // Center of visible part + + // Try to center the logview port in the center of the minimap. + // This is only possible when the user scrolled enough from top. + // This code also makes sure that when scrolled to the end of the log file and zoomed out, the minimap stops at the bottom of the screen. + const logMinimapCenterDiff = minimapCenter - logCenter; + const miniMapScrollTop = Math.min(logMinimapCenterDiff, scrollTop); + + let scrollBottom = + fileHeight < height + ? 0 + : Math.min(fileHeight + miniMapScrollTop - minimapEnd, 0); + + // If the logView is displaying empty lines at the bottom of the screen (in side-by-side), continue scrolling up the minimap until it disapears. + if (logEnd > fileHeight && scrollBottom !== 0) { + scrollBottom -= (fileHeight - logEnd); + } + + ctx.translate(0, miniMapScrollTop - scrollBottom); + + // Draw blocks + let index = 0; //for caculating the position + for (let columnIndex = 0; columnIndex < logFile.amountOfColumns(); columnIndex++) { + const header = logFile.getAllHeaders()[columnIndex]; + if (isColumnVisibleMinimap(header, logFile, columnSelection)) { + const colors = this.props.onRequestColors(header); + let counter = 0; //increase only when row is rendered + for (let i = 0; i < colors.length; i++) { + if ( + rowProperties[i].isRendered + ) { + ctx.beginPath(); + ctx.fillStyle = colors[i]; + ctx.fillRect( + index * constants.MINIMAP_COLUMN_WIDTH, + (counter * constants.LOG_ROW_HEIGHT + extraPaddingTop), + constants.MINIMAP_COLUMN_WIDTH, + constants.LOG_ROW_HEIGHT, + ); + ctx.stroke(); + counter++; + } + } + index++; + } + } + + // Draw the log viewport on top of the minimap (grey block) + ctx.beginPath(); + ctx.fillStyle = "#d3d3d380"; + ctx.fillRect(0, scrollTop + extraPaddingTop, canvas.width, logEnd - (scrollTop + extraPaddingTop)); + ctx.stroke(); + } + + handleClick(e: React.MouseEvent) { + const canvas = this.canvasRef?.current; + if (!canvas) return; + + const bounding = canvas.getBoundingClientRect(); + let y = e.clientY; + if (bounding != undefined) { + y = e.clientY - bounding.top; + } + const { logViewState, visibleItems, logFile } = this.props; + const { + visibleItems: minVisibleItems, + height, + start: logStart, + scrollTop: logScrollTop, + } = logViewState; + const maxVisibleItems = logFile.amountOfRows(); + const scaleItem = minVisibleItems / visibleItems; + + const minimapEnd = logScrollTop + height / scaleItem; + const minimapCenter = logScrollTop + (minimapEnd - logScrollTop) / 2; + const logEnd = (logStart + minVisibleItems) * constants.LOG_ROW_HEIGHT; + const logCenter = logScrollTop + (logEnd - logScrollTop) / 2; + + const logMinimapCenterDiff = minimapCenter - logCenter; + const scrollTopBox = Math.min(logMinimapCenterDiff, logScrollTop); + const scrollBottom = + minVisibleItems === maxVisibleItems + ? 0 + : Math.min(logFile.amountOfRows() * constants.LOG_ROW_HEIGHT + scrollTopBox - minimapEnd, 0); + let nrOfRows = 0; + let scrollTop = 0; + if (scrollBottom < 0) { + //scrollBottom becomes smaller than 0, when the log view scrolls to the last part of the log. + nrOfRows = ((scrollTopBox - scrollBottom) * scaleItem - y) / (height / visibleItems); //number of rows to move, can be positive or negative + scrollTop = (logStart - nrOfRows) * constants.LOG_ROW_HEIGHT; + } else { + nrOfRows = (y - scrollTopBox * scaleItem) / (height / visibleItems); //number of rows to move, can be positive or negative + scrollTop = (logStart + nrOfRows) * constants.LOG_ROW_HEIGHT; + } + //when grey box meet the bottom of the log, the scroll top will not increase. + const maximumScrollTop = (maxVisibleItems - minVisibleItems) * constants.LOG_ROW_HEIGHT; + scrollTop = Math.min(maximumScrollTop, scrollTop); + + if (!this.props.forwardRef.current) return; + this.props.forwardRef.current.scrollTo({top: scrollTop}); + this.draw(); + } + + handleWheel(e: React.WheelEvent) { + if (!this.state.controlDown) { + this.updateState(e.deltaY); + return; + } + + const maxVisibleItems = this.props.rowProperties.filter((r) => r.isRendered).length; + const minVisibleItems = this.props.logViewState.visibleItems; + + // The hardcoded numbers in this formula don't have a meaning, they are used to tune the zooming speed. + const newLines = e.deltaY / 30 * (this.props.visibleItems / maxVisibleItems * 20 + 1); + + // Limits the new visible item count between the max item count and item in log view + const visibleItems = Math.max(Math.min(maxVisibleItems, this.props.visibleItems + newLines), minVisibleItems); + + this.props.onMinimapScaleChange(visibleItems); + } + + updateState(scroll: number) { + if (!this.props.forwardRef.current) return; + const logViewElement = this.props.forwardRef.current; + logViewElement.scrollTop = logViewElement.scrollTop + scroll; + } + + controlDownListener(e: any) { + if (e.key === "Control" && this.state.controlDown === false) + this.setState({ controlDown: true }); + } + + controlUpListener(e: any) { + if (e.key === "Control" && this.state.controlDown) this.setState({ controlDown: false }); + } + + render() { + return ( +
+ +
+ ); + } +} diff --git a/src/viewer/constants.ts b/src/viewer/constants.ts index 7b0bbf7..8b53037 100644 --- a/src/viewer/constants.ts +++ b/src/viewer/constants.ts @@ -1,131 +1,63 @@ -import LogFile from "./LogFile"; +export namespace constants { -export const defaultAppState = { - rules: [], - logFile: LogFile.create([], []), - logFileAsString: "", - logViewState: undefined, - coloredTable: false, - showMinimapHeader: true, - showExportDialog: false, - showStatesDialog: false, - showFlagsDialog: false, - showSelectDialog: false, - selectedColumns: [], - selectedColumnsMini: [], - reSearch: false, - wholeSearch: false, - caseSearch: false, - filterSearch: false, - searchMatches: [], - currentSearchMatch: null, - currentSearchMatchIndex: null, - selectedLogRows: [], - rowProperties: [], - logEntryCharIndexMaps: null, - showStructureDialog: false, - loadedStructureDefinition: null, - structureMatches: [], - currentStructureMatchIndex: null, - currentStructureMatch: [], - lastSelectedRow: undefined, - collapsibleRows: {}, -} + const RGB_LIME_GREEN = "0, 208, 0"; + const RGB_TURQUOISE = "69, 205, 191"; + const RGB_OFFICE_GREEN = "0, 100, 0"; + const RGB_OFF_ORANGE = "244, 157, 71"; -const RGB_LIME_GREEN = "0, 208, 0"; + export const MINIMAP_COLUMN_WIDTH = 15; -const RGB_TURQUOISE = "69, 205, 191"; + export const LOG_HEADER_HEIGHT = 40; -const RGB_OFFICE_GREEN = "0, 100, 0"; + export const LOG_ROW_HEIGHT = 28; -const RGB_OFF_ORANGE = "244, 157, 71"; + export const LOG_DEFAULT_COLUMN_WIDTH = 100; -export const MINIMAP_COLUMN_WIDTH = 15; + export const BORDER_SIZE = 1; -export const LOG_HEADER_HEIGHT = 40; + export const STRUCUTURE_MATCH_BORDER_SIZE = 3; -export const LOG_ROW_HEIGHT = 28; + export const SELECTED_ROW_BORDER_SIZE = 2; -export const LOG_DEFAULT_COLUMN_WIDTH = 100; + export const BORDER = `${BORDER_SIZE}px solid grey`; -export const BORDER_SIZE = 1; + export const BORDER_SELECTED_ROW = `${SELECTED_ROW_BORDER_SIZE}px solid rgb(${RGB_TURQUOISE})`; -export const STRUCUTURE_MATCH_BORDER_SIZE = 3; + export const BACKGROUND_COLOR_SELECTED_ROW = `rgba(${RGB_TURQUOISE}, 0.5)`; -export const SELECTED_ROW_BORDER_SIZE = 2; + export const BACKGROUND_COLOR_SEARCH_ROW = `rgba(${RGB_OFF_ORANGE}, 0.5)`; -export const BORDER = `${BORDER_SIZE}px solid grey`; + export const BACKGROUND_COLOR_SEARCH_HIGHLIGHTED_ROW = `rgba(${RGB_OFF_ORANGE}, 0.7)`; -export const BORDER_SELECTED_ROW = `${SELECTED_ROW_BORDER_SIZE}px solid rgb(${RGB_TURQUOISE})`; + export const BORDER_COLOR_CURRENT_SEARCH_ROW = `${STRUCUTURE_MATCH_BORDER_SIZE}px solid rgb(${RGB_OFF_ORANGE})`; -export const BACKGROUND_COLOR_SELECTED_ROW = `rgba(${RGB_TURQUOISE}, 0.5)`; + export const BORDER_STRUCTURE_MATCH_CURRENT = `${STRUCUTURE_MATCH_BORDER_SIZE}px solid rgb(${RGB_LIME_GREEN})`; -export const BACKGROUND_COLOR_SEARCH_ROW = `rgba(${RGB_OFF_ORANGE}, 0.5)`; + export const BACKGROUND_COLOR_MATCHED_ROW_CURRENT = `rgba(${RGB_LIME_GREEN}, 0.5)`; -export const BORDER_STRUCTURE_MATCH_CURRENT = `${STRUCUTURE_MATCH_BORDER_SIZE}px solid rgb(${RGB_LIME_GREEN})`; + export const BORDER_STRUCTURE_MATCH_OTHER = `${STRUCUTURE_MATCH_BORDER_SIZE}px solid rgb(${RGB_OFFICE_GREEN})`; -export const BACKGROUND_COLOR_MATCHED_ROW_CURRENT = `rgba(${RGB_LIME_GREEN}, 0.5)`; + export const BACKGROUND_COLOR_MATCHED_ROW_OTHER = `rgba(${RGB_OFFICE_GREEN}, 0.5)`; -export const BORDER_STRUCTURE_MATCH_OTHER = `${STRUCUTURE_MATCH_BORDER_SIZE}px solid rgb(${RGB_OFFICE_GREEN})`; + export const BORDER_SELECTED_ROW_RADIUS = `5px`; -export const BACKGROUND_COLOR_MATCHED_ROW_OTHER = `rgba(${RGB_OFFICE_GREEN}, 0.5)`; + export const COLUMN_0_HEADER_STYLE = { + height: LOG_HEADER_HEIGHT, + display: "flex", + justifyContent: "center", + alignItems: "center", + borderLeft: BORDER, + borderBottom: BORDER, + }; -export const BORDER_SELECTED_ROW_RADIUS = `5px`; + export const COLUMN_2_HEADER_STYLE = { + height: "100%", + display: "flex", + borderLeft: BORDER, + }; -export const RGB_ANNOTATION0 = `rgb(223, 90, 90)`; + export const STRUCTURE_WIDTH = 28; -export const RGB_ANNOTATION1 = `rgb(71, 186, 89)`; - -export const RGB_ANNOTATION2 = `rgb(46, 111, 179)`; - -export const RGB_ANNOTATION3 = `rgb(255, 165, 0)`; - -export const RGB_ANNOTATION4 = `rgb(128, 0, 128)`; - -export const COLUMN_0_HEADER_STYLE = { - height: LOG_HEADER_HEIGHT, - display: "flex", - justifyContent: "center", - alignItems: "center", - borderLeft: BORDER, - borderBottom: BORDER, -}; - -export const COLUMN_2_HEADER_STYLE = { - height: "100%", - display: "flex", - borderLeft: BORDER, -}; - -// TODO: determine column width automatically, not hardcoded -export const LOG_COLUMN_WIDTH_LOOKUP = { - timestamp: 180, - level: 50, - threadID: 80, - location: 200, - message: 400, - Line: 50 -}; - -export const STRUCTURE_WIDTH = 28; - -export const STRUCTURE_LINK_HEIGHT = 16; - -export enum StructureLinkDistance { - None = "NONE", - Min = "MIN", - Max = "MAX", -} - -export enum StructureHeaderColumnType { - Unselected = "UNSELECTED", - Selected = "SELECTED", - Custom = "CUSTOM", -} - -export enum SelectedRowType { - None = "NONE", - UserSelect = "SELECTED", - QueryResult = "QUERY_RESULT", - SearchResult = "SEARCH_RESULT" -} + export const STRUCTURE_LINK_HEIGHT = 16; + +} \ No newline at end of file diff --git a/src/viewer/enums.ts b/src/viewer/enums.ts new file mode 100644 index 0000000..83cc851 --- /dev/null +++ b/src/viewer/enums.ts @@ -0,0 +1,30 @@ +export namespace enums { + + export enum StructureLinkDistance { + None = "NONE", + Min = "MIN", + Max = "MAX", + } + + export enum StructureHeaderColumnType { + Unselected = "UNSELECTED", + Selected = "SELECTED", + Custom = "CUSTOM", + } + + export enum DialogType { + ExportDialog, + StatesDialog, + FlagsDialog, + SelectDialog, + StructureDialog + } + + export enum EventTrigger { + Initalisation, + UserScroll, + LogViewJump, + Syncronize + } + +} \ No newline at end of file diff --git a/src/viewer/hooks/useColor.ts b/src/viewer/hooks/useColor.ts new file mode 100644 index 0000000..559cb2e --- /dev/null +++ b/src/viewer/hooks/useColor.ts @@ -0,0 +1,5 @@ +export function isLight(color: string) { + const colors = JSON.parse(color.slice(3).replace("(", "[").replace(")", "]")); + const brightness = (colors[0] * 299 + colors[1] * 587 + colors[2] * 114) / 1000; + return brightness > 110; +} \ No newline at end of file diff --git a/src/viewer/hooks/useColumnSelection.ts b/src/viewer/hooks/useColumnSelection.ts new file mode 100644 index 0000000..9482ce2 --- /dev/null +++ b/src/viewer/hooks/useColumnSelection.ts @@ -0,0 +1,24 @@ +import { ColumnSelection } from "../interfaces"; +import LogFile from "../lib/LogFile"; + +// Display priority: columnSelection, default value of custom column, def, true +export function isColumnVisibleLogView(header: string, logFile: LogFile, columnSelection: ColumnSelection, def: boolean = true) { + return columnSelection[header]?.logView ?? logFile.customColumns[header]?.showInLogView ?? def; +} + +// Display priority: columnSelection, default value of custom column, def, true +export function isColumnVisibleMinimap(header: string, logFile: LogFile, columnSelection: ColumnSelection, def: boolean = true) { + return columnSelection[header]?.miniMap ?? logFile.customColumns[header]?.showInMinimap ?? def; +} + +// Display priority: columnSelection, default value of custom column, def, true +export function getVisibleColumnsLogView(logFile: LogFile, columnSelection: ColumnSelection, def: boolean = true) { + return logFile.getAllHeaders() + .filter((h) => isColumnVisibleLogView(h, logFile, columnSelection)); +} + +// Display priority: columnSelection, default value of custom column, def, true +export function getVisibleColumnsMinimap(logFile: LogFile, columnSelection: ColumnSelection, def: boolean = true) { + return logFile.getAllHeaders() + .filter((h) => isColumnVisibleMinimap(h, logFile, columnSelection)); +} \ No newline at end of file diff --git a/src/viewer/hooks/useLogFile.ts b/src/viewer/hooks/useLogFile.ts new file mode 100644 index 0000000..6aa25ed --- /dev/null +++ b/src/viewer/hooks/useLogFile.ts @@ -0,0 +1,93 @@ +import LogFile from "../lib/LogFile"; +import { LogFileData } from "../interfaces"; +import Rule from "../rules/Rule"; +import LineNumberingColumn from "../lib/columns/LineNumberingColumn"; +import { types } from "../types"; +import { logDataToString } from "./useTracyLogData"; + +export function createEmptyLogFile(): LogFile { + const fileData: LogFileData = { + filePath: '', + headers: [], + rows: [] + } + return new LogFile(fileData); +} + +export function createLogFile( + data: LogFileData, + rules: Rule[] +): LogFile { + const { rows, dateTimeIndex } = data; + let parsedRows: types.TracyLogData[][] = rows; + if (dateTimeIndex !== undefined) { + parsedRows = rows.map((row) => { + let value = row[dateTimeIndex]; + if (typeof(value) === 'string') { + if (!value.includes('Z')) { + value = value + 'Z'; + } + const date = new Date(value); + + // If date time is not a number, date-time string is not valid. + if (!isNaN(date.getTime())) { + row[dateTimeIndex] = date; + } + } + return row; + }); + } + + const parsedData: LogFileData = { ...data, rows: parsedRows} + const logFile = new LogFile(parsedData); + logFile.registerCustomColumn(new LineNumberingColumn()); + return logFile.updateLogFile(rules); +} + +export function toExportData(logFile: LogFile, exportIndices?: number[]): {[key: string]: string}[] { + if (exportIndices === undefined) { + exportIndices = Array.from(Array(logFile.amountOfRows()).keys()); + } + + var exportData: {[key: string]: string}[] = [] + const originalColumns = logFile.getAllHeaders(); + + for (var index of exportIndices) { + var rowObject: {[key: string]: string} = {}; + const row = logFile.getRow(index); + for (var columnIndex = 0; columnIndex <= originalColumns.length - 1; columnIndex++) { + if (logFile.customColumns[originalColumns[columnIndex]]?.exportable !== false) { + rowObject[originalColumns[columnIndex]] = logDataToString(row[columnIndex]); + } + } + exportData.push(rowObject); + } + + return exportData; +} + +export function toStructureData(logFile: LogFile): {[key: string]: string}[] { + var exportData: {[key: string]: string}[] = [] + const originalColumns = logFile.getAllHeaders(); + + for (var i = 0; i < logFile.amountOfRows(); i++) { + var rowObject: {[key: string]: string} = {}; + const row = logFile.getRow(i); + for (var columnIndex = 0; columnIndex < originalColumns.length; columnIndex++) { + rowObject[originalColumns[columnIndex]] = logDataToString(row[columnIndex]); + } + exportData.push(rowObject); + } + + return exportData; +} + +export function toDataGrid(logFile: LogFile): types.TracyLogData[][] { + const rows: types.TracyLogData[][] = []; + + for (var i = 0; i < logFile.amountOfRows(); i++) { + rows.push(logFile.getRow(i)); + } + + return rows; +} \ No newline at end of file diff --git a/src/viewer/hooks/useLogSearchManager.ts b/src/viewer/hooks/useLogSearchManager.ts index d770536..0d69de1 100644 --- a/src/viewer/hooks/useLogSearchManager.ts +++ b/src/viewer/hooks/useLogSearchManager.ts @@ -1,4 +1,6 @@ -import { LogEntryCharMaps } from "../types"; +import { logDataToString } from "./useTracyLogData"; +import { LogEntryCharMaps } from "../interfaces"; +import { types } from "../types"; export const escapeSpecialChars = (text: string): string => { let safeText = ""; @@ -18,102 +20,59 @@ export const escapeSpecialChars = (text: string): string => { return safeText; }; -// Long function to reduce number of checks -export const returnSearchIndices = ( - rows: string[][], - columnIndex: number, +const getLogLine = ( + logData: types.TracyLogData[] | types.TracyLogData, + caseSearchBool: boolean +): string => { + var logText: string; + if (Array.isArray(logData)) { + logText = logData.join(" "); + } else { + logText = logDataToString(logData); + } + + if (!caseSearchBool) { + logText = logText.toLocaleLowerCase(); + } + + return logText; +} + +const matchesSearchQuery = ( + logData: types.TracyLogData[] | types.TracyLogData, searchText: string, reSearchBool: boolean, wholeSearchBool: boolean, caseSearchBool: boolean, -): number[] => { - let loglineText: string; - const indices: number[] = []; - if (!caseSearchBool && !reSearchBool) searchText = searchText.toLowerCase(); - if (!reSearchBool) { - if (!wholeSearchBool) { - if (columnIndex === -1) { - if (!caseSearchBool) { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i].join(" ").toLowerCase(); - if (loglineText.indexOf(searchText) != -1) - indices.push(i); - } - } else { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i].join(" "); - if (loglineText.indexOf(searchText) != -1) - indices.push(i); - } - } - } else { - if (!caseSearchBool) { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i][columnIndex].toLowerCase(); - if (loglineText.indexOf(searchText) != -1) - indices.push(i); - } - } else { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i][columnIndex]; - if (loglineText.indexOf(searchText) != -1) - indices.push(i); - } - } - } - } - else { - if (columnIndex === -1) { - if (!caseSearchBool) { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i].join(" ").toLowerCase(); - if (matchWholeString(loglineText, searchText)) - indices.push(i); - } - } else { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i].join(" "); - if (matchWholeString(loglineText, searchText)) - indices.push(i); - } - } - } else { - if (!caseSearchBool) { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i][columnIndex].toLowerCase(); - if (matchWholeString(loglineText, searchText)) - indices.push(i); - } - } else { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i][columnIndex]; - if (matchWholeString(loglineText, searchText)) - indices.push(i); - } - } - } - } - } else { - let flags: string; - if (!caseSearchBool) flags = "gsi"; - else flags = "gs"; +): boolean => { + let logLine = getLogLine(logData, caseSearchBool); + + if (reSearchBool) { + let flags = caseSearchBool ? "gs" : "gsi"; + if (wholeSearchBool) searchText = "\\b" + searchText + "\\b"; - if (columnIndex === -1) { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i].join(" "); - if (useRegularExpressionSearch(flags, searchText, loglineText)) - indices.push(i); - } - } else { - for (let i = 0; i < rows.length; i++) { - loglineText = rows[i][columnIndex]; - if (useRegularExpressionSearch(flags, searchText, loglineText)) - indices.push(i); - } - } + + return useRegularExpressionSearch(flags, searchText, logLine); } - return indices; + + if (wholeSearchBool) { + return matchWholeString(logLine, searchText); + } + + return logLine.indexOf(searchText) !== -1; +} + +export const returnSearchIndices = ( + logData: types.TracyLogData[][] | types.TracyLogData[], + searchText: string, + reSearchBool: boolean, + wholeSearchBool: boolean, + caseSearchBool: boolean, +): number[] => { + if (!caseSearchBool && !reSearchBool) searchText = searchText.toLowerCase(); + return logData.map((data) => matchesSearchQuery(data, searchText, reSearchBool, wholeSearchBool, caseSearchBool)) + .flatMap((match, i) => match ? [i] : []); }; export const useRegularExpressionSearch = ( diff --git a/src/viewer/hooks/useMessage.ts b/src/viewer/hooks/useMessage.ts new file mode 100644 index 0000000..d622b69 --- /dev/null +++ b/src/viewer/hooks/useMessage.ts @@ -0,0 +1,19 @@ +import { LoadFileForComparisonMessage, ReadExportPathMessage, ReadFileMessage } from "../interfaces"; + +export function isReadFileMessage(message: unknown): message is ReadFileMessage { + return message !== null && + typeof(message) === "object" && + message['type'] === 'readFile'; +} + +export function isLoadFileForComparisonMessage(message: unknown): message is LoadFileForComparisonMessage { + return message !== null && + typeof(message) === "object" && + message['type'] === 'loadFileForComparison'; +} + +export function isReadExportPathMessage(message: unknown): message is ReadExportPathMessage { + return message !== null && + typeof(message) === "object" && + message['type'] === 'readExportPath'; +} \ No newline at end of file diff --git a/src/viewer/hooks/useRowProperty.ts b/src/viewer/hooks/useRowProperty.ts index f71af9b..565ba98 100644 --- a/src/viewer/hooks/useRowProperty.ts +++ b/src/viewer/hooks/useRowProperty.ts @@ -1,15 +1,10 @@ -import { RowProperty, Segment } from "../types"; -import { SelectedRowType } from "../constants"; +import { RowProperty, Segment } from "../interfaces"; export const maximumSegmentation = 4; -export const constructNewRowProperty = ( - isRendered: boolean, - rowType: SelectedRowType, -): RowProperty => { - const newRowProperty: RowProperty = { isRendered, rowType }; - return newRowProperty; -}; +export function isSelected(rowProperty: RowProperty) { + return rowProperty.isSelected; +} export const constructNewSegment = (start: number, end: number, level: number) => { const newSegment: Segment = { start, end, level }; @@ -32,3 +27,23 @@ export const getSegment = (segments: Segment[], start: number) => { } }); }; + +export function createDefaultRowProperties(n: number): RowProperty[] { + let rowProperties: RowProperty[] = []; + + for (let i = 0; i < n; i++) { + rowProperties.push(createDefaultRowProperty(i)); + } + + return rowProperties; +} + +export function createDefaultRowProperty(index: number): RowProperty { + return { + index, + isRendered: true, + isSelected: false, + isQueried: false, + isHighlighted: false + }; +} \ No newline at end of file diff --git a/src/viewer/hooks/useStructureEntryManager.ts b/src/viewer/hooks/useStructureEntryManager.ts index 7586b32..6bd4ef4 100644 --- a/src/viewer/hooks/useStructureEntryManager.ts +++ b/src/viewer/hooks/useStructureEntryManager.ts @@ -1,202 +1,204 @@ -import { StructureHeaderColumnType, StructureLinkDistance } from "../constants"; -import { StructureEntry, Wildcard } from "../types"; - -export const constructStructureEntriesArray = ( - headerColumnTypes: StructureHeaderColumnType[], - selectedRows: string[][], -): StructureEntry[] => { - const structureEntries: StructureEntry[] = []; - let structureEntry: StructureEntry; - - for (let i = 0; i < selectedRows.length; i++) { - structureEntry = constructNewStructureEntry(headerColumnTypes, selectedRows[i]); - structureEntries.push(structureEntry); - } - - return structureEntries; -}; - -export const constructNewStructureEntry = ( - headerColumnType: StructureHeaderColumnType[], - row: string[], -): StructureEntry => { - const rowCellContents = row.map((v, _i) => { - return [{ contentsIndex: 0, textValue: v, wildcardIndex: null }]; - }); - - const allCellsSelected = row.map((_v, i) => { - if (headerColumnType[i] === StructureHeaderColumnType.Selected) { - return true; - } - return false; - }); - - const defaultStructureLink = StructureLinkDistance.Min; - - const arraysForWilcardIndexes: number[][] = []; - row.map(() => { - arraysForWilcardIndexes.push([]); - }); - - const newEntry: StructureEntry = { - row: rowCellContents, - cellSelection: allCellsSelected, - structureLink: defaultStructureLink, - wildcardsIndices: arraysForWilcardIndexes, - }; - - return newEntry; -}; - -export const appendNewStructureEntries = ( - currentStructureEntries: StructureEntry[], - newStructureEntries: StructureEntry[], -): StructureEntry[] => { - const lastIndexOfStructureEntry = currentStructureEntries.length - 1; - let modifiedStructureEntries: StructureEntry[] = currentStructureEntries; - - modifiedStructureEntries[lastIndexOfStructureEntry].structureLink = StructureLinkDistance.Min; - - newStructureEntries.forEach((newEntry) => { - modifiedStructureEntries.push(newEntry); - }); - - modifiedStructureEntries.sort((a, b) => - a.row[0][0].textValue.localeCompare(b.row[0][0].textValue, undefined, {'numeric': true}), - ); - - modifiedStructureEntries = removeLastStructureLink(modifiedStructureEntries); - - return modifiedStructureEntries; -}; - -export const removeStructureEntryFromList = ( - structureEntries: StructureEntry[], - indexOfRowToBeRemoved: number, -): StructureEntry[] => { - const lastIndexOfStructureEntry = structureEntries.length - 1; - const modifiedStructureEntries = structureEntries.filter((_v, i) => i !== indexOfRowToBeRemoved); - - if (indexOfRowToBeRemoved === lastIndexOfStructureEntry && modifiedStructureEntries.length != 0) { - modifiedStructureEntries[lastIndexOfStructureEntry - 1].structureLink = undefined; - } - - return modifiedStructureEntries; -}; - -export const updateStructureEntriesAfterWildcardDeletion = ( - structureEntries: StructureEntry[], - wildcards: Wildcard[], - deletedWildcardIndex: number, -): StructureEntry[] => { - const modifiedStructureEntries = structureEntries; - - for (let e = 0; e < modifiedStructureEntries.length; e++) { - for (let c = 0; c < modifiedStructureEntries[e].row.length; c++) { - for (let w = 0; w < modifiedStructureEntries[e].wildcardsIndices[c].length; w++) { - if (modifiedStructureEntries[e].wildcardsIndices[c][w] > deletedWildcardIndex) { - modifiedStructureEntries[e].wildcardsIndices[c][w] -= 1; - } - } - - for (let cc = 0; cc < modifiedStructureEntries[e].row[c].length; cc++) { - if ( - modifiedStructureEntries[e].row[c][cc].wildcardIndex && - modifiedStructureEntries[e].row[c][cc].wildcardIndex! > deletedWildcardIndex - ) { - modifiedStructureEntries[e].row[c][cc].wildcardIndex! -= 1; - } - } - } - } - - return modifiedStructureEntries; -}; - -export const toggleStructureLink = ( - structureEntries: StructureEntry[], - structureEntryIndex: number, -): StructureEntry[] => { - const modifiedStructureEntries = structureEntries; - - let structureLink = modifiedStructureEntries[structureEntryIndex].structureLink; - - switch (structureLink) { - case StructureLinkDistance.None: - structureLink = StructureLinkDistance.Min; - break; - case StructureLinkDistance.Min: - structureLink = StructureLinkDistance.Max; - break; - case StructureLinkDistance.Max: - structureLink = StructureLinkDistance.None; - break; - } - - modifiedStructureEntries[structureEntryIndex].structureLink = structureLink; - - return modifiedStructureEntries; -}; - -export const toggleCellSelection = ( - headerColumnType: StructureHeaderColumnType[], - structureEntries: StructureEntry[], - structureEntryIndex: number, - cellIndex: number, - isShiftPressed: boolean, -): StructureEntry[] => { - const modifiedStructureEntries = structureEntries; - let selectedCell = modifiedStructureEntries[structureEntryIndex].cellSelection[cellIndex]; - - if (headerColumnType[cellIndex] !== StructureHeaderColumnType.Custom) { - modifiedStructureEntries[structureEntryIndex].cellSelection[cellIndex] = !selectedCell; - selectedCell = modifiedStructureEntries[structureEntryIndex].cellSelection[cellIndex]; - - if (isShiftPressed) { - //toggle the selection of all the other cells - modifiedStructureEntries[structureEntryIndex].cellSelection.forEach((cell, index) => { - if (index != cellIndex && headerColumnType[index] !== StructureHeaderColumnType.Custom) { - modifiedStructureEntries[structureEntryIndex].cellSelection[index] = !selectedCell; - } - }); - } - } - - return modifiedStructureEntries; -}; - -export const removeLastStructureLink = (structureEntries: StructureEntry[]): StructureEntry[] => { - const modifiedStructureEntries = structureEntries; - - modifiedStructureEntries[modifiedStructureEntries.length - 1].structureLink = undefined; - - return modifiedStructureEntries; -}; - -export const addWildcardToStructureEntry = ( - structureEntries: StructureEntry[], - structureEntryIndex: number, - cellIndex: number, - wildcardIndex: number, -): StructureEntry[] => { - const modifiedStructureEntries = structureEntries; - modifiedStructureEntries[structureEntryIndex].wildcardsIndices[cellIndex].push(wildcardIndex); - - return modifiedStructureEntries; -}; - -export const removeWildcardFromStructureEntry = ( - structureEntries: StructureEntry[], - structureEntryIndex: number, - cellIndex: number, - wildcardIndex: number, -): StructureEntry[] => { - const modifiedStructureEntries = structureEntries; - let wildcardsInCell = modifiedStructureEntries[structureEntryIndex].wildcardsIndices[cellIndex]; - - wildcardsInCell = wildcardsInCell.filter((value) => value !== wildcardIndex); - - modifiedStructureEntries[structureEntryIndex].wildcardsIndices[cellIndex] = wildcardsInCell; - - return modifiedStructureEntries; -}; +import { enums } from "../enums"; +import { StructureEntry, Wildcard } from "../interfaces"; +import { types } from "../types"; +import { logDataToString } from "./useTracyLogData"; + +export const constructStructureEntriesArray = ( + headerColumnTypes: enums.StructureHeaderColumnType[], + selectedRows: types.TracyLogData[][], +): StructureEntry[] => { + const structureEntries: StructureEntry[] = []; + let structureEntry: StructureEntry; + + for (let i = 0; i < selectedRows.length; i++) { + structureEntry = constructNewStructureEntry(headerColumnTypes, selectedRows[i]); + structureEntries.push(structureEntry); + } + + return structureEntries; +}; + +export const constructNewStructureEntry = ( + headerColumnType: enums.StructureHeaderColumnType[], + row: types.TracyLogData[], +): StructureEntry => { + const rowCellContents = row.map((v, _i) => { + return [{ contentsIndex: 0, textValue: logDataToString(v), wildcardIndex: null }]; + }); + + const allCellsSelected = row.map((_v, i) => { + if (headerColumnType[i] === enums.StructureHeaderColumnType.Selected) { + return true; + } + return false; + }); + + const defaultStructureLink = enums.StructureLinkDistance.Min; + + const arraysForWilcardIndexes: number[][] = []; + row.map(() => { + arraysForWilcardIndexes.push([]); + }); + + const newEntry: StructureEntry = { + row: rowCellContents, + cellSelection: allCellsSelected, + structureLink: defaultStructureLink, + wildcardsIndices: arraysForWilcardIndexes, + }; + + return newEntry; +}; + +export const appendNewStructureEntries = ( + currentStructureEntries: StructureEntry[], + newStructureEntries: StructureEntry[], +): StructureEntry[] => { + const lastIndexOfStructureEntry = currentStructureEntries.length - 1; + let modifiedStructureEntries: StructureEntry[] = currentStructureEntries; + + modifiedStructureEntries[lastIndexOfStructureEntry].structureLink = enums.StructureLinkDistance.Min; + + newStructureEntries.forEach((newEntry) => { + modifiedStructureEntries.push(newEntry); + }); + + modifiedStructureEntries.sort((a, b) => + a.row[0][0].textValue.localeCompare(b.row[0][0].textValue, undefined, {'numeric': true}), + ); + + modifiedStructureEntries = removeLastStructureLink(modifiedStructureEntries); + + return modifiedStructureEntries; +}; + +export const removeStructureEntryFromList = ( + structureEntries: StructureEntry[], + indexOfRowToBeRemoved: number, +): StructureEntry[] => { + const lastIndexOfStructureEntry = structureEntries.length - 1; + const modifiedStructureEntries = structureEntries.filter((_v, i) => i !== indexOfRowToBeRemoved); + + if (indexOfRowToBeRemoved === lastIndexOfStructureEntry && modifiedStructureEntries.length != 0) { + modifiedStructureEntries[lastIndexOfStructureEntry - 1].structureLink = undefined; + } + + return modifiedStructureEntries; +}; + +export const updateStructureEntriesAfterWildcardDeletion = ( + structureEntries: StructureEntry[], + wildcards: Wildcard[], + deletedWildcardIndex: number, +): StructureEntry[] => { + const modifiedStructureEntries = structureEntries; + + for (let e = 0; e < modifiedStructureEntries.length; e++) { + for (let c = 0; c < modifiedStructureEntries[e].row.length; c++) { + for (let w = 0; w < modifiedStructureEntries[e].wildcardsIndices[c].length; w++) { + if (modifiedStructureEntries[e].wildcardsIndices[c][w] > deletedWildcardIndex) { + modifiedStructureEntries[e].wildcardsIndices[c][w] -= 1; + } + } + + for (let cc = 0; cc < modifiedStructureEntries[e].row[c].length; cc++) { + if ( + modifiedStructureEntries[e].row[c][cc].wildcardIndex && + modifiedStructureEntries[e].row[c][cc].wildcardIndex! > deletedWildcardIndex + ) { + modifiedStructureEntries[e].row[c][cc].wildcardIndex! -= 1; + } + } + } + } + + return modifiedStructureEntries; +}; + +export const toggleStructureLink = ( + structureEntries: StructureEntry[], + structureEntryIndex: number, +): StructureEntry[] => { + const modifiedStructureEntries = structureEntries; + + let structureLink = modifiedStructureEntries[structureEntryIndex].structureLink; + + switch (structureLink) { + case enums.StructureLinkDistance.None: + structureLink = enums.StructureLinkDistance.Min; + break; + case enums.StructureLinkDistance.Min: + structureLink = enums.StructureLinkDistance.Max; + break; + case enums.StructureLinkDistance.Max: + structureLink = enums.StructureLinkDistance.None; + break; + } + + modifiedStructureEntries[structureEntryIndex].structureLink = structureLink; + + return modifiedStructureEntries; +}; + +export const toggleCellSelection = ( + headerColumnType: enums.StructureHeaderColumnType[], + structureEntries: StructureEntry[], + structureEntryIndex: number, + cellIndex: number, + isShiftPressed: boolean, +): StructureEntry[] => { + const modifiedStructureEntries = structureEntries; + let selectedCell = modifiedStructureEntries[structureEntryIndex].cellSelection[cellIndex]; + + if (headerColumnType[cellIndex] !== enums.StructureHeaderColumnType.Custom) { + modifiedStructureEntries[structureEntryIndex].cellSelection[cellIndex] = !selectedCell; + selectedCell = modifiedStructureEntries[structureEntryIndex].cellSelection[cellIndex]; + + if (isShiftPressed) { + //toggle the selection of all the other cells + modifiedStructureEntries[structureEntryIndex].cellSelection.forEach((cell, index) => { + if (index != cellIndex && headerColumnType[index] !== enums.StructureHeaderColumnType.Custom) { + modifiedStructureEntries[structureEntryIndex].cellSelection[index] = !selectedCell; + } + }); + } + } + + return modifiedStructureEntries; +}; + +export const removeLastStructureLink = (structureEntries: StructureEntry[]): StructureEntry[] => { + const modifiedStructureEntries = structureEntries; + + modifiedStructureEntries[modifiedStructureEntries.length - 1].structureLink = undefined; + + return modifiedStructureEntries; +}; + +export const addWildcardToStructureEntry = ( + structureEntries: StructureEntry[], + structureEntryIndex: number, + cellIndex: number, + wildcardIndex: number, +): StructureEntry[] => { + const modifiedStructureEntries = structureEntries; + modifiedStructureEntries[structureEntryIndex].wildcardsIndices[cellIndex].push(wildcardIndex); + + return modifiedStructureEntries; +}; + +export const removeWildcardFromStructureEntry = ( + structureEntries: StructureEntry[], + structureEntryIndex: number, + cellIndex: number, + wildcardIndex: number, +): StructureEntry[] => { + const modifiedStructureEntries = structureEntries; + let wildcardsInCell = modifiedStructureEntries[structureEntryIndex].wildcardsIndices[cellIndex]; + + wildcardsInCell = wildcardsInCell.filter((value) => value !== wildcardIndex); + + modifiedStructureEntries[structureEntryIndex].wildcardsIndices[cellIndex] = wildcardsInCell; + + return modifiedStructureEntries; +}; diff --git a/src/viewer/hooks/useStructureRegularExpressionManager.ts b/src/viewer/hooks/useStructureRegularExpressionManager.ts index a525e47..d17fd84 100644 --- a/src/viewer/hooks/useStructureRegularExpressionManager.ts +++ b/src/viewer/hooks/useStructureRegularExpressionManager.ts @@ -1,310 +1,310 @@ -import { CellContents, Header, LogEntryCharMaps, StructureEntry, Wildcard } from "../types"; -import { StructureHeaderColumnType, StructureLinkDistance } from "../constants"; -import { isSubstitutionFirstForWildcard } from "./useWildcardManager"; - -const regExpAnyCharMin = ".+?"; -const regExpAnyCharMax = ".+"; -const regExpLineFeed = "\\n"; -const regExpTimeStampPattern = "[A-Za-z0-9 ,:_=@|*+.\\(\\)\\[\\]\\/\\-]*?"; -const regExpValuePattern = "[A-Za-z0-9 ,:;~`'\"_=@#%&|!$^*+<>?.{}()\\[\\]\\/\\\\-]*?"; -const regExpjsonObject = "{.+?},?\\n"; -const flags = "gs"; - -const getRegExpExactWhiteSpace = (amountOfWhitespace: number): string => - `\\s{${amountOfWhitespace}}`; - -const escapeSpecialChars = (text: string): string => { - let safeText = ""; - - if (text !== undefined) { - safeText = text.replace(/[\\]/g, "\\\\\\$&"); //double escaped slashes - safeText = safeText.replace(/[\.\*\+\?\^\$\{\}\(\)\|\[\]\-]/g, "\\$&"); // replace special characters - safeText = safeText.replace(/[\"]/g, "\\\\$&"); //double quotes - } - - const regExpCarriageReturnAtEnd = /\r$/; - - if (regExpCarriageReturnAtEnd.test(text)) { - safeText = safeText.replace(regExpCarriageReturnAtEnd, "\\\\r"); - } - - return safeText; -}; - -const getLineEndString = (amountOfWhiteSpace: number): string => { - return regExpLineFeed + getRegExpExactWhiteSpace(amountOfWhiteSpace); -}; - -function getHeaderValue(text: string) { - let headerValue = text; - - headerValue = `"${escapeSpecialChars(headerValue)}"`; - - return headerValue; -} - -const getCellValue = ( - content: CellContents[], - rowIndex: number, - cellIndex: number, - header: Header, - headerColumnType: StructureHeaderColumnType, - isSelected: boolean, - wildcards: Wildcard[], -): string => { - let value: string; - - if (isSelected && headerColumnType !== StructureHeaderColumnType.Custom) { - if (content[0].textValue == null) value = "null"; - else { - const valueParts: string[] = []; - - for (let i = 0; i < content.length; i++) { - if (content[i].wildcardIndex == null) { - valueParts.push(`${escapeSpecialChars(content[i].textValue)}`); - } else { - const isFirstSubstitution = isSubstitutionFirstForWildcard( - wildcards[content[i].wildcardIndex!], - rowIndex, - cellIndex, - i, - ); - - if (isFirstSubstitution) { - valueParts.push(`(?${regExpValuePattern})`); - } else { - valueParts.push(`\\k`); - } - } - } - - value = '"' + valueParts.join("") + '"'; - } - } else { - value = - header.name.toLowerCase() === "timestamp" - ? `"${regExpTimeStampPattern}"` - : `"${regExpValuePattern}"`; - } - - return value; -}; - -const getRegExpForLogEntry = ( - logHeaders: Header[], - headerTypes: StructureHeaderColumnType[], - row: CellContents[][], - rowIndex: number, - cellSelection: boolean[], - wildcards: Wildcard[], -): string => { - let objectString = "{" + getLineEndString(4); - let rowString = ""; - let hasProcessedLastUsableColumn = false; - - for (let c = logHeaders.length - 1; c >= 0; c--) { - - const headerString = getHeaderValue(logHeaders[c].name); - - const headerType = headerTypes[c]; - const isCellSelected = cellSelection[c]; - - if (headerType !== StructureHeaderColumnType.Custom && row[c] !== undefined) { - let valueString = getCellValue( - row[c], - rowIndex, - c, - logHeaders[c], - headerType, - isCellSelected, - wildcards, - ); - let headerAndCellString = ""; - - if (hasProcessedLastUsableColumn) { - valueString = valueString.concat(","); - headerAndCellString = headerAndCellString.concat( - headerString, - ": ", - valueString, - getLineEndString(4), - ); - } else { - headerAndCellString = headerAndCellString.concat( - headerString, - ": ", - valueString, - getLineEndString(2), - ); - } - - rowString = headerAndCellString.concat(rowString); - - hasProcessedLastUsableColumn = true; - } - } - - objectString = objectString.concat(rowString, "},?", regExpLineFeed); - - return objectString; -}; - -export const useStructureQueryConstructor = ( - logHeaders: Header[], - headerColumnTypes: StructureHeaderColumnType[], - structureEntries: StructureEntry[], - wildcards: Wildcard[], -): string => { - let regularExp = ""; - - for (let r = 0; r < structureEntries.length; r++) { - const structureEntry = structureEntries[r]; - - const rowRegExp = getRegExpForLogEntry( - logHeaders, - headerColumnTypes, - structureEntry.row, - r, - structureEntry.cellSelection, - wildcards, - ); - regularExp = regularExp.concat(rowRegExp); - - if (structureEntry.structureLink !== undefined) { - let structureLinkRegExp = ""; - - switch (structureEntry.structureLink) { - case StructureLinkDistance.None: - structureLinkRegExp = getRegExpExactWhiteSpace(2); - break; - case StructureLinkDistance.Min: - structureLinkRegExp = regExpAnyCharMin; - break; - case StructureLinkDistance.Max: - structureLinkRegExp = regExpAnyCharMax; - break; - } - - regularExp = regularExp.concat(structureLinkRegExp); - } - } - - return regularExp; -}; - -export const useGetCharIndicesForLogEntries = (logFileAsString: string): LogEntryCharMaps => { - const perfStart = performance.now(); - const jsonObjectsRegExp = new RegExp(regExpjsonObject, flags); - const firstCharIndexMap = new Map(); - const lastCharIndexMap = new Map(); - let logEntryIndex = 0; - - let result = jsonObjectsRegExp.exec(logFileAsString); - - if (result !== null) { - do { - firstCharIndexMap.set(result.index, logEntryIndex); - lastCharIndexMap.set(jsonObjectsRegExp.lastIndex, logEntryIndex); - logEntryIndex++; - } while ((result = jsonObjectsRegExp.exec(logFileAsString)) !== null); - } - - const perfEnd = performance.now(); - console.log(`Execution time (mapLogFileTextIndicesToObject()): ${perfEnd - perfStart} ms`); - - return { firstCharIndexMap: firstCharIndexMap, lastCharIndexMap: lastCharIndexMap }; -}; - -export const useStructureRegularExpressionSearch = ( - expression: string, - logFileAsString: string, - logEntryCharIndexMaps: LogEntryCharMaps, -): number[][] => { - console.log("Starting Structure Matching"); - const perfStart = performance.now(); - const textRanges: number[][] = []; - const structureQuery = new RegExp(expression, flags); - let result; - - while ((result = structureQuery.exec(logFileAsString)) !== null) { - textRanges.push([result.index, structureQuery.lastIndex]); - } - - const perfEnd = performance.now(); - console.log(`Execution time (regular expression run): ${perfEnd - perfStart} ms`); - - const transStart = performance.now(); - - const resultingMatches = extractMatches(textRanges, logEntryCharIndexMaps); - - const transEnd = performance.now(); - console.log(`Execution time (translation from char indices to logFile.rows indices): ${transEnd - transStart} ms`); - - return resultingMatches; -}; - -export const useStructureRegularExpressionNestedSearch = ( - minExpression: string, - maxExpression: string, - logFileAsString: string, - logEntryCharIndexMaps: LogEntryCharMaps, -): number[][] => { - const textRanges: number[][] = []; - const minQuery = new RegExp(minExpression, "s"); - const maxQuery = new RegExp(maxExpression, "s"); - - let previousIndex = 0; - let remainingText = logFileAsString; - - while (true) { - let match = remainingText.match(minQuery); - if ((match == undefined) || (match.index == undefined)) - break; - else { - let startIndex = previousIndex + match.index; - let lastIndex = startIndex + match[0].length; - textRanges.push([startIndex, lastIndex]); - previousIndex = startIndex + 1; - remainingText = remainingText.substring(match.index + 1); - } - } - - previousIndex = logFileAsString.length; - remainingText = logFileAsString; - - while (true) { - let match = remainingText.match(maxQuery); - if ((match == undefined) || (match.index == undefined)) - break; - else { - let startIndex = match.index; - let lastIndex = startIndex + match[0].length; - textRanges.push([startIndex, lastIndex]); - remainingText = remainingText.substring(0, lastIndex - 1); - } - } - - const resultingMatches = extractMatches(textRanges, logEntryCharIndexMaps); - - return resultingMatches; -}; - -function extractMatches(textRanges: number[][], logEntryCharIndexMaps: LogEntryCharMaps) { - let resultingMatches: number[][] = []; - textRanges.forEach((matchRanges) => { - const indexesOfEntriesInMatch: number[] = []; - - const indexOfFirstObjectInMatch = logEntryCharIndexMaps.firstCharIndexMap.get(matchRanges[0]); - const indexOfLastObjectInMatch = logEntryCharIndexMaps.lastCharIndexMap.get(matchRanges[1]); - - if ((indexOfFirstObjectInMatch !== undefined) && (indexOfLastObjectInMatch !== undefined)) { - - for (let i = indexOfFirstObjectInMatch; i <= indexOfLastObjectInMatch; i++) { - indexesOfEntriesInMatch.push(i); - } - resultingMatches.push(indexesOfEntriesInMatch); - } - - }); - return resultingMatches; +import { CellContents, LogEntryCharMaps, StructureEntry, Wildcard } from "../interfaces"; +import { isSubstitutionFirstForWildcard } from "./useWildcardManager"; +import { enums } from "../enums"; + +const regExpAnyCharMin = ".+?"; +const regExpAnyCharMax = ".+"; +const regExpLineFeed = "\\n"; +const regExpTimeStampPattern = "[A-Za-z0-9 ,:_=@|*+.\\(\\)\\[\\]\\/\\-]*?"; +const regExpValuePattern = "[A-Za-z0-9 ,:;~`'\"_=@#%&|!$^*+<>?.{}()\\[\\]\\/\\\\-]*?"; +const regExpjsonObject = "{.+?},?\\n"; +const flags = "gs"; + +const getRegExpExactWhiteSpace = (amountOfWhitespace: number): string => + `\\s{${amountOfWhitespace}}`; + +const escapeSpecialChars = (text: string): string => { + let safeText = ""; + + if (text !== undefined) { + safeText = text.replace(/[\\]/g, "\\\\\\$&"); //double escaped slashes + safeText = safeText.replace(/[\.\*\+\?\^\$\{\}\(\)\|\[\]\-]/g, "\\$&"); // replace special characters + safeText = safeText.replace(/[\"]/g, "\\\\$&"); //double quotes + } + + const regExpCarriageReturnAtEnd = /\r$/; + + if (regExpCarriageReturnAtEnd.test(text)) { + safeText = safeText.replace(regExpCarriageReturnAtEnd, "\\\\r"); + } + + return safeText; +}; + +const getLineEndString = (amountOfWhiteSpace: number): string => { + return regExpLineFeed + getRegExpExactWhiteSpace(amountOfWhiteSpace); +}; + +function getHeaderValue(text: string) { + let headerValue = text; + + headerValue = `"${escapeSpecialChars(headerValue)}"`; + + return headerValue; +} + +const getCellValue = ( + content: CellContents[], + rowIndex: number, + cellIndex: number, + header: string, + headerColumnType: enums.StructureHeaderColumnType, + isSelected: boolean, + wildcards: Wildcard[], +): string => { + let value: string; + + if (isSelected && headerColumnType !== enums.StructureHeaderColumnType.Custom) { + if (content[0].textValue == null) value = "null"; + else { + const valueParts: string[] = []; + + for (let i = 0; i < content.length; i++) { + if (content[i].wildcardIndex == null) { + valueParts.push(`${escapeSpecialChars(content[i].textValue)}`); + } else { + const isFirstSubstitution = isSubstitutionFirstForWildcard( + wildcards[content[i].wildcardIndex!], + rowIndex, + cellIndex, + i, + ); + + if (isFirstSubstitution) { + valueParts.push(`(?${regExpValuePattern})`); + } else { + valueParts.push(`\\k`); + } + } + } + + value = '"' + valueParts.join("") + '"'; + } + } else { + value = + header.toLowerCase() === "timestamp" + ? `"${regExpTimeStampPattern}"` + : `"${regExpValuePattern}"`; + } + + return value; +}; + +const getRegExpForLogEntry = ( + logHeaders: string[], + headerTypes: enums.StructureHeaderColumnType[], + row: CellContents[][], + rowIndex: number, + cellSelection: boolean[], + wildcards: Wildcard[], +): string => { + let objectString = "{" + getLineEndString(4); + let rowString = ""; + let hasProcessedLastUsableColumn = false; + + for (let c = logHeaders.length - 1; c >= 0; c--) { + + const headerString = getHeaderValue(logHeaders[c]); + + const headerType = headerTypes[c]; + const isCellSelected = cellSelection[c]; + + if (headerType !== enums.StructureHeaderColumnType.Custom && row[c] !== undefined) { + let valueString = getCellValue( + row[c], + rowIndex, + c, + logHeaders[c], + headerType, + isCellSelected, + wildcards, + ); + let headerAndCellString = ""; + + if (hasProcessedLastUsableColumn) { + valueString = valueString.concat(","); + headerAndCellString = headerAndCellString.concat( + headerString, + ": ", + valueString, + getLineEndString(4), + ); + } else { + headerAndCellString = headerAndCellString.concat( + headerString, + ": ", + valueString, + getLineEndString(2), + ); + } + + rowString = headerAndCellString.concat(rowString); + + hasProcessedLastUsableColumn = true; + } + } + + objectString = objectString.concat(rowString, "},?", regExpLineFeed); + + return objectString; +}; + +export const useStructureQueryConstructor = ( + logHeaders: string[], + headerColumnTypes: enums.StructureHeaderColumnType[], + structureEntries: StructureEntry[], + wildcards: Wildcard[], +): string => { + let regularExp = ""; + + for (let r = 0; r < structureEntries.length; r++) { + const structureEntry = structureEntries[r]; + + const rowRegExp = getRegExpForLogEntry( + logHeaders, + headerColumnTypes, + structureEntry.row, + r, + structureEntry.cellSelection, + wildcards, + ); + regularExp = regularExp.concat(rowRegExp); + + if (structureEntry.structureLink !== undefined) { + let structureLinkRegExp = ""; + + switch (structureEntry.structureLink) { + case enums.StructureLinkDistance.None: + structureLinkRegExp = getRegExpExactWhiteSpace(2); + break; + case enums.StructureLinkDistance.Min: + structureLinkRegExp = regExpAnyCharMin; + break; + case enums.StructureLinkDistance.Max: + structureLinkRegExp = regExpAnyCharMax; + break; + } + + regularExp = regularExp.concat(structureLinkRegExp); + } + } + + return regularExp; +}; + +export const useGetCharIndicesForLogEntries = (logFileAsString: string): LogEntryCharMaps => { + const perfStart = performance.now(); + const jsonObjectsRegExp = new RegExp(regExpjsonObject, flags); + const firstCharIndexMap = new Map(); + const lastCharIndexMap = new Map(); + let logEntryIndex = 0; + + let result = jsonObjectsRegExp.exec(logFileAsString); + + if (result !== null) { + do { + firstCharIndexMap.set(result.index, logEntryIndex); + lastCharIndexMap.set(jsonObjectsRegExp.lastIndex, logEntryIndex); + logEntryIndex++; + } while ((result = jsonObjectsRegExp.exec(logFileAsString)) !== null); + } + + const perfEnd = performance.now(); + console.log(`Execution time (mapLogFileTextIndicesToObject()): ${perfEnd - perfStart} ms`); + + return { firstCharIndexMap: firstCharIndexMap, lastCharIndexMap: lastCharIndexMap }; +}; + +export const useStructureRegularExpressionSearch = ( + expression: string, + logFileAsString: string, + logEntryCharIndexMaps: LogEntryCharMaps, +): number[][] => { + console.log("Starting Structure Matching"); + const perfStart = performance.now(); + const textRanges: number[][] = []; + const structureQuery = new RegExp(expression, flags); + let result; + + while ((result = structureQuery.exec(logFileAsString)) !== null) { + textRanges.push([result.index, structureQuery.lastIndex]); + } + + const perfEnd = performance.now(); + console.log(`Execution time (regular expression run): ${perfEnd - perfStart} ms`); + + const transStart = performance.now(); + + const resultingMatches = extractMatches(textRanges, logEntryCharIndexMaps); + + const transEnd = performance.now(); + console.log(`Execution time (translation from char indices to logFile.rows indices): ${transEnd - transStart} ms`); + + return resultingMatches; +}; + +export const useStructureRegularExpressionNestedSearch = ( + minExpression: string, + maxExpression: string, + logFileAsString: string, + logEntryCharIndexMaps: LogEntryCharMaps, +): number[][] => { + const textRanges: number[][] = []; + const minQuery = new RegExp(minExpression, "s"); + const maxQuery = new RegExp(maxExpression, "s"); + + let previousIndex = 0; + let remainingText = logFileAsString; + + while (true) { + let match = remainingText.match(minQuery); + if ((match == undefined) || (match.index == undefined)) + break; + else { + let startIndex = previousIndex + match.index; + let lastIndex = startIndex + match[0].length; + textRanges.push([startIndex, lastIndex]); + previousIndex = startIndex + 1; + remainingText = remainingText.substring(match.index + 1); + } + } + + previousIndex = logFileAsString.length; + remainingText = logFileAsString; + + while (true) { + let match = remainingText.match(maxQuery); + if ((match == undefined) || (match.index == undefined)) + break; + else { + let startIndex = match.index; + let lastIndex = startIndex + match[0].length; + textRanges.push([startIndex, lastIndex]); + remainingText = remainingText.substring(0, lastIndex - 1); + } + } + + const resultingMatches = extractMatches(textRanges, logEntryCharIndexMaps); + + return resultingMatches; +}; + +function extractMatches(textRanges: number[][], logEntryCharIndexMaps: LogEntryCharMaps) { + let resultingMatches: number[][] = []; + textRanges.forEach((matchRanges) => { + const indexesOfEntriesInMatch: number[] = []; + + const indexOfFirstObjectInMatch = logEntryCharIndexMaps.firstCharIndexMap.get(matchRanges[0]); + const indexOfLastObjectInMatch = logEntryCharIndexMaps.lastCharIndexMap.get(matchRanges[1]); + + if ((indexOfFirstObjectInMatch !== undefined) && (indexOfLastObjectInMatch !== undefined)) { + + for (let i = indexOfFirstObjectInMatch; i <= indexOfLastObjectInMatch; i++) { + indexesOfEntriesInMatch.push(i); + } + resultingMatches.push(indexesOfEntriesInMatch); + } + + }); + return resultingMatches; }; \ No newline at end of file diff --git a/src/viewer/hooks/useStyleManager.ts b/src/viewer/hooks/useStyleManager.ts index bb6a2fb..9f2ddab 100644 --- a/src/viewer/hooks/useStyleManager.ts +++ b/src/viewer/hooks/useStyleManager.ts @@ -1,372 +1,352 @@ -import { - SelectedRowType, - LOG_HEADER_HEIGHT, - LOG_ROW_HEIGHT, - STRUCTURE_LINK_HEIGHT, - STRUCTURE_WIDTH, - BORDER, - BORDER_SELECTED_ROW, - BORDER_STRUCTURE_MATCH_CURRENT, - BORDER_STRUCTURE_MATCH_OTHER, - BACKGROUND_COLOR_MATCHED_ROW_CURRENT, - BACKGROUND_COLOR_MATCHED_ROW_OTHER, - BACKGROUND_COLOR_SELECTED_ROW, - BACKGROUND_COLOR_SEARCH_ROW, -} from "../constants"; -import { RowProperty, StructureEntry } from "../types"; - -const getLogViewRowStyle = (rowIndex: number, leftPadding: number): React.CSSProperties => { - const rowStyle: React.CSSProperties = { - left: leftPadding, - position: "absolute", - height: LOG_ROW_HEIGHT, - overflow: "hidden", - top: rowIndex * LOG_ROW_HEIGHT, - userSelect: "text", - borderRadius: "5px", - }; - - return rowStyle; -}; - -export const structureDialogBackdropStyle: React.CSSProperties = { - bottom: "10px", - width: "100%", - backgroundColor: "#00000030", - display: "flex", - justifyContent: "center", - alignItems: "center", - overflow: "visible", -}; - -export const structureDialogDialogStyle: React.CSSProperties = { - width: "98%", - padding: "10px", - display: "flex", - flexDirection: "column", - overflow: "scroll", -}; - -export const wildcardStyle: React.CSSProperties = { - border: "1px solid", - borderRadius: "2px", - userSelect: "none", -}; - -export const getStructureTableHeaderStyle = (containerWidth: number): React.CSSProperties => { - const headerStyle: React.CSSProperties = { - width: containerWidth, - height: LOG_HEADER_HEIGHT, - position: "relative", - userSelect: "none", - left: STRUCTURE_WIDTH, - display: "flex", - }; - - return headerStyle; -}; - -export const getHeaderColumnStyle = ( - columnWidth: number, - columnIndex: number, - height: number, -): React.CSSProperties => { - const headerColumnStyle: React.CSSProperties = { - overflow: "hidden", - whiteSpace: "nowrap", - display: "inline-block", - height, - width: columnWidth, - }; - - return headerColumnStyle; -}; - -export const getSegmentStyle = (columnWidth: number, height: number): React.CSSProperties => { - const headerColumnStyle: React.CSSProperties = { - overflow: "hidden", - whiteSpace: "nowrap", - display: "inline-block", - height, - width: columnWidth, - position: "sticky", - left: 0, - }; - - return headerColumnStyle; -}; - -export const getSegmentRowStyle = (segmentWidth: number, top: number): React.CSSProperties => { - const segmentRowStyle: React.CSSProperties = { - position: "absolute", - height: LOG_ROW_HEIGHT, - top: top, - width: segmentWidth, - } - return segmentRowStyle; -} -export const getHeaderColumnInnerStyle = ( - height: number, - isHeader: boolean, -): React.CSSProperties => { - const headerColumnInnerStyle: React.CSSProperties = { - display: "flex", - height, - alignItems: "center", - justifyContent: isHeader ? "center" : "left", - paddingLeft: "2px", - borderRight: BORDER, - }; - - return headerColumnInnerStyle; -}; - -export const getStructureTableEntryIconStyle = ( - isRemovingStructureEntries: boolean, -): React.CSSProperties => { - const structureEntryIconStyle: React.CSSProperties = { - width: STRUCTURE_WIDTH, - height: LOG_ROW_HEIGHT, - display: "inline-block", - verticalAlign: "top", - textAlign: "center", - lineHeight: `${LOG_ROW_HEIGHT}px`, - color: isRemovingStructureEntries ? "red" : "", - }; - - return structureEntryIconStyle; -}; - -export const getStructureTableRowStyle = ( - rowIndex: number, - structureLinkIndex: number, -): React.CSSProperties => { - const rowStyle: React.CSSProperties = { - position: "absolute", - height: LOG_ROW_HEIGHT, - top: rowIndex * LOG_ROW_HEIGHT + structureLinkIndex * STRUCTURE_LINK_HEIGHT, - overflow: "hidden", - }; - - return rowStyle; -}; - -export const getStructureTableLinkStyle = ( - rowIndex: number, - structureLinkIndex: number, -): React.CSSProperties => { - const structureLinkStyle: React.CSSProperties = { - position: "absolute", - height: STRUCTURE_LINK_HEIGHT, - top: (rowIndex + 1) * LOG_ROW_HEIGHT + structureLinkIndex * STRUCTURE_LINK_HEIGHT, - overflow: "hidden", - userSelect: "none", - width: STRUCTURE_WIDTH, - textAlign: "center", - }; - - return structureLinkStyle; -}; - -export const getStructureTableColumnStyle = ( - columnWidth: number, - columnIndex: number, -): React.CSSProperties => { - const columnStyle: React.CSSProperties = { - overflow: "hidden", - whiteSpace: "nowrap", - display: "inline-block", - height: LOG_ROW_HEIGHT, - width: columnWidth, - verticalAlign: "top", - borderLeft: columnIndex !== 0 ? BORDER : "", - borderTop: BORDER, - borderBottom: BORDER, - }; - - return columnStyle; -}; - -export const getStructureTableCellSelectionStyle = ( - structureEntries: StructureEntry[], - rowIndex: number, - cellIndex: number, -): React.CSSProperties => { - let cellSelectionStyle: React.CSSProperties; - - if (!structureEntries[rowIndex].cellSelection[cellIndex]) { - cellSelectionStyle = { - display: "flex", - height: LOG_ROW_HEIGHT, - alignItems: "center", - justifyContent: "left", - paddingLeft: "2px", - color: "var(--vscode-titleBar-inactiveForeground)", - background: - "repeating-linear-gradient(-55deg, #222222b3, #222222b3 10px, #333333b3 10px, #333333b3 20px)", - userSelect: "none", - }; - } else { - cellSelectionStyle = { - display: "flex", - height: LOG_ROW_HEIGHT, - alignItems: "center", - justifyContent: "left", - paddingLeft: "2px", - backgroundColor: "transparent", - userSelect: "text", - }; - } - - return cellSelectionStyle; -}; - -export const getLogViewRowSelectionStyle = ( - selectedRows: RowProperty[], - rowIndex: number, - index: number, - left: number, -): React.CSSProperties => { - let rowSelectionStyle: React.CSSProperties = {}; - - switch (selectedRows[rowIndex].rowType) { - case SelectedRowType.UserSelect: - rowSelectionStyle = { - borderBottom: BORDER_SELECTED_ROW, - borderTop: BORDER_SELECTED_ROW, - backgroundColor: BACKGROUND_COLOR_SELECTED_ROW, - }; - break; - case SelectedRowType.SearchResult: - rowSelectionStyle = { - backgroundColor: BACKGROUND_COLOR_SEARCH_ROW, - }; - break; - case SelectedRowType.None: - rowSelectionStyle = { - borderBottom: BORDER, - }; - break; - } - - rowSelectionStyle = { ...getLogViewRowStyle(index, left), ...rowSelectionStyle }; - - return rowSelectionStyle; -}; - -export const getLogViewStructureMatchStyle = ( - currentStructureMatch: number[], - structureMatches: number[][], - rowIndex: number, - left: number, -): React.CSSProperties => { - const isCurrentMatch = currentStructureMatch.includes(rowIndex) ? true : false; - let structureMatchRowStyle = getLogViewRowStyle(rowIndex, left); - const currentStructureMatchLastIndex = currentStructureMatch.length - 1; - - const structureMatchFirstAndLastRowStyle = { - borderTop: isCurrentMatch ? BORDER_STRUCTURE_MATCH_CURRENT : BORDER_STRUCTURE_MATCH_OTHER, - borderBottom: isCurrentMatch ? BORDER_STRUCTURE_MATCH_CURRENT : BORDER_STRUCTURE_MATCH_OTHER, - backgroundColor: isCurrentMatch - ? BACKGROUND_COLOR_MATCHED_ROW_CURRENT - : BACKGROUND_COLOR_MATCHED_ROW_OTHER, - }; - const structureMatchFirstRowStyle = { - borderTop: isCurrentMatch ? BORDER_STRUCTURE_MATCH_CURRENT : BORDER_STRUCTURE_MATCH_OTHER, - borderBottom: BORDER, - backgroundColor: isCurrentMatch - ? BACKGROUND_COLOR_MATCHED_ROW_CURRENT - : BACKGROUND_COLOR_MATCHED_ROW_OTHER, - }; - const structureMatchMiddleRowStyle = { - backgroundColor: isCurrentMatch - ? BACKGROUND_COLOR_MATCHED_ROW_CURRENT - : BACKGROUND_COLOR_MATCHED_ROW_OTHER, - borderBottom: BORDER, - }; - const structureMatchLastRowStyle = { - borderBottom: isCurrentMatch ? BORDER_STRUCTURE_MATCH_CURRENT : BORDER_STRUCTURE_MATCH_OTHER, - backgroundColor: isCurrentMatch - ? BACKGROUND_COLOR_MATCHED_ROW_CURRENT - : BACKGROUND_COLOR_MATCHED_ROW_OTHER, - }; - - if (rowIndex === currentStructureMatch[0] && currentStructureMatch.length === 1) { - structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchFirstAndLastRowStyle }; - } else if (rowIndex === currentStructureMatch[0]) { - structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchFirstRowStyle }; - } else if (rowIndex === currentStructureMatch[currentStructureMatchLastIndex]) { - structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchLastRowStyle }; - } else if ( - currentStructureMatch[0] < rowIndex && - rowIndex < currentStructureMatch[currentStructureMatchLastIndex] - ) { - structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchMiddleRowStyle }; - } else { - structureMatches = structureMatches.filter((match) => match !== currentStructureMatch); - - for (let i = 0; i < structureMatches.length; i++) { - const match = structureMatches[i]; - - if ((match.length > 1 && rowIndex <= match[match.length - 1]) || rowIndex <= match[0]) { - if (rowIndex === match[0] && match.length === 1) { - structureMatchRowStyle = { - ...structureMatchRowStyle, - ...structureMatchFirstAndLastRowStyle, - }; - } else if (rowIndex === match[0]) { - structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchFirstRowStyle }; - } else if (rowIndex === match[match.length - 1]) { - structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchLastRowStyle }; - } else { - structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchMiddleRowStyle }; - } - - break; - } - } - } - - return structureMatchRowStyle; -}; - -export const getContextMenuStyle = ( - contextMenuHeight: number, - contextMenuWidth: number, - xPos: number, - yPos: number, -): React.CSSProperties => { - const contextMenuStyle: React.CSSProperties = { - height: contextMenuHeight, - width: contextMenuWidth, - top: yPos, - left: xPos, - position: "fixed", - userSelect: "none", - }; - - const vw = document.documentElement.clientWidth; - const vh = document.documentElement.clientHeight; - - if (xPos + contextMenuWidth > vw) contextMenuStyle.left = vw - contextMenuWidth; - - if (yPos + contextMenuHeight > vh) contextMenuStyle.top = vh - contextMenuHeight; - - return contextMenuStyle; -}; - -export const getContextMenuItemStyle = (isSelected: boolean): React.CSSProperties => { - const contextMenuItemStyle: React.CSSProperties = { - height: "28px", - lineHeight: "28px", - paddingLeft: "5px", - paddingRight: "5px", - borderRadius: "4px", - cursor: "pointer", - textOverflow: "ellipsis", - background: isSelected ? "var(--vscode-menu-selectionBackground)" : "", - color: isSelected ? "var(--vscode-menu-selectionForeground)" : "", - }; - - return contextMenuItemStyle; -}; +import { constants } from "../constants"; +import { RowProperty, StructureEntry } from "../interfaces"; + +const getLogViewRowStyle = (viewIndex: number, leftPadding: number): React.CSSProperties => { + const rowStyle: React.CSSProperties = { + left: leftPadding, + position: "absolute", + height: constants.LOG_ROW_HEIGHT, + overflow: "hidden", + top: viewIndex * constants.LOG_ROW_HEIGHT, + userSelect: "text", + borderRadius: "5px", + }; + + return rowStyle; +}; + +export const structureDialogBackdropStyle: React.CSSProperties = { + bottom: "10px", + width: "100%", + backgroundColor: "#00000030", + display: "flex", + justifyContent: "center", + alignItems: "center", + overflow: "visible", +}; + +export const structureDialogDialogStyle: React.CSSProperties = { + width: "98%", + padding: "10px", + display: "flex", + flexDirection: "column", + overflow: "scroll", +}; + +export const wildcardStyle: React.CSSProperties = { + border: "1px solid", + borderRadius: "2px", + userSelect: "none", +}; + +export const getStructureTableHeaderStyle = (containerWidth: number): React.CSSProperties => { + const headerStyle: React.CSSProperties = { + width: containerWidth, + height: constants.LOG_HEADER_HEIGHT, + position: "relative", + userSelect: "none", + left: constants.STRUCTURE_WIDTH, + display: "flex", + }; + + return headerStyle; +}; + +export const getHeaderColumnStyle = ( + columnWidth: number, + height: number, +): React.CSSProperties => { + const headerColumnStyle: React.CSSProperties = { + overflow: "hidden", + whiteSpace: "nowrap", + display: "inline-block", + height, + width: columnWidth, + }; + + return headerColumnStyle; +}; + +export const getSegmentStyle = (columnWidth: number, height: number): React.CSSProperties => { + const headerColumnStyle: React.CSSProperties = { + overflow: "hidden", + whiteSpace: "nowrap", + display: "inline-block", + height, + width: columnWidth, + position: "sticky", + left: 0, + }; + + return headerColumnStyle; +}; + +export const getSegmentRowStyle = (segmentWidth: number, top: number): React.CSSProperties => { + const segmentRowStyle: React.CSSProperties = { + position: "absolute", + height: constants.LOG_ROW_HEIGHT, + top: top, + width: segmentWidth, + } + return segmentRowStyle; +} +export const getHeaderColumnInnerStyle = ( + height: number, + isHeader: boolean, +): React.CSSProperties => { + const headerColumnInnerStyle: React.CSSProperties = { + display: "flex", + height, + alignItems: "center", + justifyContent: isHeader ? "center" : "left", + paddingLeft: "2px", + borderRight: constants.BORDER, + }; + + return headerColumnInnerStyle; +}; + +export const getStructureTableEntryIconStyle = ( + isRemovingStructureEntries: boolean, +): React.CSSProperties => { + const structureEntryIconStyle: React.CSSProperties = { + width: constants.STRUCTURE_WIDTH, + height: constants.LOG_ROW_HEIGHT, + display: "inline-block", + verticalAlign: "top", + textAlign: "center", + lineHeight: `${constants.LOG_ROW_HEIGHT}px`, + color: isRemovingStructureEntries ? "red" : "", + }; + + return structureEntryIconStyle; +}; + +export const getStructureTableRowStyle = ( + rowIndex: number, + structureLinkIndex: number, +): React.CSSProperties => { + const rowStyle: React.CSSProperties = { + position: "absolute", + height: constants.LOG_ROW_HEIGHT, + top: rowIndex * constants.LOG_ROW_HEIGHT + structureLinkIndex * constants.STRUCTURE_LINK_HEIGHT, + overflow: "hidden", + }; + + return rowStyle; +}; + +export const getStructureTableLinkStyle = ( + rowIndex: number, + structureLinkIndex: number, +): React.CSSProperties => { + const structureLinkStyle: React.CSSProperties = { + position: "absolute", + height: constants.STRUCTURE_LINK_HEIGHT, + top: (rowIndex + 1) * constants.LOG_ROW_HEIGHT + structureLinkIndex * constants.STRUCTURE_LINK_HEIGHT, + overflow: "hidden", + userSelect: "none", + width: constants.STRUCTURE_WIDTH, + textAlign: "center", + }; + + return structureLinkStyle; +}; + +export const getStructureTableColumnStyle = ( + columnWidth: number, + columnIndex: number, +): React.CSSProperties => { + const columnStyle: React.CSSProperties = { + overflow: "hidden", + whiteSpace: "nowrap", + display: "inline-block", + height: constants.LOG_ROW_HEIGHT, + width: columnWidth, + verticalAlign: "top", + borderLeft: columnIndex !== 0 ? constants.BORDER : "", + borderTop: constants.BORDER, + borderBottom: constants.BORDER, + }; + + return columnStyle; +}; + +export const getStructureTableCellSelectionStyle = ( + structureEntries: StructureEntry[], + rowIndex: number, + cellIndex: number, +): React.CSSProperties => { + let cellSelectionStyle: React.CSSProperties; + + if (!structureEntries[rowIndex].cellSelection[cellIndex]) { + cellSelectionStyle = { + display: "flex", + height: constants.LOG_ROW_HEIGHT, + alignItems: "center", + justifyContent: "left", + paddingLeft: "2px", + color: "var(--vscode-titleBar-inactiveForeground)", + background: + "repeating-linear-gradient(-55deg, #222222b3, #222222b3 10px, #333333b3 10px, #333333b3 20px)", + userSelect: "none", + }; + } else { + cellSelectionStyle = { + display: "flex", + height: constants.LOG_ROW_HEIGHT, + alignItems: "center", + justifyContent: "left", + paddingLeft: "2px", + backgroundColor: "transparent", + userSelect: "text", + }; + } + + return cellSelectionStyle; +}; + +export const getLogViewRowSelectionStyle = ( + rowProperties: RowProperty, + viewIndex: number +): React.CSSProperties => { + let rowSelectionStyle: React.CSSProperties = {}; + + // Row selection has priority over search results + if (rowProperties.isSelected) { + rowSelectionStyle = { + borderBottom: constants.BORDER_SELECTED_ROW, + borderTop: constants.BORDER_SELECTED_ROW, + backgroundColor: constants.BACKGROUND_COLOR_SELECTED_ROW, + }; + } else if (rowProperties.isQueried) { + rowSelectionStyle = { + backgroundColor: rowProperties.isHighlighted ? constants.BACKGROUND_COLOR_SEARCH_HIGHLIGHTED_ROW : constants.BACKGROUND_COLOR_SEARCH_ROW, + }; + } else { + rowSelectionStyle = { + borderBottom: constants.BORDER, + }; + } + + rowSelectionStyle = { ...getLogViewRowStyle(viewIndex, 0), ...rowSelectionStyle }; + + return rowSelectionStyle; +}; + +export const getLogViewStructureMatchStyle = ( + currentStructureMatch: number[], + structureMatches: number[][], + rowIndex: number, + left: number, +): React.CSSProperties => { + const isCurrentMatch = currentStructureMatch.includes(rowIndex) ? true : false; + let structureMatchRowStyle = getLogViewRowStyle(rowIndex, left); + const currentStructureMatchLastIndex = currentStructureMatch.length - 1; + + const structureMatchFirstAndLastRowStyle = { + borderTop: isCurrentMatch ? constants.BORDER_STRUCTURE_MATCH_CURRENT : constants.BORDER_STRUCTURE_MATCH_OTHER, + borderBottom: isCurrentMatch ? constants.BORDER_STRUCTURE_MATCH_CURRENT : constants.BORDER_STRUCTURE_MATCH_OTHER, + backgroundColor: isCurrentMatch + ? constants.BACKGROUND_COLOR_MATCHED_ROW_CURRENT + : constants.BACKGROUND_COLOR_MATCHED_ROW_OTHER, + }; + const structureMatchFirstRowStyle = { + borderTop: isCurrentMatch ? constants.BORDER_STRUCTURE_MATCH_CURRENT : constants.BORDER_STRUCTURE_MATCH_OTHER, + borderBottom: constants.BORDER, + backgroundColor: isCurrentMatch + ? constants.BACKGROUND_COLOR_MATCHED_ROW_CURRENT + : constants.BACKGROUND_COLOR_MATCHED_ROW_OTHER, + }; + const structureMatchMiddleRowStyle = { + backgroundColor: isCurrentMatch + ? constants.BACKGROUND_COLOR_MATCHED_ROW_CURRENT + : constants.BACKGROUND_COLOR_MATCHED_ROW_OTHER, + borderBottom: constants.BORDER, + }; + const structureMatchLastRowStyle = { + borderBottom: isCurrentMatch ? constants.BORDER_STRUCTURE_MATCH_CURRENT : constants.BORDER_STRUCTURE_MATCH_OTHER, + backgroundColor: isCurrentMatch + ? constants.BACKGROUND_COLOR_MATCHED_ROW_CURRENT + : constants.BACKGROUND_COLOR_MATCHED_ROW_OTHER, + }; + + if (rowIndex === currentStructureMatch[0] && currentStructureMatch.length === 1) { + structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchFirstAndLastRowStyle }; + } else if (rowIndex === currentStructureMatch[0]) { + structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchFirstRowStyle }; + } else if (rowIndex === currentStructureMatch[currentStructureMatchLastIndex]) { + structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchLastRowStyle }; + } else if ( + currentStructureMatch[0] < rowIndex && + rowIndex < currentStructureMatch[currentStructureMatchLastIndex] + ) { + structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchMiddleRowStyle }; + } else { + structureMatches = structureMatches.filter((match) => match !== currentStructureMatch); + + for (let i = 0; i < structureMatches.length; i++) { + const match = structureMatches[i]; + + if ((match.length > 1 && rowIndex <= match[match.length - 1]) || rowIndex <= match[0]) { + if (rowIndex === match[0] && match.length === 1) { + structureMatchRowStyle = { + ...structureMatchRowStyle, + ...structureMatchFirstAndLastRowStyle, + }; + } else if (rowIndex === match[0]) { + structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchFirstRowStyle }; + } else if (rowIndex === match[match.length - 1]) { + structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchLastRowStyle }; + } else { + structureMatchRowStyle = { ...structureMatchRowStyle, ...structureMatchMiddleRowStyle }; + } + + break; + } + } + } + + return structureMatchRowStyle; +}; + +export const getContextMenuStyle = ( + contextMenuHeight: number, + contextMenuWidth: number, + xPos: number, + yPos: number, +): React.CSSProperties => { + const contextMenuStyle: React.CSSProperties = { + height: contextMenuHeight, + width: contextMenuWidth, + top: yPos, + left: xPos, + position: "fixed", + userSelect: "none", + }; + + const vw = document.documentElement.clientWidth; + const vh = document.documentElement.clientHeight; + + if (xPos + contextMenuWidth > vw) contextMenuStyle.left = vw - contextMenuWidth; + + if (yPos + contextMenuHeight > vh) contextMenuStyle.top = vh - contextMenuHeight; + + return contextMenuStyle; +}; + +export const getContextMenuItemStyle = (isSelected: boolean): React.CSSProperties => { + const contextMenuItemStyle: React.CSSProperties = { + height: "28px", + lineHeight: "28px", + paddingLeft: "5px", + paddingRight: "5px", + borderRadius: "4px", + cursor: "pointer", + textOverflow: "ellipsis", + background: isSelected ? "var(--vscode-menu-selectionBackground)" : "", + color: isSelected ? "var(--vscode-menu-selectionForeground)" : "", + }; + + return contextMenuItemStyle; +}; diff --git a/src/viewer/hooks/useTracyLogData.ts b/src/viewer/hooks/useTracyLogData.ts new file mode 100644 index 0000000..ff4ba13 --- /dev/null +++ b/src/viewer/hooks/useTracyLogData.ts @@ -0,0 +1,48 @@ +import { CustomDisplayString } from "../interfaces"; +import { types } from "../types"; + +export function logDataToString(data: types.TracyLogData): string { + if (data === undefined || data === null) { + return ''; + } else if (typeof(data) === 'string') { + return data; + } else if (data instanceof Date) { + return data.toISOString(); + } else if (isCustomDisplayString(data)) { + return data.display; + } else { + return String(data); + } +} + +export function isNumericNullOrUndefined(item: types.TracyLogData): boolean { + return isNumeric(item) || isNullUndefined(item); +} + +export function isNumeric(item: types.TracyLogData): boolean { + return typeof(item) === 'number' || item instanceof Date || isCustomDisplayString(item) && isNumericNullOrUndefined(item.value); +} + +export function isNullUndefined(item: types.TracyLogData): boolean { + return item === null || item === undefined || item === ''; +} + +export function isCustomDisplayString(item: types.TracyLogData): item is CustomDisplayString { + return item instanceof Object && 'value' in item && 'display' in item; +} + +export function getNumericValue(item: types.TracyLogData): number | undefined { + if (typeof(item) === 'number') { + return item; + } + if (item instanceof Date) { + return item.getTime(); + } + if (isCustomDisplayString(item)) { + return getNumericValue(item.value); + } +} + +export function containsOnlyNumericValues(items: types.TracyLogData[]): boolean { + return !items.some((i) => !isNumericNullOrUndefined(i)); +} \ No newline at end of file diff --git a/src/viewer/hooks/useVsCode.ts b/src/viewer/hooks/useVsCode.ts new file mode 100644 index 0000000..3c8a39f --- /dev/null +++ b/src/viewer/hooks/useVsCode.ts @@ -0,0 +1,27 @@ +import { State as AppState } from "../App"; +import { TracyStoredState, VsCode } from "../interfaces"; + +export function setVsState(vscode: VsCode, partialState: Partial) { + const state = {...vscode.getState(), ...partialState}; + vscode.setState(state); +} + +export function storeAppState(vscode: VsCode, state: AppState) { + const { + comparisonFile, + showMinimapHeader, + coloredTable, + selectedColumns, + currentDialog + } = state; + + setVsState(vscode, { + comparisonFile: comparisonFile?.filePath(), + ui: { + showMinimapHeader, + coloredTable, + selectedColumns, + currentDialog + } + }); +} \ No newline at end of file diff --git a/src/viewer/hooks/useWildcardManager.tsx b/src/viewer/hooks/useWildcardManager.tsx index 3e66f5d..3eba65d 100644 --- a/src/viewer/hooks/useWildcardManager.tsx +++ b/src/viewer/hooks/useWildcardManager.tsx @@ -1,457 +1,457 @@ -import React, { ReactNode } from "react"; -import { wildcardStyle } from "./useStyleManager"; -import { Wildcard, CellContents } from "../types"; - -export const createWildcard = ( - structureEntryIndex: number, - cellIndex: number, - contentsIndex: number, -) => { - const newWildcard: Wildcard = { - wildcardSubstitutions: [ - { - entryIndex: structureEntryIndex, - cellIndex: cellIndex, - contentsIndex: contentsIndex, - }, - ], - }; - - return newWildcard; -}; - -export const updateCellContentsIndices = ( - cellContents: CellContents[], - startingIndex: number, -): CellContents[] => { - let newIndex = startingIndex; - const modifiedContents = cellContents; - for (let i = 0; i < modifiedContents.length; i++) { - modifiedContents[i].contentsIndex = newIndex; - newIndex++; - } - - return modifiedContents; -}; - -export const getIndicesForWildcardFromDivId = (divId: string): string[] => { - const indicesForWildcard: string[] = []; - const chars = divId.split("-"); - - chars.map((val) => indicesForWildcard.push(val)); - - return indicesForWildcard; -}; - -export const insertWildcardIntoCellsContents = ( - cellContents: CellContents[], - wildcards: Wildcard[], - structureEntryIndex: number, - cellIndex: number, - wildcardIndex: number, - contentsIndex: number, - startIndex: number, - endIndex: number, -): { - wildcards: Wildcard[]; - insertedWildcardContentsIndex: number; - cellContents: CellContents[]; -} => { - let finalCellContents: CellContents[] = []; - - const contentsBeforeCurrent = cellContents.slice(0, contentsIndex); - const contentsAfterCurrent = cellContents.slice(contentsIndex + 1); - - const contentsToBeModified = cellContents[contentsIndex]; - const contentsToBeModifiedText = contentsToBeModified.textValue!; - - const isFirst = contentsBeforeCurrent.length === 0; - const isLast = contentsAfterCurrent.length === 0; - - const cellContentsSplitResults = getCellContentsFromTextValue( - contentsToBeModifiedText, - wildcardIndex, - contentsIndex, - startIndex, - endIndex, - ); - - if (!isFirst) finalCellContents.push(...contentsBeforeCurrent); - - finalCellContents.push(...cellContentsSplitResults.cellContents); - - if (!isLast) finalCellContents.push(...contentsAfterCurrent); - - wildcards.forEach((wildcard) => { - const nrOfSteps = cellContentsSplitResults.cellContents.length - 1; - - for (let s = 0; s < wildcard.wildcardSubstitutions.length; s++) { - if ( - wildcard.wildcardSubstitutions[s].entryIndex === structureEntryIndex && - wildcard.wildcardSubstitutions[s].cellIndex === cellIndex - ) { - if (wildcard.wildcardSubstitutions[s].contentsIndex > contentsIndex) { - wildcard.wildcardSubstitutions[s].contentsIndex = - wildcard.wildcardSubstitutions[s].contentsIndex + nrOfSteps; - } - } - } - }); - - finalCellContents = updateCellContentsIndices(finalCellContents, 0); - - return { - wildcards: wildcards, - insertedWildcardContentsIndex: cellContentsSplitResults.wildcardContentsIndex, - cellContents: finalCellContents, - }; -}; - -const getCellContentsFromTextValue = ( - textValue: string, - wildcardIndex: number, - contentsIndex: number, - startIndex: number, - endIndex: number, -): { wildcardContentsIndex: number; cellContents: CellContents[] } => { - const cellContents: CellContents[] = []; - const chars = [...textValue]; - - const stringBeforeWildcard = chars.slice(0, startIndex); - const textToBeSubstitutedByWildcard = chars.slice(startIndex, endIndex); - const stringAfterWildcard = chars.slice(endIndex); - - let textContentsAfterWildcard: CellContents; - let newWildcardContentsIndex: number; - - if (stringBeforeWildcard.length > 0) { - const textContentsBeforeWildcard = createCellContents( - contentsIndex, - stringBeforeWildcard.join(""), - null, - ); - cellContents.push(textContentsBeforeWildcard); - - newWildcardContentsIndex = contentsIndex + 1; - } else { - newWildcardContentsIndex = contentsIndex; - } - - const wildcardContents = createCellContents( - newWildcardContentsIndex, - textToBeSubstitutedByWildcard.join(""), - wildcardIndex, - ); - cellContents.push(wildcardContents); - - if (stringAfterWildcard.length > 0) { - textContentsAfterWildcard = createCellContents( - newWildcardContentsIndex + 1, - stringAfterWildcard.join(""), - null, - ); - cellContents.push(textContentsAfterWildcard); - } - - return { wildcardContentsIndex: newWildcardContentsIndex, cellContents: cellContents }; -}; - -export const removeWildcardSubstitution = ( - wildcards: Wildcard[], - wildcardIndex: number, - entryIndex: number, - cellIndex: number, - contentsIndex: number, -): { wildcards: Wildcard[]; isWildcardDeleted: boolean } => { - let modifiedWildcards = wildcards; - let isDeleted; - - const filteredSubstitutions = modifiedWildcards[wildcardIndex].wildcardSubstitutions.filter( - (value) => - entryIndex !== value.entryIndex || - cellIndex !== value.cellIndex || - contentsIndex !== value.contentsIndex, - ); - - modifiedWildcards[wildcardIndex].wildcardSubstitutions = filteredSubstitutions; - - if (filteredSubstitutions.length === 0) { - modifiedWildcards = modifiedWildcards.filter((_val, ind) => ind !== wildcardIndex); - isDeleted = true; - } - - return { wildcards: modifiedWildcards, isWildcardDeleted: isDeleted }; -}; - -export const removeWildcardSubstitutionsForStructureEntry = ( - wildcards: Wildcard[], - entryIndex: number, -): { modifiedWildcards: Wildcard[]; indicesOfWildcardsToBeRemoved: number[] } => { - const modifiedWildcards: Wildcard[] = []; - const indicesOfWildcardsToBeRemoved: number[] = []; - - wildcards.forEach((wildcard, ind) => { - const wildcardSubstitutionsInOtherEntries = wildcard.wildcardSubstitutions.filter( - (substitution) => substitution.entryIndex !== entryIndex, - ); - - if (wildcardSubstitutionsInOtherEntries.length !== 0) { - modifiedWildcards.push({ wildcardSubstitutions: wildcardSubstitutionsInOtherEntries }); - } else { - indicesOfWildcardsToBeRemoved.push(ind); - } - }); - return { modifiedWildcards, indicesOfWildcardsToBeRemoved }; -}; - -export const removeWildcardFromCellContent = ( - cellContents: CellContents[], - wildcards: Wildcard[], - entryIndex: number, - cellIndex: number, - contentsIndex: number, -): { wildcards: Wildcard[]; cellContents: CellContents[] } => { - let finalCellContents: CellContents[] = []; - const contentsToBeRemoved = cellContents[contentsIndex]; - let nrOfSteps = 1; - - if (contentsIndex === 0) { - let contentsAfterCurrent = cellContents.slice(contentsIndex + 1); - - if (contentsAfterCurrent.length !== 0) { - contentsAfterCurrent[0].textValue = - contentsToBeRemoved.textValue + contentsAfterCurrent[0].textValue; - - contentsAfterCurrent = updateCellContentsIndices(contentsAfterCurrent, 0); - - finalCellContents = contentsAfterCurrent; - } else { - finalCellContents.push({ - contentsIndex: 0, - textValue: contentsToBeRemoved.textValue, - wildcardIndex: null, - }); - } - } else if (contentsIndex === cellContents.length - 1) { - const contentsBeforeCurrent = cellContents.slice(0, contentsIndex); - contentsBeforeCurrent[contentsIndex - 1].textValue = - contentsBeforeCurrent[contentsIndex - 1].textValue + contentsToBeRemoved.textValue; - - finalCellContents = contentsBeforeCurrent; - - nrOfSteps = 0; - } else { - let contentsBeforeCurrent = cellContents.slice(0, contentsIndex); - let contentsAfterCurrent = cellContents.slice(contentsIndex + 1); - let newCellContents: CellContents[] = []; - - if ( - contentsBeforeCurrent[contentsBeforeCurrent.length - 1].wildcardIndex !== null && - contentsAfterCurrent[0].wildcardIndex !== null - ) { - // Both are wildcards - contentsToBeRemoved.wildcardIndex = null; - - newCellContents.push(...contentsBeforeCurrent); - newCellContents.push(contentsToBeRemoved); - newCellContents.push(...contentsAfterCurrent); - - nrOfSteps = 0; - } else if ( - contentsBeforeCurrent[contentsBeforeCurrent.length - 1].wildcardIndex === null && - contentsAfterCurrent[0].wildcardIndex === null - ) { - // Both are text - const newContentsTextValue = - contentsBeforeCurrent[contentsBeforeCurrent.length - 1].textValue + - contentsToBeRemoved.textValue + - contentsAfterCurrent[0].textValue; - const newContents: CellContents = { - contentsIndex: contentsBeforeCurrent.length - 1, - textValue: newContentsTextValue, - wildcardIndex: null, - }; - - contentsBeforeCurrent = cellContents.slice(0, newContents.contentsIndex); - contentsAfterCurrent = cellContents.slice(newContents.contentsIndex + 3, cellContents.length); - - if (contentsBeforeCurrent.length !== 0) - newCellContents = newCellContents.concat(contentsBeforeCurrent); - - newCellContents.push(newContents); - - if (contentsAfterCurrent.length !== 0) - newCellContents = newCellContents.concat(contentsAfterCurrent); - - nrOfSteps = 2; - } else if ( - contentsBeforeCurrent[contentsBeforeCurrent.length - 1].wildcardIndex === null && - contentsAfterCurrent[0].wildcardIndex !== null - ) { - // Before is text, after is wildcard - const newContentsTextValue = - contentsBeforeCurrent[contentsBeforeCurrent.length - 1] + contentsToBeRemoved.textValue; - const newContents: CellContents = { - contentsIndex: contentsBeforeCurrent.length - 1, - textValue: newContentsTextValue, - wildcardIndex: null, - }; - - contentsBeforeCurrent = cellContents.slice(0, newContents.contentsIndex); - - if (contentsBeforeCurrent.length !== 0) - newCellContents = newCellContents.concat(contentsBeforeCurrent); - - newCellContents.push(newContents); - newCellContents = newCellContents.concat(contentsAfterCurrent); - } else { - // Before is wildcard, after is text - const newContentsTextValue = - contentsToBeRemoved.textValue + contentsAfterCurrent[0].textValue; - const newContents: CellContents = { - contentsIndex: contentsIndex, - textValue: newContentsTextValue, - wildcardIndex: null, - }; - - contentsAfterCurrent = cellContents.slice(newContents.contentsIndex); - - newCellContents = newCellContents.concat(contentsBeforeCurrent); - newCellContents.push(newContents); - - if (contentsAfterCurrent.length !== 0) - newCellContents = newCellContents.concat(contentsAfterCurrent); - } - - newCellContents = updateCellContentsIndices(newCellContents, 0); - finalCellContents = newCellContents; - } - - wildcards.forEach((wildcard) => { - for (let s = 0; s < wildcard.wildcardSubstitutions.length; s++) { - if ( - wildcard.wildcardSubstitutions[s].entryIndex === entryIndex && - wildcard.wildcardSubstitutions[s].cellIndex === cellIndex - ) { - if (wildcard.wildcardSubstitutions[s].contentsIndex > contentsIndex) { - wildcard.wildcardSubstitutions[s].contentsIndex = - wildcard.wildcardSubstitutions[s].contentsIndex - nrOfSteps; - } - } - } - }); - - return { wildcards: wildcards, cellContents: finalCellContents }; -}; - -export const createCellContents = ( - contentsIndex: number, - textValue: string, - wildcardIndex: number | null, -): CellContents => { - const newCellContent: CellContents = { - contentsIndex: contentsIndex, - textValue: textValue, - wildcardIndex: wildcardIndex, - }; - - return newCellContent; -}; - -export const getReactElementsFromCellContents = ( - entryIndex: number, - cellIndex: number, - contentsIndex: number, - wildcardIndex: number | null, - textValue: string | null, -): ReactNode => { - if (wildcardIndex !== null) { - return ( -
- ?{wildcardIndex + 1} -
- ); - } else { - return ( -
- {textValue} -
- ); - } -}; - -export const getWildcardIndex = ( - wildcards: Wildcard[], - structureEntryIndex: number, - cellIndex: number, - contentsIndex: number, -) => { - let wildcardIndex = -1; - - for (let w = 0; w < wildcards.length; w++) { - const substitutions = wildcards[w].wildcardSubstitutions; - - for (let s = 0; s < substitutions.length; s++) { - if ( - substitutions[s].entryIndex === structureEntryIndex && - substitutions[s].cellIndex === cellIndex && - substitutions[s].contentsIndex == contentsIndex - ) { - wildcardIndex = w; - } - } - } - - return wildcardIndex; -}; - -export const isSubstitutionFirstForWildcard = ( - wildcard: Wildcard, - structureEntryIndex: number, - cellIndex: number, - contentsIndex: number, -): boolean => { - let isSubstitutionFirst = false; - let smallestStructureEntry, - smallestcellIndex, - smallestContentsIndex: number | null = null; - - wildcard.wildcardSubstitutions.forEach((substitution) => { - if (smallestStructureEntry == null || smallestStructureEntry > substitution.entryIndex) - smallestStructureEntry = substitution.entryIndex; - }); - - wildcard.wildcardSubstitutions.forEach((substitution) => { - if ( - smallestcellIndex == null || - (smallestStructureEntry === structureEntryIndex && smallestcellIndex > substitution.cellIndex) - ) - smallestcellIndex = substitution.cellIndex; - }); - - wildcard.wildcardSubstitutions.forEach((substitution) => { - if ( - smallestContentsIndex == null || - (smallestStructureEntry === structureEntryIndex && - smallestcellIndex === substitution.cellIndex && - smallestContentsIndex > substitution.contentsIndex) - ) - smallestContentsIndex = substitution.contentsIndex; - }); - - if ( - structureEntryIndex === smallestStructureEntry && - cellIndex === smallestcellIndex && - contentsIndex === smallestContentsIndex - ) - isSubstitutionFirst = true; - - return isSubstitutionFirst; -}; +import React, { ReactNode } from "react"; +import { wildcardStyle } from "./useStyleManager"; +import { Wildcard, CellContents } from "../interfaces"; + +export const createWildcard = ( + structureEntryIndex: number, + cellIndex: number, + contentsIndex: number, +) => { + const newWildcard: Wildcard = { + wildcardSubstitutions: [ + { + entryIndex: structureEntryIndex, + cellIndex: cellIndex, + contentsIndex: contentsIndex, + }, + ], + }; + + return newWildcard; +}; + +export const updateCellContentsIndices = ( + cellContents: CellContents[], + startingIndex: number, +): CellContents[] => { + let newIndex = startingIndex; + const modifiedContents = cellContents; + for (let i = 0; i < modifiedContents.length; i++) { + modifiedContents[i].contentsIndex = newIndex; + newIndex++; + } + + return modifiedContents; +}; + +export const getIndicesForWildcardFromDivId = (divId: string): string[] => { + const indicesForWildcard: string[] = []; + const chars = divId.split("-"); + + chars.map((val) => indicesForWildcard.push(val)); + + return indicesForWildcard; +}; + +export const insertWildcardIntoCellsContents = ( + cellContents: CellContents[], + wildcards: Wildcard[], + structureEntryIndex: number, + cellIndex: number, + wildcardIndex: number, + contentsIndex: number, + startIndex: number, + endIndex: number, +): { + wildcards: Wildcard[]; + insertedWildcardContentsIndex: number; + cellContents: CellContents[]; +} => { + let finalCellContents: CellContents[] = []; + + const contentsBeforeCurrent = cellContents.slice(0, contentsIndex); + const contentsAfterCurrent = cellContents.slice(contentsIndex + 1); + + const contentsToBeModified = cellContents[contentsIndex]; + const contentsToBeModifiedText = contentsToBeModified.textValue!; + + const isFirst = contentsBeforeCurrent.length === 0; + const isLast = contentsAfterCurrent.length === 0; + + const cellContentsSplitResults = getCellContentsFromTextValue( + contentsToBeModifiedText, + wildcardIndex, + contentsIndex, + startIndex, + endIndex, + ); + + if (!isFirst) finalCellContents.push(...contentsBeforeCurrent); + + finalCellContents.push(...cellContentsSplitResults.cellContents); + + if (!isLast) finalCellContents.push(...contentsAfterCurrent); + + wildcards.forEach((wildcard) => { + const nrOfSteps = cellContentsSplitResults.cellContents.length - 1; + + for (let s = 0; s < wildcard.wildcardSubstitutions.length; s++) { + if ( + wildcard.wildcardSubstitutions[s].entryIndex === structureEntryIndex && + wildcard.wildcardSubstitutions[s].cellIndex === cellIndex + ) { + if (wildcard.wildcardSubstitutions[s].contentsIndex > contentsIndex) { + wildcard.wildcardSubstitutions[s].contentsIndex = + wildcard.wildcardSubstitutions[s].contentsIndex + nrOfSteps; + } + } + } + }); + + finalCellContents = updateCellContentsIndices(finalCellContents, 0); + + return { + wildcards: wildcards, + insertedWildcardContentsIndex: cellContentsSplitResults.wildcardContentsIndex, + cellContents: finalCellContents, + }; +}; + +const getCellContentsFromTextValue = ( + textValue: string, + wildcardIndex: number, + contentsIndex: number, + startIndex: number, + endIndex: number, +): { wildcardContentsIndex: number; cellContents: CellContents[] } => { + const cellContents: CellContents[] = []; + const chars = [...textValue]; + + const stringBeforeWildcard = chars.slice(0, startIndex); + const textToBeSubstitutedByWildcard = chars.slice(startIndex, endIndex); + const stringAfterWildcard = chars.slice(endIndex); + + let textContentsAfterWildcard: CellContents; + let newWildcardContentsIndex: number; + + if (stringBeforeWildcard.length > 0) { + const textContentsBeforeWildcard = createCellContents( + contentsIndex, + stringBeforeWildcard.join(""), + null, + ); + cellContents.push(textContentsBeforeWildcard); + + newWildcardContentsIndex = contentsIndex + 1; + } else { + newWildcardContentsIndex = contentsIndex; + } + + const wildcardContents = createCellContents( + newWildcardContentsIndex, + textToBeSubstitutedByWildcard.join(""), + wildcardIndex, + ); + cellContents.push(wildcardContents); + + if (stringAfterWildcard.length > 0) { + textContentsAfterWildcard = createCellContents( + newWildcardContentsIndex + 1, + stringAfterWildcard.join(""), + null, + ); + cellContents.push(textContentsAfterWildcard); + } + + return { wildcardContentsIndex: newWildcardContentsIndex, cellContents: cellContents }; +}; + +export const removeWildcardSubstitution = ( + wildcards: Wildcard[], + wildcardIndex: number, + entryIndex: number, + cellIndex: number, + contentsIndex: number, +): { wildcards: Wildcard[]; isWildcardDeleted: boolean } => { + let modifiedWildcards = wildcards; + let isDeleted; + + const filteredSubstitutions = modifiedWildcards[wildcardIndex].wildcardSubstitutions.filter( + (value) => + entryIndex !== value.entryIndex || + cellIndex !== value.cellIndex || + contentsIndex !== value.contentsIndex, + ); + + modifiedWildcards[wildcardIndex].wildcardSubstitutions = filteredSubstitutions; + + if (filteredSubstitutions.length === 0) { + modifiedWildcards = modifiedWildcards.filter((_val, ind) => ind !== wildcardIndex); + isDeleted = true; + } + + return { wildcards: modifiedWildcards, isWildcardDeleted: isDeleted }; +}; + +export const removeWildcardSubstitutionsForStructureEntry = ( + wildcards: Wildcard[], + entryIndex: number, +): { modifiedWildcards: Wildcard[]; indicesOfWildcardsToBeRemoved: number[] } => { + const modifiedWildcards: Wildcard[] = []; + const indicesOfWildcardsToBeRemoved: number[] = []; + + wildcards.forEach((wildcard, ind) => { + const wildcardSubstitutionsInOtherEntries = wildcard.wildcardSubstitutions.filter( + (substitution) => substitution.entryIndex !== entryIndex, + ); + + if (wildcardSubstitutionsInOtherEntries.length !== 0) { + modifiedWildcards.push({ wildcardSubstitutions: wildcardSubstitutionsInOtherEntries }); + } else { + indicesOfWildcardsToBeRemoved.push(ind); + } + }); + return { modifiedWildcards, indicesOfWildcardsToBeRemoved }; +}; + +export const removeWildcardFromCellContent = ( + cellContents: CellContents[], + wildcards: Wildcard[], + entryIndex: number, + cellIndex: number, + contentsIndex: number, +): { wildcards: Wildcard[]; cellContents: CellContents[] } => { + let finalCellContents: CellContents[] = []; + const contentsToBeRemoved = cellContents[contentsIndex]; + let nrOfSteps = 1; + + if (contentsIndex === 0) { + let contentsAfterCurrent = cellContents.slice(contentsIndex + 1); + + if (contentsAfterCurrent.length !== 0) { + contentsAfterCurrent[0].textValue = + contentsToBeRemoved.textValue + contentsAfterCurrent[0].textValue; + + contentsAfterCurrent = updateCellContentsIndices(contentsAfterCurrent, 0); + + finalCellContents = contentsAfterCurrent; + } else { + finalCellContents.push({ + contentsIndex: 0, + textValue: contentsToBeRemoved.textValue, + wildcardIndex: null, + }); + } + } else if (contentsIndex === cellContents.length - 1) { + const contentsBeforeCurrent = cellContents.slice(0, contentsIndex); + contentsBeforeCurrent[contentsIndex - 1].textValue = + contentsBeforeCurrent[contentsIndex - 1].textValue + contentsToBeRemoved.textValue; + + finalCellContents = contentsBeforeCurrent; + + nrOfSteps = 0; + } else { + let contentsBeforeCurrent = cellContents.slice(0, contentsIndex); + let contentsAfterCurrent = cellContents.slice(contentsIndex + 1); + let newCellContents: CellContents[] = []; + + if ( + contentsBeforeCurrent[contentsBeforeCurrent.length - 1].wildcardIndex !== null && + contentsAfterCurrent[0].wildcardIndex !== null + ) { + // Both are wildcards + contentsToBeRemoved.wildcardIndex = null; + + newCellContents.push(...contentsBeforeCurrent); + newCellContents.push(contentsToBeRemoved); + newCellContents.push(...contentsAfterCurrent); + + nrOfSteps = 0; + } else if ( + contentsBeforeCurrent[contentsBeforeCurrent.length - 1].wildcardIndex === null && + contentsAfterCurrent[0].wildcardIndex === null + ) { + // Both are text + const newContentsTextValue = + contentsBeforeCurrent[contentsBeforeCurrent.length - 1].textValue + + contentsToBeRemoved.textValue + + contentsAfterCurrent[0].textValue; + const newContents: CellContents = { + contentsIndex: contentsBeforeCurrent.length - 1, + textValue: newContentsTextValue, + wildcardIndex: null, + }; + + contentsBeforeCurrent = cellContents.slice(0, newContents.contentsIndex); + contentsAfterCurrent = cellContents.slice(newContents.contentsIndex + 3, cellContents.length); + + if (contentsBeforeCurrent.length !== 0) + newCellContents = newCellContents.concat(contentsBeforeCurrent); + + newCellContents.push(newContents); + + if (contentsAfterCurrent.length !== 0) + newCellContents = newCellContents.concat(contentsAfterCurrent); + + nrOfSteps = 2; + } else if ( + contentsBeforeCurrent[contentsBeforeCurrent.length - 1].wildcardIndex === null && + contentsAfterCurrent[0].wildcardIndex !== null + ) { + // Before is text, after is wildcard + const newContentsTextValue = + contentsBeforeCurrent[contentsBeforeCurrent.length - 1] + contentsToBeRemoved.textValue; + const newContents: CellContents = { + contentsIndex: contentsBeforeCurrent.length - 1, + textValue: newContentsTextValue, + wildcardIndex: null, + }; + + contentsBeforeCurrent = cellContents.slice(0, newContents.contentsIndex); + + if (contentsBeforeCurrent.length !== 0) + newCellContents = newCellContents.concat(contentsBeforeCurrent); + + newCellContents.push(newContents); + newCellContents = newCellContents.concat(contentsAfterCurrent); + } else { + // Before is wildcard, after is text + const newContentsTextValue = + contentsToBeRemoved.textValue + contentsAfterCurrent[0].textValue; + const newContents: CellContents = { + contentsIndex: contentsIndex, + textValue: newContentsTextValue, + wildcardIndex: null, + }; + + contentsAfterCurrent = cellContents.slice(newContents.contentsIndex); + + newCellContents = newCellContents.concat(contentsBeforeCurrent); + newCellContents.push(newContents); + + if (contentsAfterCurrent.length !== 0) + newCellContents = newCellContents.concat(contentsAfterCurrent); + } + + newCellContents = updateCellContentsIndices(newCellContents, 0); + finalCellContents = newCellContents; + } + + wildcards.forEach((wildcard) => { + for (let s = 0; s < wildcard.wildcardSubstitutions.length; s++) { + if ( + wildcard.wildcardSubstitutions[s].entryIndex === entryIndex && + wildcard.wildcardSubstitutions[s].cellIndex === cellIndex + ) { + if (wildcard.wildcardSubstitutions[s].contentsIndex > contentsIndex) { + wildcard.wildcardSubstitutions[s].contentsIndex = + wildcard.wildcardSubstitutions[s].contentsIndex - nrOfSteps; + } + } + } + }); + + return { wildcards: wildcards, cellContents: finalCellContents }; +}; + +export const createCellContents = ( + contentsIndex: number, + textValue: string, + wildcardIndex: number | null, +): CellContents => { + const newCellContent: CellContents = { + contentsIndex: contentsIndex, + textValue: textValue, + wildcardIndex: wildcardIndex, + }; + + return newCellContent; +}; + +export const getReactElementsFromCellContents = ( + entryIndex: number, + cellIndex: number, + contentsIndex: number, + wildcardIndex: number | null, + textValue: string | null, +): ReactNode => { + if (wildcardIndex !== null) { + return ( +
+ ?{wildcardIndex + 1} +
+ ); + } else { + return ( +
+ {textValue} +
+ ); + } +}; + +export const getWildcardIndex = ( + wildcards: Wildcard[], + structureEntryIndex: number, + cellIndex: number, + contentsIndex: number, +) => { + let wildcardIndex = -1; + + for (let w = 0; w < wildcards.length; w++) { + const substitutions = wildcards[w].wildcardSubstitutions; + + for (let s = 0; s < substitutions.length; s++) { + if ( + substitutions[s].entryIndex === structureEntryIndex && + substitutions[s].cellIndex === cellIndex && + substitutions[s].contentsIndex == contentsIndex + ) { + wildcardIndex = w; + } + } + } + + return wildcardIndex; +}; + +export const isSubstitutionFirstForWildcard = ( + wildcard: Wildcard, + structureEntryIndex: number, + cellIndex: number, + contentsIndex: number, +): boolean => { + let isSubstitutionFirst = false; + let smallestStructureEntry, + smallestcellIndex, + smallestContentsIndex: number | null = null; + + wildcard.wildcardSubstitutions.forEach((substitution) => { + if (smallestStructureEntry == null || smallestStructureEntry > substitution.entryIndex) + smallestStructureEntry = substitution.entryIndex; + }); + + wildcard.wildcardSubstitutions.forEach((substitution) => { + if ( + smallestcellIndex == null || + (smallestStructureEntry === structureEntryIndex && smallestcellIndex > substitution.cellIndex) + ) + smallestcellIndex = substitution.cellIndex; + }); + + wildcard.wildcardSubstitutions.forEach((substitution) => { + if ( + smallestContentsIndex == null || + (smallestStructureEntry === structureEntryIndex && + smallestcellIndex === substitution.cellIndex && + smallestContentsIndex > substitution.contentsIndex) + ) + smallestContentsIndex = substitution.contentsIndex; + }); + + if ( + structureEntryIndex === smallestStructureEntry && + cellIndex === smallestcellIndex && + contentsIndex === smallestContentsIndex + ) + isSubstitutionFirst = true; + + return isSubstitutionFirst; +}; diff --git a/src/viewer/interfaces.ts b/src/viewer/interfaces.ts new file mode 100644 index 0000000..ebf70ac --- /dev/null +++ b/src/viewer/interfaces.ts @@ -0,0 +1,140 @@ +import { State as SearchState } from "./components/SearchBar"; +import { enums } from "./enums"; +import LogFile from "./lib/LogFile"; +import { types } from "./types"; + +export interface LogViewState { + height: number; + start: number; + end: number; + visibleItems: number; + scrollTop: number; + scrollLeft: number; +} + +export interface StructureDefinition { + headerColumns: string[], + headerColumnsTypes: enums.StructureHeaderColumnType[], + entries: StructureEntry[], + wildcards: Wildcard[] +} + +export interface StructureEntry { + row: CellContents[][]; + cellSelection: boolean[]; + wildcardsIndices: number[][]; + structureLink?: enums.StructureLinkDistance; +} + +export interface ContextMenuItem { + text: string; + callback: (anchorDiv: string) => void; +} + +export interface Wildcard { + wildcardSubstitutions: WildcardSubstitution[]; +} + +export interface WildcardSubstitution { + entryIndex: number; + cellIndex: number; + contentsIndex: number; +} + +export interface CellContents { + contentsIndex: number; + textValue: string; + wildcardIndex: number | null; +} + +export interface RowProperty { + index: number; + isRendered: boolean; + isSelected: boolean; + isQueried: boolean; + isHighlighted: boolean; +} + +export interface ColumnProperty { + isRendered: boolean; + name: string; + index: number; + width: number; + colors: string[]; +} + +export interface Segment { + start: number; + end: number; + level: number; +} + +export interface LogEntryCharMaps { + firstCharIndexMap: Map; + lastCharIndexMap: Map; +} + +export interface ColumnSelection { + [column: string]: {logView?: boolean, miniMap?: boolean} | undefined; +} + +export interface Message { + type: string; +} + +export interface LogFileData { + filePath: string; + headers: string[]; + rows: types.TracyLogData[][]; + dateTimeIndex?: number; +} + +export interface ReadFileMessage extends Message { + logFile: LogFileData; + rules: []; +} + +export interface LoadFileForComparisonMessage extends Message { + logFile: LogFileData; +} + +export interface ReadExportPathMessage extends Message { + text: string; +} + +export interface CustomColumn { + header: string; + alignment?: 'left' | 'right'; + showInLogView?: boolean; + showInMinimap?: boolean; + toggleable?: boolean; + width?: number; + exportable?: boolean; + getValues(logFile: LogFile): T[]; +} + +export interface CustomDisplayString { + value: types.TracyLogData; + display: string; +} + +export interface UiStoredState { + showMinimapHeader: boolean; + coloredTable: boolean; + selectedColumns: ColumnSelection; + currentDialog?: enums.DialogType; +} + +export interface TracyStoredState { + comparisonFile?: string; + ui?: UiStoredState; + search?: SearchState; + primaryView?: LogViewState; + comparisonView?: LogViewState; +} + +export interface VsCode { + setState: (state: TracyStoredState) => void; + getState: () => TracyStoredState | undefined; + postMessage: (message: {}) => void; +} \ No newline at end of file diff --git a/src/viewer/lib/ColorMappingHandler.ts b/src/viewer/lib/ColorMappingHandler.ts new file mode 100644 index 0000000..4b51137 --- /dev/null +++ b/src/viewer/lib/ColorMappingHandler.ts @@ -0,0 +1,63 @@ +import { scaleSequential } from "d3-scale"; +import { interpolateTurbo } from "d3-scale-chromatic"; +import { extent } from "d3-array"; +import LogFile from "./LogFile"; +import { containsOnlyNumericValues, getNumericValue } from "../hooks/useTracyLogData"; +import { types } from "../types"; + +export default class ColorMappingHandler { + + private primaryColors: {[column: string]: string[]} = {}; + private comparisonColors: {[column: string]: string[]} = {}; + + constructor() { + this.getPrimaryColors = this.getPrimaryColors.bind(this); + this.getComparisonColors = this.getComparisonColors.bind(this); + } + + getPrimaryColors(column: string): string[] { + return this.primaryColors[column]; + } + + getComparisonColors(column: string): string[] { + return this.comparisonColors[column]; + } + + computeAllColors(logFile: LogFile, comparisonLogFile?: LogFile) { + const headers = [ + ...logFile.getAllHeaders(), + ...comparisonLogFile?.getAllHeaders() ?? [] + ]; + + for (let i = 0; i < headers.length; i++) { + this.computeColors(headers[i], logFile, comparisonLogFile); + } + } + + computeColors(header: string, logFile: LogFile, comparisonLogFile?: LogFile) { + const logFileValues = logFile.values(header); + const comparisonFileValues = comparisonLogFile?.values(header) ?? []; + const allValues: types.TracyLogData[] = [ + ...logFileValues, + ...comparisonFileValues + ]; + + if (containsOnlyNumericValues(allValues)) { + const uniqueSorted = [...new Set(allValues)] + .map(getNumericValue) + .flatMap((n) => n === undefined ? [] : [n]) // Using flat map to prevent having to cast (number | undefined)[] to number[] after filtering out undefined. + .sort(function (a, b) { return a - b; }); + + const colorizer = scaleSequential().domain(extent(uniqueSorted) as [number, number]).interpolator(interpolateTurbo); + this.primaryColors[header] = logFileValues.map((v) => colorizer(getNumericValue(v) ?? 0)); + this.comparisonColors[header] = comparisonFileValues.map((v) => colorizer(getNumericValue(v) ?? 0)); + return; + } + + const uniqueValues = [...new Set(allValues)].sort(); + const colorizer = (v: types.TracyLogData) => interpolateTurbo(uniqueValues.indexOf(v) / uniqueValues.length); + this.primaryColors[header] = logFileValues.map((v) => colorizer(v)); + this.comparisonColors[header] = comparisonFileValues.map((v) => colorizer(v)); + } + +} \ No newline at end of file diff --git a/src/viewer/lib/LogFile.ts b/src/viewer/lib/LogFile.ts new file mode 100644 index 0000000..d6dbebb --- /dev/null +++ b/src/viewer/lib/LogFile.ts @@ -0,0 +1,102 @@ +import Rule from "../rules/Rule"; +import { CustomColumn, LogFileData } from "../interfaces"; +import { types } from "../types"; + +export default class LogFile { + private readonly fileData: LogFileData; + readonly customColumns: { [key: string]: CustomColumn }; + + constructor(fileData: LogFileData, customColumns: { [key: string]: CustomColumn } = {}) { + this.fileData = fileData; + this.customColumns = customColumns; + } + + updateLogFile(rules: Rule[]): LogFile { + this.registerCustomColumns(rules.map((r) => r.toCustomColumn())); + return new LogFile(this.fileData, this.customColumns); + } + + registerCustomColumns(columns: CustomColumn[]) { + for (let i = 0; i < columns.length; i++) { + this.registerCustomColumn(columns[i]); + } + } + + registerCustomColumn(column: CustomColumn) { + this.customColumns[column.header] = column; + } + + unRegisterCustomColumn(header: string) { + delete this.customColumns[header]; + } + + amountOfRows = () => this.fileData.rows.length; + isEmpty = () => this.amountOfRows() === 0; + + getAllHeaders() { + const customHeadersLeft = Object.keys(this.customColumns) + .map((key) => this.customColumns[key]) + .filter((c) => (c.alignment ?? 'right') === 'left') + .map((c) => c.header); + const customHeadersRight = Object.keys(this.customColumns) + .map((key) => this.customColumns[key]) + .filter((c) => (c.alignment ?? 'right') === 'right') + .map((c) => c.header); + return [ + ...customHeadersLeft, + ...this.fileData.headers, + ...customHeadersRight + ]; + } + + getCustomColumns(): CustomColumn[] { + return Object.keys(this.customColumns) + .map((header) => this.customColumns[header]); + } + + amountOfColumns = () => this.getAllHeaders().length; + + dateTimeColumn = () => this.fileData.dateTimeIndex !== undefined ? this.fileData.headers[this.fileData.dateTimeIndex] : undefined; + + filePath = () => this.fileData.filePath; + + values(column: string): types.TracyLogData[] { + const customColumn = this.customColumns[column]; + if (customColumn) { + return customColumn.getValues(this); + } + + const columnIndex = this.fileData.headers + .indexOf(column); + if (columnIndex === -1) { + return []; + } + + return this.fileData.rows.map((r) => r[columnIndex]); + } + + getRow(i: number): types.TracyLogData[] { + if (i < 0 || i >= this.amountOfRows()) { + return []; + } + + const customColumnValuesLeft = Object.keys(this.customColumns) + .map((key) => this.customColumns[key]) + .filter((c) => (c.alignment ?? 'right') === 'left') + .map((c) => c.getValues(this)[i]); + const customColumnValuesRight = Object.keys(this.customColumns) + .map((key) => this.customColumns[key]) + .filter((c) => (c.alignment ?? 'right') === 'right') + .map((c) => c.getValues(this)[i]); + + return [ + ...customColumnValuesLeft, + ...this.fileData.rows[i], + ...customColumnValuesRight + ]; + } + + value(column: string, row: number): types.TracyLogData { + return this.values(column)[row]; + } +} diff --git a/src/viewer/lib/RowSelectionHandler.ts b/src/viewer/lib/RowSelectionHandler.ts new file mode 100644 index 0000000..57448d1 --- /dev/null +++ b/src/viewer/lib/RowSelectionHandler.ts @@ -0,0 +1,53 @@ +export default class RowSelectionHandler { + + private lastSelectedRow?: number; + private readonly selectedRows: boolean[]; + private readonly onSelectionChange: (selection: number[]) => void; + + constructor(onSelectionChange: (selection: number[]) => void) { + this.selectedRows = []; + this.onSelectionChange = onSelectionChange; + } + + handleLogRowClick(rowIndex: number, event: React.MouseEvent) { + if (!event.ctrlKey) { + return; + } + + const setSelected: boolean = !this.selectedRows[rowIndex]; + + // (de)select section + if (event.shiftKey && this.lastSelectedRow) { + const start = Math.min(this.lastSelectedRow, rowIndex); + const end = Math.max(this.lastSelectedRow, rowIndex); + + for (let i = start; i <= end; i++) { + this.selectedRows[i] = setSelected; + } + } + // (de)select row + else { + this.selectedRows[rowIndex] = setSelected; + } + + this.lastSelectedRow = rowIndex; + + const selectedRowIndices: number[] = []; + for (let i = 0; i < this.selectedRows.length; i++) { + if (this.selectedRows[i]) { + selectedRowIndices.push(i); + } + } + + this.onSelectionChange(selectedRowIndices); + } + + clearSelection() { + this.lastSelectedRow = undefined; + for (let i = 0; i < this.selectedRows.length; i++) { + delete this.selectedRows[i]; + } + + this.onSelectionChange([]); + } +} \ No newline at end of file diff --git a/src/viewer/lib/SideBySideAlignmentHandler.ts b/src/viewer/lib/SideBySideAlignmentHandler.ts new file mode 100644 index 0000000..948828f --- /dev/null +++ b/src/viewer/lib/SideBySideAlignmentHandler.ts @@ -0,0 +1,61 @@ +import { RefObject } from 'react'; +import LogViewAndMinimap from '../components/LogViewAndMinimap'; +import { LogViewState } from '../interfaces'; +import { enums } from '../enums'; + +export default class SideBySideAlignmentHandler { + + private readonly primaryView: RefObject; + private readonly comparisonView: RefObject; + + private syncronizeScroll: boolean; + private comparisonOffset: number; + + constructor(primaryView: RefObject, comparisonView: RefObject) { + this.primaryView = primaryView; + this.comparisonView = comparisonView; + this.syncronizeScroll = true; + this.comparisonOffset = 0; + + this.handlePrimaryViewStateChange = this.handlePrimaryViewStateChange.bind(this); + this.handleComparisonViewStateChange = this.handleComparisonViewStateChange.bind(this); + this.handlePrimaryMinimapScaleChange = this.handlePrimaryMinimapScaleChange.bind(this); + this.handleComparisonMinimapScaleChange = this.handleComparisonMinimapScaleChange.bind(this); + } + + public toggleSynchonizedScrolling() { + this.syncronizeScroll = !this.syncronizeScroll; + if (this.syncronizeScroll) { + const primaryStart = this.primaryView.current?.state.logViewState?.start ?? 0; + const comparisonStart = this.comparisonView.current?.state.logViewState?.start ?? 0; + this.comparisonOffset = Math.round(comparisonStart - primaryStart); + } + } + + public handlePrimaryViewStateChange(trigger: enums.EventTrigger, state: LogViewState) { + if (this.syncronizeScroll && trigger === enums.EventTrigger.UserScroll) { + const newStart = state.start + this.comparisonOffset; + this.comparisonView.current?.setLogViewStart(newStart); + } + } + + public handleComparisonViewStateChange(trigger: enums.EventTrigger, state: LogViewState) { + if (this.syncronizeScroll && trigger === enums.EventTrigger.UserScroll) { + const newStart = state.start - this.comparisonOffset; + this.primaryView.current?.setLogViewStart(newStart); + } + } + + public handlePrimaryMinimapScaleChange(trigger: enums.EventTrigger, visibleItems: number) { + if (this.syncronizeScroll && trigger === enums.EventTrigger.UserScroll) { + this.comparisonView.current?.setMinimapVisibleItems(enums.EventTrigger.Syncronize, visibleItems); + } + } + + public handleComparisonMinimapScaleChange(trigger: enums.EventTrigger, visibleItems: number) { + if (this.syncronizeScroll && trigger === enums.EventTrigger.UserScroll) { + this.primaryView.current?.setMinimapVisibleItems(enums.EventTrigger.Syncronize, visibleItems); + } + } + +} \ No newline at end of file diff --git a/src/viewer/lib/columns/CachingColumn.ts b/src/viewer/lib/columns/CachingColumn.ts new file mode 100644 index 0000000..ea9c2eb --- /dev/null +++ b/src/viewer/lib/columns/CachingColumn.ts @@ -0,0 +1,24 @@ +import { CustomColumn } from "../../interfaces"; +import { types } from "../../types"; +import LogFile from "../LogFile"; + +export default class CachingColumn implements CustomColumn { + + header: string; + getValuesFunction: (logFile: LogFile) => T[]; + values?: T[]; + + constructor(header: string, getValuesFunction: (logFile: LogFile) => T[]) { + this.header = header; + this.getValuesFunction = getValuesFunction; + } + + getValues(logFile: LogFile): T[] { + if (!this.values) { + this.values = this.getValuesFunction(logFile); + } + + return this.values; + } + +} \ No newline at end of file diff --git a/src/viewer/lib/columns/LineNumberingColumn.ts b/src/viewer/lib/columns/LineNumberingColumn.ts new file mode 100644 index 0000000..c374283 --- /dev/null +++ b/src/viewer/lib/columns/LineNumberingColumn.ts @@ -0,0 +1,15 @@ +import CachingColumn from "./CachingColumn"; + +export default class LineNumberingColumn extends CachingColumn { + + alignment: "left" | "right" = 'left'; + toggleable: boolean = false; + showInMinimap: boolean = false; + width = 30; + exportable = false; + + constructor() { + super('#', (logFile) => Array.from({length: logFile.amountOfRows()}, (_, i) => i + 1)); + } + +} \ No newline at end of file diff --git a/src/viewer/lib/columns/RelativeTimeColumn.ts b/src/viewer/lib/columns/RelativeTimeColumn.ts new file mode 100644 index 0000000..d62189c --- /dev/null +++ b/src/viewer/lib/columns/RelativeTimeColumn.ts @@ -0,0 +1,95 @@ +import LogViewAndMinimap from "../../components/LogViewAndMinimap"; +import { isSelected } from "../../hooks/useRowProperty"; +import { isNullUndefined } from "../../hooks/useTracyLogData"; +import { CustomColumn, CustomDisplayString } from "../../interfaces"; +import LogFile from "../LogFile"; + +export const HEADER = 'Relative Time'; + +export default class RelativeTimeColumn implements CustomColumn { + + header = HEADER; + alignment: "left" | "right" = 'left'; + exportable = false; + showInLogView?: boolean; + showInMinimap?: boolean; + + startingRow: number; + values?: CustomDisplayString[]; + + constructor(showInLogView?: boolean, showInMinimap?: boolean) { + this.showInLogView = showInLogView; + this.showInMinimap = showInMinimap; + this.startingRow = 0; + } + + getValues(logFile: LogFile): CustomDisplayString[] { + if (!this.values) { + this.values = getRelativeTimestamps(logFile, this.startingRow); + } + + return this.values; + } + +} + +export function handleSetStartingPoint(logViewAndMinimap: LogViewAndMinimap): boolean { + const column = logViewAndMinimap.props.logFile.customColumns[HEADER]; + if (!(column instanceof RelativeTimeColumn)) { + return false; + } + + const selectedCount = logViewAndMinimap.state.rowProperties.filter(isSelected).length; + const selected = logViewAndMinimap.state.rowProperties.findIndex(isSelected); + if (selectedCount !== 1 || selected === -1) { + return false; + } + + column.startingRow = selected; + column.values = undefined; // Values set to undefines to regenerate them the next time they are requested. + logViewAndMinimap.clearSelection(); + return true; +} + +function getRelativeTimestamps(logFile: LogFile, startingRow: number) { + if (logFile.isEmpty()) { + return []; + } + + const dateTimeColumn = logFile.dateTimeColumn(); + if (dateTimeColumn === undefined) { + return []; + } + + const dateTimeValues = logFile.values(dateTimeColumn); + if (isNullUndefined(dateTimeValues[startingRow])) { + return []; + } + + const startDateTime = (dateTimeValues[startingRow] as Date).getTime(); + + return dateTimeValues + .map((d) => d instanceof Date ? d.getTime() - startDateTime : undefined) + .map((t) => ({ + value: t, + display: t !== undefined ? getTimeDuration(t) : '' + })); +} + +function getTimeDuration(miliseconds: number): string { + const negative = miliseconds < 0; + miliseconds = Math.abs(miliseconds); + let seconds = Math.floor(miliseconds/1000); + let minutes = Math.floor(seconds/60); + let hours = Math.floor(minutes/60); + + miliseconds -= seconds * 1000; + seconds -= minutes * 60; + minutes -= hours * 60; + + return `${negative ? '-' : ''}${hours}:${numToString(minutes, 2)}:${numToString(seconds, 2)}.${numToString(miliseconds, 3)}` +} + +function numToString(num: number, digits: number) { + return String(num).padStart(digits, '0'); +} \ No newline at end of file diff --git a/src/viewer/rules/FlagRule.tsx b/src/viewer/rules/FlagRule.tsx index b18d480..d8008ea 100644 --- a/src/viewer/rules/FlagRule.tsx +++ b/src/viewer/rules/FlagRule.tsx @@ -2,7 +2,7 @@ import React from "react"; import Rule from "./Rule"; import StateBasedRule from "./StateBasedRule"; -import LogFile from "../LogFile"; +import LogFile from "../lib/LogFile"; import Table from "./Tables/Table"; import FlagTable from "./Tables/FlagTable"; import { @@ -14,6 +14,7 @@ import { VSCodePanelView, } from "@vscode/webview-ui-toolkit/react"; import { useRegularExpressionSearch } from "../hooks/useLogSearchManager"; +import { logDataToString } from "../hooks/useTracyLogData"; interface Flag { name: string; @@ -144,7 +145,7 @@ export default class FlagRule extends Rule { logFile: LogFile, rules: Rule[] ) { - const allColumns = ["", ...logFile.contentHeaders, ...user_columns]; + const allColumns = ["", ...logFile.getAllHeaders().filter((h) => h !== this.column)]; const editFlagName = (index: number, value: string) => { const flags = [...this.flags]; @@ -474,7 +475,7 @@ export default class FlagRule extends Rule { for (const conditionSet of flag.conditions) { let allConditionsSatisfied = true; for (const condition of conditionSet) { - const logValue = logFile.value(condition.Column, r) ?? ""; + const logValue = logDataToString(logFile.value(condition.Column, r)); if (condition.Operation === "contains") { if (!logValue.includes(condition.Text)) { allConditionsSatisfied = false; @@ -524,7 +525,7 @@ export default class FlagRule extends Rule { } } else if (this.flagType === "Capture Match") { for (const flag of this.flags) { - const logValue = logFile.value(flag.conditions[0][0].Column, r) ?? ""; + const logValue = logDataToString(logFile.value(flag.conditions[0][0].Column, r)); const flagFound = logValue.match(flag.conditions[0][0].Text); if (flagFound !== null) values[r] = flagFound[1]; } diff --git a/src/viewer/rules/Rule.tsx b/src/viewer/rules/Rule.tsx index 1b162f4..2e83564 100644 --- a/src/viewer/rules/Rule.tsx +++ b/src/viewer/rules/Rule.tsx @@ -1,4 +1,6 @@ -import LogFile from "../LogFile"; +import { CustomColumn } from "../interfaces"; +import CachingColumn from "../lib/columns/CachingColumn"; +import LogFile from "../lib/LogFile"; export default abstract class Rule { readonly column: string; @@ -9,6 +11,7 @@ export default abstract class Rule { constructor(column: string, description: string) { this.column = column; this.description = description; + this.computeValues = this.computeValues.bind(this); } abstract reset(): Rule; @@ -31,4 +34,11 @@ export default abstract class Rule { }; return lookup[json.type]?.fromJSON(json); } + + toCustomColumn(): CustomColumn { + return new CachingColumn( + this.column, + this.computeValues + ); + } } diff --git a/src/viewer/rules/StateBasedRule.tsx b/src/viewer/rules/StateBasedRule.tsx index 0ebca2a..0b75c3e 100644 --- a/src/viewer/rules/StateBasedRule.tsx +++ b/src/viewer/rules/StateBasedRule.tsx @@ -2,7 +2,7 @@ import React from "react"; import Rule from "./Rule"; import FlagRule from "./FlagRule"; -import LogFile from "../LogFile"; +import LogFile from "../lib/LogFile"; import Table from "./Tables/Table"; import StateTable from "./Tables/StateTable"; import TransitionTable from "./Tables/TransitionTable"; @@ -15,6 +15,7 @@ import { VSCodePanelView, } from "@vscode/webview-ui-toolkit/react"; import { useRegularExpressionSearch } from "../hooks/useLogSearchManager"; +import { logDataToString } from "../hooks/useTracyLogData"; interface State { name: string; @@ -220,7 +221,7 @@ export default class StateBasedRule extends Rule { ); }; - const allColumns = ["", ...logFile.contentHeaders, ...user_columns]; + const allColumns = ["", ...logFile.getAllHeaders().filter((h) => h !== this.column)]; const transitionRows: any[][] = []; if (this.ruleStates.length > 0) { @@ -526,7 +527,7 @@ export default class StateBasedRule extends Rule { for (const conditionSet of transition.conditions) { let allConditionsSatisfied = true; for (const condition of conditionSet) { - const logValue = logFile.value(condition.Column, r) ?? ""; + const logValue = logDataToString(logFile.value(condition.Column, r)); if (condition.Operation === "contains") { if (!logValue.includes(condition.Text)) { allConditionsSatisfied = false; diff --git a/src/viewer/types.ts b/src/viewer/types.ts new file mode 100644 index 0000000..bbb708f --- /dev/null +++ b/src/viewer/types.ts @@ -0,0 +1,5 @@ +import { CustomDisplayString } from "./interfaces"; + +export namespace types { + export type TracyLogData = string | number | boolean | Date | null | undefined | CustomDisplayString; +} \ No newline at end of file