From 08a7ee0850ad9c5e3eafec4aadc961e602ab875e Mon Sep 17 00:00:00 2001 From: Tomoyuki Aota Date: Tue, 7 Jan 2025 02:25:08 +0900 Subject: [PATCH 1/9] Set timeout to 10 seconds. --- .../raster-tile-base-layer-configs-version-1.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts index a7350929..7d5a79c7 100644 --- a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts +++ b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts @@ -49,7 +49,11 @@ const configsFileUrl = 'https://cdn.jsdelivr.net/gh/TomoyukiAota/photo-location-map-resources@main/map-configs/raster-tile-base-layer-configs-version-1.jsonc'; async function fetchRasterTileBaseLayerConfigsVersion1(): Promise { - const response = await fetch(configsFileUrl); + const timeoutMilliseconds = 10000; + const response = await fetch(configsFileUrl, { + cache: 'no-store', + signal: AbortSignal.timeout(timeoutMilliseconds), + }); if (!response.ok) { throw new Error(`response.status: ${response.status}, response.statusText: ${response.statusText}`); } @@ -73,7 +77,7 @@ async function fetchRasterTileBaseLayerConfigsVersion1WithFallback(): Promise Date: Tue, 7 Jan 2025 03:43:06 +0900 Subject: [PATCH 2/9] Retry fetching raster-tile-base-layer-configs-version-1.jsonc from a different URL in case of failure. --- ...aster-tile-base-layer-configs-version-1.ts | 89 ++++++++++++------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts index 7d5a79c7..89337761 100644 --- a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts +++ b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts @@ -45,15 +45,25 @@ export const rasterTileBaseLayerConfigsVersion1Fallback: RasterTileBaseLayerConf // In the production environment, the content of main branch in photo-location-map-resources repo is used. // In the development environment, the content of the feature branch can be used as needed. -const configsFileUrl - = 'https://cdn.jsdelivr.net/gh/TomoyukiAota/photo-location-map-resources@main/map-configs/raster-tile-base-layer-configs-version-1.jsonc'; +const configsFileFetchArguments: Array<{ url: string, options: RequestInit }> = [ + { + url: 'https://cdn.jsdelivr.net/gh/TomoyukiAota/photo-location-map-resources@main/map-configs/raster-tile-base-layer-configs-version-1.jsonc', + options: { + cache: 'no-store', + signal: AbortSignal.timeout(10000 /* milliseconds */), + }, + }, + { + url: 'https://raw.githubusercontent.com/TomoyukiAota/photo-location-map-resources/refs/heads/main/map-configs/raster-tile-base-layer-configs-version-1.jsonc', + options: { + cache: 'no-store', + // No timeout for the last attempt. + }, + }, +]; -async function fetchRasterTileBaseLayerConfigsVersion1(): Promise { - const timeoutMilliseconds = 10000; - const response = await fetch(configsFileUrl, { - cache: 'no-store', - signal: AbortSignal.timeout(timeoutMilliseconds), - }); +async function fetchRasterTileBaseLayerConfigsVersion1(url: string, options: RequestInit): Promise { + const response = await fetch(url, options); if (!response.ok) { throw new Error(`response.status: ${response.status}, response.statusText: ${response.statusText}`); } @@ -62,34 +72,53 @@ async function fetchRasterTileBaseLayerConfigsVersion1(): Promise { - const fetchingMessage = `Fetching ${configsFileUrl}`; - logger.info(fetchingMessage); - Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fetching BaseLayerConfigs`, fetchingMessage); +function recordFetchSuccess(url: string, configs: RasterTileBaseLayerConfigsVersion1): void { + const message = `Fetched ${url}\n${toLoggableString(configs)}`; + logger.info(message); + Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fetched BaseLayerConfigs`, message); +} - let configs: RasterTileBaseLayerConfigsVersion1; - try { - configs = await fetchRasterTileBaseLayerConfigsVersion1(); - } catch (error) { - const message = `Failed to fetch ${configsFileUrl}. Using the fallback configs. error.message: "${error.message}"`; - return recordErrorAndGetFallback(message); - } +function recordFetchFailed(url: string, error: Error): void { + const message = `Failed to fetch ${url}. error.message: "${error.message}"`; + logger.error(message); + Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Failed to fetch configs`, message); +} + +function recordFetchFailedForAllUrls(): void { + logger.error(`Failed to fetch the configs from all the URLs. Using the fallback configs.`); + Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fallback BaseLayerConfigs`); +} - if (!configs?.rasterTileBaseLayerConfigs?.length) { - const message = `Invalid configs object is fetched from ${configsFileUrl}. Using the fallback configs.\n${toLoggableString(configs)}`; - return recordErrorAndGetFallback(message); +async function fetchRasterTileBaseLayerConfigsVersion1WithRetry(): Promise { + for (const {url, options} of configsFileFetchArguments) { + try { + recordFetchingConfigs(url); + const configs = await fetchRasterTileBaseLayerConfigsVersion1(url, options); + if (!configs?.rasterTileBaseLayerConfigs?.length) { + recordInvalidConfigsObjectError(url); + continue; + } + recordFetchSuccess(url, configs); + return configs; + } catch (error) { + recordFetchFailed(url, error); + } } - const fetchedMessage = `Fetched ${configsFileUrl}\n${toLoggableString(configs)}`; - logger.info(fetchedMessage); - Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fetched BaseLayerConfigs`, fetchedMessage); - return configs; + recordFetchFailedForAllUrls(); + return rasterTileBaseLayerConfigsVersion1Fallback; } -export const rasterTileBaseLayerConfigsVersion1 = await fetchRasterTileBaseLayerConfigsVersion1WithFallback(); +export const rasterTileBaseLayerConfigsVersion1 = await fetchRasterTileBaseLayerConfigsVersion1WithRetry(); From 128788538f0d858a8428586502e00282032b67cc Mon Sep 17 00:00:00 2001 From: Tomoyuki Aota Date: Wed, 8 Jan 2025 02:17:45 +0900 Subject: [PATCH 3/9] Introduce animation to the application loading indicator. --- src/index.html | 6 +++- src/styles/_app-loading-indicator.scss | 41 ++++++++++++++++++++++++++ src/styles/global-styles.scss | 1 + 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/styles/_app-loading-indicator.scss diff --git a/src/index.html b/src/index.html index 282ba1ce..6b5a0fa7 100644 --- a/src/index.html +++ b/src/index.html @@ -29,6 +29,10 @@ - Loading... + +
+ Loading +
+
diff --git a/src/styles/_app-loading-indicator.scss b/src/styles/_app-loading-indicator.scss new file mode 100644 index 00000000..98dda7c4 --- /dev/null +++ b/src/styles/_app-loading-indicator.scss @@ -0,0 +1,41 @@ +// Reference: https://cssloaders.github.io/ + +$loading-indicator-color: #888888; + +.app-loading-indicator-container { + height: 100%; + display: grid; + place-items: center; +} + +.app-loading-indicator { + color: $loading-indicator-color; + display: inline-block; + position: relative; + font-size: 28px; + box-sizing: border-box; +} + +.app-loading-indicator::after { + content: ''; + width: 4px; + height: 4px; + background: currentColor; + position: absolute; + bottom: 8px; + right: -8px; + box-sizing: border-box; + animation: appLoadingAnimation 1s linear infinite; +} + +@keyframes appLoadingAnimation { + 0% { + box-shadow: 10px 0 rgba(255, 255, 255, 0), 20px 0 rgba(255, 255, 255, 0); + } + 50% { + box-shadow: 10px 0 $loading-indicator-color, 20px 0 rgba(255, 255, 255, 0); + } + 100% { + box-shadow: 10px 0 $loading-indicator-color, 20px 0 $loading-indicator-color; + } +} diff --git a/src/styles/global-styles.scss b/src/styles/global-styles.scss index d80eab4e..0451f204 100644 --- a/src/styles/global-styles.scss +++ b/src/styles/global-styles.scss @@ -53,6 +53,7 @@ $max-z-index: 2147483647; display: none; } +@import "app-loading-indicator"; @import "photo-cluster-viewer/photo-cluster-viewer"; @import "photo-info-viewer/photo-info-viewer"; @import "plm-leaflet-map/plm-leaflet-map"; From 0eb3b71552d3bed6bc7701fb13528513075757db Mon Sep 17 00:00:00 2001 From: Tomoyuki Aota Date: Wed, 8 Jan 2025 02:50:02 +0900 Subject: [PATCH 4/9] Fade in the application loading indicator. --- src/styles/_app-loading-indicator.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/styles/_app-loading-indicator.scss b/src/styles/_app-loading-indicator.scss index 98dda7c4..751cd531 100644 --- a/src/styles/_app-loading-indicator.scss +++ b/src/styles/_app-loading-indicator.scss @@ -6,6 +6,14 @@ $loading-indicator-color: #888888; height: 100%; display: grid; place-items: center; + + opacity: 0; + animation: fadeInAppLoadingIndicator 2s ease 0.5s forwards; +} + +@keyframes fadeInAppLoadingIndicator { + from { opacity: 0; } + to { opacity: 1; } } .app-loading-indicator { From bb4627af6d2fb69125de5c5dc32361f286d993ff Mon Sep 17 00:00:00 2001 From: Tomoyuki Aota Date: Wed, 8 Jan 2025 03:35:39 +0900 Subject: [PATCH 5/9] Use a spinner as the application loading indicator. This looks better in case of launching multiple apps. (For some reason, there is a significant delay in showing the animation and text when launching multiple apps, and using a spinner looks better in such circumstances.) --- src/index.html | 3 ++- src/styles/_app-loading-indicator.scss | 32 +++++++++----------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/index.html b/src/index.html index 6b5a0fa7..fc7e51d9 100644 --- a/src/index.html +++ b/src/index.html @@ -31,7 +31,8 @@
- Loading + + Loading...
diff --git a/src/styles/_app-loading-indicator.scss b/src/styles/_app-loading-indicator.scss index 751cd531..d7f7e2cb 100644 --- a/src/styles/_app-loading-indicator.scss +++ b/src/styles/_app-loading-indicator.scss @@ -5,6 +5,8 @@ $loading-indicator-color: #888888; .app-loading-indicator-container { height: 100%; display: grid; + gap: 10px; + place-content: center; place-items: center; opacity: 0; @@ -17,33 +19,21 @@ $loading-indicator-color: #888888; } .app-loading-indicator { - color: $loading-indicator-color; + width: 48px; + height: 48px; + border: 5px solid $loading-indicator-color; + border-bottom-color: transparent; + border-radius: 50%; display: inline-block; - position: relative; - font-size: 28px; box-sizing: border-box; + animation: rotation 1s linear infinite; } -.app-loading-indicator::after { - content: ''; - width: 4px; - height: 4px; - background: currentColor; - position: absolute; - bottom: 8px; - right: -8px; - box-sizing: border-box; - animation: appLoadingAnimation 1s linear infinite; -} - -@keyframes appLoadingAnimation { +@keyframes rotation { 0% { - box-shadow: 10px 0 rgba(255, 255, 255, 0), 20px 0 rgba(255, 255, 255, 0); - } - 50% { - box-shadow: 10px 0 $loading-indicator-color, 20px 0 rgba(255, 255, 255, 0); + transform: rotate(0deg); } 100% { - box-shadow: 10px 0 $loading-indicator-color, 20px 0 $loading-indicator-color; + transform: rotate(360deg); } } From 13b116f2a59a8f1289f80ecd54bb53e2d1a72423 Mon Sep 17 00:00:00 2001 From: Tomoyuki Aota Date: Wed, 8 Jan 2025 03:41:04 +0900 Subject: [PATCH 6/9] Update color. --- src/styles/_app-loading-indicator.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/_app-loading-indicator.scss b/src/styles/_app-loading-indicator.scss index d7f7e2cb..d6633d3f 100644 --- a/src/styles/_app-loading-indicator.scss +++ b/src/styles/_app-loading-indicator.scss @@ -8,6 +8,7 @@ $loading-indicator-color: #888888; gap: 10px; place-content: center; place-items: center; + color: $loading-indicator-color; opacity: 0; animation: fadeInAppLoadingIndicator 2s ease 0.5s forwards; From 857e3b256ff49f965e0434d722acffd7a7e5049a Mon Sep 17 00:00:00 2001 From: Tomoyuki Aota Date: Wed, 8 Jan 2025 03:43:43 +0900 Subject: [PATCH 7/9] Rename rotation to rotateAppLoadingIndicator. --- src/styles/_app-loading-indicator.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/_app-loading-indicator.scss b/src/styles/_app-loading-indicator.scss index d6633d3f..9b6fa21c 100644 --- a/src/styles/_app-loading-indicator.scss +++ b/src/styles/_app-loading-indicator.scss @@ -27,10 +27,10 @@ $loading-indicator-color: #888888; border-radius: 50%; display: inline-block; box-sizing: border-box; - animation: rotation 1s linear infinite; + animation: rotateAppLoadingIndicator 1s linear infinite; } -@keyframes rotation { +@keyframes rotateAppLoadingIndicator { 0% { transform: rotate(0deg); } From 5bab1feae6ba1dc9f8d7e70f1c72f0875f86c3c2 Mon Sep 17 00:00:00 2001 From: Tomoyuki Aota Date: Thu, 9 Jan 2025 02:43:31 +0900 Subject: [PATCH 8/9] Record configs for invalid configs object cases. Record responseText. --- ...aster-tile-base-layer-configs-version-1.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts index 89337761..8df5f1bb 100644 --- a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts +++ b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts @@ -62,14 +62,14 @@ const configsFileFetchArguments: Array<{ url: string, options: RequestInit }> = }, ]; -async function fetchRasterTileBaseLayerConfigsVersion1(url: string, options: RequestInit): Promise { +async function fetchRasterTileBaseLayerConfigsVersion1(url: string, options: RequestInit): Promise<{configs: RasterTileBaseLayerConfigsVersion1, responseText: string}> { const response = await fetch(url, options); if (!response.ok) { throw new Error(`response.status: ${response.status}, response.statusText: ${response.statusText}`); } - const jsonc = await response.text(); - const json = parseJsonc(jsonc); - return json as RasterTileBaseLayerConfigsVersion1; + const responseText = await response.text(); + const configs = parseJsonc(responseText) as RasterTileBaseLayerConfigsVersion1; + return {configs, responseText}; } function recordFetchingConfigs(url: string): void { @@ -78,14 +78,14 @@ function recordFetchingConfigs(url: string): void { Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fetching BaseLayerConfigs`, message); } -function recordInvalidConfigsObjectError(url: string): void { - const message = `Invalid configs object is fetched from ${url}.`; +function recordInvalidConfigsObjectError(url: string, configs: any, responseText: string): void { + const message = `Invalid configs object is fetched from ${url}.\n----------\nconfigs:\n${toLoggableString(configs)}\n----------\nresponseText:\n${responseText}`; logger.error(message); Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Invalid BaseLayerConfigs`, message); } -function recordFetchSuccess(url: string, configs: RasterTileBaseLayerConfigsVersion1): void { - const message = `Fetched ${url}\n${toLoggableString(configs)}`; +function recordFetchSuccess(url: string, configs: RasterTileBaseLayerConfigsVersion1, responseText: string): void { + const message = `Fetched ${url}\n----------\nconfigs:\n${toLoggableString(configs)}\n----------\nresponseText:\n${responseText}`; logger.info(message); Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fetched BaseLayerConfigs`, message); } @@ -105,12 +105,12 @@ async function fetchRasterTileBaseLayerConfigsVersion1WithRetry(): Promise Date: Thu, 9 Jan 2025 03:03:47 +0900 Subject: [PATCH 9/9] Update logging/analytics for the case of failing to fetch from all the possible URLs. --- .../leaflet-map/raster-tile-base-layer-configs-version-1.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts index 8df5f1bb..ac2ec49e 100644 --- a/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts +++ b/src/app/map/leaflet-map/raster-tile-base-layer-configs-version-1.ts @@ -97,8 +97,9 @@ function recordFetchFailed(url: string, error: Error): void { } function recordFetchFailedForAllUrls(): void { - logger.error(`Failed to fetch the configs from all the URLs. Using the fallback configs.`); - Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fallback BaseLayerConfigs`); + const message = `Failed to fetch the configs from all the possible URLs. Using the fallback configs.`; + logger.error(message); + Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fallback BaseLayerConfigs`, message); } async function fetchRasterTileBaseLayerConfigsVersion1WithRetry(): Promise {