Skip to content

Commit

Permalink
Merge pull request #974 from geoadmin/PB-495-drag-and-drop-files
Browse files Browse the repository at this point in the history
  • Loading branch information
pakb authored Jul 2, 2024
2 parents 77d1dd9 + 76ac665 commit f6aa13d
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ import LayerFeature from '@/api/features/LayerFeature.class'
import LayerTypes from '@/api/layers/LayerTypes.enum'
import { DRAWING_HIT_TOLERANCE, IS_TESTING_WITH_CYPRESS } from '@/config'
import { useDragBoxSelect } from '@/modules/map/components/openlayers/utils/useDragBoxSelect.composable'
import { handleFileContent } from '@/modules/menu/components/advancedTools/ImportFile/utils'
import { ClickInfo, ClickType } from '@/store/modules/map.store'
import { normalizeExtent } from '@/utils/coordinates/coordinateUtils'
import { normalizeExtent, OutOfBoundsError } from '@/utils/coordinates/coordinateUtils'
import { EmptyGPXError } from '@/utils/gpxUtils'
import { EmptyKMLError } from '@/utils/kmlUtils'
import log from '@/utils/logging'

const dispatcher = {
dispatcher: 'useMapInteractions.composable',
}

export default function useMapInteractions(map) {
const store = useStore()

Expand Down Expand Up @@ -57,10 +64,12 @@ export default function useMapInteractions(map) {
})

registerPointerEvents()
registerDragAndDropEvent()
map.addInteraction(freeMouseWheelInteraction)

onBeforeUnmount(() => {
unregisterPointerEvents()
unregisterDragAndDropEvent()
})

/*
Expand Down Expand Up @@ -202,4 +211,85 @@ export default function useMapInteractions(map) {
mapHasMoved = false
}, 500)
}

function registerDragAndDropEvent() {
log.debug(`Register drag and drop events`)
const mapElement = map.getTargetElement()
mapElement.addEventListener('dragover', onDragOver)
mapElement.addEventListener('drop', onDrop)
mapElement.addEventListener('dragleave', onDragLeave)
}

function unregisterDragAndDropEvent() {
log.debug(`Unregister drag and drop events`)
const mapElement = map.getTargetElement()
mapElement.removeEventListener('dragover', onDragOver)
mapElement.removeEventListener('drop', onDrop)
mapElement.removeEventListener('dragleave', onDragLeave)
}

function readFileContent(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (event) => resolve(event.target.result)
reader.onerror = (error) => reject(error)
reader.readAsText(file)
})
}

async function handleFile(file) {
try {
const fileContent = await readFileContent(file)
handleFileContent(store, fileContent, file.name)
} catch (error) {
let errorKey
log.error(`Error loading file`, file.name, error)
if (error instanceof OutOfBoundsError) {
errorKey = 'kml_gpx_file_out_of_bounds'
} else if (error instanceof EmptyKMLError || error instanceof EmptyGPXError) {
errorKey = 'kml_gpx_file_empty'
} else {
errorKey = 'invalid_kml_gpx_file_error'
log.error(`Failed to load file`, error)
}
store.dispatch('setErrorText', { errorText: errorKey, ...dispatcher })
}
}

function onDragOver(event) {
event.preventDefault()
store.dispatch('setShowDragAndDropOverlay', { showDragAndDropOverlay: true, ...dispatcher })
}

function onDrop(event) {
event.preventDefault()
store.dispatch('setShowDragAndDropOverlay', {
showDragAndDropOverlay: false,
...dispatcher,
})

if (event.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (let i = 0; i < event.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (event.dataTransfer.items[i].kind === 'file') {
const file = event.dataTransfer.items[i].getAsFile()
handleFile(file)
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (let i = 0; i < event.dataTransfer.files.length; i++) {
const file = event.dataTransfer.files[i]
handleFile(file)
}
}
}

function onDragLeave() {
store.dispatch('setShowDragAndDropOverlay', {
showDragAndDropOverlay: false,
...dispatcher,
})
}
}
12 changes: 12 additions & 0 deletions src/store/modules/ui.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ export default {
* @type String
*/
errorText: null,

/**
* Flag telling if the "Drop file here" overlay will be displayed on top of the map.
*
* @type Boolean
*/
showDragAndDropOverlay: false,
},
getters: {
showLoadingBar(state) {
Expand Down Expand Up @@ -380,6 +387,9 @@ export default {
setErrorText({ commit }, { errorText, dispatcher }) {
commit('setErrorText', { errorText, dispatcher })
},
setShowDragAndDropOverlay({ commit }, { showDragAndDropOverlay, dispatcher }) {
commit('setShowDragAndDropOverlay', { showDragAndDropOverlay, dispatcher })
},
},
mutations: {
setSize(state, { height, width }) {
Expand Down Expand Up @@ -440,5 +450,7 @@ export default {
},
setShowDisclaimer: (state, { showDisclaimer }) => (state.showDisclaimer = showDisclaimer),
setErrorText: (state, { errorText }) => (state.errorText = errorText),
setShowDragAndDropOverlay: (state, { showDragAndDropOverlay }) =>
(state.showDragAndDropOverlay = showDragAndDropOverlay),
},
}
53 changes: 53 additions & 0 deletions src/utils/components/DragDropOverlay.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script setup>
import { useI18n } from 'vue-i18n'
import BlackBackdrop from '@/utils/components/BlackBackdrop.vue'
const { t } = useI18n()
</script>

<template>
<div class="drag-n-drop-overlay">
<BlackBackdrop />
<div class="inner-overlay">
<h2>{{ t('drop_me_here') }}</h2>
</div>
</div>
</template>

<style lang="scss" scoped>
@import '@/scss/webmapviewer-bootstrap-theme';
.drag-n-drop-overlay {
$drag-n-drop-z-index: 9999;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
cursor: copy;
z-index: $drag-n-drop-z-index;
display: flex;
flex-direction: column;
justify-content: stretch;
align-content: stretch;
.inner-overlay {
$inner-overlay-border-width: 0.5rem;
margin: $inner-overlay-border-width;
border: $inner-overlay-border-width dashed $white;
position: absolute;
top: 0;
left: 0;
width: calc(100% - 2 * #{$inner-overlay-border-width});
height: calc(100% - 2 * #{$inner-overlay-border-width});
z-index: $drag-n-drop-z-index + 1;
display: flex;
flex-direction: column;
justify-content: center;
align-content: stretch;
text-align: center;
color: $white;
}
}
</style>
3 changes: 3 additions & 0 deletions src/views/MapView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import TimeSliderButton from '@/modules/map/components/toolbox/TimeSliderButton.
import MapModule from '@/modules/map/MapModule.vue'
import MenuModule from '@/modules/menu/MenuModule.vue'
import { UIModes } from '@/store/modules/ui.store'
import DragDropOverlay from '@/utils/components/DragDropOverlay.vue'
import LoadingBar from '@/utils/components/LoadingBar.vue'
import log from '@/utils/logging'
Expand All @@ -29,6 +30,7 @@ const isDrawingMode = computed(() => store.state.drawing.drawingOverlay.show)
const activeKmlLayer = computed(() => store.getters.activeKmlLayer)
const isPhoneMode = computed(() => store.state.ui.mode === UIModes.PHONE)
const showLoadingBar = computed(() => store.getters.showLoadingBar)
const showDragAndDropOverlay = computed(() => store.state.ui.showDragAndDropOverlay)
const loadDrawingModule = computed(() => {
return (
Expand Down Expand Up @@ -95,6 +97,7 @@ onMounted(() => {
</template>
</MapModule>
<I18nModule />
<DragDropOverlay v-if="showDragAndDropOverlay" />
</div>
</template>
Expand Down
5 changes: 4 additions & 1 deletion tests/cypress/tests-e2e/featureSelection.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,10 @@ describe('Testing the feature selection', () => {
results: [],
})
}).as('emptyIdentify')
cy.get('@olMap').click(20, 100)
drawRectangleOnMap({
x: 100,
y: -100,
})
cy.wait('@emptyIdentify')
cy.get('@highlightedFeatures').should('not.exist')
})
Expand Down

0 comments on commit f6aa13d

Please sign in to comment.