diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index ba853082102..de74fdbf744 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/positronComponents/button/button.css b/src/vs/base/browser/ui/positronComponents/button/button.css index d6c95e18213..f93a059d1ca 100644 --- a/src/vs/base/browser/ui/positronComponents/button/button.css +++ b/src/vs/base/browser/ui/positronComponents/button/button.css @@ -6,6 +6,7 @@ border: none; cursor: pointer; background-color: transparent; + font-family: unset !important; } .button:focus { diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 76a226bd70e..688c7922fbe 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -667,6 +667,7 @@ export const Codicon = { positronClearRowFilters: register('positron-clear-row-filters', 0xf273), positronPowerButtonThin: register('positron-power-button-thin', 0xf274), positronRestartRuntimeThin: register('positron-restart-runtime-thin', 0xf275), + positronClearFilter: register('positron-clear-filter', 0xf276), // --- End Positron --- diff --git a/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okActionBar.tsx b/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okActionBar.tsx index bbb8ef03e54..3faf6032ccc 100644 --- a/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okActionBar.tsx +++ b/src/vs/workbench/browser/positronComponents/positronModalDialog/components/okActionBar.tsx @@ -2,7 +2,7 @@ * Copyright (C) 2022 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -// CSS> +// CSS. import 'vs/css!./okActionBar'; // React. diff --git a/src/vs/workbench/browser/positronComponents/positronModalDialog/positronModalDialog.tsx b/src/vs/workbench/browser/positronComponents/positronModalDialog/positronModalDialog.tsx index df8cefcfe11..4231a62c1fb 100644 --- a/src/vs/workbench/browser/positronComponents/positronModalDialog/positronModalDialog.tsx +++ b/src/vs/workbench/browser/positronComponents/positronModalDialog/positronModalDialog.tsx @@ -110,7 +110,7 @@ export const PositronModalDialog = (props: PropsWithChildren void; } /** @@ -195,6 +196,13 @@ export const PositronModalPopup = (props: PropsWithChildren { @@ -38,13 +38,13 @@ const validateRowFilterValue = (columnSchema: ColumnSchema, value: string) => { /** * Checks whether the value is a boolean. - * @returns true if the value is a number; otherwise, false. + * @returns true if the value is a boolean; otherwise, false. */ const isBoolean = () => /^(true|false)$/i.test(value); /** - * Checks whether the value is a boolean. - * @returns true if the value is a number; otherwise, false. + * Checks whether the value is a date. + * @returns true if the value is a date; otherwise, false. */ const isDate = () => !Number.isNaN(Date.parse(value)); @@ -74,24 +74,6 @@ const validateRowFilterValue = (columnSchema: ColumnSchema, value: string) => { } }; -/** - * RowFilterCondition enumeration. - */ -enum RowFilterCondition { - // Conditions with no parameters. - CONDITION_IS_EMPTY = 'is-empty', - CONDITION_IS_NOT_EMPTY = 'is-not-empty', - - // Conditions with one parameter. - CONDITION_IS_LESS_THAN = 'is-less-than', - CONDITION_IS_GREATER_THAN = 'is-greater-than', - CONDITION_IS_EQUAL = 'is-equal', - - // Conditions with two parameters. - CONDITION_IS_BETWEEN = 'is-between', - CONDITION_IS_NOT_BETWEEN = 'is-not-between' -} - /** * AddEditRowFilterModalPopupProps interface. */ @@ -99,8 +81,8 @@ interface AddEditRowFilterModalPopupProps { dataExplorerClientInstance: DataExplorerClientInstance; renderer: PositronModalReactRenderer; anchor: HTMLElement; - rowFilter?: RowFilter; - onAddRowFilter: (rowFilter: RowFilter) => void; + editRowFilter?: RowFilter; + onApplyRowFilter: (rowFilter: RowFilter) => void; } /** @@ -114,11 +96,28 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp const secondRowFilterParameterRef = useRef(undefined!); // State hooks. - const [selectedColumnSchema, setSelectedColumnSchema] = - useState(undefined); - const [selectedCondition, setSelectedCondition] = useState(undefined); - const [firstRowFilterValue, setFirstRowFilterValue] = useState(''); - const [secondRowFilterValue, setSecondRowFilterValue] = useState(''); + const [selectedColumnSchema, setSelectedColumnSchema] = useState( + props.editRowFilter?.columnSchema + ); + const [selectedCondition, setSelectedCondition] = useState( + props.editRowFilter?.rowFilterCondition + ); + const [firstRowFilterValue, setFirstRowFilterValue] = useState(() => { + if (props.editRowFilter instanceof SingleValueRowFilter) { + return props.editRowFilter.value; + } else if (props.editRowFilter instanceof RangeRowFilter) { + return props.editRowFilter.lowerLimit; + } else { + return ''; + } + }); + const [secondRowFilterValue, setSecondRowFilterValue] = useState(() => { + if (props.editRowFilter instanceof RangeRowFilter) { + return props.editRowFilter.upperLimit; + } else { + return ''; + } + }); const [errorText, setErrorText] = useState(undefined); // useEffect for when the selectedCondition changes. @@ -182,7 +181,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } - // Add is exactly condition. + // Add is equal to condition. switch (selectedColumnSchema.type_display) { case ColumnSchemaTypeDisplay.Number: case ColumnSchemaTypeDisplay.Boolean: @@ -191,10 +190,10 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp case ColumnSchemaTypeDisplay.Datetime: case ColumnSchemaTypeDisplay.Time: conditionEntries.push(new DropDownListBoxItem({ - identifier: RowFilterCondition.CONDITION_IS_EQUAL, + identifier: RowFilterCondition.CONDITION_IS_EQUAL_TO, title: localize( - 'positron.addEditRowFilter.conditionIsExactly', - "is exactly" + 'positron.addEditRowFilter.conditionIsEqualTo', + "is equal to" ) })); break; @@ -241,7 +240,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp // Render the first row filter parameter component in single-value mode. case RowFilterCondition.CONDITION_IS_LESS_THAN: case RowFilterCondition.CONDITION_IS_GREATER_THAN: - case RowFilterCondition.CONDITION_IS_EQUAL: + case RowFilterCondition.CONDITION_IS_EQUAL_TO: placeholderText = localize( 'positron.addEditRowFilter.valuePlaceholder', "value" @@ -263,6 +262,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp { // Set the first row filter value. setFirstRowFilterValue(text); @@ -275,7 +275,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp })(); // Set the second row filter parameter component. - const secondRowFilterParameter = (() => { + const secondRowFilterParameterComponent = (() => { let placeholderText: string | undefined = undefined; switch (selectedCondition) { // Do not render the second row filter parameter component. @@ -284,7 +284,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp case RowFilterCondition.CONDITION_IS_NOT_EMPTY: case RowFilterCondition.CONDITION_IS_LESS_THAN: case RowFilterCondition.CONDITION_IS_GREATER_THAN: - case RowFilterCondition.CONDITION_IS_EQUAL: + case RowFilterCondition.CONDITION_IS_EQUAL_TO: return null; // Render the second row filter parameter component in two-value mode. @@ -302,6 +302,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp { // Set the second row filter value. setSecondRowFilterValue(text); @@ -314,9 +315,9 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp })(); /** - * Apply button onPressed handler. + * Applies the row filter, if it is valid. */ - const applyButtonPressed = () => { + const applyRowFilter = () => { // Ensure that the user has selected a column schema. if (!selectedColumnSchema) { setErrorText(localize( @@ -438,54 +439,57 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp }; /** - * Adds a row filter. + * Applies a row filter. * @param rowFilter The row filter to add. */ - const addRowFilter = (rowFilter: RowFilter) => { + const applyRowFilter = (rowFilter: RowFilter) => { setErrorText(undefined); props.renderer.dispose(); - props.onAddRowFilter(rowFilter); + props.onApplyRowFilter(rowFilter); }; // Validate the condition and row filter values. If things are valid, add the row filter. switch (selectedCondition) { - // Add the is empty row filter. + // Apply the is empty row filter. case RowFilterCondition.CONDITION_IS_EMPTY: { - addRowFilter(new RowFilterIsEmpty(selectedColumnSchema)); + applyRowFilter(new RowFilterIsEmpty(selectedColumnSchema)); break; } - // Add the is not empty row filter. + // Apply the is not empty row filter. case RowFilterCondition.CONDITION_IS_NOT_EMPTY: { - addRowFilter(new RowFilterIsNotEmpty(selectedColumnSchema)); + applyRowFilter(new RowFilterIsNotEmpty(selectedColumnSchema)); break; } - // Add the is less than row filter. + // Apply the is less than row filter. case RowFilterCondition.CONDITION_IS_LESS_THAN: { if (!validateFirstRowFilterValue()) { return; } - addRowFilter(new RowFilterIsLessThan(selectedColumnSchema, firstRowFilterValue)); + applyRowFilter(new RowFilterIsLessThan(selectedColumnSchema, firstRowFilterValue)); break; } + // Apply the is greater than row filter. case RowFilterCondition.CONDITION_IS_GREATER_THAN: { if (!validateFirstRowFilterValue()) { return; } - addRowFilter(new RowFilterIsGreaterThan(selectedColumnSchema, firstRowFilterValue)); + applyRowFilter(new RowFilterIsGreaterThan(selectedColumnSchema, firstRowFilterValue)); break; } - case RowFilterCondition.CONDITION_IS_EQUAL: { + // Apply the is equal to row filter. + case RowFilterCondition.CONDITION_IS_EQUAL_TO: { if (!validateFirstRowFilterValue()) { return; } - addRowFilter(new RowFilterIsEqualTo(selectedColumnSchema, firstRowFilterValue)); + applyRowFilter(new RowFilterIsEqualTo(selectedColumnSchema, firstRowFilterValue)); break; } + // Apply the is between row filter. case RowFilterCondition.CONDITION_IS_BETWEEN: { if (!validateFirstRowFilterValue()) { return; @@ -493,7 +497,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp if (!validateSecondRowFilterValue()) { return; } - addRowFilter(new RowFilterIsBetween( + applyRowFilter(new RowFilterIsBetween( selectedColumnSchema, firstRowFilterValue, secondRowFilterValue @@ -501,6 +505,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } + // Apply the is not between row filter. case RowFilterCondition.CONDITION_IS_NOT_BETWEEN: { if (!validateFirstRowFilterValue()) { return; @@ -508,7 +513,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp if (!validateSecondRowFilterValue()) { return; } - addRowFilter(new RowFilterIsNotBetween( + applyRowFilter(new RowFilterIsNotBetween( selectedColumnSchema, firstRowFilterValue, secondRowFilterValue @@ -518,6 +523,15 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp } }; + /** + * Clears the filter values and error text. + */ + const clearFilterValuesAndErrorText = () => { + setFirstRowFilterValue(''); + setSecondRowFilterValue(''); + setErrorText(undefined); + }; + // Render. return (
{ + selectedColumnSchema={selectedColumnSchema} + onSelectedColumnSchemaChanged={columnSchema => { // Set the selected column schema. setSelectedColumnSchema(columnSchema); // Reset the selected condition. setSelectedCondition(undefined); - // Clear the state. - setFirstRowFilterValue(''); - setSecondRowFilterValue(''); - setErrorText(undefined); + // Clear the filter values and error text. + clearFilterValuesAndErrorText(); }} /> {firstRowFilterParameterComponent} - {secondRowFilterParameter} + {secondRowFilterParameterComponent} {errorText && (
{errorText}
)} - -
-
- {!filtersHidden && filters.map((filter, index) => -
{filter.name}
- )} - -
- - ); -}; diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.css b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.css new file mode 100644 index 00000000000..d2ed6f9b90e --- /dev/null +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.css @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +.row-filter-widget { + padding: 0; + height: 25px; + display: flex; + cursor: pointer; + border-radius: 3px; + align-items: center; + box-sizing: border-box; + justify-content: center; + color: var(--vscode-positronDataExplorer-foreground); + border: 1px solid var(--vscode-positronDataExplorer-border); + background-color: var(--vscode-positronDataExplorer-background); +} + +.row-filter-widget:hover { + background-color: var(--vscode-positronDataExplorer-contrastBackground); +} + +.row-filter-widget +.boolean-operator { + height: 100%; + padding: 0 6px; + display: flex; + align-items: center; + justify-content: center; + border-right: 1px solid var(--vscode-positronDataExplorer-border); +} + +.row-filter-widget +.title { + margin: 0 2px 0 6px; +} + +.row-filter-widget +.title +.column-name { + font-weight: 600; +} + +.row-filter-widget +.title +.space-before::before { + content: " "; + white-space: pre; +} + +.row-filter-widget +.title +.space-after::after { + content: " "; + white-space: pre; +} + +.row-filter-widget +.clear-filter-button { + width: 18px; + height: 18px; + display: flex; + opacity: 80%; + cursor: pointer; + margin-right: 3px; + border-radius: 3px; + align-items: center; + box-sizing: border-box; + justify-content: center; +} + +.row-filter-widget +.clear-filter-button:hover { + border: 1px solid var(--vscode-positronDataExplorer-border); + + /* outline: 1px solid var(--vscode-focusBorder); */ + background-color: var(--vscode-positronDataExplorer-contrastBackground); +} + +.row-filter-widget +.clear-button:focus { + outline: none !important; +} + +.row-filter-widget +.clear-button:focus-visible { + outline: 1px solid var(--vscode-focusBorder); +} diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx new file mode 100644 index 00000000000..277f09f7a69 --- /dev/null +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// CSS. +import 'vs/css!./rowFilterWidget'; + +// React. +import * as React from 'react'; +import { forwardRef } from 'react'; // eslint-disable-line no-duplicate-imports + +// Other dependencies. +import { localize } from 'vs/nls'; +import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; +import { RowFilter, RowFilterIsBetween, RowFilterIsEmpty, RowFilterIsEqualTo, RowFilterIsGreaterThan, RowFilterIsLessThan, RowFilterIsNotBetween, RowFilterIsNotEmpty } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter'; + +/** + * RowFilterWidgetProps interface. + */ +interface RowFilterWidgetProps { + rowFilter: RowFilter; + booleanOperator?: 'and'; + onEdit: () => void; + onClear: () => void; +} + +/** + * RowFilterWidget component. + * @param props A RowFilterWidgetProps that contains the component properties. + * @returns The rendered component. + */ +export const RowFilterWidget = forwardRef((props, ref) => { + // Compute the title. + const title = (() => { + if (props.rowFilter instanceof RowFilterIsEmpty) { + return <> + {props.rowFilter.columnSchema.column_name} + + {localize('positron.dataExplorer.rowFilterWidget.isEmpty', "is empty")} + + ; + } else if (props.rowFilter instanceof RowFilterIsNotEmpty) { + return <> + {props.rowFilter.columnSchema.column_name} + + {localize('positron.dataExplorer.rowFilterWidget.isNotEmpty', "is not empty")} + + ; + } else if (props.rowFilter instanceof RowFilterIsLessThan) { + return <> + {props.rowFilter.columnSchema.column_name} + < + {props.rowFilter.value} + ; + } else if (props.rowFilter instanceof RowFilterIsGreaterThan) { + return <> + {props.rowFilter.columnSchema.column_name} + > + {props.rowFilter.value} + ; + } else if (props.rowFilter instanceof RowFilterIsEqualTo) { + return <> + {props.rowFilter.columnSchema.column_name} + = + {props.rowFilter.value} + ; + } else if (props.rowFilter instanceof RowFilterIsBetween) { + return <> + {props.rowFilter.columnSchema.column_name} + >= + {props.rowFilter.lowerLimit} + + {localize('positron.dataExplorer.rowFilterWidget.and', "and")} + + {props.rowFilter.columnSchema.column_name} + <= + {props.rowFilter.upperLimit} + ; + } else if (props.rowFilter instanceof RowFilterIsNotBetween) { + return <> + {props.rowFilter.columnSchema.column_name} + < + {props.rowFilter.lowerLimit} + + {localize('positron.dataExplorer.rowFilterWidget.and', "and")} + + {props.rowFilter.columnSchema.column_name} + > + {props.rowFilter.upperLimit} + ; + } else { + // This indicates a bug. + return null; + } + })(); + + // Render. + return ( + + + ); +}); + +// Set the display name. +RowFilterWidget.displayName = 'RowFilterWidget'; diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/filterBar.css b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.css similarity index 86% rename from src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/filterBar.css rename to src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.css index 2964dc0aa90..dcd7f1cf7aa 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/filterBar.css +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.css @@ -3,7 +3,7 @@ *--------------------------------------------------------------------------------------------*/ .data-explorer-panel -.filter-bar { +.row-filter-bar { padding: 8px; display: grid; grid-row: filter-bar / data-explorer; @@ -13,15 +13,8 @@ } .data-explorer-panel -.filter-bar -.filter { - grid-column: icon / gutter; -} - -.data-explorer-panel -.filter-bar -.filter -.filter-button { +.row-filter-bar +.row-filter-button { width: 25px; height: 25px; display: flex; @@ -31,47 +24,45 @@ align-items: center; box-sizing: border-box; justify-content: center; + grid-column: icon / gutter; border: 1px solid var(--vscode-positronDataExplorer-border); background-color: var(--vscode-positronDataExplorer-background); } .data-explorer-panel -.filter-bar -.filter -.filter-button:focus { +.row-filter-bar +.row-filter-button:focus { outline: none !important; } .data-explorer-panel -.filter-bar -.filter -.filter-button:focus-visible { +.row-filter-bar +.row-filter-button:focus-visible { border-radius: 3px; outline: 1px solid var(--vscode-focusBorder) !important; } .data-explorer-panel -.filter-bar -.filter -.filter-button +.row-filter-bar +.row-filter-button .counter { top: -6px; right: -6px; - font-size: 60%; - font-weight: 600; - position: absolute; - background-color: var(--vscode-positronDataGrid-sortIndexForeground); width: 14px; height: 14px; display: flex; + font-size: 60%; + color: white; + font-weight: 600; + border-radius: 7px; + position: absolute; align-items: center; justify-content: center; - border-radius: 7px; - color: white; + background-color: var(--vscode-positronDataGrid-sortIndexForeground); } .data-explorer-panel -.filter-bar +.row-filter-bar .filter-entries { row-gap: 8px; column-gap: 4px; @@ -82,9 +73,9 @@ } .data-explorer-panel -.filter-bar +.row-filter-bar .filter-entries -.add-filter-button { +.add-row-filter-button { width: 25px; height: 25px; display: flex; @@ -98,22 +89,22 @@ } .data-explorer-panel -.filter-bar +.row-filter-bar .filter-entries -.add-filter-button:focus { +.add-row-filter-button:focus { outline: none !important; } .data-explorer-panel -.filter-bar +.row-filter-bar .filter-entries -.add-filter-button:focus-visible { +.add-row-filter-button:focus-visible { border-radius: 3px; outline: 1px solid var(--vscode-focusBorder) !important; } .data-explorer-panel -.filter-bar +.row-filter-bar .filter-entries .filter { width: 90px; diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx new file mode 100644 index 00000000000..2774c3da843 --- /dev/null +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// CSS. +import 'vs/css!./rowFilterBar'; + +// React. +import * as React from 'react'; +import { useRef, useState } from 'react'; // eslint-disable-line no-duplicate-imports + +// Other dependencies. +import { localize } from 'vs/nls'; +import * as DOM from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; +import { showContextMenu } from 'vs/workbench/browser/positronComponents/contextMenu/contextMenu'; +import { ContextMenuItem } from 'vs/workbench/browser/positronComponents/contextMenu/contextMenuItem'; +import { ContextMenuSeparator } from 'vs/workbench/browser/positronComponents/contextMenu/contextMenuSeparator'; +import { usePositronDataExplorerContext } from 'vs/workbench/browser/positronDataExplorer/positronDataExplorerContext'; +import { PositronModalReactRenderer } from 'vs/workbench/browser/positronModalReactRenderer/positronModalReactRenderer'; +import { RowFilter } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilter'; +import { RowFilterWidget } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget'; +import { AddEditRowFilterModalPopup } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup'; + +/** + * RowFilterBar component. + * @returns The rendered component. + */ +export const RowFilterBar = () => { + // Context hooks. + const context = usePositronDataExplorerContext(); + + // Reference hooks. + const ref = useRef(undefined!); + const filterButtonRef = useRef(undefined!); + const rowFilterWidgetRefs = useRef<(HTMLButtonElement)[]>([]); + const addFilterButtonRef = useRef(undefined!); + + // State hooks. + const [rowFilters, setRowFilters] = useState([]); + const [filtersHidden, setFiltersHidden] = useState(false); + + /** + * Shows the add / edit row filter modal popup. + * @param rowFilterToEdit The row filter to edit, or undefined, to add a row filter. + */ + const showAddEditRowFilterModalPopup = (anchor: HTMLElement, rowFilterToEdit?: RowFilter) => { + // Create the renderer. + const renderer = new PositronModalReactRenderer({ + keybindingService: context.keybindingService, + layoutService: context.layoutService, + container: context.layoutService.getContainer(DOM.getWindow(ref.current)) + }); + + /** + * onApplyRowFilter event handler. + * @param rowFilterToApply The row filter to apply. + */ + const applyRowFilterHandler = (rowFilterToApply: RowFilter) => { + // If this is a new row filter, append it to the array of row filters. Otherwise, + // replace the row filter that was edited. + if (!rowFilterToEdit) { + setRowFilters(rowFilters => [...rowFilters, rowFilterToApply]); + } else { + // Find the index of the row filter to edit. + const index = rowFilters.findIndex(rowFilter => + rowFilterToEdit.identifier === rowFilter.identifier + ); + + setRowFilters(rowFilters => + [ + ...rowFilters.slice(0, index), + rowFilterToApply, + ...rowFilters.slice(index + 1) + ] + ); + } + }; + + // Show the add /edit row filter modal popup. + renderer.render( + + ); + }; + + /** + * Filter button pressed handler. + */ + const filterButtonPressedHandler = async () => { + // Build the context menu entries. + const entries: (ContextMenuItem | ContextMenuSeparator)[] = []; + entries.push(new ContextMenuItem({ + label: localize('positron.dataExplorer.addFilter', "Add filter"), + icon: 'positron-add-filter', + onSelected: () => showAddEditRowFilterModalPopup(filterButtonRef.current) + })); + entries.push(new ContextMenuSeparator()); + if (!filtersHidden) { + entries.push(new ContextMenuItem({ + label: localize('positron.dataExplorer.hideFilters', "Hide filters"), + icon: 'positron-hide-filters', + disabled: rowFilters.length === 0, + onSelected: () => setFiltersHidden(true) + })); + } else { + entries.push(new ContextMenuItem({ + label: localize('positron.dataExplorer.showFilters', "Show filters"), + icon: 'positron-show-filters', + onSelected: () => setFiltersHidden(false) + })); + } + entries.push(new ContextMenuSeparator()); + entries.push(new ContextMenuItem({ + label: localize('positron.dataExplorer.clearFilters', "Clear filters"), + icon: 'positron-clear-row-filters', + disabled: rowFilters.length === 0, + onSelected: () => setRowFilters([]) + })); + + // Show the context menu. + await showContextMenu( + context.keybindingService, + context.layoutService, + filterButtonRef.current, + 'left', + 200, + entries + ); + }; + + /** + * Clears the row filter at the specified row filter index. + * @param rowFilterIndex The row filter index. + */ + const clearRowFilter = (identifier: string) => { + setRowFilters(rowFilters => rowFilters.filter(rowFilter => + identifier !== rowFilter.identifier + )); + }; + + // Render. + return ( +
+ +
+ {!filtersHidden && rowFilters.map((rowFilter, index) => + { + if (ref) { + rowFilterWidgetRefs.current[index] = ref; + } + }} + key={index} + rowFilter={rowFilter} + booleanOperator={index ? 'and' : undefined} + onEdit={() => { + if (rowFilterWidgetRefs.current[index]) { + showAddEditRowFilterModalPopup( + rowFilterWidgetRefs.current[index], + rowFilter + ); + } + }} + onClear={() => clearRowFilter(rowFilter.identifier)} /> + )} + +
+
+ ); +}; diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx index 253ef41dd94..31bc3e7d879 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx @@ -2,16 +2,16 @@ * Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -// CSS> +// CSS. import 'vs/css!./dataExplorerPanel'; // React. import * as React from 'react'; // Other dependencies. -import { FilterBar } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/filterBar'; import { StatusBar } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/statusBar'; import { DataExplorer } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/dataExplorer'; +import { RowFilterBar } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar'; /** * DataExplorerPanel component. @@ -21,7 +21,7 @@ export const DataExplorerPanel = () => { // Render. return (
- +