Skip to content

Commit

Permalink
Merge pull request #1071 from geoadmin/feat-pb-901-kml-change-name-file
Browse files Browse the repository at this point in the history
PB-901: Modify name of KML file
  • Loading branch information
sommerfe authored Oct 24, 2024
2 parents f02da8c + c7850c9 commit f4a4a47
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 96 deletions.
5 changes: 4 additions & 1 deletion src/api/layers/KMLLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { EMPTY_KML_DATA, parseKmlName } from '@/utils/kmlUtils'
*/
export default class KMLLayer extends AbstractLayer {
/**
* @param {String} [kmlLayerData.name] The name for this KML layer. If none is given, 'KML' will
* be used.
* @param {String} kmlLayerData.kmlFileUrl The URL to access the KML data.
* @param {Boolean} [kmlLayerData.visible=true] If the layer is visible on the map (or hidden).
* When `null` is given, then it uses the default value. Default is `true`
Expand All @@ -39,6 +41,7 @@ export default class KMLLayer extends AbstractLayer {
throw new InvalidLayerDataError('Missing KML layer data', kmlLayerData)
}
const {
name = null,
kmlFileUrl = null,
visible = true,
opacity = 1.0,
Expand All @@ -54,7 +57,7 @@ export default class KMLLayer extends AbstractLayer {
const attributionName = isLocalFile ? kmlFileUrl : new URL(kmlFileUrl).hostname
const isExternal = kmlFileUrl.indexOf(getServiceKmlBaseUrl()) === -1
super({
name: 'KML',
name: name ?? 'KML',
id: kmlFileUrl,
type: LayerTypes.KML,
baseUrl: kmlFileUrl,
Expand Down
3 changes: 2 additions & 1 deletion src/modules/drawing/components/DrawingExporter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const store = useStore()
const projection = computed(() => store.state.position.projection)
const isDrawingEmpty = computed(() => store.getters.isDrawingEmpty)
const activeKmlLayer = computed(() => store.getters.activeKmlLayer)
function onExportOptionSelected(dropdownItem) {
exportSelection.value = dropdownItem.value
Expand All @@ -37,7 +38,7 @@ function exportDrawing() {
type = 'application/gpx+xml;charset=UTF-8'
} else {
fileName = generateFilename('.kml')
content = generateKmlString(projection.value, features)
content = generateKmlString(projection.value, features, activeKmlLayer.value?.name)
type = 'application/vnd.google-earth.kml+xml;charset=UTF-8'
}
saveAs(new Blob([content], { type }), fileName)
Expand Down
64 changes: 60 additions & 4 deletions src/modules/drawing/components/DrawingToolbox.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { computed, inject, ref } from 'vue'
import DOMPurify from 'dompurify'
import { computed, inject, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
Expand All @@ -11,13 +12,13 @@ import SharePopup from '@/modules/drawing/components/SharePopup.vue'
import { DrawingState } from '@/modules/drawing/lib/export-utils'
import useSaveKmlOnChange from '@/modules/drawing/useKmlDataManagement.composable'
import ModalWithBackdrop from '@/utils/components/ModalWithBackdrop.vue'
import { useTippyTooltip } from '@/utils/composables/useTippyTooltip.js'
import DrawingHeader from './DrawingHeader.vue'
const dispatcher = { dispatcher: 'DrawingToolbox.vue' }
const drawingLayer = inject('drawingLayer')
const { saveState, debounceSaveDrawing } = useSaveKmlOnChange()
const i18n = useI18n()
const store = useStore()
Expand All @@ -38,7 +39,7 @@ const isDrawingLineOrMeasure = computed(() =>
)
)
const activeKmlLayer = computed(() => store.getters.activeKmlLayer)
const drawingName = ref(activeKmlLayer.value?.name || i18n.t('draw_layer_label'))
const isDrawingStateError = computed(() => saveState.value < 0)
/** Return a different translation key depending on the saving status */
const drawingStateMessage = computed(() => {
Expand All @@ -63,10 +64,44 @@ function onCloseClearConfirmation(confirmed) {
store.dispatch('clearDrawingFeatures', dispatcher)
store.dispatch('clearAllSelectedFeatures', dispatcher)
drawingLayer.getSource().clear()
debounceSaveDrawing()
debounceSaveDrawing({ drawingName: drawingName.value })
store.dispatch('setDrawingMode', { mode: null, ...dispatcher })
}
}
const { removeTippy: removeNoActiveKmlWarning } = useTippyTooltip('#drawing-name-container')
onMounted(() => {
// if there's already an active KML layer, we can remove the tippy (as it will be empty, see template below)
if (activeKmlLayer.value) {
removeNoActiveKmlWarning()
}
})
watch(drawingName, () => {
sanitizeDrawingName()
debounceSaveDrawing({
// Lowering the risk of the user changing the name and leaving the drawing module without the name being saved.
// It is terrible design that this can occur, but moving the drawing name management other places raises the complexity tenfold
// so that is a trade-off we can live with.
debounceTime: 500,
drawingName: drawingName.value.trim(),
})
})
watch(activeKmlLayer, () => {
if (activeKmlLayer.value) {
// no need for the message telling the user the drawing is empty, and he can't edit the drawing name
removeNoActiveKmlWarning()
}
})
function sanitizeDrawingName() {
drawingName.value = DOMPurify.sanitize(drawingName.value, {
USE_PROFILES: { xml: true },
})
}
function closeDrawing() {
emits('closeDrawing')
}
Expand All @@ -87,6 +122,27 @@ function onDeleteLastPoint() {
class="card text-center drawing-toolbox-content shadow-lg rounded-bottom rounded-top-0 rounded-start-0"
:class="{ 'rounded-bottom-0': isPhoneMode }"
>
<div
id="drawing-name-container"
class="d-flex justify-content-center align-items-center gap-2 mt-3 mx-4"
:data-tippy-content="
!activeKmlLayer ? i18n.t('drawing_empty_cannot_edit_name') : ''
"
>
<label for="drawing-name" class="text-nowrap">
{{ i18n.t('file_name') }}
</label>
<input
id="drawing-name"
v-model="drawingName"
type="text"
class="form-control"
data-cy="drawing-toolbox-file-name-input"
:placeholder="`${i18n.t('draw_layer_label')}`"
:disabled="!activeKmlLayer"
/>
</div>
<div class="card-body position-relative container">
<div
class="row justify-content-start g-2"
Expand Down
6 changes: 3 additions & 3 deletions src/modules/drawing/lib/export-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,10 @@ export function generateGpxString(projection, features = []) {
*
* @param {CoordinateSystem} projection Coordinate system of the features
* @param features {Feature[]} Features (OpenLayers) to be converted to KML format
* @param fileName {String} name of the file
* @returns {string}
*/
export function generateKmlString(projection, features = []) {
export function generateKmlString(projection, features = [], fileName) {
log.debug(`Generate KML for ${features.length} features`)
if (!projection) {
log.error('Cannot generate KML string without projection')
Expand Down Expand Up @@ -131,10 +132,9 @@ export function generateKmlString(projection, features = []) {

// Remove empty placemark added to have <Document> tag
kmlString = kmlString.replace(/<Placemark\/>/g, '')

kmlString = kmlString.replace(
/<Document>/,
`<Document><name>${i18n.global.t('draw_layer_label')}</name>`
`<Document><name>${fileName ? fileName : i18n.global.t('draw_layer_label')}</name>`
)
}
return kmlString
Expand Down
35 changes: 26 additions & 9 deletions src/modules/drawing/useKmlDataManagement.composable.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default function useSaveKmlOnChange(drawingLayerDirectReference) {
}
}

async function saveDrawing(retryOnError = true) {
async function saveDrawing({ retryOnError = true, drawingName = null }) {
try {
log.debug(
`Save drawing retryOnError ${retryOnError}, differSaveDrawing=${differSaveDrawingTimeout}`
Expand All @@ -84,29 +84,36 @@ export default function useSaveKmlOnChange(drawingLayerDirectReference) {
saveState.value = DrawingState.SAVING
const kmlData = generateKmlString(
projection.value,
drawingLayer.getSource().getFeatures()
drawingLayer.getSource().getFeatures(),
drawingName ?? activeKmlLayer.value?.name
)
if (online.value) {
await saveOnlineDrawing(kmlData)
await saveOnlineDrawing(kmlData, drawingName)
} else {
await saveLocalDrawing(kmlData)
await saveLocalDrawing(kmlData, drawingName)
}
saveState.value = DrawingState.SAVED
} catch (e) {
log.error('Could not save KML layer: ', e)
saveState.value = DrawingState.SAVE_ERROR
if (!IS_TESTING_WITH_CYPRESS && retryOnError) {
// Retry saving in 5 seconds
debounceSaveDrawing({ debounceTime: 5000, retryOnError: false })
debounceSaveDrawing({ debounceTime: 5000, retryOnError: false, drawingName })
}
}
}

async function saveOnlineDrawing(kmlData) {
/**
* @param {String} kmlData
* @param {String} [drawingName=null] Default is `null`
* @returns {Promise<void>}
*/
async function saveOnlineDrawing(kmlData, drawingName = null) {
if (!activeKmlLayer.value?.adminId) {
// creation of the new KML (copy or new)
const kmlMetadata = await createKml(kmlData)
const kmlLayer = new KMLLayer({
name: drawingName ?? activeKmlLayer.value?.name,
kmlFileUrl: getKmlUrl(kmlMetadata.id),
visible: true,
opacity: activeKmlLayer.value?.opacity, // re-use current KML layer opacity, or null
Expand Down Expand Up @@ -143,8 +150,14 @@ export default function useSaveKmlOnChange(drawingLayerDirectReference) {
}
}

async function saveLocalDrawing(kmlData) {
/**
* @param {String} kmlData
* @param {String} [drawingName=null] Default is `null`
* @returns {Promise<void>}
*/
async function saveLocalDrawing(kmlData, drawingName = null) {
const kmlLayer = new KMLLayer({
name: drawingName,
kmlFileUrl: temporaryKmlId.value,
visible: true,
opacity: 1,
Expand All @@ -157,7 +170,11 @@ export default function useSaveKmlOnChange(drawingLayerDirectReference) {
}
}

async function debounceSaveDrawing({ debounceTime = 2000, retryOnError = true } = {}) {
async function debounceSaveDrawing({
debounceTime = 2000,
retryOnError = true,
drawingName = null,
} = {}) {
log.debug(
`Debouncing save drawing debounceTime=${debounceTime} differSaveDrawingTimeout=${differSaveDrawingTimeout}`
)
Expand All @@ -175,7 +192,7 @@ export default function useSaveKmlOnChange(drawingLayerDirectReference) {
)
})
}
const savePromise = saveDrawing(retryOnError)
const savePromise = saveDrawing({ retryOnError, drawingName })
savesInProgress.value.push(savePromise)
await savePromise
// removing this promise from the "in-progress" list when it's done
Expand Down
2 changes: 2 additions & 0 deletions src/modules/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
"draw_tooltip": "Zeichnen Sie auf der Karte",
"draw_type_marker": "Linie / Fläche",
"drawing_attached": "Zeichnung als Anhang hinzugefügt",
"drawing_empty_cannot_edit_name": "Bitte fügen Sie der Zeichnung etwas hinzu, bevor Sie ihren Namen bearbeiten.",
"drawing_too_large": "Ihre Zeichnung ist zu gross, entfernen Sie einige Details.",
"drop_invalid_url": "URL ist ungültig.",
"drop_me_here": "Datei hier ablegen (KML, KMZ, GPX, GeoTIFF)",
Expand Down Expand Up @@ -241,6 +242,7 @@
"field_required": "Dieses Feld ist erforderlich",
"file_imported_success": "Datei erfolgreich importiert",
"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}).",
"file_unsupported_format": "Dieses Dateiformat wird nicht unterstützt. Nur die folgenden Formate sind erlaubt: {allowedFormats}",
"follow_us": "Folgen Sie uns",
Expand Down
2 changes: 2 additions & 0 deletions src/modules/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
"draw_tooltip": "Draw on the map",
"draw_type_marker": "Line / surface",
"drawing_attached": "Drawing added as attachment",
"drawing_empty_cannot_edit_name": "Please add something to the drawing before editing its name.",
"drawing_too_large": "Your drawing is too large, remove some features",
"drop_invalid_url": "URL is not valid.",
"drop_me_here": "Drop file here (KML, KMZ, GPX, GeoTIFF)",
Expand Down Expand Up @@ -241,6 +242,7 @@
"field_required": "This field is required",
"file_imported_success": "File successfully imported",
"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}).",
"file_unsupported_format": "This file format is not supported. Only the following formats are allowed: {allowedFormats}",
"follow_us": "Follow us",
Expand Down
2 changes: 2 additions & 0 deletions src/modules/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
"draw_tooltip": "Dessiner sur la carte",
"draw_type_marker": "Trait / surface",
"drawing_attached": "Dessin ajouté en pièce jointe",
"drawing_empty_cannot_edit_name": "Veuillez ajouter quelque chose au dessin avant de modifier son nom.",
"drawing_too_large": "Ton dessin est trop grand, enlève quelques éléments",
"drop_invalid_url": "URL non valide.",
"drop_me_here": "Déposer le fichier ici (KML, KMZ, GPX, GeoTIFF)",
Expand Down Expand Up @@ -241,6 +242,7 @@
"field_required": "Ce champ est requis",
"file_imported_success": "Fichier importé avec succès",
"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})",
"file_unsupported_format": "Ce format de fichier n'est pas pris en charge. Seuls les formats suivants sont autorisés : {allowedFormats}",
"follow_us": "Suivez-nous",
Expand Down
2 changes: 2 additions & 0 deletions src/modules/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
"draw_tooltip": "Disegnare sulla mappa",
"draw_type_marker": "Linea / superficie",
"drawing_attached": "Disegno aggiunto come allegato",
"drawing_empty_cannot_edit_name": "Aggiungere qualcosa al disegno prima di modificarne il nome.",
"drawing_too_large": "Il suo disegno è troppo grande, rimuova alcuni elementi",
"drop_invalid_url": "URL non valido",
"drop_me_here": "Lasciare qui il file (KML, KMZ, GPX, GeoTIFF)",
Expand Down Expand Up @@ -241,6 +242,7 @@
"field_required": "Questo campo è obbligatorio",
"file_imported_success": "File importato con successo",
"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})",
"file_unsupported_format": "Questo formato file non è supportato. Sono ammessi solo i seguenti formati: {allowedFormats}",
"follow_us": "Seguiteci",
Expand Down
2 changes: 2 additions & 0 deletions src/modules/i18n/locales/rm.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
"draw_tooltip": "Dissegnar sin la carta",
"draw_type_marker": "Lingia / surfatscha",
"drawing_attached": "Dissegn è agiuntà sco agiunta.",
"drawing_empty_cannot_edit_name": "Bitte fügen Sie der Zeichnung etwas hinzu, bevor Sie ihren Namen bearbeiten.",
"drawing_too_large": "Tes dissegn è memia grond, stizza intgins detagls",
"drop_invalid_url": "URL è nunvalid",
"drop_me_here": "Dar giu la datoteca (KML, KMZ, GPX, GeoTIFF)",
Expand Down Expand Up @@ -239,6 +240,7 @@
"field_required": "Quest champ è obligatoric",
"file_imported_success": "Datoteca importada cun success",
"file_is_not_kml": "Questa datoteca nun è ina datoteca KML",
"file_name": "Zeichnungsname",
"file_too_large": "Questa datoteca è memia grond (grondezza maximala lubida: {maxFileSize})",
"file_unsupported_format": "Quest format da datoteca na vegn betg sustegnì. Sun ils seguents formats èn permess: {allowedFormats}",
"follow_us": "Giais suenter a nus",
Expand Down
2 changes: 1 addition & 1 deletion src/scss/variables.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ $dev-disclaimer-height: 24px;
$footer-height: 30px;
$overlay-width: 340px;
$menu-tray-width: 24rem;
$drawing-tools-height-mobile: 121px;
$drawing-tools-height-mobile: 191px;
$time-slider-bar-height: 102px;
$time-slider-dropdown-height: 62px;

Expand Down
11 changes: 10 additions & 1 deletion src/store/modules/layers.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ const getters = {
(layer) => layer.visible && layer.type === LayerTypes.KML && !layer.isExternal
) ?? null,

/**
* Get index of active layer by ID.
*
* When there exists no layer with this ID then -1 is returned.
*
* @returns {number}
*/
getIndexOfActiveLayerById: (state) => (layerId) =>
state.activeLayers.findIndex((layer) => layer.id === layerId),

/**
* All layers in the config that have the flag `background` to `true` (that can be shown as a
* background layer).
Expand Down Expand Up @@ -451,7 +461,6 @@ const actions = {

const updatedLayer = layer2Update.clone()
Object.entries(layer).forEach((entry) => (updatedLayer[entry[0]] = entry[1]))

commit('updateLayer', { index, layer: updatedLayer, dispatcher })
}
},
Expand Down
Loading

0 comments on commit f4a4a47

Please sign in to comment.