import WithRender from "./search-box.vue" ; import Vue from "vue" ; import { Component , ProvideReactive , Ref } from "vue-property-decorator" ; import "./search-box.scss" ; import $ from "jquery" ; import { BTab } from "bootstrap-vue" ; import Icon from "../ui/icon/icon" ; import SearchFormTab from "../search-form/search-form-tab" ; import MarkerInfoTab from "../marker-info/marker-info-tab" ; import LineInfoTab from "../line-info/line-info-tab" ; import hammer from "hammerjs" ; import { InjectContext , InjectMapComponents , InjectMapContext , SEARCH_BOX_CONTEXT_INJECT_KEY } from "../../utils/decorators" ; import { MapComponents , MapContext } from "../leaflet-map/leaflet-map" ; import RouteFormTab from "../route-form/route-form-tab" ; import { HashQuery } from "facilmap-leaflet" ; import MultipleInfoTab from "../multiple-info/multiple-info-tab" ; import { Context } from "../facilmap/facilmap" ; import { PortalTarget } from "portal-vue" ; import OverpassInfoTab from "../overpass-info/overpass-info-tab" ; import OverpassFormTab from "../overpass-form/overpass-form-tab" ; export type SearchBoxContext = Vue ; @ WithRender @ Component ( { components : { Icon , LineInfoTab , MarkerInfoTab , MultipleInfoTab , OverpassInfoTab , OverpassFormTab , PortalTarget , RouteFormTab , SearchFormTab } } ) export default class SearchBox extends Vue { @ InjectContext ( ) context ! : Context ; @ InjectMapComponents ( ) mapComponents ! : MapComponents ; @ InjectMapContext ( ) mapContext ! : MapContext ; @ ProvideReactive ( SEARCH_BOX_CONTEXT_INJECT_KEY ) searchBoxContext = new Vue ( ) ; @ Ref ( ) tabsComponent ! : any ; @ Ref ( ) searchBox ! : HTMLElement ; @ Ref ( ) resizeHandle ! : HTMLElement ; cardHeader ! : HTMLElement ; tab = 0 ; tabHistory = [ 0 ] ; panStartHeight : number | null = null ; restoreHeight : number | null = null ; resizeStartHeight : number | null = null ; resizeStartWidth : number | null = null ; hasFocus = false ; isResizing = false ; isMounted = false ; mounted ( ) { this . mapContext . $on ( "fm-search-box-show-tab" , this . handleShowTab ) ; this . cardHeader = this . searchBox . querySelector ( ".card-header" ) ! ; this . cardHeader . addEventListener ( "contextmenu" , ( e ) => { e . preventDefault ( ) ; } ) ; const pan = new hammer . Manager ( this . cardHeader ) ; pan . add ( new hammer . Pan ( { direction : hammer . DIRECTION_VERTICAL } ) ) ; pan . on ( "panstart" , this . handlePanStart ) ; pan . on ( "pan" , this . handlePanMove ) ; pan . on ( "panend" , this . handlePanEnd ) ; const resize = new hammer . Manager ( this . resizeHandle ) ; resize . add ( new hammer . Pan ( { direction : hammer . DIRECTION_ALL } ) ) ; resize . add ( new hammer . Tap ( ) ) ; resize . on ( "panstart" , this . handleResizeStart ) ; resize . on ( "pan" , this . handleResizeMove ) ; resize . on ( "panend" , this . handleResizeEnd ) ; resize . on ( "tap" , this . handleResizeClick ) ; this . $watch ( ( ) => this . tabsComponent ?. tabs [ this . tab ] ?. $attrs ?. [ "fm-hash-query" ] , ( hashQuery ) => { this . mapContext . fallbackQuery = hashQuery ; } ) ; this . isMounted = true ; } beforeDestroy ( ) { this . mapContext . $off ( "fm-search-box-show-tab" , this . handleShowTab ) ; this . cardHeader = undefined as any ; } get hasTabs ( ) : boolean { return this . isMounted && this . tabsComponent ?. tabs . length > 0 ; } handlePanStart ( event ) { this . restoreHeight = null ; this . panStartHeight = parseInt ( $ ( this . searchBox ) . css ( "flex-basis" ) ) ; } handlePanMove ( event ) { if ( this . context . isNarrow && this . panStartHeight != null && event . srcEvent . type != "pointercancel" ) $ ( this . searchBox ) . stop ( ) . css ( "flexBasis" , `${ this . getSanitizedHeight ( this . panStartHeight - event . deltaY ) }px` ) ; } handlePanEnd ( ) { this . mapComponents . map . invalidateSize ( { pan : false } ) ; } getSanitizedHeight ( height ) { const maxHeight = ( this . searchBox . offsetParent as HTMLElement ) . offsetHeight - 5 ; return Math . max ( 0 , Math . min ( maxHeight , height ) ) ; } handleActivateTab ( idx ) { this . restoreHeight = null ; this . tabHistory = [ ... this . tabHistory . filter ( ( tab ) => tab != idx ) , idx ] ; } handleChanged ( newTabs , oldTabs ) { if ( this . restoreHeight != null && newTabs . length < oldTabs . length ) { $ ( this . searchBox ) . animate ( { flexBasis : this . restoreHeight } , ( ) => { this . mapComponents . map . invalidateSize ( { pan : false } ) ; } ) ; this . restoreHeight = null ; } const lastActiveTab = this . tabHistory [ this . tabHistory . length - 1 ] ; this . tabHistory = this . tabHistory . map ( ( idx ) => newTabs . indexOf ( oldTabs [ idx ] ) ) . filter ( ( idx ) => idx != - 1 ) ; if ( ! newTabs . includes ( oldTabs [ lastActiveTab ] ) ) this . tab = this . tabHistory [ this . tabHistory . length - 1 ] ; } handleShowTab ( id , expand = true ) { const idx = this . tabsComponent . tabs . findIndex ( ( tab ) => tab . id == id ) ; if ( idx != - 1 ) this . tab = idx ; if ( this . context . isNarrow && expand ) { setTimeout ( ( ) => { const currentHeight = parseInt ( $ ( this . searchBox ) . css ( "flex-basis" ) ) ; if ( currentHeight < 120 ) { this . restoreHeight = currentHeight ; $ ( this . searchBox ) . animate ( { flexBasis : 170 } , ( ) => { this . mapComponents . map . invalidateSize ( { pan : false } ) ; } ) ; } } , 0 ) ; } } handleResizeStart ( event ) { this . isResizing = true ; this . resizeStartWidth = this . searchBox . offsetWidth ; this . resizeStartHeight = this . searchBox . offsetHeight ; this . $root . $emit ( 'bv::hide::tooltip' ) ; this . searchBoxContext . $emit ( "resizestart" ) ; } handleResizeMove ( event ) { this . searchBox . style . width = `${ this . resizeStartWidth + event . deltaX }px` ; this . searchBox . style . height = `${ this . resizeStartHeight + event . deltaY }px` ; this . searchBoxContext . $emit ( "resize" ) ; } handleResizeEnd ( event ) { this . isResizing = false ; this . searchBoxContext . $emit ( "resizeend" ) ; } handleResizeClick ( ) { this . searchBox . style . width = "" ; this . searchBox . style . height = "" ; this . $root . $emit ( 'bv::hide::tooltip' ) ; this . searchBoxContext . $emit ( "resizereset" ) ; } handleFocusIn ( e ) { if ( ( e . target as HTMLElement ) . closest ( "input,textarea" ) ) this . hasFocus = true ; } handleFocusOut ( e ) { this . hasFocus = false ; } }