From 013350213022c94ab1698e2c0fba4a3f45e7de5a Mon Sep 17 00:00:00 2001 From: Ruben Thoms Date: Tue, 22 Oct 2024 12:15:01 +0200 Subject: [PATCH] wip --- .../ensembleRealizationFilter.tsx | 47 ++++++---- .../byParameterValueFilter.tsx | 92 ++++++++++--------- .../realizationNumberDisplay.tsx | 14 ++- .../private-utils/treeNodeSelection.ts | 6 ++ .../SmartNodeSelector/smartNodeSelector.tsx | 23 +++-- 5 files changed, 111 insertions(+), 71 deletions(-) diff --git a/frontend/src/framework/components/EnsembleRealizationFilter/ensembleRealizationFilter.tsx b/frontend/src/framework/components/EnsembleRealizationFilter/ensembleRealizationFilter.tsx index 21a62d178..e2a8af87e 100644 --- a/frontend/src/framework/components/EnsembleRealizationFilter/ensembleRealizationFilter.tsx +++ b/frontend/src/framework/components/EnsembleRealizationFilter/ensembleRealizationFilter.tsx @@ -54,6 +54,8 @@ export type EnsembleRealizationFilterProps = { * The selection creates a valid subset of realization numbers for the ensemble throughout the application. */ export const EnsembleRealizationFilter: React.FC = (props) => { + const { onFilterChange } = props; + const [prevIsActive, setPrevIsActive] = React.useState(props.isActive); // States for handling initial realization number selections and smart node selector tags @@ -118,26 +120,35 @@ export const EnsembleRealizationFilter: React.FC }); } - function handleParameterValueFilterChanged(selection: ByParameterValueFilterSelection) { - setSelectedSmartNodeSelectorTags(selection.smartNodeSelectorTags); + const handleParameterValueFilterChanged = React.useCallback( + function handleParameterValueFilterChanged(selection: ByParameterValueFilterSelection) { + setSelectedSmartNodeSelectorTags(selection.smartNodeSelectorTags); - if (!props.onFilterChange) { - return; - } + if (!onFilterChange) { + return; + } - // Create realization number array to display based on current selection - const realizationNumberArray = RealizationFilter.createFilteredRealizationsFromParameterValueSelections( - selection.parameterIdentStringToValueSelectionMap, - props.ensembleParameters, - props.availableEnsembleRealizations - ); + // Create realization number array to display based on current selection + const realizationNumberArray = RealizationFilter.createFilteredRealizationsFromParameterValueSelections( + selection.parameterIdentStringToValueSelectionMap, + props.ensembleParameters, + props.availableEnsembleRealizations + ); - props.onFilterChange({ - ...props.selections, - displayRealizationNumbers: realizationNumberArray, - parameterIdentStringToValueSelectionReadonlyMap: selection.parameterIdentStringToValueSelectionMap, - }); - } + onFilterChange({ + ...props.selections, + displayRealizationNumbers: realizationNumberArray, + parameterIdentStringToValueSelectionReadonlyMap: selection.parameterIdentStringToValueSelectionMap, + }); + }, + [ + onFilterChange, + props.ensembleParameters, + props.availableEnsembleRealizations, + props.selections, + setSelectedSmartNodeSelectorTags, + ] + ); function handleActiveFilterTypeChange(newFilterType: RealizationFilterType) { if (!props.onFilterChange) { @@ -231,6 +242,8 @@ export const EnsembleRealizationFilter: React.FC } } + console.debug(actualSmartNodeSelectorTags); + return (
= (props) => { + const { onFilterChange } = props; + // Compare by reference (ensure if it is enough to compare by reference) const smartNodeSelectorTreeDataNodes = React.useMemo(() => { const includeConstantParameters = false; @@ -50,56 +52,64 @@ export const ByParameterValueFilter: React.FC = (pr ); }, [props.ensembleParameters]); - function handleParameterNameSelectionChanged(selection: SmartNodeSelectorSelection) { - if (selection === null) { - return; - } + const handleParameterNameSelectionChanged = React.useCallback( + function handleParameterNameSelectionChanged(selection: SmartNodeSelectorSelection) { + if (selection === null) { + return; + } - // Find new parameter ident strings that are not in the current map - const newMap = new Map(props.selectedParameterIdentStringToValueSelectionReadonlyMap); + console.debug(selection.selectedIds); + console.debug(selection.selectedNodes); - // Get selected parameter ident strings - const selectedParameterIdentStrings = selection.selectedIds; + // Find new parameter ident strings that are not in the current map + const newMap = new Map(props.selectedParameterIdentStringToValueSelectionReadonlyMap); - // Delete deselected parameter ident strings - const deselectedParameterIdentStrings = Array.from(newMap.keys()).filter( - (paramIdentString) => !selectedParameterIdentStrings.includes(paramIdentString) - ); - for (const deselectedParameterIdentString of deselectedParameterIdentStrings) { - newMap.delete(deselectedParameterIdentString); - } + // Get selected parameter ident strings + const selectedParameterIdentStrings = selection.selectedIds; - // Add new selected parameter ident strings - const newDiscreteValueSelection: Readonly = []; - for (const parameterIdentString of selectedParameterIdentStrings) { - const parameter = props.ensembleParameters.findParameter(ParameterIdent.fromString(parameterIdentString)); - if (!parameter || newMap.has(parameterIdentString)) { - continue; + // Delete deselected parameter ident strings + const deselectedParameterIdentStrings = Array.from(newMap.keys()).filter( + (paramIdentString) => !selectedParameterIdentStrings.includes(paramIdentString) + ); + for (const deselectedParameterIdentString of deselectedParameterIdentStrings) { + newMap.delete(deselectedParameterIdentString); } - let newParameterValueSelection: ParameterValueSelection = newDiscreteValueSelection; - if (parameter.type === ParameterType.CONTINUOUS) { - const max = Math.max(...parameter.values); - const min = Math.min(...parameter.values); - const numberRange: Readonly = { start: min, end: max }; - newParameterValueSelection = numberRange; - } + // Add new selected parameter ident strings + const newDiscreteValueSelection: Readonly = []; + for (const parameterIdentString of selectedParameterIdentStrings) { + const parameter = props.ensembleParameters.findParameter( + ParameterIdent.fromString(parameterIdentString) + ); + if (!parameter || newMap.has(parameterIdentString)) { + continue; + } - // Update value selection with .set() - // - Do not use .get() and modify by reference, as .get() will return reference to source, - // i.e. props.selectedParameterIdentStringToValueSelectionMap. Thus modifying the value - // will modify the source, which is not allowed. - newMap.set(parameterIdentString, newParameterValueSelection); - } + let newParameterValueSelection: ParameterValueSelection = newDiscreteValueSelection; + if (parameter.type === ParameterType.CONTINUOUS) { + const max = Math.max(...parameter.values); + const min = Math.min(...parameter.values); + const numberRange: Readonly = { start: min, end: max }; + newParameterValueSelection = numberRange; + } - const nonEmptyMap = newMap.size > 0 ? (newMap as ReadonlyMap) : null; + // Update value selection with .set() + // - Do not use .get() and modify by reference, as .get() will return reference to source, + // i.e. props.selectedParameterIdentStringToValueSelectionMap. Thus modifying the value + // will modify the source, which is not allowed. + newMap.set(parameterIdentString, newParameterValueSelection); + } - // Trigger filter change - props.onFilterChange({ - parameterIdentStringToValueSelectionMap: nonEmptyMap, - smartNodeSelectorTags: selection.selectedTags, - }); - } + const nonEmptyMap = newMap.size > 0 ? (newMap as ReadonlyMap) : null; + + // Trigger filter change + onFilterChange({ + parameterIdentStringToValueSelectionMap: nonEmptyMap, + smartNodeSelectorTags: selection.selectedTags, + }); + }, + [onFilterChange, props.ensembleParameters, props.selectedParameterIdentStringToValueSelectionReadonlyMap] + ); function setNewParameterValueSelectionAndTriggerOnChange( parameterIdentString: string, diff --git a/frontend/src/framework/components/EnsembleRealizationFilter/private-components/realizationNumberDisplay.tsx b/frontend/src/framework/components/EnsembleRealizationFilter/private-components/realizationNumberDisplay.tsx index 01b953213..1b5a45b82 100644 --- a/frontend/src/framework/components/EnsembleRealizationFilter/private-components/realizationNumberDisplay.tsx +++ b/frontend/src/framework/components/EnsembleRealizationFilter/private-components/realizationNumberDisplay.tsx @@ -50,6 +50,7 @@ export const RealizationNumberDisplay: React.FC = const realizationDivSizeClass = isCompact ? "w-[9px] h-[9px]" : "w-[12px] h-[12px]"; let rowElmCounter = 0; + let groupCounter = 0; let rowElements: JSX.Element[] = []; for (const [index, realization] of allRealizationsInRange.entries()) { const isCurrentRealizationAvailable = props.availableRealizations.includes(realization); @@ -63,12 +64,12 @@ export const RealizationNumberDisplay: React.FC = title={`real-${realization}`} key={realization} className={resolveClassNames( - `${realizationDivSizeClass} rounded-full aspect-square flex justify-center items-center`, + `${realizationDivSizeClass} rounded-full aspect-square flex justify-center items-center outline-blue-300 outline-2`, { "bg-green-600": isRealizationSelected, "bg-gray-400": !isRealizationSelected && isCurrentRealizationAvailable, - "bg-gray-300": !isRealizationSelected && !isCurrentRealizationAvailable, - "cursor-pointer": !isClickDisabled, + "bg-gray-300 cursor-not-allowed": !isRealizationSelected && !isCurrentRealizationAvailable, + "cursor-pointer hover:outline": !isClickDisabled, } )} onClick={isClickDisabled ? undefined : () => handleRealizationElementClick(realization)} @@ -79,9 +80,14 @@ export const RealizationNumberDisplay: React.FC = // If the group is full (or last realization), add it to the main div elements and reset counter const isLastRealization = index === allRealizationsInRange.length - 1; if (++rowElmCounter === numRealizationPerRow || isLastRealization) { - const groupDiv =
{[...rowElements]}
; + const groupDiv = ( +
+ {[...rowElements]} +
+ ); mainDivElements.push(groupDiv); rowElmCounter = 0; + groupCounter++; } } return
{mainDivElements}
; diff --git a/frontend/src/lib/components/SmartNodeSelector/private-utils/treeNodeSelection.ts b/frontend/src/lib/components/SmartNodeSelector/private-utils/treeNodeSelection.ts index 79e66f555..af2c54cf3 100644 --- a/frontend/src/lib/components/SmartNodeSelector/private-utils/treeNodeSelection.ts +++ b/frontend/src/lib/components/SmartNodeSelector/private-utils/treeNodeSelection.ts @@ -303,6 +303,12 @@ export class TreeNodeSelection { return this._treeData.findNodes(this._nodePath, MatchType.fullMatch).nodePaths; } + exactlyMatchedNodeIds(): (string | undefined)[][] { + return this._treeData + .findNodes(this._nodePath, MatchType.fullMatch) + .metaData.map((el) => el.map((meta) => meta.id)); + } + countExactlyMatchedNodePaths(): number { return this.exactlyMatchedNodePaths().length; } diff --git a/frontend/src/lib/components/SmartNodeSelector/smartNodeSelector.tsx b/frontend/src/lib/components/SmartNodeSelector/smartNodeSelector.tsx index 45b4e6474..a825c97c9 100644 --- a/frontend/src/lib/components/SmartNodeSelector/smartNodeSelector.tsx +++ b/frontend/src/lib/components/SmartNodeSelector/smartNodeSelector.tsx @@ -236,6 +236,7 @@ export class SmartNodeSelectorComponent extends React.Component { - this.updateSelectedTagsAndNodes(); - } + () => this.updateSelectedTagsAndNodes() ); } - const selectedTags = this.state.nodeSelections - .filter((nodeSelection) => nodeSelection.isValid()) - .map((nodeSelection) => nodeSelection.getCompleteNodePathAsString()); + const selectedTags = this.state.nodeSelections.map((nodeSelection) => + nodeSelection.getCompleteNodePathAsString() + ); if ( this.props.selectedTags && JSON.stringify(this.props.selectedTags) !== JSON.stringify(selectedTags) && @@ -283,7 +282,8 @@ export class SmartNodeSelectorComponent extends React.Component { this.focusCurrentTag(); this.maybeShowSuggestions(); + this.noUserInputSelect = false; }, }; } else { this.focusCurrentTag(); struct.callback = () => { this.maybeShowSuggestions(); + this.noUserInputSelect = false; }; } struct.suggestionsVisible = false; @@ -962,19 +965,21 @@ export class SmartNodeSelectorComponent extends React.Component= maxNumSelectedNodes && maxNumSelectedNodes > 0) { break loop1; } selectedNodes.push(matchedNodePaths[j]); - selectedIds.push(nodeSelection.getId() || ""); + selectedIds.push(matchedIds[j][matchedIds[j].length - 1] ?? ""); } } } if ( !this.selectedNodes || selectedNodes.length !== this.selectedNodes.length || - JSON.stringify(this.selectedNodes) !== JSON.stringify(selectedNodes) + JSON.stringify(this.selectedNodes) !== JSON.stringify(selectedNodes) || + JSON.stringify(this.props.selectedTags) !== JSON.stringify(selectedTags) ) { if (!initialUpdate) { this.updateFromWithin = true;