diff --git a/packages/layers/src/utils/layerUtils.ts b/packages/layers/src/utils/layerUtils.ts index 7ff0b404ff..b702a2b9fb 100644 --- a/packages/layers/src/utils/layerUtils.ts +++ b/packages/layers/src/utils/layerUtils.ts @@ -1,5 +1,6 @@ import type { CoordinateSystem } from '@swissgeo/coordinates' +import log from '@swissgeo/log' import { servicesBaseUrl } from '@swissgeo/staging-config' import { cloneDeep, merge, omit } from 'lodash' import { v4 as uuidv4 } from 'uuid' @@ -20,9 +21,11 @@ import type { GPXLayer, KMLLayer, Layer, + LayerAttribution, } from '@/types/layers' import { DEFAULT_GEOADMIN_MAX_WMTS_RESOLUTION } from '@/config' + import { DEFAULT_OPACITY, KMLStyle, LayerType, WMTSEncodingType } from '@/types/layers' import timeConfigUtils from '@/utils/timeConfigUtils' import { InvalidLayerDataError } from '@/validation' @@ -256,6 +259,9 @@ function makeKMLLayer(values: Partial): KMLLayer { } const kmlFileUrl: string = values.kmlFileUrl + // Detect if this is a local file or a URL + const isLocalFile = !kmlFileUrl.includes('://') + let isExternal: boolean = true if (values.isExternal !== undefined) { isExternal = values.isExternal @@ -289,6 +295,27 @@ function makeKMLLayer(values: Partial): KMLLayer { fileId = kmlFileUrl.split('/').pop() } + // Set up attributions based on file source (only if not provided in values) + let attributions: LayerAttribution[] + if (values.attributions && values.attributions.length > 0) { + attributions = values.attributions + } else { + let attributionName: string + if (isLocalFile) { + attributionName = kmlFileUrl + } else { + try { + attributionName = new URL(kmlFileUrl).hostname + } catch (err) { + const error = err ? (err as Error) : new Error('Unknown error') + log.error('Error parsing KML file URL for attribution:', error) + // If URL parsing fails, fall back to using the kmlFileUrl as-is + attributionName = kmlFileUrl + } + } + attributions = [{ name: attributionName }] + } + const defaults: KMLLayer = { kmlFileUrl: '', uuid: uuidv4(), @@ -303,8 +330,8 @@ function makeKMLLayer(values: Partial): KMLLayer { fileId, kmlData: undefined, kmlMetadata: undefined, - isLocalFile: false, - attributions: [], + isLocalFile, + attributions, style, type: LayerType.KML, hasTooltip: false, @@ -322,6 +349,17 @@ function makeKMLLayer(values: Partial): KMLLayer { } const layer: KMLLayer = merge(defaults, values) + + // If kmlData is already provided, set isLoading to false + if (layer.kmlData) { + layer.isLoading = false + } + + // Set hasWarning based on whether warningMessages exist + if (layer.warningMessages && layer.warningMessages.length > 0) { + layer.hasWarning = true + } + validateBaseData(layer) return layer } @@ -333,7 +371,7 @@ function makeKMLLayer(values: Partial): KMLLayer { * the function parameter will be used from defaults */ function makeGPXLayer(values: Partial): GPXLayer { - const isLocalFile = !values.gpxFileUrl?.startsWith('http') + const isLocalFile = !values.gpxFileUrl?.includes('://') if (!values.gpxFileUrl) { throw new InvalidLayerDataError('Missing GPX file URL', values) } @@ -470,7 +508,7 @@ function makeCloudOptimizedGeoTIFFLayer( } const fileSource = values.fileSource - const isLocalFile = !fileSource?.startsWith('http') + const isLocalFile = !fileSource?.includes('://') const attributionName = isLocalFile ? fileSource : new URL(fileSource).hostname const attributions = [{ name: attributionName }] const fileName = isLocalFile diff --git a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue index c5a546b4f3..64bf9dd02b 100644 --- a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue +++ b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue @@ -78,6 +78,7 @@ function validateForm(valid: boolean) { :invalid-marker="!!errorFileLoadingMessage" :invalid-message="errorFileLoadingMessage?.msg" :invalid-message-extra-params="errorFileLoadingMessage?.params" + :valid-marker="!!importSuccessMessage" :valid-message="importSuccessMessage" @validate="validateForm" /> diff --git a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue index 692f71a40c..0980283928 100644 --- a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue +++ b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue @@ -95,9 +95,8 @@ async function loadFile() { dispatcher ) } + isLoading.value = false importSuccessMessage.value = 'file_imported_success' - - setTimeout(() => (isLoading.value = false), 3000) } catch (error) { log.error({ title: 'Import File Online Tab', @@ -107,8 +106,9 @@ async function loadFile() { if (error instanceof Error) { errorFileLoadingMessage.value = generateErrorMessageFromErrorType(error) } + } finally { + isLoading.value = false } - isLoading.value = false } @@ -133,10 +133,11 @@ async function loadFile() { class="mb-2" placeholder="import_file_url_placeholder" :activate-validation="activateValidation" + :valid-marker="!!importSuccessMessage" + :valid-message="importSuccessMessage" :invalid-marker="!!errorFileLoadingMessage" :invalid-message="errorFileLoadingMessage?.msg" :invalid-message-params="errorFileLoadingMessage?.params" - :valid-message="importSuccessMessage" :validate="validateUrl" data-cy="import-file-online-url" @validate="onUrlValidate" diff --git a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.ts b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.ts index d59c05ca68..07b6e6d08e 100644 --- a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.ts +++ b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class.ts @@ -9,7 +9,7 @@ import EmptyFileContentError from '@/modules/menu/components/advancedTools/Impor import InvalidFileContentError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/InvalidFileContentError.error' import OutOfBoundsError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/OutOfBoundsError.error' import FileParser from '@/modules/menu/components/advancedTools/ImportFile/parser/FileParser.class' -import { getKmlExtent, isKml, isKmlFeaturesValid } from '@/utils/kmlUtils' +import { getKmlExtent, isKml, isKmlFeaturesValid, parseKmlName } from '@/utils/kmlUtils' export class KMLParser extends FileParser { constructor() { @@ -53,6 +53,7 @@ export class KMLParser extends FileParser { } const kmlFileUrl = this.isLocalFile(fileSource) ? fileSource.name : fileSource + const kmlName = parseKmlName(kmlAsText) ?? kmlFileUrl const internalFiles: Record = {} linkFiles.forEach((value, key) => { internalFiles[key] = value @@ -62,12 +63,13 @@ export class KMLParser extends FileParser { if (!isKmlFeaturesValid(kmlAsText)) { warningMessages.push( new WarningMessage('kml_malformed', { - filename: kmlFileUrl, + filename: kmlName, }) ) } return layerUtils.makeKMLLayer({ + name: kmlName, opacity: 1.0, isVisible: true, extent: extentInCurrentProjection, diff --git a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/index.ts b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/index.ts index e771a2ec4b..61c798b386 100644 --- a/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/index.ts +++ b/packages/viewer/src/modules/menu/components/advancedTools/ImportFile/parser/index.ts @@ -8,6 +8,9 @@ import type { ParseOptions } from '@/modules/menu/components/advancedTools/Impor import { getFileContentThroughServiceProxy } from '@/api/file-proxy.api' import { checkOnlineFileCompliance, getFileContentFromUrl } from '@/api/files.api' import { CloudOptimizedGeoTIFFParser } from '@/modules/menu/components/advancedTools/ImportFile/parser/CloudOptimizedGeoTIFFParser.class' +import EmptyFileContentError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/EmptyFileContentError.error' +import OutOfBoundsError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/OutOfBoundsError.error' +import UnknownProjectionError from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/UnknownProjectionError.error' import GPXParser from '@/modules/menu/components/advancedTools/ImportFile/parser/GPXParser.class' import { KMLParser } from '@/modules/menu/components/advancedTools/ImportFile/parser/KMLParser.class' import KMZParser from '@/modules/menu/components/advancedTools/ImportFile/parser/KMZParser.class' @@ -34,12 +37,29 @@ async function parseAll(config: ParseAllConfig, options?: ParseOptions): Promise if (firstFulfilled) { return (firstFulfilled as PromiseFulfilledResult).value } - const anyErrorRaised = allSettled.find( + + // Prioritize specific errors over generic InvalidFileContentError + const rejectedResponses = allSettled.filter( (response) => response.status === 'rejected' && response.reason + ) as PromiseRejectedResult[] + + // Find first specific error (OutOfBounds, Empty, UnknownProjection) + const specificError = rejectedResponses.find( + (response) => + response.reason instanceof OutOfBoundsError || + response.reason instanceof EmptyFileContentError || + response.reason instanceof UnknownProjectionError ) - if (anyErrorRaised) { - throw (anyErrorRaised as PromiseRejectedResult).reason + if (specificError) { + throw specificError.reason } + + // Fall back to any error (including InvalidFileContentError) + const firstError = rejectedResponses[0] + if (firstError) { + throw firstError.reason + } + throw new Error('Could not parse file') } diff --git a/packages/viewer/src/utils/components/EmailInput.vue b/packages/viewer/src/utils/components/EmailInput.vue index af7feefaa1..00f8e10d2f 100644 --- a/packages/viewer/src/utils/components/EmailInput.vue +++ b/packages/viewer/src/utils/components/EmailInput.vue @@ -1,5 +1,5 @@