Created
August 13, 2019 08:14
-
-
Save lukebrandonfarrell/0d1ce7213bfb0289c85bb13e6a827def to your computer and use it in GitHub Desktop.
Funnels TypeScript implementation for RMN
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author Luke Brandon Farrell | |
* @description Funnels allow us to re-use screens with react-native-navigation | |
* and build more complex patterns. In the context of this utility a stack | |
* refers to a single screen, where as a funnel refres to a collection of screens. | |
*/ | |
import { Platform } from "react-native"; | |
import { Navigation, Layout, Options } from "react-native-navigation"; | |
import * as NavigationLayouts from "react-native-navigation-layouts"; | |
import _get from "lodash/get"; | |
import _isArray from "lodash/isArray"; | |
import _isInteger from "lodash/isInteger"; | |
import _isString from "lodash/isString"; | |
import _remove from "lodash/remove"; | |
import _isNil from "lodash/isNil"; | |
import _has from "lodash/has"; | |
import _size from "lodash/size"; | |
import _isEmpty from "lodash/isEmpty"; | |
import _findIndex from "lodash/findIndex"; | |
import _castArray from "lodash/castArray"; | |
import { _set } from "./lodash-extension"; | |
enum NavigationMethodsType { | |
pop = "pop", | |
popTo = "popTo", | |
popToRoot = "popToRoot", | |
push = "push", | |
setRoot = "setRoot", | |
showModal = "showModal", | |
showOverlay = "showOverlay", | |
dismissModal = "dismissModal", | |
dismissOverlay = "dismissOverlay", | |
dismissAllModals = "dismissAllModals", | |
} | |
interface StackInterface { | |
layout: Layout | string, | |
navigate: NavigationMethodsType, | |
disabled?: boolean, | |
options?: Options, | |
pointer: number, | |
passProps?: object, | |
componentId?: string, | |
targetComponentId?: string, | |
branch: Array<StackInterface> | |
} | |
/** | |
* Keeps track of the funnel location | |
* | |
* @type {number} | |
*/ | |
let pointer : number = 0; | |
/** | |
* Keeps track of all stacks in the funnel | |
* | |
* @type {Array} | |
*/ | |
let funnel : Array<StackInterface> = []; | |
class Funnels { | |
/** | |
* Creates a stack | |
* | |
* @param {array} data | |
*/ | |
public create(data : Array<StackInterface>) { | |
funnel = []; | |
pointer = 0; | |
if (_isArray(data)) { | |
if (_isEmpty(funnel)) { | |
// Populate the stack | |
data.map(value => { | |
if(!value.disabled){ | |
funnel.push({ | |
pointer: 0, | |
branch: [], | |
...value | |
}); | |
} | |
}); | |
} | |
} | |
return this; | |
} | |
/** | |
* Consumes the stack | |
* | |
* @param data | |
*/ | |
public navigate(data : StackInterface) { | |
/* | |
* If the plain object contains a name / layout, then | |
* we want to perform plain navigation, it it does | |
* not contain name / layout and stack exists, we | |
* navigate from the stack | |
*/ | |
const isPlainNavigation = _isEmpty(funnel); | |
if (isPlainNavigation) { | |
this.routeTo(data); | |
} else { | |
/* | |
* Adds data passed via the navigate method | |
* to the current stack object, this is | |
* important for having access to componentId | |
* on each screen. | |
*/ | |
funnel[pointer] = { | |
...funnel[pointer], | |
...data | |
}; | |
const currentStack = funnel[pointer - 1]; | |
const nextStack = funnel[pointer]; | |
/* | |
* If there is a branch key in this | |
* 'stack', then we have created a | |
* sub stack, meaning we need to | |
* navigate inside it until exited. | |
*/ | |
if (!_isEmpty(_get(currentStack, "branch"))) { | |
const { branch: branchStack, pointer: branchPointer } = currentStack; | |
this.routeTo({ | |
...branchStack[branchPointer], | |
...data | |
}); | |
currentStack["pointer"] = currentStack["pointer"] + 1; | |
} else { | |
/* | |
* We automatically exit the stack if we get | |
* to the end of it, the exit method does the | |
* same as the routeTo method, although it | |
* empties the stack and removes it from async | |
* storage. | |
*/ | |
if (pointer === _size(funnel) - 1) { | |
this.exit(nextStack); | |
} else { | |
this.routeTo(nextStack); | |
pointer = pointer + 1; | |
} | |
} | |
} | |
return this; | |
} | |
/** | |
* Branching a funnel will create sub funnel in this parent funnel | |
* to allow the consumer to return to the parent funnel when | |
* needed. | |
* | |
* @param data | |
*/ | |
public branch(data : Array<StackInterface>) { | |
/* | |
* If stack is not empty, we add the | |
* stack inside this stack, to build | |
* a sub-stack. | |
*/ | |
if (!_isEmpty(funnel)) { | |
const currentStack = funnel[pointer - 1]; | |
currentStack["branch"] = data.map(value => value); | |
currentStack["pointer"] = 0; | |
} | |
return this; | |
} | |
/** | |
* Moves the pointer one back in the funnel. | |
* | |
* A stack has to be passed as data param | |
* to perform navigation. | |
* | |
* @param data | |
*/ | |
public back(data : StackInterface) { | |
if (!_isNil(funnel)) { | |
const currentStack : StackInterface = funnel[pointer - 1]; | |
if (_has(currentStack, "branch")) { | |
currentStack["pointer"] = currentStack["pointer"] - 1; | |
if (currentStack["pointer"] === 0) { | |
currentStack["branch"] = []; | |
} | |
} else { | |
pointer = pointer - 1; | |
} | |
} | |
this.routeTo(data); | |
return this; | |
} | |
/** | |
* Exits the stack | |
* | |
* This will reset the funnel. | |
* | |
* @param layout - optional navigation on revoke | |
*/ | |
public exit(layout : StackInterface) : void { | |
funnel = []; | |
pointer = 0; | |
if (!_isNil(layout)) { | |
this.routeTo(layout); | |
} | |
} | |
/** | |
* Uses RNN methods to navigate | |
* | |
* @param data | |
*/ | |
private routeTo(data : StackInterface) : void { | |
const { | |
layout, | |
passProps, | |
options, | |
navigate: type, | |
componentId, | |
targetComponentId | |
} = data; | |
if(!_isNil(componentId)){ | |
if (type === NavigationMethodsType.push) { | |
if (_isString(layout)) { | |
Navigation.push( | |
componentId, | |
NavigationLayouts.component(layout, passProps, options) | |
); | |
} else { | |
Navigation.push(componentId, layout); | |
} | |
} else if (type === "pop") { | |
Navigation.pop(componentId); | |
} else if (type === "popTo") { | |
if(!_isNil(targetComponentId)){ | |
Navigation.popTo(targetComponentId); | |
} | |
} else if (type === "popToRoot") { | |
Navigation.popToRoot(componentId); | |
} else if (type === "setRoot") { | |
if (Platform.OS === "android") { | |
Navigation.dismissAllModals(); | |
} | |
if (_isString(layout)) { | |
Navigation.setRoot( | |
NavigationLayouts.root(NavigationLayouts.stack([NavigationLayouts.component(layout, passProps, options)])) | |
); | |
} else { | |
Navigation.setRoot(NavigationLayouts.root(layout)); | |
} | |
} else if (type === "showModal") { | |
Navigation.showModal(NavigationLayouts.component(layout)); | |
} else if (type === "dismissModal") { | |
Navigation.dismissModal(componentId, options); | |
} else if (type === "dismissAllModals") { | |
Navigation.dismissAllModals(options); | |
} else if (type === "showOverlay") { | |
Navigation.showOverlay(NavigationLayouts.component(layout)); | |
} else if (type === "dismissOverlay") { | |
Navigation.dismissOverlay(componentId); | |
} | |
} | |
} | |
} | |
export default new Funnels(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment