@panneau/app 4.1.1 → 5.0.0-alpha.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.
@@ -0,0 +1,10 @@
1
+ import { type ComponentProps } from 'react';
2
+ /** The current path, updated on navigation and on back and forward. */
3
+ export declare function usePathname(): string;
4
+ /** Navigates to a path of the app, without reloading the page. */
5
+ export declare function navigate(path: string): void;
6
+ /** A link to a path of the app. Plain clicks navigate without reloading the page. */
7
+ export declare function Link({ href, onClick, ...props }: ComponentProps<'a'> & {
8
+ href: string;
9
+ }): import("react").JSX.Element;
10
+ //# sourceMappingURL=location.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"location.d.ts","sourceRoot":"","sources":["../src/location.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAyC,MAAM,OAAO,CAAC;AAgBnF,uEAAuE;AACvE,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,QAGpC;AAED,qFAAqF;AACrF,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,GAAG,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,+BAkBvF"}
@@ -0,0 +1,69 @@
1
+ import { useSyncExternalStore } from "react";
2
+ import { c } from "react/compiler-runtime";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/location.tsx
5
+ var listeners = /* @__PURE__ */ new Set();
6
+ function subscribe(listener) {
7
+ listeners.add(listener);
8
+ window.addEventListener("popstate", listener);
9
+ return () => {
10
+ listeners.delete(listener);
11
+ window.removeEventListener("popstate", listener);
12
+ };
13
+ }
14
+ /** The current path, updated on navigation and on back and forward. */
15
+ function usePathname() {
16
+ return useSyncExternalStore(subscribe, _temp);
17
+ }
18
+ /** Navigates to a path of the app, without reloading the page. */
19
+ function _temp() {
20
+ return window.location.pathname;
21
+ }
22
+ function navigate(path) {
23
+ window.history.pushState(null, "", path);
24
+ listeners.forEach((listener) => listener());
25
+ }
26
+ /** A link to a path of the app. Plain clicks navigate without reloading the page. */
27
+ function Link(t0) {
28
+ const $ = c(8);
29
+ let href;
30
+ let props;
31
+ let t1;
32
+ if ($[0] !== t0) {
33
+ const { href: t2, onClick, ...t3 } = t0;
34
+ href = t2;
35
+ props = t3;
36
+ t1 = (event) => {
37
+ onClick?.(event);
38
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || props.target !== void 0 && props.target !== "_self") return;
39
+ event.preventDefault();
40
+ navigate(href);
41
+ };
42
+ $[0] = t0;
43
+ $[1] = href;
44
+ $[2] = props;
45
+ $[3] = t1;
46
+ } else {
47
+ href = $[1];
48
+ props = $[2];
49
+ t1 = $[3];
50
+ }
51
+ const handleClick = t1;
52
+ let t2;
53
+ if ($[4] !== handleClick || $[5] !== href || $[6] !== props) {
54
+ t2 = /* @__PURE__ */ jsx("a", {
55
+ ...props,
56
+ href,
57
+ onClick: handleClick
58
+ });
59
+ $[4] = handleClick;
60
+ $[5] = href;
61
+ $[6] = props;
62
+ $[7] = t2;
63
+ } else t2 = $[7];
64
+ return t2;
65
+ }
66
+ //#endregion
67
+ export { Link, navigate, usePathname };
68
+
69
+ //# sourceMappingURL=location.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"location.js","names":["href","handleClick","_c"],"sources":["../src/location.tsx"],"sourcesContent":["import { type ComponentProps, type MouseEvent, useSyncExternalStore } from 'react';\n\n// A minimal history-mode location, for the placeholder shell. The shell of Phase 2 replaces it\n// with the app's router.\n\nconst listeners = new Set<() => void>();\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n window.addEventListener('popstate', listener);\n return () => {\n listeners.delete(listener);\n window.removeEventListener('popstate', listener);\n };\n}\n\n/** The current path, updated on navigation and on back and forward. */\nexport function usePathname(): string {\n return useSyncExternalStore(subscribe, () => window.location.pathname);\n}\n\n/** Navigates to a path of the app, without reloading the page. */\nexport function navigate(path: string) {\n window.history.pushState(null, '', path);\n listeners.forEach((listener) => listener());\n}\n\n/** A link to a path of the app. Plain clicks navigate without reloading the page. */\nexport function Link({ href, onClick, ...props }: ComponentProps<'a'> & { href: string }) {\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n onClick?.(event);\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey ||\n (props.target !== undefined && props.target !== '_self')\n ) {\n return;\n }\n event.preventDefault();\n navigate(href);\n };\n return <a {...props} href={href} onClick={handleClick} />;\n}\n"],"mappings":";;;;AAKA,IAAM,4BAAY,IAAI,IAAgB;AAEtC,SAAS,UAAU,UAAsB;CACrC,UAAU,IAAI,QAAQ;CACtB,OAAO,iBAAiB,YAAY,QAAQ;CAC5C,aAAa;EACT,UAAU,OAAO,QAAQ;EACzB,OAAO,oBAAoB,YAAY,QAAQ;CACnD;AACJ;;AAGA,SAAO,cAAA;CAAA,OACI,qBAAqB,WAAW,KAA8B;AAAC;;AADnE,SAAA,QAAA;CAAA,OAC0C,OAAM,SAAS;AAAS;AAIzE,SAAgB,SAAS,MAAc;CACnC,OAAO,QAAQ,UAAU,MAAM,IAAI,IAAI;CACvC,UAAU,SAAS,aAAa,SAAS,CAAC;AAC9C;;AAGA,SAAO,KAAA,IAAA;CAAA,MAAA,IAAAE,EAAA,CAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,IAAA;EAAc,MAAA,EAAA,MAAA,IAAA,SAAA,GAAA,OAAA;EAAA,OAAA;EAAA,QAAA;EACG,MAAA,UAAA;GAChB,UAAU,KAAK;GACf,IACI,MAAK,oBACL,MAAK,WAAY,KACjB,MAAK,WACL,MAAK,WACL,MAAK,YACL,MAAK,UACJ,MAAK,WAAY,KAAA,KAAa,MAAK,WAAY,SAAQ;GAI5D,MAAK,eAAgB;GACrB,SAAS,IAAI;EAAC;EACjB,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA;EAAA,OAAA,EAAA;EAAA,QAAA,EAAA;EAAA,KAAA,EAAA;CAAA;CAfD,MAAA,cAAoB;CAelB,IAAA;CAAA,IAAA,EAAA,OAAA,eAAA,EAAA,OAAA,QAAA,EAAA,OAAA,OAAA;EACK,KAAA,oBAAA,KAAA;GAAkD,GAA3C;GAAa;GAAeD,SAAA;EAAW,CAAA;EAAI,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAAlD;AAAkD"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The client routes of the app, under the base path (see docs/decisions/0019). The shell of
3
+ * Phase 2 builds on them; they are internal until then.
4
+ */
5
+ export type AppRoute = {
6
+ name: 'home';
7
+ } | {
8
+ name: 'resource.index';
9
+ resource: string;
10
+ } | {
11
+ name: 'resource.create';
12
+ resource: string;
13
+ } | {
14
+ name: 'resource.show';
15
+ resource: string;
16
+ id: string;
17
+ } | {
18
+ name: 'resource.edit';
19
+ resource: string;
20
+ id: string;
21
+ } | {
22
+ name: 'page';
23
+ page: string;
24
+ } | {
25
+ name: 'not-found';
26
+ };
27
+ /** The base path without its trailing slash: `/panneau/` is `/panneau`, and `/` is ``. */
28
+ export declare function normalizeBasePath(basePath: string): string;
29
+ /**
30
+ * The route of a path, or `null` when the path is outside the base path. Paths under the base
31
+ * path that match no route are `not-found`.
32
+ */
33
+ export declare function matchRoute(pathname: string, basePath: string): AppRoute | null;
34
+ /** The path of a route, under the base path. */
35
+ export declare function routePath(route: Exclude<AppRoute, {
36
+ name: 'not-found';
37
+ }>, basePath: string): string;
38
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,QAAQ,GACd;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAE5B,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAUD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAgC9E;AAED,gDAAgD;AAChD,wBAAgB,SAAS,CACrB,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,EAC/C,QAAQ,EAAE,MAAM,GACjB,MAAM,CAkBR"}
package/dist/routes.js ADDED
@@ -0,0 +1,65 @@
1
+ //#region src/routes.ts
2
+ /** The base path without its trailing slash: `/panneau/` is `/panneau`, and `/` is ``. */
3
+ function normalizeBasePath(basePath) {
4
+ return basePath.replace(/\/+$/, "");
5
+ }
6
+ function decode(segment) {
7
+ try {
8
+ return decodeURIComponent(segment);
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+ /**
14
+ * The route of a path, or `null` when the path is outside the base path. Paths under the base
15
+ * path that match no route are `not-found`.
16
+ */
17
+ function matchRoute(pathname, basePath) {
18
+ const base = normalizeBasePath(basePath);
19
+ if (pathname !== base && !pathname.startsWith(`${base}/`)) return null;
20
+ const rest = pathname.slice(base.length).replace(/^\/+|\/+$/g, "");
21
+ const segments = rest === "" ? [] : rest.split("/").map(decode);
22
+ if (segments.some((segment) => segment === null || segment === "")) return { name: "not-found" };
23
+ const [first, resource, third, fourth, ...extra] = segments;
24
+ if (first === void 0) return { name: "home" };
25
+ if (first === "pages" && resource !== void 0 && third === void 0) return {
26
+ name: "page",
27
+ page: resource
28
+ };
29
+ if (first !== "resources" || resource === void 0 || extra.length > 0) return { name: "not-found" };
30
+ if (third === void 0) return {
31
+ name: "resource.index",
32
+ resource
33
+ };
34
+ if (third === "create" && fourth === void 0) return {
35
+ name: "resource.create",
36
+ resource
37
+ };
38
+ if (fourth === void 0) return {
39
+ name: "resource.show",
40
+ resource,
41
+ id: third
42
+ };
43
+ return fourth === "edit" ? {
44
+ name: "resource.edit",
45
+ resource,
46
+ id: third
47
+ } : { name: "not-found" };
48
+ }
49
+ /** The path of a route, under the base path. */
50
+ function routePath(route, basePath) {
51
+ const base = normalizeBasePath(basePath);
52
+ const path = (...segments) => `${base}/${segments.map((segment) => encodeURIComponent(segment)).join("/")}`;
53
+ switch (route.name) {
54
+ case "home": return base === "" ? "/" : base;
55
+ case "page": return path("pages", route.page);
56
+ case "resource.index": return path("resources", route.resource);
57
+ case "resource.create": return `${path("resources", route.resource)}/create`;
58
+ case "resource.show": return path("resources", route.resource, route.id);
59
+ case "resource.edit": return `${path("resources", route.resource, route.id)}/edit`;
60
+ }
61
+ }
62
+ //#endregion
63
+ export { matchRoute, normalizeBasePath, routePath };
64
+
65
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","names":[],"sources":["../src/routes.ts"],"sourcesContent":["/**\n * The client routes of the app, under the base path (see docs/decisions/0019). The shell of\n * Phase 2 builds on them; they are internal until then.\n */\nexport type AppRoute =\n | { name: 'home' }\n | { name: 'resource.index'; resource: string }\n | { name: 'resource.create'; resource: string }\n | { name: 'resource.show'; resource: string; id: string }\n | { name: 'resource.edit'; resource: string; id: string }\n | { name: 'page'; page: string }\n | { name: 'not-found' };\n\n/** The base path without its trailing slash: `/panneau/` is `/panneau`, and `/` is ``. */\nexport function normalizeBasePath(basePath: string): string {\n return basePath.replace(/\\/+$/, '');\n}\n\nfunction decode(segment: string): string | null {\n try {\n return decodeURIComponent(segment);\n } catch {\n return null;\n }\n}\n\n/**\n * The route of a path, or `null` when the path is outside the base path. Paths under the base\n * path that match no route are `not-found`.\n */\nexport function matchRoute(pathname: string, basePath: string): AppRoute | null {\n const base = normalizeBasePath(basePath);\n if (pathname !== base && !pathname.startsWith(`${base}/`)) {\n return null;\n }\n const rest = pathname.slice(base.length).replace(/^\\/+|\\/+$/g, '');\n const segments = rest === '' ? [] : rest.split('/').map(decode);\n if (segments.some((segment) => segment === null || segment === '')) {\n return { name: 'not-found' };\n }\n const [first, resource, third, fourth, ...extra] = segments as string[];\n if (first === undefined) {\n return { name: 'home' };\n }\n if (first === 'pages' && resource !== undefined && third === undefined) {\n return { name: 'page', page: resource };\n }\n if (first !== 'resources' || resource === undefined || extra.length > 0) {\n return { name: 'not-found' };\n }\n if (third === undefined) {\n return { name: 'resource.index', resource };\n }\n if (third === 'create' && fourth === undefined) {\n return { name: 'resource.create', resource };\n }\n if (fourth === undefined) {\n return { name: 'resource.show', resource, id: third };\n }\n return fourth === 'edit'\n ? { name: 'resource.edit', resource, id: third }\n : { name: 'not-found' };\n}\n\n/** The path of a route, under the base path. */\nexport function routePath(\n route: Exclude<AppRoute, { name: 'not-found' }>,\n basePath: string,\n): string {\n const base = normalizeBasePath(basePath);\n const path = (...segments: string[]) =>\n `${base}/${segments.map((segment) => encodeURIComponent(segment)).join('/')}`;\n switch (route.name) {\n case 'home':\n return base === '' ? '/' : base;\n case 'page':\n return path('pages', route.page);\n case 'resource.index':\n return path('resources', route.resource);\n case 'resource.create':\n return `${path('resources', route.resource)}/create`;\n case 'resource.show':\n return path('resources', route.resource, route.id);\n case 'resource.edit':\n return `${path('resources', route.resource, route.id)}/edit`;\n }\n}\n"],"mappings":";;AAcA,SAAgB,kBAAkB,UAA0B;CACxD,OAAO,SAAS,QAAQ,QAAQ,EAAE;AACtC;AAEA,SAAS,OAAO,SAAgC;CAC5C,IAAI;EACA,OAAO,mBAAmB,OAAO;CACrC,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;AAMA,SAAgB,WAAW,UAAkB,UAAmC;CAC5E,MAAM,OAAO,kBAAkB,QAAQ;CACvC,IAAI,aAAa,QAAQ,CAAC,SAAS,WAAW,GAAG,KAAI,EAAG,GACpD,OAAO;CAEX,MAAM,OAAO,SAAS,MAAM,KAAK,MAAM,CAAC,CAAC,QAAQ,cAAc,EAAE;CACjE,MAAM,WAAW,SAAS,KAAK,CAAA,IAAK,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CAC9D,IAAI,SAAS,MAAM,YAAY,YAAY,QAAQ,YAAY,EAAE,GAC7D,OAAO,EAAE,MAAM,YAAY;CAE/B,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAAG,SAAS;CACnD,IAAI,UAAU,KAAA,GACV,OAAO,EAAE,MAAM,OAAO;CAE1B,IAAI,UAAU,WAAW,aAAa,KAAA,KAAa,UAAU,KAAA,GACzD,OAAO;EAAE,MAAM;EAAQ,MAAM;CAAS;CAE1C,IAAI,UAAU,eAAe,aAAa,KAAA,KAAa,MAAM,SAAS,GAClE,OAAO,EAAE,MAAM,YAAY;CAE/B,IAAI,UAAU,KAAA,GACV,OAAO;EAAE,MAAM;EAAkB;CAAS;CAE9C,IAAI,UAAU,YAAY,WAAW,KAAA,GACjC,OAAO;EAAE,MAAM;EAAmB;CAAS;CAE/C,IAAI,WAAW,KAAA,GACX,OAAO;EAAE,MAAM;EAAiB;EAAU,IAAI;CAAM;CAExD,OAAO,WAAW,SACZ;EAAE,MAAM;EAAiB;EAAU,IAAI;CAAM,IAC7C,EAAE,MAAM,YAAY;AAC9B;;AAGA,SAAgB,UACZ,OACA,UACM;CACN,MAAM,OAAO,kBAAkB,QAAQ;CACvC,MAAM,QAAQ,GAAG,aACb,GAAG,KAAI,GAAI,SAAS,KAAK,YAAY,mBAAmB,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG;CAC9E,QAAQ,MAAM,MAAd;EACI,KAAK,QACD,OAAO,SAAS,KAAK,MAAM;EAC/B,KAAK,QACD,OAAO,KAAK,SAAS,MAAM,IAAI;EACnC,KAAK,kBACD,OAAO,KAAK,aAAa,MAAM,QAAQ;EAC3C,KAAK,mBACD,OAAO,GAAG,KAAK,aAAa,MAAM,QAAQ,EAAC;EAC/C,KAAK,iBACD,OAAO,KAAK,aAAa,MAAM,UAAU,MAAM,EAAE;EACrD,KAAK,iBACD,OAAO,GAAG,KAAK,aAAa,MAAM,UAAU,MAAM,EAAE,EAAC;CAC7D;AACJ"}
@@ -0,0 +1,21 @@
1
+ import { type DataAdapter } from '@panneau/data';
2
+ import type { BootConfig, Definition } from '@panneau/protocol';
3
+ /** Whether the API said that the user isn't authenticated, with a way to know when it does. */
4
+ export interface SessionState {
5
+ isSignedOut(): boolean;
6
+ subscribe(listener: () => void): () => void;
7
+ signOut(): void;
8
+ }
9
+ export declare function createSessionState(): SessionState;
10
+ /**
11
+ * Wraps an adapter so that every call that rejects with an `UnauthorizedError` (`401`, or `419`
12
+ * with the Laravel preset) signs the session out. The call still rejects.
13
+ */
14
+ export declare function watchUnauthorized(adapter: DataAdapter, session: SessionState): DataAdapter;
15
+ /**
16
+ * Where to send unauthenticated users: `auth.loginUrl` of the definition in `external` mode, the
17
+ * `loginUrl` of the boot config before the definition is loaded, and nowhere in `panneau` mode,
18
+ * whose login page is part of the app.
19
+ */
20
+ export declare function getLoginUrl(definition: Definition | undefined, config: BootConfig): string | null;
21
+ //# sourceMappingURL=unauthorized.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unauthorized.d.ts","sourceRoot":"","sources":["../src/unauthorized.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEhE,+FAA+F;AAC/F,MAAM,WAAW,YAAY;IACzB,WAAW,IAAI,OAAO,CAAC;IACvB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,OAAO,IAAI,IAAI,CAAC;CACnB;AAED,wBAAgB,kBAAkB,IAAI,YAAY,CAgBjD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,WAAW,CAqB1F;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAKjG"}
@@ -0,0 +1,50 @@
1
+ import { UnauthorizedError } from "@panneau/data";
2
+ //#region src/unauthorized.ts
3
+ function createSessionState() {
4
+ let signedOut = false;
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ return {
7
+ isSignedOut: () => signedOut,
8
+ subscribe(listener) {
9
+ listeners.add(listener);
10
+ return () => listeners.delete(listener);
11
+ },
12
+ signOut() {
13
+ if (!signedOut) {
14
+ signedOut = true;
15
+ listeners.forEach((listener) => listener());
16
+ }
17
+ }
18
+ };
19
+ }
20
+ /**
21
+ * Wraps an adapter so that every call that rejects with an `UnauthorizedError` (`401`, or `419`
22
+ * with the Laravel preset) signs the session out. The call still rejects.
23
+ */
24
+ function watchUnauthorized(adapter, session) {
25
+ return new Proxy(adapter, { get(target, key, receiver) {
26
+ const value = Reflect.get(target, key, receiver);
27
+ if (typeof value !== "function") return value;
28
+ return (...args) => {
29
+ const result = Reflect.apply(value, target, args);
30
+ if (result instanceof Promise) return result.catch((error) => {
31
+ if (error instanceof UnauthorizedError) session.signOut();
32
+ throw error;
33
+ });
34
+ return result;
35
+ };
36
+ } });
37
+ }
38
+ /**
39
+ * Where to send unauthenticated users: `auth.loginUrl` of the definition in `external` mode, the
40
+ * `loginUrl` of the boot config before the definition is loaded, and nowhere in `panneau` mode,
41
+ * whose login page is part of the app.
42
+ */
43
+ function getLoginUrl(definition, config) {
44
+ if (definition?.auth?.mode === "panneau") return null;
45
+ return definition?.auth?.loginUrl ?? config.loginUrl ?? null;
46
+ }
47
+ //#endregion
48
+ export { createSessionState, getLoginUrl, watchUnauthorized };
49
+
50
+ //# sourceMappingURL=unauthorized.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unauthorized.js","names":[],"sources":["../src/unauthorized.ts"],"sourcesContent":["import { type DataAdapter, UnauthorizedError } from '@panneau/data';\nimport type { BootConfig, Definition } from '@panneau/protocol';\n\n/** Whether the API said that the user isn't authenticated, with a way to know when it does. */\nexport interface SessionState {\n isSignedOut(): boolean;\n subscribe(listener: () => void): () => void;\n signOut(): void;\n}\n\nexport function createSessionState(): SessionState {\n let signedOut = false;\n const listeners = new Set<() => void>();\n return {\n isSignedOut: () => signedOut,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n signOut() {\n if (!signedOut) {\n signedOut = true;\n listeners.forEach((listener) => listener());\n }\n },\n };\n}\n\n/**\n * Wraps an adapter so that every call that rejects with an `UnauthorizedError` (`401`, or `419`\n * with the Laravel preset) signs the session out. The call still rejects.\n */\nexport function watchUnauthorized(adapter: DataAdapter, session: SessionState): DataAdapter {\n return new Proxy(adapter, {\n get(target, key, receiver) {\n const value: unknown = Reflect.get(target, key, receiver);\n if (typeof value !== 'function') {\n return value;\n }\n return (...args: unknown[]): unknown => {\n const result: unknown = Reflect.apply(value, target, args);\n if (result instanceof Promise) {\n return result.catch((error: unknown) => {\n if (error instanceof UnauthorizedError) {\n session.signOut();\n }\n throw error;\n });\n }\n return result;\n };\n },\n });\n}\n\n/**\n * Where to send unauthenticated users: `auth.loginUrl` of the definition in `external` mode, the\n * `loginUrl` of the boot config before the definition is loaded, and nowhere in `panneau` mode,\n * whose login page is part of the app.\n */\nexport function getLoginUrl(definition: Definition | undefined, config: BootConfig): string | null {\n if (definition?.auth?.mode === 'panneau') {\n return null;\n }\n return definition?.auth?.loginUrl ?? config.loginUrl ?? null;\n}\n"],"mappings":";;AAUA,SAAgB,qBAAmC;CAC/C,IAAI,YAAY;CAChB,MAAM,4BAAY,IAAI,IAAgB;CACtC,OAAO;EACH,mBAAmB;EACnB,UAAU,UAAU;GAChB,UAAU,IAAI,QAAQ;GACtB,aAAa,UAAU,OAAO,QAAQ;EAC1C;EACA,UAAU;GACN,IAAI,CAAC,WAAW;IACZ,YAAY;IACZ,UAAU,SAAS,aAAa,SAAS,CAAC;GAC9C;EACJ;CACJ;AACJ;;;;;AAMA,SAAgB,kBAAkB,SAAsB,SAAoC;CACxF,OAAO,IAAI,MAAM,SAAS,EACtB,IAAI,QAAQ,KAAK,UAAU;EACvB,MAAM,QAAiB,QAAQ,IAAI,QAAQ,KAAK,QAAQ;EACxD,IAAI,OAAO,UAAU,YACjB,OAAO;EAEX,QAAQ,GAAG,SAA6B;GACpC,MAAM,SAAkB,QAAQ,MAAM,OAAO,QAAQ,IAAI;GACzD,IAAI,kBAAkB,SAClB,OAAO,OAAO,OAAO,UAAmB;IACpC,IAAI,iBAAiB,mBACjB,QAAQ,QAAQ;IAEpB,MAAM;GACV,CAAC;GAEL,OAAO;EACX;CACJ,EACJ,CAAC;AACL;;;;;;AAOA,SAAgB,YAAY,YAAoC,QAAmC;CAC/F,IAAI,YAAY,MAAM,SAAS,WAC3B,OAAO;CAEX,OAAO,YAAY,MAAM,YAAY,OAAO,YAAY;AAC5D"}
package/package.json CHANGED
@@ -1,128 +1,44 @@
1
1
  {
2
2
  "name": "@panneau/app",
3
- "version": "4.1.1",
4
- "description": "",
5
- "keywords": [
6
- "javascript"
7
- ],
8
- "homepage": "https://github.com/folkloreinc/panneau-js",
3
+ "version": "5.0.0-alpha.0",
4
+ "description": "The Panneau admin panel: the app entry (createPanneauApp), and the shell, routes and pages to come.",
5
+ "type": "module",
6
+ "license": "MIT",
9
7
  "repository": {
10
8
  "type": "git",
11
- "url": "git+https://github.com/folkloreinc/panneau-js.git"
12
- },
13
- "license": "ISC",
14
- "author": {
15
- "name": "Folklore",
16
- "email": "info@folklore.email"
9
+ "url": "git+https://github.com/folkloreinc/panneau-js.git",
10
+ "directory": "packages/app"
17
11
  },
18
- "contributors": [
19
- {
20
- "name": "David Mongeau-Petitpas",
21
- "email": "dmp@folklore.email"
22
- },
23
- {
24
- "name": "Nicolas Roy-Bourdages",
25
- "email": "nrb@folklore.email"
26
- },
27
- {
28
- "name": "Julien Carignan",
29
- "email": "jc@folklore.email"
30
- },
31
- {
32
- "name": "Théo Gjini",
33
- "email": "tg@folklore.email"
34
- }
35
- ],
36
12
  "sideEffects": [
37
- "**/*.css"
13
+ "*.css"
38
14
  ],
39
- "type": "module",
40
15
  "exports": {
41
16
  ".": {
17
+ "@panneau/source": "./src/index.ts",
42
18
  "types": "./dist/index.d.ts",
43
- "style": "./assets/css/styles.css",
44
19
  "default": "./dist/index.js"
45
20
  },
46
- "./assets/css/styles": "./assets/css/styles.css",
47
- "./assets/css/styles.css": "./assets/css/styles.css",
48
- "./assets/css/packages": "./assets/css/packages.css",
49
- "./assets/css/packages.css": "./assets/css/packages.css"
21
+ "./package.json": "./package.json"
50
22
  },
51
- "module": "dist/index.js",
52
- "types": "dist/index.d.ts",
53
- "style": "assets/css/styles.css",
54
23
  "files": [
55
- "dist",
56
- "assets"
24
+ "dist"
57
25
  ],
58
26
  "scripts": {
59
- "build": "npm run css:aggregates && ../../scripts/prepare-package.sh --types && npm run css:packages",
60
- "css:aggregates": "../../scripts/build-packages-styles.js --pattern='@panneau/element-*' ./src/styles/packages.css.ejs ./src/styles/packages.css",
61
- "css:packages": "postcss ./src/styles/packages.css -o ./assets/css/packages.css",
62
- "prepublishOnly": "npm run build"
27
+ "build": "vite build && tsc -p tsconfig.build.json",
28
+ "typecheck": "tsc --noEmit",
29
+ "api": "tsc -p tsconfig.build.json && node ../../scripts/api-report.js"
63
30
  },
64
- "dependencies": {
65
- "@babel/runtime": "^7.28.6",
66
- "@folklore/routes": "^0.4.11",
67
- "@fortawesome/fontawesome-svg-core": "^6.5.1",
68
- "@fortawesome/free-solid-svg-icons": "^6.5.1",
69
- "@fortawesome/react-fontawesome": "^0.2.0",
70
- "@panneau/actions": "^4.1.1",
71
- "@panneau/auth": "^4.1.1",
72
- "@panneau/core": "^4.1.1",
73
- "@panneau/data": "^4.1.1",
74
- "@panneau/displays": "^4.1.1",
75
- "@panneau/element-accordion": "^4.1.1",
76
- "@panneau/element-alert": "^4.1.1",
77
- "@panneau/element-avatar": "^4.1.1",
78
- "@panneau/element-breadcrumbs": "^4.1.1",
79
- "@panneau/element-button": "^4.1.1",
80
- "@panneau/element-buttons": "^4.1.1",
81
- "@panneau/element-dropdown": "^4.1.1",
82
- "@panneau/element-form": "^4.1.1",
83
- "@panneau/element-form-group": "^4.1.1",
84
- "@panneau/element-grid": "^4.1.1",
85
- "@panneau/element-icon": "^4.1.1",
86
- "@panneau/element-label": "^4.1.1",
87
- "@panneau/element-link": "^4.1.1",
88
- "@panneau/element-loading": "^4.1.1",
89
- "@panneau/element-media-card": "^4.1.1",
90
- "@panneau/element-media-player": "^4.1.1",
91
- "@panneau/element-menu": "^4.1.1",
92
- "@panneau/element-modal": "^4.1.1",
93
- "@panneau/element-navbar": "^4.1.1",
94
- "@panneau/element-pagination": "^4.1.1",
95
- "@panneau/element-radios": "^4.1.1",
96
- "@panneau/element-range": "^4.1.1",
97
- "@panneau/element-resource-card": "^4.1.1",
98
- "@panneau/fields": "^4.1.1",
99
- "@panneau/filter-filters": "^4.1.1",
100
- "@panneau/filters": "^4.1.1",
101
- "@panneau/form": "^4.1.1",
102
- "@panneau/form-resource": "^4.1.1",
103
- "@panneau/forms": "^4.1.1",
104
- "@panneau/intl": "^4.1.1",
105
- "@panneau/list-resource-items": "^4.1.1",
106
- "@panneau/lists": "^4.1.1",
107
- "@panneau/modals": "^4.1.1",
108
- "@panneau/themes": "^4.1.1",
109
- "@panneau/uppy": "^4.1.1",
110
- "classnames": "^2.5.1",
111
- "lodash-es": "^4.17.21",
112
- "query-string": "^9.0.0",
113
- "react-intl": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^10.0.0",
114
- "wouter": "^3.1.2"
115
- },
116
- "devDependencies": {
117
- "react": "^19.2.0",
118
- "react-dom": "^19.0.0"
31
+ "publishConfig": {
32
+ "access": "public"
119
33
  },
120
34
  "peerDependencies": {
35
+ "@tanstack/react-query": "^5.0.0",
121
36
  "react": "^19.2.0",
122
- "react-dom": "^19.0.0"
37
+ "react-dom": "^19.2.0"
123
38
  },
124
- "publishConfig": {
125
- "access": "public"
126
- },
127
- "gitHead": "28b394278cffa0c927989873dd4b3f184635ceff"
39
+ "dependencies": {
40
+ "@panneau/core": "5.0.0-alpha.0",
41
+ "@panneau/data": "5.0.0-alpha.0",
42
+ "@panneau/protocol": "5.0.0-alpha.0"
43
+ }
128
44
  }