Skip to content

Commit 42c1b1e

Browse files
authored
elements visibility with toggle filter (#237)
iTwin/changed-elements#119 - Updates & Modified changed elements now gets hidden when toggled off - Type of change filtering also hides the elements instead of turning them grey - should apply to all model node & elements nodes --------- Co-authored-by: naronchen <naronchen@users.noreply.github.com>
1 parent d12f564 commit 42c1b1e

7 files changed

Lines changed: 117 additions & 45 deletions

File tree

.changeset/loose-worms-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@itwin/changed-elements-react": patch
3+
---
4+
5+
Changed elements gets hidden when toggling filters

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ pnpm-debug.log*
1313
.env.*
1414

1515
packages/test-app-frontend/dist/
16-
.DS_Store
16+
*.DS_Store

packages/changed-elements-react/src/NamedVersionSelector/NamedVersionSelector.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ export function NamedVersionSelectorWidget(props: Readonly<NamedVersionSelectorW
9393
throw new Error("V2 Client is not initialized in given context.");
9494
}
9595

96-
const iTwinId = iModel.iTwinId as string;
97-
const iModelId = iModel.iModelId as string;
96+
if (!iModel.iTwinId || !iModel.iModelId || !iModel.changeset.id) {
97+
throw new Error("Empty IModel Connection")
98+
}
99+
100+
const iTwinId = iModel.iTwinId;
101+
const iModelId = iModel.iModelId;
98102
const currentChangesetId = iModel.changeset.id;
99103

100104
const { isLoading, currentNamedVersion, entries, updateJobStatus } = useNamedVersionsList({

packages/changed-elements-react/src/api/ChangesTreeDataProvider.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export interface ModelElementChanges {
3131
typeOfChange: number;
3232
}
3333

34+
export interface EntryPartition {
35+
visible: ChangedElementEntry[];
36+
hidden: ChangedElementEntry[];
37+
}
38+
3439
export function isModelElementChanges(input: unknown): input is ModelElementChanges {
3540
return !!input
3641
&& typeof input === "object"
@@ -1077,19 +1082,30 @@ export class ChangesTreeDataProvider implements ITreeDataProvider {
10771082
return entries;
10781083
}
10791084

1080-
/** Get entries with model ids based on a filter function. */
1081-
public getEntriesWithModelIds(
1085+
/** Get entries with model ids based on a filter function.
1086+
* @param includeFilter Function to filter entries that should be visible
1087+
* @param hideFilter Function to filter entries that should be hidden
1088+
*/
1089+
public GetEntriesByModelIdsAndFilters(
10821090
modelIds: Set<string>,
1083-
filterFunc: (entry: ChangedElementEntry) => boolean,
1084-
): ChangedElementEntry[] {
1085-
const entries = [];
1086-
for (const pair of this._elements) {
1087-
if (pair[1].modelId !== undefined && modelIds.has(pair[1].modelId) && filterFunc(pair[1])) {
1088-
entries.push(pair[1]);
1091+
includeFilter: (entry: ChangedElementEntry) => boolean,
1092+
hideFilter: (entry: ChangedElementEntry) => boolean,
1093+
): EntryPartition {
1094+
const visible: ChangedElementEntry[] = [];
1095+
const hidden: ChangedElementEntry[] = [];
1096+
1097+
for (const [, entry] of this._elements) {
1098+
if (!entry.modelId || !modelIds.has(entry.modelId)) continue;
1099+
1100+
if (hideFilter(entry)){
1101+
hidden.push(entry);
1102+
}
1103+
else if (includeFilter(entry)) {
1104+
visible.push(entry);
10891105
}
10901106
}
10911107

1092-
return entries;
1108+
return { visible, hidden };
10931109
}
10941110

10951111
public getModelAllChildElementEntries(modelId: string): ChangedElementEntry[] {

packages/changed-elements-react/src/api/VersionCompareTiles.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ export class Provider
116116
private _treeRef2d: Reference | undefined;
117117
public readonly iModel: IModelConnection;
118118
public secondaryIModelOverrides: FeatureSymbology.Overrides;
119-
public changedElems: ChangedElementEntry[];
119+
public visibleChangedElems: ChangedElementEntry[];
120+
public hiddenChangedElems: ChangedElementEntry[];
120121
public readonly viewport: Viewport;
121122
private readonly _removals: Array<() => void> = [];
122123
private _options: VersionDisplayOptions | undefined;
@@ -135,9 +136,11 @@ export class Provider
135136
options?: VersionDisplayOptions,
136137
targetIModelModels?: Set<string>,
137138
targetIModelCategories?: Set<string>,
139+
hiddenElems?: ChangedElementEntry[],
138140
) {
139141
this.iModel = iModel;
140-
this.changedElems = elems;
142+
this.visibleChangedElems = elems;
143+
this.hiddenChangedElems = hiddenElems || [];
141144
this.viewport = vp;
142145
this._options = options;
143146

@@ -224,9 +227,11 @@ export class Provider
224227
/**
225228
* Set changed element entries to visualize
226229
* @param elems Changed elements
230+
* @param hiddenElems Optional hidden changed elements to override display
227231
*/
228-
public setChangedElems(elems: ChangedElementEntry[]) {
229-
this.changedElems = elems;
232+
public setChangedElems(elems: ChangedElementEntry[], hiddenElems: ChangedElementEntry[] | undefined) {
233+
this.visibleChangedElems = elems;
234+
this.hiddenChangedElems = hiddenElems || [];
230235
this.viewport.invalidateScene();
231236
this.viewport.setFeatureOverrideProviderChanged();
232237
refreshEvent.raiseEvent();
@@ -347,8 +352,8 @@ export class Provider
347352
this._wantHideUnchanged() ? hiddenAppearance : unchangedAppearance,
348353
);
349354

350-
const insertedElems = this.changedElems.filter((entry: ChangedElement) => entry.opcode === DbOpcode.Insert);
351-
const updatedElems = this.changedElems.filter((entry: ChangedElement) => entry.opcode === DbOpcode.Update);
355+
const insertedElems = this.visibleChangedElems.filter((entry: ChangedElement) => entry.opcode === DbOpcode.Insert);
356+
const updatedElems = this.visibleChangedElems.filter((entry: ChangedElement) => entry.opcode === DbOpcode.Update);
352357

353358
const inserted = FeatureAppearance.fromJSON({
354359
rgb: VersionCompareVisualizationManager.colorInsertedRgb(),
@@ -360,6 +365,7 @@ export class Provider
360365
? true
361366
: undefined,
362367
});
368+
363369
for (const elem of insertedElems) {
364370
// Check if user is emphasizing some elements, and if so, override said elements
365371
if (this._internalAlwaysDrawn.size === 0 || this._internalAlwaysDrawn.has(elem.id)) {
@@ -391,7 +397,22 @@ export class Provider
391397
for (const elem of updatedElems) {
392398
// Check if user is emphasizing some elements, and if so, only override said elements
393399
if (this._internalAlwaysDrawn.size === 0 || this._internalAlwaysDrawn.has(elem.id)) {
394-
overrides.override({ elementId: elem.id, appearance: elem.indirect ? updatedIndirectly : updated });
400+
overrides.override({
401+
elementId: elem.id,
402+
appearance: elem.indirect
403+
? updatedIndirectly
404+
: updated,
405+
});
406+
}
407+
}
408+
409+
for (const elem of this.hiddenChangedElems){
410+
// If the user has hidden elements, we have to override them with the hidden appearance
411+
if (this._internalAlwaysDrawn.size === 0 || this._internalAlwaysDrawn.has(elem.id)) {
412+
overrides.override({
413+
elementId: elem.id,
414+
appearance: hiddenAppearance,
415+
});
395416
}
396417
}
397418
}
@@ -454,7 +475,7 @@ export class Provider
454475
// Handle removed elements that are in secondary iModel
455476
if (!this._wantHideRemoved()) {
456477
const deletedElemIds = new Set(
457-
this.changedElems
478+
this.visibleChangedElems
458479
.filter(
459480
(entry: ChangedElement) =>
460481
entry.opcode === DbOpcode.Delete &&
@@ -474,7 +495,7 @@ export class Provider
474495
// Handle modified elements that are in secondary iModel
475496
if (this._options?.wantModified && !this._wantHideModified()) {
476497
const modifiedElemIds = new Set(
477-
this.changedElems
498+
this.visibleChangedElems
478499
.filter(
479500
(entry: ChangedElement) =>
480501
entry.opcode === DbOpcode.Update && !neverDrawn.has(entry.id),
@@ -745,7 +766,7 @@ export class Provider
745766

746767
public toJSON(): ProviderProps {
747768
return {
748-
changedElems: this.changedElems,
769+
changedElems: this.visibleChangedElems,
749770
options: this._options,
750771
internalAlwaysDrawn: this._internalAlwaysDrawn,
751772
internalNeverDrawn: this._internalNeverDrawn,
@@ -762,7 +783,7 @@ export class Provider
762783
}
763784

764785
public fromJSON(props: ProviderProps) {
765-
this.changedElems = props.changedElems;
786+
this.visibleChangedElems = props.changedElems;
766787
this._internalAlwaysDrawn = props.internalAlwaysDrawn;
767788
this._internalNeverDrawn = props.internalNeverDrawn;
768789
this._exclusive = props.exclusive;
@@ -926,11 +947,12 @@ export function isVersionComparisonDisplayUsingContextTools(vp: Viewport): boole
926947

927948
export function updateVersionCompareDisplayEntries(
928949
vp: Viewport,
929-
entries: ChangedElementEntry[],
950+
visibleEntries: ChangedElementEntry[],
951+
hiddenEntries: ChangedElementEntry[] | undefined,
930952
): boolean {
931953
const existing = vp.findFeatureOverrideProviderOfType(Provider);
932954
if (undefined !== existing && existing instanceof Provider) {
933-
existing.setChangedElems(entries);
955+
existing.setChangedElems(visibleEntries, hiddenEntries);
934956
return true;
935957
}
936958

packages/changed-elements-react/src/api/VersionCompareVisualization.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,17 @@ export class VersionCompareVisualizationManager {
102102

103103
/**
104104
* Used to emphasize and focus on a list of elements instead of all changed elements in comparison
105-
* @param elements Elements to focus during visualization
105+
* @param visible Elements to focus during visualization
106+
* @param hiddenElements Optional list of elements that should not be shown in the visualization
106107
*/
107-
public setFocusedElements = async (elements: ChangedElementEntry[] | undefined) => {
108-
this._focusedElements = elements;
108+
public setFocusedElements = async (visible: ChangedElementEntry[] | undefined, hiddenElements?: ChangedElementEntry[]) => {
109+
this._focusedElements = visible;
109110
updateVersionCompareDisplayEntries(
110111
this._viewport,
111112
this._focusedElements !== undefined
112113
? this._focusedElements
113114
: this._changedElements,
115+
hiddenElements,
114116
);
115117
};
116118

@@ -288,6 +290,7 @@ export class VersionCompareVisualizationManager {
288290
public async toggleUnchangedVisibility(hide?: boolean): Promise<boolean> {
289291
this.displayOptions.hideUnchanged =
290292
hide !== undefined ? hide : !this.displayOptions.hideUnchanged;
293+
291294
this.displayOptions.changedModels = this._changedModels;
292295
this.displayOptions.emphasized = true;
293296

packages/changed-elements-react/src/widgets/EnhancedElementsInspector.tsx

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -810,26 +810,48 @@ export class ChangedElementsListComponent extends Component<ChangedElementsListP
810810
return this.props.dataProvider.getEntriesFromIds(ids);
811811
};
812812

813-
/** Returns true it state's filterOptions matches the entry. */
814-
private _filterEntryWithOptions = (entry: ChangedElementEntry): boolean => {
815-
return this._filterEntryWithGivenOptions(entry, this.state.filterOptions);
816-
};
813+
/** return true for entries we want to hide */
814+
private _hideFilter = (entry: ChangedElementEntry, opts: FilterOptions): boolean => {
815+
if (entry.opcode === DbOpcode.Update) {
816+
// hide when the filter options do not want modified elements
817+
if (!opts.wantModified) {
818+
return true;
819+
}
820+
// hide when type of change filtering or advanced filtering is matched
821+
if (!this._modifiedEntryMatchesFilters(entry, opts)){
822+
return true;
823+
}
824+
}
817825

818-
/** Returns true if filterOptions matches the entry. */
819-
private _filterEntryWithGivenOptions = (entry: ChangedElementEntry, opts: FilterOptions): boolean => {
820-
return this._wantShowEntry(entry, opts);
821-
};
826+
// hide when the filter options do not want added elements
827+
if (entry.opcode === DbOpcode.Insert && !opts.wantAdded) {
828+
return true;
829+
}
830+
return false;
831+
}
832+
833+
/** return true for entries we want to include */
834+
private _includeFilter = (entry: ChangedElementEntry, opts: FilterOptions): boolean => {
835+
if (entry.opcode === DbOpcode.Delete){
836+
return opts.wantDeleted;
837+
}
838+
return true;
839+
}
822840

823841
/** Obtains the children nodes of the models, creates their entries, and visualizes them. */
824842
private _visualizeModelNodes = async (nodes: TreeNodeItem[], options?: FilterOptions): Promise<void> => {
825843
// Handle model nodes: Get the children entries they already have into an array
826-
const filter = options
827-
? (entry: ChangedElementEntry) => this._filterEntryWithGivenOptions(entry, options)
828-
: this._filterEntryWithOptions;
829844
const modelIds = new Set(nodes.map((value) => value.id));
830-
const entries = this.props.dataProvider.getEntriesWithModelIds(modelIds, filter);
845+
const opts = options ?? this.state.filterOptions;
846+
847+
const includeFilter = (e: ChangedElementEntry) => this._includeFilter(e, opts);
848+
const hideFilter = (e: ChangedElementEntry) => this._hideFilter(e, opts);
849+
850+
const { visible, hidden } =
851+
this.props.dataProvider.GetEntriesByModelIdsAndFilters(modelIds, includeFilter, hideFilter);
852+
831853
const visualizationManager = this.props.manager.visualization?.getSingleViewVisualizationManager();
832-
await visualizationManager?.setFocusedElements(entries);
854+
await visualizationManager?.setFocusedElements(visible, hidden);
833855
};
834856

835857
/**
@@ -856,15 +878,15 @@ export class ChangedElementsListComponent extends Component<ChangedElementsListP
856878
): Promise<void> => {
857879
// Get entries to visualize containing the relevant children entries as well
858880
const entries = targetNode ? this._getEntriesToVisualize(targetNode) : directChildNodes.map(nodeToEntry);
881+
const opts = options ?? this.state.filterOptions;
859882

860883
// Filter function for matching to the given filter options
861-
const filterFunc = options
862-
? (entry: ChangedElementEntry) => this._filterEntryWithGivenOptions(entry, options)
863-
: this._filterEntryWithOptions;
884+
const includeFilter = (e: ChangedElementEntry) => this._includeFilter(e, opts) && !this._hideFilter(e, opts);
885+
const hideFilter = (e: ChangedElementEntry) => this._hideFilter(e, opts);
864886

865887
// Visualize the filtered elements and focus
866888
const visualizationManager = this.props.manager.visualization?.getSingleViewVisualizationManager();
867-
await visualizationManager?.setFocusedElements(entries.filter(filterFunc));
889+
await visualizationManager?.setFocusedElements(entries.filter(includeFilter), entries.filter(hideFilter));
868890
};
869891

870892
/** Sets viewport visualization based on the given nodes and target/parent node. */

0 commit comments

Comments
 (0)