Skip to content

Instantly share code, notes, and snippets.

@cevek
Last active September 22, 2016 17:38
Show Gist options
  • Save cevek/28a2ab1eb79bc3e658793ad3f581ad57 to your computer and use it in GitHub Desktop.
Save cevek/28a2ab1eb79bc3e658793ad3f581ad57 to your computer and use it in GitHub Desktop.
type Resolve<T> = (value?: T | PromiseLike<T> | undefined) => void;
type Reject = (reason?: any) => void;
type AbortFn = ((onCancel: () => void) => void) | undefined;
type Executor<T> = (resolve: Resolve<T>, reject: Reject, onCancel?: AbortFn) => void
export class CancellablePromise<T> implements Promise<T> {
cancelled = false;
children?: CancellablePromise<{}>[] = undefined;
promise: Promise<T>;
onCancel?: ()=>void;
parent: CancellablePromise<{}>;
onfulfilled: any;
constructor(private executor: Executor<T>) {
this.children = [];
this.promise = new Promise((resolve, reject) => {
let abortFn: AbortFn = undefined;
if (executor.length >= 2) {
abortFn = onCancel => this.onCancel = onCancel;
}
executor(data => {
if (data instanceof CancellablePromise) {
let topPromise = this.findTopPromise(data);
this.addChild(topPromise);
if (this.cancelled) {
topPromise.cancel();
}
}
resolve(data);
}, reject, abortFn);
});
}
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): CancellablePromise<TResult> {
this.onfulfilled = onfulfilled;
let resolve: Resolve<TResult>;
let reject: Reject;
let newPromise = new CancellablePromise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
this.addChild(newPromise);
let _didFulfill = (data: T) => resolve(!this.cancelled ? (onfulfilled ? onfulfilled(data) : data as {} as TResult) : undefined);
let _didReject = (err: any) => onrejected ? resolve(!this.cancelled ? onrejected(err) : undefined) : reject(err);
this.promise.then(_didFulfill!, _didReject!);
return newPromise;
}
// todo: doesn't work
catch<TResult>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>): CancellablePromise<TResult> {
return this.then(undefined, onrejected);
}
protected findTopPromise(promise: CancellablePromise<{}>) {
let top = promise;
while (top.parent) {
top = top.parent;
}
return top;
}
protected addChild(promise: CancellablePromise<{}>) {
if (!this.children) {
this.children = [];
}
promise.parent = this;
this.children.push(promise);
}
cancelAll() {
this.findTopPromise(this).cancel();
}
cancel() {
if (this.cancelled) {
return;
}
this.cancelled = true;
if (this.onCancel) {
this.onCancel();
}
if (this.children) {
for (let i = 0; i < this.children.length; i++) {
this.children[i].cancel();
}
}
}
static resolve<T>(value?: T | PromiseLike<T>): CancellablePromise<T> {
return new CancellablePromise(resolve => resolve(value as any));
}
static reject<T>(reason: T): CancellablePromise<T> {
return new CancellablePromise((resolve, reject) => reject(reason));
}
static all<TAll>(array: (TAll | PromiseLike<TAll>)[]): CancellablePromise<TAll[]> {
return new CancellablePromise((resolve, reject) => {
let done = 0;
let newArr = new Array(array.length);
array.forEach((promise, i) => {
let p = promise as PromiseLike<TAll>;
if (p && p.then) {
done++;
p.then(val => {
newArr[i] = val;
done--;
if (done == 0) {
resolve(newArr);
}
}, reject);
} else {
newArr[i] = promise as TAll;
}
});
if (!array.length) {
resolve(newArr);
}
});
}
static race(array: PromiseLike<{}>[]) {
return new CancellablePromise((resolve, reject) => {
for (let i = 0; i < array.length; i++) {
let promise = array[i];
if (promise && promise.then) {
promise.then(resolve, reject);
} else {
resolve(promise);
}
}
if (!array.length) {
resolve(undefined);
}
});
}
static waterfall(values: PromiseLike<{}>[]) {
let promise = CancellablePromise.resolve();
for (let i = 0; i < values.length; i++) {
const p = values[i];
promise = promise.then(val => p);
}
}
}
CancellablePromise.resolve(2).then(null, a => a).then(a => console.log('XXXXXXXXXX', a));
window.CancellablePromise = CancellablePromise;
const enum PromiseState{
PENDING,
RESOLVED,
REJECTED,
CANCELLED
}
type This = {} | undefined;
type PDef = P<{} | undefined>;
type Callback<R, T> = <R>(val: T | undefined) => R | P<R>;
class P<T> {
value: T | undefined;
state = PromiseState.PENDING;
children: PDef[];
callback: Callback<{}, T>;
thisArg: This;
rejectCallback: Callback<{}, T>;
rejectThisArg: This;
inner: boolean;
resolve(value: T | undefined | PromiseLike<T>) {
if (!this.inner) {
if (this.state !== PromiseState.PENDING) {
return this;
}
const newValue = this.callback ? (this.thisArg ? this.callback.call(this.thisArg, value) : this.callback(value as T)) : value;
if (newValue instanceof P) {
this.processResultPromise(newValue);
return this;
}
this.value = newValue;
this.state = PromiseState.RESOLVED;
}
this.runChildren(false);
return this;
}
reject(reason: T | Error) {
if (!this.inner) {
if (this.state !== PromiseState.PENDING) {
return this;
}
this.value = this.rejectCallback ? (this.rejectThisArg ? this.rejectCallback.call(this.rejectThisArg, reason) : this.rejectCallback(reason as T)) : reason;
}
this.state = PromiseState.REJECTED;
this.runChildren(!this.rejectCallback);
return this;
}
protected processResultPromise(promise: PDef) {
const state = promise.state;
this.inner = true;
this.value = promise.value as T;
this.state = promise.state;
if (promise.children) {
this.children = this.children ? promise.children.concat(this.children) : promise.children.slice();
}
this.runChildren(state == PromiseState.REJECTED);
}
protected runChildren(doReject: boolean) {
if (this.children) {
if (doReject) {
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];
child.reject(this.value);
}
} else {
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];
child.resolve(this.value);
}
}
}
}
then<TResult>(callback: (val: T) => (TResult | P<TResult>), thisArg?: This): P<TResult> {
const p = new P<TResult>();
p.callback = callback as {} as Callback<T, TResult>;
p.thisArg = thisArg;
if (!this.children) {
this.children = [];
}
this.children.push(p);
return p;
}
catch<TResult>(callback: (val: T) => (TResult | P<TResult>), thisArg?: This): P<TResult> {
const p = new P<TResult>();
p.rejectCallback = callback as {} as Callback<T, TResult>;
p.rejectThisArg = thisArg;
if (!this.children) {
this.children = [];
}
this.children.push(p);
return p;
}
}
// window.P = P;
function check(val: any, expected: any) {
for (let i = 0; i < Math.max(val.length, expected.length); i++) {
if (val[i] !== expected[i]) {
console.error('Test failed', i, val, expected, val[i], expected[i]);
}
}
}
function test1() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
p.resolve(1);
check(calls, [1]);
}
function test2() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
p.reject(1);
check(calls, []);
}
function test3() {
const calls: number[] = [];
const p = new P<number>();
p.catch(val => 2)
.then(val => calls.push(val));
p.reject(1);
check(calls, [2]);
}
function test4() {
const calls: number[] = [];
const p = new P<number>();
p.then(() => 2)
.catch(val => 3)
.then(val => calls.push(val));
p.reject(1);
check(calls, [3]);
}
function test5() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => new P().resolve(val + 2))
.catch(val => 30)
.then(val => calls.push(val));
p.resolve(1);
check(calls, [3]);
}
function test6() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => new P().reject(val + 10))
.catch((val: number) => {
calls.push(val);
return 7
})
.then(val => calls.push(val));
p.resolve(1);
check(calls, [11, 7]);
}
function test7() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => calls.push(val));
p.then(val => {
calls.push(val + 1);
return val + 1
})
.then(val => calls.push(val));
p.resolve(1);
check(calls, [1, 2, 2]);
}
function test8() {
const calls: number[] = [];
const p = new P<number>();
const pp = p.then(val => {
const r = new P<number>().resolve(val + 1);
r.catch(a => calls.push(a + 100));
r.then(a => calls.push(a + 1));
return r;
});
pp.then(val => calls.push(val + 5));
pp.then(val => {
calls.push(val + 2);
return val + 1
})
.then(val => calls.push(val));
p.resolve(1);
check(calls, [3, 7, 4, 3]);
}
function test9() {
const calls: number[] = [];
const p = new P<number>();
p.then(val => {
const r = new P<number>();
const rr = r.then(val => new P<number>().resolve(val + 1));
r.resolve(val + 1);
return rr;
}).then(val => calls.push(val));
p.resolve(1);
check(calls, [3]);
}
test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();
test9();
// p.reject();
interface AppProps {
search: {
y: number;
}
app: {
}
}
class App extends React.Component<AppProps, {}> {
static resolve(params: AppProps) {
return new CancellablePromise((resolve) => {
params.app = {};
setTimeout(() => {
console.log("App enter done", params);
resolve()
}, 100);
})
}
static leave(nextUrl: Url) {
return new CancellablePromise((resolve) => {
setTimeout(() => {
console.log("App leave", nextUrl);
resolve()
}, 300);
})
}
render() {
console.log("Render App", this.props);
return <div className="foo">
Main
<div><ActionButton onClick={()=>indexRoute.goto({})}>Index</ActionButton></div>
<div><ActionButton onClick={()=>indexRoute.profile.settings.goto({})}>Settings</ActionButton></div>
{this.props.children}
</div>
}
}
interface ProfileProps {
profile: {
}
}
class Profile extends React.Component<ProfileProps, {}> {
static resolve(params: ProfileProps) {
return new CancellablePromise((resolve) => {
params.profile = {};
setTimeout(() => {
console.log("Profile enter done", params);
// indexRoute.goto({}, undefined, true);
resolve()
}, 200);
})
}
static leave(nextUrl: Url) {
return new CancellablePromise((resolve) => {
setTimeout(() => {
console.log("Profile leave", nextUrl);
resolve()
}, 200);
})
}
render() {
console.log("Render Profile", this.props);
return <div className="profile">
Profile
{this.props.children}
</div>
}
}
interface ProfileSettingsProps {
settings: {
foo: number;
}
}
class ProfileSettings extends React.Component<ProfileSettingsProps, {}> {
static resolve(params: ProfileSettingsProps) {
return new CancellablePromise((resolve) => {
params.settings = {foo: 123};
setTimeout(() => {
console.log("ProfileSettings enter done", params);
resolve()
}, 300);
})
}
static leave(nextUrl: Url) {
return new CancellablePromise((resolve) => {
setTimeout(() => {
console.log("ProfileSettings leave", nextUrl);
resolve()
}, 1000);
})
}
render() {
console.log("Render ProfileSettings", this.props);
return <div className="profile-settings">
Settings
{this.props.children}
</div>
}
}
interface NewsProps {
settings: {
foo: number;
}
}
class News extends React.Component<NewsProps, {}> {
static resolve(params: NewsProps) {
return new CancellablePromise((resolve) => {
params.settings = {foo: 123};
setTimeout(() => {
console.log("ProfileSettings enter done", params);
resolve()
}, 300);
})
}
static leave(nextUrl: Url) {
return new CancellablePromise((resolve) => {
setTimeout(() => {
console.log("news leave", nextUrl);
resolve()
}, 100);
})
}
render() {
console.log("Render news", this.props);
return <div className="news">
News
{this.props.children}
</div>
}
}
const indexRoute = route('/', App, {
index: () => <div>Index</div>,
any: () => <div>Index Not Found</div>,
profile: route('profile', Profile, {
index: () => <div>Index Profile</div>,
any: () => <div>Profile Not Found</div>,
settings: route('settings', ProfileSettings, {
index: () => <div>Settings index</div>,
any: () => <div>Settings not found</div>
})
}),
news: route('news', News, {
index: () => <div>News index</div>,
any: () => <div>News not found</div>
})
});
interface ActionButtonProps {
onClick: () => CancellablePromise<{}>
}
class ActionButton extends React.Component<ActionButtonProps, {}> {
disabled = false;
onClick = () => {
const promise = this.props.onClick();
promise.then(() => {
this.disabled = false;
this.forceUpdate();
});
this.disabled = true;
this.forceUpdate();
};
render() {
return <button disabled={this.disabled} onClick={this.onClick}>{this.props.children}</button>
}
}
const urlHistory = new BrowserHistory();
(window as any).start = (initialData?: {}) => {
// history.pushState(null, null, '/profile/');
// history.pushState(null, null, '/profile/settings/');
// history.pushState(null, null, '/profile/');
// history.pushState(null, null, '/');
document.onclick = () => {
// indexRoute.profile.settings.goto({}, {x: 1});
};
ReactDOM.render(<RouterView history={urlHistory} indexRoute={indexRoute}/>, document.body);
// ReactDOM.render(<RouterView initialData={initialData}/>, document.body);
};
import {CancellablePromise} from "./CancellablePromise";
interface ComponentCls<P> {
resolve?(params: P): CancellablePromise<{}>;
leave?(nextUrl: Url): CancellablePromise<{}>;
}
type ComponentClass<P> = (React.ComponentClass<P> | React.StatelessComponent<P>) & ComponentCls<P>;
type RouteDef = Route<{}>;
export interface RouteProps {
params: {};
search: {},
url: Url;
}
interface RouteParams {
url: string;
component: ComponentClass<{}>;
}
interface Children {
index?: ComponentClass<{}>
any?: ComponentClass<{}>
}
interface PublicRoute {
goto(props: {}, search?: {}, replace?: boolean): Promise<{}>;
}
interface InnerRoute {
_route: Route<{}>;
}
export function route<C extends Children>(url: string, component: ComponentClass<{}>, children = {} as C): C & PublicRoute {
const route = new Route({
url: url,
component: component,
});
(children as {} as InnerRoute)._route = route;
if (children) {
const keys = Object.keys(children);
if (children.index) {
route.addChild(new Route({url: '^', component: children.index}));
}
if (children.any) {
route.addChild(new Route({url: '*', component: children.any}));
}
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = children[key] as InnerRoute;
if (value._route) {
route.addChild(value._route);
}
}
}
const ret = children as C & PublicRoute;
ret.goto = (props: {}, search?: {}, replace = false) => route.goto(props, search, replace);
return ret;
}
export class Route<Props> {
router: Router;
parent: RouteDef;
children: RouteDef[] = [];
regexp: RegExp;
names: string[] = [];
regexpNames: RegExp[] = [];
selfUrl: string;
url: string;
component: ComponentClass<RouteProps>;
onEnter?: (params: RouteProps) => CancellablePromise<{}>;
onLeave?: (nextUrl: Url)=>CancellablePromise<{}>;
constructor(params: RouteParams) {
this.selfUrl = params.url;
this.component = params.component as ComponentClass<RouteProps>;
this.onEnter = params.component.resolve;
this.onLeave = params.component.leave;
}
init() {
this.makeRegexp();
}
makeRegexp() {
let url = '/' + this.selfUrl.replace(/(^\/+|\/+$)/g, '');
url = url === '/' ? url : url + '/';
if (this.parent) {
url = this.parent.url + url.substr(1);
}
const reg = /:([^\/]+)/g;
while (true) {
const v = reg.exec(url);
if (!v) {
break;
}
this.names.push(v[1]);
this.regexpNames.push(new RegExp(':' + v[1] + '(/|$)'));
}
this.url = url;
this.regexp = new RegExp('^' + url.replace(/(:([^\/]+))/g, '([^\/]+)').replace(/\*\//g, '.+').replace(/\^\//g, '') + '?$');
}
goto(params: Props, search?: {}, replace = false) {
return this.router.changeUrl(this.toUrl(params, search), false, replace);
}
enter(enterData: RouteProps) {
if (this.onEnter) {
return this.onEnter(enterData).then(() => enterData);
}
return CancellablePromise.resolve(enterData);
}
leave(nextUrl: Url) {
if (this.onLeave) {
return this.onLeave(nextUrl);
}
return CancellablePromise.resolve();
}
getParams(url: Url) {
const m = this.regexp.exec(url.pathName);
if (m) {
const params = {} as Props;
for (let j = 0; j < this.names.length; j++) {
params[this.names[j]] = m[j + 1];
}
return params;
}
return {} as Props;
}
toUrl(params: Props, search?: {}) {
let url = this.url;
for (let i = 0; i < this.names.length; i++) {
const name = this.names[i];
const regexp = this.regexpNames[i];
url = url.replace(regexp, params[name]);
}
return new Url({url: url, search: search});
}
getParents() {
let route = this as RouteDef;
const parents: RouteDef[] = [];
while (route) {
parents.unshift(route);
route = route.parent;
}
return parents;
}
addChild(route: RouteDef) {
route.parent = this;
this.children.push(route);
return route;
}
check(url: Url) {
return this.regexp.test(url.pathName);
}
}
export class Router {
history: UrlHistory;
activePromise:CancellablePromise<{} | void> = CancellablePromise.resolve();
routeStack: RouteDef[] = [];
routeStackEnterData: RouteProps[] = [];
registeredRoutes: RouteDef[];
url = new Url({});
activeRoute: RouteDef;
onChangeCallbacks: (()=>void)[] = [];
publicPromise: Promise<{}> | null;
publicPromiseResolve: (()=>void) | null;
publicPromiseReject: (()=>void) | null;
constructor(route: PublicRoute, urlHistory: UrlHistory) {
this.history = urlHistory;
this.setRootRoute((route as {} as InnerRoute)._route);
console.log(this.registeredRoutes.map(r => r.url));
}
addRoute(route: RouteDef) {
this.registeredRoutes.push(route);
route.init();
route.router = this;
for (let i = 0; i < route.children.length; i++) {
this.addRoute(route.children[i]);
}
}
setRootRoute(route: RouteDef) {
this.registeredRoutes = [];
this.addRoute(route);
this.registeredRoutes.sort((a, b) => a.url < b.url ? -1 : 1);
}
changeUrl<T>(url: Url, urlHasChanged: boolean, replace: boolean) {
console.log('changeUrl', url, this.url, this.url.href, url.href, this.url.state, url.state);
this.activePromise.cancelAll();
if (!this.publicPromise) {
this.publicPromise = new Promise((resolve, reject) => {
this.publicPromiseResolve = resolve;
this.publicPromiseReject = reject;
});
}
if (this.url.href === url.href && this.url.state === url.state) {
console.log("skip");
// restore old url
this.history.replace(this.url);
this.activePromise = CancellablePromise.resolve().then(()=>this.resolvePublicPromise());
} else {
const route = this.findRouteByUrl(url);
if (route) {
const routeWithParents = route.getParents();
const pos = this.getChangedRoutesPos(routeWithParents);
const unMountRoutes = this.routeStack.slice(pos) as RouteDef[];
const toMountRoutes = routeWithParents.slice(pos) as RouteDef[];
console.log({
router: this,
routeStack: this.routeStack,
unMountRoutes,
toMountRoutes,
pos,
route,
routeWithParents,
url
});
let promise = CancellablePromise.resolve();
// leave
promise = unMountRoutes.reverse().reduce((promise, route) => promise.then(() => route.leave(url)), promise);
// enter
const enterData = {params: route.getParams(url), search: url.search, url};
const stackData: RouteProps[] = [];
promise = promise.then(() => enterData);
promise = toMountRoutes.reduce((promise, route, i) =>
promise.then(val => {
stackData[i] = val as RouteProps;
return route.enter(Object.create(val) as RouteProps);
}), promise);
// action
promise/*.catch(err => {console.error(err)})*/.then(() => {
console.log("SDASASFDASFA");
this.routeStack = this.routeStack.slice(0, pos).concat(toMountRoutes);
this.routeStackEnterData = stackData;
this.url.apply(url);
if (!urlHasChanged) {
if (replace) {
this.history.replace(url);
} else {
this.history.push(url);
}
}
this.resolvePublicPromise();
this.activeRoute = route;
this.callListeners();
});
this.activePromise = promise;
} else {
this.activePromise = CancellablePromise.resolve();
}
}
return this.publicPromise;
}
resolvePublicPromise() {
if (this.publicPromiseResolve) {
this.publicPromiseResolve();
}
this.publicPromise = null;
this.publicPromiseResolve = null;
this.publicPromiseReject = null;
}
getChangedRoutesPos(newRoutes: RouteDef[]) {
for (let i = 0; i < this.routeStack.length; i++) {
const route = this.routeStack[i];
const newRoute = newRoutes[i];
if (route !== newRoute) {
return i;
}
}
return this.routeStack.length;
}
findRouteByUrl(url: Url): RouteDef | undefined {
return this.registeredRoutes.filter(route => route.check(url)).pop();
}
onPopState = () => {
this.changeUrl(this.history.getCurrentUrl(), true, false);
};
init() {
this.history.addListener(this.onPopState);
this.onPopState();
}
addListener(onChange: ()=>void) {
this.onChangeCallbacks.push(onChange);
}
removeListener(onChange: ()=>void) {
const pos = this.onChangeCallbacks.indexOf(onChange);
if (pos > -1) {
this.onChangeCallbacks.splice(pos, 1);
}
}
private callListeners() {
for (let i = 0; i < this.onChangeCallbacks.length; i++) {
const callback = this.onChangeCallbacks[i];
callback();
}
}
}
import * as React from "react";
interface ReactViewProps {
history: UrlHistory;
indexRoute: PublicRoute;
}
export class RouterView extends React.Component<ReactViewProps, {}> {
router: Router;
update = () => {
this.forceUpdate();
};
componentWillMount() {
this.router = new Router(this.props.indexRoute, this.props.history);
this.router.init();
this.router.addListener(this.update);
}
render() {
const routes = this.router.routeStack;
let Component: React.ReactElement<{}> | null = null;
for (let i = routes.length - 1; i >= 0; i--) {
const route = routes[i];
const enterData = this.router.routeStackEnterData[i];
Component = React.createElement(route.component, enterData, Component!)
}
console.log('render', Component, routes);
return Component!;
}
}
abstract class UrlHistory {
abstract history: History;
abstract getCurrentUrl(): Url;
constructor() {
this.listen();
}
abstract listen(): void;
private onChangeCallbacks: (()=>void)[] = [];
protected onPopState = (event: PopStateEvent) => {
for (let i = 0; i < this.onChangeCallbacks.length; i++) {
const callback = this.onChangeCallbacks[i];
callback();
}
};
get length() {
return this.history.length;
}
push(url: Url) {
this.history.pushState(url.state, undefined, url.href);
}
replace(url: Url) {
this.history.replaceState(url.state, undefined, url.href);
}
back() {
this.history.back();
}
forward() {
this.history.forward();
}
addListener(onChange: ()=>void) {
this.onChangeCallbacks.push(onChange);
}
removeListener(onChange: ()=>void) {
const pos = this.onChangeCallbacks.indexOf(onChange);
if (pos > -1) {
this.onChangeCallbacks.splice(pos, 1);
}
}
}
export class BrowserHistory extends UrlHistory {
history = window.history;
getCurrentUrl() {
return new Url({url: window.location.pathname + window.location.search, state: history.state})
}
listen() {
window.addEventListener('popstate', this.onPopState);
}
}
interface IURL {
url?: string;
search?: Search;
searchParts?: Search;
state?: State;
}
type State = {} | null;
type Search = {};
export class Url {
pathName: string = '';
state: State;
search: Search;
href: string = '';
constructor(url: IURL) {
this.setParams(url);
}
private setHref() {
const searchParams = this.search;
let search = Object.keys(searchParams).filter(k => k && searchParams[k]).map(k => `${k}=${searchParams[k]}`).join('&');
this.href = this.pathName + (search ? ('?' + search) : '');
}
private parseHref(url: string) {
const m = url.match(/^(.*?)(\?(.*))?$/);
if (!m) {
throw new Error('Incorrect url: ' + url);
}
this.pathName = m[1];
if (m[3]) {
this.parseSearch(m[3]);
}
}
setParams(url: IURL) {
this.state = typeof url.state == 'undefined' ? null : url.state;
if (url.url) {
this.parseHref(url.url);
}
if (url.search) {
this.search = url.search;
} else if (url.searchParts) {
const parts = url.searchParts;
for (const prop in parts) {
const part = parts[prop];
if (part) {
this.search[prop] = part;
}
}
}
if (!this.search || typeof this.search !== 'object') {
this.search = {};
}
this.setHref();
return this;
}
private parseSearch(search: string) {
const params: Search = {};
const parts = search.split('&');
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (part) {
const [key, value] = part.split('=');
params[key] = value;
}
}
this.search = params;
}
apply(url: Url) {
this.pathName = url.pathName;
this.search = url.search;
this.state = url.state;
this.href = url.href;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment