@itwin/map-layers 3.0.0-dev.180 → 3.0.0-dev.184

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