@mostajs/qrpanel 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +5 -4
- package/dist/client.d.ts +12 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +22 -3
- package/dist/client.js.map +1 -1
- package/dist/qr-image.d.ts +32 -0
- package/dist/qr-image.d.ts.map +1 -0
- package/dist/qr-image.js +68 -0
- package/dist/qr-image.js.map +1 -0
- package/llms.txt +103 -0
- package/package.json +18 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @mostajs/qrpanel — Changelog
|
|
2
|
+
|
|
3
|
+
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
|
4
|
+
|
|
5
|
+
## 0.5.0 — 2026-05-21
|
|
6
|
+
|
|
7
|
+
### Feature — `<QrImage>` : génération QR locale côté navigateur
|
|
8
|
+
|
|
9
|
+
Nouveau sous-export **`@mostajs/qrpanel/qr-image`** comblant le cas non
|
|
10
|
+
couvert jusqu'ici : générer un QR code **dans le navigateur**, sans
|
|
11
|
+
round-trip serveur ni service externe.
|
|
12
|
+
|
|
13
|
+
- **`<QrImage data size … />`** — composant React qui génère le QR
|
|
14
|
+
localement (data URL via `qrcode`) et le rend en `<img>`. Style-neutre
|
|
15
|
+
(`style` / `className`), utilisable sans Tailwind.
|
|
16
|
+
- **`useQrDataUrl(data, opts)`** — hook primitif renvoyant
|
|
17
|
+
`{ src, loading, error }`.
|
|
18
|
+
- **COEP-safe** : sous cross-origin isolation (`require-corp`), une image
|
|
19
|
+
QR cross-origin est bloquée par la politique ; la génération locale est
|
|
20
|
+
alors la seule option côté client.
|
|
21
|
+
- Sous-export dédié : `qrcode` n'entre dans le bundle client que pour les
|
|
22
|
+
consumers qui importent `@mostajs/qrpanel/qr-image`.
|
|
23
|
+
|
|
24
|
+
Complémentarité des trois briques :
|
|
25
|
+
- `./server` — `generateQr{Png,Svg,DataUrl}` (Node, thèmes, @resvg natif).
|
|
26
|
+
- `./client` — `<QrPanel>` (copie / partage / mailto, `qrSrc` fourni).
|
|
27
|
+
- `./qr-image` — `<QrImage>` (génération locale navigateur).
|
|
28
|
+
|
|
29
|
+
### Change — `@mostajs/auth` et `@resvg/resvg-js` en peer deps optionnelles
|
|
30
|
+
|
|
31
|
+
Ces deux dépendances ne servent qu'à `./server` (`@resvg` pour la
|
|
32
|
+
rasterisation thématique, `@mostajs/auth` pour `buildInviteUrls`). Elles
|
|
33
|
+
passent de `dependencies` à **`peerDependencies` optionnelles** : un
|
|
34
|
+
consumer de `./qr-image` ou `./client` n'embarque plus la galaxie
|
|
35
|
+
auth/rbac/net ni un binaire natif inutile. Les consumers de `./server`
|
|
36
|
+
doivent désormais installer `@mostajs/auth` et `@resvg/resvg-js`
|
|
37
|
+
eux-mêmes.
|
|
38
|
+
|
|
39
|
+
## 0.4.0
|
|
40
|
+
|
|
41
|
+
Générateur QR serveur (PNG/SVG/DataUrl) avec 12 thèmes intégrés
|
|
42
|
+
(image-as-frame, ECC=H), pilotage par `.qrconfig.json`, cross-OS sans
|
|
43
|
+
chromium ; composant React `<QrPanel>` (copie / ouvrir / mailto) ;
|
|
44
|
+
helper `buildInviteUrls` (invite-token HMAC) ; CLI `qrpanel`.
|
package/README.md
CHANGED
|
@@ -15,10 +15,11 @@ Aucune dépendance Chromium / Puppeteer / node-gyp. Pure-JS pour `qrcode`, prebu
|
|
|
15
15
|
```js
|
|
16
16
|
/** @type {import('next').NextConfig} */
|
|
17
17
|
const nextConfig = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
// Seulement @resvg/resvg-js (vraie dep native).
|
|
19
|
+
// ⚠️ Ne PAS mettre '@mostajs/qrpanel' ici : ça externaliserait
|
|
20
|
+
// aussi /client (composant React <QrPanel>), et un Server
|
|
21
|
+
// Component qui le rend crasherait avec "React.useState=null".
|
|
22
|
+
serverExternalPackages: ['@resvg/resvg-js'],
|
|
22
23
|
}
|
|
23
24
|
export default nextConfig
|
|
24
25
|
```
|
package/dist/client.d.ts
CHANGED
|
@@ -21,10 +21,21 @@ export interface QrPanelProps {
|
|
|
21
21
|
mailSubject?: string;
|
|
22
22
|
/** Pré-remplissage `mailto` body — `{url}` est remplacé par l'URL courante. */
|
|
23
23
|
mailBodyTemplate?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Destinataires placés dans `To:` du `mailto:` *(visibles entre eux)*.
|
|
26
|
+
* Liste d'emails (array ou string CSV). Combinable avec `mailBcc`.
|
|
27
|
+
*/
|
|
28
|
+
mailTo?: string[] | string;
|
|
29
|
+
/**
|
|
30
|
+
* Destinataires placés dans `Bcc:` *(invisibles entre eux — recommandé
|
|
31
|
+
* pour les listes de cohort où les participants ne doivent pas voir
|
|
32
|
+
* les adresses des autres)*. Liste d'emails (array ou string CSV).
|
|
33
|
+
*/
|
|
34
|
+
mailBcc?: string[] | string;
|
|
24
35
|
/** Taille de l'image QR. Default 260. */
|
|
25
36
|
qrSize?: number;
|
|
26
37
|
/** Classe CSS racine optionnelle. */
|
|
27
38
|
className?: string;
|
|
28
39
|
}
|
|
29
|
-
export declare function QrPanel({ modes, initialModeIndex, title, mailSubject, mailBodyTemplate, qrSize, className, }: QrPanelProps): import("react/jsx-runtime").JSX.Element | null;
|
|
40
|
+
export declare function QrPanel({ modes, initialModeIndex, title, mailSubject, mailBodyTemplate, mailTo, mailBcc, qrSize, className, }: QrPanelProps): import("react/jsx-runtime").JSX.Element | null;
|
|
30
41
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAA;IACX,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAA;IACX,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IAC3B,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAWD,wBAAgB,OAAO,CAAC,EACtB,KAAK,EAAE,gBAAoB,EAAE,KAAsC,EACnE,WAA0B,EAAE,gBAA+B,EAC3D,MAAM,EAAE,OAAO,EACf,MAAY,EAAE,SAAc,GAC7B,EAAE,YAAY,kDA8Gd"}
|
package/dist/client.js
CHANGED
|
@@ -16,14 +16,33 @@
|
|
|
16
16
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
17
17
|
import { useState, useId } from 'react';
|
|
18
18
|
const DEFAULT_BODY = 'Bonjour,\n\nVoici ton lien personnel :\n\n{url}\n\n';
|
|
19
|
-
|
|
19
|
+
/** Accepte une string CSV ou un array, normalise en array d'emails non-vides. */
|
|
20
|
+
function normaliseRecipients(input) {
|
|
21
|
+
if (!input)
|
|
22
|
+
return [];
|
|
23
|
+
const arr = Array.isArray(input) ? input : input.split(',');
|
|
24
|
+
return arr.map(s => s.trim()).filter(s => s.length > 0);
|
|
25
|
+
}
|
|
26
|
+
export function QrPanel({ modes, initialModeIndex = 0, title = 'QR code & lien d\'invitation', mailSubject = 'Invitation', mailBodyTemplate = DEFAULT_BODY, mailTo, mailBcc, qrSize = 260, className = '', }) {
|
|
20
27
|
const [idx, setIdx] = useState(Math.min(Math.max(0, initialModeIndex), modes.length - 1));
|
|
21
28
|
const [copied, setCopied] = useState(false);
|
|
22
29
|
const textareaId = useId();
|
|
23
30
|
const current = modes[idx] ?? modes[0];
|
|
24
31
|
if (!current)
|
|
25
32
|
return null;
|
|
26
|
-
const
|
|
33
|
+
const toList = normaliseRecipients(mailTo);
|
|
34
|
+
const bccList = normaliseRecipients(mailBcc);
|
|
35
|
+
const parts = [];
|
|
36
|
+
parts.push(`subject=${encodeURIComponent(mailSubject)}`);
|
|
37
|
+
parts.push(`body=${encodeURIComponent(mailBodyTemplate.replace('{url}', current.url))}`);
|
|
38
|
+
if (bccList.length > 0)
|
|
39
|
+
parts.push(`bcc=${encodeURIComponent(bccList.join(','))}`);
|
|
40
|
+
// `To:` est positionné juste après `mailto:` quand fourni (les
|
|
41
|
+
// destinataires To: sont rendus visibles entre eux par tous les clients).
|
|
42
|
+
// Les Bcc: passent par query param `&bcc=`.
|
|
43
|
+
const toPart = toList.length > 0 ? encodeURIComponent(toList.join(',')) : '';
|
|
44
|
+
const mailto = `mailto:${toPart}?${parts.join('&')}`;
|
|
45
|
+
const recipientCount = toList.length + bccList.length;
|
|
27
46
|
async function copy() {
|
|
28
47
|
try {
|
|
29
48
|
await navigator.clipboard.writeText(current.url);
|
|
@@ -35,6 +54,6 @@ export function QrPanel({ modes, initialModeIndex = 0, title = 'QR code & lien d
|
|
|
35
54
|
el?.select();
|
|
36
55
|
}
|
|
37
56
|
}
|
|
38
|
-
return (_jsxs("div", { className: 'bg-white border border-slate-200 rounded-xl p-4 space-y-3 ' + className, children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("h2", { className: "font-semibold text-slate-900", children: title }), modes.length > 1 && (_jsx("div", { className: "flex items-center gap-1 text-xs", children: modes.map((m, i) => (_jsx("button", { type: "button", onClick: () => setIdx(i), className: 'px-2 py-1 rounded ' + (i === idx ? 'bg-blue-600 text-white' : 'bg-slate-100 hover:bg-slate-200'), children: m.label }, m.key))) }))] }), current.description && (_jsx("p", { className: "text-xs text-slate-500", children: current.description })), _jsxs("div", { className: "grid md:grid-cols-2 gap-4 items-start", children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("img", { src: current.qrSrc, alt: `QR ${current.label}`, width: qrSize, height: qrSize, className: "border border-slate-200 rounded-lg" }), _jsx("a", { href: current.qrSrc, download: `qr-${current.key}.png`, className: "px-3 py-1.5 rounded text-xs bg-emerald-50 hover:bg-emerald-100 text-emerald-700 border border-emerald-200", children: "\u2B07 T\u00E9l\u00E9charger le PNG" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "block text-xs font-medium text-slate-700 mb-1", children: "Lien direct *(\u00E0 coller dans un email)*" }), _jsx("textarea", { id: textareaId, readOnly: true, value: current.url, rows: current.url.length > 60 ? 4 : 2, onClick: (e) => e.currentTarget.select(), className: "w-full px-2 py-1.5 border border-slate-300 rounded text-xs font-mono bg-slate-50 break-all" })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("button", { type: "button", onClick: copy, className: "px-3 py-1.5 rounded text-xs bg-blue-600 text-white hover:bg-blue-700", children: copied ? '✓ Copié' : '📋 Copier le lien' }), _jsx("a", { href: current.url, target: "_blank", rel: "noreferrer", className: "px-3 py-1.5 rounded text-xs bg-slate-100 hover:bg-slate-200 text-slate-700", children: "\u2197 Ouvrir" }),
|
|
57
|
+
return (_jsxs("div", { className: 'bg-white border border-slate-200 rounded-xl p-4 space-y-3 ' + className, children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("h2", { className: "font-semibold text-slate-900", children: title }), modes.length > 1 && (_jsx("div", { className: "flex items-center gap-1 text-xs", children: modes.map((m, i) => (_jsx("button", { type: "button", onClick: () => setIdx(i), className: 'px-2 py-1 rounded ' + (i === idx ? 'bg-blue-600 text-white' : 'bg-slate-100 hover:bg-slate-200'), children: m.label }, m.key))) }))] }), current.description && (_jsx("p", { className: "text-xs text-slate-500", children: current.description })), _jsxs("div", { className: "grid md:grid-cols-2 gap-4 items-start", children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("img", { src: current.qrSrc, alt: `QR ${current.label}`, width: qrSize, height: qrSize, className: "border border-slate-200 rounded-lg" }), _jsx("a", { href: current.qrSrc, download: `qr-${current.key}.png`, className: "px-3 py-1.5 rounded text-xs bg-emerald-50 hover:bg-emerald-100 text-emerald-700 border border-emerald-200", children: "\u2B07 T\u00E9l\u00E9charger le PNG" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "block text-xs font-medium text-slate-700 mb-1", children: "Lien direct *(\u00E0 coller dans un email)*" }), _jsx("textarea", { id: textareaId, readOnly: true, value: current.url, rows: current.url.length > 60 ? 4 : 2, onClick: (e) => e.currentTarget.select(), className: "w-full px-2 py-1.5 border border-slate-300 rounded text-xs font-mono bg-slate-50 break-all" })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("button", { type: "button", onClick: copy, className: "px-3 py-1.5 rounded text-xs bg-blue-600 text-white hover:bg-blue-700", children: copied ? '✓ Copié' : '📋 Copier le lien' }), _jsx("a", { href: current.url, target: "_blank", rel: "noreferrer", className: "px-3 py-1.5 rounded text-xs bg-slate-100 hover:bg-slate-200 text-slate-700", children: "\u2197 Ouvrir" }), _jsxs("a", { href: mailto, className: "px-3 py-1.5 rounded text-xs bg-amber-50 hover:bg-amber-100 text-amber-700 border border-amber-200", title: recipientCount > 0 ? `Ouvre le client mail avec ${recipientCount} destinataire(s) pré-remplis` : 'Ouvre le client mail avec sujet + corps pré-remplis', children: ["\u2709 Pr\u00E9-remplir un mail", recipientCount > 0 ? ` (${recipientCount})` : ''] })] })] })] })] }));
|
|
39
58
|
}
|
|
40
59
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,0CAA0C;AAC1C,EAAE;AACF,mEAAmE;AACnE,2DAA2D;AAC3D,uEAAuE;AACvE,8CAA8C;AAC9C,EAAE;AACF,kEAAkE;AAClE,8DAA8D;AAC9D,EAAE;AACF,qEAAqE;AACrE,qEAAqE;AACrE,QAAQ;AAER,YAAY,CAAA;;AACZ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface QrDataUrlOptions {
|
|
2
|
+
/** Côté de l'image en pixels. Défaut 200. */
|
|
3
|
+
size?: number;
|
|
4
|
+
/** Marge blanche autour du QR (en modules). Défaut 2. */
|
|
5
|
+
margin?: number;
|
|
6
|
+
/** Correction d'erreur : L 7 % | M 15 % | Q 25 % | H 30 %. Défaut 'M'. */
|
|
7
|
+
errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
|
|
8
|
+
/** Couleur des modules sombres. Défaut '#0f172a'. */
|
|
9
|
+
darkColor?: string;
|
|
10
|
+
/** Couleur du fond. Défaut '#ffffff'. */
|
|
11
|
+
lightColor?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface UseQrDataUrlResult {
|
|
14
|
+
/** Data URL `data:image/png;base64,…` du QR, ou '' tant que non prêt. */
|
|
15
|
+
src: string;
|
|
16
|
+
/** True pendant la génération. */
|
|
17
|
+
loading: boolean;
|
|
18
|
+
/** Message d'erreur si la génération a échoué, sinon null. */
|
|
19
|
+
error: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** Hook : génère le data URL d'un QR localement (navigateur). */
|
|
22
|
+
export declare function useQrDataUrl(data: string, opts?: QrDataUrlOptions): UseQrDataUrlResult;
|
|
23
|
+
export interface QrImageProps extends QrDataUrlOptions {
|
|
24
|
+
/** Donnée encodée dans le QR (URL, texte…). */
|
|
25
|
+
data: string;
|
|
26
|
+
alt?: string;
|
|
27
|
+
style?: React.CSSProperties;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
/** QR code généré localement et rendu en `<img>` data-URL (COEP-safe). */
|
|
31
|
+
export default function QrImage({ data, alt, style, className, ...opts }: QrImageProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
//# sourceMappingURL=qr-image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-image.d.ts","sourceRoot":"","sources":["../src/qr-image.tsx"],"names":[],"mappings":"AAuBA,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0EAA0E;IAC1E,oBAAoB,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IAC5C,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAA;IACX,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB,GAAG,kBAAkB,CAwB1F;AAED,MAAM,WAAW,YAAa,SAAQ,gBAAgB;IACpD,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,0EAA0E;AAC1E,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,EAC9B,IAAI,EAAE,GAAe,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,EACjD,EAAE,YAAY,2CAqBd"}
|
package/dist/qr-image.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @mostajs/qrpanel/qr-image — composant QR généré côté navigateur
|
|
2
|
+
// Author: Dr Hamid MADANI <drmdh@msn.com>
|
|
3
|
+
//
|
|
4
|
+
// QR code généré 100% dans le navigateur (data URL via `qrcode`), sans
|
|
5
|
+
// appel réseau ni round-trip serveur. Complète les deux briques
|
|
6
|
+
// existantes de qrpanel :
|
|
7
|
+
// - `<QrPanel>` (./client) attend un `qrSrc` déjà fourni par l'app.
|
|
8
|
+
// - `generateQr*` (./server) génère côté Node uniquement (@resvg natif).
|
|
9
|
+
// `<QrImage>` couvre le cas manquant : génération locale, côté client.
|
|
10
|
+
//
|
|
11
|
+
// Cas d'usage clé — COEP `require-corp` : sous cross-origin isolation
|
|
12
|
+
// (requise par ex. par ffmpeg.wasm / SharedArrayBuffer), une image QR
|
|
13
|
+
// servie par un service externe est bloquée par la politique. La
|
|
14
|
+
// génération locale est alors la seule option côté client.
|
|
15
|
+
//
|
|
16
|
+
// Sub-export dédié (`@mostajs/qrpanel/qr-image`) : `qrcode` n'est
|
|
17
|
+
// embarqué dans le bundle client que pour les consumers qui l'importent.
|
|
18
|
+
'use client';
|
|
19
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
20
|
+
import { useEffect, useState } from 'react';
|
|
21
|
+
import QRCode from 'qrcode';
|
|
22
|
+
/** Hook : génère le data URL d'un QR localement (navigateur). */
|
|
23
|
+
export function useQrDataUrl(data, opts = {}) {
|
|
24
|
+
const { size = 200, margin = 2, errorCorrectionLevel = 'M', darkColor = '#0f172a', lightColor = '#ffffff', } = opts;
|
|
25
|
+
const [src, setSrc] = useState('');
|
|
26
|
+
const [loading, setLoading] = useState(true);
|
|
27
|
+
const [error, setError] = useState(null);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let alive = true;
|
|
30
|
+
setLoading(true);
|
|
31
|
+
setError(null);
|
|
32
|
+
setSrc('');
|
|
33
|
+
QRCode.toDataURL(data, {
|
|
34
|
+
width: size, margin, errorCorrectionLevel,
|
|
35
|
+
color: { dark: darkColor, light: lightColor },
|
|
36
|
+
})
|
|
37
|
+
.then((url) => { if (alive) {
|
|
38
|
+
setSrc(url);
|
|
39
|
+
setLoading(false);
|
|
40
|
+
} })
|
|
41
|
+
.catch((e) => {
|
|
42
|
+
if (alive) {
|
|
43
|
+
setError(e?.message || 'QR generation failed');
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return () => { alive = false; };
|
|
48
|
+
}, [data, size, margin, errorCorrectionLevel, darkColor, lightColor]);
|
|
49
|
+
return { src, loading, error };
|
|
50
|
+
}
|
|
51
|
+
/** QR code généré localement et rendu en `<img>` data-URL (COEP-safe). */
|
|
52
|
+
export default function QrImage({ data, alt = 'QR code', style, className, ...opts }) {
|
|
53
|
+
const size = opts.size ?? 200;
|
|
54
|
+
const { src, loading, error } = useQrDataUrl(data, opts);
|
|
55
|
+
const box = {
|
|
56
|
+
width: size, height: size, display: 'flex',
|
|
57
|
+
alignItems: 'center', justifyContent: 'center',
|
|
58
|
+
background: '#fff', borderRadius: 6, border: '1px solid #e2e8f0',
|
|
59
|
+
fontSize: 11, color: '#94a3b8', textAlign: 'center',
|
|
60
|
+
...style,
|
|
61
|
+
};
|
|
62
|
+
if (error)
|
|
63
|
+
return _jsx("div", { style: box, className: className, children: "QR indisponible" });
|
|
64
|
+
if (loading || !src)
|
|
65
|
+
return _jsx("div", { style: box, className: className, "aria-busy": "true", children: "\u2026" });
|
|
66
|
+
return (_jsx("img", { src: src, alt: alt, width: size, height: size, style: { background: '#fff', borderRadius: 6, display: 'block', ...style }, className: className }));
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=qr-image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-image.js","sourceRoot":"","sources":["../src/qr-image.tsx"],"names":[],"mappings":"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"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @mostajs/qrpanel — fiche LLM
|
|
2
|
+
> Panneau QR code : générateur serveur PNG/SVG à 12 thèmes (image-as-frame, ECC=H), piloté par config (.qrconfig.json), sans chromium, + composant React <QrPanel> avec copier/partager/mailto.
|
|
3
|
+
|
|
4
|
+
- Version: 0.5.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
|
|
5
|
+
- Chemin: mostajs/mosta-qrpanel · Statut audit: complet (dist/)
|
|
6
|
+
|
|
7
|
+
## RÔLE
|
|
8
|
+
Génère des QR codes pour apps Node/React. Trois briques complémentaires : (1) serveur —
|
|
9
|
+
PNG/SVG/DataURL thématiques rendus côté Node via @resvg/resvg-js (pas de chromium) ;
|
|
10
|
+
(2) client `<QrPanel>` — panneau d'invitation interactif (QR + lien + copier/partager) ;
|
|
11
|
+
(3) `<QrImage>` — génération locale navigateur, COEP-safe. Inclut un CLI `qrpanel`.
|
|
12
|
+
|
|
13
|
+
## INSTALLATION
|
|
14
|
+
npm i @mostajs/qrpanel
|
|
15
|
+
Dépendance directe : qrcode. Peers optionnelles : react (>=18 <20) pour ./client et
|
|
16
|
+
./qr-image ; @mostajs/auth + @resvg/resvg-js requis uniquement par ./server.
|
|
17
|
+
|
|
18
|
+
## EXPORTS
|
|
19
|
+
- ./server : generateQrPng, generateQrSvg, generateQrDataUrl, buildInviteUrls + (ré-export) themes & config
|
|
20
|
+
- ./client : QrPanel
|
|
21
|
+
- ./qr-image : QrImage (default), useQrDataUrl
|
|
22
|
+
- ./themes : THEMES, THEME_KEYS, listThemes, getTheme, pickRandomTheme, buildThemeFrameSvg
|
|
23
|
+
- ./config : DEFAULT_CONFIG, loadQrConfig, ensureQrConfig, clearConfigCache
|
|
24
|
+
- "." (barrel) : ré-exporte server + client + themes + config
|
|
25
|
+
- bin `qrpanel` → CLI
|
|
26
|
+
|
|
27
|
+
## EXPORTS PAR SOUS-CHEMIN
|
|
28
|
+
- "." → barrel complet (server + client + themes + config) — ⚠ tire le code natif serveur
|
|
29
|
+
- "./server" → generateQr*, buildInviteUrls, themes, config (Node uniquement)
|
|
30
|
+
- "./client" → QrPanel (React, qrSrc fourni par l'app)
|
|
31
|
+
- "./qr-image" → QrImage, useQrDataUrl (React, génération locale)
|
|
32
|
+
- "./themes" → registry des 12 thèmes natifs
|
|
33
|
+
- "./config" → chargement/écriture de .qrconfig.json
|
|
34
|
+
|
|
35
|
+
## API — SIGNATURES
|
|
36
|
+
- function generateQrPng(text: string, opts?: QrOptions): Promise<Buffer>
|
|
37
|
+
- function generateQrSvg(text: string, opts?: QrOptions): Promise<string>
|
|
38
|
+
- function generateQrDataUrl(text: string, opts?: QrOptions): Promise<string> // data:image/png;base64
|
|
39
|
+
- function buildInviteUrls(opts: BuildInviteUrlsOptions): InviteUrls
|
|
40
|
+
- function QrPanel(props: QrPanelProps): JSX.Element | null
|
|
41
|
+
- function QrImage(props: QrImageProps): JSX.Element
|
|
42
|
+
- function useQrDataUrl(data: string, opts?: QrDataUrlOptions): { src: string; loading: boolean; error: string|null }
|
|
43
|
+
- function listThemes(): ThemeKey[]
|
|
44
|
+
- function getTheme(key: ThemeKey): ThemeAsset
|
|
45
|
+
- function pickRandomTheme(pool?: ThemeKey[]): ThemeKey
|
|
46
|
+
- function buildThemeFrameSvg(theme: ThemeAsset, opts: BuildFrameOpts): string
|
|
47
|
+
- function loadQrConfig(cwd?: string): QrConfig
|
|
48
|
+
- function ensureQrConfig(cwd?: string, overrides?: Partial<QrConfigDefaults>): string
|
|
49
|
+
- function clearConfigCache(): void
|
|
50
|
+
|
|
51
|
+
## TYPES CLÉS
|
|
52
|
+
- QrOptions { width?=600; margin?=2; errorCorrectionLevel?='L'|'M'|'Q'|'H'; darkColor?='#0f172a';
|
|
53
|
+
lightColor?='#ffffff'; genimage?: boolean; theme?: ThemeKey|'random'|'none'|{ svg; label? };
|
|
54
|
+
themePool?: ThemeKey[]; framePadding?=0.13; centerWhiteRatio?=0.62; themeOpacity?=1; themeColor?='#1e293b' }
|
|
55
|
+
- BuildInviteUrlsOptions { baseUrl: string; directPath: string; inviteSecret: string|Buffer;
|
|
56
|
+
inviteId: string; ttlMs?; invitePath?='/invite'; inviteMeta? }
|
|
57
|
+
- InviteUrls { directUrl: string; inviteUrl: string; inviteToken: string }
|
|
58
|
+
- QrPanelMode { key; label; url; qrSrc: string; description? }
|
|
59
|
+
- QrPanelProps { modes: QrPanelMode[]; initialModeIndex?=0; title?; mailSubject?; mailBodyTemplate?;
|
|
60
|
+
mailTo?: string[]|string; mailBcc?: string[]|string; qrSize?=260; className? }
|
|
61
|
+
- QrImageProps extends QrDataUrlOptions { data: string; alt?; style?; className? }
|
|
62
|
+
- QrDataUrlOptions { size?=200; margin?=2; errorCorrectionLevel?='M'; darkColor?='#0f172a'; lightColor?='#ffffff' }
|
|
63
|
+
- ThemeKey = 'baby'|'animals'|'science'|'physics'|'chemistry'|'math'|'nature'|'tech'|'space'|'music'|'book'|'health'
|
|
64
|
+
- ThemeAsset { key: ThemeKey; label: string; motif: string }
|
|
65
|
+
- QrConfig { default: QrConfigDefaults; customThemes?: Record<string,{svg;label?}> }
|
|
66
|
+
- QrFormat = 'svg'|'png'|'dataUrl' ; QrEcc = 'L'|'M'|'Q'|'H'
|
|
67
|
+
|
|
68
|
+
## PATTERN
|
|
69
|
+
```ts
|
|
70
|
+
// Serveur — QR thématique en route API (Node uniquement)
|
|
71
|
+
import { generateQrPng } from '@mostajs/qrpanel/server';
|
|
72
|
+
const png = await generateQrPng('https://app.example.com/x', { theme: 'tech', errorCorrectionLevel: 'H' });
|
|
73
|
+
|
|
74
|
+
// Client — panneau d'invitation
|
|
75
|
+
import { QrPanel } from '@mostajs/qrpanel/client';
|
|
76
|
+
<QrPanel modes={[{ key: 'direct', label: 'Lien', url, qrSrc: '/api/qr?u=' + url }]} />
|
|
77
|
+
|
|
78
|
+
// Génération locale navigateur (COEP-safe)
|
|
79
|
+
import QrImage from '@mostajs/qrpanel/qr-image';
|
|
80
|
+
<QrImage data="https://example.com" size={240} />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## DÉPEND DE
|
|
84
|
+
- @mostajs/auth (peer optionnel, >=3.2.0) — requis par buildInviteUrls (signature HMAC du token)
|
|
85
|
+
- @resvg/resvg-js (peer optionnel, >=2.6.2) — requis par generateQr* (rasterisation SVG → PNG)
|
|
86
|
+
- react (peer optionnel, >=18 <20) — pour ./client et ./qr-image
|
|
87
|
+
|
|
88
|
+
## PIÈGES
|
|
89
|
+
- Ne PAS importer le barrel "." ni "./server" dans un bundle navigateur : @resvg/resvg-js
|
|
90
|
+
est un binaire natif Node. Côté client, utiliser exclusivement ./client ou ./qr-image.
|
|
91
|
+
- `<QrPanel>` NE génère PAS le QR : l'app fournit `qrSrc` (endpoint API ou data URL).
|
|
92
|
+
- `<QrPanel>` requiert Tailwind (style compilé par l'app) ; `<QrImage>` est style-neutre.
|
|
93
|
+
- `genimage: false` court-circuite tout le pipeline thématique (QR pur, comportement legacy).
|
|
94
|
+
- `theme: 'H'` (ECC=30%) recommandé pour les thèmes image-as-frame ; un ECC plus bas peut
|
|
95
|
+
rendre le QR illisible avec un cadre.
|
|
96
|
+
- `loadQrConfig` peut auto-créer `.qrconfig.json` à `process.cwd()` ; désactiver via
|
|
97
|
+
`QRPANEL_AUTO_ENSURE=false`. Échec d'écriture → fallback transparent sur DEFAULT_CONFIG.
|
|
98
|
+
- `mailBcc` recommandé pour les cohortes (les destinataires ne se voient pas) vs `mailTo`.
|
|
99
|
+
|
|
100
|
+
## RÉFÉRENCES
|
|
101
|
+
- README.md
|
|
102
|
+
- CHANGELOG.md
|
|
103
|
+
- src/, dist/cli.js (CLI `npx qrpanel`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/qrpanel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "QR code panel — server-side PNG/SVG generator with 12 built-in themes (image-as-frame composite, ECC=H), config-driven (.qrconfig.json), cross-OS no chromium, + React <QrPanel> client with copy/share/mailto.",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
"import": "./dist/client.js",
|
|
27
27
|
"default": "./dist/client.js"
|
|
28
28
|
},
|
|
29
|
+
"./qr-image": {
|
|
30
|
+
"types": "./dist/qr-image.d.ts",
|
|
31
|
+
"import": "./dist/qr-image.js",
|
|
32
|
+
"default": "./dist/qr-image.js"
|
|
33
|
+
},
|
|
29
34
|
"./themes": {
|
|
30
35
|
"types": "./dist/themes.d.ts",
|
|
31
36
|
"import": "./dist/themes.js",
|
|
@@ -40,7 +45,9 @@
|
|
|
40
45
|
"files": [
|
|
41
46
|
"dist",
|
|
42
47
|
"LICENSE",
|
|
43
|
-
"README.md"
|
|
48
|
+
"README.md",
|
|
49
|
+
"CHANGELOG.md",
|
|
50
|
+
"llms.txt"
|
|
44
51
|
],
|
|
45
52
|
"keywords": [
|
|
46
53
|
"qrcode",
|
|
@@ -58,20 +65,24 @@
|
|
|
58
65
|
"prepublishOnly": "npm run build"
|
|
59
66
|
},
|
|
60
67
|
"dependencies": {
|
|
61
|
-
"qrcode": "^1.5.4"
|
|
62
|
-
"@mostajs/auth": "^3.2.0",
|
|
63
|
-
"@resvg/resvg-js": "^2.6.2"
|
|
68
|
+
"qrcode": "^1.5.4"
|
|
64
69
|
},
|
|
65
70
|
"peerDependencies": {
|
|
66
|
-
"react": ">=18 <20"
|
|
71
|
+
"react": ">=18 <20",
|
|
72
|
+
"@mostajs/auth": ">=3.2.0",
|
|
73
|
+
"@resvg/resvg-js": ">=2.6.2"
|
|
67
74
|
},
|
|
68
75
|
"peerDependenciesMeta": {
|
|
69
|
-
"react": { "optional": true }
|
|
76
|
+
"react": { "optional": true },
|
|
77
|
+
"@mostajs/auth": { "optional": true },
|
|
78
|
+
"@resvg/resvg-js": { "optional": true }
|
|
70
79
|
},
|
|
71
80
|
"devDependencies": {
|
|
72
81
|
"@types/node": "^22.0.0",
|
|
73
82
|
"@types/qrcode": "^1.5.5",
|
|
74
83
|
"@types/react": "^19.0.0",
|
|
84
|
+
"@mostajs/auth": "^3.2.0",
|
|
85
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
75
86
|
"react": "^19.0.0",
|
|
76
87
|
"typescript": "^5.6.0"
|
|
77
88
|
}
|