From 0f7d208a35d2b80fa840326b40851af2c0bae412 Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Wed, 21 Oct 2026 00:27:36 -0700 Subject: [PATCH 1/3] seearch box --- src/diagnostics-schema.ts | 1 + src/table-search.test.ts | 29 +++++++++++++++++++ src/table-search.ts | 6 ++++ .../src/diagnostics_panel/DynamicTab.tsx | 14 +++++++++ .../MinecraftMultiColumnStatisticTable.tsx | 29 ++++++++++++++++++- .../controls/MinecraftStatisticTable.tsx | 23 ++++++++++++++- 6 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/table-search.test.ts create mode 100644 src/table-search.ts diff --git a/src/diagnostics-schema.ts b/src/diagnostics-schema.ts index f888df71..fcc0221f 100644 --- a/src/diagnostics-schema.ts +++ b/src/diagnostics-schema.ts @@ -25,6 +25,7 @@ export interface DiagnosticsTabDescriptor { target_value?: number; key_label?: string; value_labels?: string[]; + show_search_box?: boolean; statistic_id?: string; statistic_ids?: string[]; is_empty_tab?: boolean; diff --git a/src/table-search.test.ts b/src/table-search.test.ts new file mode 100644 index 00000000..6e7c00fa --- /dev/null +++ b/src/table-search.test.ts @@ -0,0 +1,29 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +import { describe, expect, it } from 'vitest'; +import { matchesTableSearch } from './table-search'; + +describe('matchesTableSearch', () => { + const cells = ['Player Count', 'Active', '12.0']; + + it('shows all rows for an empty or whitespace-only query', () => { + expect(matchesTableSearch('', cells)).toBe(true); + expect(matchesTableSearch(' ', cells)).toBe(true); + expect(matchesTableSearch('', [''])).toBe(true); + }); + + it('matches keys and values without regard to case', () => { + expect(matchesTableSearch('PLAYER', cells)).toBe(true); + expect(matchesTableSearch('active', cells)).toBe(true); + expect(matchesTableSearch('12.0', cells)).toBe(true); + }); + + it('ignores surrounding query whitespace', () => { + expect(matchesTableSearch(' count ', cells)).toBe(true); + }); + + it('does not match unrelated rows or across cell boundaries', () => { + expect(matchesTableSearch('missing', cells)).toBe(false); + expect(matchesTableSearch('CountActive', cells)).toBe(false); + }); +}); diff --git a/src/table-search.ts b/src/table-search.ts new file mode 100644 index 00000000..56caf9f8 --- /dev/null +++ b/src/table-search.ts @@ -0,0 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +export function matchesTableSearch(query: string, cells: string[]): boolean { + const normalizedQuery = query.trim().toLowerCase(); + return cells.some(cell => cell.toLowerCase().includes(normalizedQuery)); +} diff --git a/webview-ui/src/diagnostics_panel/DynamicTab.tsx b/webview-ui/src/diagnostics_panel/DynamicTab.tsx index b1736d23..a7f71a4c 100644 --- a/webview-ui/src/diagnostics_panel/DynamicTab.tsx +++ b/webview-ui/src/diagnostics_panel/DynamicTab.tsx @@ -132,6 +132,18 @@ export default function DynamicTab({ descriptor, selectedClient, selectedPlugin ); case 'table': + if (isScriptView) { + return ( + + ); + } return ( ); @@ -150,6 +163,7 @@ export default function DynamicTab({ descriptor, selectedClient, selectedPlugin valueLabels={descriptor.value_labels ?? ['Value']} statisticDataProvider={provider as MultipleStatisticProvider} statisticResolver={resolver} + showSearchBox={descriptor.show_search_box} /> ); diff --git a/webview-ui/src/diagnostics_panel/controls/MinecraftMultiColumnStatisticTable.tsx b/webview-ui/src/diagnostics_panel/controls/MinecraftMultiColumnStatisticTable.tsx index 8ad4c41c..426cc28b 100644 --- a/webview-ui/src/diagnostics_panel/controls/MinecraftMultiColumnStatisticTable.tsx +++ b/webview-ui/src/diagnostics_panel/controls/MinecraftMultiColumnStatisticTable.tsx @@ -10,7 +10,9 @@ import { VSCodeDropdown, VSCodeOption, VSCodeButton, + VSCodeTextField, } from '@vscode/webview-ui-toolkit/react'; +import { matchesTableSearch } from '../../../../src/table-search'; export enum MinecraftMultiColumnStatisticTableSortOrder { Ascending, @@ -59,6 +61,7 @@ type MinecraftMultiColumnStatisticTableProps = { rowAction?: MinecraftMultiColumnStatisticTableRowAction; // Optional per-row action button nonConsolidatedColumnResolver?: NonConsolidatedColumnResolver; // Maps split events to target columns for non-consolidated streams valueFormatter?: (value: string | number, columnIndex: number) => string; // Optional custom display formatter for cell values + showSearchBox?: boolean; }; const sortOrderOptions = [ @@ -146,6 +149,7 @@ export default function MinecraftMultiColumnStatisticTable({ rowAction, nonConsolidatedColumnResolver, valueFormatter, + showSearchBox = false }: MinecraftMultiColumnStatisticTableProps): JSX.Element { // Memoize sort column options to prevent unnecessary recreations const sortColumnOptions = useMemo( @@ -158,6 +162,7 @@ export default function MinecraftMultiColumnStatisticTable({ // states const [data, setData] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); const [selectedSortOrder, setSelectedSortOrder] = useState(defaultSortOrder); const [selectedSortType, setSelectedSortType] = @@ -370,9 +375,31 @@ export default function MinecraftMultiColumnStatisticTable({ prettifyNames, ]); + const visibleData = showSearchBox + ? data.filter(dataPoint => matchesTableSearch(searchQuery, [ + dataPoint.category, + ...dataPoint.values.map((value, index) => + valueFormatter + ? valueFormatter(value, index) + : typeof value === 'number' + ? value.toFixed(1) + : value, + ), + ])) + : data; + return (

{title}

+ {showSearchBox && ( + setSearchQuery((event.target as HTMLInputElement).value)} + > + Search + + )} {actions?.length && (
{actions.map(action => ( @@ -464,7 +491,7 @@ export default function MinecraftMultiColumnStatisticTable({ )} - {data.map(dataPoint => ( + {visibleData.map(dataPoint => ( ([]); + const [searchQuery, setSearchQuery] = useState(''); const [selectedSortOrder, setSelectedSortOrder] = useState(defaultSortOrder); const [selectedSortType, setSelectedSortType] = useState(defaultSortType); @@ -115,9 +120,25 @@ export default function MinecraftStatisticTable({ }; }, [statisticDataProvider, selectedSortOrder, selectedSortType]); + const visibleData = showSearchBox + ? data.filter(dataPoint => matchesTableSearch(searchQuery, [ + dataPoint.category ?? '', + dataPoint.absoluteValue.toFixed(1), + ])) + : data; + return (

{title}

+ {showSearchBox && ( + setSearchQuery((event.target as HTMLInputElement).value)} + > + Search + + )}
@@ -154,7 +175,7 @@ export default function MinecraftStatisticTable({ {valueLabel} - {data.map(dataPoint => ( + {visibleData.map(dataPoint => ( {dataPoint.category} {dataPoint.absoluteValue.toFixed(1)} From b64ae0b94a4a950fc2a6c68aae6d7247970ad9e5 Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Wed, 21 Oct 2026 00:31:14 -0700 Subject: [PATCH 2/3] Update DynamicTab.tsx --- webview-ui/src/diagnostics_panel/DynamicTab.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/webview-ui/src/diagnostics_panel/DynamicTab.tsx b/webview-ui/src/diagnostics_panel/DynamicTab.tsx index a7f71a4c..d4ffc2d4 100644 --- a/webview-ui/src/diagnostics_panel/DynamicTab.tsx +++ b/webview-ui/src/diagnostics_panel/DynamicTab.tsx @@ -132,18 +132,6 @@ export default function DynamicTab({ descriptor, selectedClient, selectedPlugin ); case 'table': - if (isScriptView) { - return ( - - ); - } return ( Date: Fri, 23 Oct 2026 13:21:19 -0700 Subject: [PATCH 3/3] search for dynamic properties --- .../MinecraftDynamicPropertiesTable.tsx | 106 ++++++++++-------- .../prefabs/tabs/DynamicProperties.tsx | 1 + 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/webview-ui/src/diagnostics_panel/controls/MinecraftDynamicPropertiesTable.tsx b/webview-ui/src/diagnostics_panel/controls/MinecraftDynamicPropertiesTable.tsx index 6da9d109..8d92aece 100644 --- a/webview-ui/src/diagnostics_panel/controls/MinecraftDynamicPropertiesTable.tsx +++ b/webview-ui/src/diagnostics_panel/controls/MinecraftDynamicPropertiesTable.tsx @@ -1,8 +1,14 @@ // Copyright (C) Microsoft Corporation. All rights reserved. import { useEffect, useState } from 'react'; -import { VSCodeDataGrid, VSCodeDataGridCell, VSCodeDataGridRow } from '@vscode/webview-ui-toolkit/react'; +import { + VSCodeDataGrid, + VSCodeDataGridCell, + VSCodeDataGridRow, + VSCodeTextField, +} from '@vscode/webview-ui-toolkit/react'; import { StatisticProvider, StatisticUpdatedMessage } from '../StatisticProvider'; +import { matchesTableSearch } from '../../../../src/table-search'; function updateCachedData(propertyCache: Map, data: string[][]): boolean { let hasChanges = false; @@ -31,63 +37,73 @@ function updateCachedData(propertyCache: Map, data: string[][]): return hasChanges; } -export function MinecraftDynamicPropertiesTable(statisticDataProviders: Record) { +type MinecraftDynamicPropertiesTableProps = { + statisticDataProviders: StatisticProvider; + showSearchBox?: boolean; +}; + +export function MinecraftDynamicPropertiesTable({ + statisticDataProviders, + showSearchBox = false, +}: MinecraftDynamicPropertiesTableProps) { // the groups directly under the 'statParentId' const [dynamicValueCache, setDynamicValueCache] = useState>(new Map()); + const [searchQuery, setSearchQuery] = useState(''); //draws chart useEffect(() => { - const eventHandlersByName = new Map void>(); - - Object.keys(statisticDataProviders).forEach(statisticDataProviderName => { - const statsProvider = statisticDataProviders[statisticDataProviderName]; - const eventHandler = (event: StatisticUpdatedMessage): void => { - // Update data with new data point - setDynamicValueCache((prevCache: Map): Map => { - const propertyCache = new Map(prevCache); - if (updateCachedData(propertyCache, event.children_string_values)) { - return propertyCache; - } - - return prevCache; - }); - }; + const eventHandler = (event: StatisticUpdatedMessage): void => { + // Update data with new data point + setDynamicValueCache((prevCache: Map): Map => { + const propertyCache = new Map(prevCache); + if (updateCachedData(propertyCache, event.children_string_values)) { + return propertyCache; + } - statsProvider.registerWindowListener(window); - statsProvider.addSubscriber(eventHandler); + return prevCache; + }); + }; - eventHandlersByName.set(statisticDataProviderName, eventHandler); - }); + statisticDataProviders.registerWindowListener(window); + statisticDataProviders.addSubscriber(eventHandler); // Remove old listener return () => { - Object.keys(statisticDataProviders).forEach(statisticDataProviderName => { - const statsProvider = statisticDataProviders[statisticDataProviderName]; - - const eventHandler = eventHandlersByName.get(statisticDataProviderName); - if (eventHandler) { - statsProvider.removeSubscriber(eventHandler); - } - statsProvider.unregisterWindowListener(window); - }); + statisticDataProviders.removeSubscriber(eventHandler); + statisticDataProviders.unregisterWindowListener(window); }; }, [dynamicValueCache]); + const visibleProperties = Array.from(dynamicValueCache.entries()).filter(([name, value]) => + matchesTableSearch(searchQuery, [name, value]) + ); + return ( - - - - {'Name'} - - - {'Value'} - - - {Array.from(dynamicValueCache.entries()).map(event => ( - - {`${event[0]}`} - {`${event[1]}`} + <> + {showSearchBox && ( + setSearchQuery((event.target as HTMLInputElement).value)} + > + Search + + )} + + + + {'Name'} + + + {'Value'} + - ))} - + {visibleProperties.map(([name, value]) => ( + + {name} + {value} + + ))} + + ); } diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/DynamicProperties.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/DynamicProperties.tsx index 9bb6d008..f2ba2e99 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/DynamicProperties.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/DynamicProperties.tsx @@ -9,6 +9,7 @@ const statsTab: TabPrefab = { return (