@lightningjs/sdk
Version:
The Lightning-SDK helps you build great Lightning-based TV apps!
215 lines (197 loc) • 6.37 kB
JavaScript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2020 Metrological
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
app,
getComponent,
onRequestResolved,
getLastHash,
pagesHost,
stage,
getPreviousState,
getActivePage,
routerConfig,
routeExists,
} from './router'
import Log from '../../Log'
import { isBoolean, isComponentConstructor, isFunction, symbols } from './helpers'
import { getProvider, hasProvider, isPageExpired, dataHooks, addPersistData } from './provider'
import { createComponent } from './components'
import { executeTransition } from './transition'
import { getActiveWidget } from './widgets'
import emit from './emit'
import { getOption } from './route'
import { navigate } from '../index'
import { setHistory, updateHistory } from './history'
import { isWildcard } from './regex'
/**
* The actual loading of the component
* */
export const load = async request => {
let expired = false
try {
request = await loader(request)
if (request && !request.isCancelled) {
// in case of on() providing we need to reset
// app state;
if (app.state === 'Loading') {
if (getPreviousState() === 'Widgets') {
app._setState('Widgets', [getActiveWidget()])
} else {
app._setState('')
}
}
// Do page transition if instance
// is not shared between the routes
if (!request.isSharedInstance && !request.isCancelled) {
await executeTransition(request.page, getActivePage())
}
} else {
expired = true
}
// on expired we only cleanup
if (expired || request.isCancelled) {
Log.debug('[router]:', `Rejected ${request.hash} because route to ${getLastHash()} started`)
if (request.isCreated && !request.isSharedInstance) {
// remove from render-tree
pagesHost.remove(request.page)
}
} else {
onRequestResolved(request)
// resolve promise
return request.page
}
} catch (request) {
if (!request.route) {
console.error(request)
} else if (!expired) {
// @todo: revisit
const { route } = request
// clean up history if modifier is set
if (getOption(route.options, 'clearHistory')) {
setHistory([])
} else if (!isWildcard.test(route.path)) {
updateHistory(request)
}
if (request.isCreated && !request.isSharedInstance) {
// remove from render-tree
pagesHost.remove(request.page)
}
handleError(request)
}
}
}
const loader = async request => {
const route = request.route
const hash = request.hash
const register = request.register
// todo: grab from Route instance
let type = getComponent(route.path)
let isConstruct = isComponentConstructor(type)
let provide = false
// if it's an instance bt we're not coming back from
// history we test if we can re-use this instance
if (!isConstruct && !register.get(symbols.backtrack)) {
if (!mustReuse(route)) {
type = type.constructor
isConstruct = true
}
}
// If page is Lightning Component instance
if (!isConstruct) {
request.page = type
// if we have have a data route for current page
if (hasProvider(route.path)) {
if (isPageExpired(type) || type[symbols.hash] !== hash) {
provide = true
}
}
let currentRoute = getActivePage() && getActivePage()[symbols.route]
// if the new route is equal to the current route it means that both
// route share the Component instance and stack location / since this case
// is conflicting with the way before() and after() loading works we flag it,
// and check platform settings in we want to re-use instance
if (route.path === currentRoute) {
request.isSharedInstance = true
// since we're re-using the instance we must attach
// historyState to the request to prevent it from
// being overridden.
if (isFunction(request.page.historyState)) {
request.copiedHistoryState = request.page.historyState()
}
}
} else {
request.page = createComponent(stage, type)
pagesHost.a(request.page)
// test if need to request data provider
if (hasProvider(route.path)) {
provide = true
}
request.isCreated = true
}
// we store hash and route as properties on the page instance
// that way we can easily calculate new behaviour on page reload
request.page[symbols.hash] = hash
request.page[symbols.route] = route.path
try {
if (provide) {
// extract attached data-provider for route
// we're processing
const { type: loadType, provider } = getProvider(route)
// update running request
request.provider = provider
request.providerType = loadType
await dataHooks[loadType](request)
// we early exit if the current request is expired
if (hash !== getLastHash()) {
return false
} else {
if (request.providerType !== 'after') {
emit(request.page, 'dataProvided')
}
// resolve promise
return request
}
} else {
addPersistData(request)
return request
}
} catch (e) {
request.error = e
return Promise.reject(request)
}
}
const handleError = request => {
if (request && request.error) {
console.error(request.error)
} else if (request) {
Log.error(request)
}
if (request.page && routeExists('!')) {
navigate('!', { request }, false)
}
}
export const mustReuse = route => {
const opt = getOption(route.options, 'reuseInstance')
const config = routerConfig.get('reuseInstance')
// route always has final decision
if (isBoolean(opt)) {
return opt
}
return !(isBoolean(config) && config === false)
}