crelte 0.4.8 → 0.5.0-alpha.2
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/dist/Crelte.d.ts +7 -6
- package/dist/Crelte.d.ts.map +1 -1
- package/dist/Crelte.js +5 -13
- package/dist/CrelteRequest.d.ts +9 -0
- package/dist/CrelteRequest.d.ts.map +1 -1
- package/dist/CrelteRequest.js +16 -2
- package/dist/blocks/Blocks.svelte +2 -2
- package/dist/blocks/Blocks.svelte.d.ts +3 -19
- package/dist/blocks/Blocks.svelte.d.ts.map +1 -1
- package/dist/cookies/ClientCookies.d.ts +0 -1
- package/dist/cookies/ClientCookies.d.ts.map +1 -1
- package/dist/cookies/ClientCookies.js +0 -1
- package/dist/cookies/ServerCookies.d.ts +1 -2
- package/dist/cookies/ServerCookies.d.ts.map +1 -1
- package/dist/cookies/ServerCookies.js +2 -6
- package/dist/cookies/index.d.ts +0 -2
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/graphql/GraphQl.d.ts +2 -2
- package/dist/graphql/GraphQl.d.ts.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/init/InternalApp.d.ts +30 -0
- package/dist/init/InternalApp.d.ts.map +1 -0
- package/dist/init/InternalApp.js +71 -0
- package/dist/init/client.d.ts +0 -5
- package/dist/init/client.d.ts.map +1 -1
- package/dist/init/client.js +88 -75
- package/dist/init/crelte-vite-plugin.d.ts +5 -0
- package/dist/init/server.d.ts +0 -5
- package/dist/init/server.d.ts.map +1 -1
- package/dist/init/server.js +49 -20
- package/dist/init/shared.d.ts +7 -18
- package/dist/init/shared.d.ts.map +1 -1
- package/dist/init/shared.js +97 -154
- package/dist/init/svelteComponents.d.ts +3 -0
- package/dist/init/svelteComponents.d.ts.map +1 -0
- package/dist/init/svelteComponents.js +7 -0
- package/dist/loadData/Globals.d.ts +40 -33
- package/dist/loadData/Globals.d.ts.map +1 -1
- package/dist/loadData/Globals.js +99 -88
- package/dist/loadData/index.d.ts +3 -2
- package/dist/loadData/index.d.ts.map +1 -1
- package/dist/loadData/index.js +2 -0
- package/dist/plugins/Events.d.ts +11 -13
- package/dist/plugins/Events.d.ts.map +1 -1
- package/dist/plugins/Events.js +10 -3
- package/dist/routing/BaseRoute.d.ts +255 -0
- package/dist/routing/BaseRoute.d.ts.map +1 -0
- package/dist/routing/BaseRoute.js +349 -0
- package/dist/routing/BaseRouter.d.ts +210 -0
- package/dist/routing/BaseRouter.d.ts.map +1 -0
- package/dist/routing/BaseRouter.js +444 -0
- package/dist/routing/ClientRouter.d.ts +32 -0
- package/dist/routing/ClientRouter.d.ts.map +1 -0
- package/dist/routing/ClientRouter.js +259 -0
- package/dist/routing/LoadRunner.d.ts +39 -0
- package/dist/routing/LoadRunner.d.ts.map +1 -0
- package/dist/routing/{PageLoader.js → LoadRunner.js} +32 -20
- package/dist/routing/Request.d.ts +35 -3
- package/dist/routing/Request.d.ts.map +1 -1
- package/dist/routing/Request.js +64 -5
- package/dist/routing/Route.d.ts +24 -223
- package/dist/routing/Route.d.ts.map +1 -1
- package/dist/routing/Route.js +26 -315
- package/dist/routing/Router.d.ts +49 -73
- package/dist/routing/Router.d.ts.map +1 -1
- package/dist/routing/Router.js +85 -251
- package/dist/routing/ServerRouter.d.ts +23 -0
- package/dist/routing/ServerRouter.d.ts.map +1 -0
- package/dist/routing/ServerRouter.js +57 -0
- package/dist/routing/utils.d.ts +5 -0
- package/dist/routing/utils.d.ts.map +1 -1
- package/dist/routing/utils.js +39 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -0
- package/package.json +7 -6
- package/src/Crelte.ts +12 -18
- package/src/CrelteRequest.ts +21 -2
- package/src/cookies/ClientCookies.ts +0 -2
- package/src/cookies/ServerCookies.ts +2 -7
- package/src/cookies/index.ts +0 -3
- package/src/graphql/GraphQl.ts +2 -1
- package/src/index.ts +17 -9
- package/src/init/InternalApp.ts +134 -0
- package/src/init/client.ts +104 -93
- package/src/init/crelte-vite-plugin.d.ts +5 -0
- package/src/init/server.ts +67 -35
- package/src/init/shared.ts +107 -227
- package/src/init/svelteComponents.ts +12 -0
- package/src/loadData/Globals.ts +121 -102
- package/src/loadData/index.ts +3 -2
- package/src/plugins/Events.ts +40 -42
- package/src/routing/BaseRoute.ts +422 -0
- package/src/routing/BaseRouter.ts +528 -0
- package/src/routing/ClientRouter.ts +329 -0
- package/src/routing/{PageLoader.ts → LoadRunner.ts} +43 -30
- package/src/routing/Request.ts +97 -12
- package/src/routing/Route.ts +56 -376
- package/src/routing/Router.ts +100 -359
- package/src/routing/ServerRouter.ts +78 -0
- package/src/routing/utils.ts +53 -0
- package/src/utils.ts +4 -0
- package/dist/routing/InnerRouter.d.ts +0 -113
- package/dist/routing/InnerRouter.d.ts.map +0 -1
- package/dist/routing/InnerRouter.js +0 -417
- package/dist/routing/PageLoader.d.ts +0 -36
- package/dist/routing/PageLoader.d.ts.map +0 -1
- package/src/routing/InnerRouter.ts +0 -498
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import CrelteRequest from '../CrelteRequest.js';
|
|
2
|
+
import BaseRouter, { BaseRouterOptions } from './BaseRouter.js';
|
|
3
|
+
import { Request, RequestOptions } from './index.js';
|
|
4
|
+
import Route from './Route.js';
|
|
5
|
+
import { SiteFromGraphQl } from './Site.js';
|
|
6
|
+
|
|
7
|
+
export type ClientRouterOptions = {
|
|
8
|
+
preloadOnMouseOver: boolean;
|
|
9
|
+
} & BaseRouterOptions;
|
|
10
|
+
|
|
11
|
+
export default class ClientRouter extends BaseRouter {
|
|
12
|
+
private scrollDebounceTimeout: any | null;
|
|
13
|
+
private preloadOnMouseOver: boolean;
|
|
14
|
+
|
|
15
|
+
onError: (e: any) => void;
|
|
16
|
+
|
|
17
|
+
constructor(sites: SiteFromGraphQl[], opts: ClientRouterOptions) {
|
|
18
|
+
super(sites, opts);
|
|
19
|
+
|
|
20
|
+
this.scrollDebounceTimeout = null;
|
|
21
|
+
this.preloadOnMouseOver = opts.preloadOnMouseOver;
|
|
22
|
+
this.onError = () => {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ## Throws
|
|
27
|
+
*/
|
|
28
|
+
async init() {
|
|
29
|
+
this.listen();
|
|
30
|
+
|
|
31
|
+
// let's first try to load from the state
|
|
32
|
+
const req = this.targetToRequest(window.location.href);
|
|
33
|
+
req._fillFromState(window.history.state);
|
|
34
|
+
|
|
35
|
+
req.origin = 'init';
|
|
36
|
+
window.history.scrollRestoration = 'manual';
|
|
37
|
+
|
|
38
|
+
return await this.handleRequest(req, () => {});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Do not call this with origin push or replace
|
|
43
|
+
*/
|
|
44
|
+
async openRequest(req: Request): Promise<Route | void> {
|
|
45
|
+
if (['push', 'replace'].includes(req.origin)) {
|
|
46
|
+
throw new Error('Do not use open with push or replace');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const current = this.route.get();
|
|
50
|
+
// store scrollY
|
|
51
|
+
if (current) {
|
|
52
|
+
// if the scrollY would still be updated we clear the timeout
|
|
53
|
+
// since we should have the latest scrollY
|
|
54
|
+
if (this.scrollDebounceTimeout) {
|
|
55
|
+
clearTimeout(this.scrollDebounceTimeout);
|
|
56
|
+
this.scrollDebounceTimeout = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// store the scroll position
|
|
60
|
+
const scrollY = window.scrollY;
|
|
61
|
+
if (typeof scrollY === 'number') {
|
|
62
|
+
current.scrollY = scrollY;
|
|
63
|
+
window.history.replaceState(current._toState(), '');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// if the domain of the current site is different than the domain of the
|
|
68
|
+
// new site we need to do a window.location.href call
|
|
69
|
+
if (
|
|
70
|
+
(current && current.url.origin !== req.url.origin) ||
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
import.meta.env.SSR
|
|
73
|
+
) {
|
|
74
|
+
window.location.href = req.url.href;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
req.index = (current?.index ?? 0) + 1;
|
|
79
|
+
return await this.handleRequestAndError(req, route => {
|
|
80
|
+
const url = route.url;
|
|
81
|
+
window.history.pushState(
|
|
82
|
+
route._toState(),
|
|
83
|
+
'',
|
|
84
|
+
url.pathname + url.search + url.hash,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async pushRequest(req: Request, _opts: RequestOptions = {}) {
|
|
90
|
+
const url = req.url;
|
|
91
|
+
// todo a push should also store the previous scrollY
|
|
92
|
+
|
|
93
|
+
let nReq = req;
|
|
94
|
+
if (req.scrollY === null) {
|
|
95
|
+
// if there is no scrollY stored we store the current scrollY
|
|
96
|
+
// since a push does not cause a scroll top
|
|
97
|
+
// todo: probably should refactor something probably
|
|
98
|
+
// should not be here
|
|
99
|
+
nReq = req.clone();
|
|
100
|
+
nReq.scrollY = window.scrollY;
|
|
101
|
+
|
|
102
|
+
// todo this does not work? nReq is never used
|
|
103
|
+
// why not assign it without a clone?
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return await this.handleRequest(req, route => {
|
|
107
|
+
window.history.pushState(
|
|
108
|
+
route._toState(),
|
|
109
|
+
url.pathname + url.search + url.hash,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async replaceRequest(req: Request, _opts: RequestOptions = {}) {
|
|
115
|
+
const url = req.url;
|
|
116
|
+
|
|
117
|
+
let nReq = req;
|
|
118
|
+
if (req.scrollY === null) {
|
|
119
|
+
// if there is no scrollY stored we store the current scrollY
|
|
120
|
+
// since a replace does not cause a scrollTo and we wan't
|
|
121
|
+
// history back to work as intended
|
|
122
|
+
// todo: probably should refactor something probably
|
|
123
|
+
// should not be here
|
|
124
|
+
nReq = req.clone();
|
|
125
|
+
nReq.scrollY = window.scrollY;
|
|
126
|
+
|
|
127
|
+
// todo this does not work? nReq is never used
|
|
128
|
+
// why not assign it without a clone?
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
return await this.handleRequest(req, () => {
|
|
133
|
+
window.history.replaceState(
|
|
134
|
+
req._toState(),
|
|
135
|
+
url.pathname + url.search + url.hash,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.warn('replacing route failed', e);
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* This returns a route if it was handled else if an error occured
|
|
146
|
+
* or the request was cancelled returns void
|
|
147
|
+
*/
|
|
148
|
+
async handleRequestAndError(
|
|
149
|
+
req: Request,
|
|
150
|
+
updateHistory: (route: Route) => void,
|
|
151
|
+
): Promise<Route | void> {
|
|
152
|
+
try {
|
|
153
|
+
return await this.handleRequest(req, updateHistory);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
console.error('request failed', e);
|
|
156
|
+
this.onError(e);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
listen() {
|
|
161
|
+
window.addEventListener('click', async e => {
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
const link = e.target.closest('a');
|
|
164
|
+
const openInNewTab = e.metaKey || e.ctrlKey || e.shiftKey;
|
|
165
|
+
const saveLink = e.altKey;
|
|
166
|
+
if (!link || !link.href || openInNewTab || saveLink) return;
|
|
167
|
+
if (link.target.toLowerCase() === '_blank') return;
|
|
168
|
+
if (!link.href.startsWith('http')) return;
|
|
169
|
+
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
|
|
172
|
+
const req = this.targetToRequest(link.href, { origin: 'click' });
|
|
173
|
+
const currRoute = this.route.get();
|
|
174
|
+
const routeEq =
|
|
175
|
+
currRoute && currRoute.eqUrl(req) && currRoute.eqSearch(req);
|
|
176
|
+
|
|
177
|
+
// this means the route is the same maybe with a different hash
|
|
178
|
+
// so it is not necessary to load the data again
|
|
179
|
+
if (routeEq) {
|
|
180
|
+
req.disableLoadData = true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.openRequest(req);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (this.preloadOnMouseOver) {
|
|
187
|
+
let currentMouseOver: any = null;
|
|
188
|
+
window.addEventListener('mouseover', e => {
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
const link = e.target.closest('a');
|
|
191
|
+
|
|
192
|
+
if (currentMouseOver && link === currentMouseOver) return;
|
|
193
|
+
if (link && link.target.toLowerCase() === '_blank') return;
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
link &&
|
|
197
|
+
!link.hasAttribute('data-no-preload') &&
|
|
198
|
+
link.href
|
|
199
|
+
) {
|
|
200
|
+
this.preload(link.href);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
currentMouseOver = link;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// store the scrollY position every 200ms
|
|
208
|
+
// we can't do this at the time of the open call since the pop event
|
|
209
|
+
// has already changed to a new history state so we can't update our
|
|
210
|
+
// current/previous state
|
|
211
|
+
// eslint-disable-next-line no-constant-condition
|
|
212
|
+
if (true) {
|
|
213
|
+
window.addEventListener('scroll', () => {
|
|
214
|
+
const current = this.route.get();
|
|
215
|
+
if (!current) return;
|
|
216
|
+
|
|
217
|
+
// store the scroll position
|
|
218
|
+
current.scrollY = window.scrollY;
|
|
219
|
+
|
|
220
|
+
if (this.scrollDebounceTimeout) return;
|
|
221
|
+
|
|
222
|
+
// this might cause `Attempt to use history.replaceState() more than
|
|
223
|
+
// 100 times per 30 seconds` in safari
|
|
224
|
+
// since we wait a moment we should almost ever be fine
|
|
225
|
+
this.scrollDebounceTimeout = setTimeout(() => {
|
|
226
|
+
const routerRoute = this.route.get();
|
|
227
|
+
if (!routerRoute || !current.eq(routerRoute)) return;
|
|
228
|
+
|
|
229
|
+
// use the latest state
|
|
230
|
+
window.history.replaceState(routerRoute._toState(), '');
|
|
231
|
+
|
|
232
|
+
if (current.inLivePreview()) {
|
|
233
|
+
sessionStorage.setItem(
|
|
234
|
+
'live-preview-scroll',
|
|
235
|
+
// use the latest scrollY
|
|
236
|
+
routerRoute.scrollY + '',
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.scrollDebounceTimeout = null;
|
|
241
|
+
}, 280);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
window.addEventListener('popstate', async e => {
|
|
246
|
+
if (!e.state?.route) return;
|
|
247
|
+
|
|
248
|
+
const req = this.targetToRequest(window.location.href);
|
|
249
|
+
req._fillFromState(e.state);
|
|
250
|
+
req.origin = 'pop';
|
|
251
|
+
|
|
252
|
+
// todo handle errors
|
|
253
|
+
this.handleRequest(req, () => {});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
updateScroll(cr: CrelteRequest, _route: Route): void {
|
|
258
|
+
const req = cr.req;
|
|
259
|
+
|
|
260
|
+
if (req.disableScroll) return;
|
|
261
|
+
|
|
262
|
+
// scroll to target
|
|
263
|
+
let scrollTo:
|
|
264
|
+
| { top: number; behavior: ScrollBehavior }
|
|
265
|
+
| { intoView: HTMLElement; behavior: ScrollBehavior }
|
|
266
|
+
| null = null;
|
|
267
|
+
|
|
268
|
+
// if the route is a live preview init and we have a scrollY stored
|
|
269
|
+
// scroll to that
|
|
270
|
+
if (req.inLivePreview()) {
|
|
271
|
+
const scrollY = sessionStorage.getItem('live-preview-scroll');
|
|
272
|
+
if (scrollY) {
|
|
273
|
+
scrollTo = {
|
|
274
|
+
top: parseInt(scrollY),
|
|
275
|
+
behavior: 'instant',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// if we have a hash and the route was not visited
|
|
279
|
+
} else if (
|
|
280
|
+
req.hash &&
|
|
281
|
+
((req.origin === 'init' && typeof req.scrollY !== 'number') ||
|
|
282
|
+
req.origin === 'click')
|
|
283
|
+
) {
|
|
284
|
+
const el = document.getElementById(req.hash.substring(1));
|
|
285
|
+
if (el) {
|
|
286
|
+
scrollTo = {
|
|
287
|
+
intoView: el,
|
|
288
|
+
behavior: 'smooth',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// restore scroll position
|
|
294
|
+
if (
|
|
295
|
+
!scrollTo &&
|
|
296
|
+
req.origin !== 'click' &&
|
|
297
|
+
typeof req.scrollY === 'number'
|
|
298
|
+
) {
|
|
299
|
+
scrollTo = {
|
|
300
|
+
top: req.scrollY,
|
|
301
|
+
behavior: 'instant',
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// make sure push and replace don't cause a scroll if it is not intended
|
|
306
|
+
if (!scrollTo && (req.origin === 'push' || req.origin === 'replace'))
|
|
307
|
+
return;
|
|
308
|
+
|
|
309
|
+
// scroll to the top if nothing else matches
|
|
310
|
+
if (!scrollTo) {
|
|
311
|
+
scrollTo = {
|
|
312
|
+
top: 0,
|
|
313
|
+
behavior: 'instant',
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if ('top' in scrollTo) {
|
|
318
|
+
window.scrollTo({
|
|
319
|
+
top: scrollTo.top,
|
|
320
|
+
behavior: scrollTo.behavior,
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
scrollTo.intoView.scrollIntoView({
|
|
324
|
+
behavior: scrollTo.behavior,
|
|
325
|
+
block: 'start',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type CrelteRequest } from '../index.js';
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type LoadRunnerOptions = {
|
|
4
4
|
debugTiming: boolean;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
export type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export type LoadFn = (req: Request, opts: LoadOptions) => Promise<any> | any;
|
|
7
|
+
export type LoadFn = (
|
|
8
|
+
cr: CrelteRequest,
|
|
9
|
+
opts: LoadOptions,
|
|
10
|
+
) => Promise<void> | void;
|
|
13
11
|
|
|
14
12
|
export type LoadOptions = {
|
|
15
13
|
setProgress: (num: number) => void;
|
|
14
|
+
isCanceled: () => boolean;
|
|
16
15
|
};
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* The PageLoader which is responsible for loading page Data
|
|
20
19
|
*/
|
|
21
|
-
export default class
|
|
20
|
+
export default class LoadRunner {
|
|
22
21
|
private debugTiming: boolean;
|
|
23
22
|
private preloadedUrls: Set<string>;
|
|
24
23
|
|
|
25
24
|
private loadingVersion: number;
|
|
26
25
|
|
|
27
|
-
onLoaded: (resp: LoadResponse, req: Request, more: More) => void;
|
|
28
26
|
onProgress: (loading: boolean, progress?: number) => void;
|
|
29
27
|
loadFn: LoadFn;
|
|
30
28
|
|
|
@@ -33,13 +31,12 @@ export default class PageLoader<More> {
|
|
|
33
31
|
*
|
|
34
32
|
* @param {Object} options `{debugTiming}`
|
|
35
33
|
*/
|
|
36
|
-
constructor(options:
|
|
34
|
+
constructor(options: LoadRunnerOptions) {
|
|
37
35
|
this.debugTiming = options.debugTiming;
|
|
38
36
|
this.preloadedUrls = new Set();
|
|
39
37
|
|
|
40
38
|
this.loadingVersion = 0;
|
|
41
39
|
|
|
42
|
-
this.onLoaded = () => null!;
|
|
43
40
|
this.onProgress = () => null!;
|
|
44
41
|
this.loadFn = () => null!;
|
|
45
42
|
}
|
|
@@ -52,49 +49,65 @@ export default class PageLoader<More> {
|
|
|
52
49
|
this.onProgress(false);
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
|
|
52
|
+
/**
|
|
53
|
+
* @returns true if the load was completed
|
|
54
|
+
*
|
|
55
|
+
* ## Throws
|
|
56
|
+
* if there was an error but not if the request
|
|
57
|
+
* was cancelled before
|
|
58
|
+
*/
|
|
59
|
+
async load(req: CrelteRequest): Promise<boolean> {
|
|
56
60
|
this.onProgress(true);
|
|
57
61
|
|
|
58
62
|
const version = ++this.loadingVersion;
|
|
59
63
|
const startTime = this.debugTiming ? Date.now() : null;
|
|
60
64
|
|
|
65
|
+
const isCanceled = () => this.loadingVersion !== version;
|
|
66
|
+
|
|
61
67
|
const setProgress = (num: number) => {
|
|
62
|
-
if (
|
|
68
|
+
if (isCanceled()) return;
|
|
63
69
|
|
|
64
70
|
this.onProgress(true, num);
|
|
65
71
|
};
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
// a function which should return the response
|
|
74
|
+
let resp: () => void;
|
|
68
75
|
try {
|
|
69
|
-
|
|
70
|
-
resp
|
|
76
|
+
const data = await this.loadFn(req, { isCanceled, setProgress });
|
|
77
|
+
resp = () => data;
|
|
71
78
|
} catch (e) {
|
|
72
|
-
resp
|
|
73
|
-
|
|
79
|
+
resp = () => {
|
|
80
|
+
throw e;
|
|
81
|
+
};
|
|
74
82
|
}
|
|
75
83
|
|
|
84
|
+
if (isCanceled()) {
|
|
85
|
+
console.log('route changed quickly, ignoring response');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.onProgress(false);
|
|
90
|
+
|
|
76
91
|
if (startTime)
|
|
77
92
|
console.log('page load took ' + (Date.now() - startTime) + 'ms');
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
if (this.loadingVersion !== version)
|
|
81
|
-
return console.log('route changed quickly, ignoring response');
|
|
82
|
-
|
|
83
|
-
this.onProgress(false);
|
|
84
|
-
this.onLoaded(resp, req, more);
|
|
94
|
+
return (resp(), true);
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
// you don't need to wait on this call
|
|
88
|
-
async preload(
|
|
89
|
-
const url = req.url.origin + req.url.pathname;
|
|
98
|
+
async preload(cr: CrelteRequest) {
|
|
99
|
+
const url = cr.req.url.origin + cr.req.url.pathname;
|
|
90
100
|
if (this.preloadedUrls.has(url)) return;
|
|
91
101
|
|
|
92
102
|
this.preloadedUrls.add(url);
|
|
93
103
|
|
|
94
104
|
try {
|
|
95
|
-
await this.loadFn(
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
await this.loadFn(cr, {
|
|
106
|
+
isCanceled: () => false,
|
|
107
|
+
setProgress: () => null,
|
|
108
|
+
});
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.log('preload failed', e);
|
|
98
111
|
// retry at another time
|
|
99
112
|
this.preloadedUrls.delete(url);
|
|
100
113
|
}
|
package/src/routing/Request.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { Barrier } from 'crelte-std/sync';
|
|
2
|
-
import Route, { RouteOrigin } from './Route.js';
|
|
3
2
|
import Site from './Site.js';
|
|
4
3
|
import { objClone } from '../utils.js';
|
|
4
|
+
import { Entry } from '../entry/index.js';
|
|
5
|
+
import BaseRoute, { RouteOrigin } from './BaseRoute.js';
|
|
6
|
+
import Route, { TemplateModule } from './Route.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Options to create a Request
|
|
8
10
|
*/
|
|
9
11
|
export type RequestOptions = {
|
|
12
|
+
entry?: Entry;
|
|
13
|
+
template?: TemplateModule;
|
|
14
|
+
loadedData?: Record<string, any>;
|
|
10
15
|
scrollY?: number;
|
|
11
16
|
index?: number;
|
|
12
17
|
origin?: RouteOrigin;
|
|
@@ -22,7 +27,22 @@ export type RequestOptions = {
|
|
|
22
27
|
* you get a Request from the onRequest event or
|
|
23
28
|
* in a loadGlobal function.
|
|
24
29
|
*/
|
|
25
|
-
export default class Request extends
|
|
30
|
+
export default class Request extends BaseRoute {
|
|
31
|
+
/**
|
|
32
|
+
* The entry of the request once loaded
|
|
33
|
+
*/
|
|
34
|
+
entry: Entry | null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The template module of the request once loaded
|
|
38
|
+
*/
|
|
39
|
+
template: TemplateModule | null;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The loaded data of the request
|
|
43
|
+
*/
|
|
44
|
+
loadedData: Record<string, any> | null;
|
|
45
|
+
|
|
26
46
|
/**
|
|
27
47
|
* Disable scrolling for this request
|
|
28
48
|
* @default false
|
|
@@ -43,20 +63,29 @@ export default class Request extends Route {
|
|
|
43
63
|
/** @hidden */
|
|
44
64
|
_renderBarrier: RenderBarrier;
|
|
45
65
|
|
|
66
|
+
private _cancelled: boolean;
|
|
67
|
+
|
|
46
68
|
/**
|
|
47
69
|
* Create a new Request
|
|
48
70
|
*/
|
|
49
71
|
constructor(url: string | URL, site: Site, opts: RequestOptions = {}) {
|
|
50
72
|
super(url, site, opts);
|
|
51
73
|
|
|
74
|
+
this.entry = opts.entry ?? null;
|
|
75
|
+
this.template = opts.template ?? null;
|
|
76
|
+
this.loadedData = opts.loadedData ?? null;
|
|
52
77
|
this.disableScroll = opts.disableScroll ?? false;
|
|
53
78
|
this.disableLoadData = opts.disableLoadData ?? false;
|
|
54
79
|
this.statusCode = opts.statusCode ?? null;
|
|
55
80
|
this._renderBarrier = new RenderBarrier();
|
|
81
|
+
this._cancelled = false;
|
|
56
82
|
}
|
|
57
83
|
|
|
58
84
|
/**
|
|
59
85
|
* Create a Request from a Route
|
|
86
|
+
*
|
|
87
|
+
* Clears the entry, template, and loadedData
|
|
88
|
+
* todo should it?
|
|
60
89
|
*/
|
|
61
90
|
static fromRoute(route: Route, opts: RequestOptions = {}) {
|
|
62
91
|
return new Request(route.url.href, route.site, {
|
|
@@ -69,6 +98,10 @@ export default class Request extends Route {
|
|
|
69
98
|
});
|
|
70
99
|
}
|
|
71
100
|
|
|
101
|
+
get cancelled(): boolean {
|
|
102
|
+
return this._cancelled;
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
/**
|
|
73
106
|
* With delayRender you can make sure that the render waits
|
|
74
107
|
* until you are ready. This is useful for building page transitions.
|
|
@@ -103,9 +136,20 @@ export default class Request extends Route {
|
|
|
103
136
|
|
|
104
137
|
/**
|
|
105
138
|
* Create a copy of the request
|
|
139
|
+
*
|
|
140
|
+
* without including the entry, template, loadedData or cancelled state
|
|
141
|
+
*
|
|
142
|
+
* Is this what we wan't, maybe it should copy everything
|
|
106
143
|
*/
|
|
107
144
|
clone() {
|
|
108
|
-
|
|
145
|
+
// todo should this clone the entry
|
|
146
|
+
// the route for example does not do it
|
|
147
|
+
//
|
|
148
|
+
// todo should this clone keep the entry?
|
|
149
|
+
const req = new Request(this.url.href, this.site, {
|
|
150
|
+
// entry: objClone(this.entry),
|
|
151
|
+
// template: this.template ?? undefined,
|
|
152
|
+
// loadedData: objClone(this.loadedData),
|
|
109
153
|
scrollY: this.scrollY ?? undefined,
|
|
110
154
|
index: this.index,
|
|
111
155
|
origin: this.origin,
|
|
@@ -115,23 +159,55 @@ export default class Request extends Route {
|
|
|
115
159
|
disableLoadData: this.disableLoadData,
|
|
116
160
|
statusCode: this.statusCode ?? undefined,
|
|
117
161
|
});
|
|
162
|
+
|
|
163
|
+
return req;
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
/**
|
|
121
167
|
* Create a Route from the Request
|
|
168
|
+
*
|
|
169
|
+
* ## Throws
|
|
170
|
+
* if the entry, template or loadedData is missing
|
|
171
|
+
* or the request was cancelled
|
|
122
172
|
*/
|
|
123
173
|
toRoute() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
174
|
+
if (this.cancelled)
|
|
175
|
+
throw new Error(
|
|
176
|
+
'cannot create a new route because it was cancelled',
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (!this.entry || !this.template || !this.loadedData)
|
|
180
|
+
throw new Error(
|
|
181
|
+
'cannot create a new route because entry, template or loadedData is missing',
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const route = new Route(
|
|
185
|
+
this.url.href,
|
|
186
|
+
this.site,
|
|
187
|
+
this.entry,
|
|
188
|
+
this.template,
|
|
189
|
+
this.loadedData,
|
|
190
|
+
{
|
|
191
|
+
scrollY: this.scrollY ?? undefined,
|
|
192
|
+
index: this.index,
|
|
193
|
+
origin: this.origin,
|
|
194
|
+
state: objClone(this._state),
|
|
195
|
+
context: this._context,
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
route.entryChanged = !this.disableLoadData;
|
|
199
|
+
|
|
200
|
+
return route;
|
|
131
201
|
}
|
|
132
202
|
|
|
133
203
|
/** @hidden */
|
|
134
204
|
_updateOpts(opts: RequestOptions = {}) {
|
|
205
|
+
// todo maybe should check that if entry is updated
|
|
206
|
+
// template and loadedData is also updated
|
|
207
|
+
|
|
208
|
+
this.entry = opts.entry ?? this.entry;
|
|
209
|
+
this.template = opts.template ?? this.template;
|
|
210
|
+
this.loadedData = opts.loadedData ?? this.loadedData;
|
|
135
211
|
this.scrollY = opts.scrollY ?? this.scrollY;
|
|
136
212
|
this.index = opts.index ?? this.index;
|
|
137
213
|
this.origin = opts.origin ?? this.origin;
|
|
@@ -141,6 +217,12 @@ export default class Request extends Route {
|
|
|
141
217
|
this.disableLoadData = opts.disableLoadData ?? this.disableLoadData;
|
|
142
218
|
this.statusCode = opts.statusCode ?? this.statusCode;
|
|
143
219
|
}
|
|
220
|
+
|
|
221
|
+
/** @hidden */
|
|
222
|
+
_cancel() {
|
|
223
|
+
this._cancelled = true;
|
|
224
|
+
this._renderBarrier.cancel();
|
|
225
|
+
}
|
|
144
226
|
}
|
|
145
227
|
|
|
146
228
|
export function isRequest(req: any): req is Request {
|
|
@@ -178,15 +260,18 @@ class RenderBarrier {
|
|
|
178
260
|
|
|
179
261
|
/** @hidden */
|
|
180
262
|
cancel() {
|
|
263
|
+
// if the barrier is already open
|
|
264
|
+
// we don't wan't to flag the render as cancelled
|
|
265
|
+
// because it already happened
|
|
181
266
|
if (this.inner.isOpen()) return;
|
|
182
267
|
|
|
183
268
|
this.cancelled = true;
|
|
184
269
|
this.root.remove();
|
|
185
270
|
}
|
|
186
271
|
|
|
187
|
-
// returns if the render was cancelled
|
|
272
|
+
// returns true if the render was cancelled
|
|
188
273
|
/** @hidden */
|
|
189
|
-
ready(): Promise<boolean> {
|
|
274
|
+
ready(): Promise<boolean> | boolean {
|
|
190
275
|
return this.root.ready();
|
|
191
276
|
}
|
|
192
277
|
}
|