@hgdc/ui 0.65.0 → 0.66.1
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/app-shell.js +2 -2
- package/dist/module-bar-store.d.ts +4 -2
- package/dist/module-bar-store.js +6 -2
- package/dist/module-bar.js +2 -0
- package/dist/product-bar.d.ts +8 -1
- package/dist/product-bar.js +62 -2
- package/dist/products.js +11 -2
- package/package.json +1 -1
package/dist/app-shell.js
CHANGED
|
@@ -44,7 +44,7 @@ export function AppShell({ product, organization, productRail: productRailOption
|
|
|
44
44
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
45
45
|
const preference = useProductBarPreference();
|
|
46
46
|
// Relu chez Account seulement par un produit passé aux deux vues (il donne `view`, ADR 0014).
|
|
47
|
-
useProductBarSync(productRailOption
|
|
47
|
+
useProductBarSync(productRailOption !== false);
|
|
48
48
|
const productRail = productRailOption ?? (viewOption === undefined || productBarShown(viewOption, preference));
|
|
49
49
|
const [navLevel, setNavLevel] = useState("tabs");
|
|
50
50
|
useToggleShortcut();
|
|
@@ -102,7 +102,7 @@ export function AppShell({ product, organization, productRail: productRailOption
|
|
|
102
102
|
useAssistantShortcut(assistant);
|
|
103
103
|
const otherView = viewOption === undefined ? null : _jsx(OtherViewLink, { product: product, view: viewOption });
|
|
104
104
|
// Dans la barre de Basalte, seul Réglages reste au-dessus du compte : le reste passe dans son menu.
|
|
105
|
-
const footer = (_jsxs(_Fragment, { children: [account !== undefined ? sidebarFooter : null, productRail && project ? (_jsx("div", { className: "
|
|
105
|
+
const footer = (_jsxs(_Fragment, { children: [account !== undefined ? sidebarFooter : null, productRail && project ? (_jsx("div", { className: "pb-1", children: _jsxs("a", { href: project.settingsHref, className: SIDEBAR_ITEM, children: [_jsx(HugeiconsIcon, { icon: Settings02Icon, strokeWidth: 1.8, "aria-hidden": true, className: "size-5 shrink-0 lg:size-[1.125rem]" }), _jsx("span", { className: "truncate lg:rail:sr-only", children: "R\u00E9glages du projet" })] }) })) : null, _jsx(ShellCommonLinks, { links: productRail ? { ...(links?.settings ? { settings: links.settings } : {}), status: false } : (links ?? {}), statusPageUrl: statusPageUrl, otherView: productRail ? null : otherView }), account ?? sidebarFooter] }));
|
|
106
106
|
const menuLinks = productRail
|
|
107
107
|
? {
|
|
108
108
|
documentation: links?.documentation,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type ModuleWord } from "./modules.ts";
|
|
2
2
|
/**
|
|
3
|
-
* L'ordre et le choix des modules de la barre, propres à chaque personne
|
|
4
|
-
*
|
|
3
|
+
* L'ordre et le choix des modules de la barre, propres à chaque personne : Account les garde
|
|
4
|
+
* (`product-bar.ts`), et chaque origine en retient la dernière valeur, pour s'afficher sans attendre.
|
|
5
5
|
*/
|
|
6
6
|
export declare const MODULE_BAR_KEY = "hgdc-shell:modules";
|
|
7
7
|
export declare function saveModuleBar(order: ReadonlyArray<ModuleWord>): void;
|
|
8
|
+
/** L'ordre retenu dans cette origine, lu hors de React. */
|
|
9
|
+
export declare function storedModuleBar(): ReadonlyArray<ModuleWord>;
|
|
8
10
|
/** L'ordre retenu ; celui par défaut au rendu serveur. */
|
|
9
11
|
export declare function useModuleBar(): ReadonlyArray<ModuleWord>;
|
package/dist/module-bar-store.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useSyncExternalStore } from "react";
|
|
2
2
|
import { DEFAULT_MODULE_BAR, parseModuleBar } from "./modules.js";
|
|
3
3
|
/**
|
|
4
|
-
* L'ordre et le choix des modules de la barre, propres à chaque personne
|
|
5
|
-
*
|
|
4
|
+
* L'ordre et le choix des modules de la barre, propres à chaque personne : Account les garde
|
|
5
|
+
* (`product-bar.ts`), et chaque origine en retient la dernière valeur, pour s'afficher sans attendre.
|
|
6
6
|
*/
|
|
7
7
|
export const MODULE_BAR_KEY = "hgdc-shell:modules";
|
|
8
8
|
const EVENT = "hgdc-module-bar";
|
|
@@ -50,6 +50,10 @@ export function saveModuleBar(order) {
|
|
|
50
50
|
}
|
|
51
51
|
window.dispatchEvent(new Event(EVENT));
|
|
52
52
|
}
|
|
53
|
+
/** L'ordre retenu dans cette origine, lu hors de React. */
|
|
54
|
+
export function storedModuleBar() {
|
|
55
|
+
return read();
|
|
56
|
+
}
|
|
53
57
|
/** L'ordre retenu ; celui par défaut au rendu serveur. */
|
|
54
58
|
export function useModuleBar() {
|
|
55
59
|
return useSyncExternalStore(subscribe, read, () => DEFAULT_MODULE_BAR);
|
package/dist/module-bar.js
CHANGED
|
@@ -11,6 +11,7 @@ import { useState, useSyncExternalStore } from "react";
|
|
|
11
11
|
import { Button } from "./button.js";
|
|
12
12
|
import { cn } from "./cn.js";
|
|
13
13
|
import { saveModuleBar, useModuleBar } from "./module-bar-store.js";
|
|
14
|
+
import { pushModuleBar } from "./product-bar.js";
|
|
14
15
|
import { hiddenModules, moduleByWord, moveModule, shownModules } from "./modules.js";
|
|
15
16
|
import { ProductBadge } from "./product-badge.js";
|
|
16
17
|
import { PRODUCT_PRERENDER_ATTRIBUTE } from "./product-switch.js";
|
|
@@ -86,6 +87,7 @@ export function ShellNavigation({ product, nav, accountHref, level, onLevelChang
|
|
|
86
87
|
const { products, account } = shellProductEntries({ current: product, accountHref, pageOrigin });
|
|
87
88
|
return (_jsxs("div", { "data-slot": "shell-navigation", className: "flex min-h-0 flex-1 flex-col", children: [_jsxs("div", { "data-slot": "shell-manager", className: "shrink-0 px-3 pt-1", children: [_jsx("ul", { children: _jsx(ShellEntryLink, { entry: account, icon: UserGroupIcon }) }), _jsx("div", { "aria-hidden": true, className: "mx-2 mt-2 h-px bg-foreground/8" })] }), level === "modules" ? (_jsx(ModuleList, { entries: products, order: shownModules(order, product).filter((word) => !projectModules || word === product || projectModules.includes(word)), onOpenCurrent: () => onLevelChange("tabs"), onCustomize: () => onLevelChange("edit") })) : level === "edit" ? (_jsx(ModuleBarEditor, { order: order, onSave: (next) => {
|
|
88
89
|
saveModuleBar(next);
|
|
90
|
+
void pushModuleBar(next);
|
|
89
91
|
onLevelChange("modules");
|
|
90
92
|
}, onCancel: () => onLevelChange("modules") })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "shrink-0 px-3 pt-2", children: [_jsx("ul", { children: _jsx(SidebarNavBack, { label: "Retour aux modules", keepMenuOpen: true, render: _jsx("button", { type: "button", className: "w-full", onClick: () => onLevelChange("modules") }) }) }), _jsxs("div", { "data-slot": "shell-module-title", className: "flex min-h-9 items-center gap-2.5 px-3 text-sm font-semibold lg:text-shell-foreground lg:rail:hidden", children: [_jsx(ProductBadge, { product: product, className: "size-[1.125rem] shrink-0" }), _jsx("span", { className: "truncate font-heading", children: familyProductName(product) })] })] }), nav] }))] }));
|
|
91
93
|
}
|
package/dist/product-bar.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ProductView } from "./addresses.ts";
|
|
2
|
+
import { type ModuleWord } from "./modules.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Réglage « Barre des produits » (ADR 0014 de la famille), choisi dans Account :
|
|
4
5
|
* - `auto` : la barre sous `app.hgdc.ch`, pas sur `<mot>.hgdc.ch` ;
|
|
@@ -13,7 +14,7 @@ export type ProductBarPreference = "auto" | "always" | "never";
|
|
|
13
14
|
export declare const PRODUCT_BAR_PREFERENCES: ReadonlyArray<ProductBarPreference>;
|
|
14
15
|
/** Libellés du réglage, dans l'ordre d'affichage. */
|
|
15
16
|
export declare const PRODUCT_BAR_LABELS: Readonly<Record<ProductBarPreference, string>>;
|
|
16
|
-
/**
|
|
17
|
+
/** Réglages chez Account : `{ productBar, moduleBar }`, rien d'autre. */
|
|
17
18
|
export declare const ACCOUNT_PREFERENCES_URL = "https://account.hgdc.ch/api/preferences";
|
|
18
19
|
/** Valeur reçue (Account, stockage) : un réglage connu, sinon `auto`. */
|
|
19
20
|
export declare function parseProductBar(value: unknown): ProductBarPreference;
|
|
@@ -25,5 +26,11 @@ export declare function rememberProductBar(preference: ProductBarPreference): vo
|
|
|
25
26
|
export declare function useProductBarPreference(): ProductBarPreference;
|
|
26
27
|
/** Relit le réglage chez Account une fois par chargement de page ; une erreur garde la dernière valeur. */
|
|
27
28
|
export declare function syncProductBar(fetchImpl?: typeof fetch): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Enregistre la barre des modules chez Account, pour la retrouver sur un autre appareil. Un échec n'est
|
|
31
|
+
* pas signalé : la barre reste réglée dans cette origine, et repart au prochain chargement.
|
|
32
|
+
* `keepalive` : un clic sur un autre module recharge la page, l'envoi doit y survivre.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pushModuleBar(order: ReadonlyArray<ModuleWord>, fetchImpl?: typeof fetch): Promise<void>;
|
|
28
35
|
/** À monter une fois (la coque le fait) : relecture du réglage après l'hydratation. */
|
|
29
36
|
export declare function useProductBarSync(enabled?: boolean): void;
|
package/dist/product-bar.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { useEffect, useSyncExternalStore } from "react";
|
|
2
|
+
import { saveModuleBar, storedModuleBar } from "./module-bar-store.js";
|
|
3
|
+
import { parseModuleBar } from "./modules.js";
|
|
2
4
|
export const PRODUCT_BAR_PREFERENCES = ["auto", "always", "never"];
|
|
3
5
|
/** Libellés du réglage, dans l'ordre d'affichage. */
|
|
4
6
|
export const PRODUCT_BAR_LABELS = {
|
|
@@ -6,10 +8,12 @@ export const PRODUCT_BAR_LABELS = {
|
|
|
6
8
|
always: "Toujours",
|
|
7
9
|
never: "Jamais",
|
|
8
10
|
};
|
|
9
|
-
/**
|
|
11
|
+
/** Réglages chez Account : `{ productBar, moduleBar }`, rien d'autre. */
|
|
10
12
|
export const ACCOUNT_PREFERENCES_URL = "https://account.hgdc.ch/api/preferences";
|
|
11
13
|
const STORAGE_KEY = "hgdc-product-bar";
|
|
12
14
|
const EVENT = "hgdc-product-bar";
|
|
15
|
+
/** Présent tant qu'Account n'a pas confirmé la barre des modules réglée ici : elle ne doit pas être écrasée. */
|
|
16
|
+
const MODULE_BAR_UNSENT_KEY = "hgdc-shell:modules-unsent";
|
|
13
17
|
/** Valeur reçue (Account, stockage) : un réglage connu, sinon `auto`. */
|
|
14
18
|
export function parseProductBar(value) {
|
|
15
19
|
return PRODUCT_BAR_PREFERENCES.includes(value) ? value : "auto";
|
|
@@ -62,13 +66,69 @@ export async function syncProductBar(fetchImpl = fetch) {
|
|
|
62
66
|
if (!response.ok)
|
|
63
67
|
return;
|
|
64
68
|
const body = await response.json();
|
|
65
|
-
if (typeof body
|
|
69
|
+
if (typeof body !== "object" || body === null)
|
|
70
|
+
return;
|
|
71
|
+
if ("productBar" in body)
|
|
66
72
|
rememberProductBar(parseProductBar(body.productBar));
|
|
73
|
+
if (moduleBarUnsent()) {
|
|
74
|
+
await pushModuleBar(storedModuleBar(), fetchImpl);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Absente : la personne n'a encore rien réglé, cette origine garde ce qu'elle a.
|
|
78
|
+
if ("moduleBar" in body) {
|
|
79
|
+
const order = parseModuleBar(body.moduleBar);
|
|
80
|
+
if (order)
|
|
81
|
+
saveModuleBar(order);
|
|
82
|
+
}
|
|
67
83
|
}
|
|
68
84
|
catch {
|
|
69
85
|
// Account injoignable ou hors ligne : rien ne change.
|
|
70
86
|
}
|
|
71
87
|
}
|
|
88
|
+
function moduleBarUnsent() {
|
|
89
|
+
try {
|
|
90
|
+
return window.localStorage.getItem(MODULE_BAR_UNSENT_KEY) !== null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function markModuleBarUnsent(unsent) {
|
|
97
|
+
try {
|
|
98
|
+
if (unsent)
|
|
99
|
+
window.localStorage.setItem(MODULE_BAR_UNSENT_KEY, "1");
|
|
100
|
+
else
|
|
101
|
+
window.localStorage.removeItem(MODULE_BAR_UNSENT_KEY);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Stockage bloqué : la prochaine relecture reprendra la valeur d'Account.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Enregistre la barre des modules chez Account, pour la retrouver sur un autre appareil. Un échec n'est
|
|
109
|
+
* pas signalé : la barre reste réglée dans cette origine, et repart au prochain chargement.
|
|
110
|
+
* `keepalive` : un clic sur un autre module recharge la page, l'envoi doit y survivre.
|
|
111
|
+
*/
|
|
112
|
+
export async function pushModuleBar(order, fetchImpl = fetch) {
|
|
113
|
+
// Account n'accepte que les consoles de `hgdc.ch` : en local, en test, rien à envoyer.
|
|
114
|
+
if (!window.location.hostname.endsWith(".hgdc.ch"))
|
|
115
|
+
return;
|
|
116
|
+
markModuleBarUnsent(true);
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetchImpl(ACCOUNT_PREFERENCES_URL, {
|
|
119
|
+
method: "PUT",
|
|
120
|
+
credentials: "include",
|
|
121
|
+
keepalive: true,
|
|
122
|
+
headers: { "content-type": "application/json" },
|
|
123
|
+
body: JSON.stringify({ moduleBar: order }),
|
|
124
|
+
});
|
|
125
|
+
if (response.ok)
|
|
126
|
+
markModuleBarUnsent(false);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Account injoignable ou hors ligne.
|
|
130
|
+
}
|
|
131
|
+
}
|
|
72
132
|
let synced = false;
|
|
73
133
|
/** À monter une fois (la coque le fait) : relecture du réglage après l'hydratation. */
|
|
74
134
|
export function useProductBarSync(enabled = true) {
|
package/dist/products.js
CHANGED
|
@@ -106,7 +106,8 @@ export function isProductOpen(item) {
|
|
|
106
106
|
export function productMenuEntries(options = {}) {
|
|
107
107
|
const current = options.current?.toLowerCase();
|
|
108
108
|
const { manager, others } = managerFirst(options.catalog ?? HGDC_PRODUCTS);
|
|
109
|
-
const
|
|
109
|
+
const local = localManagerHref(options.accountHref);
|
|
110
|
+
const head = manager && local ? { ...manager, entryHref: local } : manager;
|
|
110
111
|
return [...(head ? [head] : []), ...others.filter(isProductOpen)].map((item) => ({
|
|
111
112
|
name: item.name,
|
|
112
113
|
href: item.entryHref,
|
|
@@ -159,12 +160,20 @@ export function shellProductEntries(options = {}) {
|
|
|
159
160
|
};
|
|
160
161
|
const { manager, others } = managerFirst(options.catalog ?? HGDC_PRODUCTS);
|
|
161
162
|
const head = manager ?? HGDC_MANAGER;
|
|
163
|
+
const local = localManagerHref(options.accountHref);
|
|
162
164
|
return {
|
|
163
165
|
products: others.filter(isProductOpen).map((item) => entry(item, item.entryHref, true)),
|
|
164
|
-
account: entry(head,
|
|
166
|
+
account: entry(head, local ?? head.entryHref, local === undefined),
|
|
165
167
|
};
|
|
166
168
|
}
|
|
167
169
|
/** Manager du catalogue, s'il y est, sorti des autres produits : il prend la place d'Account en tête. */
|
|
170
|
+
/**
|
|
171
|
+
* L'adresse reçue, seulement pour un Manager local : les consoles passent aussi l'adresse d'Account en
|
|
172
|
+
* production, et `account.hgdc.ch` redirige vers ID, pas vers Manager.
|
|
173
|
+
*/
|
|
174
|
+
function localManagerHref(accountHref) {
|
|
175
|
+
return accountHref?.startsWith("https://account.hgdc.ch") ? undefined : accountHref;
|
|
176
|
+
}
|
|
168
177
|
function managerFirst(catalog) {
|
|
169
178
|
const manager = catalog.find((item) => item.badge === "manager" && isProductOpen(item));
|
|
170
179
|
return { manager, others: catalog.filter((item) => item !== manager) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.1",
|
|
4
4
|
"description": "Interface de la H Collection : jetons de la DA, thèmes, polices et composants.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/ui#readme",
|