UNPKG

react-native-router-flux-custom-tabs

Version:

React Native Router using Flux architecture this is my fork for testing

404 lines (361 loc) 13.1 kB
/** * Copyright (c) 2015-present, Pavel Aksonov * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. * */ /* eslint-disable no-param-reassign */ import * as ActionConst from './ActionConst'; import { ActionMap } from './Actions'; import { assert } from './Util'; import { getInitialState } from './State'; import { Platform } from 'react-native'; // WARN: it is not working correct. rewrite it. function checkPropertiesEqual(action, lastAction) { let isEqual = true; for (const key of Object.keys(action)) { if (['key', 'type', 'parent'].indexOf(key) === -1) { if (action[key] !== lastAction[key]) { isEqual = false; } } } return isEqual; } function resetHistoryStack(child) { const newChild = child; newChild.index = 0; child.children.map( (el, i) => { if (el.initial) { newChild.index = i; if (!newChild.tabs) { newChild.children = [el]; } } if (el.children) { resetHistoryStack(el); } return newChild; } ); } function refreshTopChild(children, refresh) { if (refresh) { const topChild = children[children.length - 1]; return [...children.slice(0, -1), { ...topChild, ...refresh }]; } return children; } function inject(state, action, props, scenes) { const condition = ActionMap[action.type] === ActionConst.REFRESH ? state.key === props.key || state.sceneKey === action.key : state.sceneKey === props.parent; // console.log("INJECT:", action.key, state.sceneKey, condition); if (!condition) { if (state.children) { const res = state.children.map(el => inject(el, action, props, scenes)); let changed = false; let changedIndex = -1; for (let i = 0; i < res.length; i++) { if (res[i] !== state.children[i]) { changed = true; changedIndex = i; break; } } return changed ? { ...state, children: res, index: changedIndex } : state; } return state; } let ind; switch (ActionMap[action.type]) { case ActionConst.POP_TO: { const targetIndex = action.targetIndex; return { ...state, index: targetIndex, children: refreshTopChild(state.children.slice(0, (targetIndex + 1)), action.refresh), }; } case ActionConst.BACK: case ActionConst.BACK_ACTION: { assert(!state.tabs, 'pop() operation cannot be run on tab bar (tabs=true)'); if (Platform.OS === 'android') { assert(state.index > 0, 'You are already in the root scene.'); } if (state.index === 0) { return state; } let popNum = 1; if (action.popNum) { assert(typeof(action.popNum) === 'number', 'The data is the number of scenes you want to pop, it must be Number'); popNum = action.popNum; assert(popNum % 1 === 0, 'The data is the number of scenes you want to pop, it must be integer.'); assert(popNum > 1, 'The data is the number of scenes you want to pop, it must be bigger than 1.'); assert(popNum <= state.index, 'The data is the number of scenes you want to pop, ' + "it must be smaller than scenes stack's length."); } return { ...state, index: state.index - popNum, from: state.children[state.children.length - popNum], children: refreshTopChild(state.children.slice(0, -1 * popNum), action.refresh), }; } // This action will pop the scene stack and then replace current scene in one go case ActionConst.POP_AND_REPLACE: { assert(!state.tabs, 'pop() operation cannot be run on tab bar (tabs=true)'); assert(state.index > 0, 'You are already in the root scene.'); let popNum = 1; if (action.popNum) { assert(typeof(action.popNum) === 'number', 'The data is the number of scenes you want to pop, it must be Number'); popNum = action.popNum; assert(popNum % 1 === 0, 'The data is the number of scenes you want to pop, it must be integer.'); assert(popNum > 1, 'The data is the number of scenes you want to pop, it must be bigger than 1.'); assert(popNum <= state.index, 'The data is the number of scenes you want to pop, ' + "it must be smaller than scenes stack's length."); } state = { ...state, index: state.index - popNum, from: state.children[state.children.length - popNum], children: state.children.slice(0, -1 * popNum), }; if (state.children[state.index].sceneKey === action.key) { return state; } const newAction = { duration: 0, // do not animate ...action, }; delete newAction.popNum; const newProps = { ...props }; delete newProps.popNum; state.children[state.children.length - 1] = getInitialState( newProps, scenes, state.index, newAction ); return { ...state, children: state.children }; } case ActionConst.REFRESH: return props.base ? { navBar: state.navBar, ...scenes.rootProps, ...props, key: state.key, from: null } : { ...state, ...props, key: state.key, from: null, }; case ActionConst.PUSH_OR_POP: ind = state.children.findIndex(el => el.sceneKey === action.key); if (ind !== -1) { return { ...state, index: ind, from: state.children[state.index], children: refreshTopChild(state.children.slice(0, ind + 1), action.refresh), }; } return { ...state, index: state.index + 1, from: null, children: [...state.children, getInitialState(props, scenes, state.index + 1, action)], }; case ActionConst.PUSH: if (state.children[state.index].sceneKey === action.key && !props.clone && checkPropertiesEqual(action, state.children[state.index])) { return state; } return { ...state, index: state.index + 1, from: null, children: [...state.children, getInitialState(props, scenes, state.index + 1, action)], }; case ActionConst.JUMP: { assert(state.tabs, `Parent=${state.key} is not tab bar, jump action is not valid`); ind = -1; state.children.forEach((c, i) => { if (c.sceneKey === action.key) { ind = i; } }); assert(ind !== -1, `Cannot find route with key=${action.key} for parent=${state.key}`); if (action.unmountScenes) { resetHistoryStack(state.children[ind]); } return { ...state, index: ind }; } case ActionConst.REPLACE: if (state.children[state.index].sceneKey === action.key) { return state; } state.children[state.children.length - 1] = getInitialState( props, scenes, state.index, action ); return { ...state, children: state.children }; case ActionConst.RESET: if (state.children[state.index].sceneKey === action.key) { return state; } state.children = state.children.splice(0, 1); state.children[0] = getInitialState(props, scenes, state.index, action); return { ...state, index: 0, from: null, children: state.children, }; default: return state; } } export function findElement(state, key, type) { if ((ActionMap[type] === ActionConst.REFRESH && state.key === key) || state.sceneKey === key) { return state; } if (state.children) { for (const child of state.children) { const current = findElement(child, key, type); if (current) return current; } } return null; } export function getCurrent(state) { if (!state.children) { return state; } return getCurrent(state.children[state.index]); } function update(state, action) { // find parent in the state const props = { ...state.scenes[action.key], ...action }; assert(props.parent, `No parent is defined for route=${action.key}`); return inject(state, action, props, state.scenes); } function reducer({ initialState, scenes }) { assert(initialState, 'initialState should not be null'); assert(initialState.key, 'initialState.key should not be null'); assert(scenes, 'scenes should not be null'); return (stateParam, actionParam) => { let state = stateParam; let action = actionParam; state = state || { ...initialState, scenes }; assert(action, 'action should be defined'); assert(action.type, 'action type should be defined'); assert(state.scenes, 'state.scenes is missed'); if (action.key) { if (ActionMap[action.type] === ActionConst.REFRESH) { let key = action.key; let child = findElement(state, key, action.type) || state.scenes[key]; let sceneKey = child.sceneKey; if (child.base) { child = { ...state.scenes[child.base], ...child }; assert(state.scenes[child.base], `No scene exists for base=${child.base}`); key = state.scenes[child.base].key; sceneKey = state.scenes[child.base].sceneKey; } assert(child, `missed child data for key=${key}`); // evaluate functions within actions to allow conditional set, like switch values const evaluated = {}; Object.keys(action).forEach(el => { if (typeof action[el] === 'function' && typeof child[el] !== 'undefined' && typeof child[el] !== typeof action[el]) { evaluated[el] = action[el](child[el], child); } }); action = { ...child, ...action, ...evaluated, sceneKey, key }; // console.log("REFRESH ACTION:", action); } else { const scene = state.scenes[action.key]; assert(scene, `missed route data for key=${action.key}`); // clone scene if (scene.clone) { action.parent = getCurrent(state).parent; } } } else { // set current route for pop action or refresh action if (ActionMap[action.type] === ActionConst.BACK_ACTION || ActionMap[action.type] === ActionConst.BACK || ActionMap[action.type] === ActionConst.POP_AND_REPLACE || ActionMap[action.type] === ActionConst.REFRESH || ActionMap[action.type] === ActionConst.POP_TO) { if (!action.key && !action.parent) { action = { ...getCurrent(state), ...action }; } } // Find the parent and index of the future state if (ActionMap[action.type] === ActionConst.POP_TO) { /* * if a string is passed as only argument * Actions.filterParam will put it in the data property * otherwise look for the scene property */ const target = action.data || action.scene; assert(target, 'PopTo() must be called with a single argument: ' + 'either the scene name (string) or an object with within the scene property ' + 'carrying the target scene to pop to'); const targetEl = findElement(state, target, action.type); assert(targetEl, `Cannot find element name named ${target} within current state`); // target is a node let parent = targetEl.sceneKey; let targetIndex = 0; // target is child of a node if (!targetEl.children) { const targetParent = findElement(state, targetEl.parent, action.type); assert(targetParent, `Cannot find parent for target ${target}`); parent = targetParent.sceneKey; targetIndex = targetParent.children.indexOf(targetEl); assert(targetIndex > -1, `${target} does not belong to ${targetParent.sceneKey}`); } action.parent = parent; action.targetIndex = targetIndex; } // recursive pop parent if (ActionMap[action.type] === ActionConst.BACK_ACTION || ActionMap[action.type] === ActionConst.BACK || ActionMap[action.type] === ActionConst.POP_AND_REPLACE) { const parent = action.parent || state.scenes[action.key].parent; let el = findElement(state, parent, action.type); while (el.parent && (el.children.length <= 1 || el.tabs)) { el = findElement(state, el.parent, action.type); assert(el, `Cannot find element for parent=${el.parent} within current state`); } action.parent = el.sceneKey; } } switch (ActionMap[action.type]) { case ActionConst.BACK: case ActionConst.BACK_ACTION: case ActionConst.POP_AND_REPLACE: case ActionConst.POP_TO: case ActionConst.REFRESH: case ActionConst.PUSH: case ActionConst.PUSH_OR_POP: case ActionConst.JUMP: case ActionConst.REPLACE: case ActionConst.RESET: return update(state, action); default: return state; } }; } export default reducer;