@virtuous/conductor
Version:
717 lines (548 loc) • 24.5 kB
JavaScript
;
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;