Repository URL to install this package:
|
Version:
7.0.0-alpha.3 ▾
|
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const mobx_1 = require("xmobx/mobx");
const query_string_1 = __importDefault(require("query-string"));
const react_router_dom_1 = require("react-router-dom");
const exotic_1 = require("exotic");
const chain_able_lego_1 = require("chain-able-lego");
const dopemerge_1 = __importDefault(require("chain-able-deps/dist/dopemerge"));
// local
const makeHistory_1 = __importDefault(require("./makeHistory"));
const deps_1 = require("./deps");
const config_1 = require("./config");
/**
* @todo why is this not done in another file?
*/
exports.history = makeHistory_1.default();
/**
* @todo @@perf dedupe from exotic
*/
const isNonEmptyString = (x) => exotic_1.isString(x) && x !== '';
/**
* @todo @@perf freeze if it is safe
*/
exports.EMPTY_ROUTER = {
history: exports.history,
route: undefined,
location: undefined,
};
/**
* @todo subscribe, observe, unsubscribe
*
* @note modules/ does not hot reload
* extends Container
*/
class OneRouterToRuleThemAll {
constructor() {
/**
* @todo @@prod
*/
this.version = '6.0.3';
// this.observersList = new WeakMap()
// observersList: Array<Function> = []
this.oneUrl = undefined;
// @todo @type OneRouterStoreType
// @todo can use this to avoid
// making all history and router observable
this.store = mobx_1.observable.map();
this.shallowStore = mobx_1.observable.map(undefined, { deep: false });
// @todo put this in shallow store...
this._router = exports.EMPTY_ROUTER;
/**
* @deprecated remove this
* .shallow
*/
this.observable = {
urlList: [],
url: '',
};
this.handleRouteChange = (location, actionName) => {
console.debug('[oneRouter] updated');
this.setAsDirty(true);
};
}
setAsDirty(isDirty = true) {
console.debug('marking as dirty: ' + isDirty);
// @todo could use `location` instead?
this.store.set('hasChangedSinceLastEntries', isDirty);
this.shallowStore.delete('entries');
return this;
}
get url() {
// used ||, but @deprecated
return this.store.get('url') || this.observable.url || '';
}
get router() {
return this._router;
}
set router(router) {
/**
* @todo did this change in react-router????
*/
const historyToListenTo = exotic_1.isObj(router.history) ? router.history : exports.history;
if (this.shallowStore.has('listener')) {
const unsubscribe = this.shallowStore.get('listener');
unsubscribe();
}
if (exotic_1.isFunction(historyToListenTo.listen) === false) {
console.warn('oneRouter.router = {} < router is missing history.listen');
}
else {
const listener = historyToListenTo.listen(this.handleRouteChange);
this.shallowStore.set('listener', listener);
}
this.setAsDirty(true);
this._router = router;
}
/**
* @todo could ensure no duplication?
*/
setUrl(value) {
this.store.set('url', value);
if (this.store.has('urlList') === false) {
this.store.set('urlList', mobx_1.observable.array());
}
const urlList = this.store.get('urlList');
urlList.push(value);
// @@remove - @@compatability
this.observable.url = value;
this.observable.urlList = urlList;
this.setAsDirty(true);
return this;
}
get prevUrl() {
// @@perf could optimize by checking length
const length = this.observable.urlList.length;
const lastIndex = length - 1;
const last = this.observable.urlList[lastIndex];
return last || '';
}
setTapEntries(tapEntries) {
if (tapEntries === undefined) {
this.store.delete('tapEntries');
}
else {
this.store.set('tapEntries', tapEntries);
}
this.setAsDirty(true);
return this;
}
get location() {
if (process.env.NODE_ENV === 'development') {
if (this.router.location) {
throw new Error('invalid router - has location');
}
}
return this.history.location;
}
/**
* @todo @@typings
*/
get(key) {
const entries = this.entries();
return entries[key] === undefined ? this.store.get(key) : entries[key];
}
set(key, value) {
this.store.set(key, value);
return this;
}
/**
* @todo @@typings
*/
from(obj) {
const keys = Object.keys(obj);
let didChange = false;
// const original = stringify(this.entries())
// const changed = stringify(obj)
// if (original === changed) {
// return
// }
// // go through keys
keys.forEach(key => {
const value = obj[key];
// update if it changed
if (this.store.get(key) !== value) {
this.store.set(key, value);
didChange = true;
}
});
// // could also set the .diff
// if (didChange === true) {
// this.observersList.forEach(subscriber => subscriber(this))
// }
return this;
}
get urlFromExpress() {
// @note this is because of the next line with the `set`
return this.location.full || '';
}
/**
* @todo action @@strict
*/
set urlFromExpress(oneUrl) {
this.oneUrl = oneUrl;
this.history.location = oneUrl;
}
/**
* @todo optimize with computed
*/
toString() {
const entries = this.entries();
return chain_able_lego_1.stringify(entries);
}
/**
* @todo can use this for debug
* replacing using `pretty: true` in toString
* @todo can serialize ALL like the `store` etc
*/
toJSON() {
// oneUrl, store,
// router, router.history, router.route,
// props, observable
// toString()
return {
entries: this.entries(),
};
}
/**
* @note the type was `AnyObj | string`
* but not sure how that makes much sense
* don't see the use case, YAGNI
*/
has(data) {
return this.toString().includes(data);
}
/**
* @see history/goBack
* @description common naming
* @alias goBack
* @alias gotoBack
*/
gotoPrev() {
/**
* @todo - should unify this history usage stuff & simplify v3
*/
this.history.goBack();
}
gotoNext() {
this.history.goForward();
}
/**
* @example https://uxui.com/route?search=eh#hash
* @alias fullyQualifiedWebAddress
* @alias absolute
* @alias absoluteWebAddress
*/
get full() {
const IS_BROWSER = typeof window === 'object';
return IS_BROWSER
? window.location.href
: this.history.location.href ||
this.history.location.origin +
'/' +
this.history.location.pathname;
}
/**
* @note also available in entries
* and the history.location can be from express
*/
get origin() {
const IS_BROWSER = typeof window === 'object';
return IS_BROWSER
? window.location.origin
: this.history.location.origin ||
this.history.location.origin;
}
/**
* @description (pathname.match(/\w+/gim) || []).join('')
*/
// @computed
get pathname() {
const IS_BROWSER = typeof window === 'object';
if (IS_BROWSER) {
console.debug('[1router] window.location.pathname');
return window.location.pathname;
}
if (this.oneUrl !== undefined) {
console.debug('[1router] oneUrl');
return this.oneUrl.pathname;
}
if (typeof global === 'object' && global.oneUrl !== undefined) {
console.debug('[1router] global.oneUrl.pathname');
return global.oneUrl.pathname;
}
if (isNonEmptyString(this.urlFromExpress)) {
console.debug('[1router] urlFromExpress');
return String(this.urlFromExpress);
}
if (exotic_1.isObj(this.router) &&
exotic_1.isObj(this.history) &&
exotic_1.isObj(this.history.location) &&
exotic_1.isString(this.history.location.pathname) &&
this.history.location.pathname !== '/') {
console.debug('[1router] router.history.location.pathname 1');
return this.history.location.pathname;
}
if (process.env.NODE_ENV !== 'production') {
if (this.history.pathname) {
throw new Error('history has pathname');
}
}
if (this.history.location.pathname) {
console.debug('[1router] history.location.pathname 2');
return this.history.location.pathname;
}
const unknown = new Error('could not find pathname');
console.error(unknown);
console.log(this);
return '@@unknown';
}
/**
* @example
* localhost/eh/10/
* /eh/:categoryId
* match.params.categoryId
* => 10
*/
get matched() {
const matched = {};
const pathname = this.pathname;
const routePathsList = config_1.config.get('routePathsList');
// @todo forEach for readability?
//
// go through our routes
// use the sealed object for perf
// match params (which has a cache)
// merge with matched object
// return matched
for (let index = 0; index < routePathsList.length; index++) {
const matchablePath = routePathsList[index];
deps_1.routePathCurrent.path = matchablePath;
const matchedFromPath = react_router_dom_1.matchPath(pathname, deps_1.routePathCurrent);
// !!!!!!!!! IMPORTANT
// console.log(
// '[1router] ____MATCHED___',
// index,
// pathname,
// matchedFromPath,
// matchablePath
// )
if (exotic_1.isObj(matchedFromPath)) {
// @note this was
// const params = { ...matchedFromPath.params }
Object.assign(matched, matchedFromPath.params);
}
}
return matched;
}
/**
* @todo not sure if it's used
*/
get searchParams() {
return this.getSearchParams();
}
/**
* @note - removed `@computed` from it
* since we had issues with that updating before
* because `router` is a ref, not an observable
* @description this.router this.history history
*/
// @computed
get history() {
return exotic_1.isObj(this.router) && exotic_1.isObj(this.router.history)
? this.router.history
: exports.history;
}
/**
* @see http://unixpapa.com/js/querystring.html
* @api https://github.com/sindresorhus/query-string#nesting
* @todo these entries could be observable...
*/
getSearchParams(fallback = exotic_1.EMPTY_OBJ) {
const IS_BROWSER = typeof window === 'object';
/**
* @todo use URLSearchParams not qs...
*/
const parsed = this.history.location.search
? query_string_1.default.parse(this.history.location.search)
: fallback;
if (!parsed) {
return parsed;
}
// could optimize this
const searchParams = {};
Object.assign(searchParams, parsed);
/**
* it does not support doing things like
* @example &eh=1&eh=10
*/
if (IS_BROWSER) {
const params = new URLSearchParams(window.location.search);
const entries = Array.from(params);
const obj = exotic_1.fromPairsToObj(entries);
Object.assign(searchParams, obj);
}
const final = this.parse(searchParams);
return final;
}
/**
* @todo should add parsing options
* @param parsed object with values that can be parsed
* @return destringified/parsed
*/
parse(parsed) {
if (exotic_1.isObj(parsed)) {
deps_1.parseObjWithSerializedValues(parsed);
}
return parsed;
}
/**
* @todo !!! optimize with a hash for sure
*
* @todo computed
* @todo this needs to get the routing from routing
* ^ need to put in config
*
* @alias toObj
*/
entries() {
if (this.store.get('hasChangedSinceLastEntries')) {
this.setAsDirty(false);
}
else if (this.shallowStore.has('entries')) {
console.debug('[1router] using optimized .entries');
return this.shallowStore.get('entries');
}
else {
console.warn('[1router] should never get here...');
}
const IS_BROWSER = typeof window === 'object';
const pathname = this.pathname;
const browserLocation = IS_BROWSER ? window.location : exotic_1.EMPTY_OBJ;
const params = this.getSearchParams();
const matched = this.matched;
/**
* @todo should make things not enumerable here
*/
const flattenedActions = Object.assign({}, browserLocation, this.history, this.history.location, this.router);
// autofixSafe
const entries = Object.assign({ pathname }, params, matched);
const mergedEntries = Object.assign({}, flattenedActions, entries);
console.debug('[1router] calling .entries');
if (this.store.has('tapEntries') === true) {
const tapEntries = this.store.get('tapEntries');
const finalEntries = tapEntries(mergedEntries);
this.shallowStore.set('entries', finalEntries);
}
else {
this.shallowStore.set('entries', mergedEntries);
}
return this.shallowStore.get('entries');
}
/**
* @tutorial https://github.com/ReactTraining/history/blob/master/modules/LocationUtils.js
* @alias push
* @alias merge
* @alias updateQueryParams
*
* @fires observable update
* @description any kind of url update can be done here
*
* @param to
*
* @example
* [state, pathname, hash]
* toUrlOrPathNameOrParams, optionalParamAsDataOrPath
* let to = toUrlOrPathNameOrParams
* let state = optionalParamAsDataOrPath
* let hash
* if (arguments.length === 2)
*/
update(to, options = {
shouldStringify: true,
shouldMerge: true,
shouldUseNative: false,
}) {
console.info('oneRouter.update(to =' + JSON.stringify(to) + ')');
this.setAsDirty(true);
if (exotic_1.isObj(to)) {
console.info('oneRouter.update isObj(to)');
// if (to.search) {
// to = to.search
// }
// if (to.hash) {
// return this.update('#' + to.hash)
// }
if (process.env.NODE_ENV !== 'production') {
if (to.pathname) {
console.warn(`avoid stringifying history-like properties: ` + to);
}
}
// Commented the below line for filter to work in plp
const searchParams = {};
// const searchParams = this.getSearchParams(false)
console.info('.update(to = ', to, ')');
if (options.shouldMerge !== false) {
to = dopemerge_1.default(searchParams, to);
console.info('oneRouter.update(to =' + JSON.stringify(to) + ')');
}
// const params = options.shouldStringify ? qs.stringify(to) : to
const params = deps_1.goodLookingStringify(to);
if (options.shouldUseNative === true) {
// to, document.title, url
window.history.pushState(to, '', this.pathname + '?' + params);
return this;
}
else {
this.history.push({ pathname: this.pathname, search: params });
}
}
// @todo types below should work
// not a string
if (!exotic_1.isString(to)) {
if (process.env.NODE_ENV !== 'production') {
console.warn('oneRouter.update(to, opts) must have an object or a string as `to`');
}
}
// @note this works but we don't want to use this
// the way it was being used in our domain/reference-store
// else if (isHash(to)) {
// this.history.push({ pathname: this.pathname, hash: to })
// return this
// }
else if (deps_1.isStringifiedParams(to)) {
console.info('isStringifiedParams');
const parsed = query_string_1.default.parse(to);
return this.update(parsed);
}
//
// @todo for the below two cases, check our local routes
//
// @example https://eh.com
else if (deps_1.isFullyQualifiedWebAddress(to)) {
console.info('isFullyQualifiedWebAddress');
this.history.push({
pathname: to,
});
}
// @example /eh
else if (deps_1.isRelativeWebAddress(to)) {
console.info('isRelativeWebAddress');
this.history.replace(to);
}
// uglifyable
else {
return this;
}
}
/**
* @todo !!! should be used as last resort, need to debug usage
*/
forceUpdate(path) {
console.warn('[1router] should not be using .forceUpdate, this is a last resort!');
this.setAsDirty(true);
const finalPath = deps_1.isRelativeWebAddress(path) ? this.origin + path : path;
const IS_BROWSER = typeof window === 'object';
if (IS_BROWSER) {
window.location.href = path;
console.warn('todo - for tests');
// window.location.pathname = path
}
this.history.location.href = finalPath;
this.store.clear();
return this;
}
/**
* @param param query param to delete
*/
delete(param) {
this.store.delete(param);
this.setAsDirty(true);
if (exotic_1.isString(param)) {
const searchParamsRaw = this.getSearchParams();
if (searchParamsRaw === undefined) {
return this;
}
/**
* @todo there be a better way to do this...
*/
const searchParams = this.parse(searchParamsRaw);
delete searchParams[param];
return this.update(searchParams);
}
else {
return this;
}
}
/**
* @alias goto
* @description same as update, but goto this url instead of merging
*/
replace(path) {
this.setAsDirty(true);
this.history.replace(path);
return this;
}
/**
* @description reset url, goto root
*/
clearHistory() {
this.setAsDirty(true);
this.history.replace({
pathname: this.location.pathname,
search: ``,
});
return this;
}
clear() {
this.setAsDirty(true);
this.history.location.pathname = '';
this.history.location.href = '';
this.history.location.origin = '';
this.clearHistory();
this.store.clear();
return this;
}
/**
* @alias observe
* @alias subscribe
* @param subscriber called when observable changes
*/
onChange(subscriber) {
const subscribeToRouteChange = this.history.listen;
if (exotic_1.isFunction(subscribeToRouteChange)) {
subscribeToRouteChange(subscriber);
}
if (process.env.NODE_ENV !== 'production') {
console.assert(typeof subscribeToRouteChange === 'function', 'ON_CHANGE_ONE_ROUTER_NOT_FUNCTION');
}
return this;
}
/**
* @description window.location.reload()
*/
reload() {
window.location.reload();
}
/**
* @todo use chain-able/matcher
* ^ regexp, function, or string, or string wildcard/route-params
* @example isMatch(restrictedRoutes, this.pathname)
*/
get isRestricted() {
const restrictedRoutes = this.get('restrictedRoutes') || exotic_1.EMPTY_ARRAY;
const isRestricted = restrictedRoutes.includes(this.pathname);
return isRestricted;
}
}
__decorate([
mobx_1.observable.ref
], OneRouterToRuleThemAll.prototype, "oneUrl", void 0);
__decorate([
mobx_1.observable.ref
], OneRouterToRuleThemAll.prototype, "_router", void 0);
__decorate([
mobx_1.observable
], OneRouterToRuleThemAll.prototype, "observable", void 0);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "setAsDirty", null);
__decorate([
mobx_1.computed
], OneRouterToRuleThemAll.prototype, "url", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "setUrl", null);
__decorate([
mobx_1.computed
], OneRouterToRuleThemAll.prototype, "prevUrl", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "setTapEntries", null);
__decorate([
mobx_1.computed
], OneRouterToRuleThemAll.prototype, "location", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "set", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "from", null);
__decorate([
mobx_1.computed
], OneRouterToRuleThemAll.prototype, "matched", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "update", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "delete", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "replace", null);
__decorate([
mobx_1.action
], OneRouterToRuleThemAll.prototype, "clearHistory", null);
__decorate([
mobx_1.computed
], OneRouterToRuleThemAll.prototype, "isRestricted", null);
exports.OneRouterToRuleThemAll = OneRouterToRuleThemAll;
exports.default = OneRouterToRuleThemAll;
//# sourceMappingURL=OneRouterToRuleThemAll.js.map