UNPKG

@virtuous/conductor

Version:
717 lines (548 loc) 24.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _Route = _interopRequireDefault(require("../Route")); var errors = _interopRequireWildcard(require("./errors")); var _emitter = _interopRequireDefault(require("../emitter")); var _history = _interopRequireDefault(require("../history")); var _matcher = _interopRequireDefault(require("../matcher")); var _Stack = _interopRequireDefault(require("../Stack")); var constants = _interopRequireWildcard(require("../constants")); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var Router = /*#__PURE__*/ function () { /** * @param {Function} createHistory The function to create a history instance. */ function Router() { var _this = this; var createHistory = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _history["default"]; _classCallCheck(this, Router); _defineProperty(this, "handleNativeEvent", function (location, action) { if (!_this.nativeEvent) { return; } var next = _Stack["default"].getByIndex(_this.routeIndex + 1); if (next && location.state && location.state.route && next.id === location.state.route.id) { _this.handlePush({ pathname: location.pathname, state: location.state }, false, true); return; } if (action === constants.ACTION_POP) { _this.handlePop(); } }); _defineProperty(this, "createId", function () { return Math.random().toString(36).substr(2, 5); }); _defineProperty(this, "addInitialRoute", function () { var _this$history$locatio = _this.history.location, hash = _this$history$locatio.hash, pathname = _this$history$locatio.pathname, search = _this$history$locatio.search; var fullPathname = "".concat(pathname).concat(search).concat(hash); var id = _this.createId(); _Stack["default"].add(id, new _Route["default"]({ id: id, pathname: fullPathname })); }); _defineProperty(this, "handlePop", function () { var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return new Promise(function (resolve, reject) { var _params$emitBefore = params.emitBefore, emitBefore = _params$emitBefore === void 0 ? true : _params$emitBefore, _params$emitAfter = params.emitAfter, emitAfter = _params$emitAfter === void 0 ? true : _params$emitAfter, _params$forceNative = params.forceNative, forceNative = _params$forceNative === void 0 ? false : _params$forceNative, _params$steps = params.steps, steps = _params$steps === void 0 ? 1 : _params$steps, _params$state = params.state, state = _params$state === void 0 ? null : _params$state; var unlisten = null; if (steps <= 0) { reject(new Error(errors.EINVALIDSTEPS)); _this.nativeEvent = true; return; } // Get id of target route. var targetIndex = Math.max(_this.routeIndex - steps, 0); var prev = _Stack["default"].getByIndex(_this.routeIndex); var next = _Stack["default"].getByIndex(targetIndex); var end = { prev: prev, next: next }; // Emit creation event. if (emitBefore) { _emitter["default"].emit(constants.EVENT_WILL_POP, end); } if (state) { next.state = Object.assign(next.state, state); } /** * */ var callback = function callback() { unlisten(); _this.routeIndex = targetIndex; _this.action = constants.ACTION_POP; if (emitAfter) { _emitter["default"].emit(constants.EVENT_DID_POP, end); } resolve(end); _this.nativeEvent = true; }; /** * Create a reference to the history listener * to be able to unsubscribe from inside the callback. */ unlisten = _this.history.listen(callback); // Perform the history back action. if (forceNative || !_this.nativeEvent) { _this.history.go(steps * -1); } else { callback(); } }); }); _defineProperty(this, "findPattern", function (pathname) { var pattern = Object.keys(_this.patterns).find(function (key) { return _this.patterns[key].match(pathname); }); return pattern || null; }); _defineProperty(this, "register", function (pattern) { var transform = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (!pattern) { throw new Error(errors.EMISSINGPATTERN); } if (typeof pattern !== 'string') { throw new Error(errors.EINVALIDPATTERN); } // var match = (0, _matcher["default"])(pattern); // _this.patterns[pattern] = { match: match, transform: transform }; // Find the pathname of the first route. var _stack$first = _Stack["default"].first(), _stack$first2 = _slicedToArray(_stack$first, 2), route = _stack$first2[1]; // If it has been set then we don't need to match it. if (route.pattern !== null) { return; } // if (match(route.pathname)) { route.setPattern(pattern, transform); var end = { prev: null, next: route }; _emitter["default"].emit(constants.EVENT_WILL_PUSH, end, true); _emitter["default"].emit(constants.EVENT_DID_PUSH, end, true); } }); _defineProperty(this, "handleReplace", function (params) { return new Promise(function (resolve, reject) { // Check for missing parameters. if (!params) { reject(new Error(errors.EPARAMSMISSING)); _this.nativeEvent = true; return; } // Check for empty params. if (Object.keys(params).length === 0) { reject(new Error(errors.EPARAMSEMPTY)); _this.nativeEvent = true; return; } var _params$emitBefore2 = params.emitBefore, emitBefore = _params$emitBefore2 === void 0 ? true : _params$emitBefore2, _params$emitAfter2 = params.emitAfter, emitAfter = _params$emitAfter2 === void 0 ? true : _params$emitAfter2, pathname = params.pathname, state = params.state; var pattern = _this.findPattern(pathname.split('?')[0]); var unlisten = null; if (!pattern) { reject(new Error(errors.EINVALIDPATHNAME)); _this.nativeEvent = true; return; } var _stack$getByIndex = _Stack["default"].getByIndex(_this.routeIndex), id = _stack$getByIndex.id; var transform = _this.patterns[pattern].transform; var prev = _Stack["default"].get(id); var next = new _Route["default"]({ id: id, pathname: pathname, pattern: pattern, state: state, transform: transform }); var end = { prev: prev, next: next }; // Add item to the stack _Stack["default"].add(id, next); // Emit creation event. if (emitBefore) { _emitter["default"].emit(constants.EVENT_WILL_REPLACE, end); } /** * The history event callback. */ var callback = function callback() { // Unsubscribe from the history events. unlisten(); _this.action = constants.ACTION_REPLACE; // Emit completion event. if (emitAfter) { _emitter["default"].emit(constants.EVENT_DID_REPLACE, end); } resolve(end); _this.nativeEvent = true; }; /** * Create a reference to the history listener * to be able to unsubscribe from inside the callback. */ unlisten = _this.history.listen(callback); // Perform the history replace action. if (!_this.nativeEvent) { _this.history.replace({ pathname: pathname, state: _objectSpread({}, state, { route: { id: id } }) }); } else { callback(); } }); }); _defineProperty(this, "push", function (params) { _this.nativeEvent = false; return _this.handlePush(params); }); _defineProperty(this, "pop", function (params) { _this.nativeEvent = false; return _this.handlePop(params); }); _defineProperty(this, "replace", function (params) { _this.nativeEvent = false; return _this.handleReplace(params); }); _defineProperty(this, "reset", function () { var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; return new Promise( /*#__PURE__*/ function () { var _ref = _asyncToGenerator( /*#__PURE__*/ _regenerator["default"].mark(function _callee(resolve) { var _stack$first3, _stack$first4, route, prev, next; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _stack$first3 = _Stack["default"].first(), _stack$first4 = _slicedToArray(_stack$first3, 2), route = _stack$first4[1]; prev = _Stack["default"].getByIndex(_this.routeIndex); next = { prev: prev, next: route }; if (state) { _this.update(route.id, state, false); } _emitter["default"].emit(constants.EVENT_WILL_RESET, next); if (!(_this.routeIndex > 0)) { _context.next = 8; break; } _context.next = 8; return _this.handlePop({ emitBefore: false, emitAfter: false, forceNative: true, steps: _this.routeIndex }); case 8: _emitter["default"].emit(constants.EVENT_DID_RESET, next); resolve(next); case 10: case "end": return _context.stop(); } } }, _callee); })); return function (_x) { return _ref.apply(this, arguments); }; }()); }); _defineProperty(this, "resetTo", function (pathname) { var state = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return new Promise( /*#__PURE__*/ function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/ _regenerator["default"].mark(function _callee2(resolve, reject) { var previous, _stack$first5, _stack$first6, route, next; return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: if (pathname) { _context2.next = 3; break; } reject(new Error(errors.EMISSINGPATHNAME)); return _context2.abrupt("return"); case 3: if (_this.findPattern(pathname)) { _context2.next = 6; break; } reject(new Error(errors.EINVALIDPATHNAME)); return _context2.abrupt("return"); case 6: previous = _this.getCurrentRoute(); if (!(_this.routeIndex > 0)) { _context2.next = 10; break; } _context2.next = 10; return _this.handlePop({ emitBefore: false, emitAfter: false, forceNative: true, steps: _this.routeIndex }); case 10: _context2.next = 12; return _this.handleReplace({ pathname: pathname, state: state }); case 12: _stack$first5 = _Stack["default"].first(), _stack$first6 = _slicedToArray(_stack$first5, 2), route = _stack$first6[1]; next = { prev: previous, next: route }; _emitter["default"].emit(constants.EVENT_WILL_RESET, next); _emitter["default"].emit(constants.EVENT_DID_RESET, next); resolve(next); case 17: case "end": return _context2.stop(); } } }, _callee2); })); return function (_x2, _x3) { return _ref2.apply(this, arguments); }; }()); }); _defineProperty(this, "update", function (id) { var state = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var emit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; return new Promise(function (resolve, reject) { if (!id || Object.keys(state).length === 0) { reject(new Error(errors.EPARAMSINVALID)); return; } var route = _Stack["default"].get(id); if (!route) { reject(new Error(errors.EINVALIDID)); return; } route.state = _objectSpread({}, route.state, {}, state); route.updated = Date.now(); _Stack["default"].update(id, route); if (emit) { _emitter["default"].emit(constants.EVENT_UPDATE, route); } resolve(route); }); }); _defineProperty(this, "getCurrentRoute", function () { return _Stack["default"].getByIndex(_this.routeIndex); }); _defineProperty(this, "match", function () { var pathname = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var foundPattern = false; if (!pathname) { return false; } Object.entries(_this.patterns).some(function (_ref3) { var _ref4 = _slicedToArray(_ref3, 2), pattern = _ref4[0], properties = _ref4[1]; if (properties.match(pathname)) { foundPattern = pattern; return true; } return false; }); return foundPattern; }); _defineProperty(this, "matches", function () { var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var pathname = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (!pattern || !pathname) { return false; } if (!(pattern in _this.patterns)) { return false; } return !!_this.patterns[pattern].match(pathname); }); // Flag to indicate a native history event. Should always be reset to true. this.nativeEvent = true; this.history = createHistory(); // The patterns are collected to match against pathnames. this.patterns = {}; // The `routeIndex` is used to track which stack entry is the current route. this.routeIndex = 0; this.action = constants.ACTION_PUSH; // Unsubscribe to any other history module changes. if (typeof this.historyListener === 'function') { this.historyListener(); _Stack["default"].clear(); } this.addInitialRoute(); this.historyListener = this.history.listen(this.handleNativeEvent); } /** * @param {string} location The new history location. * @param {string} action The most recent history action. */ _createClass(Router, [{ key: "handlePush", /** * @param {Object} params The params to use when navigating. * @param {boolean} [cleanStack=true] When true, the overhanging routes will be removed. * @param {boolean} [nativePush=false] When true, the process of adding a route to * the stack is skipped. * @returns {Promise} */ value: function handlePush(params) { var _this2 = this; var cleanStack = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; var nativePush = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; return new Promise(function (resolve, reject) { // Check for missing parameters. if (!params) { reject(new Error(errors.EPARAMSMISSING)); _this2.nativeEvent = true; return; } // Check for empty params. if (Object.keys(params).length === 0) { reject(new Error(errors.EPARAMSEMPTY)); _this2.nativeEvent = true; return; } var _params$emitBefore3 = params.emitBefore, emitBefore = _params$emitBefore3 === void 0 ? true : _params$emitBefore3, _params$emitAfter3 = params.emitAfter, emitAfter = _params$emitAfter3 === void 0 ? true : _params$emitAfter3, pathname = params.pathname, state = params.state; var pattern = _this2.findPattern(pathname.split('?')[0]); var unlisten = null; // Remove all unwanted items from the stack. if (cleanStack) { while (_this2.routeIndex < _Stack["default"].getAll().size - 1) { var _stack$last = _Stack["default"].last(), _stack$last2 = _slicedToArray(_stack$last, 1), _id = _stack$last2[0]; _Stack["default"].remove(_id); } } var id = _this2.createId(); var _ref5 = pattern ? _this2.patterns[pattern] : {}, transform = _ref5.transform; var prev = _Stack["default"].getByIndex(_this2.routeIndex); var next = nativePush ? _Stack["default"].getByIndex(_this2.routeIndex + 1) : new _Route["default"]({ id: id, pathname: pathname, pattern: pattern, state: state, transform: transform }); if (!nativePush) { // Add item to the stack _Stack["default"].add(id, next); } // Emit creation event. if (emitBefore) { _emitter["default"].emit(constants.EVENT_WILL_PUSH, { prev: prev, next: next }); } /** * TODO: move to class function with params * The history event callback. */ var callback = function callback() { // Unsubscribe from the history events. unlisten(); // Increment the route index. _this2.routeIndex += 1; _this2.action = constants.ACTION_PUSH; // Emit completion event. if (emitAfter) { _emitter["default"].emit(constants.EVENT_DID_PUSH, { prev: prev, next: next }); } // Resolve the Promise. resolve({ prev: prev, next: next }); _this2.nativeEvent = true; }; /** * Create a reference to the history listener * to be able to unsubscribe from inside the callback. */ unlisten = _this2.history.listen(callback); // Perform the history push action. if (!_this2.nativeEvent) { _this2.history.push({ pathname: pathname, state: _objectSpread({}, state, { route: { id: id } }) }); } else { callback(); } }); } /** * Match the given pathname to a registered pattern. * @param {string} pathname The pathname to match. * @returns {string|null} */ }]); return Router; }(); var _default = new Router(); exports["default"] = _default;