Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b7f37da
PB-2064: Fix the wrong emit value.
ismailsunni Nov 25, 2025
6f9882d
PB-2064: Fix missing success message.
ismailsunni Nov 25, 2025
80d976e
PB-2064: Better solution without withDefaults.
ismailsunni Nov 26, 2025
2afa88d
PB-2064: Accept computed props.
ismailsunni Nov 26, 2025
6153495
PB-2064: Use simpler way to handle not provided prop.
ismailsunni Nov 26, 2025
c6d9cd5
PB-2064: Use the same approach for FileInput as in TextInput.
ismailsunni Nov 26, 2025
f41a558
PB-2064: Unify field validation: use computed props in both component…
ismailsunni Nov 26, 2025
7ebfd7c
PB-2064: Update TextAreaInput and EmailInput to use computed validati…
ismailsunni Nov 26, 2025
6198136
PB-2064: Fix coordinate typo and timing issues
ismailsunni Nov 26, 2025
d707314
PB-2064: Fix KML name and some tests.
ismailsunni Nov 27, 2025
a6dd8a7
PB-2064: Fix failed test on KML: KML layer id, hasWarning
ismailsunni Nov 27, 2025
3e8c56a
PB-2064: Fix missing attribution on KML Layer.
ismailsunni Nov 27, 2025
42428ef
PB-2064: Fix test on KML layer's buttons
ismailsunni Nov 28, 2025
d389aa8
PB-2064: Spliting some test for easier fix. Skip 3D test and warning …
ismailsunni Nov 28, 2025
4d2df2a
PB-2064: Partially fix on KML error handling, due to different layer id.
ismailsunni Nov 28, 2025
2b9bc21
PB-2064: Fix TS type error.
ismailsunni Nov 28, 2025
fac4d34
PB-2064: Fix error prioritization in file parser
ismailsunni Nov 28, 2025
0f446a6
PB-2064: Extract GPX profile error test into separate test block
ismailsunni Nov 28, 2025
8e0b2e3
PB-2064: Add another goToMapView to fix cypress running issue in the CI.
ismailsunni Dec 1, 2025
c0b7032
PB-2064: Fix import KML file error handling after rebase.
ismailsunni Dec 5, 2025
e4565db
PB-2064: don't use force true to fix failed test after rebase.
ismailsunni Dec 8, 2025
f9cd5c5
PB-2064: No force true on click to fix the failed test.
ismailsunni Dec 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions packages/layers/src/utils/layerUtils.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -23,6 +24,7 @@ import {
type KMLLayer,
KMLStyle,
type Layer,
type LayerAttribution,
LayerType,
WMTSEncodingType,
} from '@/types/layers'
Expand Down Expand Up @@ -258,6 +260,9 @@ function makeKMLLayer(values: Partial<KMLLayer>): KMLLayer {
}
const kmlFileUrl: string = values.kmlFileUrl

// Detect if this is a local file or a URL
const isLocalFile = !kmlFileUrl.startsWith('http')

let isExternal: boolean = true
if (values.isExternal !== undefined) {
isExternal = values.isExternal
Expand Down Expand Up @@ -291,6 +296,27 @@ function makeKMLLayer(values: Partial<KMLLayer>): 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(),
Expand All @@ -305,8 +331,8 @@ function makeKMLLayer(values: Partial<KMLLayer>): KMLLayer {
fileId,
kmlData: undefined,
kmlMetadata: undefined,
isLocalFile: false,
attributions: [],
isLocalFile,
attributions,
style,
type: LayerType.KML,
hasTooltip: false,
Expand All @@ -324,6 +350,7 @@ function makeKMLLayer(values: Partial<KMLLayer>): KMLLayer {
}

const layer: KMLLayer = merge(defaults, values)

validateBaseData(layer)
return layer
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -108,7 +107,6 @@ async function loadFile() {
errorFileLoadingMessage.value = generateErrorMessageFromErrorType(error)
}
}
isLoading.value = false
}
</script>

Expand All @@ -133,10 +131,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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<KMLLayer> {
constructor() {
Expand Down Expand Up @@ -53,6 +53,7 @@ export class KMLParser extends FileParser<KMLLayer> {
}

const kmlFileUrl = this.isLocalFile(fileSource) ? fileSource.name : fileSource
const kmlName = parseKmlName(kmlAsText) ?? kmlFileUrl
const internalFiles: Record<string, ArrayBuffer> = {}
linkFiles.forEach((value, key) => {
internalFiles[key] = value
Expand All @@ -62,19 +63,22 @@ export class KMLParser extends FileParser<KMLLayer> {
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,
kmlFileUrl,
kmlData: kmlAsText,
extentProjection: currentProjection,
warningMessages,
hasWarning: warningMessages.length > 0,
isLoading: false, // Local files already have their data loaded
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ 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 { 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'
Expand All @@ -36,12 +37,42 @@ async function parseAll(config: ParseAllConfig, options?: ParseOptions): Promise
if (firstFulfilled) {
return (firstFulfilled as PromiseFulfilledResult<FileLayer>).value
}
const anyErrorRaised = allSettled.find(

// Prioritize specific errors over generic InvalidFileContentError
// This ensures that if a parser successfully identifies the file format
// but encounters a specific issue (e.g., out of bounds), that error is shown
const rejectedResponses = allSettled.filter(
(response) => response.status === 'rejected' && response.reason
) as PromiseRejectedResult[]

// Priority order: OutOfBounds > Empty > UnknownProjection > Invalid > Other
const outOfBoundsError = rejectedResponses.find(
(response) => response.reason instanceof OutOfBoundsError
)
if (outOfBoundsError) {
throw outOfBoundsError.reason
}

const emptyFileError = rejectedResponses.find(
(response) => response.reason instanceof EmptyFileContentError
)
if (anyErrorRaised) {
throw (anyErrorRaised as PromiseRejectedResult).reason
if (emptyFileError) {
throw emptyFileError.reason
}

const unknownProjectionError = rejectedResponses.find(
(response) => response.reason instanceof UnknownProjectionError
)
if (unknownProjectionError) {
throw unknownProjectionError.reason
}
Comment on lines +48 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you bunch all these instance checking in one if block, if the goal is to prioritize anything else than InvalidFileContentError?

I don't really see the benefit of treating them one by one, with the goal you stated


// Fall back to any error (including InvalidFileContentError)
const firstError = rejectedResponses[0]
if (firstError) {
throw firstError.reason
}

throw new Error('Could not parse file')
}

Expand All @@ -66,7 +97,7 @@ export async function parseLayerFromFile(
const fileComplianceCheck = await checkOnlineFileCompliance(fileSource)
log.debug({
title: '[FileParser][parseLayerFromFile]',
messages: ['file', fileSource, 'has compliance', fileComplianceCheck]
messages: ['file', fileSource, 'has compliance', fileComplianceCheck],
})
const { mimeType, supportsCORS, supportsHTTPS } = fileComplianceCheck

Expand All @@ -79,7 +110,7 @@ export async function parseLayerFromFile(
if (parserMatchingMIME) {
log.debug({
title: '[FileParser][parseLayerFromFile]',
messages: ['parser found for MIME type', mimeType, parserMatchingMIME]
messages: ['parser found for MIME type', mimeType, parserMatchingMIME],
})
return parserMatchingMIME.parseUrl(fileSource, currentProjection, {
fileCompliance: fileComplianceCheck,
Expand All @@ -91,7 +122,7 @@ export async function parseLayerFromFile(
try {
log.debug({
title: '[FileParser][parseLayerFromFile]',
messages: ['no MIME type match, loading file content for', fileSource]
messages: ['no MIME type match, loading file content for', fileSource],
})
let loadedContent: ArrayBuffer | undefined
if (supportsCORS && supportsHTTPS) {
Expand Down
6 changes: 3 additions & 3 deletions packages/viewer/src/utils/components/EmailInput.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { computed, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'

import { useComponentUniqueId } from '@/utils/composables/useComponentUniqueId'
Expand Down Expand Up @@ -89,15 +89,15 @@ const model = defineModel<string>({ default: '' })
const emits = defineEmits(['change', 'validate', 'focusin', 'focusout', 'keydown.enter'])
const { t } = useI18n()

const validationProps = {
const validationProps = computed(() => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why use a computed here?
Do you need some refs here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because when we use the prop deconstruction with a default value, we lose the reactivity if we pass it to the composable. So, I need to change useFieldValidation to accept ComputedRef.

required,
validMarker,
validMessage,
invalidMarker,
invalidMessage,
activateValidation,
validate,
}
}))

const {
value,
Expand Down
12 changes: 6 additions & 6 deletions packages/viewer/src/utils/components/FileInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const {
validMessage = '',
invalidMarker = undefined,
invalidMessage = '',
invalidMessageExtraParams = {},
invalidMessageExtraParams = undefined,
activateValidation = false,
validate = undefined,
dataCy = '',
Expand Down Expand Up @@ -146,23 +146,23 @@ function validateFile(): { valid: boolean; invalidMessage: string } {
}
}

// Use the field validation composable with properly typed props
const validationProps = {
const validationProps = computed(() => ({
required,
validMarker,
validMessage,
invalidMarker,
invalidMessage,
activateValidation,
validate,
}
}))

const {
value,
validMarker: computedValidMarker,
invalidMarker: computedInvalidMarker,
validMessage: computedValidMessage,
invalidMessage: computedInvalidMessage,
required: computedRequired,
} = useFieldValidation<File>(
validationProps,
model,
Expand Down Expand Up @@ -199,7 +199,7 @@ function onFileSelected(evt: Event): void {
<label
v-if="label"
class="mb-2"
:class="{ 'fw-bolder': required }"
:class="{ 'fw-bolder': computedRequired }"
:for="inputFileId"
data-cy="file-input-label"
>
Expand Down Expand Up @@ -249,7 +249,7 @@ function onFileSelected(evt: Event): void {
t(computedInvalidMessage, {
maxFileSize: maxFileSizeHuman,
allowedFormats: acceptedFileTypes.join(', '),
...invalidMessageExtraParams,
...(invalidMessageExtraParams ?? {}),
})
}}
</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/viewer/src/utils/components/TextAreaInput.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { computed, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'

import { useComponentUniqueId } from '@/utils/composables/useComponentUniqueId'
Expand Down Expand Up @@ -90,15 +90,15 @@ const { t } = useI18n()

const textAreaElement = useTemplateRef('textAreaElement')

const validationProps = {
const validationProps = computed(() => ({
required,
validMarker,
validMessage,
invalidMarker,
invalidMessage,
activateValidation,
validate,
}
}))

const {
value,
Expand Down
10 changes: 5 additions & 5 deletions packages/viewer/src/utils/components/TextInput.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
/** Input with clear button component */
import { nextTick, ref, useSlots, useTemplateRef } from 'vue'
import { computed, nextTick, ref, useSlots, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'

import { useComponentUniqueId } from '@/utils/composables/useComponentUniqueId'
Expand All @@ -23,9 +23,9 @@ const {
disabled = false,
placeholder = '',
required = false,
validMarker = undefined,
validMarker = false,
validMessage = '',
invalidMarker = undefined,
invalidMarker = false,
invalidMessage = '',
invalidMessageParams = undefined,
activateValidation = false,
Expand Down Expand Up @@ -114,15 +114,15 @@ const emits = defineEmits<{
'keydown.enter': []
}>()

const validationProps = {
const validationProps = computed(() => ({
required,
validMarker,
validMessage,
invalidMarker,
invalidMessage,
activateValidation,
validate,
}
}))

const {
value,
Expand Down
Loading
Loading