crelte 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +41 -0
- package/dist/Crelte.d.ts +55 -0
- package/dist/Crelte.d.ts.map +1 -0
- package/dist/Crelte.js +106 -0
- package/dist/CrelteBase.d.ts +16 -0
- package/dist/CrelteBase.d.ts.map +1 -0
- package/dist/CrelteBase.js +1 -0
- package/dist/CrelteRouted.d.ts +50 -0
- package/dist/CrelteRouted.d.ts.map +1 -0
- package/dist/CrelteRouted.js +88 -0
- package/dist/blocks/Blocks.d.ts +35 -0
- package/dist/blocks/Blocks.d.ts.map +1 -0
- package/dist/blocks/Blocks.js +100 -0
- package/dist/blocks/Blocks.svelte +21 -0
- package/dist/blocks/Blocks.svelte.d.ts +24 -0
- package/dist/blocks/Blocks.svelte.d.ts.map +1 -0
- package/dist/blocks/index.d.ts +5 -0
- package/dist/blocks/index.d.ts.map +1 -0
- package/dist/blocks/index.js +3 -0
- package/dist/cookies/ClientCookies.d.ts +9 -0
- package/dist/cookies/ClientCookies.d.ts.map +1 -0
- package/dist/cookies/ClientCookies.js +22 -0
- package/dist/cookies/ServerCookies.d.ts +13 -0
- package/dist/cookies/ServerCookies.d.ts.map +1 -0
- package/dist/cookies/ServerCookies.js +31 -0
- package/dist/cookies/index.d.ts +20 -0
- package/dist/cookies/index.d.ts.map +1 -0
- package/dist/cookies/index.js +1 -0
- package/dist/cookies/utils.d.ts +12 -0
- package/dist/cookies/utils.d.ts.map +1 -0
- package/dist/cookies/utils.js +32 -0
- package/dist/graphql/GraphQl.d.ts +60 -0
- package/dist/graphql/GraphQl.d.ts.map +1 -0
- package/dist/graphql/GraphQl.js +197 -0
- package/dist/graphql/gql.test.d.ts +2 -0
- package/dist/graphql/gql.test.d.ts.map +1 -0
- package/dist/graphql/gql.test.js +80 -0
- package/dist/graphql/index.d.ts +3 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +2 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/init/client.d.ts +13 -0
- package/dist/init/client.d.ts.map +1 -0
- package/dist/init/client.js +129 -0
- package/dist/init/server.d.ts +38 -0
- package/dist/init/server.d.ts.map +1 -0
- package/dist/init/server.js +95 -0
- package/dist/init/shared.d.ts +29 -0
- package/dist/init/shared.d.ts.map +1 -0
- package/dist/init/shared.js +154 -0
- package/dist/loadData/Globals.d.ts +33 -0
- package/dist/loadData/Globals.d.ts.map +1 -0
- package/dist/loadData/Globals.js +119 -0
- package/dist/loadData/index.d.ts +25 -0
- package/dist/loadData/index.d.ts.map +1 -0
- package/dist/loadData/index.js +39 -0
- package/dist/plugins/Events.d.ts +11 -0
- package/dist/plugins/Events.d.ts.map +1 -0
- package/dist/plugins/Events.js +29 -0
- package/dist/plugins/Plugins.d.ts +12 -0
- package/dist/plugins/Plugins.d.ts.map +1 -0
- package/dist/plugins/Plugins.js +12 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +2 -0
- package/dist/routing/History.d.ts +22 -0
- package/dist/routing/History.d.ts.map +1 -0
- package/dist/routing/History.js +36 -0
- package/dist/routing/InnerRouter.d.ts +111 -0
- package/dist/routing/InnerRouter.d.ts.map +1 -0
- package/dist/routing/InnerRouter.js +397 -0
- package/dist/routing/PageLoader.d.ts +37 -0
- package/dist/routing/PageLoader.d.ts.map +1 -0
- package/dist/routing/PageLoader.js +72 -0
- package/dist/routing/Route.d.ts +82 -0
- package/dist/routing/Route.d.ts.map +1 -0
- package/dist/routing/Route.js +134 -0
- package/dist/routing/Router.d.ts +162 -0
- package/dist/routing/Router.d.ts.map +1 -0
- package/dist/routing/Router.js +333 -0
- package/dist/routing/Site.d.ts +47 -0
- package/dist/routing/Site.d.ts.map +1 -0
- package/dist/routing/Site.js +48 -0
- package/dist/routing/index.d.ts +5 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +4 -0
- package/dist/ssr/SsrCache.d.ts +12 -0
- package/dist/ssr/SsrCache.d.ts.map +1 -0
- package/dist/ssr/SsrCache.js +50 -0
- package/dist/ssr/SsrComponents.d.ts +7 -0
- package/dist/ssr/SsrComponents.d.ts.map +1 -0
- package/dist/ssr/SsrComponents.js +30 -0
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +3 -0
- package/package.json +79 -0
- package/src/Crelte.ts +135 -0
- package/src/CrelteBase.ts +24 -0
- package/src/CrelteRouted.ts +128 -0
- package/src/blocks/Blocks.svelte +68 -0
- package/src/blocks/Blocks.ts +155 -0
- package/src/blocks/index.ts +14 -0
- package/src/cookies/ClientCookies.ts +30 -0
- package/src/cookies/ServerCookies.ts +42 -0
- package/src/cookies/index.ts +24 -0
- package/src/cookies/utils.ts +53 -0
- package/src/graphql/GraphQl.ts +281 -0
- package/src/graphql/gql.test.ts +123 -0
- package/src/graphql/index.ts +8 -0
- package/src/index.ts +109 -0
- package/src/init/client.ts +190 -0
- package/src/init/server.ts +177 -0
- package/src/init/shared.ts +221 -0
- package/src/loadData/Globals.ts +150 -0
- package/src/loadData/index.ts +67 -0
- package/src/plugins/Events.ts +50 -0
- package/src/plugins/Plugins.ts +23 -0
- package/src/plugins/index.ts +5 -0
- package/src/routing/History.ts +52 -0
- package/src/routing/InnerRouter.ts +469 -0
- package/src/routing/PageLoader.ts +112 -0
- package/src/routing/Route.ts +184 -0
- package/src/routing/Router.ts +476 -0
- package/src/routing/Site.ts +65 -0
- package/src/routing/index.ts +5 -0
- package/src/ssr/SsrCache.ts +61 -0
- package/src/ssr/SsrComponents.ts +34 -0
- package/src/ssr/index.ts +4 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { trimSlashEnd } from './Router.js';
|
|
2
|
+
import Site from './Site.js';
|
|
3
|
+
|
|
4
|
+
export type RouteOpts = {
|
|
5
|
+
scrollY?: number;
|
|
6
|
+
index?: number;
|
|
7
|
+
origin?: RouteOrigin;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* RouteOrigin represents the origin of a route.
|
|
12
|
+
* This type is non-exhaustive and might expand in the future.
|
|
13
|
+
*
|
|
14
|
+
* - `'init'`: is set on the first page load
|
|
15
|
+
* - `'manual'`: is set when a route is triggered manually via `Router.open`
|
|
16
|
+
* - `'live-preview-init'`: is set on the first page load in live preview mode
|
|
17
|
+
* - `'click'`: is set when a route is triggered by a click event
|
|
18
|
+
* - `'pop'`: is set when a route is triggered by a popstate event (back/forward)
|
|
19
|
+
*/
|
|
20
|
+
export type RouteOrigin =
|
|
21
|
+
| 'init'
|
|
22
|
+
| 'live-preview-init'
|
|
23
|
+
| 'manual'
|
|
24
|
+
| 'click'
|
|
25
|
+
| 'pop';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A Route contains information about the current page for example the url and
|
|
29
|
+
* the site id
|
|
30
|
+
*
|
|
31
|
+
* ## Note
|
|
32
|
+
* Never update the route directly, clone it before
|
|
33
|
+
*/
|
|
34
|
+
export default class Route {
|
|
35
|
+
/**
|
|
36
|
+
* The url of the route
|
|
37
|
+
*/
|
|
38
|
+
url: URL;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The site of the route if it could be defined
|
|
42
|
+
*/
|
|
43
|
+
site: Site | null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The scroll position of the current route
|
|
47
|
+
*/
|
|
48
|
+
scrollY: number | null;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* the position in the browser history of this route
|
|
52
|
+
* this allows to find out if we can go back
|
|
53
|
+
*/
|
|
54
|
+
index: number;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The origin of this route
|
|
58
|
+
*
|
|
59
|
+
* Might pop, click or init (non exclusive)
|
|
60
|
+
*/
|
|
61
|
+
origin: RouteOrigin;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a new Route
|
|
65
|
+
*/
|
|
66
|
+
constructor(url: string | URL, site: Site | null, opts: RouteOpts = {}) {
|
|
67
|
+
this.url = new URL(url);
|
|
68
|
+
|
|
69
|
+
this.site = site;
|
|
70
|
+
this.scrollY = opts.scrollY ?? null;
|
|
71
|
+
this.index = opts.index ?? 0;
|
|
72
|
+
this.origin = opts.origin ?? 'manual';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns the uri of the route
|
|
77
|
+
*
|
|
78
|
+
* Never ends with a slash
|
|
79
|
+
*/
|
|
80
|
+
get uri(): string {
|
|
81
|
+
// todo check if this is correct
|
|
82
|
+
if (this.site) {
|
|
83
|
+
return trimSlashEnd(
|
|
84
|
+
this.url.pathname.substring(this.site.uri.length),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return trimSlashEnd(this.url.pathname);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// todo is this correct and do we wan't it?
|
|
92
|
+
/**
|
|
93
|
+
* Never ends with a slash
|
|
94
|
+
*/
|
|
95
|
+
get baseUrl(): string {
|
|
96
|
+
if (this.site) return trimSlashEnd(this.site.url.href);
|
|
97
|
+
|
|
98
|
+
return this.url.origin;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get search(): URLSearchParams {
|
|
102
|
+
return this.url.searchParams;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get hash(): string {
|
|
106
|
+
return this.url.hash;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
eq(route: Route) {
|
|
110
|
+
const searchEq = (a: URLSearchParams, b: URLSearchParams) => {
|
|
111
|
+
if (a.size !== b.size) return false;
|
|
112
|
+
|
|
113
|
+
a.sort();
|
|
114
|
+
b.sort();
|
|
115
|
+
|
|
116
|
+
const aEntries = Array.from(a.entries());
|
|
117
|
+
const bEntries = Array.from(b.entries());
|
|
118
|
+
|
|
119
|
+
return aEntries
|
|
120
|
+
.map((a, i) => [a, bEntries[i]])
|
|
121
|
+
.every(([[ak, av], [bk, bv]]) => ak === bk && av === bv);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
route &&
|
|
126
|
+
this.url.pathname === route.url.pathname &&
|
|
127
|
+
this.url.origin === route.url.origin &&
|
|
128
|
+
searchEq(this.search, route.search) &&
|
|
129
|
+
this.hash === route.hash
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Checks if there are previous routes which would allow it to go back
|
|
135
|
+
*/
|
|
136
|
+
canGoBack(): boolean {
|
|
137
|
+
return !!this.index;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Gets the search param
|
|
142
|
+
*/
|
|
143
|
+
getSearchParam(key: string): string | null {
|
|
144
|
+
return this.search.get(key);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Sets the search param or removes it if the value is null or undefined
|
|
149
|
+
*/
|
|
150
|
+
setSearchParam(key: string, value?: string | number | null) {
|
|
151
|
+
if (typeof value !== 'undefined' && value !== null) {
|
|
152
|
+
this.search.set(key, value as string);
|
|
153
|
+
} else {
|
|
154
|
+
this.search.delete(key);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
clone() {
|
|
159
|
+
return new Route(this.url.href, this.site, {
|
|
160
|
+
scrollY: this.scrollY ?? undefined,
|
|
161
|
+
index: this.index,
|
|
162
|
+
origin: this.origin,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// internal function
|
|
167
|
+
_fillFromState(state: any) {
|
|
168
|
+
if (typeof state?.route?.scrollY === 'number')
|
|
169
|
+
this.scrollY = state.route.scrollY;
|
|
170
|
+
|
|
171
|
+
if (typeof state?.route?.index === 'number')
|
|
172
|
+
this.index = state.route.index;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// internal function
|
|
176
|
+
_toState(): any {
|
|
177
|
+
return {
|
|
178
|
+
route: {
|
|
179
|
+
scrollY: this.scrollY,
|
|
180
|
+
index: this.index,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import Route from './Route.js';
|
|
2
|
+
import Site, { SiteFromGraphQl } from './Site.js';
|
|
3
|
+
import InnerRouter from './InnerRouter.js';
|
|
4
|
+
import PageLoader, { LoadFn, LoadResponse } from './PageLoader.js';
|
|
5
|
+
import { ServerHistory } from './History.js';
|
|
6
|
+
import { Readable, Writable } from 'crelte-std/stores';
|
|
7
|
+
import { Barrier, Listeners } from 'crelte-std/sync';
|
|
8
|
+
|
|
9
|
+
export type RouterOpts = {
|
|
10
|
+
preloadOnMouseOver?: boolean;
|
|
11
|
+
debugTiming?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const defaultRouterOpts = {
|
|
15
|
+
preloadOnMouseOver: false,
|
|
16
|
+
deubgTiming: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type LoadedMore = {
|
|
20
|
+
changeHistory: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* internal only
|
|
25
|
+
*/
|
|
26
|
+
type Internal = {
|
|
27
|
+
onLoaded: (
|
|
28
|
+
success: boolean,
|
|
29
|
+
route: Route,
|
|
30
|
+
site: Site,
|
|
31
|
+
// call ready once your ready to update the dom
|
|
32
|
+
// this makes sure we trigger a route and site update
|
|
33
|
+
// almost at the same moment and probably the same tick
|
|
34
|
+
// to make sure we don't have any flickering
|
|
35
|
+
ready: () => any,
|
|
36
|
+
) => void;
|
|
37
|
+
|
|
38
|
+
onLoad: LoadFn;
|
|
39
|
+
|
|
40
|
+
domReady: (route: Route) => void;
|
|
41
|
+
|
|
42
|
+
initClient: () => void;
|
|
43
|
+
|
|
44
|
+
initServer: (url: string, acceptLang?: string) => Promise<ServerInited>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type ServerInited = {
|
|
48
|
+
success: boolean;
|
|
49
|
+
// redirect to the route url
|
|
50
|
+
redirect: boolean;
|
|
51
|
+
route: Route;
|
|
52
|
+
site: Site;
|
|
53
|
+
props: any;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function trimSlashEnd(str: string) {
|
|
57
|
+
return str.endsWith('/') ? str.substring(0, str.length - 1) : str;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type OnNextRouteOpts = {
|
|
61
|
+
/**
|
|
62
|
+
* If you call delayRender you need to call ready or the render will never happen
|
|
63
|
+
*/
|
|
64
|
+
delayRender: () => DelayRender;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Make sure route and nextRoute are not the same object as _inner.route
|
|
68
|
+
export default class Router {
|
|
69
|
+
/**
|
|
70
|
+
* The current route
|
|
71
|
+
*/
|
|
72
|
+
private _route: Writable<Route>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The current site
|
|
76
|
+
*/
|
|
77
|
+
private _site: Writable<Site>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The next route which is currently being loaded
|
|
81
|
+
*/
|
|
82
|
+
private _nextRoute: Writable<Route>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The next site which is currently being loaded
|
|
86
|
+
*/
|
|
87
|
+
private _nextSite: Writable<Site>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The loading flag, specifies if a page is currently
|
|
91
|
+
* getting loaded
|
|
92
|
+
*/
|
|
93
|
+
private _loading: Writable<boolean>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The loading progress, the value is between 0 and 1
|
|
97
|
+
*/
|
|
98
|
+
private _loadingProgress: Writable<number>;
|
|
99
|
+
|
|
100
|
+
private _onRouteEv: Listeners<[Route, Site]>;
|
|
101
|
+
|
|
102
|
+
private _onNextRoute: Listeners<[Route, Site, OnNextRouteOpts]>;
|
|
103
|
+
private _renderBarrier: RenderBarrier | null;
|
|
104
|
+
|
|
105
|
+
// doc hidden
|
|
106
|
+
_internal: Internal;
|
|
107
|
+
|
|
108
|
+
private inner: InnerRouter;
|
|
109
|
+
private pageLoader: PageLoader<LoadedMore>;
|
|
110
|
+
|
|
111
|
+
constructor(sites: SiteFromGraphQl[], opts: RouterOpts = {}) {
|
|
112
|
+
opts = { ...defaultRouterOpts, ...opts };
|
|
113
|
+
|
|
114
|
+
this.inner = new InnerRouter(sites, {
|
|
115
|
+
preloadOnMouseOver: opts.preloadOnMouseOver!,
|
|
116
|
+
});
|
|
117
|
+
this.pageLoader = new PageLoader({
|
|
118
|
+
debugTiming: opts.debugTiming!,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// in the first onRoute call we will update this value
|
|
122
|
+
this._route = new Writable(null!);
|
|
123
|
+
this._site = new Writable(null!);
|
|
124
|
+
this._nextRoute = new Writable(null!);
|
|
125
|
+
this._nextSite = new Writable(null!);
|
|
126
|
+
this._loading = new Writable(false);
|
|
127
|
+
this._loadingProgress = new Writable(0);
|
|
128
|
+
|
|
129
|
+
this._onRouteEv = new Listeners();
|
|
130
|
+
|
|
131
|
+
this._onNextRoute = new Listeners();
|
|
132
|
+
this._renderBarrier = null;
|
|
133
|
+
|
|
134
|
+
this._internal = {
|
|
135
|
+
onLoaded: () => {},
|
|
136
|
+
onLoad: () => {},
|
|
137
|
+
domReady: route => this.inner.domReady(route),
|
|
138
|
+
initClient: () => this._initClient(),
|
|
139
|
+
initServer: (url, acceptLang) => this._initServer(url, acceptLang),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
this.inner.onRoute = (route, site, changeHistory) =>
|
|
143
|
+
this._onRoute(route, site, changeHistory);
|
|
144
|
+
this.inner.onPreload = (route, site) => this._onPreload(route, site);
|
|
145
|
+
|
|
146
|
+
this.pageLoader.onLoaded = (resp, route, site, more) =>
|
|
147
|
+
this._onLoaded(resp, route, site, more);
|
|
148
|
+
this.pageLoader.loadFn = (route, site, opts) =>
|
|
149
|
+
this._internal.onLoad(route, site, opts);
|
|
150
|
+
this.pageLoader.onProgress = (loading, progress) =>
|
|
151
|
+
this._onProgress(loading, progress);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The current route
|
|
156
|
+
*
|
|
157
|
+
* this is a svelte store
|
|
158
|
+
*/
|
|
159
|
+
get route(): Readable<Route> {
|
|
160
|
+
return this._route.readclone();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The current site
|
|
165
|
+
*/
|
|
166
|
+
get site(): Readable<Site> {
|
|
167
|
+
return this._site.readonly();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The next route which is currently being loaded
|
|
172
|
+
*/
|
|
173
|
+
get nextRoute(): Readable<Route> {
|
|
174
|
+
return this._nextRoute.readclone();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The next site which is currently being loaded
|
|
179
|
+
*/
|
|
180
|
+
get nextSite(): Readable<Site> {
|
|
181
|
+
return this._nextSite.readonly();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The sites which are available
|
|
186
|
+
*/
|
|
187
|
+
get sites(): Site[] {
|
|
188
|
+
return this.inner.sites;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The loading flag, specifies if a page is currently
|
|
193
|
+
* getting loaded
|
|
194
|
+
*/
|
|
195
|
+
get loading(): Readable<boolean> {
|
|
196
|
+
return this._loading.readonly();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The loading progress, the value is between 0 and 1
|
|
201
|
+
*/
|
|
202
|
+
get loadingProgress(): Readable<number> {
|
|
203
|
+
return this._loadingProgress.readonly();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Open a new route
|
|
208
|
+
*
|
|
209
|
+
* @param target the target to open can be an url or a route
|
|
210
|
+
* the url needs to start with http or with a / which will be considered as
|
|
211
|
+
* the site baseUrl
|
|
212
|
+
*/
|
|
213
|
+
open(target: string | URL | Route) {
|
|
214
|
+
this.inner.open(target);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* This pushes the state of the route without triggering an event
|
|
219
|
+
*
|
|
220
|
+
* You can use this when using pagination for example change the route object
|
|
221
|
+
* (search argument) and then call pushState
|
|
222
|
+
*
|
|
223
|
+
* @param route
|
|
224
|
+
*/
|
|
225
|
+
pushState(route: Route) {
|
|
226
|
+
this.pageLoader.discard();
|
|
227
|
+
this.inner.pushState(route);
|
|
228
|
+
this.setNewRoute(route);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* This replaces the state of the route without triggering an event
|
|
233
|
+
*
|
|
234
|
+
* @param route
|
|
235
|
+
*/
|
|
236
|
+
replaceState(route: Route) {
|
|
237
|
+
this.pageLoader.discard();
|
|
238
|
+
this.inner.replaceState(route);
|
|
239
|
+
this.setNewRoute(route);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Checks if there are previous routes which would allow it to go back
|
|
244
|
+
*/
|
|
245
|
+
canGoBack(): boolean {
|
|
246
|
+
return this.inner.route?.canGoBack() ?? false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Go back in the history
|
|
251
|
+
*/
|
|
252
|
+
back() {
|
|
253
|
+
window.history.back();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Preload a url
|
|
258
|
+
*/
|
|
259
|
+
preload(target: string | URL | Route) {
|
|
260
|
+
this.inner.preload(target);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Add a listener for the onRoute event
|
|
265
|
+
*
|
|
266
|
+
* This differs from router.route.subscribe in the way that
|
|
267
|
+
* it will only trigger if a new render / load will occur
|
|
268
|
+
*/
|
|
269
|
+
onRoute(fn: (route: Route, site: Site) => void): () => void {
|
|
270
|
+
return this._onRouteEv.add(fn);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
onNextRoute(
|
|
274
|
+
fn: (route: Route, site: Site, opts: OnNextRouteOpts) => void,
|
|
275
|
+
): () => void {
|
|
276
|
+
return this._onNextRoute.add(fn);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private setNewRoute(route: Route) {
|
|
280
|
+
this._route.setSilent(route);
|
|
281
|
+
this._nextRoute.setSilent(route);
|
|
282
|
+
|
|
283
|
+
if (route.site) {
|
|
284
|
+
this._site.setSilent(route.site);
|
|
285
|
+
this._nextSite.setSilent(route.site);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this._nextRoute.notify();
|
|
289
|
+
this._route.notify();
|
|
290
|
+
|
|
291
|
+
if (route.site) {
|
|
292
|
+
this._nextSite.notify();
|
|
293
|
+
this._site.notify();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private async _initClient() {
|
|
298
|
+
this.inner.initClient();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private async _initServer(
|
|
302
|
+
url: string,
|
|
303
|
+
acceptLang?: string,
|
|
304
|
+
): Promise<ServerInited> {
|
|
305
|
+
this.inner.initServer();
|
|
306
|
+
|
|
307
|
+
const prom: Promise<ServerInited> = new Promise(resolve => {
|
|
308
|
+
this._internal.onLoaded = (success, route, site, ready) => {
|
|
309
|
+
const props = ready();
|
|
310
|
+
this._internal.onLoaded = () => {};
|
|
311
|
+
|
|
312
|
+
resolve({
|
|
313
|
+
success,
|
|
314
|
+
redirect: false,
|
|
315
|
+
route,
|
|
316
|
+
site,
|
|
317
|
+
props,
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const route = this.inner.targetToRoute(url);
|
|
323
|
+
route.origin = 'init';
|
|
324
|
+
|
|
325
|
+
// let's see if the url matches any route and site
|
|
326
|
+
// if not let's redirect to the site which matches the acceptLang
|
|
327
|
+
if (!route.site) {
|
|
328
|
+
const site = this.inner.siteByAcceptLang(acceptLang);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
redirect: true,
|
|
333
|
+
route: new Route(site.url, site),
|
|
334
|
+
site,
|
|
335
|
+
props: {},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const hist = this.inner.history as ServerHistory;
|
|
340
|
+
if (hist.url) {
|
|
341
|
+
const nRoute = new Route(hist.url, null);
|
|
342
|
+
if (!route.eq(nRoute)) {
|
|
343
|
+
return {
|
|
344
|
+
success: true,
|
|
345
|
+
redirect: true,
|
|
346
|
+
route: nRoute,
|
|
347
|
+
site: route.site!,
|
|
348
|
+
props: {},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.inner.setRoute(route);
|
|
354
|
+
|
|
355
|
+
return await prom;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private _onRoute(route: Route, site: Site, changeHistory: () => void) {
|
|
359
|
+
this._nextRoute.setSilent(route);
|
|
360
|
+
const siteChanged = this.nextSite.get()?.id !== site.id;
|
|
361
|
+
this._nextSite.setSilent(site);
|
|
362
|
+
this._nextRoute.notify();
|
|
363
|
+
if (siteChanged) this._nextSite.notify();
|
|
364
|
+
|
|
365
|
+
if (this._renderBarrier) {
|
|
366
|
+
const barr = this._renderBarrier;
|
|
367
|
+
this._renderBarrier = null;
|
|
368
|
+
// make sure nobody waits forevery
|
|
369
|
+
barr.cancel();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const barrier = new RenderBarrier();
|
|
373
|
+
this._renderBarrier = barrier;
|
|
374
|
+
|
|
375
|
+
this._onNextRoute.trigger(route.clone(), site, {
|
|
376
|
+
delayRender: () => barrier.add(),
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// route prepared
|
|
380
|
+
this.pageLoader.load(route.clone(), site, { changeHistory });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private _onPreload(route: Route, site: Site) {
|
|
384
|
+
this.pageLoader.preload(route, site);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private async _onLoaded(
|
|
388
|
+
resp: LoadResponse,
|
|
389
|
+
route: Route,
|
|
390
|
+
site: Site,
|
|
391
|
+
more: LoadedMore,
|
|
392
|
+
) {
|
|
393
|
+
// we need to wait on the renderBarrier
|
|
394
|
+
const renderBarrier = this._renderBarrier;
|
|
395
|
+
if (renderBarrier) {
|
|
396
|
+
// check if the render was cancelled
|
|
397
|
+
if (await renderBarrier.ready()) return;
|
|
398
|
+
this._renderBarrier = null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// when the data is loaded let's update the route of the inner
|
|
402
|
+
// this is will only happen if no other route has been requested
|
|
403
|
+
// in the meantime
|
|
404
|
+
more.changeHistory();
|
|
405
|
+
|
|
406
|
+
const updateRoute = () => {
|
|
407
|
+
this._route.setSilent(route);
|
|
408
|
+
const siteChanged = this.site.get()?.id !== site.id;
|
|
409
|
+
this._site.setSilent(site);
|
|
410
|
+
this._route.notify();
|
|
411
|
+
if (siteChanged) this._site.notify();
|
|
412
|
+
|
|
413
|
+
this._onRouteEv.trigger(route.clone(), site);
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
this._internal.onLoaded(resp.success, route, site, () => {
|
|
417
|
+
updateRoute();
|
|
418
|
+
return resp.data;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private _onProgress(loading: boolean, progress?: number): void {
|
|
423
|
+
if (this._loading.get() !== loading) this._loading.set(loading);
|
|
424
|
+
|
|
425
|
+
if (typeof progress === 'number') this._loadingProgress.set(progress);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
class RenderBarrier {
|
|
430
|
+
inner: Barrier<unknown>;
|
|
431
|
+
cancelled: boolean;
|
|
432
|
+
root: DelayRender;
|
|
433
|
+
|
|
434
|
+
constructor() {
|
|
435
|
+
this.inner = new Barrier();
|
|
436
|
+
this.cancelled = false;
|
|
437
|
+
this.root = this.add();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
add(): DelayRender {
|
|
441
|
+
const action = this.inner.add();
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
ready: async () => {
|
|
445
|
+
await action.ready(null);
|
|
446
|
+
return this.cancelled;
|
|
447
|
+
},
|
|
448
|
+
remove: () => action.remove(),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
cancel() {
|
|
453
|
+
this.cancelled = true;
|
|
454
|
+
this.root.remove();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// returns if the render was cancelled
|
|
458
|
+
ready(): Promise<boolean> {
|
|
459
|
+
return this.root.ready();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export type DelayRender = {
|
|
464
|
+
/**
|
|
465
|
+
* Call this when you're ready for the render to happen
|
|
466
|
+
* the promise will resolve when the render is done or was cancelled
|
|
467
|
+
*
|
|
468
|
+
* @returns if the render was cancelled
|
|
469
|
+
*/
|
|
470
|
+
ready: () => Promise<boolean>;
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* If youre not interested in the render anymore
|
|
474
|
+
*/
|
|
475
|
+
remove: () => void;
|
|
476
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { trimSlashEnd } from './Router.js';
|
|
2
|
+
|
|
3
|
+
export type SiteFromGraphQl = {
|
|
4
|
+
id: number;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
language: string;
|
|
7
|
+
name: string | null;
|
|
8
|
+
handle: string;
|
|
9
|
+
primary: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Craft Site
|
|
14
|
+
*/
|
|
15
|
+
export default class Site {
|
|
16
|
+
/**
|
|
17
|
+
* The id of the site
|
|
18
|
+
*/
|
|
19
|
+
id: number;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The base url of the site
|
|
23
|
+
*/
|
|
24
|
+
url: URL;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The language of the site
|
|
28
|
+
*
|
|
29
|
+
* ex: de-CH
|
|
30
|
+
*/
|
|
31
|
+
language: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The name of the site
|
|
35
|
+
*/
|
|
36
|
+
name: string | null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The handle of the site
|
|
40
|
+
*/
|
|
41
|
+
handle: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Is this the primary site
|
|
45
|
+
*/
|
|
46
|
+
primary: boolean;
|
|
47
|
+
|
|
48
|
+
constructor(obj: SiteFromGraphQl) {
|
|
49
|
+
this.id = obj.id;
|
|
50
|
+
this.url = new URL(obj.baseUrl);
|
|
51
|
+
this.language = obj.language;
|
|
52
|
+
this.name = obj.name;
|
|
53
|
+
this.handle = obj.handle;
|
|
54
|
+
this.primary = obj.primary;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns the uri of the site
|
|
59
|
+
*
|
|
60
|
+
* Never ends with a slash
|
|
61
|
+
*/
|
|
62
|
+
get uri(): string {
|
|
63
|
+
return trimSlashEnd(this.url.pathname);
|
|
64
|
+
}
|
|
65
|
+
}
|