@itwin/presentation-frontend 3.6.0-dev.14 → 3.6.0-dev.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Change Log - @itwin/presentation-frontend
2
2
 
3
- This log was last generated on Wed, 07 Dec 2022 19:12:37 GMT and should not be manually modified.
3
+ This log was last generated on Thu, 15 Dec 2022 16:38:29 GMT and should not be manually modified.
4
+
5
+ ## 3.5.1
6
+ Thu, 15 Dec 2022 16:38:29 GMT
7
+
8
+ _Version update only_
4
9
 
5
10
  ## 3.5.0
6
11
  Wed, 07 Dec 2022 19:12:37 GMT
@@ -1 +1 @@
1
- {"version":3,"file":"IpcRequestsHandler.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,OAAO,EAAwE,eAAe,EAAuB,wBAAwB,EAC7I,0BAA0B,EAAE,0BAA0B,EACvD,MAAM,4BAA4B,CAAC;AAEpC,gBAAgB;AAChB,qBAAa,kBAAkB;IAC7B,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,QAAQ,EAAE,MAAM;YAId,IAAI;IAIL,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,wBAAwB,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC;IAStF,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,EAAE,UAAU,CAAC;IAQzE,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;CAQhG"}
1
+ {"version":3,"file":"IpcRequestsHandler.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,OAAO,EAAwE,eAAe,EAAuB,wBAAwB,EAC7I,0BAA0B,EAAE,0BAA0B,EACvD,MAAM,4BAA4B,CAAC;AAEpC,gBAAgB;AAChB,qBAAa,kBAAkB;IAC7B,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,QAAQ,EAAE,MAAM;YAId,IAAI;IAIL,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,wBAAwB,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC;IAStF,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,EAAE,UAAU,CAAC;IAQzE,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;CAWhG"}
@@ -34,7 +34,10 @@ class IpcRequestsHandler {
34
34
  const jsonParams = {
35
35
  ...params,
36
36
  clientId: this.clientId,
37
- nodeKeys: params.nodeKeys.map(presentation_common_1.NodeKey.toJSON),
37
+ stateChanges: params.stateChanges.map((sc) => ({
38
+ ...sc,
39
+ ...(sc.nodeKey ? { nodeKey: presentation_common_1.NodeKey.toJSON(sc.nodeKey) } : undefined),
40
+ })),
38
41
  };
39
42
  return this.call("updateHierarchyState", jsonParams);
40
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IpcRequestsHandler.js","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":";AAAA;;;+FAG+F;;;AAG/F,wDAA8C;AAC9C,oEAGoC;AAEpC,gBAAgB;AAChB,MAAa,kBAAkB;IAG7B,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,IAAI,CAAqD,UAAa,EAAE,GAAG,IAA6C;QACpI,OAAO,sBAAM,CAAC,cAAc,CAAC,mDAA6B,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC;IACnF,CAAC;IAEM,KAAK,CAAC,kBAAkB,CAAC,MAAmE;QACjG,MAAM,UAAU,GAAkD;YAChE,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,qCAAe,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;SAClD,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAAoD;QACpF,MAAM,UAAU,GAA+B;YAC7C,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAA6D;QAC7F,MAAM,UAAU,GAA4C;YAC1D,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,6BAAO,CAAC,MAAM,CAAC;SAC9C,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;CACF;AApCD,gDAoCC","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { AsyncMethodsOf, PromiseReturnType } from \"@itwin/core-bentley\";\r\nimport { IpcApp } from \"@itwin/core-frontend\";\r\nimport {\r\n NodeKey, NodeKeyJSON, PRESENTATION_IPC_CHANNEL_NAME, PresentationIpcInterface, RulesetVariable, RulesetVariableJSON, SetRulesetVariableParams,\r\n UnsetRulesetVariableParams, UpdateHierarchyStateParams,\r\n} from \"@itwin/presentation-common\";\r\n\r\n/** @internal */\r\nexport class IpcRequestsHandler {\r\n public readonly clientId: string;\r\n\r\n constructor(clientId: string) {\r\n this.clientId = clientId;\r\n }\r\n\r\n private async call<T extends AsyncMethodsOf<PresentationIpcInterface>>(methodName: T, ...args: Parameters<PresentationIpcInterface[T]>): Promise<PromiseReturnType<PresentationIpcInterface[T]>> {\r\n return IpcApp.callIpcChannel(PRESENTATION_IPC_CHANNEL_NAME, methodName, ...args);\r\n }\r\n\r\n public async setRulesetVariable(params: Omit<SetRulesetVariableParams<RulesetVariable>, \"clientId\">) {\r\n const jsonParams: SetRulesetVariableParams<RulesetVariableJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n variable: RulesetVariable.toJSON(params.variable),\r\n };\r\n return this.call(\"setRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async unsetRulesetVariable(params: Omit<UnsetRulesetVariableParams, \"clientId\">) {\r\n const jsonParams: UnsetRulesetVariableParams = {\r\n ...params,\r\n clientId: this.clientId,\r\n };\r\n return this.call(\"unsetRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async updateHierarchyState(params: Omit<UpdateHierarchyStateParams<NodeKey>, \"clientId\">) {\r\n const jsonParams: UpdateHierarchyStateParams<NodeKeyJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n nodeKeys: params.nodeKeys.map(NodeKey.toJSON),\r\n };\r\n return this.call(\"updateHierarchyState\", jsonParams);\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"IpcRequestsHandler.js","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":";AAAA;;;+FAG+F;;;AAG/F,wDAA8C;AAC9C,oEAGoC;AAEpC,gBAAgB;AAChB,MAAa,kBAAkB;IAG7B,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,IAAI,CAAqD,UAAa,EAAE,GAAG,IAA6C;QACpI,OAAO,sBAAM,CAAC,cAAc,CAAC,mDAA6B,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC;IACnF,CAAC;IAEM,KAAK,CAAC,kBAAkB,CAAC,MAAmE;QACjG,MAAM,UAAU,GAAkD;YAChE,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,qCAAe,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;SAClD,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAAoD;QACpF,MAAM,UAAU,GAA+B;YAC7C,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAA6D;QAC7F,MAAM,UAAU,GAA4C;YAC1D,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC7C,GAAG,EAAE;gBACL,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,6BAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;aACtE,CAAC,CAAC;SACJ,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;CACF;AAvCD,gDAuCC","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { AsyncMethodsOf, PromiseReturnType } from \"@itwin/core-bentley\";\r\nimport { IpcApp } from \"@itwin/core-frontend\";\r\nimport {\r\n NodeKey, NodeKeyJSON, PRESENTATION_IPC_CHANNEL_NAME, PresentationIpcInterface, RulesetVariable, RulesetVariableJSON, SetRulesetVariableParams,\r\n UnsetRulesetVariableParams, UpdateHierarchyStateParams,\r\n} from \"@itwin/presentation-common\";\r\n\r\n/** @internal */\r\nexport class IpcRequestsHandler {\r\n public readonly clientId: string;\r\n\r\n constructor(clientId: string) {\r\n this.clientId = clientId;\r\n }\r\n\r\n private async call<T extends AsyncMethodsOf<PresentationIpcInterface>>(methodName: T, ...args: Parameters<PresentationIpcInterface[T]>): Promise<PromiseReturnType<PresentationIpcInterface[T]>> {\r\n return IpcApp.callIpcChannel(PRESENTATION_IPC_CHANNEL_NAME, methodName, ...args);\r\n }\r\n\r\n public async setRulesetVariable(params: Omit<SetRulesetVariableParams<RulesetVariable>, \"clientId\">) {\r\n const jsonParams: SetRulesetVariableParams<RulesetVariableJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n variable: RulesetVariable.toJSON(params.variable),\r\n };\r\n return this.call(\"setRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async unsetRulesetVariable(params: Omit<UnsetRulesetVariableParams, \"clientId\">) {\r\n const jsonParams: UnsetRulesetVariableParams = {\r\n ...params,\r\n clientId: this.clientId,\r\n };\r\n return this.call(\"unsetRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async updateHierarchyState(params: Omit<UpdateHierarchyStateParams<NodeKey>, \"clientId\">) {\r\n const jsonParams: UpdateHierarchyStateParams<NodeKeyJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n stateChanges: params.stateChanges.map((sc) => ({\r\n ...sc,\r\n ...(sc.nodeKey ? { nodeKey: NodeKey.toJSON(sc.nodeKey) } : undefined),\r\n })),\r\n };\r\n return this.call(\"updateHierarchyState\", jsonParams);\r\n }\r\n}\r\n"]}
@@ -12,13 +12,28 @@ export interface NodeIdentifier {
12
12
  id: string;
13
13
  key: NodeKey;
14
14
  }
15
- /** @internal */
15
+ /**
16
+ * @internal
17
+ */
18
+ export interface NodeState {
19
+ isExpanded?: boolean;
20
+ instanceFilter?: string;
21
+ }
22
+ /**
23
+ * The tracker stores up-to-date UI state of the hierarchies on the frontend and reports
24
+ * just the state changes to the backend as soon as component sends in a new hierarchy state.
25
+ *
26
+ * @internal
27
+ */
16
28
  export declare class StateTracker {
17
- private _expandedHierarchies;
29
+ private _hierarchyStates;
18
30
  private _ipcRequestsHandler;
19
31
  constructor(ipcRequestsHandler: IpcRequestsHandler);
20
32
  private updateHierarchyStateIfNeeded;
21
33
  onHierarchyClosed(imodel: IModelConnection, rulesetId: string, sourceId: string): Promise<void>;
22
- onExpandedNodesChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, expandedNodes: NodeIdentifier[]): Promise<void>;
34
+ onHierarchyStateChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, newHierarchyState: Array<{
35
+ node: NodeIdentifier | undefined;
36
+ state: NodeState;
37
+ }>): Promise<void>;
23
38
  }
24
39
  //# sourceMappingURL=StateTracker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateTracker.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,OAAO,CAAC;CACd;AAcD,gBAAgB;AAChB,qBAAa,YAAY;IACvB,OAAO,CAAC,oBAAoB,CAAiC;IAC7D,OAAO,CAAC,mBAAmB,CAAqB;gBAEpC,kBAAkB,EAAE,kBAAkB;YAKpC,4BAA4B;IAM7B,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAsB/E,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE;CAwCnI"}
1
+ {"version":3,"file":"StateTracker.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":"AAIA;;GAEG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,OAAO,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAgBD;;;;;GAKG;AACH,qBAAa,YAAY;IAEvB,OAAO,CAAC,gBAAgB,CAAwD;IAChF,OAAO,CAAC,mBAAmB,CAAqB;gBAEpC,kBAAkB,EAAE,kBAAkB;YAKpC,4BAA4B;IAM7B,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAmB/E,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;CA+DrL"}
@@ -8,68 +8,123 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.StateTracker = void 0;
11
- /** @internal */
11
+ const core_bentley_1 = require("@itwin/core-bentley");
12
+ /**
13
+ * The tracker stores up-to-date UI state of the hierarchies on the frontend and reports
14
+ * just the state changes to the backend as soon as component sends in a new hierarchy state.
15
+ *
16
+ * @internal
17
+ */
12
18
  class StateTracker {
13
19
  constructor(ipcRequestsHandler) {
14
20
  this._ipcRequestsHandler = ipcRequestsHandler;
15
- this._expandedHierarchies = new Map();
21
+ this._hierarchyStates = new Map();
16
22
  }
17
- async updateHierarchyStateIfNeeded(imodelKey, rulesetId, changeType, nodeKeys) {
18
- if (nodeKeys.length === 0)
23
+ async updateHierarchyStateIfNeeded(imodelKey, rulesetId, stateChanges) {
24
+ if (stateChanges.length === 0)
19
25
  return;
20
- await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, changeType, nodeKeys });
26
+ await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, stateChanges });
21
27
  }
22
28
  async onHierarchyClosed(imodel, rulesetId, sourceId) {
23
- const hierarchy = this._expandedHierarchies.get(rulesetId);
24
- if (!hierarchy)
29
+ const hierarchyState = this._hierarchyStates.get(rulesetId);
30
+ if (!hierarchyState)
25
31
  return;
26
- const removedKeys = [];
27
- for (const [nodeId, expandedNode] of hierarchy) {
28
- expandedNode.expandedIn.delete(sourceId);
29
- // if there are other sources that have this node expanded leave it.
30
- if (expandedNode.expandedIn.size !== 0)
31
- continue;
32
- hierarchy.delete(nodeId);
33
- removedKeys.push(expandedNode.key);
34
- }
35
- if (hierarchy.size === 0)
36
- this._expandedHierarchies.delete(rulesetId);
37
- await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, "nodesCollapsed", removedKeys);
32
+ const stateChanges = [];
33
+ hierarchyState.forEach((entry) => {
34
+ if (!entry.states.has(sourceId)) {
35
+ // the node has no state for this source - nothing to do
36
+ return;
37
+ }
38
+ (0, core_bentley_1.using)(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {
39
+ entry.states.delete(sourceId);
40
+ });
41
+ });
42
+ await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);
38
43
  }
39
- async onExpandedNodesChanged(imodel, rulesetId, sourceId, expandedNodes) {
40
- let hierarchy = this._expandedHierarchies.get(rulesetId);
41
- if (expandedNodes.length === 0 && !hierarchy)
42
- return;
43
- if (!hierarchy) {
44
- hierarchy = new Map();
45
- this._expandedHierarchies.set(rulesetId, hierarchy);
44
+ async onHierarchyStateChanged(imodel, rulesetId, sourceId, newHierarchyState) {
45
+ let hierarchyState = this._hierarchyStates.get(rulesetId);
46
+ if (!hierarchyState) {
47
+ if (newHierarchyState.length === 0)
48
+ return;
49
+ hierarchyState = new Map();
50
+ this._hierarchyStates.set(rulesetId, hierarchyState);
46
51
  }
47
- const removedKeys = [];
48
- const addedKeys = [];
49
- for (const [key, existingNode] of hierarchy) {
50
- // existing node is in new expanded nodes list. Add current source
51
- if (expandedNodes.find((expandedNode) => expandedNode.id === key)) {
52
- existingNode.expandedIn.add(sourceId);
53
- continue;
52
+ const handledNodeIds = new Set();
53
+ const stateChanges = [];
54
+ // step 1: walk over new state and report all changes
55
+ newHierarchyState.forEach(({ node, state }) => {
56
+ const nodeId = node === null || node === void 0 ? void 0 : node.id;
57
+ const nodeKey = node === null || node === void 0 ? void 0 : node.key;
58
+ const existingNodeEntry = hierarchyState.get(nodeId);
59
+ if (existingNodeEntry) {
60
+ (0, core_bentley_1.using)(new MergedNodeStateChangeReporter(existingNodeEntry, stateChanges), (_) => {
61
+ existingNodeEntry.states.set(sourceId, state);
62
+ });
54
63
  }
55
- // node was not found in expanded nodes list. Remove current source
56
- existingNode.expandedIn.delete(sourceId);
57
- if (existingNode.expandedIn.size !== 0)
58
- continue;
59
- removedKeys.push(existingNode.key);
60
- hierarchy.delete(key);
61
- }
62
- // add any new nodes that were not in expanded nodes hierarchy already
63
- for (const expandedNode of expandedNodes) {
64
- const existingNode = hierarchy.get(expandedNode.id);
65
- if (existingNode)
66
- continue;
67
- hierarchy.set(expandedNode.id, { key: expandedNode.key, expandedIn: new Set([sourceId]) });
68
- addedKeys.push(expandedNode.key);
64
+ else {
65
+ hierarchyState.set(nodeId, { key: nodeKey, states: new Map([[sourceId, state]]) });
66
+ stateChanges.push({ ...calculateMergedNodeState([state].values()), nodeKey });
67
+ }
68
+ handledNodeIds.add(nodeId);
69
+ });
70
+ // step 2: walk over old state and remove all state that's not in the new state
71
+ const erasedNodeIds = new Set();
72
+ hierarchyState.forEach((entry, nodeId) => {
73
+ if (handledNodeIds.has(nodeId)) {
74
+ // the node was handled with the new state - nothing to do here
75
+ return;
76
+ }
77
+ if (!entry.states.has(sourceId)) {
78
+ // the node had no state for this source, so it's not affected by this report
79
+ return;
80
+ }
81
+ (0, core_bentley_1.using)(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {
82
+ entry.states.delete(sourceId);
83
+ });
84
+ // istanbul ignore next
85
+ if (entry.states.size === 0) {
86
+ // there are no more components holding state for this node
87
+ erasedNodeIds.add(nodeId);
88
+ }
89
+ });
90
+ // step 3: cleanup erased node ids and possibly the whole hierarchy state
91
+ for (const nodeId of erasedNodeIds) {
92
+ hierarchyState.delete(nodeId);
69
93
  }
70
- await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, "nodesCollapsed", removedKeys);
71
- await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, "nodesExpanded", addedKeys);
94
+ if (hierarchyState.size === 0)
95
+ this._hierarchyStates.delete(rulesetId);
96
+ // finally, report
97
+ await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);
72
98
  }
73
99
  }
74
100
  exports.StateTracker = StateTracker;
101
+ function calculateMergedNodeState(perComponentStates) {
102
+ const merged = {};
103
+ for (const state of perComponentStates) {
104
+ if (state.isExpanded)
105
+ merged.isExpanded = true;
106
+ if (state.instanceFilter) {
107
+ if (!merged.instanceFilters)
108
+ merged.instanceFilters = [state.instanceFilter];
109
+ else if (!merged.instanceFilters.includes(state.instanceFilter))
110
+ merged.instanceFilters.push(state.instanceFilter);
111
+ }
112
+ }
113
+ return merged;
114
+ }
115
+ class MergedNodeStateChangeReporter {
116
+ constructor(entry, outStateChanges) {
117
+ this._entry = entry;
118
+ this._stateBefore = calculateMergedNodeState(this._entry.states.values());
119
+ this._outStateChanges = outStateChanges;
120
+ }
121
+ dispose() {
122
+ var _a, _b, _c, _d;
123
+ const stateAfter = calculateMergedNodeState(this._entry.states.values());
124
+ const expandedFlagsDiffer = !!stateAfter.isExpanded !== !!this._stateBefore.isExpanded;
125
+ const instanceFiltersDiffer = ((_b = (_a = stateAfter.instanceFilters) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) !== ((_d = (_c = this._stateBefore.instanceFilters) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0);
126
+ if (expandedFlagsDiffer || instanceFiltersDiffer)
127
+ this._outStateChanges.push({ ...stateAfter, nodeKey: this._entry.key });
128
+ }
129
+ }
75
130
  //# sourceMappingURL=StateTracker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateTracker.js","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module Core\r\n */\r\n\r\nimport { IModelConnection } from \"@itwin/core-frontend\";\r\nimport { NodeKey } from \"@itwin/presentation-common\";\r\nimport { IpcRequestsHandler } from \"./IpcRequestsHandler\";\r\n\r\n/**\r\n * Data structure that describes information used by [[StateTracker]] to identify node.\r\n * @internal\r\n */\r\nexport interface NodeIdentifier {\r\n id: string;\r\n key: NodeKey;\r\n}\r\n\r\n/**\r\n * Data structure that describes expanded node.\r\n */\r\ninterface ExpandedNode {\r\n key: NodeKey;\r\n /** Set of source ids in which this node is expanded. */\r\n expandedIn: Set<string>;\r\n}\r\n\r\n/** Maps node ids to expanded nodes. */\r\ntype ExpandedHierarchy = Map<string, ExpandedNode>;\r\n\r\n/** @internal */\r\nexport class StateTracker {\r\n private _expandedHierarchies: Map<string, ExpandedHierarchy>;\r\n private _ipcRequestsHandler: IpcRequestsHandler;\r\n\r\n constructor(ipcRequestsHandler: IpcRequestsHandler) {\r\n this._ipcRequestsHandler = ipcRequestsHandler;\r\n this._expandedHierarchies = new Map<string, ExpandedHierarchy>();\r\n }\r\n\r\n private async updateHierarchyStateIfNeeded(imodelKey: string, rulesetId: string, changeType: \"nodesExpanded\" | \"nodesCollapsed\", nodeKeys: NodeKey[]) {\r\n if (nodeKeys.length === 0)\r\n return;\r\n await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, changeType, nodeKeys });\r\n }\r\n\r\n public async onHierarchyClosed(imodel: IModelConnection, rulesetId: string, sourceId: string) {\r\n const hierarchy = this._expandedHierarchies.get(rulesetId);\r\n if (!hierarchy)\r\n return;\r\n\r\n const removedKeys: NodeKey[] = [];\r\n for (const [nodeId, expandedNode] of hierarchy) {\r\n expandedNode.expandedIn.delete(sourceId);\r\n // if there are other sources that have this node expanded leave it.\r\n if (expandedNode.expandedIn.size !== 0)\r\n continue;\r\n\r\n hierarchy.delete(nodeId);\r\n removedKeys.push(expandedNode.key);\r\n }\r\n\r\n if (hierarchy.size === 0)\r\n this._expandedHierarchies.delete(rulesetId);\r\n\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, \"nodesCollapsed\", removedKeys);\r\n }\r\n\r\n public async onExpandedNodesChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, expandedNodes: NodeIdentifier[]) {\r\n let hierarchy = this._expandedHierarchies.get(rulesetId);\r\n if (expandedNodes.length === 0 && !hierarchy)\r\n return;\r\n if (!hierarchy) {\r\n hierarchy = new Map<string, ExpandedNode>();\r\n this._expandedHierarchies.set(rulesetId, hierarchy);\r\n }\r\n\r\n const removedKeys: NodeKey[] = [];\r\n const addedKeys: NodeKey[] = [];\r\n for (const [key, existingNode] of hierarchy) {\r\n // existing node is in new expanded nodes list. Add current source\r\n if (expandedNodes.find((expandedNode) => expandedNode.id === key)) {\r\n existingNode.expandedIn.add(sourceId);\r\n continue;\r\n }\r\n\r\n // node was not found in expanded nodes list. Remove current source\r\n existingNode.expandedIn.delete(sourceId);\r\n if (existingNode.expandedIn.size !== 0)\r\n continue;\r\n\r\n removedKeys.push(existingNode.key);\r\n hierarchy.delete(key);\r\n }\r\n\r\n // add any new nodes that were not in expanded nodes hierarchy already\r\n for (const expandedNode of expandedNodes) {\r\n const existingNode = hierarchy.get(expandedNode.id);\r\n if (existingNode)\r\n continue;\r\n\r\n hierarchy.set(expandedNode.id, { key: expandedNode.key, expandedIn: new Set<string>([sourceId]) });\r\n addedKeys.push(expandedNode.key);\r\n }\r\n\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, \"nodesCollapsed\", removedKeys);\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, \"nodesExpanded\", addedKeys);\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"StateTracker.js","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":";AAAA;;;+FAG+F;AAC/F;;GAEG;;;AAEH,sDAAyD;AAoCzD;;;;;GAKG;AACH,MAAa,YAAY;IAKvB,YAAY,kBAAsC;QAChD,IAAI,CAAC,mBAAmB,GAAG,kBAAkB,CAAC;QAC9C,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG,EAAE,CAAC;IACpC,CAAC;IAEO,KAAK,CAAC,4BAA4B,CAAC,SAAiB,EAAE,SAAiB,EAAE,YAAiC;QAChH,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;YAC3B,OAAO;QACT,MAAM,IAAI,CAAC,mBAAmB,CAAC,oBAAoB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;IAC9F,CAAC;IAEM,KAAK,CAAC,iBAAiB,CAAC,MAAwB,EAAE,SAAiB,EAAE,QAAgB;QAC1F,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,cAAc;YACjB,OAAO;QAET,MAAM,YAAY,GAAwB,EAAE,CAAC;QAC7C,cAAc,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YAC/B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;gBAC/B,wDAAwD;gBACxD,OAAO;aACR;YACD,IAAA,oBAAK,EAAC,IAAI,6BAA6B,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;gBAClE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,4BAA4B,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;IAC/E,CAAC;IAEM,KAAK,CAAC,uBAAuB,CAAC,MAAwB,EAAE,SAAiB,EAAE,QAAgB,EAAE,iBAAgF;QAClL,IAAI,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1D,IAAI,CAAC,cAAc,EAAE;YACnB,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;gBAChC,OAAO;YAET,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;SACtD;QAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAsB,CAAC;QACrD,MAAM,YAAY,GAAwB,EAAE,CAAC;QAE7C,qDAAqD;QACrD,iBAAiB,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;YAC5C,MAAM,MAAM,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG,CAAC;YAC1B,MAAM,iBAAiB,GAAG,cAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACtD,IAAI,iBAAiB,EAAE;gBACrB,IAAA,oBAAK,EAAC,IAAI,6BAA6B,CAAC,iBAAiB,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;oBAC9E,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBAChD,CAAC,CAAC,CAAC;aACJ;iBAAM;gBACL,cAAe,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACpF,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,wBAAwB,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;aAC/E;YACD,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,+EAA+E;QAC/E,MAAM,aAAa,GAAG,IAAI,GAAG,EAAsB,CAAC;QACpD,cAAc,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACvC,IAAI,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;gBAC9B,+DAA+D;gBAC/D,OAAO;aACR;YAED,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;gBAC/B,6EAA6E;gBAC7E,OAAO;aACR;YAED,IAAA,oBAAK,EAAC,IAAI,6BAA6B,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;gBAClE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;YAEH,uBAAuB;YACvB,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE;gBAC3B,2DAA2D;gBAC3D,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;aAC3B;QACH,CAAC,CAAC,CAAC;QAEH,yEAAyE;QACzE,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE;YAClC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;SAC/B;QACD,IAAI,cAAc,CAAC,IAAI,KAAK,CAAC;YAC3B,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAE1C,kBAAkB;QAClB,MAAM,IAAI,CAAC,4BAA4B,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;IAC/E,CAAC;CACF;AAlGD,oCAkGC;AAED,SAAS,wBAAwB,CAAC,kBAA+C;IAC/E,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,kBAAkB,EAAE;QACtC,IAAI,KAAK,CAAC,UAAU;YAClB,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;QAC3B,IAAI,KAAK,CAAC,cAAc,EAAE;YACxB,IAAI,CAAC,MAAM,CAAC,eAAe;gBACzB,MAAM,CAAC,eAAe,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;iBAC7C,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC;gBAC7D,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;SACrD;KACF;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,6BAA6B;IAIjC,YAAmB,KAAsB,EAAE,eAAoC;QAC7E,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,YAAY,GAAG,wBAAwB,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1E,IAAI,CAAC,gBAAgB,GAAG,eAAe,CAAC;IAC1C,CAAC;IACM,OAAO;;QACZ,MAAM,UAAU,GAAG,wBAAwB,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACzE,MAAM,mBAAmB,GAAG,CAAC,CAAC,UAAU,CAAC,UAAU,KAAK,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;QACvF,MAAM,qBAAqB,GAAG,CAAC,MAAA,MAAA,UAAU,CAAC,eAAe,0CAAE,MAAM,mCAAI,CAAC,CAAC,KAAK,CAAC,MAAA,MAAA,IAAI,CAAC,YAAY,CAAC,eAAe,0CAAE,MAAM,mCAAI,CAAC,CAAC,CAAC;QAC7H,IAAI,mBAAmB,IAAI,qBAAqB;YAC9C,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5E,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module Core\r\n */\r\n\r\nimport { IDisposable, using } from \"@itwin/core-bentley\";\r\nimport { IModelConnection } from \"@itwin/core-frontend\";\r\nimport { NodeKey } from \"@itwin/presentation-common\";\r\nimport { IpcRequestsHandler } from \"./IpcRequestsHandler\";\r\n\r\n/**\r\n * Data structure that describes information used by [[StateTracker]] to identify node.\r\n * @internal\r\n */\r\nexport interface NodeIdentifier {\r\n id: string;\r\n key: NodeKey;\r\n}\r\n\r\n/**\r\n * @internal\r\n */\r\nexport interface NodeState {\r\n isExpanded?: boolean;\r\n instanceFilter?: string;\r\n}\r\n\r\ninterface MergedNodeState {\r\n isExpanded?: boolean;\r\n instanceFilters?: string[];\r\n}\r\n\r\ninterface ReportedNodeState extends MergedNodeState {\r\n nodeKey: NodeKey | undefined;\r\n}\r\n\r\ninterface NodeStatesEntry {\r\n key: NodeKey | undefined;\r\n states: Map<string, NodeState>; // per-component node state\r\n}\r\n\r\n/**\r\n * The tracker stores up-to-date UI state of the hierarchies on the frontend and reports\r\n * just the state changes to the backend as soon as component sends in a new hierarchy state.\r\n *\r\n * @internal\r\n */\r\nexport class StateTracker {\r\n // Ruleset ID => Node ID => Node state info\r\n private _hierarchyStates: Map<string, Map<string | undefined, NodeStatesEntry>>;\r\n private _ipcRequestsHandler: IpcRequestsHandler;\r\n\r\n constructor(ipcRequestsHandler: IpcRequestsHandler) {\r\n this._ipcRequestsHandler = ipcRequestsHandler;\r\n this._hierarchyStates = new Map();\r\n }\r\n\r\n private async updateHierarchyStateIfNeeded(imodelKey: string, rulesetId: string, stateChanges: ReportedNodeState[]) {\r\n if (stateChanges.length === 0)\r\n return;\r\n await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, stateChanges });\r\n }\r\n\r\n public async onHierarchyClosed(imodel: IModelConnection, rulesetId: string, sourceId: string) {\r\n const hierarchyState = this._hierarchyStates.get(rulesetId);\r\n if (!hierarchyState)\r\n return;\r\n\r\n const stateChanges: ReportedNodeState[] = [];\r\n hierarchyState.forEach((entry) => {\r\n if (!entry.states.has(sourceId)) {\r\n // the node has no state for this source - nothing to do\r\n return;\r\n }\r\n using(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {\r\n entry.states.delete(sourceId);\r\n });\r\n });\r\n\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);\r\n }\r\n\r\n public async onHierarchyStateChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, newHierarchyState: Array<{ node: NodeIdentifier | undefined, state: NodeState }>) {\r\n let hierarchyState = this._hierarchyStates.get(rulesetId);\r\n if (!hierarchyState) {\r\n if (newHierarchyState.length === 0)\r\n return;\r\n\r\n hierarchyState = new Map();\r\n this._hierarchyStates.set(rulesetId, hierarchyState);\r\n }\r\n\r\n const handledNodeIds = new Set<string | undefined>();\r\n const stateChanges: ReportedNodeState[] = [];\r\n\r\n // step 1: walk over new state and report all changes\r\n newHierarchyState.forEach(({ node, state }) => {\r\n const nodeId = node?.id;\r\n const nodeKey = node?.key;\r\n const existingNodeEntry = hierarchyState!.get(nodeId);\r\n if (existingNodeEntry) {\r\n using(new MergedNodeStateChangeReporter(existingNodeEntry, stateChanges), (_) => {\r\n existingNodeEntry.states.set(sourceId, state);\r\n });\r\n } else {\r\n hierarchyState!.set(nodeId, { key: nodeKey, states: new Map([[sourceId, state]]) });\r\n stateChanges.push({ ...calculateMergedNodeState([state].values()), nodeKey });\r\n }\r\n handledNodeIds.add(nodeId);\r\n });\r\n\r\n // step 2: walk over old state and remove all state that's not in the new state\r\n const erasedNodeIds = new Set<string | undefined>();\r\n hierarchyState.forEach((entry, nodeId) => {\r\n if (handledNodeIds.has(nodeId)) {\r\n // the node was handled with the new state - nothing to do here\r\n return;\r\n }\r\n\r\n if (!entry.states.has(sourceId)) {\r\n // the node had no state for this source, so it's not affected by this report\r\n return;\r\n }\r\n\r\n using(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {\r\n entry.states.delete(sourceId);\r\n });\r\n\r\n // istanbul ignore next\r\n if (entry.states.size === 0) {\r\n // there are no more components holding state for this node\r\n erasedNodeIds.add(nodeId);\r\n }\r\n });\r\n\r\n // step 3: cleanup erased node ids and possibly the whole hierarchy state\r\n for (const nodeId of erasedNodeIds) {\r\n hierarchyState.delete(nodeId);\r\n }\r\n if (hierarchyState.size === 0)\r\n this._hierarchyStates.delete(rulesetId);\r\n\r\n // finally, report\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);\r\n }\r\n}\r\n\r\nfunction calculateMergedNodeState(perComponentStates: IterableIterator<NodeState>): MergedNodeState {\r\n const merged: MergedNodeState = {};\r\n for (const state of perComponentStates) {\r\n if (state.isExpanded)\r\n merged.isExpanded = true;\r\n if (state.instanceFilter) {\r\n if (!merged.instanceFilters)\r\n merged.instanceFilters = [state.instanceFilter];\r\n else if (!merged.instanceFilters.includes(state.instanceFilter))\r\n merged.instanceFilters.push(state.instanceFilter);\r\n }\r\n }\r\n return merged;\r\n}\r\n\r\nclass MergedNodeStateChangeReporter implements IDisposable {\r\n private _entry: NodeStatesEntry;\r\n private _stateBefore: MergedNodeState;\r\n private _outStateChanges: ReportedNodeState[];\r\n public constructor(entry: NodeStatesEntry, outStateChanges: ReportedNodeState[]) {\r\n this._entry = entry;\r\n this._stateBefore = calculateMergedNodeState(this._entry.states.values());\r\n this._outStateChanges = outStateChanges;\r\n }\r\n public dispose() {\r\n const stateAfter = calculateMergedNodeState(this._entry.states.values());\r\n const expandedFlagsDiffer = !!stateAfter.isExpanded !== !!this._stateBefore.isExpanded;\r\n const instanceFiltersDiffer = (stateAfter.instanceFilters?.length ?? 0) !== (this._stateBefore.instanceFilters?.length ?? 0);\r\n if (expandedFlagsDiffer || instanceFiltersDiffer)\r\n this._outStateChanges.push({ ...stateAfter, nodeKey: this._entry.key });\r\n }\r\n}\r\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"IpcRequestsHandler.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,OAAO,EAAwE,eAAe,EAAuB,wBAAwB,EAC7I,0BAA0B,EAAE,0BAA0B,EACvD,MAAM,4BAA4B,CAAC;AAEpC,gBAAgB;AAChB,qBAAa,kBAAkB;IAC7B,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,QAAQ,EAAE,MAAM;YAId,IAAI;IAIL,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,wBAAwB,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC;IAStF,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,EAAE,UAAU,CAAC;IAQzE,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;CAQhG"}
1
+ {"version":3,"file":"IpcRequestsHandler.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,OAAO,EAAwE,eAAe,EAAuB,wBAAwB,EAC7I,0BAA0B,EAAE,0BAA0B,EACvD,MAAM,4BAA4B,CAAC;AAEpC,gBAAgB;AAChB,qBAAa,kBAAkB;IAC7B,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,QAAQ,EAAE,MAAM;YAId,IAAI;IAIL,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,wBAAwB,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC;IAStF,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,EAAE,UAAU,CAAC;IAQzE,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;CAWhG"}
@@ -31,7 +31,10 @@ export class IpcRequestsHandler {
31
31
  const jsonParams = {
32
32
  ...params,
33
33
  clientId: this.clientId,
34
- nodeKeys: params.nodeKeys.map(NodeKey.toJSON),
34
+ stateChanges: params.stateChanges.map((sc) => ({
35
+ ...sc,
36
+ ...(sc.nodeKey ? { nodeKey: NodeKey.toJSON(sc.nodeKey) } : undefined),
37
+ })),
35
38
  };
36
39
  return this.call("updateHierarchyState", jsonParams);
37
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IpcRequestsHandler.js","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAG/F,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,OAAO,EAAe,6BAA6B,EAA4B,eAAe,GAE/F,MAAM,4BAA4B,CAAC;AAEpC,gBAAgB;AAChB,MAAM,OAAO,kBAAkB;IAG7B,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,IAAI,CAAqD,UAAa,EAAE,GAAG,IAA6C;QACpI,OAAO,MAAM,CAAC,cAAc,CAAC,6BAA6B,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC;IACnF,CAAC;IAEM,KAAK,CAAC,kBAAkB,CAAC,MAAmE;QACjG,MAAM,UAAU,GAAkD;YAChE,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;SAClD,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAAoD;QACpF,MAAM,UAAU,GAA+B;YAC7C,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAA6D;QAC7F,MAAM,UAAU,GAA4C;YAC1D,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;SAC9C,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { AsyncMethodsOf, PromiseReturnType } from \"@itwin/core-bentley\";\r\nimport { IpcApp } from \"@itwin/core-frontend\";\r\nimport {\r\n NodeKey, NodeKeyJSON, PRESENTATION_IPC_CHANNEL_NAME, PresentationIpcInterface, RulesetVariable, RulesetVariableJSON, SetRulesetVariableParams,\r\n UnsetRulesetVariableParams, UpdateHierarchyStateParams,\r\n} from \"@itwin/presentation-common\";\r\n\r\n/** @internal */\r\nexport class IpcRequestsHandler {\r\n public readonly clientId: string;\r\n\r\n constructor(clientId: string) {\r\n this.clientId = clientId;\r\n }\r\n\r\n private async call<T extends AsyncMethodsOf<PresentationIpcInterface>>(methodName: T, ...args: Parameters<PresentationIpcInterface[T]>): Promise<PromiseReturnType<PresentationIpcInterface[T]>> {\r\n return IpcApp.callIpcChannel(PRESENTATION_IPC_CHANNEL_NAME, methodName, ...args);\r\n }\r\n\r\n public async setRulesetVariable(params: Omit<SetRulesetVariableParams<RulesetVariable>, \"clientId\">) {\r\n const jsonParams: SetRulesetVariableParams<RulesetVariableJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n variable: RulesetVariable.toJSON(params.variable),\r\n };\r\n return this.call(\"setRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async unsetRulesetVariable(params: Omit<UnsetRulesetVariableParams, \"clientId\">) {\r\n const jsonParams: UnsetRulesetVariableParams = {\r\n ...params,\r\n clientId: this.clientId,\r\n };\r\n return this.call(\"unsetRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async updateHierarchyState(params: Omit<UpdateHierarchyStateParams<NodeKey>, \"clientId\">) {\r\n const jsonParams: UpdateHierarchyStateParams<NodeKeyJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n nodeKeys: params.nodeKeys.map(NodeKey.toJSON),\r\n };\r\n return this.call(\"updateHierarchyState\", jsonParams);\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"IpcRequestsHandler.js","sourceRoot":"","sources":["../../../src/presentation-frontend/IpcRequestsHandler.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAG/F,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,OAAO,EAAe,6BAA6B,EAA4B,eAAe,GAE/F,MAAM,4BAA4B,CAAC;AAEpC,gBAAgB;AAChB,MAAM,OAAO,kBAAkB;IAG7B,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,IAAI,CAAqD,UAAa,EAAE,GAAG,IAA6C;QACpI,OAAO,MAAM,CAAC,cAAc,CAAC,6BAA6B,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC;IACnF,CAAC;IAEM,KAAK,CAAC,kBAAkB,CAAC,MAAmE;QACjG,MAAM,UAAU,GAAkD;YAChE,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;SAClD,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAAoD;QACpF,MAAM,UAAU,GAA+B;YAC7C,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,MAA6D;QAC7F,MAAM,UAAU,GAA4C;YAC1D,GAAG,MAAM;YACT,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC7C,GAAG,EAAE;gBACL,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;aACtE,CAAC,CAAC;SACJ,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { AsyncMethodsOf, PromiseReturnType } from \"@itwin/core-bentley\";\r\nimport { IpcApp } from \"@itwin/core-frontend\";\r\nimport {\r\n NodeKey, NodeKeyJSON, PRESENTATION_IPC_CHANNEL_NAME, PresentationIpcInterface, RulesetVariable, RulesetVariableJSON, SetRulesetVariableParams,\r\n UnsetRulesetVariableParams, UpdateHierarchyStateParams,\r\n} from \"@itwin/presentation-common\";\r\n\r\n/** @internal */\r\nexport class IpcRequestsHandler {\r\n public readonly clientId: string;\r\n\r\n constructor(clientId: string) {\r\n this.clientId = clientId;\r\n }\r\n\r\n private async call<T extends AsyncMethodsOf<PresentationIpcInterface>>(methodName: T, ...args: Parameters<PresentationIpcInterface[T]>): Promise<PromiseReturnType<PresentationIpcInterface[T]>> {\r\n return IpcApp.callIpcChannel(PRESENTATION_IPC_CHANNEL_NAME, methodName, ...args);\r\n }\r\n\r\n public async setRulesetVariable(params: Omit<SetRulesetVariableParams<RulesetVariable>, \"clientId\">) {\r\n const jsonParams: SetRulesetVariableParams<RulesetVariableJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n variable: RulesetVariable.toJSON(params.variable),\r\n };\r\n return this.call(\"setRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async unsetRulesetVariable(params: Omit<UnsetRulesetVariableParams, \"clientId\">) {\r\n const jsonParams: UnsetRulesetVariableParams = {\r\n ...params,\r\n clientId: this.clientId,\r\n };\r\n return this.call(\"unsetRulesetVariable\", jsonParams);\r\n }\r\n\r\n public async updateHierarchyState(params: Omit<UpdateHierarchyStateParams<NodeKey>, \"clientId\">) {\r\n const jsonParams: UpdateHierarchyStateParams<NodeKeyJSON> = {\r\n ...params,\r\n clientId: this.clientId,\r\n stateChanges: params.stateChanges.map((sc) => ({\r\n ...sc,\r\n ...(sc.nodeKey ? { nodeKey: NodeKey.toJSON(sc.nodeKey) } : undefined),\r\n })),\r\n };\r\n return this.call(\"updateHierarchyState\", jsonParams);\r\n }\r\n}\r\n"]}
@@ -12,13 +12,28 @@ export interface NodeIdentifier {
12
12
  id: string;
13
13
  key: NodeKey;
14
14
  }
15
- /** @internal */
15
+ /**
16
+ * @internal
17
+ */
18
+ export interface NodeState {
19
+ isExpanded?: boolean;
20
+ instanceFilter?: string;
21
+ }
22
+ /**
23
+ * The tracker stores up-to-date UI state of the hierarchies on the frontend and reports
24
+ * just the state changes to the backend as soon as component sends in a new hierarchy state.
25
+ *
26
+ * @internal
27
+ */
16
28
  export declare class StateTracker {
17
- private _expandedHierarchies;
29
+ private _hierarchyStates;
18
30
  private _ipcRequestsHandler;
19
31
  constructor(ipcRequestsHandler: IpcRequestsHandler);
20
32
  private updateHierarchyStateIfNeeded;
21
33
  onHierarchyClosed(imodel: IModelConnection, rulesetId: string, sourceId: string): Promise<void>;
22
- onExpandedNodesChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, expandedNodes: NodeIdentifier[]): Promise<void>;
34
+ onHierarchyStateChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, newHierarchyState: Array<{
35
+ node: NodeIdentifier | undefined;
36
+ state: NodeState;
37
+ }>): Promise<void>;
23
38
  }
24
39
  //# sourceMappingURL=StateTracker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateTracker.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,OAAO,CAAC;CACd;AAcD,gBAAgB;AAChB,qBAAa,YAAY;IACvB,OAAO,CAAC,oBAAoB,CAAiC;IAC7D,OAAO,CAAC,mBAAmB,CAAqB;gBAEpC,kBAAkB,EAAE,kBAAkB;YAKpC,4BAA4B;IAM7B,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAsB/E,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE;CAwCnI"}
1
+ {"version":3,"file":"StateTracker.d.ts","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":"AAIA;;GAEG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,OAAO,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAgBD;;;;;GAKG;AACH,qBAAa,YAAY;IAEvB,OAAO,CAAC,gBAAgB,CAAwD;IAChF,OAAO,CAAC,mBAAmB,CAAqB;gBAEpC,kBAAkB,EAAE,kBAAkB;YAKpC,4BAA4B;IAM7B,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAmB/E,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;CA+DrL"}
@@ -5,67 +5,122 @@
5
5
  /** @packageDocumentation
6
6
  * @module Core
7
7
  */
8
- /** @internal */
8
+ import { using } from "@itwin/core-bentley";
9
+ /**
10
+ * The tracker stores up-to-date UI state of the hierarchies on the frontend and reports
11
+ * just the state changes to the backend as soon as component sends in a new hierarchy state.
12
+ *
13
+ * @internal
14
+ */
9
15
  export class StateTracker {
10
16
  constructor(ipcRequestsHandler) {
11
17
  this._ipcRequestsHandler = ipcRequestsHandler;
12
- this._expandedHierarchies = new Map();
18
+ this._hierarchyStates = new Map();
13
19
  }
14
- async updateHierarchyStateIfNeeded(imodelKey, rulesetId, changeType, nodeKeys) {
15
- if (nodeKeys.length === 0)
20
+ async updateHierarchyStateIfNeeded(imodelKey, rulesetId, stateChanges) {
21
+ if (stateChanges.length === 0)
16
22
  return;
17
- await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, changeType, nodeKeys });
23
+ await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, stateChanges });
18
24
  }
19
25
  async onHierarchyClosed(imodel, rulesetId, sourceId) {
20
- const hierarchy = this._expandedHierarchies.get(rulesetId);
21
- if (!hierarchy)
26
+ const hierarchyState = this._hierarchyStates.get(rulesetId);
27
+ if (!hierarchyState)
22
28
  return;
23
- const removedKeys = [];
24
- for (const [nodeId, expandedNode] of hierarchy) {
25
- expandedNode.expandedIn.delete(sourceId);
26
- // if there are other sources that have this node expanded leave it.
27
- if (expandedNode.expandedIn.size !== 0)
28
- continue;
29
- hierarchy.delete(nodeId);
30
- removedKeys.push(expandedNode.key);
31
- }
32
- if (hierarchy.size === 0)
33
- this._expandedHierarchies.delete(rulesetId);
34
- await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, "nodesCollapsed", removedKeys);
29
+ const stateChanges = [];
30
+ hierarchyState.forEach((entry) => {
31
+ if (!entry.states.has(sourceId)) {
32
+ // the node has no state for this source - nothing to do
33
+ return;
34
+ }
35
+ using(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {
36
+ entry.states.delete(sourceId);
37
+ });
38
+ });
39
+ await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);
35
40
  }
36
- async onExpandedNodesChanged(imodel, rulesetId, sourceId, expandedNodes) {
37
- let hierarchy = this._expandedHierarchies.get(rulesetId);
38
- if (expandedNodes.length === 0 && !hierarchy)
39
- return;
40
- if (!hierarchy) {
41
- hierarchy = new Map();
42
- this._expandedHierarchies.set(rulesetId, hierarchy);
41
+ async onHierarchyStateChanged(imodel, rulesetId, sourceId, newHierarchyState) {
42
+ let hierarchyState = this._hierarchyStates.get(rulesetId);
43
+ if (!hierarchyState) {
44
+ if (newHierarchyState.length === 0)
45
+ return;
46
+ hierarchyState = new Map();
47
+ this._hierarchyStates.set(rulesetId, hierarchyState);
43
48
  }
44
- const removedKeys = [];
45
- const addedKeys = [];
46
- for (const [key, existingNode] of hierarchy) {
47
- // existing node is in new expanded nodes list. Add current source
48
- if (expandedNodes.find((expandedNode) => expandedNode.id === key)) {
49
- existingNode.expandedIn.add(sourceId);
50
- continue;
49
+ const handledNodeIds = new Set();
50
+ const stateChanges = [];
51
+ // step 1: walk over new state and report all changes
52
+ newHierarchyState.forEach(({ node, state }) => {
53
+ const nodeId = node === null || node === void 0 ? void 0 : node.id;
54
+ const nodeKey = node === null || node === void 0 ? void 0 : node.key;
55
+ const existingNodeEntry = hierarchyState.get(nodeId);
56
+ if (existingNodeEntry) {
57
+ using(new MergedNodeStateChangeReporter(existingNodeEntry, stateChanges), (_) => {
58
+ existingNodeEntry.states.set(sourceId, state);
59
+ });
60
+ }
61
+ else {
62
+ hierarchyState.set(nodeId, { key: nodeKey, states: new Map([[sourceId, state]]) });
63
+ stateChanges.push({ ...calculateMergedNodeState([state].values()), nodeKey });
64
+ }
65
+ handledNodeIds.add(nodeId);
66
+ });
67
+ // step 2: walk over old state and remove all state that's not in the new state
68
+ const erasedNodeIds = new Set();
69
+ hierarchyState.forEach((entry, nodeId) => {
70
+ if (handledNodeIds.has(nodeId)) {
71
+ // the node was handled with the new state - nothing to do here
72
+ return;
73
+ }
74
+ if (!entry.states.has(sourceId)) {
75
+ // the node had no state for this source, so it's not affected by this report
76
+ return;
51
77
  }
52
- // node was not found in expanded nodes list. Remove current source
53
- existingNode.expandedIn.delete(sourceId);
54
- if (existingNode.expandedIn.size !== 0)
55
- continue;
56
- removedKeys.push(existingNode.key);
57
- hierarchy.delete(key);
78
+ using(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {
79
+ entry.states.delete(sourceId);
80
+ });
81
+ // istanbul ignore next
82
+ if (entry.states.size === 0) {
83
+ // there are no more components holding state for this node
84
+ erasedNodeIds.add(nodeId);
85
+ }
86
+ });
87
+ // step 3: cleanup erased node ids and possibly the whole hierarchy state
88
+ for (const nodeId of erasedNodeIds) {
89
+ hierarchyState.delete(nodeId);
58
90
  }
59
- // add any new nodes that were not in expanded nodes hierarchy already
60
- for (const expandedNode of expandedNodes) {
61
- const existingNode = hierarchy.get(expandedNode.id);
62
- if (existingNode)
63
- continue;
64
- hierarchy.set(expandedNode.id, { key: expandedNode.key, expandedIn: new Set([sourceId]) });
65
- addedKeys.push(expandedNode.key);
91
+ if (hierarchyState.size === 0)
92
+ this._hierarchyStates.delete(rulesetId);
93
+ // finally, report
94
+ await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);
95
+ }
96
+ }
97
+ function calculateMergedNodeState(perComponentStates) {
98
+ const merged = {};
99
+ for (const state of perComponentStates) {
100
+ if (state.isExpanded)
101
+ merged.isExpanded = true;
102
+ if (state.instanceFilter) {
103
+ if (!merged.instanceFilters)
104
+ merged.instanceFilters = [state.instanceFilter];
105
+ else if (!merged.instanceFilters.includes(state.instanceFilter))
106
+ merged.instanceFilters.push(state.instanceFilter);
66
107
  }
67
- await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, "nodesCollapsed", removedKeys);
68
- await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, "nodesExpanded", addedKeys);
108
+ }
109
+ return merged;
110
+ }
111
+ class MergedNodeStateChangeReporter {
112
+ constructor(entry, outStateChanges) {
113
+ this._entry = entry;
114
+ this._stateBefore = calculateMergedNodeState(this._entry.states.values());
115
+ this._outStateChanges = outStateChanges;
116
+ }
117
+ dispose() {
118
+ var _a, _b, _c, _d;
119
+ const stateAfter = calculateMergedNodeState(this._entry.states.values());
120
+ const expandedFlagsDiffer = !!stateAfter.isExpanded !== !!this._stateBefore.isExpanded;
121
+ const instanceFiltersDiffer = ((_b = (_a = stateAfter.instanceFilters) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) !== ((_d = (_c = this._stateBefore.instanceFilters) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0);
122
+ if (expandedFlagsDiffer || instanceFiltersDiffer)
123
+ this._outStateChanges.push({ ...stateAfter, nodeKey: this._entry.key });
69
124
  }
70
125
  }
71
126
  //# sourceMappingURL=StateTracker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateTracker.js","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module Core\r\n */\r\n\r\nimport { IModelConnection } from \"@itwin/core-frontend\";\r\nimport { NodeKey } from \"@itwin/presentation-common\";\r\nimport { IpcRequestsHandler } from \"./IpcRequestsHandler\";\r\n\r\n/**\r\n * Data structure that describes information used by [[StateTracker]] to identify node.\r\n * @internal\r\n */\r\nexport interface NodeIdentifier {\r\n id: string;\r\n key: NodeKey;\r\n}\r\n\r\n/**\r\n * Data structure that describes expanded node.\r\n */\r\ninterface ExpandedNode {\r\n key: NodeKey;\r\n /** Set of source ids in which this node is expanded. */\r\n expandedIn: Set<string>;\r\n}\r\n\r\n/** Maps node ids to expanded nodes. */\r\ntype ExpandedHierarchy = Map<string, ExpandedNode>;\r\n\r\n/** @internal */\r\nexport class StateTracker {\r\n private _expandedHierarchies: Map<string, ExpandedHierarchy>;\r\n private _ipcRequestsHandler: IpcRequestsHandler;\r\n\r\n constructor(ipcRequestsHandler: IpcRequestsHandler) {\r\n this._ipcRequestsHandler = ipcRequestsHandler;\r\n this._expandedHierarchies = new Map<string, ExpandedHierarchy>();\r\n }\r\n\r\n private async updateHierarchyStateIfNeeded(imodelKey: string, rulesetId: string, changeType: \"nodesExpanded\" | \"nodesCollapsed\", nodeKeys: NodeKey[]) {\r\n if (nodeKeys.length === 0)\r\n return;\r\n await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, changeType, nodeKeys });\r\n }\r\n\r\n public async onHierarchyClosed(imodel: IModelConnection, rulesetId: string, sourceId: string) {\r\n const hierarchy = this._expandedHierarchies.get(rulesetId);\r\n if (!hierarchy)\r\n return;\r\n\r\n const removedKeys: NodeKey[] = [];\r\n for (const [nodeId, expandedNode] of hierarchy) {\r\n expandedNode.expandedIn.delete(sourceId);\r\n // if there are other sources that have this node expanded leave it.\r\n if (expandedNode.expandedIn.size !== 0)\r\n continue;\r\n\r\n hierarchy.delete(nodeId);\r\n removedKeys.push(expandedNode.key);\r\n }\r\n\r\n if (hierarchy.size === 0)\r\n this._expandedHierarchies.delete(rulesetId);\r\n\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, \"nodesCollapsed\", removedKeys);\r\n }\r\n\r\n public async onExpandedNodesChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, expandedNodes: NodeIdentifier[]) {\r\n let hierarchy = this._expandedHierarchies.get(rulesetId);\r\n if (expandedNodes.length === 0 && !hierarchy)\r\n return;\r\n if (!hierarchy) {\r\n hierarchy = new Map<string, ExpandedNode>();\r\n this._expandedHierarchies.set(rulesetId, hierarchy);\r\n }\r\n\r\n const removedKeys: NodeKey[] = [];\r\n const addedKeys: NodeKey[] = [];\r\n for (const [key, existingNode] of hierarchy) {\r\n // existing node is in new expanded nodes list. Add current source\r\n if (expandedNodes.find((expandedNode) => expandedNode.id === key)) {\r\n existingNode.expandedIn.add(sourceId);\r\n continue;\r\n }\r\n\r\n // node was not found in expanded nodes list. Remove current source\r\n existingNode.expandedIn.delete(sourceId);\r\n if (existingNode.expandedIn.size !== 0)\r\n continue;\r\n\r\n removedKeys.push(existingNode.key);\r\n hierarchy.delete(key);\r\n }\r\n\r\n // add any new nodes that were not in expanded nodes hierarchy already\r\n for (const expandedNode of expandedNodes) {\r\n const existingNode = hierarchy.get(expandedNode.id);\r\n if (existingNode)\r\n continue;\r\n\r\n hierarchy.set(expandedNode.id, { key: expandedNode.key, expandedIn: new Set<string>([sourceId]) });\r\n addedKeys.push(expandedNode.key);\r\n }\r\n\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, \"nodesCollapsed\", removedKeys);\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, \"nodesExpanded\", addedKeys);\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"StateTracker.js","sourceRoot":"","sources":["../../../src/presentation-frontend/StateTracker.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module Core\r\n */\r\n\r\nimport { IDisposable, using } from \"@itwin/core-bentley\";\r\nimport { IModelConnection } from \"@itwin/core-frontend\";\r\nimport { NodeKey } from \"@itwin/presentation-common\";\r\nimport { IpcRequestsHandler } from \"./IpcRequestsHandler\";\r\n\r\n/**\r\n * Data structure that describes information used by [[StateTracker]] to identify node.\r\n * @internal\r\n */\r\nexport interface NodeIdentifier {\r\n id: string;\r\n key: NodeKey;\r\n}\r\n\r\n/**\r\n * @internal\r\n */\r\nexport interface NodeState {\r\n isExpanded?: boolean;\r\n instanceFilter?: string;\r\n}\r\n\r\ninterface MergedNodeState {\r\n isExpanded?: boolean;\r\n instanceFilters?: string[];\r\n}\r\n\r\ninterface ReportedNodeState extends MergedNodeState {\r\n nodeKey: NodeKey | undefined;\r\n}\r\n\r\ninterface NodeStatesEntry {\r\n key: NodeKey | undefined;\r\n states: Map<string, NodeState>; // per-component node state\r\n}\r\n\r\n/**\r\n * The tracker stores up-to-date UI state of the hierarchies on the frontend and reports\r\n * just the state changes to the backend as soon as component sends in a new hierarchy state.\r\n *\r\n * @internal\r\n */\r\nexport class StateTracker {\r\n // Ruleset ID => Node ID => Node state info\r\n private _hierarchyStates: Map<string, Map<string | undefined, NodeStatesEntry>>;\r\n private _ipcRequestsHandler: IpcRequestsHandler;\r\n\r\n constructor(ipcRequestsHandler: IpcRequestsHandler) {\r\n this._ipcRequestsHandler = ipcRequestsHandler;\r\n this._hierarchyStates = new Map();\r\n }\r\n\r\n private async updateHierarchyStateIfNeeded(imodelKey: string, rulesetId: string, stateChanges: ReportedNodeState[]) {\r\n if (stateChanges.length === 0)\r\n return;\r\n await this._ipcRequestsHandler.updateHierarchyState({ imodelKey, rulesetId, stateChanges });\r\n }\r\n\r\n public async onHierarchyClosed(imodel: IModelConnection, rulesetId: string, sourceId: string) {\r\n const hierarchyState = this._hierarchyStates.get(rulesetId);\r\n if (!hierarchyState)\r\n return;\r\n\r\n const stateChanges: ReportedNodeState[] = [];\r\n hierarchyState.forEach((entry) => {\r\n if (!entry.states.has(sourceId)) {\r\n // the node has no state for this source - nothing to do\r\n return;\r\n }\r\n using(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {\r\n entry.states.delete(sourceId);\r\n });\r\n });\r\n\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);\r\n }\r\n\r\n public async onHierarchyStateChanged(imodel: IModelConnection, rulesetId: string, sourceId: string, newHierarchyState: Array<{ node: NodeIdentifier | undefined, state: NodeState }>) {\r\n let hierarchyState = this._hierarchyStates.get(rulesetId);\r\n if (!hierarchyState) {\r\n if (newHierarchyState.length === 0)\r\n return;\r\n\r\n hierarchyState = new Map();\r\n this._hierarchyStates.set(rulesetId, hierarchyState);\r\n }\r\n\r\n const handledNodeIds = new Set<string | undefined>();\r\n const stateChanges: ReportedNodeState[] = [];\r\n\r\n // step 1: walk over new state and report all changes\r\n newHierarchyState.forEach(({ node, state }) => {\r\n const nodeId = node?.id;\r\n const nodeKey = node?.key;\r\n const existingNodeEntry = hierarchyState!.get(nodeId);\r\n if (existingNodeEntry) {\r\n using(new MergedNodeStateChangeReporter(existingNodeEntry, stateChanges), (_) => {\r\n existingNodeEntry.states.set(sourceId, state);\r\n });\r\n } else {\r\n hierarchyState!.set(nodeId, { key: nodeKey, states: new Map([[sourceId, state]]) });\r\n stateChanges.push({ ...calculateMergedNodeState([state].values()), nodeKey });\r\n }\r\n handledNodeIds.add(nodeId);\r\n });\r\n\r\n // step 2: walk over old state and remove all state that's not in the new state\r\n const erasedNodeIds = new Set<string | undefined>();\r\n hierarchyState.forEach((entry, nodeId) => {\r\n if (handledNodeIds.has(nodeId)) {\r\n // the node was handled with the new state - nothing to do here\r\n return;\r\n }\r\n\r\n if (!entry.states.has(sourceId)) {\r\n // the node had no state for this source, so it's not affected by this report\r\n return;\r\n }\r\n\r\n using(new MergedNodeStateChangeReporter(entry, stateChanges), (_) => {\r\n entry.states.delete(sourceId);\r\n });\r\n\r\n // istanbul ignore next\r\n if (entry.states.size === 0) {\r\n // there are no more components holding state for this node\r\n erasedNodeIds.add(nodeId);\r\n }\r\n });\r\n\r\n // step 3: cleanup erased node ids and possibly the whole hierarchy state\r\n for (const nodeId of erasedNodeIds) {\r\n hierarchyState.delete(nodeId);\r\n }\r\n if (hierarchyState.size === 0)\r\n this._hierarchyStates.delete(rulesetId);\r\n\r\n // finally, report\r\n await this.updateHierarchyStateIfNeeded(imodel.key, rulesetId, stateChanges);\r\n }\r\n}\r\n\r\nfunction calculateMergedNodeState(perComponentStates: IterableIterator<NodeState>): MergedNodeState {\r\n const merged: MergedNodeState = {};\r\n for (const state of perComponentStates) {\r\n if (state.isExpanded)\r\n merged.isExpanded = true;\r\n if (state.instanceFilter) {\r\n if (!merged.instanceFilters)\r\n merged.instanceFilters = [state.instanceFilter];\r\n else if (!merged.instanceFilters.includes(state.instanceFilter))\r\n merged.instanceFilters.push(state.instanceFilter);\r\n }\r\n }\r\n return merged;\r\n}\r\n\r\nclass MergedNodeStateChangeReporter implements IDisposable {\r\n private _entry: NodeStatesEntry;\r\n private _stateBefore: MergedNodeState;\r\n private _outStateChanges: ReportedNodeState[];\r\n public constructor(entry: NodeStatesEntry, outStateChanges: ReportedNodeState[]) {\r\n this._entry = entry;\r\n this._stateBefore = calculateMergedNodeState(this._entry.states.values());\r\n this._outStateChanges = outStateChanges;\r\n }\r\n public dispose() {\r\n const stateAfter = calculateMergedNodeState(this._entry.states.values());\r\n const expandedFlagsDiffer = !!stateAfter.isExpanded !== !!this._stateBefore.isExpanded;\r\n const instanceFiltersDiffer = (stateAfter.instanceFilters?.length ?? 0) !== (this._stateBefore.instanceFilters?.length ?? 0);\r\n if (expandedFlagsDiffer || instanceFiltersDiffer)\r\n this._outStateChanges.push({ ...stateAfter, nodeKey: this._entry.key });\r\n }\r\n}\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/presentation-frontend",
3
- "version": "3.6.0-dev.14",
3
+ "version": "3.6.0-dev.21",
4
4
  "description": "Frontend of iModel.js Presentation library",
5
5
  "main": "lib/cjs/presentation-frontend.js",
6
6
  "module": "lib/esm/presentation-frontend.js",
@@ -22,21 +22,21 @@
22
22
  "url": "http://www.bentley.com"
23
23
  },
24
24
  "peerDependencies": {
25
- "@itwin/core-bentley": "^3.6.0-dev.14",
26
- "@itwin/core-common": "^3.6.0-dev.14",
27
- "@itwin/core-frontend": "^3.6.0-dev.14",
28
- "@itwin/core-quantity": "^3.6.0-dev.14",
29
- "@itwin/presentation-common": "^3.6.0-dev.14"
25
+ "@itwin/core-bentley": "^3.6.0-dev.21",
26
+ "@itwin/core-common": "^3.6.0-dev.21",
27
+ "@itwin/core-frontend": "^3.6.0-dev.21",
28
+ "@itwin/core-quantity": "^3.6.0-dev.21",
29
+ "@itwin/presentation-common": "^3.6.0-dev.21"
30
30
  },
31
31
  "devDependencies": {
32
- "@itwin/build-tools": "3.6.0-dev.14",
33
- "@itwin/core-bentley": "3.6.0-dev.14",
34
- "@itwin/core-common": "3.6.0-dev.14",
35
- "@itwin/core-frontend": "3.6.0-dev.14",
36
- "@itwin/core-i18n": "3.6.0-dev.14",
37
- "@itwin/core-quantity": "3.6.0-dev.14",
38
- "@itwin/eslint-plugin": "3.6.0-dev.14",
39
- "@itwin/presentation-common": "3.6.0-dev.14",
32
+ "@itwin/build-tools": "3.6.0-dev.21",
33
+ "@itwin/core-bentley": "3.6.0-dev.21",
34
+ "@itwin/core-common": "3.6.0-dev.21",
35
+ "@itwin/core-frontend": "3.6.0-dev.21",
36
+ "@itwin/core-i18n": "3.6.0-dev.21",
37
+ "@itwin/core-quantity": "3.6.0-dev.21",
38
+ "@itwin/eslint-plugin": "3.6.0-dev.21",
39
+ "@itwin/presentation-common": "3.6.0-dev.21",
40
40
  "@types/chai": "4.3.1",
41
41
  "@types/chai-as-promised": "^7",
42
42
  "@types/chai-jest-snapshot": "^1.3.0",