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.
Files changed (130) hide show
  1. package/LICENSE.md +41 -0
  2. package/dist/Crelte.d.ts +55 -0
  3. package/dist/Crelte.d.ts.map +1 -0
  4. package/dist/Crelte.js +106 -0
  5. package/dist/CrelteBase.d.ts +16 -0
  6. package/dist/CrelteBase.d.ts.map +1 -0
  7. package/dist/CrelteBase.js +1 -0
  8. package/dist/CrelteRouted.d.ts +50 -0
  9. package/dist/CrelteRouted.d.ts.map +1 -0
  10. package/dist/CrelteRouted.js +88 -0
  11. package/dist/blocks/Blocks.d.ts +35 -0
  12. package/dist/blocks/Blocks.d.ts.map +1 -0
  13. package/dist/blocks/Blocks.js +100 -0
  14. package/dist/blocks/Blocks.svelte +21 -0
  15. package/dist/blocks/Blocks.svelte.d.ts +24 -0
  16. package/dist/blocks/Blocks.svelte.d.ts.map +1 -0
  17. package/dist/blocks/index.d.ts +5 -0
  18. package/dist/blocks/index.d.ts.map +1 -0
  19. package/dist/blocks/index.js +3 -0
  20. package/dist/cookies/ClientCookies.d.ts +9 -0
  21. package/dist/cookies/ClientCookies.d.ts.map +1 -0
  22. package/dist/cookies/ClientCookies.js +22 -0
  23. package/dist/cookies/ServerCookies.d.ts +13 -0
  24. package/dist/cookies/ServerCookies.d.ts.map +1 -0
  25. package/dist/cookies/ServerCookies.js +31 -0
  26. package/dist/cookies/index.d.ts +20 -0
  27. package/dist/cookies/index.d.ts.map +1 -0
  28. package/dist/cookies/index.js +1 -0
  29. package/dist/cookies/utils.d.ts +12 -0
  30. package/dist/cookies/utils.d.ts.map +1 -0
  31. package/dist/cookies/utils.js +32 -0
  32. package/dist/graphql/GraphQl.d.ts +60 -0
  33. package/dist/graphql/GraphQl.d.ts.map +1 -0
  34. package/dist/graphql/GraphQl.js +197 -0
  35. package/dist/graphql/gql.test.d.ts +2 -0
  36. package/dist/graphql/gql.test.d.ts.map +1 -0
  37. package/dist/graphql/gql.test.js +80 -0
  38. package/dist/graphql/index.d.ts +3 -0
  39. package/dist/graphql/index.d.ts.map +1 -0
  40. package/dist/graphql/index.js +2 -0
  41. package/dist/index.d.ts +67 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +83 -0
  44. package/dist/init/client.d.ts +13 -0
  45. package/dist/init/client.d.ts.map +1 -0
  46. package/dist/init/client.js +129 -0
  47. package/dist/init/server.d.ts +38 -0
  48. package/dist/init/server.d.ts.map +1 -0
  49. package/dist/init/server.js +95 -0
  50. package/dist/init/shared.d.ts +29 -0
  51. package/dist/init/shared.d.ts.map +1 -0
  52. package/dist/init/shared.js +154 -0
  53. package/dist/loadData/Globals.d.ts +33 -0
  54. package/dist/loadData/Globals.d.ts.map +1 -0
  55. package/dist/loadData/Globals.js +119 -0
  56. package/dist/loadData/index.d.ts +25 -0
  57. package/dist/loadData/index.d.ts.map +1 -0
  58. package/dist/loadData/index.js +39 -0
  59. package/dist/plugins/Events.d.ts +11 -0
  60. package/dist/plugins/Events.d.ts.map +1 -0
  61. package/dist/plugins/Events.js +29 -0
  62. package/dist/plugins/Plugins.d.ts +12 -0
  63. package/dist/plugins/Plugins.d.ts.map +1 -0
  64. package/dist/plugins/Plugins.js +12 -0
  65. package/dist/plugins/index.d.ts +5 -0
  66. package/dist/plugins/index.d.ts.map +1 -0
  67. package/dist/plugins/index.js +2 -0
  68. package/dist/routing/History.d.ts +22 -0
  69. package/dist/routing/History.d.ts.map +1 -0
  70. package/dist/routing/History.js +36 -0
  71. package/dist/routing/InnerRouter.d.ts +111 -0
  72. package/dist/routing/InnerRouter.d.ts.map +1 -0
  73. package/dist/routing/InnerRouter.js +397 -0
  74. package/dist/routing/PageLoader.d.ts +37 -0
  75. package/dist/routing/PageLoader.d.ts.map +1 -0
  76. package/dist/routing/PageLoader.js +72 -0
  77. package/dist/routing/Route.d.ts +82 -0
  78. package/dist/routing/Route.d.ts.map +1 -0
  79. package/dist/routing/Route.js +134 -0
  80. package/dist/routing/Router.d.ts +162 -0
  81. package/dist/routing/Router.d.ts.map +1 -0
  82. package/dist/routing/Router.js +333 -0
  83. package/dist/routing/Site.d.ts +47 -0
  84. package/dist/routing/Site.d.ts.map +1 -0
  85. package/dist/routing/Site.js +48 -0
  86. package/dist/routing/index.d.ts +5 -0
  87. package/dist/routing/index.d.ts.map +1 -0
  88. package/dist/routing/index.js +4 -0
  89. package/dist/ssr/SsrCache.d.ts +12 -0
  90. package/dist/ssr/SsrCache.d.ts.map +1 -0
  91. package/dist/ssr/SsrCache.js +50 -0
  92. package/dist/ssr/SsrComponents.d.ts +7 -0
  93. package/dist/ssr/SsrComponents.d.ts.map +1 -0
  94. package/dist/ssr/SsrComponents.js +30 -0
  95. package/dist/ssr/index.d.ts +4 -0
  96. package/dist/ssr/index.d.ts.map +1 -0
  97. package/dist/ssr/index.js +3 -0
  98. package/package.json +79 -0
  99. package/src/Crelte.ts +135 -0
  100. package/src/CrelteBase.ts +24 -0
  101. package/src/CrelteRouted.ts +128 -0
  102. package/src/blocks/Blocks.svelte +68 -0
  103. package/src/blocks/Blocks.ts +155 -0
  104. package/src/blocks/index.ts +14 -0
  105. package/src/cookies/ClientCookies.ts +30 -0
  106. package/src/cookies/ServerCookies.ts +42 -0
  107. package/src/cookies/index.ts +24 -0
  108. package/src/cookies/utils.ts +53 -0
  109. package/src/graphql/GraphQl.ts +281 -0
  110. package/src/graphql/gql.test.ts +123 -0
  111. package/src/graphql/index.ts +8 -0
  112. package/src/index.ts +109 -0
  113. package/src/init/client.ts +190 -0
  114. package/src/init/server.ts +177 -0
  115. package/src/init/shared.ts +221 -0
  116. package/src/loadData/Globals.ts +150 -0
  117. package/src/loadData/index.ts +67 -0
  118. package/src/plugins/Events.ts +50 -0
  119. package/src/plugins/Plugins.ts +23 -0
  120. package/src/plugins/index.ts +5 -0
  121. package/src/routing/History.ts +52 -0
  122. package/src/routing/InnerRouter.ts +469 -0
  123. package/src/routing/PageLoader.ts +112 -0
  124. package/src/routing/Route.ts +184 -0
  125. package/src/routing/Router.ts +476 -0
  126. package/src/routing/Site.ts +65 -0
  127. package/src/routing/index.ts +5 -0
  128. package/src/ssr/SsrCache.ts +61 -0
  129. package/src/ssr/SsrComponents.ts +34 -0
  130. 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
+ }
@@ -0,0 +1,5 @@
1
+ import Router, { type DelayRender, type OnNextRouteOpts } from './Router.js';
2
+ import Route from './Route.js';
3
+ import Site from './Site.js';
4
+
5
+ export { Router, Route, Site, OnNextRouteOpts, DelayRender };