import { InjectionToken } from '@angular/core' ; import { App } from '../components/app/app' ; import { NavigationContainer } from './navigation-container' ; import { DeepLinkConfig , DehydratedSegment , DehydratedSegmentPair , NavGroup , NavLink , NavSegment } from './nav-util' ; import { isArray , isBlank , isPresent } from '../util/util' ; hidden export class UrlSerializer { links : NavLink [ ] ; constructor ( public _app , config ) { if ( config && isArray ( config . links ) ) { this . links = normalizeLinks ( config . links ) ; } else { this . links = [ ] ; } } /**
     * Parse the URL into a Path, which is made up of multiple NavSegments.
     * Match which components belong to each segment.
     */ parse ( browserUrl ) { if ( browserUrl . charAt ( 0 ) === '/' ) { browserUrl = browserUrl . substr ( 1 ) ; } browserUrl = browserUrl . split ( '?' ) [ 0 ] . split ( '#' ) [ 0 ] ; return convertUrlToSegments ( this . _app , browserUrl , this . links ) ; } createSegmentFromName ( navContainer , nameOrComponent ) { const configLink = this . getLinkFromName ( nameOrComponent ) ; if ( configLink ) { return this . _createSegment ( this . _app , navContainer , configLink , null ) ; } return null ; } getLinkFromName ( nameOrComponent ) { return this . links . find ( link => { return ( link . component === nameOrComponent ) || ( link . name === nameOrComponent ) ; } ) ; } /**
     * Serialize a path, which is made up of multiple NavSegments,
     * into a URL string. Turn each segment into a string and concat them to a URL.
     */ serialize ( segments ) { if ( ! segments || ! segments . length ) { return '/' ; } const sections = segments . map ( segment => { if ( segment . type === 'tabs' ) { if ( segment . requiresExplicitNavPrefix ) { return `/${ segment . type }/${ segment . navId }/${ segment . secondaryId }/${ segment . id }` ; } return `/${ segment . secondaryId }/${ segment . id }` ; } if ( segment . requiresExplicitNavPrefix ) { return `/${ segment . type }/${ segment . navId }/${ segment . id }` ; } return `/${ segment . id }` ; } ) ; return sections . join ( '' ) ; } /**
     * Serializes a component and its data into a NavSegment.
     */ serializeComponent ( navContainer , component , data ) { if ( component ) { const link = findLinkByComponentData ( this . links , component , data ) ; if ( link ) { return this . _createSegment ( this . _app , navContainer , link , data ) ; } } return null ; } internal _createSegment ( app , navContainer , configLink , data ) { let urlParts = configLink . segmentParts ; if ( isPresent ( data ) ) { urlParts = urlParts . slice ( ) ; const keys = Object . keys ( data ) ; const keysLength = keys . length ; if ( keysLength ) { for ( var i = 0 ; i < urlParts . length ; i ++ ) { if ( urlParts [ i ] . charAt ( 0 ) === ':' ) { for ( var j = 0 ; j < keysLength ; j ++ ) { if ( urlParts [ i ] === `:${ keys [ j ] }` ) { urlParts [ i ] = encodeURIComponent ( data [ keys [ j ] ] ) ; break ; } } } } } } let requiresExplicitPrefix = true ; if ( navContainer . parent ) { requiresExplicitPrefix = navContainer . parent && navContainer . parent . getAllChildNavs ( ) . length > 1 ; } else { requiresExplicitPrefix = app . getRootNavById ( navContainer . id ) && app . getRootNavs ( ) . length > 1 ; } return { id : urlParts . join ( '/' ) , name : configLink . name , component : configLink . component , loadChildren : configLink . loadChildren , data : data , defaultHistory : configLink . defaultHistory , navId : navContainer . name || navContainer . id , type : navContainer . getType ( ) , secondaryId : navContainer . getSecondaryIdentifier ( ) , requiresExplicitNavPrefix : requiresExplicitPrefix } ; } } export function formatUrlPart ( name ) { name = name . replace ( URL_REPLACE_REG , '-' ) ; name = name . charAt ( 0 ) . toLowerCase ( ) + name . substring ( 1 ) . replace ( /[A-Z]/g , match => { return '-' + match . toLowerCase ( ) ; } ) ; while ( name . indexOf ( '--' ) > - 1 ) { name = name . replace ( '--' , '-' ) ; } if ( name . charAt ( 0 ) === '-' ) { name = name . substring ( 1 ) ; } if ( name . substring ( name . length - 1 ) === '-' ) { name = name . substring ( 0 , name . length - 1 ) ; } return encodeURIComponent ( name ) ; } export const isPartMatch = ( urlPart , configLinkPart ) => { if ( isPresent ( urlPart ) && isPresent ( configLinkPart ) ) { if ( configLinkPart . charAt ( 0 ) === ':' ) { return true ; } return ( urlPart === configLinkPart ) ; } return false ; } ; export const createMatchedData = ( matchedUrlParts , link ) : any => { let data = null ; for ( var i = 0 ; i < link . segmentPartsLen ; i ++ ) { if ( link . segmentParts [ i ] . charAt ( 0 ) === ':' ) { data = data || { } ; data [ link . segmentParts [ i ] . substring ( 1 ) ] = decodeURIComponent ( matchedUrlParts [ i ] ) ; } } return data ; } ; export const findLinkByComponentData = ( links , component , instanceData ) : NavLink => { let foundLink = null ; let foundLinkDataMatches = - 1 ; for ( var i = 0 ; i < links . length ; i ++ ) { var link = links [ i ] ; if ( link . component === component ) { var dataMatches = 0 ; if ( instanceData ) { var instanceDataKeys = Object . keys ( instanceData ) ; for ( var j = 0 ; j < instanceDataKeys . length ; j ++ ) { if ( isPresent ( link . dataKeys [ instanceDataKeys [ j ] ] ) ) { dataMatches ++ ; } } } else if ( link . dataLen ) { continue ; } if ( dataMatches >= foundLinkDataMatches ) { foundLink = link ; foundLinkDataMatches = dataMatches ; } } } return foundLink ; } ; export const normalizeLinks = ( links ) : NavLink [ ] => { for ( var i = 0 , ilen = links . length ; i < ilen ; i ++ ) { var link = links [ i ] ; if ( isBlank ( link . segment ) ) { link . segment = link . name ; } link . dataKeys = { } ; link . segmentParts = link . segment . split ( '/' ) ; link . segmentPartsLen = link . segmentParts . length ; link . staticLen = link . dataLen = 0 ; var stillCountingStatic = true ; for ( var j = 0 ; j < link . segmentPartsLen ; j ++ ) { if ( link . segmentParts [ j ] . charAt ( 0 ) === ':' ) { link . dataLen ++ ; stillCountingStatic = false ; link . dataKeys [ link . segmentParts [ j ] . substring ( 1 ) ] = true ; } else if ( stillCountingStatic ) { link . staticLen ++ ; } } } return links . sort ( sortConfigLinks ) ; } ; function sortConfigLinks ( a , b ) { if ( a . segmentPartsLen > b . segmentPartsLen ) { return - 1 ; } if ( a . segmentPartsLen < b . segmentPartsLen ) { return 1 ; } if ( a . staticLen > b . staticLen ) { return - 1 ; } if ( a . staticLen < b . staticLen ) { return 1 ; } if ( a . dataLen < b . dataLen ) { return - 1 ; } if ( a . dataLen > b . dataLen ) { return 1 ; } return 0 ; } const URL_REPLACE_REG = /\s+|\?|\!|\$|\,|\.|\+|\"|\'|\*|\^|\||\/|\\|\[|\]|#|%|`|>|<|;|:|@|&|=/g ; hidden export const DeepLinkConfigToken = new InjectionToken < any > ( 'USERLINKS' ) ; export function setupUrlSerializer ( app , userDeepLinkConfig ) { return new UrlSerializer ( app , userDeepLinkConfig ) ; } export function navGroupStringtoObjects ( navGroupStrings ) { return navGroupStrings . map ( navGroupString => { const sections = navGroupString . split ( '/' ) ; if ( sections [ 0 ] === 'nav' ) { return { type : 'nav' , navId : sections [ 1 ] , niceId : sections [ 1 ] , secondaryId : null , segmentPieces : sections . splice ( 2 ) } ; } else if ( sections [ 0 ] === 'tabs' ) { return { type : 'tabs' , navId : sections [ 1 ] , niceId : sections [ 1 ] , secondaryId : sections [ 2 ] , segmentPieces : sections . splice ( 3 ) } ; } return { type : null , navId : null , niceId : null , secondaryId : null , segmentPieces : sections } ; } ) ; } export function urlToNavGroupStrings ( url ) { const tokens = url . split ( '/' ) ; const keywordIndexes = [ ] ; for ( let i = 0 ; i < tokens . length ; i ++ ) { if ( i !== 0 && ( tokens [ i ] === 'nav' || tokens [ i ] === 'tabs' ) ) { keywordIndexes . push ( i ) ; } } keywordIndexes . push ( tokens . length ) ; const groupings = [ ] ; let activeKeywordIndex = 0 ; let tmpArray = [ ] ; for ( let i = 0 ; i < tokens . length ; i ++ ) { if ( i >= keywordIndexes [ activeKeywordIndex ] ) { groupings . push ( tmpArray . join ( '/' ) ) ; tmpArray = [ ] ; activeKeywordIndex ++ ; } tmpArray . push ( tokens [ i ] ) ; } groupings . push ( tmpArray . join ( '/' ) ) ; return groupings ; } export function convertUrlToSegments ( app , url , navLinks ) { const pairs = convertUrlToDehydratedSegments ( url , navLinks ) ; return hydrateSegmentsWithNav ( app , pairs ) ; } export function convertUrlToDehydratedSegments ( url , navLinks ) { const navGroupStrings = urlToNavGroupStrings ( url ) ; const navGroups = navGroupStringtoObjects ( navGroupStrings ) ; return getSegmentsFromNavGroups ( navGroups , navLinks ) ; } export function hydrateSegmentsWithNav ( app , dehydratedSegmentPairs ) { const segments = [ ] ; for ( let i = 0 ; i < dehydratedSegmentPairs . length ; i ++ ) { let navs = getNavFromNavGroup ( dehydratedSegmentPairs [ i ] . navGroup , app ) ; for ( const dehydratedSegment of dehydratedSegmentPairs [ i ] . segments ) { if ( navs . length === 1 ) { segments . push ( hydrateSegment ( dehydratedSegment , navs [ 0 ] ) ) ; navs = navs [ 0 ] . getActiveChildNavs ( ) ; } else if ( navs . length > 1 ) { segments . push ( hydrateSegment ( dehydratedSegment , navs [ navs . length - 1 ] ) ) ; navs = navs [ navs . length - 1 ] . getActiveChildNavs ( ) ; } else { break ; } } } return segments ; } export function getNavFromNavGroup ( navGroup , app ) { if ( navGroup . navId ) { const rootNav = app . getNavByIdOrName ( navGroup . navId ) ; if ( rootNav ) { return [ rootNav ] ; } return [ ] ; } return app . getRootNavs ( ) ; } export function getSegmentsFromNavGroups ( navGroups , navLinks ) { const pairs = [ ] ; const usedNavLinks = new Set < string > ( ) ; for ( const navGroup of navGroups ) { const segments = [ ] ; const segmentPieces = navGroup . segmentPieces . concat ( [ ] ) ; for ( let i = segmentPieces . length ; i >= 0 ; i -- ) { let created = false ; for ( let j = 0 ; j < i ; j ++ ) { const startIndex = i - j - 1 ; const endIndex = i ; const subsetOfUrl = segmentPieces . slice ( startIndex , endIndex ) ; for ( const navLink of navLinks ) { if ( ! usedNavLinks . has ( navLink . name ) ) { const segment = getSegmentsFromUrlPieces ( subsetOfUrl , navLink ) ; if ( segment ) { i = startIndex + 1 ; usedNavLinks . add ( navLink . name ) ; created = true ; segments . push ( segment ) ; for ( let k = startIndex ; k < endIndex ; k ++ ) { segmentPieces [ k ] = null ; } break ; } } } if ( created ) { break ; } } if ( ! created && segmentPieces [ i - 1 ] ) { segments . push ( { id : null , name : null , secondaryId : segmentPieces [ i - 1 ] , component : null , loadChildren : null , data : null , defaultHistory : null } ) ; } } const orderedSegments = segments . reverse ( ) ; for ( let i = 0 ; i < orderedSegments . length ; i ++ ) { if ( orderedSegments [ i ] . secondaryId && ! orderedSegments [ i ] . id && ( ( i + 1 ) <= orderedSegments . length - 1 ) ) { orderedSegments [ i + 1 ] . secondaryId = orderedSegments [ i ] . secondaryId ; orderedSegments [ i ] = null ; } } const cleanedSegments = segments . filter ( segment => ! ! segment ) ; if ( navGroup . secondaryId && segments . length ) { cleanedSegments [ 0 ] . secondaryId = navGroup . secondaryId ; } pairs . push ( { navGroup : navGroup , segments : cleanedSegments } ) ; } return pairs ; } export function getSegmentsFromUrlPieces ( urlSections , navLink ) { if ( navLink . segmentPartsLen !== urlSections . length ) { return null ; } for ( let i = 0 ; i < urlSections . length ; i ++ ) { if ( ! isPartMatch ( urlSections [ i ] , navLink . segmentParts [ i ] ) ) { return null ; } } return { id : urlSections . join ( '/' ) , name : navLink . name , component : navLink . component , loadChildren : navLink . loadChildren , data : createMatchedData ( urlSections , navLink ) , defaultHistory : navLink . defaultHistory } ; } export function hydrateSegment ( segment , nav ) { const hydratedSegment = Object . assign ( { } , segment ) as NavSegment ; hydratedSegment . type = nav . getType ( ) ; hydratedSegment . navId = nav . name || nav . id ; hydratedSegment . secondaryId = segment . secondaryId ; return hydratedSegment ; } export function getNonHydratedSegmentIfLinkAndUrlMatch ( urlChunks , navLink ) { let allSegmentsMatch = true ; for ( let i = 0 ; i < urlChunks . length ; i ++ ) { if ( ! isPartMatch ( urlChunks [ i ] , navLink . segmentParts [ i ] ) ) { allSegmentsMatch = false ; break ; } } if ( allSegmentsMatch ) { return { id : navLink . segmentParts . join ( '/' ) , name : navLink . name , component : navLink . component , loadChildren : navLink . loadChildren , data : createMatchedData ( urlChunks , navLink ) , defaultHistory : navLink . defaultHistory } ; } return null ; }