Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
58 lines
No EOL
12 KiB
JavaScript
58 lines
No EOL
12 KiB
JavaScript
;;;(function(window){'use strict';var NavigationFactory=window.NavigationFactory=function(window){const debug=[()=>{},(arg1,...args)=>console.log('[Navigation] '+arg1,...args),(arg1,...args)=>console.log('[Navigation] (%s) '+arg1,Date.now(),...args),][0];const NGA_VIEWS=Object.freeze([Object.freeze({name:'thread',url:'views/conversation/index.html',behavior:'ConversationView',previous:'thread-list'}),Object.freeze({name:'thread-list',url:'views/inbox/index.html',behavior:'InboxView'}),Object.freeze({name:'report-view',behavior:'ReportView',previous:'thread',partOf:'thread'}),Object.freeze({name:'composer',behavior:'ConversationView',previous:'thread-list',partOf:'thread'}),Object.freeze({name:'group-view',behavior:'GroupView',previous:'thread',partOf:'thread'})]);const OA_VIEWS=Object.freeze([Object.freeze({name:'thread-list',url:'index.html',behavior:'InboxView'}),Object.freeze({name:'thread',behavior:'ConversationView',previous:'thread-list',partOf:'thread-list'}),Object.freeze({name:'composer',behavior:'ConversationView',previous:'thread-list',partOf:'thread-list'}),Object.freeze({name:'group-view',behavior:'GroupView',previous:'thread',partOf:'thread-list'}),Object.freeze({name:'report-view',behavior:'ReportView',previous:'thread',partOf:'thread-list'})]);const VIEWS=isUsingOldArchitecture()?OA_VIEWS:NGA_VIEWS;const priv={pendingInit:Symbol('pendingInit')};var currentState;var navigationTransition;function setLocation(url){debug('Setting location',url);window.location.assign(url);}
|
|
function isUsingOldArchitecture(){debug('isUsingOldArchitecture()');var pathname=window.location.pathname;var result=(pathname==='/'||pathname==='/index.html');debug('isUsingOldArchitecture() =>',result);return result;}
|
|
function findViewNameFromHash(hash){debug('findViewNameFromHash(%s)',hash);if(!hash||!hash.startsWith('#/')){return null;}
|
|
var endOfViewName=hash.indexOf('?');if(endOfViewName<0){endOfViewName=undefined;}
|
|
return hash.slice(2,endOfViewName);}
|
|
function findViewFromLocation(location){debug('findViewFromLocation(%s)',location.href);var pathName=location.pathname;if(pathName.endsWith('/')){pathName+='index.html';}
|
|
var hash=location.hash;var viewNameFromHash=findViewNameFromHash(hash);var viewFromHash=findViewFromName(viewNameFromHash);if(viewNameFromHash&&!viewFromHash){console.error('We found no view from the hash %s',hash);return null;}
|
|
var viewFromPath=VIEWS.find((view)=>pathName.endsWith('/'+view.url))||null;if(!viewFromPath){console.error('We found no view from the path %s',pathName);return null;}
|
|
if(viewFromHash&&viewFromHash.partOf&&viewFromHash.partOf!==viewFromPath.name){console.error('The view %s found from the hash %s is not part of view %s',viewFromHash.name,hash,viewFromPath.name);viewFromHash=null;}
|
|
return viewFromHash||viewFromPath;}
|
|
function findViewFromName(name){debug('findViewFromName(%s)',name);var view=VIEWS.find((view)=>view.name===name)||null;debug('findViewFromName() => %s',view&&view.name);return view;}
|
|
function findContainerView(view){debug('findContainerView(%s)',view.name);if(view.partOf){debug('findContainerView: %s is part of view %s',view.name,view.partOf);view=findViewFromName(view.partOf);}
|
|
debug('findContainerView() => %s',view.name);return view;}
|
|
var readyDefer=Utils.Promise.defer();function waitForReady(){if(!readyDefer){return Promise.reject(new Error('waitForReady was called but status is ready already.'));}
|
|
return readyDefer.promise;}
|
|
function startNavigationFromCurrentLocation(){var location=window.location;debug('startNavigationFromCurrentLocation(), location=%s',location);var newPanel=findViewFromLocation(location);if(!newPanel){return Promise.reject(new Error('Couldn\'t find a view from location '+location.href));}
|
|
var args=Utils.params(decodeURIComponent(location.hash));debug('startNavigationFromCurrentLocation:','found panel',newPanel.name,'with args',args);return startNavigation({newPanel,args});}
|
|
function startNavigation(opts){var{newPanel,args,partial}=opts;args=args||{};debug('startNavigation(newPanel=%s, partial=%s)',newPanel&&newPanel.name,partial);if(navigationTransition&&(!navigationTransition.partial||partial)){debug('startNavigation: previous navigation exists, deferring.');return navigationTransition.defer.promise.then(()=>startNavigation(opts));}
|
|
var newContainerView=newPanel&&findContainerView(newPanel);var containerView=currentState&&findContainerView(currentState.panel);var leaveDocument=currentState&&containerView!==newContainerView;if(newPanel&&!leaveDocument){var behaviorObject=window[newPanel.behavior];var viewReady=behaviorObject&&(typeof behaviorObject.isReady!=='function'||behaviorObject.isReady());if(!viewReady){return waitForReady().then(()=>startNavigation(opts));}}
|
|
document.activeElement.blur();if(!navigationTransition){navigationTransition={stepsRun:[],defer:Utils.Promise.defer()};}
|
|
navigationTransition.partial=partial;navigationTransition.leaveDocument=leaveDocument;if('meta'in args){throw new Error('Arguments cannot have a `meta` property.');}
|
|
navigationTransition.transitionArgs={meta:{next:newPanel?{panel:newPanel.name,args}:null,prev:currentState?{panel:currentState.panel.name,args:currentState.args}:null}};Object.assign(navigationTransition.transitionArgs,args);navigationTransition.states={from:currentState,to:{panel:newPanel,args}};return Promise.resolve();}
|
|
function setFutureState(){debug('setFutureState()');currentState=navigationTransition.states.to;}
|
|
function endNavigation(){debug('endNavigation()');if(navigationTransition){navigationTransition.defer.resolve();navigationTransition=null;}}
|
|
function onNavigationError(e=new Error('RejectedPromise')){console.error('Navigation error',e);endNavigation();return Promise.reject(e);}
|
|
function catchStepError(e){if(e){console.error('[Navigation] got an exception: (%s) %s',e,e.stack&&e.stack.replace(/\n/g,'|'));}else{debug('[Navigation] got a rejected promise');}}
|
|
function executeNavigationStep(stepName){debug('executeNavigationStep(%s)',stepName);if(!navigationTransition){return Promise.reject(new Error('Trying to execute the navigation step '+stepName+' but no transition is started yet.'));}
|
|
if(navigationTransition.stepsRun.indexOf(stepName)>=0){debug('executeNavigationStep: step %s has already run.',stepName);return Promise.resolve();}
|
|
var stateKey=stepName.endsWith('Leave')?'from':'to';var state=navigationTransition.states[stateKey];if(!state){debug('Nothing to execute for step',stepName);return Promise.resolve();}
|
|
var panel=state.panel;var resultPromise=Promise.resolve();var viewObject=window[panel.behavior];if(viewObject[stepName]){resultPromise=resultPromise.then(()=>viewObject[stepName](navigationTransition.transitionArgs));}
|
|
return resultPromise.then(()=>{navigationTransition.stepsRun.push(stepName);debug(`Finished executeNavigationStep(${stepName})`);});}
|
|
var previousAnimationDefer;function waitForPanelAnimation(panelElement){if(previousAnimationDefer){previousAnimationDefer.reject(new Error('A new animation started'));}
|
|
var defer=Utils.Promise.defer();previousAnimationDefer=defer;var timeout;function onAnimationEnd(e){panelElement.removeEventListener('animationend',onAnimationEnd);clearTimeout(timeout);debug('animationend',e&&e.type);defer.resolve();}
|
|
panelElement.addEventListener('animationend',onAnimationEnd);timeout=setTimeout(onAnimationEnd,500);return defer.promise.then(()=>previousAnimationDefer=null);}
|
|
function switchPanel(){debug('switchPanel()');if(!navigationTransition){throw new Error('Trying to switch panel but no transition is started yet.');}
|
|
var from=navigationTransition.states.from;var to=navigationTransition.states.to;var oldView;var oldPanelElement;if(from){oldView=from.panel;oldPanelElement=document.querySelector(`.panel-${oldView.behavior}`);if(!oldPanelElement){throw new Error('Couldn\'t find the container element for view '+oldView.name);}}
|
|
var newView=to.panel;if(!newView){throw new Error('Trying to switch panel, but no new view is defined!');}
|
|
var newPanelElement=document.querySelector(`.panel-${newView.behavior}`);if(!newPanelElement){return Promise.reject(new Error('Couldn\'t find the container element for view '+newView.name));}
|
|
if(newPanelElement===oldPanelElement){return Promise.resolve();}
|
|
var isGoingBack=oldView&&oldView.previous===newView.name;newPanelElement.style='';newPanelElement.classList.remove('panel-hidden');newPanelElement.classList.add('panel-active');var doSlideAnimation=from&&from.panel&&from.panel.behavior!==newView.behavior;var doFadeInAnimation=!from||!from.panel;var animationPromise;if(doSlideAnimation){var shouldGoRight=isGoingBack^(document.dir==='rtl');newPanelElement.style.animationName=shouldGoRight?'left-to-current':'right-to-current';oldPanelElement.style.animationName=shouldGoRight?'current-to-right':'current-to-left';}else if(doFadeInAnimation){newPanelElement.style.animationName='fade-in';}
|
|
if(doFadeInAnimation||doSlideAnimation){animationPromise=waitForPanelAnimation(newPanelElement).catch(()=>{}).then(()=>{if(oldPanelElement){oldPanelElement.style='';}
|
|
newPanelElement.style='';});}
|
|
return(animationPromise||Promise.resolve()).then(()=>{if(oldPanelElement){oldPanelElement.classList.remove('panel-active');oldPanelElement.setAttribute('aria-hidden','true');}
|
|
newPanelElement.setAttribute('aria-hidden','false');});}
|
|
function onHashChange(){debug('onHashChange, location=',window.location);startNavigationFromCurrentLocation().then(()=>executeNavigationStep('beforeLeave')).then(()=>executeNavigationStep('beforeEnter').catch(catchStepError)).then(setFutureState).then(switchPanel).then(()=>executeNavigationStep('afterEnter').catch(catchStepError)).then(()=>executeNavigationStep('afterLeave').catch(catchStepError)).then(endNavigation).then(()=>Navigation.emit('navigated')).catch(onNavigationError);}
|
|
function attachHistoryListener(){window.addEventListener('hashchange',onHashChange);}
|
|
function detachHistoryListener(){window.removeEventListener('hashchange',onHashChange);}
|
|
var Navigation={back(){debug('back()');return startNavigation({partial:true}).then(()=>executeNavigationStep('beforeLeave')).then(()=>window.history.back()).then(()=>navigationTransition.defer.promise,onNavigationError);},toPanel(viewName,args){debug('toPanel(%s)',viewName);var view=findViewFromName(viewName);if(!view){return Promise.reject(new Error(`Couldn't find view ${viewName}`));}
|
|
if(this.isCurrentPanel(viewName,args)){debug('View %s is the current view.',viewName);return Promise.resolve();}
|
|
if(navigationTransition){debug('toPanel: previous navigation exists, deferring.');return navigationTransition.defer.promise.then(()=>this.toPanel(viewName,args));}
|
|
this[priv.pendingInit]=false;var hash='#';if(view.partOf){hash+='/'+viewName;}
|
|
hash=Utils.url(hash,args);return startNavigation({newPanel:view,args,partial:true}).then(()=>executeNavigationStep('beforeLeave')).then(()=>{var nextLocation;if(navigationTransition.leaveDocument){var containerView=findContainerView(view);nextLocation='/'+containerView.url+hash;}else{nextLocation=hash;}
|
|
setLocation(nextLocation);}).then(()=>navigationTransition.defer.promise,onNavigationError);},init(){debug('init()');attachHistoryListener();if(navigator.mozHasPendingMessage('notification')){this[priv.pendingInit]=true;return Promise.reject();}
|
|
this[priv.pendingInit]=false;return startNavigationFromCurrentLocation().then(setFutureState).then(()=>executeNavigationStep('beforeEnter').catch(catchStepError)).then(switchPanel).then(()=>executeNavigationStep('afterEnter').catch(catchStepError)).then(endNavigation).then(()=>Navigation.emit('navigated')).catch(onNavigationError);},[priv.pendingInit]:false,hasPendingInit(){return this[priv.pendingInit];},cleanup(){detachHistoryListener();},isDefaultPanel(){return currentState?!currentState.panel.partOf:!findViewNameFromHash(window.location.hash);},isCurrentPanel(panelName,args){debug('isCurrentPanel(%s)',panelName);if(!currentState||!panelName){return false;}
|
|
if(panelName!==currentState.panel.name){return false;}
|
|
if(!args){return true;}
|
|
var currentArgs=currentState.args||{};for(var arg in args){if(args[arg]!=currentArgs[arg]){return false;}}
|
|
return true;},setReady(){debug('setReady()');Array.from(document.querySelectorAll('.panel-hidden')).forEach((panel)=>{panel.classList.remove('panel-hidden');});readyDefer.resolve();readyDefer=null;}};return EventDispatcher.mixin(Navigation,['navigated']);};window.Navigation=NavigationFactory(window);})(window); |