@itwin/map-layers 3.0.0-extension.0 → 3.1.0-dev.5

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 (72) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/README.md +1 -1
  3. package/lib/cjs/MapLayerPreferences.d.ts +89 -0
  4. package/lib/cjs/MapLayerPreferences.d.ts.map +1 -0
  5. package/lib/cjs/MapLayerPreferences.js +312 -0
  6. package/lib/cjs/MapLayerPreferences.js.map +1 -0
  7. package/lib/cjs/mapLayers.d.ts +18 -13
  8. package/lib/cjs/mapLayers.d.ts.map +1 -1
  9. package/lib/cjs/mapLayers.js +25 -17
  10. package/lib/cjs/mapLayers.js.map +1 -1
  11. package/lib/cjs/public/locales/en/mapLayers.json +5 -2
  12. package/lib/cjs/ui/MapLayersUiItemsProvider.js +1 -1
  13. package/lib/cjs/ui/MapLayersUiItemsProvider.js.map +1 -1
  14. package/lib/cjs/ui/widget/AttachLayerPopupButton.d.ts.map +1 -1
  15. package/lib/cjs/ui/widget/AttachLayerPopupButton.js +5 -3
  16. package/lib/cjs/ui/widget/AttachLayerPopupButton.js.map +1 -1
  17. package/lib/cjs/ui/widget/BasemapPanel.d.ts.map +1 -1
  18. package/lib/cjs/ui/widget/BasemapPanel.js +5 -4
  19. package/lib/cjs/ui/widget/BasemapPanel.js.map +1 -1
  20. package/lib/cjs/ui/widget/MapLayerManager.d.ts +2 -2
  21. package/lib/cjs/ui/widget/MapLayerManager.d.ts.map +1 -1
  22. package/lib/cjs/ui/widget/MapLayerManager.js +24 -10
  23. package/lib/cjs/ui/widget/MapLayerManager.js.map +1 -1
  24. package/lib/cjs/ui/widget/MapLayerManager.scss +0 -7
  25. package/lib/cjs/ui/widget/MapManagerSettings.d.ts.map +1 -1
  26. package/lib/cjs/ui/widget/MapManagerSettings.js +51 -22
  27. package/lib/cjs/ui/widget/MapManagerSettings.js.map +1 -1
  28. package/lib/cjs/ui/widget/MapManagerSettings.scss +1 -1
  29. package/lib/cjs/ui/widget/MapUrlDialog.d.ts.map +1 -1
  30. package/lib/cjs/ui/widget/MapUrlDialog.js +231 -161
  31. package/lib/cjs/ui/widget/MapUrlDialog.js.map +1 -1
  32. package/lib/cjs/ui/widget/SubLayersTree.d.ts.map +1 -1
  33. package/lib/cjs/ui/widget/SubLayersTree.js +1 -1
  34. package/lib/cjs/ui/widget/SubLayersTree.js.map +1 -1
  35. package/lib/cjs/ui/widget/TransparencyPopupButton.js +2 -2
  36. package/lib/cjs/ui/widget/TransparencyPopupButton.js.map +1 -1
  37. package/lib/esm/MapLayerPreferences.d.ts +89 -0
  38. package/lib/esm/MapLayerPreferences.d.ts.map +1 -0
  39. package/lib/esm/MapLayerPreferences.js +308 -0
  40. package/lib/esm/MapLayerPreferences.js.map +1 -0
  41. package/lib/esm/mapLayers.d.ts +18 -13
  42. package/lib/esm/mapLayers.d.ts.map +1 -1
  43. package/lib/esm/mapLayers.js +26 -18
  44. package/lib/esm/mapLayers.js.map +1 -1
  45. package/lib/esm/public/locales/en/mapLayers.json +5 -2
  46. package/lib/esm/ui/MapLayersUiItemsProvider.js +1 -1
  47. package/lib/esm/ui/MapLayersUiItemsProvider.js.map +1 -1
  48. package/lib/esm/ui/widget/AttachLayerPopupButton.d.ts.map +1 -1
  49. package/lib/esm/ui/widget/AttachLayerPopupButton.js +6 -4
  50. package/lib/esm/ui/widget/AttachLayerPopupButton.js.map +1 -1
  51. package/lib/esm/ui/widget/BasemapPanel.d.ts.map +1 -1
  52. package/lib/esm/ui/widget/BasemapPanel.js +6 -5
  53. package/lib/esm/ui/widget/BasemapPanel.js.map +1 -1
  54. package/lib/esm/ui/widget/MapLayerManager.d.ts +2 -2
  55. package/lib/esm/ui/widget/MapLayerManager.d.ts.map +1 -1
  56. package/lib/esm/ui/widget/MapLayerManager.js +21 -7
  57. package/lib/esm/ui/widget/MapLayerManager.js.map +1 -1
  58. package/lib/esm/ui/widget/MapLayerManager.scss +0 -7
  59. package/lib/esm/ui/widget/MapManagerSettings.d.ts.map +1 -1
  60. package/lib/esm/ui/widget/MapManagerSettings.js +53 -24
  61. package/lib/esm/ui/widget/MapManagerSettings.js.map +1 -1
  62. package/lib/esm/ui/widget/MapManagerSettings.scss +1 -1
  63. package/lib/esm/ui/widget/MapUrlDialog.d.ts.map +1 -1
  64. package/lib/esm/ui/widget/MapUrlDialog.js +233 -163
  65. package/lib/esm/ui/widget/MapUrlDialog.js.map +1 -1
  66. package/lib/esm/ui/widget/SubLayersTree.d.ts.map +1 -1
  67. package/lib/esm/ui/widget/SubLayersTree.js +1 -1
  68. package/lib/esm/ui/widget/SubLayersTree.js.map +1 -1
  69. package/lib/esm/ui/widget/TransparencyPopupButton.js +2 -2
  70. package/lib/esm/ui/widget/TransparencyPopupButton.js.map +1 -1
  71. package/lib/public/locales/en/mapLayers.json +5 -2
  72. package/package.json +34 -39
@@ -7,13 +7,15 @@
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.MapUrlDialog = exports.MAP_TYPES = void 0;
9
9
  const React = require("react");
10
- const itwinui_react_1 = require("@itwin/itwinui-react");
11
10
  const core_react_1 = require("@itwin/core-react");
12
11
  const appui_react_1 = require("@itwin/appui-react");
12
+ const itwinui_react_1 = require("@itwin/itwinui-react");
13
13
  const MapLayersUiItemsProvider_1 = require("../MapLayersUiItemsProvider");
14
14
  const core_frontend_1 = require("@itwin/core-frontend");
15
15
  require("./MapUrlDialog.scss");
16
16
  const appui_abstract_1 = require("@itwin/appui-abstract");
17
+ const MapLayerPreferences_1 = require("../../MapLayerPreferences");
18
+ const mapLayers_1 = require("../../mapLayers");
17
19
  exports.MAP_TYPES = {
18
20
  wms: "WMS",
19
21
  arcGis: "ArcGIS",
@@ -22,9 +24,8 @@ exports.MAP_TYPES = {
22
24
  };
23
25
  // eslint-disable-next-line @typescript-eslint/naming-convention
24
26
  function MapUrlDialog(props) {
25
- var _a, _b, _c, _d, _e, _f;
27
+ var _a, _b, _c, _d, _e;
26
28
  const { isOverlay, onOkResult, mapTypesOptions } = props;
27
- const supportWmsAuthentication = ((mapTypesOptions === null || mapTypesOptions === void 0 ? void 0 : mapTypesOptions.supportWmsAuthentication) ? true : false);
28
29
  const getMapUrlFromProps = React.useCallback(() => {
29
30
  var _a;
30
31
  if (props.mapLayerSourceToEdit) {
@@ -65,10 +66,9 @@ function MapUrlDialog(props) {
65
66
  const [modelSettingsLabel] = React.useState(MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.StoreOnModelSettings"));
66
67
  const [missingCredentialsLabel] = React.useState(MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.MissingCredentials"));
67
68
  const [invalidCredentialsLabel] = React.useState(MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.InvalidCredentials"));
68
- const [serverRequireCredentials, setServerRequireCredentials] = React.useState((_a = props.layerRequiringCredentials) !== null && _a !== void 0 ? _a : false);
69
+ const [serverRequireCredentials, setServerRequireCredentials] = React.useState(false);
69
70
  const [invalidCredentialsProvided, setInvalidCredentialsProvided] = React.useState(false);
70
71
  const [layerAttachPending, setLayerAttachPending] = React.useState(false);
71
- const [warningMessage, setWarningMessage] = React.useState(props.layerRequiringCredentials ? missingCredentialsLabel : undefined);
72
72
  const [mapUrl, setMapUrl] = React.useState(getMapUrlFromProps());
73
73
  const [mapName, setMapName] = React.useState(getMapNameFromProps());
74
74
  const [userName, setUserName] = React.useState("");
@@ -78,8 +78,9 @@ function MapUrlDialog(props) {
78
78
  const [passwordRequiredLabel] = React.useState(MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:AuthenticationInputs.PasswordRequired"));
79
79
  const [userNameLabel] = React.useState(MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:AuthenticationInputs.Username"));
80
80
  const [userNameRequiredLabel] = React.useState(MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:AuthenticationInputs.UsernameRequired"));
81
- const [settingsStorage, setSettingsStorageRadio] = React.useState("ITwin");
82
- const [mapType, setMapType] = React.useState((_b = getFormatFromProps()) !== null && _b !== void 0 ? _b : exports.MAP_TYPES.arcGis);
81
+ const [settingsStorage, setSettingsStorageRadio] = React.useState("iTwin");
82
+ const [layerAuthMethod, setLayerAuthMethod] = React.useState(core_frontend_1.MapLayerAuthType.None);
83
+ const [mapType, setMapType] = React.useState((_a = getFormatFromProps()) !== null && _a !== void 0 ? _a : exports.MAP_TYPES.arcGis);
83
84
  // 'isMounted' is used to prevent any async operation once the hook has been
84
85
  // unloaded. Otherwise we get a 'Can't perform a React state update on an unmounted component.' warning in the console.
85
86
  const isMounted = React.useRef(false);
@@ -99,15 +100,10 @@ function MapUrlDialog(props) {
99
100
  types.push({ value: exports.MAP_TYPES.tileUrl, label: exports.MAP_TYPES.tileUrl });
100
101
  return types;
101
102
  });
102
- const [isSettingsStorageAvailable] = React.useState(((_d = (_c = props === null || props === void 0 ? void 0 : props.activeViewport) === null || _c === void 0 ? void 0 : _c.iModel) === null || _d === void 0 ? void 0 : _d.iTwinId) && ((_f = (_e = props === null || props === void 0 ? void 0 : props.activeViewport) === null || _e === void 0 ? void 0 : _e.iModel) === null || _f === void 0 ? void 0 : _f.iModelId));
103
+ const [isSettingsStorageAvailable] = React.useState(mapLayers_1.MapLayersUI.iTwinConfig && ((_c = (_b = props === null || props === void 0 ? void 0 : props.activeViewport) === null || _b === void 0 ? void 0 : _b.iModel) === null || _c === void 0 ? void 0 : _c.iTwinId) && ((_e = (_d = props === null || props === void 0 ? void 0 : props.activeViewport) === null || _d === void 0 ? void 0 : _d.iModel) === null || _e === void 0 ? void 0 : _e.iModelId));
103
104
  // Even though the settings storage is available,
104
105
  // we don't always want to enable it in the UI.
105
106
  const [settingsStorageDisabled] = React.useState(!isSettingsStorageAvailable || props.mapLayerSourceToEdit !== undefined || props.layerRequiringCredentials !== undefined);
106
- const isAuthSupported = React.useCallback(() => {
107
- return ((mapType === exports.MAP_TYPES.wms || mapType === exports.MAP_TYPES.wms) && supportWmsAuthentication)
108
- || mapType === exports.MAP_TYPES.arcGis;
109
- }, [mapType, supportWmsAuthentication]);
110
- // const [layerIdxToEdit] = React.useState((): number | undefined => {
111
107
  const [layerRequiringCredentialsIdx] = React.useState(() => {
112
108
  var _a;
113
109
  if (props.layerRequiringCredentials === undefined || !props.layerRequiringCredentials.name || !props.layerRequiringCredentials.url) {
@@ -122,20 +118,15 @@ function MapUrlDialog(props) {
122
118
  }
123
119
  });
124
120
  // Update warning message based on the dialog state and server response
125
- React.useEffect(() => {
126
- if (invalidCredentialsProvided) {
127
- setWarningMessage(invalidCredentialsLabel);
128
- }
129
- else if (serverRequireCredentials && (!userName || !password)) {
130
- setWarningMessage(missingCredentialsLabel);
131
- }
132
- else {
133
- setWarningMessage(undefined);
134
- }
135
- }, [invalidCredentialsProvided, invalidCredentialsLabel, missingCredentialsLabel, serverRequireCredentials, userName, password, setWarningMessage]);
136
121
  const handleMapTypeSelection = React.useCallback((newValue) => {
137
122
  setMapType(newValue);
138
- }, [setMapType]);
123
+ // Reset few states
124
+ if (invalidCredentialsProvided)
125
+ setInvalidCredentialsProvided(false);
126
+ if (layerAuthMethod !== core_frontend_1.MapLayerAuthType.None) {
127
+ setLayerAuthMethod(core_frontend_1.MapLayerAuthType.None);
128
+ }
129
+ }, [invalidCredentialsProvided, layerAuthMethod]);
139
130
  const handleCancel = React.useCallback(() => {
140
131
  if (props.onCancelResult) {
141
132
  props.onCancelResult();
@@ -153,86 +144,109 @@ function MapUrlDialog(props) {
153
144
  if (invalidCredentialsProvided)
154
145
  setInvalidCredentialsProvided(false);
155
146
  }, [setPassword, invalidCredentialsProvided, setInvalidCredentialsProvided]);
156
- const doAttach = React.useCallback(async (source) => {
157
- // Returns a promise, When true, the dialog should closed
158
- return new Promise((resolve, _reject) => {
159
- const vp = props === null || props === void 0 ? void 0 : props.activeViewport;
160
- if (vp === undefined || source === undefined) {
161
- resolve(true);
162
- return;
163
- }
147
+ // return true if authorization is needed
148
+ const updateAuthState = React.useCallback((sourceValidation) => {
149
+ var _a, _b;
150
+ const sourceRequireAuth = (sourceValidation.status === core_frontend_1.MapLayerSourceStatus.RequireAuth);
151
+ const invalidCredentials = (sourceValidation.status === core_frontend_1.MapLayerSourceStatus.InvalidCredentials);
152
+ if (sourceRequireAuth && ((_a = sourceValidation.authInfo) === null || _a === void 0 ? void 0 : _a.authMethod) !== undefined) {
153
+ setLayerAuthMethod((_b = sourceValidation.authInfo) === null || _b === void 0 ? void 0 : _b.authMethod);
154
+ }
155
+ if (invalidCredentials) {
156
+ setInvalidCredentialsProvided(true);
157
+ }
158
+ else if (invalidCredentialsProvided) {
159
+ setInvalidCredentialsProvided(false); // flag reset
160
+ }
161
+ return sourceRequireAuth || invalidCredentials;
162
+ }, [invalidCredentialsProvided]);
163
+ const updateAttachedLayer = React.useCallback(async (source, validation) => {
164
+ const vp = props === null || props === void 0 ? void 0 : props.activeViewport;
165
+ if (vp === undefined || source === undefined || layerRequiringCredentialsIdx === undefined) {
166
+ const error = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerAttachMissingViewOrSource");
167
+ const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerAttachError", { error, sourceUrl: source.url });
168
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
169
+ return true;
170
+ }
171
+ // Layer is already attached,
172
+ vp.displayStyle.changeMapLayerProps({
173
+ subLayers: validation.subLayers,
174
+ }, layerRequiringCredentialsIdx, isOverlay);
175
+ vp.displayStyle.changeMapLayerCredentials(layerRequiringCredentialsIdx, isOverlay, source.userName, source.password);
176
+ // Reset the provider's status
177
+ const provider = vp.getMapLayerImageryProvider(layerRequiringCredentialsIdx, isOverlay);
178
+ if (provider && provider.status !== core_frontend_1.MapLayerImageryProviderStatus.Valid) {
179
+ provider.status = core_frontend_1.MapLayerImageryProviderStatus.Valid;
180
+ }
181
+ vp.invalidateRenderPlan();
182
+ // This handler will close the layer source handler, and therefore the MapUrl dialog.
183
+ // don't call it if the dialog needs to remains open.
184
+ onOkResult();
185
+ return true;
186
+ }, [isOverlay, layerRequiringCredentialsIdx, onOkResult, props.activeViewport]);
187
+ // Returns true if no further input is needed from end-user.
188
+ const doAttach = React.useCallback(async (source, validation) => {
189
+ const vp = props === null || props === void 0 ? void 0 : props.activeViewport;
190
+ if (vp === undefined || source === undefined) {
191
+ const error = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerAttachMissingViewOrSource");
192
+ const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerAttachError", { error, sourceUrl: source.url });
193
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
194
+ return true;
195
+ }
196
+ // Update service settings if storage is available and we are not prompting user for credentials
197
+ if (!settingsStorageDisabled && !props.layerRequiringCredentials) {
164
198
  const storeOnIModel = "Model" === settingsStorage;
165
- source.validateSource(true).then(async (validation) => {
166
- if (validation.status === core_frontend_1.MapLayerSourceStatus.Valid
167
- || validation.status === core_frontend_1.MapLayerSourceStatus.RequireAuth
168
- || validation.status === core_frontend_1.MapLayerSourceStatus.InvalidCredentials) {
169
- const sourceRequireAuth = (validation.status === core_frontend_1.MapLayerSourceStatus.RequireAuth);
170
- const invalidCredentials = (validation.status === core_frontend_1.MapLayerSourceStatus.InvalidCredentials);
171
- const closeDialog = !sourceRequireAuth && !invalidCredentials;
172
- resolve(closeDialog);
173
- if (sourceRequireAuth && !serverRequireCredentials) {
174
- setServerRequireCredentials(true);
175
- }
176
- if (invalidCredentials) {
177
- setInvalidCredentialsProvided(true);
178
- return;
179
- }
180
- else if (invalidCredentialsProvided) {
181
- setInvalidCredentialsProvided(false); // flag reset
182
- }
183
- if (validation.status === core_frontend_1.MapLayerSourceStatus.Valid) {
184
- // Attach layer and update settings service (only if editing)
185
- if (layerRequiringCredentialsIdx !== undefined) {
186
- // Update username / password
187
- vp.displayStyle.changeMapLayerProps({
188
- subLayers: validation.subLayers,
189
- }, layerRequiringCredentialsIdx, isOverlay);
190
- vp.displayStyle.changeMapLayerCredentials(layerRequiringCredentialsIdx, isOverlay, source.userName, source.password);
191
- // Reset the provider's status
192
- const provider = vp.getMapLayerImageryProvider(layerRequiringCredentialsIdx, isOverlay);
193
- if (provider && provider.status !== core_frontend_1.MapLayerImageryProviderStatus.Valid) {
194
- provider.status = core_frontend_1.MapLayerImageryProviderStatus.Valid;
195
- }
196
- }
197
- else {
198
- // Update service settings if storage is available and we are not prompting user for credentials
199
- if (!settingsStorageDisabled && !props.layerRequiringCredentials) {
200
- if (!(await core_frontend_1.MapLayerSettingsService.storeSourceInSettingsService(source, storeOnIModel, vp.iModel.iTwinId, vp.iModel.iModelId)))
201
- return;
202
- }
203
- const layerSettings = source.toLayerSettings(validation.subLayers);
204
- if (layerSettings) {
205
- vp.displayStyle.attachMapLayerSettings(layerSettings, isOverlay, undefined);
206
- const msg = core_frontend_1.IModelApp.localization.getLocalizedString("mapLayers:Messages.MapLayerAttached", { sourceName: source.name, sourceUrl: source.url });
207
- core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Info, msg));
208
- }
209
- else {
210
- const msgError = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerLayerSettingsConversionError");
211
- const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.MapLayerAttachError", { error: msgError, sourceUrl: source.url });
212
- core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
213
- }
214
- }
215
- vp.invalidateRenderPlan();
216
- }
217
- if (closeDialog) {
218
- // This handler will close the layer source handler, and therefore the MapUrl dialog.
219
- // don't call it if the dialog needs to remains open.
220
- onOkResult();
221
- }
199
+ if (!(await MapLayerPreferences_1.MapLayerPreferences.storeSource(source, storeOnIModel, vp.iModel.iTwinId, vp.iModel.iModelId))) {
200
+ const msgError = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerPreferencesStoreFailed");
201
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msgError));
202
+ }
203
+ }
204
+ const layerSettings = source.toLayerSettings(validation.subLayers);
205
+ if (layerSettings) {
206
+ vp.displayStyle.attachMapLayerSettings(layerSettings, isOverlay, undefined);
207
+ const msg = core_frontend_1.IModelApp.localization.getLocalizedString("mapLayers:Messages.MapLayerAttached", { sourceName: source.name, sourceUrl: source.url });
208
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Info, msg));
209
+ }
210
+ else {
211
+ const msgError = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerLayerSettingsConversionError");
212
+ const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.MapLayerAttachError", { error: msgError, sourceUrl: source.url });
213
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
214
+ }
215
+ vp.invalidateRenderPlan();
216
+ // This handler will close the layer source handler, and therefore the MapUrl dialog.
217
+ // don't call it if the dialog needs to remains open.
218
+ onOkResult();
219
+ return true;
220
+ }, [isOverlay, onOkResult, props === null || props === void 0 ? void 0 : props.activeViewport, props.layerRequiringCredentials, settingsStorage, settingsStorageDisabled]);
221
+ // Validate the layer source and attempt to attach (or update) the layer.
222
+ // Returns true if no further input is needed from end-user (i.e. close the dialog)
223
+ const attemptAttachSource = React.useCallback(async (source) => {
224
+ try {
225
+ const validation = await source.validateSource(true);
226
+ if (validation.status === core_frontend_1.MapLayerSourceStatus.Valid) {
227
+ if (layerRequiringCredentialsIdx === undefined) {
228
+ return await doAttach(source, validation);
222
229
  }
223
230
  else {
224
- const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.ValidationError");
225
- core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, `${msg} ${source.url}`));
226
- resolve(true);
231
+ return await updateAttachedLayer(source, validation);
227
232
  }
228
- resolve(false);
229
- }).catch((error) => {
230
- const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.MapLayerAttachError", { error, sourceUrl: source.url });
231
- core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
232
- resolve(true);
233
- });
234
- });
235
- }, [props.activeViewport, props.layerRequiringCredentials, settingsStorage, serverRequireCredentials, invalidCredentialsProvided, onOkResult, layerRequiringCredentialsIdx, isOverlay, settingsStorageDisabled]);
233
+ }
234
+ else if (updateAuthState(validation)) {
235
+ return false;
236
+ }
237
+ else {
238
+ const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.ValidationError");
239
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, `${msg} ${source.url}`));
240
+ return true;
241
+ }
242
+ return false;
243
+ }
244
+ catch (error) {
245
+ const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerAttachError", { error, sourceUrl: source.url });
246
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
247
+ return true;
248
+ }
249
+ }, [updateAuthState, doAttach, layerRequiringCredentialsIdx, updateAttachedLayer]);
236
250
  const onNameChange = React.useCallback((event) => {
237
251
  setMapName(event.target.value);
238
252
  }, [setMapName]);
@@ -242,70 +256,101 @@ function MapUrlDialog(props) {
242
256
  const onUrlChange = React.useCallback((event) => {
243
257
  setMapUrl(event.target.value);
244
258
  }, [setMapUrl]);
245
- const handleOk = React.useCallback(() => {
259
+ const createSource = React.useCallback(() => {
246
260
  let source;
247
261
  if (mapUrl && mapName) {
248
262
  source = core_frontend_1.MapLayerSource.fromJSON({
249
263
  url: mapUrl,
250
264
  name: mapName,
251
265
  formatId: mapType,
252
- userName,
253
- password,
266
+ userName: userName || undefined,
267
+ password: password || undefined
254
268
  });
255
- if (source === undefined || props.mapLayerSourceToEdit) {
256
- appui_react_1.ModalDialogManager.closeDialog();
257
- if (source === undefined) {
258
- // Close the dialog and inform end user something went wrong.
259
- const msgError = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerLayerSourceCreationFailed");
260
- const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:CustomAttach.MapLayerAttachError", { error: msgError, sourceUrl: mapUrl });
261
- core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
262
- return;
263
- }
264
- // Simply change the source definition in the setting service
265
- if (props.mapLayerSourceToEdit !== undefined) {
266
- const vp = props.activeViewport;
267
- void (async () => {
268
- var _a;
269
- if (isSettingsStorageAvailable && vp) {
270
- if (!(await core_frontend_1.MapLayerSettingsService.replaceSourceInSettingsService(props.mapLayerSourceToEdit, source, vp.iModel.iTwinId, vp.iModel.iModelId))) {
271
- const errorMessage = core_frontend_1.IModelApp.localization.getLocalizedString("mapLayers:Messages.MapLayerEditError", { layerName: (_a = props.mapLayerSourceToEdit) === null || _a === void 0 ? void 0 : _a.name });
272
- core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, errorMessage));
273
- return;
274
- }
269
+ }
270
+ return source;
271
+ }, [mapName, mapType, mapUrl, password, userName]);
272
+ const handleOk = React.useCallback(() => {
273
+ const source = createSource();
274
+ if (source === undefined || props.mapLayerSourceToEdit) {
275
+ appui_react_1.ModalDialogManager.closeDialog();
276
+ if (source === undefined) {
277
+ // Close the dialog and inform end user something went wrong.
278
+ const msgError = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerLayerSourceCreationFailed");
279
+ const msg = MapLayersUiItemsProvider_1.MapLayersUiItemsProvider.localization.getLocalizedString("mapLayers:Messages.MapLayerAttachError", { error: msgError, sourceUrl: mapUrl });
280
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, msg));
281
+ return;
282
+ }
283
+ // Simply change the source definition in the setting service
284
+ if (props.mapLayerSourceToEdit !== undefined) {
285
+ const vp = props.activeViewport;
286
+ void (async () => {
287
+ var _a;
288
+ if (isSettingsStorageAvailable && vp) {
289
+ try {
290
+ await MapLayerPreferences_1.MapLayerPreferences.replaceSource(props.mapLayerSourceToEdit, source, vp.iModel.iTwinId, vp.iModel.iModelId);
291
+ }
292
+ catch (err) {
293
+ const errorMessage = core_frontend_1.IModelApp.localization.getLocalizedString("mapLayers:Messages.MapLayerEditError", { layerName: (_a = props.mapLayerSourceToEdit) === null || _a === void 0 ? void 0 : _a.name });
294
+ core_frontend_1.IModelApp.notifications.outputMessage(new core_frontend_1.NotifyMessageDetails(core_frontend_1.OutputMessagePriority.Error, errorMessage));
295
+ return;
275
296
  }
276
- })();
277
- return;
297
+ }
298
+ })();
299
+ return;
300
+ }
301
+ }
302
+ setLayerAttachPending(true);
303
+ // Attach source asynchronously.
304
+ void (async () => {
305
+ try {
306
+ const closeDialog = await attemptAttachSource(source);
307
+ if (isMounted.current) {
308
+ setLayerAttachPending(false);
309
+ }
310
+ // In theory the modal dialog should always get closed by the parent
311
+ // AttachLayerPanel's 'onOkResult' handler. We close it here just in case.
312
+ if (closeDialog) {
313
+ appui_react_1.ModalDialogManager.closeDialog();
278
314
  }
279
315
  }
280
- setLayerAttachPending(true);
281
- void (async () => {
282
- // Code below is executed in the an async manner but
283
- // I don't necessarily want to mark the handler as async
284
- // so Im wrapping it un in a void wrapper.
316
+ catch (_error) {
317
+ appui_react_1.ModalDialogManager.closeDialog();
318
+ }
319
+ })();
320
+ }, [createSource, props.mapLayerSourceToEdit, props.activeViewport, mapUrl, isSettingsStorageAvailable, attemptAttachSource]);
321
+ // The first time the dialog is loaded and we already know the layer requires auth. (i.e ImageryProvider already made an attempt)
322
+ // makes a request to discover the authentification types and adjust UI accordingly (i.e. username/password fields, Oauth popup)
323
+ // Without this effect, user would have to manually click the 'OK' button in order to trigger the layer connection.
324
+ React.useEffect(() => {
325
+ // Attach source asynchronously.
326
+ void (async () => {
327
+ var _a, _b;
328
+ if (((_a = props.layerRequiringCredentials) === null || _a === void 0 ? void 0 : _a.url) !== undefined && ((_b = props.layerRequiringCredentials) === null || _b === void 0 ? void 0 : _b.name) !== undefined) {
285
329
  try {
286
- const closeDialog = await doAttach(source);
287
- if (isMounted.current) {
288
- setLayerAttachPending(false);
289
- }
290
- // In theory the modal dialog should always get closed by the parent
291
- // AttachLayerPanel's 'onOkResult' handler. We close it here just in case.
292
- if (closeDialog) {
293
- appui_react_1.ModalDialogManager.closeDialog();
330
+ const source = core_frontend_1.MapLayerSource.fromJSON({ url: props.layerRequiringCredentials.url, name: props.layerRequiringCredentials.name, formatId: props.layerRequiringCredentials.formatId });
331
+ if (source !== undefined) {
332
+ setLayerAttachPending(true);
333
+ const validation = await source.validateSource(true);
334
+ if (isMounted.current) {
335
+ setLayerAttachPending(false);
336
+ }
337
+ updateAuthState(validation);
294
338
  }
295
339
  }
296
- catch (_error) {
297
- appui_react_1.ModalDialogManager.closeDialog();
298
- }
299
- })();
300
- }
301
- }, [mapUrl, mapName, mapType, userName, password, props.mapLayerSourceToEdit, props.activeViewport, isSettingsStorageAvailable, doAttach]);
340
+ catch (_error) { }
341
+ }
342
+ })();
343
+ // Only run this effect when the dialog is initialized, otherwise it will it creates undesirable side-effects when 'OK' button is clicked.
344
+ // eslint-disable-next-line react-hooks/exhaustive-deps
345
+ }, []);
302
346
  const dialogContainer = React.useRef(null);
303
347
  const readyToSave = React.useCallback(() => {
304
348
  const credentialsSet = !!userName && !!password;
305
349
  return (!!mapUrl && !!mapName)
306
- && (!serverRequireCredentials || (serverRequireCredentials && credentialsSet) && !layerAttachPending)
350
+ && !layerAttachPending
351
+ && (!serverRequireCredentials || credentialsSet)
307
352
  && !invalidCredentialsProvided;
308
- }, [mapUrl, mapName, userName, password, layerAttachPending, invalidCredentialsProvided, serverRequireCredentials]);
353
+ }, [userName, password, mapUrl, mapName, serverRequireCredentials, layerAttachPending, invalidCredentialsProvided]);
309
354
  const buttonCluster = React.useMemo(() => [
310
355
  { type: appui_abstract_1.DialogButtonType.OK, onClick: handleOk, disabled: !readyToSave() },
311
356
  { type: appui_abstract_1.DialogButtonType.Cancel, onClick: handleCancel },
@@ -316,33 +361,58 @@ function MapUrlDialog(props) {
316
361
  handleOk();
317
362
  }
318
363
  }, [handleOk, readyToSave]);
364
+ //
365
+ // Monitors authentication method changes
366
+ React.useEffect(() => {
367
+ setServerRequireCredentials(layerAuthMethod === core_frontend_1.MapLayerAuthType.Basic || layerAuthMethod === core_frontend_1.MapLayerAuthType.EsriToken);
368
+ }, [layerAuthMethod]);
369
+ // Utility function to get warning message section
370
+ function renderWarningMessage() {
371
+ let node;
372
+ let warningMessage;
373
+ // Get the proper warning message
374
+ if (invalidCredentialsProvided) {
375
+ warningMessage = invalidCredentialsLabel;
376
+ }
377
+ else if (serverRequireCredentials && (!userName || !password)) {
378
+ warningMessage = missingCredentialsLabel;
379
+ }
380
+ // Sometimes we want to add an extra node, such as a button
381
+ let extraNode;
382
+ if (warningMessage !== undefined) {
383
+ return (React.createElement("div", { className: "map-layer-source-warnMessage" },
384
+ React.createElement(core_react_1.Icon, { className: "map-layer-source-warnMessage-icon", iconSpec: "icon-status-warning" }),
385
+ React.createElement("span", { className: "map-layer-source-warnMessage-label" }, warningMessage),
386
+ extraNode));
387
+ }
388
+ else {
389
+ return (React.createElement("span", { className: "map-layer-source-placeholder" }, "\u00A0"));
390
+ }
391
+ return node;
392
+ }
319
393
  return (React.createElement("div", { ref: dialogContainer },
320
394
  React.createElement(core_react_1.Dialog, { className: "map-layer-url-dialog", title: dialogTitle, opened: true, resizable: true, movable: true, modal: true, buttonCluster: buttonCluster, onClose: handleCancel, onEscape: handleCancel, minHeight: 120, maxWidth: 600, titleStyle: { paddingLeft: "10px" }, footerStyle: { paddingBottom: "10px", paddingRight: "10px" }, trapFocus: false },
321
395
  React.createElement("div", { className: "map-layer-url-dialog-content" },
322
396
  React.createElement("div", { className: "map-layer-source-url" },
323
397
  React.createElement("span", { className: "map-layer-source-label" }, typeLabel),
324
- React.createElement(itwinui_react_1.Select, { className: "map-layer-source-select", options: mapTypes, value: mapType, disabled: props.layerRequiringCredentials !== undefined || props.mapLayerSourceToEdit !== undefined, onChange: handleMapTypeSelection }),
398
+ React.createElement(itwinui_react_1.Select, { className: "map-layer-source-select", options: mapTypes, value: mapType, disabled: props.layerRequiringCredentials !== undefined || props.mapLayerSourceToEdit !== undefined || layerAttachPending, onChange: handleMapTypeSelection, size: "small" }),
325
399
  React.createElement("span", { className: "map-layer-source-label" }, nameLabel),
326
- React.createElement(itwinui_react_1.Input, { className: "map-layer-source-input", placeholder: nameInputPlaceHolder, onChange: onNameChange, value: mapName, disabled: props.layerRequiringCredentials !== undefined }),
400
+ React.createElement(itwinui_react_1.Input, { className: "map-layer-source-input", placeholder: nameInputPlaceHolder, onChange: onNameChange, value: mapName, disabled: props.layerRequiringCredentials !== undefined || layerAttachPending }),
327
401
  React.createElement("span", { className: "map-layer-source-label" }, urlLabel),
328
- React.createElement(itwinui_react_1.Input, { className: "map-layer-source-input", placeholder: urlInputPlaceHolder, onKeyPress: handleOnKeyDown, onChange: onUrlChange, disabled: props.mapLayerSourceToEdit !== undefined, value: mapUrl }),
329
- isAuthSupported() && props.mapLayerSourceToEdit === undefined &&
402
+ React.createElement(itwinui_react_1.Input, { className: "map-layer-source-input", placeholder: urlInputPlaceHolder, onKeyPress: handleOnKeyDown, onChange: onUrlChange, disabled: props.mapLayerSourceToEdit !== undefined || layerAttachPending, value: mapUrl }),
403
+ serverRequireCredentials
404
+ && (layerAuthMethod === core_frontend_1.MapLayerAuthType.Basic || layerAuthMethod === core_frontend_1.MapLayerAuthType.EsriToken)
405
+ && props.mapLayerSourceToEdit === undefined &&
330
406
  React.createElement(React.Fragment, null,
331
407
  React.createElement("span", { className: "map-layer-source-label" }, userNameLabel),
332
- React.createElement(itwinui_react_1.LabeledInput, { className: "map-layer-source-input", displayStyle: "inline", placeholder: serverRequireCredentials ? userNameRequiredLabel : userNameLabel, status: !userName && serverRequireCredentials ? core_react_1.InputStatus.Warning : undefined, onChange: onUsernameChange }),
408
+ React.createElement(itwinui_react_1.LabeledInput, { className: "map-layer-source-input", displayStyle: "inline", placeholder: serverRequireCredentials ? userNameRequiredLabel : userNameLabel, status: !userName && serverRequireCredentials ? "warning" : undefined, disabled: layerAttachPending, onChange: onUsernameChange, size: "small" }),
333
409
  React.createElement("span", { className: "map-layer-source-label" }, passwordLabel),
334
- React.createElement(itwinui_react_1.LabeledInput, { className: "map-layer-source-input", displayStyle: "inline", type: "password", placeholder: serverRequireCredentials ? passwordRequiredLabel : passwordLabel, status: !password && serverRequireCredentials ? core_react_1.InputStatus.Warning : undefined, onChange: onPasswordChange, onKeyPress: handleOnKeyDown })),
410
+ React.createElement(itwinui_react_1.LabeledInput, { className: "map-layer-source-input", displayStyle: "inline", type: "password", placeholder: serverRequireCredentials ? passwordRequiredLabel : passwordLabel, status: !password && serverRequireCredentials ? "warning" : undefined, disabled: layerAttachPending, onChange: onPasswordChange, onKeyPress: handleOnKeyDown, size: "small" })),
335
411
  isSettingsStorageAvailable && React.createElement("div", { title: settingsStorageDisabled ? noSaveSettingsWarning : "" },
336
412
  React.createElement(itwinui_react_1.Radio, { disabled: settingsStorageDisabled, name: "settingsStorage", value: "iTwin", label: iTwinSettingsLabel, checked: settingsStorage === "iTwin", onChange: onRadioChange }),
337
413
  React.createElement(itwinui_react_1.Radio, { disabled: settingsStorageDisabled, name: "settingsStorage", value: "Model", label: modelSettingsLabel, checked: settingsStorage === "Model", onChange: onRadioChange })))),
338
- React.createElement("div", { className: "map-layer-source-warnMessage" }, warningMessage ?
339
- React.createElement(React.Fragment, null,
340
- React.createElement(core_react_1.Icon, { className: "map-layer-source-warnMessage-icon", iconSpec: "icon-status-warning" }),
341
- React.createElement("span", { className: "map-layer-source-warnMessage-label" }, warningMessage))
342
- :
343
- // Place holder to avoid dialog resize
344
- React.createElement("span", { className: "map-layer-source-placeholder" }, "\u00A0")),
345
- layerAttachPending &&
414
+ renderWarningMessage(),
415
+ (layerAttachPending) &&
346
416
  React.createElement("div", { className: "map-layer-source-progressBar" },
347
417
  React.createElement(itwinui_react_1.ProgressLinear, { indeterminate: true })))));
348
418
  }