@itwin/map-layers 4.0.0-dev.8 → 5.0.0-dev.2

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.
Files changed (203) hide show
  1. package/CHANGELOG.md +746 -848
  2. package/README.md +32 -32
  3. package/lib/cjs/MapLayerPreferences.d.ts +88 -88
  4. package/lib/cjs/MapLayerPreferences.js +311 -312
  5. package/lib/cjs/MapLayerPreferences.js.map +1 -1
  6. package/lib/cjs/map-layers.d.ts +6 -6
  7. package/lib/cjs/map-layers.js +22 -22
  8. package/lib/cjs/mapLayers.d.ts +26 -44
  9. package/lib/cjs/mapLayers.d.ts.map +1 -1
  10. package/lib/cjs/mapLayers.js +33 -63
  11. package/lib/cjs/mapLayers.js.map +1 -1
  12. package/lib/cjs/ui/FeatureInfoUiItemsProvider.d.ts +16 -11
  13. package/lib/cjs/ui/FeatureInfoUiItemsProvider.d.ts.map +1 -1
  14. package/lib/cjs/ui/FeatureInfoUiItemsProvider.js +46 -52
  15. package/lib/cjs/ui/FeatureInfoUiItemsProvider.js.map +1 -1
  16. package/lib/cjs/ui/Interfaces.d.ts +50 -50
  17. package/lib/cjs/ui/Interfaces.d.ts.map +1 -1
  18. package/lib/cjs/ui/Interfaces.js +2 -2
  19. package/lib/cjs/ui/Interfaces.js.map +1 -1
  20. package/lib/cjs/ui/MapFeatureInfoTool.d.ts +13 -13
  21. package/lib/cjs/ui/MapFeatureInfoTool.d.ts.map +1 -1
  22. package/lib/cjs/ui/MapFeatureInfoTool.js +50 -50
  23. package/lib/cjs/ui/MapFeatureInfoTool.js.map +1 -1
  24. package/lib/cjs/ui/MapLayersUiItemsProvider.d.ts +8 -8
  25. package/lib/cjs/ui/MapLayersUiItemsProvider.d.ts.map +1 -1
  26. package/lib/cjs/ui/MapLayersUiItemsProvider.js +35 -38
  27. package/lib/cjs/ui/MapLayersUiItemsProvider.js.map +1 -1
  28. package/lib/cjs/ui/widget/AttachLayerPopupButton.d.ts +13 -14
  29. package/lib/cjs/ui/widget/AttachLayerPopupButton.d.ts.map +1 -1
  30. package/lib/cjs/ui/widget/AttachLayerPopupButton.js +334 -338
  31. package/lib/cjs/ui/widget/AttachLayerPopupButton.js.map +1 -1
  32. package/lib/cjs/ui/widget/BasemapPanel.d.ts +7 -8
  33. package/lib/cjs/ui/widget/BasemapPanel.d.ts.map +1 -1
  34. package/lib/cjs/ui/widget/BasemapPanel.js +151 -156
  35. package/lib/cjs/ui/widget/BasemapPanel.js.map +1 -1
  36. package/lib/cjs/ui/widget/BasemapPanel.scss +87 -87
  37. package/lib/cjs/ui/widget/ConfirmMessageDialog.d.ts +20 -21
  38. package/lib/cjs/ui/widget/ConfirmMessageDialog.d.ts.map +1 -1
  39. package/lib/cjs/ui/widget/ConfirmMessageDialog.js +22 -25
  40. package/lib/cjs/ui/widget/ConfirmMessageDialog.js.map +1 -1
  41. package/lib/cjs/ui/widget/FeatureInfoDataProvider.d.ts +35 -40
  42. package/lib/cjs/ui/widget/FeatureInfoDataProvider.d.ts.map +1 -1
  43. package/lib/cjs/ui/widget/FeatureInfoDataProvider.js +139 -139
  44. package/lib/cjs/ui/widget/FeatureInfoDataProvider.js.map +1 -1
  45. package/lib/cjs/ui/widget/FeatureInfoWidget.d.ts +6 -7
  46. package/lib/cjs/ui/widget/FeatureInfoWidget.d.ts.map +1 -1
  47. package/lib/cjs/ui/widget/FeatureInfoWidget.js +70 -71
  48. package/lib/cjs/ui/widget/FeatureInfoWidget.js.map +1 -1
  49. package/lib/cjs/ui/widget/MapLayerDroppable.d.ts +18 -19
  50. package/lib/cjs/ui/widget/MapLayerDroppable.d.ts.map +1 -1
  51. package/lib/cjs/ui/widget/MapLayerDroppable.js +85 -88
  52. package/lib/cjs/ui/widget/MapLayerDroppable.js.map +1 -1
  53. package/lib/cjs/ui/widget/MapLayerManager.d.ts +26 -26
  54. package/lib/cjs/ui/widget/MapLayerManager.d.ts.map +1 -1
  55. package/lib/cjs/ui/widget/MapLayerManager.js +399 -403
  56. package/lib/cjs/ui/widget/MapLayerManager.js.map +1 -1
  57. package/lib/cjs/ui/widget/MapLayerManager.scss +409 -409
  58. package/lib/cjs/ui/widget/MapLayerSettingsMenu.d.ts +11 -12
  59. package/lib/cjs/ui/widget/MapLayerSettingsMenu.d.ts.map +1 -1
  60. package/lib/cjs/ui/widget/MapLayerSettingsMenu.js +79 -83
  61. package/lib/cjs/ui/widget/MapLayerSettingsMenu.js.map +1 -1
  62. package/lib/cjs/ui/widget/MapLayerSettingsPopupButton.d.ts +6 -7
  63. package/lib/cjs/ui/widget/MapLayerSettingsPopupButton.d.ts.map +1 -1
  64. package/lib/cjs/ui/widget/MapLayerSettingsPopupButton.js +65 -65
  65. package/lib/cjs/ui/widget/MapLayerSettingsPopupButton.js.map +1 -1
  66. package/lib/cjs/ui/widget/MapLayerSettingsPopupButton.scss +20 -20
  67. package/lib/cjs/ui/widget/MapLayersWidget.d.ts +10 -11
  68. package/lib/cjs/ui/widget/MapLayersWidget.d.ts.map +1 -1
  69. package/lib/cjs/ui/widget/MapLayersWidget.js +31 -31
  70. package/lib/cjs/ui/widget/MapLayersWidget.js.map +1 -1
  71. package/lib/cjs/ui/widget/MapManagerSettings.d.ts +2 -3
  72. package/lib/cjs/ui/widget/MapManagerSettings.d.ts.map +1 -1
  73. package/lib/cjs/ui/widget/MapManagerSettings.js +195 -200
  74. package/lib/cjs/ui/widget/MapManagerSettings.js.map +1 -1
  75. package/lib/cjs/ui/widget/MapManagerSettings.scss +29 -29
  76. package/lib/cjs/ui/widget/MapUrlDialog.d.ts +22 -23
  77. package/lib/cjs/ui/widget/MapUrlDialog.d.ts.map +1 -1
  78. package/lib/cjs/ui/widget/MapUrlDialog.js +528 -527
  79. package/lib/cjs/ui/widget/MapUrlDialog.js.map +1 -1
  80. package/lib/cjs/ui/widget/MapUrlDialog.scss +99 -100
  81. package/lib/cjs/ui/widget/SelectMapFormat.d.ts +17 -18
  82. package/lib/cjs/ui/widget/SelectMapFormat.d.ts.map +1 -1
  83. package/lib/cjs/ui/widget/SelectMapFormat.js +54 -59
  84. package/lib/cjs/ui/widget/SelectMapFormat.js.map +1 -1
  85. package/lib/cjs/ui/widget/SubLayersDataProvider.d.ts +18 -20
  86. package/lib/cjs/ui/widget/SubLayersDataProvider.d.ts.map +1 -1
  87. package/lib/cjs/ui/widget/SubLayersDataProvider.js +74 -76
  88. package/lib/cjs/ui/widget/SubLayersDataProvider.js.map +1 -1
  89. package/lib/cjs/ui/widget/SubLayersPopupButton.d.ts +9 -10
  90. package/lib/cjs/ui/widget/SubLayersPopupButton.d.ts.map +1 -1
  91. package/lib/cjs/ui/widget/SubLayersPopupButton.js +40 -40
  92. package/lib/cjs/ui/widget/SubLayersPopupButton.js.map +1 -1
  93. package/lib/cjs/ui/widget/SubLayersTree.d.ts +14 -15
  94. package/lib/cjs/ui/widget/SubLayersTree.d.ts.map +1 -1
  95. package/lib/cjs/ui/widget/SubLayersTree.js +408 -419
  96. package/lib/cjs/ui/widget/SubLayersTree.js.map +1 -1
  97. package/lib/cjs/ui/widget/SubLayersTree.scss +70 -69
  98. package/lib/cjs/ui/widget/TransparencyPopupButton.d.ts +13 -14
  99. package/lib/cjs/ui/widget/TransparencyPopupButton.d.ts.map +1 -1
  100. package/lib/cjs/ui/widget/TransparencyPopupButton.js +47 -47
  101. package/lib/cjs/ui/widget/TransparencyPopupButton.js.map +1 -1
  102. package/lib/cjs/ui/widget/TransparencyPopupButton.scss +35 -36
  103. package/lib/esm/MapLayerPreferences.d.ts +88 -88
  104. package/lib/esm/MapLayerPreferences.js +307 -308
  105. package/lib/esm/MapLayerPreferences.js.map +1 -1
  106. package/lib/esm/map-layers.d.ts +6 -6
  107. package/lib/esm/map-layers.js +10 -10
  108. package/lib/esm/mapLayers.d.ts +26 -44
  109. package/lib/esm/mapLayers.d.ts.map +1 -1
  110. package/lib/esm/mapLayers.js +29 -59
  111. package/lib/esm/mapLayers.js.map +1 -1
  112. package/lib/esm/ui/FeatureInfoUiItemsProvider.d.ts +16 -11
  113. package/lib/esm/ui/FeatureInfoUiItemsProvider.d.ts.map +1 -1
  114. package/lib/esm/ui/FeatureInfoUiItemsProvider.js +42 -48
  115. package/lib/esm/ui/FeatureInfoUiItemsProvider.js.map +1 -1
  116. package/lib/esm/ui/Interfaces.d.ts +50 -50
  117. package/lib/esm/ui/Interfaces.d.ts.map +1 -1
  118. package/lib/esm/ui/Interfaces.js +1 -1
  119. package/lib/esm/ui/Interfaces.js.map +1 -1
  120. package/lib/esm/ui/MapFeatureInfoTool.d.ts +13 -13
  121. package/lib/esm/ui/MapFeatureInfoTool.d.ts.map +1 -1
  122. package/lib/esm/ui/MapFeatureInfoTool.js +45 -45
  123. package/lib/esm/ui/MapFeatureInfoTool.js.map +1 -1
  124. package/lib/esm/ui/MapLayersUiItemsProvider.d.ts +8 -8
  125. package/lib/esm/ui/MapLayersUiItemsProvider.d.ts.map +1 -1
  126. package/lib/esm/ui/MapLayersUiItemsProvider.js +31 -34
  127. package/lib/esm/ui/MapLayersUiItemsProvider.js.map +1 -1
  128. package/lib/esm/ui/widget/AttachLayerPopupButton.d.ts +13 -14
  129. package/lib/esm/ui/widget/AttachLayerPopupButton.d.ts.map +1 -1
  130. package/lib/esm/ui/widget/AttachLayerPopupButton.js +330 -334
  131. package/lib/esm/ui/widget/AttachLayerPopupButton.js.map +1 -1
  132. package/lib/esm/ui/widget/BasemapPanel.d.ts +7 -8
  133. package/lib/esm/ui/widget/BasemapPanel.d.ts.map +1 -1
  134. package/lib/esm/ui/widget/BasemapPanel.js +147 -152
  135. package/lib/esm/ui/widget/BasemapPanel.js.map +1 -1
  136. package/lib/esm/ui/widget/BasemapPanel.scss +87 -87
  137. package/lib/esm/ui/widget/ConfirmMessageDialog.d.ts +20 -21
  138. package/lib/esm/ui/widget/ConfirmMessageDialog.d.ts.map +1 -1
  139. package/lib/esm/ui/widget/ConfirmMessageDialog.js +18 -21
  140. package/lib/esm/ui/widget/ConfirmMessageDialog.js.map +1 -1
  141. package/lib/esm/ui/widget/FeatureInfoDataProvider.d.ts +35 -40
  142. package/lib/esm/ui/widget/FeatureInfoDataProvider.d.ts.map +1 -1
  143. package/lib/esm/ui/widget/FeatureInfoDataProvider.js +135 -135
  144. package/lib/esm/ui/widget/FeatureInfoDataProvider.js.map +1 -1
  145. package/lib/esm/ui/widget/FeatureInfoWidget.d.ts +6 -7
  146. package/lib/esm/ui/widget/FeatureInfoWidget.d.ts.map +1 -1
  147. package/lib/esm/ui/widget/FeatureInfoWidget.js +66 -67
  148. package/lib/esm/ui/widget/FeatureInfoWidget.js.map +1 -1
  149. package/lib/esm/ui/widget/MapLayerDroppable.d.ts +18 -19
  150. package/lib/esm/ui/widget/MapLayerDroppable.d.ts.map +1 -1
  151. package/lib/esm/ui/widget/MapLayerDroppable.js +81 -84
  152. package/lib/esm/ui/widget/MapLayerDroppable.js.map +1 -1
  153. package/lib/esm/ui/widget/MapLayerManager.d.ts +26 -26
  154. package/lib/esm/ui/widget/MapLayerManager.d.ts.map +1 -1
  155. package/lib/esm/ui/widget/MapLayerManager.js +394 -398
  156. package/lib/esm/ui/widget/MapLayerManager.js.map +1 -1
  157. package/lib/esm/ui/widget/MapLayerManager.scss +409 -409
  158. package/lib/esm/ui/widget/MapLayerSettingsMenu.d.ts +11 -12
  159. package/lib/esm/ui/widget/MapLayerSettingsMenu.d.ts.map +1 -1
  160. package/lib/esm/ui/widget/MapLayerSettingsMenu.js +75 -79
  161. package/lib/esm/ui/widget/MapLayerSettingsMenu.js.map +1 -1
  162. package/lib/esm/ui/widget/MapLayerSettingsPopupButton.d.ts +6 -7
  163. package/lib/esm/ui/widget/MapLayerSettingsPopupButton.d.ts.map +1 -1
  164. package/lib/esm/ui/widget/MapLayerSettingsPopupButton.js +61 -61
  165. package/lib/esm/ui/widget/MapLayerSettingsPopupButton.js.map +1 -1
  166. package/lib/esm/ui/widget/MapLayerSettingsPopupButton.scss +20 -20
  167. package/lib/esm/ui/widget/MapLayersWidget.d.ts +10 -11
  168. package/lib/esm/ui/widget/MapLayersWidget.d.ts.map +1 -1
  169. package/lib/esm/ui/widget/MapLayersWidget.js +27 -27
  170. package/lib/esm/ui/widget/MapLayersWidget.js.map +1 -1
  171. package/lib/esm/ui/widget/MapManagerSettings.d.ts +2 -3
  172. package/lib/esm/ui/widget/MapManagerSettings.d.ts.map +1 -1
  173. package/lib/esm/ui/widget/MapManagerSettings.js +191 -196
  174. package/lib/esm/ui/widget/MapManagerSettings.js.map +1 -1
  175. package/lib/esm/ui/widget/MapManagerSettings.scss +29 -29
  176. package/lib/esm/ui/widget/MapUrlDialog.d.ts +22 -23
  177. package/lib/esm/ui/widget/MapUrlDialog.d.ts.map +1 -1
  178. package/lib/esm/ui/widget/MapUrlDialog.js +524 -523
  179. package/lib/esm/ui/widget/MapUrlDialog.js.map +1 -1
  180. package/lib/esm/ui/widget/MapUrlDialog.scss +99 -100
  181. package/lib/esm/ui/widget/SelectMapFormat.d.ts +17 -18
  182. package/lib/esm/ui/widget/SelectMapFormat.d.ts.map +1 -1
  183. package/lib/esm/ui/widget/SelectMapFormat.js +50 -55
  184. package/lib/esm/ui/widget/SelectMapFormat.js.map +1 -1
  185. package/lib/esm/ui/widget/SubLayersDataProvider.d.ts +18 -20
  186. package/lib/esm/ui/widget/SubLayersDataProvider.d.ts.map +1 -1
  187. package/lib/esm/ui/widget/SubLayersDataProvider.js +70 -72
  188. package/lib/esm/ui/widget/SubLayersDataProvider.js.map +1 -1
  189. package/lib/esm/ui/widget/SubLayersPopupButton.d.ts +9 -10
  190. package/lib/esm/ui/widget/SubLayersPopupButton.d.ts.map +1 -1
  191. package/lib/esm/ui/widget/SubLayersPopupButton.js +36 -36
  192. package/lib/esm/ui/widget/SubLayersPopupButton.js.map +1 -1
  193. package/lib/esm/ui/widget/SubLayersTree.d.ts +14 -15
  194. package/lib/esm/ui/widget/SubLayersTree.d.ts.map +1 -1
  195. package/lib/esm/ui/widget/SubLayersTree.js +403 -414
  196. package/lib/esm/ui/widget/SubLayersTree.js.map +1 -1
  197. package/lib/esm/ui/widget/SubLayersTree.scss +70 -69
  198. package/lib/esm/ui/widget/TransparencyPopupButton.d.ts +13 -14
  199. package/lib/esm/ui/widget/TransparencyPopupButton.d.ts.map +1 -1
  200. package/lib/esm/ui/widget/TransparencyPopupButton.js +43 -43
  201. package/lib/esm/ui/widget/TransparencyPopupButton.js.map +1 -1
  202. package/lib/esm/ui/widget/TransparencyPopupButton.scss +35 -36
  203. package/package.json +39 -38
@@ -1,415 +1,404 @@
1
- /*---------------------------------------------------------------------------------------------
2
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
- * See LICENSE.md in the project root for license terms and full copyright notice.
4
- *--------------------------------------------------------------------------------------------*/
5
- import { PropertyValueFormat } from "@itwin/appui-abstract";
6
- import { ControlledTree, SelectionMode, TreeEventHandler, TreeImageLoader, TreeModelSource, TreeNodeLoader, TreeNodeRenderer, TreeRenderer, useTreeModel, } from "@itwin/components-react";
7
- import { ImageMapLayerSettings } from "@itwin/core-common";
8
- import { IModelApp } from "@itwin/core-frontend";
9
- import { CheckBoxState, ImageCheckBox, useDisposable, WebFontIcon } from "@itwin/core-react";
10
- import { Button, Input } from "@itwin/itwinui-react";
11
- import * as React from "react";
12
- import { SubLayersDataProvider } from "./SubLayersDataProvider";
13
- import "./SubLayersTree.scss";
14
- import { MapLayersUI } from "../../mapLayers";
15
- const getWindow = () => {
16
- return typeof window === "undefined" ? undefined : window;
17
- };
18
- const useResizeObserver = (onResize) => {
19
- const resizeObserver = React.useRef();
20
- const elementRef = React.useCallback((element) => {
21
- var _a, _b, _c;
22
- if (!((_a = getWindow()) === null || _a === void 0 ? void 0 : _a.ResizeObserver)) {
23
- return;
24
- }
25
- (_b = resizeObserver.current) === null || _b === void 0 ? void 0 : _b.disconnect();
26
- if (element) {
27
- resizeObserver.current = new ResizeObserver(([{ contentRect }]) => onResize(contentRect));
28
- (_c = resizeObserver.current) === null || _c === void 0 ? void 0 : _c.observe(element);
29
- }
30
- }, [onResize]);
31
- return [elementRef, resizeObserver.current];
32
- };
33
- /**
34
- Mimic processing of `react-resize-detector` to return width and height.
35
- * @internal
36
- */
37
- function useResizeDetector() {
38
- const [width, setWidth] = React.useState();
39
- const [height, setHeight] = React.useState();
40
- const [ref] = useResizeObserver(React.useCallback((size) => {
41
- setWidth(size.width);
42
- setHeight(size.height);
43
- }, []));
44
- return { width, height, ref };
45
- }
46
- // eslint-disable-next-line @typescript-eslint/naming-convention
47
- function Toolbar(props) {
48
- return (React.createElement("div", { className: "map-manager-sublayer-tree-toolbar" },
49
- React.createElement("div", { className: "tree-toolbar-action-buttons" }, props.children),
50
- props.searchField && React.createElement("div", { className: "tree-toolbar-searchbox" }, props.searchField)));
51
- }
52
- // eslint-disable-next-line @typescript-eslint/naming-convention
53
- export function SubLayersPanel({ mapLayer, viewport }) {
54
- const [noneAvailableLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.NoSubLayers"));
55
- if (!viewport || (undefined === mapLayer.subLayers || 0 === mapLayer.subLayers.length)) {
56
- return React.createElement("div", { className: "map-manager-sublayer-panel" },
57
- React.createElement("div", null, noneAvailableLabel));
58
- }
59
- return (React.createElement(SubLayersTree, { mapLayer: mapLayer }));
60
- }
61
- function getSubLayerProps(subLayerSettings) {
62
- return subLayerSettings.map((subLayer) => subLayer.toJSON());
63
- }
64
- function getStyleMapLayerSettings(settings, isOverlay, layerIndex, treeVisibility) {
65
- return {
66
- visible: settings.visible,
67
- name: settings.name,
68
- source: settings.source,
69
- transparency: settings.transparency,
70
- transparentBackground: settings.transparentBackground,
71
- subLayers: settings.subLayers ? getSubLayerProps(settings.subLayers) : undefined,
72
- showSubLayers: true,
73
- isOverlay,
74
- layerIndex,
75
- provider: IModelApp.mapLayerFormatRegistry.createImageryProvider(settings),
76
- treeVisibility,
77
- };
78
- }
79
- /**
80
- * Tree Control that displays sub-layer hierarchy
81
- * @internal
82
- */
83
- // eslint-disable-next-line @typescript-eslint/naming-convention
84
- export function SubLayersTree(props) {
85
- var _a;
86
- const [placeholderLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.SearchPlaceholder"));
87
- const [allOnLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.AllOn"));
88
- const [allOffLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.AllOff"));
89
- const [mapLayer, setMapLayer] = React.useState(props.mapLayer);
90
- const [layerFilterString, setLayerFilterString] = React.useState("");
91
- // create data provider to get some nodes to show in tree
92
- // `React.useMemo' is used avoid creating new object on each render cycle
93
- const dataProvider = React.useMemo(() => new SubLayersDataProvider(mapLayer), [mapLayer]);
94
- const { modelSource, nodeLoader, nodeHighlightingProps, } = useTreeFiltering(dataProvider, layerFilterString);
95
- // create custom event handler. It handles all tree event same as `TreeEventHandler` but additionally
96
- // it selects/deselects node when checkbox is checked/unchecked and vice versa.
97
- // `useDisposable` takes care of disposing old event handler when new is created in case 'nodeLoader' has changed
98
- // `React.useCallback` is used to avoid creating new callback that creates handler on each render
99
- const eventHandler = useDisposable(React.useCallback(() => new SubLayerCheckboxHandler(mapLayer, nodeLoader), [nodeLoader, mapLayer]));
100
- // Get an immutable tree model from the model source. The model is regenerated every time the model source
101
- // emits the `onModelChanged` event.
102
- const treeModel = useTreeModel(modelSource);
103
- const showAll = React.useCallback(async () => {
104
- const vp = IModelApp.viewManager.selectedView;
105
- const displayStyle = vp === null || vp === void 0 ? void 0 : vp.displayStyle;
106
- if (displayStyle && vp) {
107
- const indexInDisplayStyle = displayStyle ? displayStyle.findMapLayerIndexByNameAndSource(mapLayer.name, mapLayer.source, mapLayer.isOverlay) : -1;
108
- displayStyle.changeMapSubLayerProps({ visible: true }, -1, indexInDisplayStyle, mapLayer.isOverlay);
109
- vp.invalidateRenderPlan();
110
- const updatedMapLayer = displayStyle.mapLayerAtIndex(indexInDisplayStyle, mapLayer.isOverlay);
111
- if (updatedMapLayer) {
112
- if (updatedMapLayer instanceof ImageMapLayerSettings) {
113
- const treeVisibility = vp.getMapLayerScaleRangeVisibility(indexInDisplayStyle, mapLayer.isOverlay);
114
- setMapLayer(getStyleMapLayerSettings(updatedMapLayer, mapLayer.isOverlay, indexInDisplayStyle, treeVisibility));
115
- }
116
- }
117
- }
118
- }, [mapLayer]);
119
- const hideAll = React.useCallback(async () => {
120
- const vp = IModelApp.viewManager.selectedView;
121
- const displayStyle = vp === null || vp === void 0 ? void 0 : vp.displayStyle;
122
- if (displayStyle && vp) {
123
- const indexInDisplayStyle = displayStyle ? displayStyle.findMapLayerIndexByNameAndSource(mapLayer.name, mapLayer.source, mapLayer.isOverlay) : -1;
124
- displayStyle.changeMapSubLayerProps({ visible: false }, -1, indexInDisplayStyle, mapLayer.isOverlay);
125
- const updatedMapLayer = displayStyle.mapLayerAtIndex(indexInDisplayStyle, mapLayer.isOverlay);
126
- if (updatedMapLayer && updatedMapLayer instanceof ImageMapLayerSettings) {
127
- const treeVisibility = vp.getMapLayerScaleRangeVisibility(indexInDisplayStyle, mapLayer.isOverlay);
128
- setMapLayer(getStyleMapLayerSettings(updatedMapLayer, mapLayer.isOverlay, indexInDisplayStyle, treeVisibility));
129
- }
130
- vp.invalidateRenderPlan();
131
- }
132
- }, [mapLayer]);
133
- const handleFilterTextChanged = React.useCallback((event) => {
134
- setLayerFilterString(event.target.value);
135
- }, []);
136
- const { width, height, ref: containerRef } = useResizeDetector();
137
- return React.createElement(React.Fragment, null,
138
- React.createElement("div", { className: "map-manager-sublayer-tree" },
139
- React.createElement(Toolbar, { searchField: React.createElement(Input, { type: "text", className: "map-manager-source-list-filter", placeholder: placeholderLabel, value: layerFilterString, onChange: handleFilterTextChanged, size: "small" }) }, ((_a = mapLayer.provider) === null || _a === void 0 ? void 0 : _a.mutualExclusiveSubLayer) ? undefined : [
140
- React.createElement(Button, { size: "small", styleType: "borderless", key: "show-all-btn", title: allOnLabel, onClick: showAll },
141
- React.createElement(WebFontIcon, { iconName: "icon-visibility" })),
142
- React.createElement(Button, { size: "small", styleType: "borderless", key: "hide-all-btn", title: allOffLabel, onClick: hideAll },
143
- React.createElement(WebFontIcon, { iconName: "icon-visibility-hide-2" })),
144
- ]),
145
- React.createElement("div", { ref: containerRef, className: "map-manager-sublayer-tree-content" }, width !== undefined && height !== undefined && React.createElement(ControlledTree, { nodeLoader: nodeLoader, selectionMode: SelectionMode.None, eventsHandler: eventHandler, model: treeModel, treeRenderer: nodeWithEyeCheckboxTreeRenderer, nodeHighlightingProps: nodeHighlightingProps, width: width, height: height }))));
146
- }
147
- /** TreeEventHandler derived class that handler processing changes to subLayer visibility */
148
- class SubLayerCheckboxHandler extends TreeEventHandler {
149
- constructor(_mapLayer, nodeLoader) {
150
- super({ modelSource: nodeLoader.modelSource, nodeLoader, collapsedChildrenDisposalEnabled: true });
151
- this._mapLayer = _mapLayer;
152
- //-----------------------------------------------------------------------
153
- // Listen to model changes
154
- //------------------------------------------------------------------------
155
- // This is required because nodes are delay loaded in the model until
156
- // they are made visible (i.e. parent node is expanded). So even though
157
- // you might have created nodes in the data provided with a proper
158
- // initial state, by the time it gets loaded, their state might have became
159
- // out of date in the TreeView's active model. So whenever a node
160
- // is added, when must confirm its state matches the current model
161
- // (i.e. state of their parent.)
162
- this.onModelChanged = (args) => {
163
- this.modelSource.modifyModel((model) => {
164
- const addedNodes = args[1].addedNodeIds.map((id) => model.getNode(id));
165
- addedNodes.forEach((node) => {
166
- if (!node)
167
- return;
168
- this.syncNodeStateWithParent(model, node);
169
- });
170
- });
171
- };
172
- this._removeModelChangedListener = this.modelSource.onModelChanged.addListener(this.onModelChanged);
173
- }
174
- dispose() {
175
- this._removeModelChangedListener();
176
- super.dispose();
177
- }
178
- // Cascade state
179
- // Children on unnamed groups must get disabled in the tree view, because
180
- // they get rendered anyway.
181
- cascadeStateToAllChildren(model, parentId) {
182
- const children = model.getChildren(parentId);
183
- if (children === undefined)
184
- return;
185
- for (const childID of children) {
186
- const childNode = childID ? model.getNode(childID) : undefined;
187
- if (childNode)
188
- this.syncNodeStateWithParent(model, childNode);
189
- // Drill down the tree.
190
- this.cascadeStateToAllChildren(model, childID);
191
- }
192
- }
193
- applyMutualExclusiveState(model, nodeId) {
194
- const changedNode = model.getNode(nodeId);
195
- if ((changedNode === null || changedNode === void 0 ? void 0 : changedNode.checkbox.state) === CheckBoxState.Off)
196
- return;
197
- for (const node of model.iterateTreeModelNodes()) {
198
- if (node.id === (changedNode === null || changedNode === void 0 ? void 0 : changedNode.id))
199
- continue;
200
- if (node && node.checkbox.state === CheckBoxState.On)
201
- node.checkbox.state = CheckBoxState.Off;
202
- }
203
- }
204
- static isUnnamedGroup(subLayer) {
205
- if (!subLayer)
206
- return false;
207
- return (!subLayer.name || subLayer.name.length === 0) && (subLayer.children !== undefined && subLayer.children.length > 0);
208
- }
209
- // Ensure the state of changed node matches the state of its parent.
210
- syncNodeStateWithParent(model, changedNode) {
211
- var _a, _b, _c, _d, _e, _f;
212
- // Lookup node parent. If non exists, I assume thats the root node,
213
- // and it must have a proper initial state.
214
- const parentNode = changedNode.parentId ? model.getNode(changedNode.parentId) : undefined;
215
- if (!parentNode)
216
- return;
217
- if (!changedNode.checkbox)
218
- return; // don't see why this would happen, but if there is no checkbox, we cant do much here.
219
- const parentLayerId = undefined !== ((_a = parentNode.item.extendedData) === null || _a === void 0 ? void 0 : _a.subLayerId) ? (_b = parentNode.item.extendedData) === null || _b === void 0 ? void 0 : _b.subLayerId : parentNode.item.id;
220
- const parentSubLayer = (_c = this._mapLayer.subLayers) === null || _c === void 0 ? void 0 : _c.find((subLayer) => subLayer.id === parentLayerId);
221
- // If parent is disabled, then children must be too.
222
- // Also, Non-visible unnamed group must have their children disabled (unamed groups have visibility inherence)
223
- if (parentNode.checkbox.isDisabled || (SubLayerCheckboxHandler.isUnnamedGroup(parentSubLayer) && parentNode.checkbox.state === CheckBoxState.Off)) {
224
- changedNode.checkbox.isDisabled = true;
225
- changedNode.checkbox.state = CheckBoxState.Off;
226
- }
227
- else {
228
- // Visibility state from StyleMapLayerSettings applies
229
- const subLayerId = undefined !== ((_d = changedNode.item.extendedData) === null || _d === void 0 ? void 0 : _d.subLayerId) ? (_e = changedNode.item.extendedData) === null || _e === void 0 ? void 0 : _e.subLayerId : changedNode.item.id;
230
- const foundSubLayer = (_f = this._mapLayer.subLayers) === null || _f === void 0 ? void 0 : _f.find((subLayer) => subLayer.id === subLayerId);
231
- changedNode.checkbox.isDisabled = false;
232
- changedNode.checkbox.state = (foundSubLayer === null || foundSubLayer === void 0 ? void 0 : foundSubLayer.visible) ? CheckBoxState.On : CheckBoxState.Off;
233
- }
234
- }
235
- /** Changes nodes checkboxes states until event is handled or handler is disposed */
236
- onCheckboxStateChanged({ stateChanges }) {
237
- // call base checkbox handling
238
- const baseHandling = super.onCheckboxStateChanged({ stateChanges });
239
- // subscribe to checkbox state changes to new checkbox states and do some additional work with them
240
- const selectionHandling = stateChanges.subscribe({
241
- next: (changes) => {
242
- const vp = IModelApp.viewManager.selectedView;
243
- const displayStyle = vp === null || vp === void 0 ? void 0 : vp.displayStyle;
244
- const indexInDisplayStyle = displayStyle ? displayStyle.findMapLayerIndexByNameAndSource(this._mapLayer.name, this._mapLayer.source, this._mapLayer.isOverlay) : -1;
245
- changes.forEach((change) => {
246
- var _a, _b, _c, _d, _e;
247
- const isSelected = (change.newState === CheckBoxState.On);
248
- const subLayerId = undefined !== ((_a = change.nodeItem.extendedData) === null || _a === void 0 ? void 0 : _a.subLayerId) ? (_b = change.nodeItem.extendedData) === null || _b === void 0 ? void 0 : _b.subLayerId : change.nodeItem.id;
249
- // Get the previously visible node if any
250
- let prevVisibleLayer;
251
- if ((_c = this._mapLayer.provider) === null || _c === void 0 ? void 0 : _c.mutualExclusiveSubLayer) {
252
- prevVisibleLayer = (_d = this._mapLayer.subLayers) === null || _d === void 0 ? void 0 : _d.find((subLayer) => subLayer.visible && subLayer.id !== subLayerId);
253
- }
254
- // Update sublayer object, otherwise state would get out of sync with DisplayStyle each time the TreeView is re-rendered
255
- const foundSubLayer = (_e = this._mapLayer.subLayers) === null || _e === void 0 ? void 0 : _e.find((subLayer) => subLayer.id === subLayerId);
256
- if (foundSubLayer)
257
- foundSubLayer.visible = isSelected;
258
- if (prevVisibleLayer === null || prevVisibleLayer === void 0 ? void 0 : prevVisibleLayer.visible)
259
- prevVisibleLayer.visible = false;
260
- // Update displaystyle state
261
- if (-1 !== indexInDisplayStyle && displayStyle) {
262
- if (prevVisibleLayer && prevVisibleLayer.id !== undefined)
263
- displayStyle.changeMapSubLayerProps({ visible: false }, prevVisibleLayer.id, indexInDisplayStyle, this._mapLayer.isOverlay);
264
- displayStyle.changeMapSubLayerProps({ visible: isSelected }, subLayerId, indexInDisplayStyle, this._mapLayer.isOverlay);
265
- }
266
- // Cascade state
267
- this.modelSource.modifyModel((model) => {
268
- var _a;
269
- if ((_a = this._mapLayer.provider) === null || _a === void 0 ? void 0 : _a.mutualExclusiveSubLayer)
270
- this.applyMutualExclusiveState(model, change.nodeItem.id);
271
- this.cascadeStateToAllChildren(model, change.nodeItem.id);
272
- });
273
- });
274
- if (vp)
275
- vp.invalidateRenderPlan();
276
- },
277
- });
278
- // stop handling selection when checkboxes handling is stopped
279
- baseHandling === null || baseHandling === void 0 ? void 0 : baseHandling.add(selectionHandling);
280
- return baseHandling;
281
- }
282
- }
283
- /** Custom checkbox renderer that renders checkbox as an eye */
284
- const eyeCheckboxRenderer = (props) => (React.createElement(ImageCheckBox, { checked: props.checked, disabled: props.disabled, imageOn: "icon-visibility", imageOff: "icon-visibility-hide-2", onClick: props.onChange, tooltip: props.title }));
285
- /** Custom node renderer. It uses default 'TreeNodeRenderer' but overrides default checkbox renderer to render checkbox as an eye */
286
- const imageLoader = new TreeImageLoader();
287
- const nodeWithEyeCheckboxRenderer = (props) => (React.createElement(TreeNodeRenderer, { ...props, checkboxRenderer: eyeCheckboxRenderer, imageLoader: imageLoader }));
288
- /** Custom tree renderer. It uses default `TreeRenderer` but overrides default node renderer to render node with custom checkbox */
289
- const nodeWithEyeCheckboxTreeRenderer = (props) => (React.createElement(TreeRenderer, { ...props, nodeRenderer: nodeWithEyeCheckboxRenderer }));
290
- function useTreeFiltering(dataProvider, filter) {
291
- const nodeLoader = useFilteredProvider(dataProvider, filter);
292
- const nodeHighlightingProps = useNodeHighlightingProps(filter);
293
- return {
294
- nodeLoader,
295
- modelSource: nodeLoader.modelSource,
296
- nodeHighlightingProps,
297
- };
298
- }
299
- function useFilteredProvider(dataProvider, filter) {
300
- const filteredProvider = React.useMemo(() => {
301
- return new FilteredTreeDataProvider(dataProvider, filter);
302
- }, [dataProvider, filter]);
303
- const nodeLoader = React.useMemo(() => {
304
- return new TreeNodeLoader(filteredProvider, new TreeModelSource());
305
- }, [filteredProvider]);
306
- return nodeLoader;
307
- }
308
- function useNodeHighlightingProps(filter) {
309
- const [nodeHighlightingProps, setNodeHighlightingProps] = React.useState();
310
- React.useEffect(() => {
311
- if (filter === "") {
312
- setNodeHighlightingProps(undefined);
313
- return;
314
- }
315
- setNodeHighlightingProps({
316
- searchText: filter,
317
- activeMatch: undefined,
318
- });
319
- }, [filter]);
320
- return nodeHighlightingProps;
321
- }
322
- class FullTreeHierarchy {
323
- constructor(dataProvider) {
324
- this._hierarchy = new Map();
325
- this._dataProvider = dataProvider;
326
- this._init = (async () => {
327
- await this.initNode();
328
- })();
329
- }
330
- async initNode(parent) {
331
- const nodes = await this._dataProvider.getNodes(parent);
332
- this._hierarchy.set(parent === null || parent === void 0 ? void 0 : parent.id, nodes);
333
- for (const node of nodes) {
334
- await this.initNode(node);
335
- }
336
- }
337
- async getHierarchy() {
338
- await this._init;
339
- return this._hierarchy;
340
- }
341
- }
342
- class FilteredTreeHierarchy {
343
- constructor(dataProvider, filter) {
344
- this._filtered = new Map();
345
- this._fullHierarchy = new FullTreeHierarchy(dataProvider);
346
- this._filter = filter;
347
- this._init = (async () => {
348
- await this.init();
349
- })();
350
- }
351
- async init() {
352
- const hierarchy = await this._fullHierarchy.getHierarchy();
353
- if (this._filter === "") {
354
- this._filtered = hierarchy;
355
- return;
356
- }
357
- this.filterNodes(hierarchy);
358
- }
359
- /** Initializes `this._filtered` field. Returns a node if it matches a filter. */
360
- filterNodes(hierarchy, current) {
361
- const matches = current ? this.matchesFilter(current) : false;
362
- const children = hierarchy.get(current === null || current === void 0 ? void 0 : current.id);
363
- if (!children)
364
- return matches ? current : undefined;
365
- const matchedChildren = new Array();
366
- for (const child of children) {
367
- const matchedChild = this.filterNodes(hierarchy, child);
368
- matchedChild && matchedChildren.push(matchedChild);
369
- }
370
- const hasChildren = matchedChildren.length > 0;
371
- const included = matches || hasChildren;
372
- let filtered;
373
- if (included) {
374
- this._filtered.set(current === null || current === void 0 ? void 0 : current.id, matchedChildren);
375
- // Return a modified copy of current node (to persist initial hierarchy when filter is cleared).
376
- if (current) {
377
- filtered = {
378
- ...current,
379
- hasChildren,
380
- autoExpand: hasChildren ? true : current.autoExpand,
381
- };
382
- }
383
- }
384
- return filtered;
385
- }
386
- matchesFilter(node) {
387
- var _a;
388
- if (node.label.value.valueFormat !== PropertyValueFormat.Primitive)
389
- return false;
390
- const value = (_a = node.label.value.displayValue) === null || _a === void 0 ? void 0 : _a.toLowerCase();
391
- if (!value)
392
- return false;
393
- return value.includes(this._filter.toLowerCase());
394
- }
395
- async getHierarchy() {
396
- await this._init;
397
- return this._filtered;
398
- }
399
- }
400
- class FilteredTreeDataProvider {
401
- constructor(parentDataProvider, filter) {
402
- this._hierarchy = new FilteredTreeHierarchy(parentDataProvider, filter);
403
- }
404
- async getNodes(parent) {
405
- const hierarchy = await this._hierarchy.getHierarchy();
406
- const nodes = hierarchy.get(parent === null || parent === void 0 ? void 0 : parent.id);
407
- return nodes || [];
408
- }
409
- async getNodesCount(parent) {
410
- const hierarchy = await this._hierarchy.getHierarchy();
411
- const nodes = hierarchy.get(parent === null || parent === void 0 ? void 0 : parent.id);
412
- return (nodes === null || nodes === void 0 ? void 0 : nodes.length) || 0;
413
- }
414
- }
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import { PropertyValueFormat } from "@itwin/appui-abstract";
6
+ import { ControlledTree, SelectionMode, TreeEventHandler, TreeImageLoader, TreeModelSource, TreeNodeLoader, TreeNodeRenderer, TreeRenderer, useTreeModel, } from "@itwin/components-react";
7
+ import { ImageMapLayerSettings } from "@itwin/core-common";
8
+ import { IModelApp } from "@itwin/core-frontend";
9
+ import { CheckBoxState, ImageCheckBox, useDisposable, WebFontIcon } from "@itwin/core-react";
10
+ import { Button, Input } from "@itwin/itwinui-react";
11
+ import * as React from "react";
12
+ import { SubLayersDataProvider } from "./SubLayersDataProvider";
13
+ import "./SubLayersTree.scss";
14
+ import { MapLayersUI } from "../../mapLayers";
15
+ const getWindow = () => {
16
+ return typeof window === "undefined" ? undefined : window;
17
+ };
18
+ const useResizeObserver = (onResize) => {
19
+ const resizeObserver = React.useRef();
20
+ const elementRef = React.useCallback((element) => {
21
+ if (!getWindow()?.ResizeObserver) {
22
+ return;
23
+ }
24
+ resizeObserver.current?.disconnect();
25
+ if (element) {
26
+ resizeObserver.current = new ResizeObserver(([{ contentRect }]) => onResize(contentRect));
27
+ resizeObserver.current?.observe(element);
28
+ }
29
+ }, [onResize]);
30
+ return [elementRef, resizeObserver.current];
31
+ };
32
+ /** Mimic processing of `react-resize-detector` to return width and height.
33
+ * @internal
34
+ */
35
+ function useResizeDetector() {
36
+ const [width, setWidth] = React.useState();
37
+ const [height, setHeight] = React.useState();
38
+ const [ref] = useResizeObserver(React.useCallback((size) => {
39
+ setWidth(size.width);
40
+ setHeight(size.height);
41
+ }, []));
42
+ return { width, height, ref };
43
+ }
44
+ // eslint-disable-next-line @typescript-eslint/naming-convention
45
+ function Toolbar(props) {
46
+ return (React.createElement("div", { className: "map-manager-sublayer-tree-toolbar" },
47
+ React.createElement("div", { className: "tree-toolbar-action-buttons" }, props.children),
48
+ props.searchField && React.createElement("div", { className: "tree-toolbar-searchbox" }, props.searchField)));
49
+ }
50
+ // eslint-disable-next-line @typescript-eslint/naming-convention
51
+ export function SubLayersPanel({ mapLayer, viewport }) {
52
+ const [noneAvailableLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.NoSubLayers"));
53
+ if (!viewport || (undefined === mapLayer.subLayers || 0 === mapLayer.subLayers.length)) {
54
+ return React.createElement("div", { className: "map-manager-sublayer-panel" },
55
+ React.createElement("div", null, noneAvailableLabel));
56
+ }
57
+ return (React.createElement(SubLayersTree, { mapLayer: mapLayer }));
58
+ }
59
+ function getSubLayerProps(subLayerSettings) {
60
+ return subLayerSettings.map((subLayer) => subLayer.toJSON());
61
+ }
62
+ function getStyleMapLayerSettings(settings, isOverlay, layerIndex, treeVisibility) {
63
+ return {
64
+ visible: settings.visible,
65
+ name: settings.name,
66
+ source: settings.source,
67
+ transparency: settings.transparency,
68
+ transparentBackground: settings.transparentBackground,
69
+ subLayers: settings.subLayers ? getSubLayerProps(settings.subLayers) : undefined,
70
+ showSubLayers: true,
71
+ isOverlay,
72
+ layerIndex,
73
+ provider: IModelApp.viewManager.selectedView?.getMapLayerImageryProvider({ index: layerIndex, isOverlay }),
74
+ treeVisibility,
75
+ };
76
+ }
77
+ /**
78
+ * Tree Control that displays sub-layer hierarchy
79
+ * @internal
80
+ */
81
+ // eslint-disable-next-line @typescript-eslint/naming-convention
82
+ export function SubLayersTree(props) {
83
+ const [placeholderLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.SearchPlaceholder"));
84
+ const [allOnLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.AllOn"));
85
+ const [allOffLabel] = React.useState(MapLayersUI.localization.getLocalizedString("mapLayers:SubLayers.AllOff"));
86
+ const [mapLayer, setMapLayer] = React.useState(props.mapLayer);
87
+ const [layerFilterString, setLayerFilterString] = React.useState("");
88
+ // create data provider to get some nodes to show in tree
89
+ // `React.useMemo' is used avoid creating new object on each render cycle
90
+ const dataProvider = React.useMemo(() => new SubLayersDataProvider(mapLayer), [mapLayer]);
91
+ const { modelSource, nodeLoader, nodeHighlightingProps, } = useTreeFiltering(dataProvider, layerFilterString);
92
+ // create custom event handler. It handles all tree event same as `TreeEventHandler` but additionally
93
+ // it selects/deselects node when checkbox is checked/unchecked and vice versa.
94
+ // `useDisposable` takes care of disposing old event handler when new is created in case 'nodeLoader' has changed
95
+ // `React.useCallback` is used to avoid creating new callback that creates handler on each render
96
+ const eventHandler = useDisposable(React.useCallback(() => new SubLayerCheckboxHandler(mapLayer, nodeLoader), [nodeLoader, mapLayer]));
97
+ // Get an immutable tree model from the model source. The model is regenerated every time the model source
98
+ // emits the `onModelChanged` event.
99
+ const treeModel = useTreeModel(modelSource);
100
+ const showAll = React.useCallback(async () => {
101
+ const vp = IModelApp.viewManager.selectedView;
102
+ const displayStyle = vp?.displayStyle;
103
+ if (displayStyle && vp) {
104
+ const indexInDisplayStyle = displayStyle ? displayStyle.findMapLayerIndexByNameAndSource(mapLayer.name, mapLayer.source, mapLayer.isOverlay) : -1;
105
+ displayStyle.changeMapSubLayerProps({ visible: true }, -1, { index: indexInDisplayStyle, isOverlay: mapLayer.isOverlay });
106
+ const updatedMapLayer = displayStyle.mapLayerAtIndex({ index: indexInDisplayStyle, isOverlay: mapLayer.isOverlay });
107
+ if (updatedMapLayer) {
108
+ if (updatedMapLayer instanceof ImageMapLayerSettings) {
109
+ const treeVisibility = vp.getMapLayerScaleRangeVisibility({ index: indexInDisplayStyle, isOverlay: mapLayer.isOverlay });
110
+ setMapLayer(getStyleMapLayerSettings(updatedMapLayer, mapLayer.isOverlay, indexInDisplayStyle, treeVisibility));
111
+ }
112
+ }
113
+ }
114
+ }, [mapLayer]);
115
+ const hideAll = React.useCallback(async () => {
116
+ const vp = IModelApp.viewManager.selectedView;
117
+ const displayStyle = vp?.displayStyle;
118
+ if (displayStyle && vp) {
119
+ const indexInDisplayStyle = displayStyle ? displayStyle.findMapLayerIndexByNameAndSource(mapLayer.name, mapLayer.source, mapLayer.isOverlay) : -1;
120
+ displayStyle.changeMapSubLayerProps({ visible: false }, -1, { index: indexInDisplayStyle, isOverlay: mapLayer.isOverlay });
121
+ const updatedMapLayer = displayStyle.mapLayerAtIndex({ index: indexInDisplayStyle, isOverlay: mapLayer.isOverlay });
122
+ if (updatedMapLayer && updatedMapLayer instanceof ImageMapLayerSettings) {
123
+ const treeVisibility = vp.getMapLayerScaleRangeVisibility({ index: indexInDisplayStyle, isOverlay: mapLayer.isOverlay });
124
+ setMapLayer(getStyleMapLayerSettings(updatedMapLayer, mapLayer.isOverlay, indexInDisplayStyle, treeVisibility));
125
+ }
126
+ }
127
+ }, [mapLayer]);
128
+ const handleFilterTextChanged = React.useCallback((event) => {
129
+ setLayerFilterString(event.target.value);
130
+ }, []);
131
+ const { width, height, ref: containerRef } = useResizeDetector();
132
+ return React.createElement(React.Fragment, null,
133
+ React.createElement("div", { className: "map-manager-sublayer-tree" },
134
+ React.createElement(Toolbar, { searchField: React.createElement(Input, { type: "text", className: "map-manager-source-list-filter", placeholder: placeholderLabel, value: layerFilterString, onChange: handleFilterTextChanged, size: "small" }) }, mapLayer.provider?.mutualExclusiveSubLayer ? undefined : [
135
+ React.createElement(Button, { size: "small", styleType: "borderless", key: "show-all-btn", title: allOnLabel, onClick: showAll },
136
+ React.createElement(WebFontIcon, { iconName: "icon-visibility" })),
137
+ React.createElement(Button, { size: "small", styleType: "borderless", key: "hide-all-btn", title: allOffLabel, onClick: hideAll },
138
+ React.createElement(WebFontIcon, { iconName: "icon-visibility-hide-2" })),
139
+ ]),
140
+ React.createElement("div", { ref: containerRef, className: "map-manager-sublayer-tree-content" }, width !== undefined && height !== undefined && React.createElement(ControlledTree, { nodeLoader: nodeLoader, selectionMode: SelectionMode.None, eventsHandler: eventHandler, model: treeModel, treeRenderer: nodeWithEyeCheckboxTreeRenderer, nodeHighlightingProps: nodeHighlightingProps, width: width, height: height }))));
141
+ }
142
+ /** TreeEventHandler derived class that handler processing changes to subLayer visibility */
143
+ class SubLayerCheckboxHandler extends TreeEventHandler {
144
+ constructor(_mapLayer, nodeLoader) {
145
+ super({ modelSource: nodeLoader.modelSource, nodeLoader, collapsedChildrenDisposalEnabled: true });
146
+ this._mapLayer = _mapLayer;
147
+ //-----------------------------------------------------------------------
148
+ // Listen to model changes
149
+ //------------------------------------------------------------------------
150
+ // This is required because nodes are delay loaded in the model until
151
+ // they are made visible (i.e. parent node is expanded). So even though
152
+ // you might have created nodes in the data provided with a proper
153
+ // initial state, by the time it gets loaded, their state might have became
154
+ // out of date in the TreeView's active model. So whenever a node
155
+ // is added, when must confirm its state matches the current model
156
+ // (i.e. state of their parent.)
157
+ this.onModelChanged = (args) => {
158
+ this.modelSource.modifyModel((model) => {
159
+ const addedNodes = args[1].addedNodeIds.map((id) => model.getNode(id));
160
+ addedNodes.forEach((node) => {
161
+ if (!node)
162
+ return;
163
+ this.syncNodeStateWithParent(model, node);
164
+ });
165
+ });
166
+ };
167
+ this._removeModelChangedListener = this.modelSource.onModelChanged.addListener(this.onModelChanged);
168
+ }
169
+ dispose() {
170
+ this._removeModelChangedListener();
171
+ super.dispose();
172
+ }
173
+ // Cascade state
174
+ // Children on unnamed groups must get disabled in the tree view, because
175
+ // they get rendered anyway.
176
+ cascadeStateToAllChildren(model, parentId) {
177
+ const children = model.getChildren(parentId);
178
+ if (children === undefined)
179
+ return;
180
+ for (const childID of children) {
181
+ const childNode = childID ? model.getNode(childID) : undefined;
182
+ if (childNode)
183
+ this.syncNodeStateWithParent(model, childNode);
184
+ // Drill down the tree.
185
+ this.cascadeStateToAllChildren(model, childID);
186
+ }
187
+ }
188
+ applyMutualExclusiveState(model, nodeId) {
189
+ const changedNode = model.getNode(nodeId);
190
+ if (changedNode?.checkbox.state === CheckBoxState.Off)
191
+ return;
192
+ for (const node of model.iterateTreeModelNodes()) {
193
+ if (node.id === changedNode?.id)
194
+ continue;
195
+ if (node && node.checkbox.state === CheckBoxState.On)
196
+ node.checkbox.state = CheckBoxState.Off;
197
+ }
198
+ }
199
+ static isUnnamedGroup(subLayer) {
200
+ if (!subLayer)
201
+ return false;
202
+ return (!subLayer.name || subLayer.name.length === 0) && (subLayer.children !== undefined && subLayer.children.length > 0);
203
+ }
204
+ // Ensure the state of changed node matches the state of its parent.
205
+ syncNodeStateWithParent(model, changedNode) {
206
+ // Lookup node parent. If non exists, I assume thats the root node,
207
+ // and it must have a proper initial state.
208
+ const parentNode = changedNode.parentId ? model.getNode(changedNode.parentId) : undefined;
209
+ if (!parentNode)
210
+ return;
211
+ if (!changedNode.checkbox)
212
+ return; // don't see why this would happen, but if there is no checkbox, we cant do much here.
213
+ const parentLayerId = undefined !== parentNode.item.extendedData?.subLayerId ? parentNode.item.extendedData?.subLayerId : parentNode.item.id;
214
+ const parentSubLayer = this._mapLayer.subLayers?.find((subLayer) => subLayer.id === parentLayerId);
215
+ // If parent is disabled, then children must be too.
216
+ // Also, Non-visible unnamed group must have their children disabled (unamed groups have visibility inherence)
217
+ if (parentNode.checkbox.isDisabled || (SubLayerCheckboxHandler.isUnnamedGroup(parentSubLayer) && parentNode.checkbox.state === CheckBoxState.Off)) {
218
+ changedNode.checkbox.isDisabled = true;
219
+ changedNode.checkbox.state = CheckBoxState.Off;
220
+ }
221
+ else {
222
+ // Visibility state from StyleMapLayerSettings applies
223
+ const subLayerId = undefined !== changedNode.item.extendedData?.subLayerId ? changedNode.item.extendedData?.subLayerId : changedNode.item.id;
224
+ const foundSubLayer = this._mapLayer.subLayers?.find((subLayer) => subLayer.id === subLayerId);
225
+ changedNode.checkbox.isDisabled = false;
226
+ changedNode.checkbox.state = foundSubLayer?.visible ? CheckBoxState.On : CheckBoxState.Off;
227
+ }
228
+ }
229
+ /** Changes nodes checkboxes states until event is handled or handler is disposed */
230
+ onCheckboxStateChanged({ stateChanges }) {
231
+ // call base checkbox handling
232
+ const baseHandling = super.onCheckboxStateChanged({ stateChanges });
233
+ // subscribe to checkbox state changes to new checkbox states and do some additional work with them
234
+ const selectionHandling = stateChanges.subscribe({
235
+ next: (changes) => {
236
+ const vp = IModelApp.viewManager.selectedView;
237
+ const displayStyle = vp?.displayStyle;
238
+ const indexInDisplayStyle = displayStyle ? displayStyle.findMapLayerIndexByNameAndSource(this._mapLayer.name, this._mapLayer.source, this._mapLayer.isOverlay) : -1;
239
+ changes.forEach((change) => {
240
+ const isSelected = (change.newState === CheckBoxState.On);
241
+ const subLayerId = undefined !== change.nodeItem.extendedData?.subLayerId ? change.nodeItem.extendedData?.subLayerId : change.nodeItem.id;
242
+ // Get the previously visible node if any
243
+ let prevVisibleLayer;
244
+ if (this._mapLayer.provider?.mutualExclusiveSubLayer) {
245
+ prevVisibleLayer = this._mapLayer.subLayers?.find((subLayer) => subLayer.visible && subLayer.id !== subLayerId);
246
+ }
247
+ // Update sublayer object, otherwise state would get out of sync with DisplayStyle each time the TreeView is re-rendered
248
+ const foundSubLayer = this._mapLayer.subLayers?.find((subLayer) => subLayer.id === subLayerId);
249
+ if (foundSubLayer)
250
+ foundSubLayer.visible = isSelected;
251
+ if (prevVisibleLayer?.visible)
252
+ prevVisibleLayer.visible = false;
253
+ // Update displaystyle state
254
+ if (-1 !== indexInDisplayStyle && displayStyle) {
255
+ if (prevVisibleLayer && prevVisibleLayer.id !== undefined)
256
+ displayStyle.changeMapSubLayerProps({ visible: false }, prevVisibleLayer.id, { index: indexInDisplayStyle, isOverlay: this._mapLayer.isOverlay });
257
+ displayStyle.changeMapSubLayerProps({ visible: isSelected }, subLayerId, { index: indexInDisplayStyle, isOverlay: this._mapLayer.isOverlay });
258
+ }
259
+ // Cascade state
260
+ this.modelSource.modifyModel((model) => {
261
+ if (this._mapLayer.provider?.mutualExclusiveSubLayer)
262
+ this.applyMutualExclusiveState(model, change.nodeItem.id);
263
+ this.cascadeStateToAllChildren(model, change.nodeItem.id);
264
+ });
265
+ });
266
+ },
267
+ });
268
+ // stop handling selection when checkboxes handling is stopped
269
+ baseHandling?.add(selectionHandling);
270
+ return baseHandling;
271
+ }
272
+ }
273
+ /** Custom checkbox renderer that renders checkbox as an eye */
274
+ const eyeCheckboxRenderer = (props) => (React.createElement(ImageCheckBox, { checked: props.checked, disabled: props.disabled, imageOn: "icon-visibility", imageOff: "icon-visibility-hide-2", onClick: props.onChange, tooltip: props.title }));
275
+ /** Custom node renderer. It uses default 'TreeNodeRenderer' but overrides default checkbox renderer to render checkbox as an eye */
276
+ const imageLoader = new TreeImageLoader();
277
+ const nodeWithEyeCheckboxRenderer = (props) => (React.createElement(TreeNodeRenderer, { ...props, checkboxRenderer: eyeCheckboxRenderer, imageLoader: imageLoader }));
278
+ /** Custom tree renderer. It uses default `TreeRenderer` but overrides default node renderer to render node with custom checkbox */
279
+ const nodeWithEyeCheckboxTreeRenderer = (props) => (React.createElement(TreeRenderer, { ...props, nodeRenderer: nodeWithEyeCheckboxRenderer }));
280
+ function useTreeFiltering(dataProvider, filter) {
281
+ const nodeLoader = useFilteredProvider(dataProvider, filter);
282
+ const nodeHighlightingProps = useNodeHighlightingProps(filter);
283
+ return {
284
+ nodeLoader,
285
+ modelSource: nodeLoader.modelSource,
286
+ nodeHighlightingProps,
287
+ };
288
+ }
289
+ function useFilteredProvider(dataProvider, filter) {
290
+ const filteredProvider = React.useMemo(() => {
291
+ return new FilteredTreeDataProvider(dataProvider, filter);
292
+ }, [dataProvider, filter]);
293
+ const nodeLoader = React.useMemo(() => {
294
+ return new TreeNodeLoader(filteredProvider, new TreeModelSource());
295
+ }, [filteredProvider]);
296
+ return nodeLoader;
297
+ }
298
+ function useNodeHighlightingProps(filter) {
299
+ const [nodeHighlightingProps, setNodeHighlightingProps] = React.useState();
300
+ React.useEffect(() => {
301
+ if (filter === "") {
302
+ setNodeHighlightingProps(undefined);
303
+ return;
304
+ }
305
+ setNodeHighlightingProps({
306
+ searchText: filter,
307
+ activeMatch: undefined,
308
+ });
309
+ }, [filter]);
310
+ return nodeHighlightingProps;
311
+ }
312
+ class FullTreeHierarchy {
313
+ constructor(dataProvider) {
314
+ this._hierarchy = new Map();
315
+ this._dataProvider = dataProvider;
316
+ this._init = (async () => {
317
+ await this.initNode();
318
+ })();
319
+ }
320
+ async initNode(parent) {
321
+ const nodes = await this._dataProvider.getNodes(parent);
322
+ this._hierarchy.set(parent?.id, nodes);
323
+ for (const node of nodes) {
324
+ await this.initNode(node);
325
+ }
326
+ }
327
+ async getHierarchy() {
328
+ await this._init;
329
+ return this._hierarchy;
330
+ }
331
+ }
332
+ class FilteredTreeHierarchy {
333
+ constructor(dataProvider, filter) {
334
+ this._filtered = new Map();
335
+ this._fullHierarchy = new FullTreeHierarchy(dataProvider);
336
+ this._filter = filter;
337
+ this._init = (async () => {
338
+ await this.init();
339
+ })();
340
+ }
341
+ async init() {
342
+ const hierarchy = await this._fullHierarchy.getHierarchy();
343
+ if (this._filter === "") {
344
+ this._filtered = hierarchy;
345
+ return;
346
+ }
347
+ this.filterNodes(hierarchy);
348
+ }
349
+ /** Initializes `this._filtered` field. Returns a node if it matches a filter. */
350
+ filterNodes(hierarchy, current) {
351
+ const matches = current ? this.matchesFilter(current) : false;
352
+ const children = hierarchy.get(current?.id);
353
+ if (!children)
354
+ return matches ? current : undefined;
355
+ const matchedChildren = new Array();
356
+ for (const child of children) {
357
+ const matchedChild = this.filterNodes(hierarchy, child);
358
+ matchedChild && matchedChildren.push(matchedChild);
359
+ }
360
+ const hasChildren = matchedChildren.length > 0;
361
+ const included = matches || hasChildren;
362
+ let filtered;
363
+ if (included) {
364
+ this._filtered.set(current?.id, matchedChildren);
365
+ // Return a modified copy of current node (to persist initial hierarchy when filter is cleared).
366
+ if (current) {
367
+ filtered = {
368
+ ...current,
369
+ hasChildren,
370
+ autoExpand: hasChildren ? true : current.autoExpand,
371
+ };
372
+ }
373
+ }
374
+ return filtered;
375
+ }
376
+ matchesFilter(node) {
377
+ if (node.label.value.valueFormat !== PropertyValueFormat.Primitive)
378
+ return false;
379
+ const value = node.label.value.displayValue?.toLowerCase();
380
+ if (!value)
381
+ return false;
382
+ return value.includes(this._filter.toLowerCase());
383
+ }
384
+ async getHierarchy() {
385
+ await this._init;
386
+ return this._filtered;
387
+ }
388
+ }
389
+ class FilteredTreeDataProvider {
390
+ constructor(parentDataProvider, filter) {
391
+ this._hierarchy = new FilteredTreeHierarchy(parentDataProvider, filter);
392
+ }
393
+ async getNodes(parent) {
394
+ const hierarchy = await this._hierarchy.getHierarchy();
395
+ const nodes = hierarchy.get(parent?.id);
396
+ return nodes || [];
397
+ }
398
+ async getNodesCount(parent) {
399
+ const hierarchy = await this._hierarchy.getHierarchy();
400
+ const nodes = hierarchy.get(parent?.id);
401
+ return nodes?.length || 0;
402
+ }
403
+ }
415
404
  //# sourceMappingURL=SubLayersTree.js.map