From 62045eea632f350e385e2f1b110316738e410b22 Mon Sep 17 00:00:00 2001 From: Michel D'Astous Date: Tue, 8 Mar 2022 09:13:10 -0500 Subject: [PATCH] Fixed issue that would close the new maplayer dialog when a new layer type was picked. (#3313) * Fixed issue that would close the new maplayer dialog when a new layer type was picked. * No need to resume outside click handling when the whole panel is unloaded. * changelog (cherry picked from commit 01a67ef87ce07ac06e96e520c5882d84f045841a) --- ...utsideclick_handling_2022-03-07-21-03.json | 10 ++ .../src/ui/widget/AttachLayerPopupButton.tsx | 94 ++++++++++++------- .../map-layers/src/ui/widget/MapUrlDialog.tsx | 5 +- 3 files changed, 75 insertions(+), 34 deletions(-) create mode 100644 common/changes/@itwin/map-layers/geo-fix_outsideclick_handling_2022-03-07-21-03.json diff --git a/common/changes/@itwin/map-layers/geo-fix_outsideclick_handling_2022-03-07-21-03.json b/common/changes/@itwin/map-layers/geo-fix_outsideclick_handling_2022-03-07-21-03.json new file mode 100644 index 000000000000..b8a57807aa64 --- /dev/null +++ b/common/changes/@itwin/map-layers/geo-fix_outsideclick_handling_2022-03-07-21-03.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/map-layers", + "comment": "Fixed issue that would close the new maplayer dialog when a new layer type was picked.", + "type": "none" + } + ], + "packageName": "@itwin/map-layers" +} \ No newline at end of file diff --git a/extensions/map-layers/src/ui/widget/AttachLayerPopupButton.tsx b/extensions/map-layers/src/ui/widget/AttachLayerPopupButton.tsx index c1085e4ad2fb..7962c9e0dc36 100644 --- a/extensions/map-layers/src/ui/widget/AttachLayerPopupButton.tsx +++ b/extensions/map-layers/src/ui/widget/AttachLayerPopupButton.tsx @@ -16,13 +16,19 @@ import { MapLayersUI } from "../../mapLayers"; // cSpell:ignore droppable Sublayer +enum LayerAction { + Attached, + Edited +} + interface AttachLayerPanelProps { isOverlay: boolean; onLayerAttached: () => void; + onHandleOutsideClick?: (shouldHandle: boolean) => void; } // eslint-disable-next-line @typescript-eslint/naming-convention -function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) { +function AttachLayerPanel({ isOverlay, onLayerAttached, onHandleOutsideClick }: AttachLayerPanelProps) { const [layerNameToAdd, setLayerNameToAdd] = React.useState(); const [sourceFilterString, setSourceFilterString] = React.useState(); @@ -41,6 +47,12 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) const [loading, setLoading] = React.useState(false); const [layerNameUnderCursor, setLayerNameUnderCursor] = React.useState(); + const resumeOutsideClick = React.useCallback(() => { + if (onHandleOutsideClick) { + onHandleOutsideClick(true); + } + }, [onHandleOutsideClick]); + // 'isMounted' is used to prevent any async operation once the hook has been // unloaded. Otherwise we get a 'Can't perform a React state update on an unmounted component.' warning in the console. const isMounted = React.useRef(false); @@ -75,16 +87,21 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) return false; }, [backgroundLayers, overlayLayers]); - const handleModalUrlDialogOk = React.useCallback(() => { + const handleModalUrlDialogOk = React.useCallback((action: LayerAction) => { + if (LayerAction.Attached === action) { // close popup and refresh UI - onLayerAttached(); - }, [onLayerAttached]); + onLayerAttached(); + } + + resumeOutsideClick(); + }, [onLayerAttached, resumeOutsideClick]); const handleModalUrlDialogCancel = React.useCallback(() => { // close popup and refresh UI setLoading(false); ModalDialogManager.closeDialog(); - }, []); + resumeOutsideClick(); + }, [resumeOutsideClick]); React.useEffect(() => { async function attemptToAddLayer(layerName: string) { @@ -129,10 +146,13 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) activeViewport={activeViewport} isOverlay={isOverlay} layerRequiringCredentials={mapLayerSettings.toJSON()} - onOkResult={handleModalUrlDialogOk} + onOkResult={()=>handleModalUrlDialogOk(LayerAction.Attached)} onCancelResult={handleModalUrlDialogCancel} mapTypesOptions={mapTypesOptions} /> ); + if (onHandleOutsideClick) { + onHandleOutsideClick(false); + } } } else { @@ -162,7 +182,7 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) setLayerNameToAdd(undefined); } } - }, [setLayerNameToAdd, layerNameToAdd, activeViewport, sources, backgroundLayers, isOverlay, overlayLayers, onLayerAttached, handleModalUrlDialogOk, mapTypesOptions, handleModalUrlDialogCancel]); + }, [setLayerNameToAdd, layerNameToAdd, activeViewport, sources, backgroundLayers, isOverlay, overlayLayers, onLayerAttached, handleModalUrlDialogOk, mapTypesOptions, handleModalUrlDialogCancel, onHandleOutsideClick]); const options = React.useMemo(() => sources?.filter((source) => !styleContainsLayer(source.name)), [sources, styleContainsLayer]); const filteredOptions = React.useMemo(() => { @@ -174,9 +194,17 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) }, [options, sourceFilterString]); const handleAddNewMapSource = React.useCallback(() => { - ModalDialogManager.openDialog(); + ModalDialogManager.openDialog(handleModalUrlDialogOk(LayerAction.Attached)} + onCancelResult={handleModalUrlDialogCancel} + mapTypesOptions={mapTypesOptions} />); + if (onHandleOutsideClick) { + onHandleOutsideClick(false); + } return; - }, [activeViewport, handleModalUrlDialogOk, isOverlay, mapTypesOptions]); + }, [activeViewport, handleModalUrlDialogCancel, handleModalUrlDialogOk, isOverlay, mapTypesOptions, onHandleOutsideClick]); const handleAttach = React.useCallback((mapName: string) => { setLayerNameToAdd(mapName); @@ -199,7 +227,8 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) const handleNoConfirmation = React.useCallback((_layerName: string) => { ModalDialogManager.closeDialog(); - }, []); + resumeOutsideClick(); + }, [resumeOutsideClick]); const handleYesConfirmation = React.useCallback(async (source: MapLayerSource) => { const layerName = source.name; @@ -215,7 +244,8 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) } ModalDialogManager.closeDialog(); - }, [iTwinId, iModelId]); + resumeOutsideClick(); + }, [iTwinId, iModelId, resumeOutsideClick]); /* Handle Remove layer button clicked @@ -238,7 +268,10 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) onNoResult={() => handleNoConfirmation(layerName)} /> ); - }, [handleNoConfirmation, handleYesConfirmation, removeLayerDefDialogTitle]); + if (onHandleOutsideClick) { + onHandleOutsideClick(false); + } + }, [handleNoConfirmation, handleYesConfirmation, onHandleOutsideClick, removeLayerDefDialogTitle]); /* Handle Edit layer button clicked @@ -257,9 +290,14 @@ function AttachLayerPanel({ isOverlay, onLayerAttached }: AttachLayerPanelProps) activeViewport={activeViewport} isOverlay={isOverlay} mapLayerSourceToEdit={matchingSource} - onOkResult={handleModalUrlDialogOk} + onOkResult={()=>handleModalUrlDialogOk(LayerAction.Edited)} + onCancelResult={handleModalUrlDialogCancel} mapTypesOptions={mapTypesOptions} />); - }, [activeViewport, handleModalUrlDialogOk, isOverlay, mapTypesOptions, sources]); + + if (onHandleOutsideClick) { + onHandleOutsideClick(false); + } + }, [activeViewport, handleModalUrlDialogCancel, handleModalUrlDialogOk, isOverlay, mapTypesOptions, onHandleOutsideClick, sources]); return (
@@ -340,6 +378,7 @@ export function AttachLayerPopupButton(props: AttachLayerPopupButtonProps) { }; }, []); + const [handleOutsideClick, setHandleOutsideClick] = React.useState(true); const [popupOpen, setPopupOpen] = React.useState(false); const buttonRef = React.useRef(null); const panelRef = React.useRef(null); @@ -362,22 +401,8 @@ export function AttachLayerPopupButton(props: AttachLayerPopupButtonProps) { setPopupOpen(false); }, []); - const isInsideCoreDialog = React.useCallback((element: HTMLElement) => { - if (element.nodeName === "DIV") { - if (element.classList && element.classList.contains("core-dialog")) - return true; - if (element.parentElement && isInsideCoreDialog(element.parentElement)) - return true; - } else { - // istanbul ignore else - if (element.parentElement && isInsideCoreDialog(element.parentElement)) - return true; - } - return false; - }, []); - - const handleOutsideClick = React.useCallback((event: MouseEvent) => { - if (isInsideCoreDialog(event.target as HTMLElement)) { + const onHandleOutsideClick = React.useCallback((event: MouseEvent) => { + if (!handleOutsideClick) { return; } @@ -394,7 +419,7 @@ export function AttachLayerPopupButton(props: AttachLayerPopupButtonProps) { // If we reach this point, we got an outside clicked, no close the popup setPopupOpen(false); - }, [isInsideCoreDialog]); + }, [handleOutsideClick]); const { refreshFromStyle } = useSourceMapContext(); @@ -443,13 +468,16 @@ export function AttachLayerPopupButton(props: AttachLayerPopupButtonProps) { isOpen={popupOpen} position={RelativePosition.BottomRight} onClose={handleClosePopup} - onOutsideClick={handleOutsideClick} + onOutsideClick={onHandleOutsideClick} target={buttonRef.current} closeOnEnter={false} closeOnContextMenu={false} >
- +
diff --git a/extensions/map-layers/src/ui/widget/MapUrlDialog.tsx b/extensions/map-layers/src/ui/widget/MapUrlDialog.tsx index f66236327255..0230050a6fd7 100644 --- a/extensions/map-layers/src/ui/widget/MapUrlDialog.tsx +++ b/extensions/map-layers/src/ui/widget/MapUrlDialog.tsx @@ -320,6 +320,7 @@ export function MapUrlDialog(props: MapUrlDialogProps) { if (source === undefined || props.mapLayerSourceToEdit) { ModalDialogManager.closeDialog(); + onOkResult(); if (source === undefined) { // Close the dialog and inform end user something went wrong. @@ -361,13 +362,15 @@ export function MapUrlDialog(props: MapUrlDialogProps) { // AttachLayerPanel's 'onOkResult' handler. We close it here just in case. if (closeDialog) { ModalDialogManager.closeDialog(); + onOkResult(); } } catch (_error) { + onOkResult(); ModalDialogManager.closeDialog(); } })(); - }, [createSource, props.mapLayerSourceToEdit, props.activeViewport, mapUrl, isSettingsStorageAvailable, attemptAttachSource]); + }, [createSource, props.mapLayerSourceToEdit, props.activeViewport, onOkResult, mapUrl, isSettingsStorageAvailable, attemptAttachSource]); // The first time the dialog is loaded and we already know the layer requires auth. (i.e ImageryProvider already made an attempt) // makes a request to discover the authentification types and adjust UI accordingly (i.e. username/password fields, Oauth popup)