Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
/**
 * @todo console.group  these
 *
 * @throws LogicException
 *
 *  1. renders routes
 *    @example /myaccount
 *    - this calls
 *  2. renders subroutes
 *    @example /myaccount/orders
 *    a. checks if the route params HAS subroutes
 *    b. checks the route.component.getComponentForRoute
 *    c. else, route.component.getComponentForSubRoute
 *
 *
 * @module Router
 *
 * @variation minimal can be
 *  1. create a flat array
 *  2. sort by length (longest, shortest)
 *
 * @variation
 *  1. use context
 *  2. if context.subroutes
 *      - render the first to match
 *      - then it would keep traversing
 *
 */
import React from 'react'
import { OneRouterContainer, oneRouter } from '@skava/router'
import { isObj, EMPTY_OBJ } from 'exotic'
import {
  array,
  object,
  string,
  number,
  any,
} from '@skava/modules/___dist/view-container/types'
import { Switch, Route, Redirect, withRouter } from 'react-router-dom'
import {
  renderRoutes,
  matchRoutes,
} from '@skava/router/dist/react-router-config'
import { toComponentName } from '@skava/modules/___dist/identifier'
import { Fallback } from 'views/pages/Fallback'
import routeList from 'routes'
import { setup } from 'src/bootstrapper/setup/observableRouting'
import ErrorBoundary from '../ErrorBoundary'

// @@demo
// wasn't happening...
setup()

/**
 * used to DI the current route
 */
const scoped = {
  params: undefined,

  // doubly linked list would be great for page animations here
  prevRoute: EMPTY_OBJ,
  prevView: undefined,
  prevLocation: undefined,
  // nextView: undefined,
}

function fromPropsToPrevLocation(props) {
  // props.location.key
  const url = props.match ? props.match.url : ''
  const params = props.match ? JSON.stringify(props.match.params) : ''
  const hashed =
    url +
    props.location.pathname +
    props.location.hash +
    props.location.search +
    params

  if (typeof window === 'object') {
    return hashed + window.location.href
  } else {
    return hashed
  }
}

/**
 * @todo really need to optimize `entries`
 * @todo @fixme this is temporary
 * @description change 1router .entries
 *              to use the scoped params for this route
 *              (.matched is a getter)
 */
function tapOneRouter() {
  // only tap 1x
  if (oneRouter.entries.isTapped) {
    return
  }

  // oneRouter.notify = action

  function entriesTapped() {
    // console.log('[routing] calling tapped entries')
    // @note - we could use [v] before overriding
    // but we can use `this.toObj` which is why that fn was method that way
    // (protected name, entries being aliased)
    // const entries = oneRouter.entries
    const obj = oneRouter.toObj()

    // update when scoped params updates
    Object.assign(obj, scoped.params)
    return obj
  }

  // make sure we only tap once
  entriesTapped.isTapped = true
  // set on the oneRouter
  oneRouter.entries = entriesTapped
}

// init
tapOneRouter()

function debugRouting(props) {
  console.log('[ROUTING] - mounting OneRouterContainer')
  // console.log(oneRouter.entries())
  // console.log('ROUTING_PROPS_FROM_ROUTER')
  // console.log(props)
}

// component
// @NOTE !!! USING @withRouter HERE PUTS A ROUTE INSIDE THE ROUTE!!!!!!
class OneRouterContainerTemporary extends React.PureComponent {
  static contextTypes = {
    router: object,
  }

  componentWillMount() {
    const params = this.props.match.params
    scoped.params = params
    debugRouting(this.props)
  }
  componentDidMount() {
    // only doing this on the browser
    oneRouter.router = this.context.router || this.props.router
  }

  render() {
    return ''
  }
}

/**
 * @description decorate a route, DI the params out into 1router
 */
// @todo @split
// eslint-disable-next-line max-statements
function toRouteItem(route) {
  const { component, path, exact } = route
  const Component = component

  // named route - this wraps the route
  // @note this is basically @withRouter & setting oneRouter props from the props
  // @todo @lint @split @@demo
  // eslint-disable-next-line
  function decoratedRouteComponent(props) {
    // !!! probably most of this is not needed since we are not deduping
    // ^ however, we can keep as comment for route transitions
    // ^ thanks to removing @withRouter

    // always update 1router
    scoped.oneRouterView = (
      <OneRouterContainerTemporary {...props} decoratedRouteItem={route} />
    )

    const prevLocation = fromPropsToPrevLocation(props)
    // I see many duplicate route updates, no good - use previously rendered
    if (
      route.path === scoped.prevRoute.path &&
      scoped.prevLocation === prevLocation
    ) {
      // todo
      console.log('[ROUTING] deduped route ', route)
      return scoped.prevView
    }

    // .render
    console.log('[ROUTING] rendering route: ', route)

    oneRouter.observable.urlList.push(prevLocation)
    oneRouter.observable.url = prevLocation
    scoped.prevLocation = prevLocation
    scoped.prevRoute = route
    scoped.prevView = (
      <React.Fragment key={path}>
        {scoped.oneRouterView}
        <Component {...props} />
      </React.Fragment>
    )

    return scoped.prevView
  }

  // if (process.env.DEBUG_REACT_DISPLAY_NAME) {
  const componentName = toComponentName(Component)
  const wrapName = `decoratedRouteComponent`

  if (componentName.includes(wrapName)) {
    console.warn('[ROUTING] component duplicated in mapping - fix: ', wrapName)
    return route
  }

  const displayName = `${wrapName}(${componentName})`
  decoratedRouteComponent.displayName = displayName

  route.component = decoratedRouteComponent
  return route
}

/**
 * @description take all routes, add 1router extractor for DI
 */
function toRoutesWithOneRouterUpdater(routes) {
  return routes.map(toRouteItem)
}

/**
 * @description OneRouterRouter
 */
class Routing extends React.PureComponent {
  static propTypes = {
    routes: array.isRequired,
    location: object.isRequired,
  }
  static defaultProps = {
    routes: routeList,
    location: EMPTY_OBJ,
  }

  constructor(props, ...args) {
    super(props, ...args)
    // no need to make it update, only needs to happen 1x
    this.routesWithOneRouterUpdater = toRoutesWithOneRouterUpdater(
      this.props.routes
    )
  }

  componentDidMount() {
    if (typeof window === 'object') {
      window.scrollTo({
        top: 0,
        behavior: 'smooth',
      })
    }
  }

  render() {
    const { routesWithOneRouterUpdater } = this

    return (
      <ErrorBoundary>{renderRoutes(routesWithOneRouterUpdater)}</ErrorBoundary>
    )
  }
}

export { Routing }
export default Routing