Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PB 1151: add warning message kml import partially out of bounds #1195

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions packages/mapviewer/src/api/layers/AbstractLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ export default class AbstractLayer {
/** @type {Set<ErrorMessage>} */
this.errorMessages = new Set()
this.hasError = false
/** @type {Set<WarningMessage><} */
this.warningMessages = new Set()
this.hasWarning = false
this.timeConfig = timeConfig
this.hasMultipleTimestamps = this.timeConfig?.timeEntries?.length > 1
this.setCustomAttributes(customAttributes)
Expand Down Expand Up @@ -171,6 +174,42 @@ export default class AbstractLayer {
this.hasError = false
}

/**
* @param {WarningMessage} warningMessage
* @returns {boolean}
*/
containsWarningMessage(warningMessage) {
return this.warningMessages.has(warningMessage)
}

/** @returns {WarningMessage} */
getFirstWarningMessage() {
return this.warningMessages.values().next().value
}

/** @param {WarningMessage} warningMessage */
addWarningMessage(warningMessage) {
this.warningMessages.add(warningMessage)
this.hasWarning = true
}

/** @param {WarningMessage} warningMessage */
removeWarningMessage(warningMessage) {
// We need to find the error message that equals to remove it
for (let msg of this.warningMessages) {
if (msg.isEquals(warningMessage)) {
this.warningMessages.delete(msg)
break
}
}
this.hasWarning = !!this.warningMessages.size
}

clearWarningMessages() {
this.warningMessages.clear()
this.hasWarning = false
}

setCustomAttributes(customAttributes) {
if (customAttributes !== null) {
if (typeof customAttributes !== 'object') {
Expand Down
1 change: 1 addition & 0 deletions packages/mapviewer/src/modules/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
"feedback_unsupported_format": "Dieser Dateityp wird leider nicht unterstützt. Bitte verwenden Sie eine .pdf, .zip, .jpg, .jpeg, .kml, .kmz oder .gpx Datei.",
"field_required": "Dieses Feld ist erforderlich",
"file_imported_success": "Datei erfolgreich importiert",
"file_imported_partially_out_of_bounds": "Die importierte Datei '{filename}' befindet sich teilweise außerhalb der Schweizer Grenzen. Einige Funktionalitäten stehen möglicherweise nicht zur Verfügung.",
"file_is_not_kml": "Dieses File ist keine KML Datei. ",
"file_name": "Zeichnungsname",
"file_too_large": "Die Datei ist zu gross (maximal erlaubte Grösse: {maxFileSize}).",
Expand Down
1 change: 1 addition & 0 deletions packages/mapviewer/src/modules/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
"feedback_unsupported_format": "This file format is not supported. Thanks for using another format for you attachment.",
"field_required": "This field is required",
"file_imported_success": "File successfully imported",
"file_imported_partially_out_of_bounds": "The imported file '{filename}' is partially outside the swiss boundaries. Some functionalities might not be available.",
"file_is_not_kml": "The file is not a KML file.",
"file_name": "Drawing name",
"file_too_large": "The file is too large (max size allowed {maxFileSize}).",
Expand Down
1 change: 1 addition & 0 deletions packages/mapviewer/src/modules/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
"feedback_unsupported_format": "Le format du fichier n’est pas pris en charge, merci d’utiliser un autre format pour votre pièce jointe.",
"field_required": "Ce champ est requis",
"file_imported_success": "Fichier importé avec succès",
"file_imported_partially_out_of_bounds": "Le fichier importé '{filename}' est partiellement en dehors des frontières suisses. Certaines fonctionnalités peuvent ne pas être disponibles.",
"file_is_not_kml": "Ce fichier n'est pas un fichier KML.",
"file_name": "Nom du dessin",
"file_too_large": "Ce fichier est trop volumineux (taille maximale autorisée : {maxFileSize})",
Expand Down
1 change: 1 addition & 0 deletions packages/mapviewer/src/modules/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
"feedback_unsupported_format": "Il formato del file selezionato non è supportato dal sistema, per favore utilizzare un altro formato per il vostro allegato.",
"field_required": "Questo campo è obbligatorio",
"file_imported_success": "File importato con successo",
"file_imported_partially_out_of_bounds": "Il file importato '{filename}' è parzialmente fuori dai confini svizzeri. Alcune funzionalità potrebbero non essere disponibili.",
"file_is_not_kml": "Questo file non è un file KML.",
"file_name": "Nome del disegno",
"file_too_large": "Il file é troppo grande (dimensione massima consentita: {maxFileSize})",
Expand Down
1 change: 1 addition & 0 deletions packages/mapviewer/src/modules/i18n/locales/rm.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
"feedback_unsupported_format": "Tip da datoteca betg supportà. Duvrai empè da quai pdf, .zip, .jpg, .jpeg, .kml, .kmz u .gpx",
"field_required": "Quest champ è obligatoric",
"file_imported_success": "Datoteca importada cun success",
"file_imported_partially_out_of_bounds": "Il dossier importà '{filename}' sa chatta per part ordaifer ils cunfins svizzers. Tschertas funcziuns na fissan forsa betg disponiblas.",
"file_is_not_kml": "Questa datoteca nun è ina datoteca KML",
"file_name": "Zeichnungsname",
"file_too_large": "Questa datoteca è memia grond (grondezza maximala lubida: {maxFileSize})",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import DropdownButton from '@/utils/components/DropdownButton.vue'
import ErrorButton from '@/utils/components/ErrorButton.vue'
import TextTruncate from '@/utils/components/TextTruncate.vue'
import ThirdPartyDisclaimer from '@/utils/components/ThirdPartyDisclaimer.vue'
import WarningButton from '@/utils/components/WarningButton.vue'
import ZoomToExtentButton from '@/utils/components/ZoomToExtentButton.vue'
import { useTippyTooltip } from '@/utils/composables/useTippyTooltip'
import debounce from '@/utils/debounce'
Expand Down Expand Up @@ -228,6 +229,12 @@ function changeStyle(newStyle) {
:error-message="layer.getFirstErrorMessage()"
:data-cy="`button-error-${id}-${index}`"
/>
<WarningButton
v-else-if="layer.hasWarning"
:compact="compact"
:warning-message="layer.getFirstWarningMessage()"
:data-cy="`button-warning-${id}-${index}`"
/>
<MenuActiveLayersListItemTimeSelector
v-if="hasMultipleTimestamps"
:layer-index="index"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const errorFileLoadingMessage = ref(null)
const isFormValid = ref(false)
const activateValidation = ref(false)
const importSuccessMessage = ref('')

const buttonState = computed(() => (loadingFile.value ? 'loading' : 'default'))

// Methods
Expand Down Expand Up @@ -77,11 +76,7 @@ function validateForm(valid) {
:valid-message="importSuccessMessage"
@validate="validateForm"
/>
<ImportFileButtons
class="mt-2"
:button-state="buttonState"
@load-file="loadFile"
/>
<ImportFileButtons class="mt-2" :button-state="buttonState" @load-file="loadFile" />
</div>
</template>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const importSuccessMessage = ref('')
const errorFileLoadingMessage = ref(null)
const isFormValid = ref(false)
const activateValidation = ref(false)

const buttonState = computed(() => (loading.value ? 'loading' : 'default'))

watch(
Expand Down Expand Up @@ -87,6 +86,7 @@ async function loadFile() {
dispatcher: 'Import File Online Tab',
})
}

importSuccessMessage.value = 'file_imported_success'
setTimeout(() => (buttonState.value = 'default'), 3000)
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default class GPXParser extends FileParser {
if (!extentInCurrentProjection) {
throw new OutOfBoundsError(`GPX is out of bounds of current projection: ${extent}`)
}
//TO DO HERE : add warning if extent is not the same
return new GPXLayer({
gpxFileUrl: this.isLocalFile(fileSource) ? fileSource.name : fileSource,
visible: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class KMLParser extends FileParser {
if (!extentInCurrentProjection) {
throw new OutOfBoundsError(`KML is out of bounds of current projection: ${extent}`)
}
// TO DO HERE : add warning if extents =!=
return new KMLLayer({
kmlFileUrl: this.isLocalFile(fileSource) ? fileSource.name : fileSource,
visible: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useStore } from 'vuex'

import { parseLayerFromFile } from '@/modules/menu/components/advancedTools/ImportFile/parser'
import generateErrorMessageFromErrorType from '@/modules/menu/components/advancedTools/ImportFile/parser/errors/generateErrorMessageFromErrorType.utils'
import WarningMessage from '@/utils/WarningMessage.class'

const dispatcher = {
dispatcher: 'useImportFile.composable',
Expand All @@ -18,9 +19,11 @@ export default function useImportFile() {
* @param {File | String} source
* @param {Boolean} [sendErrorToStore=true] If true, will dispatch any error to the store, if
* false will throw any error at you. Default is `true`
* @param {Boolean} [sendWarningToStore=true] If true, will dispatch any warning to the store,
* if false, it will ignore it. Default is `true`
* @returns {Promise<void>}
*/
async function handleFileSource(source, sendErrorToStore = true) {
async function handleFileSource(source, sendErrorToStore = true, sendWarningToStore = true) {
if (!source) {
return
}
Expand All @@ -30,13 +33,43 @@ export default function useImportFile() {
})
try {
const layer = await parseLayerFromFile(source, projection.value)

// checking that the same layer is not already present before adding it
if (store.getters.getActiveLayersById(layer.id).length === 0) {
const extent =
layer.extent.length === 4
? layer.extent
: [...layer.extent[0], ...layer.extent[1]]
const lastImportedLayerIsPartiallyOutOfBounds =
projection.value.bounds.lowerX > extent[0] ||
projection.value.bounds.lowerX > extent[2] ||
projection.value.bounds.upperX < extent[0] ||
projection.value.bounds.upperX < extent[2] ||
projection.value.bounds.lowerY > extent[1] ||
projection.value.bounds.lowerY > extent[3] ||
projection.value.bounds.upperY < extent[1] ||
projection.value.bounds.upperY < extent[3]
if (lastImportedLayerIsPartiallyOutOfBounds) {
layer.hasWarning = true
layer.addWarningMessage(
new WarningMessage('file_imported_partially_out_of_bounds', {
filename: layer.name ?? layer.id,
})
)
}

await store.dispatch('addLayer', {
layer,
zoomToLayerExtent: true,
...dispatcher,
})

if (sendWarningToStore && layer.hasWarning) {
store.dispatch('addWarnings', {
warnings: Array.from(layer.warningMessages),
...dispatcher,
})
}
}
} catch (error) {
if (!sendErrorToStore) {
Expand Down
2 changes: 2 additions & 0 deletions packages/mapviewer/src/setup-fontawesome.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import {
faTimes,
faTimesCircle,
faTree,
faTriangleExclamation,
faWindowMaximize,
faWindowMinimize,
faXmark,
Expand Down Expand Up @@ -167,6 +168,7 @@ library.add(
faSortAlphaDown,
faCirclePlus,
faCircleMinus,
faTriangleExclamation,
// Regular
faCheckSquare,
faClock,
Expand Down
1 change: 0 additions & 1 deletion packages/mapviewer/src/store/modules/ui.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ export default {
* @type Boolean
*/
showDragAndDropOverlay: false,

/**
* Flag set to true when the app is opening a new tab.
*
Expand Down
23 changes: 23 additions & 0 deletions packages/mapviewer/src/utils/components/FileInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ const props = defineProps({
type: String,
default: '',
},
/**
* Message displayed when the imported file is valid, but has some issues that we should notify
* the user. Most common issue is when we have a layer which is partially out of bound
*/
warningMessage: {
type: String,
default: '',
},
/**
* Mark the field as invalid
*
Expand Down Expand Up @@ -295,6 +303,13 @@ function onFileSelected(evt) {
>
{{ t(validMessage) }}
</div>
<div
v-if="warningMessage"
class="warning-feedback"
data-cy="file-input-warning-feedback"
>
{{ i18n.t(warningMessage) }}
</div>
</div>
<div
v-if="description"
Expand All @@ -312,4 +327,12 @@ function onFileSelected(evt) {
.local-file-input {
cursor: pointer;
}

.warning-feedback {
display: block;
width: 100%;
margin-top: 0.25rem;
font-size: 0.875em;
color: $warning;
}
</style>
74 changes: 74 additions & 0 deletions packages/mapviewer/src/utils/components/WarningButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script setup>
import tippy from 'tippy.js'
import { computed, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'

import WarningMessage from '@/utils/WarningMessage.class'

const props = defineProps({
compact: {
type: Boolean,
required: true,
},
warningMessage: {
type: WarningMessage,
required: true,
},
})
const { compact, warningMessage } = toRefs(props)
const warningButton = ref(null)

const i18n = useI18n()
const store = useStore()

const lang = computed(() => store.state.i18n.lang)
const translatedMessage = computed(() =>
i18n.t(warningMessage.value.msg, warningMessage.value.params)
)

let tippyInstance = null

watch(lang, () => tippyInstance?.setContent(translatedMessage.value))

onMounted(() => {
tippyInstance = tippy(warningButton.value, {
theme: 'warning',
content: translatedMessage.value,
arrow: true,
placement: 'top',
touch: false,
hideOnClick: false,
onCreate: (instance) => {
const dataCy = instance.reference.getAttribute('data-cy')
if (dataCy) {
instance.popper.setAttribute('data-cy', `tippy-${dataCy}`)
}
},
})
})

onBeforeUnmount(() => {
tippyInstance?.destroy()
tippyInstance = null
})
</script>

<template>
<div ref="warningButton">
<button
class="btn text-warning border-0 p-0 d-flex align-items-center"
:class="{
'btn-lg': !compact,
}"
disabled
aria-disabled="true"
tabindex="-1"
:data-cy="`button-has-warning`"
>
<FontAwesomeIcon icon="triangle-exclamation" />
</button>
</div>
</template>

<style lang="scss" scoped></style>
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,11 @@ describe('The Import File Tool', () => {
cy.get('[data-cy="file-input-text"]')
.should('have.class', 'is-valid')
.should('not.have.class', 'is-invalid')
cy.get('[data-cy="file-input-valid-feedback"]')
cy.get('[data-cy="file-input-warning-feedback"]')
.should('be.visible')
.contains('File successfully imported')
.contains(
'The imported file is partially within the swiss boundaries. Some functionalities might not be available.'
)
cy.get('[data-cy="import-file-load-button"]').should('be.visible').contains('Import')
cy.get('[data-cy="import-file-online-content"]').should('not.be.visible')
cy.readStoreValue('state.layers.activeLayers').should('have.length', 4)
Expand Down
Loading