2023-11-01 18:45:16 +00:00
< script setup lang = "ts" >
2023-11-08 20:31:03 +00:00
import { type FileResultObject , parseFiles } from "../utils/files" ;
2023-11-01 18:45:16 +00:00
import pluralize from "pluralize" ;
import { SearchResultsLayer } from "facilmap-leaflet" ;
import { Util } from "leaflet" ;
import FileResults from "./file-results.vue" ;
import SearchBoxTab from "./search-box/search-box-tab.vue" ;
2023-11-06 02:22:33 +00:00
import { computed , markRaw , ref , shallowReactive } from "vue" ;
2023-11-01 18:45:16 +00:00
import { useDomEventListener , useEventListener } from "../utils/utils" ;
import { useToasts } from "./ui/toasts/toasts.vue" ;
2023-11-06 02:22:33 +00:00
import { injectContextRequired , requireMapContext , requireSearchBoxContext } from "./facil-map-context-provider/facil-map-context-provider.vue" ;
2023-11-01 18:45:16 +00:00
const context = injectContextRequired ( ) ;
2023-11-06 02:22:33 +00:00
const mapContext = requireMapContext ( context ) ;
const searchBoxContext = requireSearchBoxContext ( context ) ;
2023-11-01 18:45:16 +00:00
const toasts = useToasts ( ) ;
const fileInputRef = ref < HTMLInputElement > ( ) ;
const files = ref < Array < FileResultObject & { title : string } > > ( [ ] ) ;
2023-11-06 02:22:33 +00:00
const layers = shallowReactive < SearchResultsLayer [ ] > ( [ ] ) ;
2023-11-01 18:45:16 +00:00
useEventListener ( mapContext , "import-file" , handleImportFile ) ;
useEventListener ( mapContext , "open-selection" , handleOpenSelection ) ;
2023-11-06 02:22:33 +00:00
useDomEventListener ( mapContext . value . components . container , "dragenter" , handleMapDragEnter ) ;
useDomEventListener ( mapContext . value . components . container , "dragover" , handleMapDragOver ) ;
useDomEventListener ( mapContext . value . components . container , "drop" , handleMapDrop ) ;
2023-11-01 18:45:16 +00:00
2023-11-06 02:22:33 +00:00
const layerIds = computed ( ( ) => layers . map ( ( layer ) => Util . stamp ( layer ) ) ) ;
2023-11-01 18:45:16 +00:00
function handleImportFile ( ) : void {
fileInputRef . value ? . click ( ) ;
}
function handleOpenSelection ( ) : void {
for ( let i = 0 ; i < layerIds . value . length ; i ++ ) {
2023-11-06 02:22:33 +00:00
if ( mapContext . value . selection . some ( ( item ) => item . type == "searchResult" && item . layerId == layerIds . value [ i ] ) ) {
searchBoxContext . value . activateTab ( ` fm ${ context . id } -import-tab- ${ i } ` ) ;
2023-11-01 18:45:16 +00:00
break ;
}
}
}
function handleMapDragEnter ( event : Event ) : void {
event . preventDefault ( ) ;
}
function handleMapDragOver ( event : Event ) : void {
event . preventDefault ( ) ;
}
function handleMapDrop ( event : Event ) : void {
event . preventDefault ( ) ;
importFiles ( ( event as DragEvent ) . dataTransfer ? . files ) ;
}
async function importFiles ( fileList : FileList | undefined ) : Promise < void > {
toasts . hideToast ( ` fm ${ context . id } -import-error ` ) ;
if ( ! fileList || fileList . length == 0 )
return ;
try {
const loadedFiles = await Promise . all ( [ ... fileList ] . map ( ( file ) => new Promise < string > ( ( resolve , reject ) => {
const reader = new FileReader ( ) ;
reader . onload = ( ) => {
resolve ( reader . result as string ) ;
} ;
reader . onerror = ( ) => {
reject ( reader . error ) ;
} ;
reader . readAsText ( file ) ;
} ) ) ) ;
const result = {
... parseFiles ( loadedFiles ) ,
title : ( fileList . length == 1 && fileList [ 0 ] . name ) || pluralize ( "file" , fileList . length , true )
} ;
if ( result . features . length == 0 && result . errors )
toasts . showErrorToast ( ` fm ${ context . id } -import-error ` , "Parsing error" , ` The selected ${ pluralize ( "file" , fileList . length ) } could not be parsed. ` ) ;
else if ( result . features . length == 0 )
toasts . showErrorToast ( ` fm ${ context . id } -import-error ` , "No geometries" , ` The selected ${ pluralize ( "file" , fileList . length ) } did not contain any geometries. ` ) ;
else {
if ( result . errors )
toasts . showErrorToast ( ` fm ${ context . id } -import-error ` , "Parsing error" , "Some of the selected files could not be parsed." , { variant : "warning" } ) ;
2023-11-06 02:22:33 +00:00
const layer = markRaw ( new SearchResultsLayer ( result . features , { pathOptions : { weight : 7 } } ) . addTo ( mapContext . value . components . map ) ) ;
mapContext . value . components . map . flyToBounds ( layer . getBounds ( ) ) ;
mapContext . value . components . selectionHandler . addSearchResultLayer ( layer ) ;
2023-11-01 18:45:16 +00:00
files . value . push ( result ) ;
2023-11-06 02:22:33 +00:00
layers . push ( layer ) ;
2023-11-01 18:45:16 +00:00
setTimeout ( ( ) => {
2023-11-06 02:22:33 +00:00
searchBoxContext . value . activateTab ( ` fm ${ context . id } -import-tab- ${ files . value . length - 1 } ` ) ;
2023-11-01 18:45:16 +00:00
} , 0 ) ;
}
} catch ( err ) {
toasts . showErrorToast ( ` fm ${ context . id } -import-error ` , "Error reading files" , err ) ;
}
}
function close ( idx : number ) : void {
files . value . splice ( idx , 1 ) ;
2023-11-06 02:22:33 +00:00
mapContext . value . components . selectionHandler . removeSearchResultLayer ( layers [ idx ] ) ;
layers [ idx ] . remove ( ) ;
layers . splice ( idx , 1 ) ;
2023-11-01 18:45:16 +00:00
}
< / script >
< template >
< div >
< input type = "file" multiple class = "d-none" ref = "fileInputRef" @ change = "importFiles(fileInputRef!.files ?? undefined)" >
< template v-for ="(file, idx) in files" :key ="idx" >
< SearchBoxTab
: id = "`fm${context.id}-import-tab-${idx}`"
class = "fm-import-tab"
isCloseable
: title = "file.title"
@ close = "close(idx)"
>
< FileResults
: file = "file"
: layer - id = "layerIds[idx]"
auto - zoom
> < / FileResults >
< / SearchBoxTab >
< / template >
< / div >
< / template >
< style lang = "scss" >
. fm - import - tab . fm - import - tab . fm - import - tab {
padding : 0.5 rem ;
display : flex ;
flex - direction : column ;
}
< / style >