From 543a9b49d3d39cd408984af871371f9d4a0607e6 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 6 Sep 2023 14:50:26 -0700 Subject: [PATCH 01/23] chore: enable Wayport releated features and use the new federated dev wayport service --- src/app-config.ts | 2 +- src/components/AppLayout/AppLayout.tsx | 4 ++-- src/components/ListView/Card.tsx | 4 ++-- src/services/export-wayback-bundle/wayportGPService.ts | 8 ++++++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app-config.ts b/src/app-config.ts index 034bf3c..bae2ce2 100644 --- a/src/app-config.ts +++ b/src/app-config.ts @@ -39,7 +39,7 @@ const config: IAppConfig = { 'world-imagery-basemap': 'https://servicesdev.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/', 'wayback-export-base': - 'https://34.220.147.218:6443/arcgis/rest/services/Wayport/GPServer/Wayport', + 'https://wayportdev.maptiles.arcgis.com/arcgis/rest/services/Wayport/GPServer/Wayport', }, }, defaultMapExtent: { diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index e096b57..5c20ca7 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -49,7 +49,7 @@ const AppLayout: React.FC = () => { - {/* */} + @@ -93,7 +93,7 @@ const AppLayout: React.FC = () => { {!onPremises && } - {/* */} + diff --git a/src/components/ListView/Card.tsx b/src/components/ListView/Card.tsx index 89c340c..a0d2d66 100644 --- a/src/components/ListView/Card.tsx +++ b/src/components/ListView/Card.tsx @@ -122,7 +122,7 @@ class ListViewCard extends React.PureComponent { - {/*
{ } > -
*/} +
=> { - const res = await fetch(`${WAYPORT_GP_SERVICE_ROOT}/jobs/${jobId}?f=json`); + const res = await fetch( + `${WAYPORT_GP_SERVICE_ROOT}/jobs/${jobId}?f=json&token=${getToken()}` + ); const data = await res.json(); @@ -126,7 +130,7 @@ export const getJobOutputInfo = async ( jobId: string ): Promise => { const outputRes = await fetch( - `${WAYPORT_GP_SERVICE_ROOT}/jobs/${jobId}/results/output?f=json` + `${WAYPORT_GP_SERVICE_ROOT}/jobs/${jobId}/results/output?f=json&token=${getToken()}` ); const data = (await outputRes.json()) as GetJobOutputResponse; From b0ee3459e53bdd4471cf540203bd9000d6490be4 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Mon, 11 Sep 2023 16:35:30 -0700 Subject: [PATCH 02/23] fix: failed Wayport job should not be removed by cleanUpDownloadJobs thunk --- src/store/DownloadMode/thunks.ts | 4 ++-- src/store/getPreloadedState.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/store/DownloadMode/thunks.ts b/src/store/DownloadMode/thunks.ts index 8d2362c..1fc4825 100644 --- a/src/store/DownloadMode/thunks.ts +++ b/src/store/DownloadMode/thunks.ts @@ -243,8 +243,8 @@ export const cleanUpDownloadJobs = const ageOfJobInSeconds = (now - job.finishTime) / 1000; return ( ageOfJobInSeconds > GP_JOB_TIME_TO_LIVE_IN_SECONDS || - job.status === 'downloaded' || - job.status === 'failed' + job.status === 'downloaded' + // job.status === 'failed' ); }); diff --git a/src/store/getPreloadedState.ts b/src/store/getPreloadedState.ts index 50928fe..d0a5a04 100644 --- a/src/store/getPreloadedState.ts +++ b/src/store/getPreloadedState.ts @@ -145,6 +145,7 @@ const getPreloadedState4Downloadmode = ( const { isDownloadDialogOpen } = urlParams; const jobs = getDownloadJobsFromLocalStorage(); + console.log(jobs); const byId = {}; const ids = []; From 074834769b62cae4205e858b4d361d6216f1148c Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Tue, 12 Sep 2023 11:47:11 -0700 Subject: [PATCH 03/23] fix(store): cleanUpDownloadJobs thunk should only check expired job when finish time is defined --- src/store/DownloadMode/thunks.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/store/DownloadMode/thunks.ts b/src/store/DownloadMode/thunks.ts index 1fc4825..879c328 100644 --- a/src/store/DownloadMode/thunks.ts +++ b/src/store/DownloadMode/thunks.ts @@ -41,7 +41,7 @@ let checkDownloadJobStatusTimeout: NodeJS.Timeout; const CHECK_JOB_STATUS_DELAY_IN_SECONDS = 15; -const GP_JOB_TIME_TO_LIVE_IN_SECONDS = 3600; +const DOWNLOAD_JOB_TIME_TO_LIVE_IN_SECONDS = 3600; export const addToDownloadList = ({ releaseNum, zoomLevel, extent }: AddToDownloadListParams) => @@ -240,12 +240,22 @@ export const cleanUpDownloadJobs = // find jobs that were finished more than 1 hour ago const jobsToBeRemoved = jobs.filter((job) => { - const ageOfJobInSeconds = (now - job.finishTime) / 1000; - return ( - ageOfJobInSeconds > GP_JOB_TIME_TO_LIVE_IN_SECONDS || - job.status === 'downloaded' - // job.status === 'failed' - ); + // downloaded job should be removed + if (job.status === 'downloaded') { + return true; + } + + // any finished job that is 1 hour old should be removed + if (job.finishTime) { + const secondsSinceJobWasFinished = + (now - job.finishTime) / 1000; + return ( + secondsSinceJobWasFinished > + DOWNLOAD_JOB_TIME_TO_LIVE_IN_SECONDS + ); + } + + false; }); for (const job of jobsToBeRemoved) { From dbb04f886b393c19f2a059f738b8d17091abc272 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 27 Sep 2023 07:30:56 -0700 Subject: [PATCH 04/23] fix: uncomment 'onMouseOut' handler in Bar Chart --- src/components/BarChart/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BarChart/index.tsx b/src/components/BarChart/index.tsx index 1d78c50..7ee2961 100644 --- a/src/components/BarChart/index.tsx +++ b/src/components/BarChart/index.tsx @@ -170,7 +170,7 @@ class BarChart extends React.PureComponent { onMouseEnter(d.releaseNum, true); }) .on('mouseout', (d: IWaybackItem) => { - // onMouseOut(); + onMouseOut(); }); } From 5e56b5be2a5f7b4f85a36def3f7a2dc13cf19608 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Mon, 2 Oct 2023 15:27:08 -0700 Subject: [PATCH 05/23] fix: user token should be re-validated when current tab becomes visible again --- src/components/AppLayout/AppLayout.tsx | 14 +++++++++++- src/hooks/useCurrenPageBecomesVisible.tsx | 26 ++++++++++++++++++++++ src/hooks/useVisibilityState.tsx | 24 ++++++++++++++++++++ src/utils/Esri-OAuth/index.ts | 27 +++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useCurrenPageBecomesVisible.tsx create mode 100644 src/hooks/useVisibilityState.tsx diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 5c20ca7..abdf933 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { AboutThisApp, @@ -36,10 +36,22 @@ import { } from '..'; import { AppContext } from '@contexts/AppContextProvider'; import { getServiceUrl } from '@utils/Tier'; +import useCurrenPageBecomesVisible from '@hooks/useCurrenPageBecomesVisible'; +import { revalidateToken } from '@utils/Esri-OAuth'; const AppLayout: React.FC = () => { const { onPremises } = React.useContext(AppContext); + const currentPageIsVisibleAgain = useCurrenPageBecomesVisible(); + + useEffect(() => { + if (!currentPageIsVisibleAgain) { + return; + } + + revalidateToken(); + }, [currentPageIsVisibleAgain]); + return ( <> diff --git a/src/hooks/useCurrenPageBecomesVisible.tsx b/src/hooks/useCurrenPageBecomesVisible.tsx new file mode 100644 index 0000000..9039ad1 --- /dev/null +++ b/src/hooks/useCurrenPageBecomesVisible.tsx @@ -0,0 +1,26 @@ +import React, { useMemo } from 'react'; +import { usePrevious } from './usePrevious'; +import useVisibilityState from './useVisibilityState'; + +/** + * Return true when current page becomes visible again + */ +const useCurrenPageBecomesVisible = () => { + /** + * If true, the tab of current page is visible + */ + const isPageVisible = useVisibilityState(); + + /** + * previous value of the visibility state + */ + const isPageVisiblePrevState = usePrevious(isPageVisible); + + const currentPageIsVisibleAgain = useMemo(() => { + return isPageVisiblePrevState === false && isPageVisible === true; + }, [isPageVisible, isPageVisiblePrevState]); + + return currentPageIsVisibleAgain; +}; + +export default useCurrenPageBecomesVisible; diff --git a/src/hooks/useVisibilityState.tsx b/src/hooks/useVisibilityState.tsx new file mode 100644 index 0000000..91a24ee --- /dev/null +++ b/src/hooks/useVisibilityState.tsx @@ -0,0 +1,24 @@ +import React, { useEffect, useState } from 'react'; + +/** + * Use Page Visibility API to check and see if the current page is visible. + * + * When the user minimizes the window or switches to another tab, + * the API sends a visibilitychange event to let listeners know the state of the page has changed. + * @returns `boolean` if true, the current page is visible + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API + */ +const useVisibilityState = () => { + const [isPageVisible, setIsPageVisible] = useState(true); + + useEffect(() => { + document.addEventListener('visibilitychange', (event) => { + setIsPageVisible(document.visibilityState == 'visible'); + }); + }, []); + + return isPageVisible; +}; + +export default useVisibilityState; diff --git a/src/utils/Esri-OAuth/index.ts b/src/utils/Esri-OAuth/index.ts index 3c6e077..eb81567 100644 --- a/src/utils/Esri-OAuth/index.ts +++ b/src/utils/Esri-OAuth/index.ts @@ -178,3 +178,30 @@ export const getUserRole = (): string => { export const getCredential = (): Credential => { return credential; }; + +export const revalidateToken = async () => { + if (isAnonymouns()) { + return; + } + + const token = getToken(); + + const portalBaseUrl = getPortalBaseUrl(); + + const requestURL = `${portalBaseUrl}/sharing/rest/portals/self?f=json&token=${token}`; + + try { + const res = await fetch(requestURL); + + const data = await res.json(); + + if (data.error) { + throw data.error; + } + } catch (err) { + console.log(err); + + // sign out if current token is invalid, means user has signed out from somewhere else + signOut(); + } +}; From d36bb2e64d184004480b1a5cd3162685654adb81 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Mon, 2 Oct 2023 15:33:11 -0700 Subject: [PATCH 06/23] doc: add comments --- src/components/AppLayout/AppLayout.tsx | 3 +++ src/utils/Esri-OAuth/index.ts | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index abdf933..2cb413d 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -49,6 +49,9 @@ const AppLayout: React.FC = () => { return; } + // should re-validate when current tab becomes visible again, + // so that we can sign out the current user if the token is no longer valid, + // this can heppen when user signs out it's ArcGIS Online account from another tab revalidateToken(); }, [currentPageIsVisibleAgain]); diff --git a/src/utils/Esri-OAuth/index.ts b/src/utils/Esri-OAuth/index.ts index eb81567..3ecae46 100644 --- a/src/utils/Esri-OAuth/index.ts +++ b/src/utils/Esri-OAuth/index.ts @@ -180,6 +180,7 @@ export const getCredential = (): Credential => { }; export const revalidateToken = async () => { + // abort if not signed-in yet if (isAnonymouns()) { return; } @@ -188,6 +189,8 @@ export const revalidateToken = async () => { const portalBaseUrl = getPortalBaseUrl(); + // use portal self request to re-validate the token, + // portal self request with an invalid token would throw an error const requestURL = `${portalBaseUrl}/sharing/rest/portals/self?f=json&token=${token}`; try { @@ -201,7 +204,7 @@ export const revalidateToken = async () => { } catch (err) { console.log(err); - // sign out if current token is invalid, means user has signed out from somewhere else + // sign out if user token is invalid, means user has signed out from somewhere else signOut(); } }; From 69f5eb612d920ebee1f01644a9d4c5d80b8de016 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Thu, 19 Oct 2023 10:38:44 -0700 Subject: [PATCH 07/23] fix(issue #91): add error handler to submit job request --- src/components/DownloadDialog/DownloadJobCard.tsx | 11 +++++++---- .../export-wayback-bundle/wayportGPService.ts | 4 ++++ src/store/DownloadMode/thunks.ts | 11 +++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/DownloadDialog/DownloadJobCard.tsx b/src/components/DownloadDialog/DownloadJobCard.tsx index f37e15b..9a6abce 100644 --- a/src/components/DownloadDialog/DownloadJobCard.tsx +++ b/src/components/DownloadDialog/DownloadJobCard.tsx @@ -33,7 +33,7 @@ type Props = { const ButtonLableByStatus: Record = { 'not started': 'create tile package', - pending: 'in progress...', + pending: 'in progress', finished: 'donwload', failed: 'failed', downloaded: 'downloaded', @@ -90,9 +90,9 @@ export const DownloadJobCard: FC = ({ }, [tileEstimations, levels]); const getStatusIcon = () => { - if (status === 'pending') { - return ; - } + // if (status === 'pending') { + // return ; + // } if (status === 'finished') { return ; @@ -213,6 +213,9 @@ export const DownloadJobCard: FC = ({ )} onClick={buttonOnClickHandler} > + {status === 'pending' && ( + + )} {getButtonLable()}
diff --git a/src/services/export-wayback-bundle/wayportGPService.ts b/src/services/export-wayback-bundle/wayportGPService.ts index 7a86e62..0eeb572 100644 --- a/src/services/export-wayback-bundle/wayportGPService.ts +++ b/src/services/export-wayback-bundle/wayportGPService.ts @@ -111,6 +111,10 @@ export const submitJob = async ({ const data = await res.json(); + if (data.error) { + throw data.error; + } + return data as SubmitJobResponse; }; diff --git a/src/store/DownloadMode/thunks.ts b/src/store/DownloadMode/thunks.ts index 879c328..7f16f20 100644 --- a/src/store/DownloadMode/thunks.ts +++ b/src/store/DownloadMode/thunks.ts @@ -128,15 +128,22 @@ export const startDownloadJob = layerIdentifier: waybackItem.layerIdentifier, }); - const updatedJobData: DownloadJob = { + const submittedJob: DownloadJob = { ...byId[id], GPJobId: res.jobId, status: 'pending', }; - dispatch(downloadJobsUpdated([updatedJobData])); + dispatch(downloadJobsUpdated([submittedJob])); } catch (err) { console.log(err); + + const failedJob: DownloadJob = { + ...byId[id], + status: 'failed', + }; + + dispatch(downloadJobsUpdated([failedJob])); } }; From a4b0c88b7156a2d78253ce64f334c6523418583d Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 8 Nov 2023 10:57:52 -0800 Subject: [PATCH 08/23] fix(issue #90): set default min package level to 12 --- src/components/DownloadDialog/DownloadDialog.tsx | 7 ++++++- src/store/DownloadMode/thunks.ts | 14 +++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/DownloadDialog/DownloadDialog.tsx b/src/components/DownloadDialog/DownloadDialog.tsx index d7f0f63..edf689a 100644 --- a/src/components/DownloadDialog/DownloadDialog.tsx +++ b/src/components/DownloadDialog/DownloadDialog.tsx @@ -72,7 +72,12 @@ export const DownloadDialog: FC = ({ }; return ( -
+
async (dispatch: StoreDispatch, getState: StoreGetState) => { @@ -54,7 +61,7 @@ export const addToDownloadList = const tileEstimations = await getTileEstimationsInOutputBundle( extent, - zoomLevel, + DEFAULT_MIN_LEVEL, releaseNum ); @@ -62,16 +69,17 @@ export const addToDownloadList = // return total + curr.count // }, 0) + const minZoomLevel = DEFAULT_MIN_LEVEL; const maxZoomLevel = tileEstimations[tileEstimations.length - 1].level; const downloadJob: DownloadJob = { id: nanoid(), waybackItem: byReleaseNumber[releaseNum], - minZoomLevel: zoomLevel, + minZoomLevel, maxZoomLevel, tileEstimations, // totalTiles, - levels: [zoomLevel, maxZoomLevel], + levels: [minZoomLevel, maxZoomLevel], extent, status: 'not started', // createdTime: new Date().getTime(), From d91ab67023168eccb7c2b10fccf0023d9f6ca019 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 8 Nov 2023 12:59:06 -0800 Subject: [PATCH 09/23] fix: minor fix to Download Dialog --- src/components/DownloadDialog/DownloadDialog.tsx | 2 +- src/components/DownloadDialog/DownloadJobCard.tsx | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/DownloadDialog/DownloadDialog.tsx b/src/components/DownloadDialog/DownloadDialog.tsx index edf689a..aeb875a 100644 --- a/src/components/DownloadDialog/DownloadDialog.tsx +++ b/src/components/DownloadDialog/DownloadDialog.tsx @@ -89,7 +89,7 @@ export const DownloadDialog: FC = ({
-

Download Local Tile Cache

+

Download Tile Package

Based on your current map extent, choose a scale range diff --git a/src/components/DownloadDialog/DownloadJobCard.tsx b/src/components/DownloadDialog/DownloadJobCard.tsx index 9a6abce..2a43a9f 100644 --- a/src/components/DownloadDialog/DownloadJobCard.tsx +++ b/src/components/DownloadDialog/DownloadJobCard.tsx @@ -36,7 +36,7 @@ const ButtonLableByStatus: Record = { pending: 'in progress', finished: 'donwload', failed: 'failed', - downloaded: 'downloaded', + downloaded: 'CHECK BROWSER FOR DOWNLOAD PROGRESS', }; export const DownloadJobCard: FC = ({ @@ -94,9 +94,9 @@ export const DownloadJobCard: FC = ({ // return ; // } - if (status === 'finished') { - return ; - } + // if (status === 'finished') { + // return ; + // } return ( = ({ const getButtonLable = () => { if (status === 'finished' && outputTilePackageInfo !== undefined) { const sizeInMB = (outputTilePackageInfo.size / 1000000).toFixed(1); - return `Download - ${sizeInMB}MB`; + return `Tiles Ready to Download - ${sizeInMB}MB`; } return ButtonLableByStatus[status] || status; @@ -206,7 +206,7 @@ export const DownloadJobCard: FC = ({

Date: Wed, 8 Nov 2023 15:44:34 -0800 Subject: [PATCH 10/23] fix(issue #90): zoom level slider should include levels from 12 to the last level that has tile at center of the current map extent --- .../DownloadDialog/DownloadJobCard.tsx | 34 ++++++++++++++++--- .../getTileEstimationsInOutputBundle.ts | 27 +++++++++------ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/components/DownloadDialog/DownloadJobCard.tsx b/src/components/DownloadDialog/DownloadJobCard.tsx index 2a43a9f..649e010 100644 --- a/src/components/DownloadDialog/DownloadJobCard.tsx +++ b/src/components/DownloadDialog/DownloadJobCard.tsx @@ -2,6 +2,7 @@ import React, { FC, useEffect, useMemo } from 'react'; import classnames from 'classnames'; import { DownloadJob, DownloadJobStatus } from '@store/DownloadMode/reducer'; import { numberFns } from 'helper-toolkit-ts'; +import { MAX_NUMBER_TO_TILES_PER_WAYPORT_EXPORT } from '@services/export-wayback-bundle/getTileEstimationsInOutputBundle'; type Props = { data: DownloadJob; @@ -127,6 +128,24 @@ export const DownloadJobCard: FC = ({ return ButtonLableByStatus[status] || status; }; + /** + * get formatted total number of title. Use comma separated if total is less than 1 million, + * otherwise, use abbreviation instead + * @param total + * @returns + */ + const formatTotalNumOfTiles = (total: number) => { + if (!total) { + return 0; + } + + if (total < 1e6) { + return numberFns.numberWithCommas(total); + } + + return numberFns.abbreviateNumber(total); + }; + const shouldDisableActionButton = () => { if ( status === 'pending' || @@ -140,6 +159,13 @@ export const DownloadJobCard: FC = ({ return true; } + if ( + status === 'not started' && + totalTiles > MAX_NUMBER_TO_TILES_PER_WAYPORT_EXPORT + ) { + return true; + } + return false; }; @@ -160,7 +186,7 @@ export const DownloadJobCard: FC = ({ return (
-
+
{getStatusIcon()}
@@ -189,7 +215,7 @@ export const DownloadJobCard: FC = ({ >
-
+
Level {levels[0]} - {levels[1]} @@ -197,9 +223,7 @@ export const DownloadJobCard: FC = ({
- - ~{numberFns.numberWithCommas(totalTiles)} tiles - + ~{formatTotalNumOfTiles(totalTiles)} tiles
diff --git a/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts b/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts index bdcdd55..138d61a 100644 --- a/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts +++ b/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts @@ -19,7 +19,7 @@ const WaybackImageBaseURL = getServiceUrl('wayback-imagery-base'); /** * maximum number of tiles allowed by the service */ -const MAX_NUM_TILES = 150000; +export const MAX_NUMBER_TO_TILES_PER_WAYPORT_EXPORT = 150000; /** * Get estimations of tiles that can be included in the output bundle. @@ -40,15 +40,15 @@ export const getTileEstimationsInOutputBundle = async ( ): Promise => { const tileEstimations: TileEstimation[] = []; - const UpperLimit = MAX_NUM_TILES * 1.1; + // const UpperLimit = MAX_NUM_TILES * 1.1; /** * a helper function to get estimation by zoom level recursively * @param zoomLevel * @returns void */ - const helper = (zoomLevel: number, total = 0) => { - if (total >= UpperLimit || zoomLevel > 23) { + const helper = (zoomLevel: number) => { + if (zoomLevel > 23) { return; } @@ -64,14 +64,19 @@ export const getTileEstimationsInOutputBundle = async ( const cols = Math.abs(tileColMax - tileColMin) + 1; const count = rows * cols; - if (total + count <= UpperLimit) { - tileEstimations.push({ - level: zoomLevel, - count, - }); - } + // if (total + count <= UpperLimit) { + // tileEstimations.push({ + // level: zoomLevel, + // count, + // }); + // } + + tileEstimations.push({ + level: zoomLevel, + count, + }); - helper(zoomLevel + 1, total + count); + helper(zoomLevel + 1); }; helper(minZoomLevel); From 177a9f036f2126548f6c1a5bb17b7034988070b4 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 8 Nov 2023 15:46:57 -0800 Subject: [PATCH 11/23] fix(issue #87): use 5 decimal places instead of 3 --- src/utils/UrlSearchParam/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/UrlSearchParam/index.ts b/src/utils/UrlSearchParam/index.ts index e9a7f3b..40fa3ea 100644 --- a/src/utils/UrlSearchParam/index.ts +++ b/src/utils/UrlSearchParam/index.ts @@ -136,7 +136,7 @@ const saveFrames2ExcludeInURLQueryParam = (rNums: number[]): void => { export const saveMapCenterToHashParams = (center: MapCenter, zoom: number) => { const { lon, lat } = center; - const value = `${lon.toFixed(3)},${lat.toFixed(3)},${zoom}`; + const value = `${lon.toFixed(5)},${lat.toFixed(5)},${zoom}`; updateHashParams('mapCenter', value); // remove ext from URL as it is no longer needed updateHashParams('ext', null); From e17a1ee087dcd8e23162960717008514a4efad67 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 8 Nov 2023 16:03:27 -0800 Subject: [PATCH 12/23] fix(issue #90): zoom level slider should have two handles --- src/components/DownloadDialog/DownloadJobCard.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/DownloadDialog/DownloadJobCard.tsx b/src/components/DownloadDialog/DownloadJobCard.tsx index 649e010..b70de73 100644 --- a/src/components/DownloadDialog/DownloadJobCard.tsx +++ b/src/components/DownloadDialog/DownloadJobCard.tsx @@ -173,9 +173,14 @@ export const DownloadJobCard: FC = ({ sliderRef.current.addEventListener( 'calciteSliderChange', (evt: any) => { - const userSelectedMaxZoomLevel = +evt.target.value; - - levelsOnChange(id, [levels[0], userSelectedMaxZoomLevel]); + const userSelectedMinZoomLevel = +evt.target.minValue; + const userSelectedMaxZoomLevel = +evt.target.maxValue; + // console.log(evt.target.minValue,evt.target.maxValue) + + levelsOnChange(id, [ + userSelectedMinZoomLevel, + userSelectedMaxZoomLevel, + ]); } ); }, []); @@ -208,7 +213,9 @@ export const DownloadJobCard: FC = ({ : maxZoomLevel } min={minZoomLevel} - value={levels[1]} + // value={levels[1]} + min-value={levels[0]} + max-value={levels[1]} step="1" ticks="1" {...sliderProp} From 5b7d58bb5e001e2ab42d6a68206f9ec36850c94c Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 8 Nov 2023 17:36:04 -0800 Subject: [PATCH 13/23] fix: download button should be enabled when zoom level is 12+ --- src/components/ListView/Card.tsx | 2 +- src/components/ListView/ListViewContainer.tsx | 4 ++-- src/components/ListView/index.tsx | 10 ++++++---- .../getTileEstimationsInOutputBundle.ts | 2 ++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/ListView/Card.tsx b/src/components/ListView/Card.tsx index a0d2d66..0a54edc 100644 --- a/src/components/ListView/Card.tsx +++ b/src/components/ListView/Card.tsx @@ -139,7 +139,7 @@ class ListViewCard extends React.PureComponent { title={ shouldDownloadButtonBeDisabled ? 'Reached the maximum limit for download jobs' - : 'Download a local copy of imagery tiles' + : 'Download a package of imagery tiles of current map extent' } > diff --git a/src/components/ListView/ListViewContainer.tsx b/src/components/ListView/ListViewContainer.tsx index fa11ac7..bee4803 100644 --- a/src/components/ListView/ListViewContainer.tsx +++ b/src/components/ListView/ListViewContainer.tsx @@ -83,8 +83,8 @@ const ListViewContainer = () => { isMobile={isMobile} waybackItems={waybackItems} activeWaybackItem={activeWaybackItem} - hasReachedLimitOfConcurrentDownloadJobs={ - hasReachedLimitOfConcurrentDownloadJobs + shouldDownloadButtonBeDisabled={ + hasReachedLimitOfConcurrentDownloadJobs || zoom < 12 } shouldOnlyShowItemsWithLocalChange={ shouldOnlyShowItemsWithLocalChange diff --git a/src/components/ListView/index.tsx b/src/components/ListView/index.tsx index 00425cd..d0bea79 100644 --- a/src/components/ListView/index.tsx +++ b/src/components/ListView/index.tsx @@ -15,9 +15,11 @@ interface IProps { rNum4WaybackItemsWithLocalChanges: Array; /** * if ture, the The donwload button will be disabled. - * the user can only have limited of number of download jobs in the list. + * The download button should only be enabled if + * - number of download jobs has not reached to the limit + * - map zoom level is 12+ */ - hasReachedLimitOfConcurrentDownloadJobs: boolean; + shouldDownloadButtonBeDisabled: boolean; toggleSelect?: (releaseNum: number) => void; onClick?: (releaseNum: number) => void; @@ -66,7 +68,7 @@ class ListView extends React.PureComponent { rNum4SelectedWaybackItems, rNum4WaybackItemsWithLocalChanges, shouldOnlyShowItemsWithLocalChange, - hasReachedLimitOfConcurrentDownloadJobs, + shouldDownloadButtonBeDisabled, toggleSelect, onClick, onMouseEnter, @@ -104,7 +106,7 @@ class ListView extends React.PureComponent { isHighlighted={isHighlighted} toggleSelect={toggleSelect} shouldDownloadButtonBeDisabled={ - hasReachedLimitOfConcurrentDownloadJobs + shouldDownloadButtonBeDisabled } onClick={onClick} onMouseEnter={onMouseEnter} diff --git a/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts b/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts index 138d61a..f75f583 100644 --- a/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts +++ b/src/services/export-wayback-bundle/getTileEstimationsInOutputBundle.ts @@ -93,6 +93,8 @@ export const getTileEstimationsInOutputBundle = async ( if (shouldBeIncluded) { output.push(tileEstimation); + } else { + break; } } From aecf0261a811ab597989d06b0c7316413b5f48e3 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Thu, 9 Nov 2023 13:06:52 -0800 Subject: [PATCH 14/23] fix: minor UI tweaks --- src/components/DownloadDialog/DownloadJobCard.tsx | 1 + src/components/ListView/Card.tsx | 4 ++-- .../OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx | 4 +--- .../SaveAsWebmapBtn/SaveAsWebmapBtnContainer.tsx | 8 ++++++-- src/style/index.css | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/DownloadDialog/DownloadJobCard.tsx b/src/components/DownloadDialog/DownloadJobCard.tsx index b70de73..cdf3a43 100644 --- a/src/components/DownloadDialog/DownloadJobCard.tsx +++ b/src/components/DownloadDialog/DownloadJobCard.tsx @@ -106,6 +106,7 @@ export const DownloadJobCard: FC = ({ style={{ cursor: 'pointer', }} + title="Cancel" onClick={removeButtonOnClick.bind(null, id)} /> ); diff --git a/src/components/ListView/Card.tsx b/src/components/ListView/Card.tsx index 0a54edc..fe058a1 100644 --- a/src/components/ListView/Card.tsx +++ b/src/components/ListView/Card.tsx @@ -138,8 +138,8 @@ class ListViewCard extends React.PureComponent { }} title={ shouldDownloadButtonBeDisabled - ? 'Reached the maximum limit for download jobs' - : 'Download a package of imagery tiles of current map extent' + ? 'Reached the maximum limit of 5 concurrent download jobs' + : 'Download an imagery tile package for the current map extent' // TO-DO: handle situation zoom out situation, add "(zoom in to enable)" If zoomed out, show zoom in reminder first befor reaching max limit } > diff --git a/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx b/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx index a60c836..f51648b 100644 --- a/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx +++ b/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx @@ -55,9 +55,7 @@ export const OpenDownloadPanelBtn = () => { disabled: numOfJobs === 0, } )} - title={ - 'download local copies of imagery tiles via the release row item' - } + title={'Choose a version from the list to download a tile package'} onClick={() => { dispatch(isDownloadDialogOpenToggled()); }} diff --git a/src/components/SaveAsWebmapBtn/SaveAsWebmapBtnContainer.tsx b/src/components/SaveAsWebmapBtn/SaveAsWebmapBtnContainer.tsx index b20b2a9..3b3e4d4 100644 --- a/src/components/SaveAsWebmapBtn/SaveAsWebmapBtnContainer.tsx +++ b/src/components/SaveAsWebmapBtn/SaveAsWebmapBtnContainer.tsx @@ -35,8 +35,12 @@ const SaveAsWebmapBtnContainer = () => { const isAnimationModeOn: boolean = useSelector(isAnimationModeOnSelector); const isDisabled = useMemo(() => { - return isSwipeWidgetOpen || isAnimationModeOn; - }, [isSwipeWidgetOpen, isAnimationModeOn]); + return ( + isSwipeWidgetOpen || + isAnimationModeOn || + rNum4SelectedWaybackItems?.length === 0 + ); + }, [isSwipeWidgetOpen, isAnimationModeOn, rNum4SelectedWaybackItems]); const clearAllBtnOnClick = () => { dispatch(releaseNum4SelectedItemsCleaned()); diff --git a/src/style/index.css b/src/style/index.css index eedcdba..1531c92 100644 --- a/src/style/index.css +++ b/src/style/index.css @@ -18,6 +18,7 @@ html, body { @apply p-0 m-0 w-full h-full overflow-hidden; color: var(--default-text-color); + background: #000; } #appRootDiv { @apply relative w-full h-full; From 059fc00a090cabe0f83a00f652a0f07ad7dff851 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Thu, 9 Nov 2023 13:33:06 -0800 Subject: [PATCH 15/23] feat: add a placeholder for the new download job before all the data is ready --- src/components/DownloadDialog/DownloadDialog.tsx | 15 ++++++++++++--- .../DownloadDialog/DownloadDialogContainer.tsx | 4 ++++ .../DownloadDialog/DownloadJobPlaceholder.tsx | 11 +++++++++++ src/store/DownloadMode/reducer.ts | 13 +++++++++++++ src/store/DownloadMode/selectors.ts | 5 +++++ src/store/DownloadMode/thunks.ts | 8 +++++++- 6 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 src/components/DownloadDialog/DownloadJobPlaceholder.tsx diff --git a/src/components/DownloadDialog/DownloadDialog.tsx b/src/components/DownloadDialog/DownloadDialog.tsx index aeb875a..0994aa3 100644 --- a/src/components/DownloadDialog/DownloadDialog.tsx +++ b/src/components/DownloadDialog/DownloadDialog.tsx @@ -1,12 +1,18 @@ import { DownloadJob } from '@store/DownloadMode/reducer'; import React, { FC } from 'react'; import { DownloadJobCard } from './DownloadJobCard'; +import { DownloadJobPlaceholder } from './DownloadJobPlaceholder'; type Props = { /** * list of donwload jobs */ jobs: DownloadJob[]; + /** + * if true, the system is in process of adding a new download job and + * a placeholder card should be displayed + */ + isAddingNewDownloadJob: boolean; /** * fires when user clicks on the create tile package button to start the download job * @param id job id @@ -40,6 +46,7 @@ type Props = { export const DownloadDialog: FC = ({ jobs, + isAddingNewDownloadJob, createTilePackageButtonOnClick, downloadTilePackageButtonOnClick, closeButtonOnClick, @@ -47,7 +54,7 @@ export const DownloadDialog: FC = ({ levelsOnChange, }: Props) => { const getJobsList = () => { - if (!jobs?.length) { + if (!jobs?.length && !isAddingNewDownloadJob) { return
No download jobs.
; } @@ -78,7 +85,7 @@ export const DownloadDialog: FC = ({ background: `radial-gradient(circle, rgba(26,61,96,0.95) 50%, rgba(13,31,49,0.95) 100%)`, }} > -
+
= ({ />
-
+

Download Tile Package

@@ -98,6 +105,8 @@ export const DownloadDialog: FC = ({ {/* You can choose this window while your tiles are prepared. */}

+ {isAddingNewDownloadJob && } +
{getJobsList()}
diff --git a/src/components/DownloadDialog/DownloadDialogContainer.tsx b/src/components/DownloadDialog/DownloadDialogContainer.tsx index cd842e5..c8fcac4 100644 --- a/src/components/DownloadDialog/DownloadDialogContainer.tsx +++ b/src/components/DownloadDialog/DownloadDialogContainer.tsx @@ -7,6 +7,7 @@ import { import { selectDownloadJobs, + selectIsAddingNewDownloadJob, selectIsDownloadDialogOpen, selectNumOfPendingDownloadJobs, } from '@store/DownloadMode/selectors'; @@ -33,6 +34,8 @@ export const DownloadDialogContainer = () => { const numPendingJobs = useSelector(selectNumOfPendingDownloadJobs); + const isAddingNewDownloadJob = useSelector(selectIsAddingNewDownloadJob); + useEffect(() => { // save jobs to localhost so they can be restored saveDownloadJobs2LocalStorage(jobs); @@ -65,6 +68,7 @@ export const DownloadDialogContainer = () => { return ( { dispatch(isDownloadDialogOpenToggled()); }} diff --git a/src/components/DownloadDialog/DownloadJobPlaceholder.tsx b/src/components/DownloadDialog/DownloadJobPlaceholder.tsx new file mode 100644 index 0000000..95a5e40 --- /dev/null +++ b/src/components/DownloadDialog/DownloadJobPlaceholder.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export const DownloadJobPlaceholder = () => { + return ( +
+
+ +
+
+ ); +}; diff --git a/src/store/DownloadMode/reducer.ts b/src/store/DownloadMode/reducer.ts index e8d3cd1..6061b60 100644 --- a/src/store/DownloadMode/reducer.ts +++ b/src/store/DownloadMode/reducer.ts @@ -94,6 +94,14 @@ export type DownloadModeState = { ids: string[]; }; isDownloadDialogOpen: boolean; + /** + * If true, the system is currently in the process of adding a new download job. + * + * Why is this necessary? When creating a new download job, the `getTileEstimationsInOutputBundle` function will be invoked, + * and this function might take 1-2 seconds to resolve. Therefore showing a loading indicator should inform the user + * that their request has been received. + */ + isAddingNewDownloadJob: boolean; }; export const initialDownloadModeState = { @@ -102,6 +110,7 @@ export const initialDownloadModeState = { ids: [], }, isDownloadDialogOpen: false, + isAddingNewDownloadJob: false, } as DownloadModeState; const slice = createSlice({ @@ -111,6 +120,9 @@ const slice = createSlice({ isDownloadDialogOpenToggled: (state) => { state.isDownloadDialogOpen = !state.isDownloadDialogOpen; }, + isAddingNewDownloadJobToggled: (state) => { + state.isAddingNewDownloadJob = !state.isAddingNewDownloadJob; + }, downloadJobCreated: (state, action: PayloadAction) => { const { id } = action.payload; state.jobs.byId[id] = action.payload; @@ -136,6 +148,7 @@ const { reducer } = slice; export const { isDownloadDialogOpenToggled, + isAddingNewDownloadJobToggled, downloadJobCreated, downloadJobRemoved, downloadJobsUpdated, diff --git a/src/store/DownloadMode/selectors.ts b/src/store/DownloadMode/selectors.ts index ca61b1e..4abc0e0 100644 --- a/src/store/DownloadMode/selectors.ts +++ b/src/store/DownloadMode/selectors.ts @@ -6,6 +6,11 @@ export const selectIsDownloadDialogOpen = createSelector( (isDownloadDialogOpen) => isDownloadDialogOpen ); +export const selectIsAddingNewDownloadJob = createSelector( + (state: RootState) => state.DownloadMode.isAddingNewDownloadJob, + (isAddingNewDownloadJob) => isAddingNewDownloadJob +); + export const selectDownloadJobs = createSelector( (state: RootState) => state.DownloadMode.jobs, (jobs) => { diff --git a/src/store/DownloadMode/thunks.ts b/src/store/DownloadMode/thunks.ts index 71fe262..55317a9 100644 --- a/src/store/DownloadMode/thunks.ts +++ b/src/store/DownloadMode/thunks.ts @@ -7,6 +7,7 @@ import { downloadJobCreated, downloadJobRemoved, downloadJobsUpdated, + isAddingNewDownloadJobToggled, isDownloadDialogOpenToggled, } from './reducer'; import { nanoid } from 'nanoid'; @@ -55,6 +56,11 @@ export const addToDownloadList = async (dispatch: StoreDispatch, getState: StoreGetState) => { // console.log(waybackItem, zoomLevel, extent); + batch(() => { + dispatch(isAddingNewDownloadJobToggled()); + dispatch(isDownloadDialogOpenToggled()); + }); + const { WaybackItems } = getState(); const { byReleaseNumber } = WaybackItems; @@ -87,7 +93,7 @@ export const addToDownloadList = batch(() => { dispatch(downloadJobCreated(downloadJob)); - dispatch(isDownloadDialogOpenToggled()); + dispatch(isAddingNewDownloadJobToggled()); }); }; From a187f270ba7769cea03945c1f329e95361001c9c Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Thu, 9 Nov 2023 14:09:16 -0800 Subject: [PATCH 16/23] fix: tooltip of Donwload Button --- src/components/ListView/Card.tsx | 12 ++++++------ src/components/ListView/ListViewContainer.tsx | 18 +++++++++++++++++- src/components/ListView/index.tsx | 7 ++++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/components/ListView/Card.tsx b/src/components/ListView/Card.tsx index fe058a1..e4903fa 100644 --- a/src/components/ListView/Card.tsx +++ b/src/components/ListView/Card.tsx @@ -13,7 +13,10 @@ interface IProps { * if true, download button should be disabled */ shouldDownloadButtonBeDisabled?: boolean; - + /** + * tooltip text for download button + */ + downloadButtonTooltipText: string; toggleSelect?: (releaseNum: number) => void; onClick?: (releaseNum: number) => void; downloadButtonOnClick: (releaseNum: number) => void; @@ -73,6 +76,7 @@ class ListViewCard extends React.PureComponent { isSelected, isHighlighted, shouldDownloadButtonBeDisabled, + downloadButtonTooltipText, onClick, onMouseEnter, onMouseOut, @@ -136,11 +140,7 @@ class ListViewCard extends React.PureComponent { downloadButtonOnClick(data.releaseNum); }} - title={ - shouldDownloadButtonBeDisabled - ? 'Reached the maximum limit of 5 concurrent download jobs' - : 'Download an imagery tile package for the current map extent' // TO-DO: handle situation zoom out situation, add "(zoom in to enable)" If zoomed out, show zoom in reminder first befor reaching max limit - } + title={downloadButtonTooltipText} >
diff --git a/src/components/ListView/ListViewContainer.tsx b/src/components/ListView/ListViewContainer.tsx index bee4803..2fb42fa 100644 --- a/src/components/ListView/ListViewContainer.tsx +++ b/src/components/ListView/ListViewContainer.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useMemo } from 'react'; import { useSelector, @@ -77,6 +77,21 @@ const ListViewContainer = () => { const mapExtent = useSelector(mapExtentSelector); + const downloadButtonTooltipText = useMemo(() => { + const text = + 'Download an imagery tile package for the current map extent'; + + if (zoom < 12) { + return text + ` (zoom in to enable)`; + } + + if (hasReachedLimitOfConcurrentDownloadJobs) { + return 'Reached the maximum limit of 5 concurrent download jobs'; + } + + return text; + }, [zoom, hasReachedLimitOfConcurrentDownloadJobs]); + return ( { shouldDownloadButtonBeDisabled={ hasReachedLimitOfConcurrentDownloadJobs || zoom < 12 } + downloadButtonTooltipText={downloadButtonTooltipText} shouldOnlyShowItemsWithLocalChange={ shouldOnlyShowItemsWithLocalChange } diff --git a/src/components/ListView/index.tsx b/src/components/ListView/index.tsx index d0bea79..d0de1c3 100644 --- a/src/components/ListView/index.tsx +++ b/src/components/ListView/index.tsx @@ -20,7 +20,10 @@ interface IProps { * - map zoom level is 12+ */ shouldDownloadButtonBeDisabled: boolean; - + /** + * tooltip text for download button + */ + downloadButtonTooltipText: string; toggleSelect?: (releaseNum: number) => void; onClick?: (releaseNum: number) => void; downloadButtonOnClick: (releaseNum: number) => void; @@ -69,6 +72,7 @@ class ListView extends React.PureComponent { rNum4WaybackItemsWithLocalChanges, shouldOnlyShowItemsWithLocalChange, shouldDownloadButtonBeDisabled, + downloadButtonTooltipText, toggleSelect, onClick, onMouseEnter, @@ -108,6 +112,7 @@ class ListView extends React.PureComponent { shouldDownloadButtonBeDisabled={ shouldDownloadButtonBeDisabled } + downloadButtonTooltipText={downloadButtonTooltipText} onClick={onClick} onMouseEnter={onMouseEnter} onMouseOut={onMouseOut} From 92a671d5a0ec9668ef406b7c34f81b85b266b2e2 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Thu, 9 Nov 2023 14:35:36 -0800 Subject: [PATCH 17/23] fix(issue #93): replaced share button using copy button --- src/components/AppLayout/AppLayout.tsx | 8 +- src/components/Gutter/GutterContainer.tsx | 17 +-- src/components/Gutter/index.tsx | 142 ++++++++++++---------- src/utils/snippets/copy2clipborad.ts | 21 ++++ 4 files changed, 112 insertions(+), 76 deletions(-) create mode 100644 src/utils/snippets/copy2clipborad.ts diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 2cb413d..451eac7 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -16,7 +16,7 @@ import { ReferenceLayerToggle, Sidebar, SearchWidget, - ShareDialog, + // ShareDialog, SwipeWidget, SaveAsWebMapDialog, SwipeWidgetToggleBtn, @@ -34,13 +34,13 @@ import { OpenDownloadPanelBtn, DownloadDialog, } from '..'; -import { AppContext } from '@contexts/AppContextProvider'; +// import { AppContext } from '@contexts/AppContextProvider'; import { getServiceUrl } from '@utils/Tier'; import useCurrenPageBecomesVisible from '@hooks/useCurrenPageBecomesVisible'; import { revalidateToken } from '@utils/Esri-OAuth'; const AppLayout: React.FC = () => { - const { onPremises } = React.useContext(AppContext); + // const { onPremises } = React.useContext(AppContext); const currentPageIsVisibleAgain = useCurrenPageBecomesVisible(); @@ -106,7 +106,7 @@ const AppLayout: React.FC = () => { - {!onPremises && } + {/* {!onPremises && } */} diff --git a/src/components/Gutter/GutterContainer.tsx b/src/components/Gutter/GutterContainer.tsx index b298909..3314456 100644 --- a/src/components/Gutter/GutterContainer.tsx +++ b/src/components/Gutter/GutterContainer.tsx @@ -1,6 +1,6 @@ import React, { useContext, useMemo } from 'react'; -import Gutter from './index'; +import { Gutter } from './index'; import { useSelector, useDispatch } from 'react-redux'; @@ -14,6 +14,7 @@ import { } from '@store/UI/reducer'; import { AppContext } from '@contexts/AppContextProvider'; import { isAnimationModeOnSelector } from '@store/AnimationMode/reducer'; +import { copy2clipboard } from '@utils/snippets/copy2clipborad'; type Props = { children: React.ReactNode; @@ -31,27 +32,27 @@ const GutterContainer: React.FC = ({ children }) => { const isHide = useSelector(isGutterHideSelector); - const { isMobile, onPremises } = useContext(AppContext); + const { isMobile } = useContext(AppContext); const aboutButtonOnClick = () => { dispatch(isAboutThisAppModalOpenToggled()); }; - const shareButtonOnClick = () => { - dispatch(isShareModalOpenToggled()); - }; - const settingButtonOnClick = () => { dispatch(isSettingModalOpenToggled()); }; + const copyButtonOnClick = () => { + copy2clipboard(window.location.href); + }; + return !isHide ? ( {children} diff --git a/src/components/Gutter/index.tsx b/src/components/Gutter/index.tsx index 648054c..12d246d 100644 --- a/src/components/Gutter/index.tsx +++ b/src/components/Gutter/index.tsx @@ -1,94 +1,108 @@ import './style.css'; -import React from 'react'; +import React, { FC, useState } from 'react'; import { MOBILE_HEADER_HEIGHT } from '@constants/UI'; interface IProps { isMobile: boolean; settingsBtnDisabled: boolean; - shareBtnDisabled: boolean; + // shareBtnDisabled: boolean; // children: JSX.Element[] | JSX.Element; aboutButtonOnClick: () => void; - shareButtonOnClick: () => void; + copyButtonOnClick: () => void; settingButtonOnClick: () => void; children?: React.ReactNode; } -class Gutter extends React.PureComponent { - constructor(props: IProps) { - super(props); - } +export const Gutter: FC = ({ + isMobile, + // shareBtnDisabled, + settingsBtnDisabled, + copyButtonOnClick, + aboutButtonOnClick, + settingButtonOnClick, + children, +}) => { + const [hasCopied2Clipboard, setHasCopied2Clipboard] = useState(false); - render(): JSX.Element { - const { - isMobile, - shareBtnDisabled, - shareButtonOnClick, - aboutButtonOnClick, - settingButtonOnClick, - } = this.props; - - return ( + return ( +
+ {/* gradient effect on right side of gutter */}
- {/* gradient effect on right side of gutter */} + >
+ +
+ className="gutter-nav-btn mb-2" + // data-modal={AboutThisAppModalConfig['modal-id']} + title="About this app" + onClick={aboutButtonOnClick} + > + +
-
+ {/* {!shareBtnDisabled && (
- +
+ )} */} - {!shareBtnDisabled && ( -
- -
- )} +
{ + copyButtonOnClick(); -
- -
-
+ setHasCopied2Clipboard(true); - {/* divider with shadow effect */} -
{ + setHasCopied2Clipboard(false); + }, 3000); }} - >
+ > + +
- {this.props.children} +
+ +
- ); - } -} -export default Gutter; + {/* divider with shadow effect */} +
+ + {children} +
+ ); +}; diff --git a/src/utils/snippets/copy2clipborad.ts b/src/utils/snippets/copy2clipborad.ts new file mode 100644 index 0000000..b3c0199 --- /dev/null +++ b/src/utils/snippets/copy2clipborad.ts @@ -0,0 +1,21 @@ +export const copy2clipboard = (text: string) => { + // Create a temporary textarea element + const textarea = document.createElement('textarea'); + textarea.value = text; + + // Set the position to be off-screen + textarea.style.position = 'absolute'; + textarea.style.left = '-9999px'; + + // Append the textarea to the document + document.body.appendChild(textarea); + + // Select the text in the textarea + textarea.select(); + + // Execute the copy command + document.execCommand('copy'); + + // Remove the textarea from the document + document.body.removeChild(textarea); +}; From b61fada77a083914925c4078a3bb81d5e28922b1 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Thu, 9 Nov 2023 14:53:01 -0800 Subject: [PATCH 18/23] chore: stop using watchUtils from JSAPI --- .../AnimationPanel/AnimationPanel.tsx | 31 +++++--- src/components/MapView/MapView.tsx | 11 +-- .../MetadataQueryTask/MetadataQueryTask.tsx | 69 +++++++---------- src/components/SwipeWidget/SwipeWidget.tsx | 75 ++----------------- src/index.tsx | 3 +- src/style/index.css | 4 + 6 files changed, 62 insertions(+), 131 deletions(-) diff --git a/src/components/AnimationPanel/AnimationPanel.tsx b/src/components/AnimationPanel/AnimationPanel.tsx index f0c6747..3977f74 100644 --- a/src/components/AnimationPanel/AnimationPanel.tsx +++ b/src/components/AnimationPanel/AnimationPanel.tsx @@ -10,7 +10,7 @@ import LoadingIndicator from './LoadingIndicator'; import DownloadGIFDialog from './DownloadGIFDialog'; import CloseBtn from './CloseBtn'; -import { whenFalse } from '@arcgis/core/core/watchUtils'; +// import { whenFalse } from '@arcgis/core/core/watchUtils'; import { IWaybackItem } from '@typings/index'; import { useDispatch, useSelector } from 'react-redux'; @@ -24,6 +24,7 @@ import { toggleIsLoadingFrameData, } from '@store/AnimationMode/reducer'; import Background from './Background'; +import { watch } from '@arcgis/core/core/reactiveUtils'; type Props = { waybackItems4Animation: IWaybackItem[]; @@ -186,15 +187,25 @@ const AnimationPanel: React.FC = ({ }, [waybackItems4Animation]); useEffect(() => { - const onUpdating = whenFalse(mapView, 'stationary', () => { - loadingWaybackItems4AnimationRef.current = true; - setFrameData(null); - }); - - return () => { - // onStationary.remove(); - onUpdating.remove(); - }; + // const onUpdating = whenFalse(mapView, 'stationary', () => { + // loadingWaybackItems4AnimationRef.current = true; + // setFrameData(null); + // }); + + watch( + () => mapView.stationary, + () => { + if (!mapView.stationary) { + loadingWaybackItems4AnimationRef.current = true; + setFrameData(null); + } + } + ); + + // return () => { + // // onStationary.remove(); + // onUpdating.remove(); + // }; }, []); useEffect(() => { diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 0c4626b..94fee98 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -1,4 +1,3 @@ -import '@arcgis/core/assets/esri/themes/dark/main.css'; import React, { useEffect, useRef } from 'react'; import MapView from '@arcgis/core/views/MapView'; @@ -79,11 +78,11 @@ const MapViewComponent: React.FC = ({ setMapView(view); view.when(() => { - initWatchUtils(view); + initEventHandlers(view); }); }; - const initWatchUtils = async (view: MapView) => { + const initEventHandlers = async (view: MapView) => { // whenTrue(mapView, 'stationary', mapViewUpdateEndHandler); when( () => view.stationary === true, @@ -140,12 +139,6 @@ const MapViewComponent: React.FC = ({ initMapView(); }, []); - // useEffect(() => { - // if (mapView) { - // initWatchUtils(); - // } - // }, [mapView]); - return ( <>
= ({ queryMetadata(evt.mapPoint); }); - watch(mapView, 'zoom', () => { - // console.log('view zoom is on updating, should hide the popup', zoom); - metadataOnChange(null); - }); - - watch(mapView, 'center', () => { - // // console.log('view center is on updating, should update the popup position'); - // // need to update the screen point for popup anchor since the map center has changed - // updateScreenPoint4PopupAnchor(); - metadataOnChange(null); - }); - - // try { - // type Modules = [typeof IWatchUtils]; - - // const [watchUtils] = await (loadModules([ - // 'esri/core/watchUtils', - // ]) as Promise); - - // mapView.on('click', (evt) => { - // console.log('view on click, should show popup', evt.mapPoint); - // queryMetadata(evt.mapPoint); - // }); - - // watch(mapView, 'zoom', () => { - // // console.log('view zoom is on updating, should hide the popup', zoom); - // metadataOnChange(null); - // }); - - // watch(mapView, 'center', () => { - // // // console.log('view center is on updating, should update the popup position'); - // // // need to update the screen point for popup anchor since the map center has changed - // // updateScreenPoint4PopupAnchor(); - // metadataOnChange(null); - // }); - // } catch (err) { - // console.error(err); - // } + // watch(mapView, 'zoom', () => { + // // console.log('view zoom is on updating, should hide the popup', zoom); + // metadataOnChange(null); + // }); + + watch( + () => mapView.zoom, + () => { + metadataOnChange(null); + } + ); + + // watch(mapView, 'center', () => { + // // // console.log('view center is on updating, should update the popup position'); + // // // need to update the screen point for popup anchor since the map center has changed + // // updateScreenPoint4PopupAnchor(); + // metadataOnChange(null); + // }); + + watch( + () => mapView.center, + () => { + metadataOnChange(null); + } + ); }; React.useEffect(() => { diff --git a/src/components/SwipeWidget/SwipeWidget.tsx b/src/components/SwipeWidget/SwipeWidget.tsx index 25c7ee7..ac449ef 100644 --- a/src/components/SwipeWidget/SwipeWidget.tsx +++ b/src/components/SwipeWidget/SwipeWidget.tsx @@ -1,16 +1,11 @@ import React, { useRef, useEffect } from 'react'; -// import { loadModules } from 'esri-loader'; -// import IMapView from 'esri/views/MapView'; -// import ISwipe from 'esri/widgets/Swipe'; -// import IWebTileLayer from 'esri/layers/WebTileLayer'; -// import IWatchUtils from 'esri/core/watchUtils'; import { IWaybackItem } from '@typings/index'; import MapView from '@arcgis/core/views/MapView'; import Swipe from '@arcgis/core/widgets/Swipe'; import WebTileLayer from '@arcgis/core/layers/WebTileLayer'; -import { watch } from '@arcgis/core/core/watchUtils'; +import { watch } from '@arcgis/core/core/reactiveUtils'; import { getWaybackLayer } from '../WaybackLayer/getWaybackLayer'; @@ -64,71 +59,15 @@ const SwipeWidget: React.FC = ({ // onLoaded(); } - - // type Modules = [ - // typeof ISwipe, - // ]; - - // try { - // const [ Swipe ] = await (loadModules([ - // 'esri/widgets/Swipe', - // ]) as Promise); - - // if(swipeWidgetRef.current){ - // show(); - // } else { - - // const leadingLayer = await getWaybackLayer(waybackItem4LeadingLayer); - // const trailingLayer = await getWaybackLayer(waybackItem4TrailingLayer); - - // layersRef.current = [leadingLayer, trailingLayer]; - - // mapView.map.addMany(layersRef.current, 1); - - // const swipe = new Swipe({ - // view: mapView, - // leadingLayers: [leadingLayer], - // trailingLayers: [trailingLayer], - // direction: "horizontal", - // position: 50 // position set to middle of the view (50%) - // }); - - // swipeWidgetRef.current = swipe; - - // mapView.ui.add(swipe); - - // addEventHandlers(swipe); - - // // onLoaded(); - // } - - // } catch(err){ - // console.error(err); - // init(); - // } }; const addEventHandlers = (swipeWidget: Swipe) => { - // try { - // type Modules = [typeof IWatchUtils]; - - // const [watchUtils] = await (loadModules([ - // 'esri/core/watchUtils', - // ]) as Promise); - - // watch(swipeWidget, 'position', (position:number) => { - // // console.log('position changes for swipe widget', position); - // positionOnChange(position); - // }); - - // } catch (err) { - // console.error(err); - // } - - watch(swipeWidget, 'position', (position: number) => { - // console.log('position changes for swipe widget', position); - positionOnChange(position); - }); + watch( + () => swipeWidget.position, + (position: number) => { + positionOnChange(position); + } + ); }; const show = () => { diff --git a/src/index.tsx b/src/index.tsx index bf6899e..51d9af0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,4 @@ +import '@arcgis/core/assets/esri/themes/dark/main.css'; import './style/index.css'; import React from 'react'; @@ -7,7 +8,7 @@ import configureAppStore, { getPreloadedState } from '@store/configureStore'; import AppContextProvider from './contexts/AppContextProvider'; import WaybackManager from './services/wayback'; import { AppLayout } from '@components/index'; -import { initEsriOAuth, isAnonymouns, signIn } from '@utils/Esri-OAuth'; +import { initEsriOAuth } from '@utils/Esri-OAuth'; import config from './app-config'; import { getCustomPortalUrl } from '@utils/LocalStorage'; import { getServiceUrl } from '@utils/Tier'; diff --git a/src/style/index.css b/src/style/index.css index 1531c92..d924691 100644 --- a/src/style/index.css +++ b/src/style/index.css @@ -33,6 +33,10 @@ html, body { outline: 0; } +#appRootDiv .esri-view-root { + --esri-view-outline: 0; +} + video, img { height: 0; } From d0e6f5e5ae3fb558d87c71a0d83674433cdd9319 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Thu, 9 Nov 2023 14:56:55 -0800 Subject: [PATCH 19/23] chore: use JSAPI version 4.28 --- package-lock.json | 252 ++++++++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 164 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6a4f7d..b2c20b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@arcgis/core": "^4.26.5", + "@arcgis/core": "4.28", "@entryline/gifstream": "^1.2.1", "@esri/arcgis-rest-feature-service": "^4.0.4", "@esri/arcgis-rest-request": "^4.2.0", @@ -93,16 +93,17 @@ } }, "node_modules/@arcgis/core": { - "version": "4.26.5", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.26.5.tgz", - "integrity": "sha512-Z8KoJrTD1ZQwVVZAIpkdjAQVT/MjAaWAr5u0krifKd6Y1m0TrEb8/iK86KvyWX8yNNWEroEP9SiE19vH/JKquQ==", + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.28.9.tgz", + "integrity": "sha512-UpWcWsBg+/yhfjzPx45+iiCJXhPWx5F+xB/sdMai1mfMvwlAxbsL9YzbLM/S4CawVpEPwC3i5ZGEgp17gxu7Kg==", "dependencies": { "@esri/arcgis-html-sanitizer": "~3.0.1", "@esri/calcite-colors": "~6.1.0", - "@esri/calcite-components": "~1.0.7", - "@popperjs/core": "~2.11.6", - "focus-trap": "~7.2.0", - "luxon": "~3.2.1", + "@esri/calcite-components": "^1.9.2", + "@popperjs/core": "~2.11.8", + "@zip.js/zip.js": "~2.7.29", + "focus-trap": "~7.5.3", + "luxon": "~3.4.3", "sortablejs": "~1.15.0" } }, @@ -2604,33 +2605,45 @@ "integrity": "sha512-wHQYWFtDa6c328EraXEVZvgOiaQyYr0yuaaZ0G3cB4C3lSkWefW34L/e5TLAhtuG3zJ/wR6pl5X1YYNfBc0/4Q==" }, "node_modules/@esri/calcite-components": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.0.8.tgz", - "integrity": "sha512-aeBSZQdtv7CRGbcGZh49RT4Z481muFspnY+98X6RRnf8QHdjpwTk+JbyS1pLAvenyLqTV+Gwa/Q5BPhbsfLiRw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.10.0.tgz", + "integrity": "sha512-nKTwU7hseSAXTqEQelVfo+qDa5jCGvabky4atRsJxROkHbSQkhdFFfwOrrffHOr9CtZBxtvZhfh/Tb6Nky15hw==", "dependencies": { - "@floating-ui/dom": "1.2.1", - "@stencil/core": "2.20.0", - "@types/color": "3.0.3", + "@floating-ui/dom": "1.5.3", + "@stencil/core": "2.22.3", + "@types/color": "3.0.4", "color": "4.2.3", - "focus-trap": "7.2.0", + "composed-offset-position": "0.0.4", + "dayjs": "1.11.10", + "focus-trap": "7.5.4", "form-request-submit-polyfill": "2.0.0", "lodash-es": "4.17.21", - "sortablejs": "1.15.0" + "sortablejs": "1.15.0", + "timezone-groups": "0.8.0" } }, "node_modules/@floating-ui/core": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz", - "integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } }, "node_modules/@floating-ui/dom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.1.tgz", - "integrity": "sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", "dependencies": { - "@floating-ui/core": "^1.2.1" + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" } }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -3244,9 +3257,9 @@ } }, "node_modules/@stencil/core": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.20.0.tgz", - "integrity": "sha512-ka+eOW+dNteXIfLCRipNbbAlBEQjqJ2fkx3fxzlKgnNHEQMdZiuIjlWt63KzvOJStNeuADdQXo89BB1dC2VRUw==", + "version": "2.22.3", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", + "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==", "bin": { "stencil": "bin/stencil" }, @@ -3331,25 +3344,25 @@ } }, "node_modules/@types/color": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.3.tgz", - "integrity": "sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.4.tgz", + "integrity": "sha512-OpisS4bqJJwbkkQRrMvURf3DOxBoAg9mysHYI7WgrWpSYHqHGKYBULHdz4ih77SILcLDo/zyHGFyfIl9yb8NZQ==", "dependencies": { "@types/color-convert": "*" } }, "node_modules/@types/color-convert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.0.tgz", - "integrity": "sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.3.tgz", + "integrity": "sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==", "dependencies": { "@types/color-name": "*" } }, "node_modules/@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw==" }, "node_modules/@types/connect": { "version": "3.4.35", @@ -4469,6 +4482,16 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.30", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.30.tgz", + "integrity": "sha512-nhMvQCj+TF1ATBqYzFds7v+yxPBhdDYHh8J341KtC1D2UrVBUIYcYK4Jy1/GiTsxOXEiKOXSUxvPG/XR+7jMqw==", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, "node_modules/abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -6517,6 +6540,11 @@ "node": "*" } }, + "node_modules/composed-offset-position": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.4.tgz", + "integrity": "sha512-vMlvu1RuNegVE0YsCDSV/X4X10j56mq7PCIyOKK74FxkXzGLwhOUmdkJLSdOBOMwWycobGUMgft2lp+YgTe8hw==" + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -7751,6 +7779,11 @@ "node": ">= 12" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -9223,11 +9256,11 @@ "dev": true }, "node_modules/focus-trap": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.2.0.tgz", - "integrity": "sha512-v4wY6HDDYvzkBy4735kW5BUEuw6Yz9ABqMYLuTNbzAFPcBOGiGHwwcNVMvUz4G0kgSYh13wa/7TG3XwTeT4O/A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", "dependencies": { - "tabbable": "^6.0.1" + "tabbable": "^6.2.0" } }, "node_modules/follow-redirects": { @@ -12180,9 +12213,9 @@ } }, "node_modules/luxon": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", - "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", + "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==", "engines": { "node": ">=12" } @@ -16679,9 +16712,9 @@ } }, "node_modules/tabbable": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz", - "integrity": "sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "node_modules/tailwindcss": { "version": "3.3.2", @@ -16920,6 +16953,14 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/timezone-groups": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/timezone-groups/-/timezone-groups-0.8.0.tgz", + "integrity": "sha512-t7E/9sPfCU0m0ZbS7Cqw52D6CB/UyeaiIBmyJCokI1SyOyOgA/ESiQ/fbreeFaUG9QSenGlZSSk/7rEbkipbOA==", + "bin": { + "timezone-groups": "dist/cli.cjs" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -18016,16 +18057,17 @@ "dev": true }, "@arcgis/core": { - "version": "4.26.5", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.26.5.tgz", - "integrity": "sha512-Z8KoJrTD1ZQwVVZAIpkdjAQVT/MjAaWAr5u0krifKd6Y1m0TrEb8/iK86KvyWX8yNNWEroEP9SiE19vH/JKquQ==", + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.28.9.tgz", + "integrity": "sha512-UpWcWsBg+/yhfjzPx45+iiCJXhPWx5F+xB/sdMai1mfMvwlAxbsL9YzbLM/S4CawVpEPwC3i5ZGEgp17gxu7Kg==", "requires": { "@esri/arcgis-html-sanitizer": "~3.0.1", "@esri/calcite-colors": "~6.1.0", - "@esri/calcite-components": "~1.0.7", - "@popperjs/core": "~2.11.6", - "focus-trap": "~7.2.0", - "luxon": "~3.2.1", + "@esri/calcite-components": "^1.9.2", + "@popperjs/core": "~2.11.8", + "@zip.js/zip.js": "~2.7.29", + "focus-trap": "~7.5.3", + "luxon": "~3.4.3", "sortablejs": "~1.15.0" } }, @@ -19780,33 +19822,45 @@ "integrity": "sha512-wHQYWFtDa6c328EraXEVZvgOiaQyYr0yuaaZ0G3cB4C3lSkWefW34L/e5TLAhtuG3zJ/wR6pl5X1YYNfBc0/4Q==" }, "@esri/calcite-components": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.0.8.tgz", - "integrity": "sha512-aeBSZQdtv7CRGbcGZh49RT4Z481muFspnY+98X6RRnf8QHdjpwTk+JbyS1pLAvenyLqTV+Gwa/Q5BPhbsfLiRw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.10.0.tgz", + "integrity": "sha512-nKTwU7hseSAXTqEQelVfo+qDa5jCGvabky4atRsJxROkHbSQkhdFFfwOrrffHOr9CtZBxtvZhfh/Tb6Nky15hw==", "requires": { - "@floating-ui/dom": "1.2.1", - "@stencil/core": "2.20.0", - "@types/color": "3.0.3", + "@floating-ui/dom": "1.5.3", + "@stencil/core": "2.22.3", + "@types/color": "3.0.4", "color": "4.2.3", - "focus-trap": "7.2.0", + "composed-offset-position": "0.0.4", + "dayjs": "1.11.10", + "focus-trap": "7.5.4", "form-request-submit-polyfill": "2.0.0", "lodash-es": "4.17.21", - "sortablejs": "1.15.0" + "sortablejs": "1.15.0", + "timezone-groups": "0.8.0" } }, "@floating-ui/core": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz", - "integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "requires": { + "@floating-ui/utils": "^0.1.3" + } }, "@floating-ui/dom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.1.tgz", - "integrity": "sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", "requires": { - "@floating-ui/core": "^1.2.1" + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" } }, + "@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -20291,9 +20345,9 @@ } }, "@stencil/core": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.20.0.tgz", - "integrity": "sha512-ka+eOW+dNteXIfLCRipNbbAlBEQjqJ2fkx3fxzlKgnNHEQMdZiuIjlWt63KzvOJStNeuADdQXo89BB1dC2VRUw==" + "version": "2.22.3", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", + "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==" }, "@trysound/sax": { "version": "0.2.0", @@ -20368,25 +20422,25 @@ } }, "@types/color": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.3.tgz", - "integrity": "sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.4.tgz", + "integrity": "sha512-OpisS4bqJJwbkkQRrMvURf3DOxBoAg9mysHYI7WgrWpSYHqHGKYBULHdz4ih77SILcLDo/zyHGFyfIl9yb8NZQ==", "requires": { "@types/color-convert": "*" } }, "@types/color-convert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.0.tgz", - "integrity": "sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.3.tgz", + "integrity": "sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==", "requires": { "@types/color-name": "*" } }, "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw==" }, "@types/connect": { "version": "3.4.35", @@ -21379,6 +21433,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zip.js/zip.js": { + "version": "2.7.30", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.30.tgz", + "integrity": "sha512-nhMvQCj+TF1ATBqYzFds7v+yxPBhdDYHh8J341KtC1D2UrVBUIYcYK4Jy1/GiTsxOXEiKOXSUxvPG/XR+7jMqw==" + }, "abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -23025,6 +23084,11 @@ } } }, + "composed-offset-position": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.4.tgz", + "integrity": "sha512-vMlvu1RuNegVE0YsCDSV/X4X10j56mq7PCIyOKK74FxkXzGLwhOUmdkJLSdOBOMwWycobGUMgft2lp+YgTe8hw==" + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -23855,6 +23919,11 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -24955,11 +25024,11 @@ "dev": true }, "focus-trap": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.2.0.tgz", - "integrity": "sha512-v4wY6HDDYvzkBy4735kW5BUEuw6Yz9ABqMYLuTNbzAFPcBOGiGHwwcNVMvUz4G0kgSYh13wa/7TG3XwTeT4O/A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", "requires": { - "tabbable": "^6.0.1" + "tabbable": "^6.2.0" } }, "follow-redirects": { @@ -27130,9 +27199,9 @@ } }, "luxon": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", - "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==" + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", + "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==" }, "make-dir": { "version": "3.1.0", @@ -30092,9 +30161,9 @@ } }, "tabbable": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz", - "integrity": "sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "tailwindcss": { "version": "3.3.2", @@ -30274,6 +30343,11 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "timezone-groups": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/timezone-groups/-/timezone-groups-0.8.0.tgz", + "integrity": "sha512-t7E/9sPfCU0m0ZbS7Cqw52D6CB/UyeaiIBmyJCokI1SyOyOgA/ESiQ/fbreeFaUG9QSenGlZSSk/7rEbkipbOA==" + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/package.json b/package.json index 902fdde..d878571 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "webpack-dev-server": "4.9" }, "dependencies": { - "@arcgis/core": "^4.26.5", + "@arcgis/core": "4.28", "@entryline/gifstream": "^1.2.1", "@esri/arcgis-rest-feature-service": "^4.0.4", "@esri/arcgis-rest-request": "^4.2.0", From abd50bd89f25e9b1f5b5ae3a10c9cb46dda7976e Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Mon, 13 Nov 2023 16:07:03 -0800 Subject: [PATCH 20/23] chore: update wayback-export-base for PROD --- src/app-config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app-config.ts b/src/app-config.ts index bae2ce2..336f3d5 100644 --- a/src/app-config.ts +++ b/src/app-config.ts @@ -21,7 +21,8 @@ const config: IAppConfig = { // this world imagery basemap will be used when user saves selected Wayback items into a new webmap 'world-imagery-basemap': 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/', - 'wayback-export-base': '', + 'wayback-export-base': + 'https://wayport.maptiles.arcgis.com/arcgis/rest/services/Wayport/GPServer/Wayport', }, }, // The dev enivornment is optional, please comment out the dev section below if don't need the dev enivornment From 911327af0de76280b4682d537f6606cadd930c85 Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Tue, 14 Nov 2023 13:35:24 -0800 Subject: [PATCH 21/23] fix: update intro text of the Download Panel --- .../DownloadDialog/DownloadDialog.tsx | 63 +++++++++++++++++-- .../DownloadDialogContainer.tsx | 8 +-- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/components/DownloadDialog/DownloadDialog.tsx b/src/components/DownloadDialog/DownloadDialog.tsx index 0994aa3..3967971 100644 --- a/src/components/DownloadDialog/DownloadDialog.tsx +++ b/src/components/DownloadDialog/DownloadDialog.tsx @@ -96,15 +96,70 @@ export const DownloadDialog: FC = ({
-

Download Tile Package

+

+ Wayback Export ( + + beta + + ) +

- Based on your current map extent, choose a scale range - for your download. Downloads are limited to 150,000 - tiles. + Exported basemap tiles are intended for offline use in + ArcGIS applications and{' '} + + offline applications + {' '} + built with an ArcGIS Runtime SDK, in accordance with + Esri’s terms of use:{' '} + + View Summary + {' '} + |{' '} + + View Terms of Use + + . {/* You can choose this window while your tiles are prepared. */}

+
    +
  • + Exports are based on map extent, with a minimum zoom + level of 12. +
  • +
  • + Each export request is limited to a maximum of + 150,000 tiles. +
  • +
  • + No more than five exports may be requested + concurrently. +
  • +
  • + This dialog can safely be closed while tile packages + are being created. +
  • +
+ +
+ {isAddingNewDownloadJob && }
{getJobsList()}
diff --git a/src/components/DownloadDialog/DownloadDialogContainer.tsx b/src/components/DownloadDialog/DownloadDialogContainer.tsx index c8fcac4..41564df 100644 --- a/src/components/DownloadDialog/DownloadDialogContainer.tsx +++ b/src/components/DownloadDialog/DownloadDialogContainer.tsx @@ -39,14 +39,14 @@ export const DownloadDialogContainer = () => { useEffect(() => { // save jobs to localhost so they can be restored saveDownloadJobs2LocalStorage(jobs); + + if (jobs?.length && isAnonymouns()) { + signIn(); + } }, [jobs]); useEffect(() => { updateHashParams('downloadMode', isOpen ? 'true' : null); - - if (isOpen && isAnonymouns()) { - signIn(); - } }, [isOpen]); useEffect(() => { From 493b340b49528227a65f0b0d911ed41e24f916df Mon Sep 17 00:00:00 2001 From: Jinnan Zhang Date: Wed, 15 Nov 2023 05:22:55 -0800 Subject: [PATCH 22/23] fix: intro text of Download Dialog --- src/components/DownloadDialog/DownloadDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DownloadDialog/DownloadDialog.tsx b/src/components/DownloadDialog/DownloadDialog.tsx index 3967971..dbebdf0 100644 --- a/src/components/DownloadDialog/DownloadDialog.tsx +++ b/src/components/DownloadDialog/DownloadDialog.tsx @@ -118,7 +118,7 @@ export const DownloadDialog: FC = ({ > offline applications {' '} - built with an ArcGIS Runtime SDK, in accordance with + built with an ArcGIS Runtime SDK in accordance with Esri’s terms of use:{' '} = ({ > View Summary {' '} - |{' '} + and{' '} Date: Wed, 15 Nov 2023 05:29:22 -0800 Subject: [PATCH 23/23] fix: minur UI related fixes: tooltip text for download button and icon for copy URL button --- src/components/Gutter/index.tsx | 2 +- src/components/ListView/ListViewContainer.tsx | 4 ++-- src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Gutter/index.tsx b/src/components/Gutter/index.tsx index 12d246d..67049d4 100644 --- a/src/components/Gutter/index.tsx +++ b/src/components/Gutter/index.tsx @@ -79,7 +79,7 @@ export const Gutter: FC = ({ }, 3000); }} > - +
{ const downloadButtonTooltipText = useMemo(() => { const text = - 'Download an imagery tile package for the current map extent'; + 'Export an imagery tile package for the current map extent'; if (zoom < 12) { return text + ` (zoom in to enable)`; } if (hasReachedLimitOfConcurrentDownloadJobs) { - return 'Reached the maximum limit of 5 concurrent download jobs'; + return 'Reached the maximum limit of 5 concurrent export jobs'; } return text; diff --git a/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx b/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx index f51648b..1acf223 100644 --- a/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx +++ b/src/components/OpenDownloadPanelBtn/OpenDownloadPanelBtn.tsx @@ -55,7 +55,7 @@ export const OpenDownloadPanelBtn = () => { disabled: numOfJobs === 0, } )} - title={'Choose a version from the list to download a tile package'} + title={'Choose a version from the list to export a tile package'} onClick={() => { dispatch(isDownloadDialogOpenToggled()); }}