react-cheminfo 0.59.0 → 0.61.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/README.md +36 -2
- package/lib/about/ui/AboutBuildDetails.d.ts +15 -0
- package/lib/about/ui/AboutBuildDetails.d.ts.map +1 -0
- package/lib/about/ui/AboutBuildDetails.js +17 -0
- package/lib/about/ui/AboutBuildDetails.js.map +1 -0
- package/lib/about/ui/AboutPage.d.ts +8 -0
- package/lib/about/ui/AboutPage.d.ts.map +1 -1
- package/lib/about/ui/AboutPage.js +12 -3
- package/lib/about/ui/AboutPage.js.map +1 -1
- package/lib/about/ui/AboutSignIn.d.ts +33 -0
- package/lib/about/ui/AboutSignIn.d.ts.map +1 -0
- package/lib/about/ui/AboutSignIn.js +28 -0
- package/lib/about/ui/AboutSignIn.js.map +1 -0
- package/lib/about/ui/AboutVersion.d.ts +4 -2
- package/lib/about/ui/AboutVersion.d.ts.map +1 -1
- package/lib/about/ui/AboutVersion.js +21 -3
- package/lib/about/ui/AboutVersion.js.map +1 -1
- package/lib/about/ui/index.d.ts +2 -2
- package/lib/about/ui/index.d.ts.map +1 -1
- package/lib/about/ui/index.js +1 -1
- package/lib/about/ui/index.js.map +1 -1
- package/lib/chrome/ui/NavEntry.d.ts +16 -0
- package/lib/chrome/ui/NavEntry.d.ts.map +1 -0
- package/lib/chrome/ui/NavEntry.js +27 -0
- package/lib/chrome/ui/NavEntry.js.map +1 -0
- package/lib/chrome/ui/SiteHeader.d.ts +11 -4
- package/lib/chrome/ui/SiteHeader.d.ts.map +1 -1
- package/lib/chrome/ui/SiteHeader.js +4 -26
- package/lib/chrome/ui/SiteHeader.js.map +1 -1
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/locales/de.json +11 -3
- package/lib/locales/en.json +11 -3
- package/lib/locales/es.json +11 -3
- package/lib/locales/fr.json +11 -3
- package/lib/locales/it.json +11 -3
- package/lib/moleculesSources/core/index.d.ts +3 -0
- package/lib/moleculesSources/core/index.d.ts.map +1 -0
- package/lib/moleculesSources/core/index.js +2 -0
- package/lib/moleculesSources/core/index.js.map +1 -0
- package/lib/moleculesSources/core/moleculesSources.d.ts +55 -0
- package/lib/moleculesSources/core/moleculesSources.d.ts.map +1 -0
- package/lib/moleculesSources/core/moleculesSources.js +63 -0
- package/lib/moleculesSources/core/moleculesSources.js.map +1 -0
- package/lib/moleculesSources/ui/MoleculesSourceBadge.d.ts +18 -0
- package/lib/moleculesSources/ui/MoleculesSourceBadge.d.ts.map +1 -0
- package/lib/moleculesSources/ui/MoleculesSourceBadge.js +22 -0
- package/lib/moleculesSources/ui/MoleculesSourceBadge.js.map +1 -0
- package/lib/moleculesSources/ui/SourceBadge.d.ts +57 -0
- package/lib/moleculesSources/ui/SourceBadge.d.ts.map +1 -0
- package/lib/moleculesSources/ui/SourceBadge.js +29 -0
- package/lib/moleculesSources/ui/SourceBadge.js.map +1 -0
- package/lib/moleculesSources/ui/index.d.ts +7 -0
- package/lib/moleculesSources/ui/index.d.ts.map +1 -0
- package/lib/moleculesSources/ui/index.js +4 -0
- package/lib/moleculesSources/ui/index.js.map +1 -0
- package/lib/moleculesSources/ui/useSourceLooks.d.ts +24 -0
- package/lib/moleculesSources/ui/useSourceLooks.d.ts.map +1 -0
- package/lib/moleculesSources/ui/useSourceLooks.js +53 -0
- package/lib/moleculesSources/ui/useSourceLooks.js.map +1 -0
- package/lib/moleculesToken/core/index.d.ts +1 -0
- package/lib/moleculesToken/core/index.d.ts.map +1 -1
- package/lib/moleculesToken/core/index.js +1 -0
- package/lib/moleculesToken/core/index.js.map +1 -1
- package/lib/moleculesToken/core/tokenFix.d.ts +18 -0
- package/lib/moleculesToken/core/tokenFix.d.ts.map +1 -0
- package/lib/moleculesToken/core/tokenFix.js +27 -0
- package/lib/moleculesToken/core/tokenFix.js.map +1 -0
- package/lib/moleculesToken/ui/MoleculesTokenFix.d.ts +22 -0
- package/lib/moleculesToken/ui/MoleculesTokenFix.d.ts.map +1 -0
- package/lib/moleculesToken/ui/MoleculesTokenFix.js +35 -0
- package/lib/moleculesToken/ui/MoleculesTokenFix.js.map +1 -0
- package/lib/moleculesToken/ui/MoleculesTokenTable.d.ts +6 -1
- package/lib/moleculesToken/ui/MoleculesTokenTable.d.ts.map +1 -1
- package/lib/moleculesToken/ui/MoleculesTokenTable.js +7 -75
- package/lib/moleculesToken/ui/MoleculesTokenTable.js.map +1 -1
- package/lib/moleculesToken/ui/index.d.ts +2 -0
- package/lib/moleculesToken/ui/index.d.ts.map +1 -1
- package/lib/moleculesToken/ui/index.js +1 -0
- package/lib/moleculesToken/ui/index.js.map +1 -1
- package/lib/moleculesToken/ui/tokenDetails.d.ts +13 -0
- package/lib/moleculesToken/ui/tokenDetails.d.ts.map +1 -0
- package/lib/moleculesToken/ui/tokenDetails.js +73 -0
- package/lib/moleculesToken/ui/tokenDetails.js.map +1 -0
- package/lib/server/siteFiles.d.ts +28 -0
- package/lib/server/siteFiles.d.ts.map +1 -0
- package/lib/server/siteFiles.js +57 -0
- package/lib/server/siteFiles.js.map +1 -0
- package/lib/server/staticSite.d.ts +42 -0
- package/lib/server/staticSite.d.ts.map +1 -0
- package/lib/server/staticSite.js +114 -0
- package/lib/server/staticSite.js.map +1 -0
- package/lib/server.d.ts +4 -0
- package/lib/server.d.ts.map +1 -0
- package/lib/server.js +3 -0
- package/lib/server.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/package.json +12 -1
- package/src/about/ui/AboutBuildDetails.tsx +45 -0
- package/src/about/ui/AboutPage.tsx +27 -13
- package/src/about/ui/AboutSignIn.tsx +63 -0
- package/src/about/ui/AboutVersion.tsx +27 -9
- package/src/about/ui/index.ts +2 -2
- package/src/chrome/ui/NavEntry.tsx +41 -0
- package/src/chrome/ui/SiteHeader.tsx +15 -41
- package/src/core.ts +1 -0
- package/src/locales/de.json +11 -3
- package/src/locales/en.json +11 -3
- package/src/locales/es.json +11 -3
- package/src/locales/fr.json +11 -3
- package/src/locales/it.json +11 -3
- package/src/moleculesSources/core/index.ts +11 -0
- package/src/moleculesSources/core/moleculesSources.ts +102 -0
- package/src/moleculesSources/ui/MoleculesSourceBadge.tsx +44 -0
- package/src/moleculesSources/ui/SourceBadge.tsx +117 -0
- package/src/moleculesSources/ui/index.ts +6 -0
- package/src/moleculesSources/ui/useSourceLooks.ts +70 -0
- package/src/moleculesToken/core/index.ts +1 -0
- package/src/moleculesToken/core/tokenFix.ts +37 -0
- package/src/moleculesToken/ui/MoleculesTokenFix.tsx +95 -0
- package/src/moleculesToken/ui/MoleculesTokenTable.tsx +20 -94
- package/src/moleculesToken/ui/index.ts +2 -0
- package/src/moleculesToken/ui/tokenDetails.tsx +91 -0
- package/src/server/siteFiles.ts +64 -0
- package/src/server/staticSite.ts +188 -0
- package/src/server.ts +7 -0
- package/src/ui.ts +1 -0
- package/styles/chrome.css +159 -0
- package/lib/about/ui/AboutBuild.d.ts +0 -18
- package/lib/about/ui/AboutBuild.d.ts.map +0 -1
- package/lib/about/ui/AboutBuild.js +0 -37
- package/lib/about/ui/AboutBuild.js.map +0 -1
- package/src/about/ui/AboutBuild.tsx +0 -54
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
/** Options of {@link staticSite}. */
|
|
3
|
+
export interface StaticSiteOptions {
|
|
4
|
+
/** The directory the build wrote, e.g. `dist`. */
|
|
5
|
+
root: string;
|
|
6
|
+
/**
|
|
7
|
+
* The analytics snippet, put at the end of the head of every page, as
|
|
8
|
+
* `TRACKING_SCRIPT` holds it.
|
|
9
|
+
* @default undefined - no snippet
|
|
10
|
+
*/
|
|
11
|
+
trackingScript?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Addresses the site does not have any more, and the one that does their
|
|
14
|
+
* work. The query is carried over, so a link handed out before the move
|
|
15
|
+
* keeps its configuration.
|
|
16
|
+
* @default {}
|
|
17
|
+
*/
|
|
18
|
+
redirects?: Readonly<Record<string, string>>;
|
|
19
|
+
/**
|
|
20
|
+
* Address prefixes another plugin answers, such as the API, which never
|
|
21
|
+
* answer a page: an unknown address there is the API's own 404.
|
|
22
|
+
* @default ['/v1/']
|
|
23
|
+
*/
|
|
24
|
+
reserved?: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Serve a site's build from Node, with the rules every static host of the
|
|
28
|
+
* family follows (static-web-server's `sws.toml`, Cloudflare Pages): a real
|
|
29
|
+
* file per address and no fallback page, `/titration` answered without a
|
|
30
|
+
* redirect, pages checked on every visit and hashed bundles kept for a year,
|
|
31
|
+
* the analytics snippet in every page, `404.html` for an address the site does
|
|
32
|
+
* not have, and `/health` for the deploy script.
|
|
33
|
+
*
|
|
34
|
+
* Register it next to the API; a build missing (a server started before the
|
|
35
|
+
* frontend was built) leaves the API alone:
|
|
36
|
+
*
|
|
37
|
+
* await app.register(staticSite, { root: 'dist', trackingScript });
|
|
38
|
+
* @param fastify - The server.
|
|
39
|
+
* @param options - The build, the snippet and the retired addresses.
|
|
40
|
+
*/
|
|
41
|
+
export declare function staticSite(fastify: FastifyInstance, options: StaticSiteOptions): Promise<void>;
|
|
42
|
+
//# sourceMappingURL=staticSite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staticSite.d.ts","sourceRoot":"","sources":["../../src/server/staticSite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,SAAS,CAAC;AAMjB,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAiBD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA4Df"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { injectTrackingScript } from "../seo/core/trackingScript.js";
|
|
4
|
+
import { isHashedBundle, listSiteFiles, resolveAddress } from "./siteFiles.js";
|
|
5
|
+
/** A page is checked on every visit: it names the bundles of its own build. */
|
|
6
|
+
const PAGE_CACHE = 'no-cache';
|
|
7
|
+
/** A bundle is named by its content, so it never changes under its name. */
|
|
8
|
+
const BUNDLE_CACHE = 'public, max-age=31536000, immutable';
|
|
9
|
+
/** Kept out of the API's documentation (`@fastify/swagger` reads `hide`). */
|
|
10
|
+
const HIDDEN = { hide: true };
|
|
11
|
+
/**
|
|
12
|
+
* Serve a site's build from Node, with the rules every static host of the
|
|
13
|
+
* family follows (static-web-server's `sws.toml`, Cloudflare Pages): a real
|
|
14
|
+
* file per address and no fallback page, `/titration` answered without a
|
|
15
|
+
* redirect, pages checked on every visit and hashed bundles kept for a year,
|
|
16
|
+
* the analytics snippet in every page, `404.html` for an address the site does
|
|
17
|
+
* not have, and `/health` for the deploy script.
|
|
18
|
+
*
|
|
19
|
+
* Register it next to the API; a build missing (a server started before the
|
|
20
|
+
* frontend was built) leaves the API alone:
|
|
21
|
+
*
|
|
22
|
+
* await app.register(staticSite, { root: 'dist', trackingScript });
|
|
23
|
+
* @param fastify - The server.
|
|
24
|
+
* @param options - The build, the snippet and the retired addresses.
|
|
25
|
+
*/
|
|
26
|
+
export async function staticSite(fastify, options) {
|
|
27
|
+
const { trackingScript, redirects = {}, reserved = ['/v1/'] } = options;
|
|
28
|
+
const root = resolve(options.root);
|
|
29
|
+
fastify.get('/health', { logLevel: 'silent', schema: HIDDEN }, (_request, reply) => reply.send({ status: 'ok' }));
|
|
30
|
+
const files = await listSiteFiles(root);
|
|
31
|
+
if (files === undefined) {
|
|
32
|
+
fastify.log.warn({ root }, 'no built site: serving the API alone');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const pages = await readPages(root, files, trackingScript);
|
|
36
|
+
// An optional peer, installed only by a site that has a server: importing
|
|
37
|
+
// react-cheminfo/server must not need it.
|
|
38
|
+
const { default: fastifyStatic } = await import('@fastify/static');
|
|
39
|
+
await fastify.register(fastifyStatic, {
|
|
40
|
+
root,
|
|
41
|
+
serve: false,
|
|
42
|
+
cacheControl: false,
|
|
43
|
+
setHeaders: (reply, path) => {
|
|
44
|
+
const file = relative(root, path).split(sep).join('/');
|
|
45
|
+
if (isHashedBundle(file)) {
|
|
46
|
+
reply.header('cache-control', BUNDLE_CACHE);
|
|
47
|
+
// A page framed by a sandboxed view may run under an opaque origin,
|
|
48
|
+
// and a module bundle is then fetched cross-origin.
|
|
49
|
+
reply.header('access-control-allow-origin', '*');
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
reply.header('cache-control', PAGE_CACHE);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const answer = (request, reply) => {
|
|
57
|
+
const url = new URL(request.url, 'http://site');
|
|
58
|
+
const { pathname, search } = url;
|
|
59
|
+
if (reserved.some((prefix) => pathname.startsWith(prefix))) {
|
|
60
|
+
return reply.callNotFound();
|
|
61
|
+
}
|
|
62
|
+
const destination = redirects[pathname] ?? redirects[pathname.replace(/\/$/, '')];
|
|
63
|
+
if (destination !== undefined) {
|
|
64
|
+
return reply.redirect(`${destination}${search}`, 301);
|
|
65
|
+
}
|
|
66
|
+
const file = resolveAddress(files, pathname);
|
|
67
|
+
if (file === undefined) {
|
|
68
|
+
return sendPage(request, reply, pages.get('404.html'), 404);
|
|
69
|
+
}
|
|
70
|
+
const page = pages.get(file);
|
|
71
|
+
if (page)
|
|
72
|
+
return sendPage(request, reply, page, 200);
|
|
73
|
+
return reply.sendFile(file);
|
|
74
|
+
};
|
|
75
|
+
fastify.get('/', { schema: HIDDEN }, answer);
|
|
76
|
+
fastify.get('/*', { schema: HIDDEN }, answer);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read every page of the build once, with the snippet in its head.
|
|
80
|
+
* @param root - The build.
|
|
81
|
+
* @param files - Its files.
|
|
82
|
+
* @param trackingScript - The snippet, if any.
|
|
83
|
+
* @returns The pages, by file.
|
|
84
|
+
*/
|
|
85
|
+
async function readPages(root, files, trackingScript) {
|
|
86
|
+
const htmlFiles = [...files].filter((file) => file.endsWith('.html'));
|
|
87
|
+
const pages = await Promise.all(htmlFiles.map(async (file) => {
|
|
88
|
+
const html = injectTrackingScript(await readFile(join(root, file), 'utf8'), trackingScript);
|
|
89
|
+
return [file, { html, etag: await etagOf(html) }];
|
|
90
|
+
}));
|
|
91
|
+
return new Map(pages);
|
|
92
|
+
}
|
|
93
|
+
function sendPage(request, reply, page, status) {
|
|
94
|
+
if (page === undefined) {
|
|
95
|
+
return reply
|
|
96
|
+
.code(status)
|
|
97
|
+
.type('text/plain; charset=utf-8')
|
|
98
|
+
.send('Not found');
|
|
99
|
+
}
|
|
100
|
+
reply.header('cache-control', PAGE_CACHE).header('etag', page.etag);
|
|
101
|
+
if (status === 200 && request.headers['if-none-match'] === page.etag) {
|
|
102
|
+
return reply.code(304).send();
|
|
103
|
+
}
|
|
104
|
+
return reply.code(status).type('text/html; charset=utf-8').send(page.html);
|
|
105
|
+
}
|
|
106
|
+
async function etagOf(text) {
|
|
107
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
|
|
108
|
+
const bytes = new Uint8Array(digest).subarray(0, 12);
|
|
109
|
+
let hex = '';
|
|
110
|
+
for (const byte of bytes)
|
|
111
|
+
hex += byte.toString(16).padStart(2, '0');
|
|
112
|
+
return `"${hex}"`;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=staticSite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staticSite.js","sourceRoot":"","sources":["../../src/server/staticSite.ts"],"names":[],"mappings":"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"}
|
package/lib/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,GACf,MAAM,uBAAuB,CAAC"}
|
package/lib/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,GACf,MAAM,uBAAuB,CAAC"}
|
package/lib/ui.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from './help/ui/index.ts';
|
|
|
18
18
|
export * from './hooks/ui/index.ts';
|
|
19
19
|
export * from './i18n/ui/index.ts';
|
|
20
20
|
export * from './language/ui/index.ts';
|
|
21
|
+
export * from './moleculesSources/ui/index.ts';
|
|
21
22
|
export * from './moleculesToken/ui/index.ts';
|
|
22
23
|
export * from './number/ui/index.ts';
|
|
23
24
|
export * from './overlay/ui/index.ts';
|
package/lib/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAKvC,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAKvC,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
|
package/lib/ui.js
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./help/ui/index.js";
|
|
|
18
18
|
export * from "./hooks/ui/index.js";
|
|
19
19
|
export * from "./i18n/ui/index.js";
|
|
20
20
|
export * from "./language/ui/index.js";
|
|
21
|
+
export * from "./moleculesSources/ui/index.js";
|
|
21
22
|
export * from "./moleculesToken/ui/index.js";
|
|
22
23
|
export * from "./number/ui/index.js";
|
|
23
24
|
export * from "./overlay/ui/index.js";
|
package/lib/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAMvC,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAmC,CAAC;AAEnE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AAMvC,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAmC,CAAC;AAEnE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-cheminfo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React components, hooks and framework-free helpers shared by the cheminfo sites: site chrome, citations, sharing, routing, pedagogy, charts, and the structure, orbital and 3D molecule viewers",
|
|
6
6
|
"author": "Luc Patiny",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"./molstar/*": "./lib/molstar/*/index.js",
|
|
26
26
|
"./orbital": "./lib/orbital.js",
|
|
27
27
|
"./pages": "./lib/pages.js",
|
|
28
|
+
"./server": "./lib/server.js",
|
|
28
29
|
"./slides": "./lib/slides.js",
|
|
29
30
|
"./spectra": "./lib/spectra.js",
|
|
30
31
|
"./structure": "./lib/structure.js",
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
},
|
|
86
87
|
"devDependencies": {
|
|
87
88
|
"@blueprintjs/core": "^6.18.0",
|
|
89
|
+
"@fastify/static": "^10.1.6",
|
|
88
90
|
"@playwright/test": "^1.62.1",
|
|
89
91
|
"@storybook/addon-docs": "^10.5.8",
|
|
90
92
|
"@storybook/react-vite": "^10.5.8",
|
|
@@ -99,6 +101,7 @@
|
|
|
99
101
|
"eslint": "^9.39.5",
|
|
100
102
|
"eslint-config-cheminfo-react": "^20.1.0",
|
|
101
103
|
"eslint-config-cheminfo-typescript": "^22.1.0",
|
|
104
|
+
"fastify": "^5.12.7",
|
|
102
105
|
"jsdom": "^30.1.1",
|
|
103
106
|
"ml-dataset-iris": "^1.2.1",
|
|
104
107
|
"ml-pca": "^4.1.1",
|
|
@@ -119,6 +122,8 @@
|
|
|
119
122
|
},
|
|
120
123
|
"peerDependencies": {
|
|
121
124
|
"@blueprintjs/core": "^6.18.0",
|
|
125
|
+
"@fastify/static": "^10.0.0",
|
|
126
|
+
"fastify": "^5.0.0",
|
|
122
127
|
"ml-pca": "^4.1.1",
|
|
123
128
|
"ml-signal-processing": "^2.2.2",
|
|
124
129
|
"molstar": "^5.11.0",
|
|
@@ -139,6 +144,12 @@
|
|
|
139
144
|
},
|
|
140
145
|
"homepage": "https://github.com/cheminfo/react-cheminfo#readme",
|
|
141
146
|
"peerDependenciesMeta": {
|
|
147
|
+
"@fastify/static": {
|
|
148
|
+
"optional": true
|
|
149
|
+
},
|
|
150
|
+
"fastify": {
|
|
151
|
+
"optional": true
|
|
152
|
+
},
|
|
142
153
|
"ml-pca": {
|
|
143
154
|
"optional": true
|
|
144
155
|
},
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { BuildInfo } from '../../build/core/buildInfo.ts';
|
|
4
|
+
import { formatBuiltAt, shortCommit } from '../../build/core/buildInfo.ts';
|
|
5
|
+
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What the badge of a release leaves to its hover: the commit it was built
|
|
9
|
+
* from, the instant to the second, and the chrome it was built against.
|
|
10
|
+
* @param props - The build and its released version.
|
|
11
|
+
* @param props.build - What the build published about itself.
|
|
12
|
+
* @param props.version - The release the badge names.
|
|
13
|
+
* @returns The card's contents.
|
|
14
|
+
*/
|
|
15
|
+
export function AboutBuildDetails(props: {
|
|
16
|
+
build: BuildInfo;
|
|
17
|
+
version: string;
|
|
18
|
+
}): ReactElement {
|
|
19
|
+
const { build, version } = props;
|
|
20
|
+
const t = useChromeT();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<div className="build-tooltip__header">
|
|
25
|
+
{t('about.buildVersion', { version })}
|
|
26
|
+
</div>
|
|
27
|
+
<dl className="build-tooltip__rows">
|
|
28
|
+
<dt>{t('about.buildBuilt')}</dt>
|
|
29
|
+
<dd>{formatBuiltAt(build.builtAt)}</dd>
|
|
30
|
+
{build.commit === undefined ? null : (
|
|
31
|
+
<>
|
|
32
|
+
<dt>{t('about.buildCommit')}</dt>
|
|
33
|
+
<dd>{shortCommit(build.commit)}</dd>
|
|
34
|
+
</>
|
|
35
|
+
)}
|
|
36
|
+
{build.reactCheminfo === undefined ? null : (
|
|
37
|
+
<>
|
|
38
|
+
<dt>react-cheminfo</dt>
|
|
39
|
+
<dd>{build.reactCheminfo}</dd>
|
|
40
|
+
</>
|
|
41
|
+
)}
|
|
42
|
+
</dl>
|
|
43
|
+
</>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
+
import { buildLabel } from '../../build/core/buildInfo.ts';
|
|
3
4
|
import { CreditsList } from '../../credits/ui/CreditsList.tsx';
|
|
4
5
|
import { Wordmark } from '../../ecosystem/ui/Wordmark.tsx';
|
|
5
6
|
import { SiteMark } from '../../ecosystem/ui/marks.tsx';
|
|
@@ -7,10 +8,11 @@ import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
|
7
8
|
import type { AboutContent } from '../core/about.ts';
|
|
8
9
|
import { resolveAbout } from '../core/about.ts';
|
|
9
10
|
|
|
10
|
-
import { AboutBuild } from './AboutBuild.tsx';
|
|
11
11
|
import { AboutCitations } from './AboutCitations.tsx';
|
|
12
12
|
import { AboutProvidedBy } from './AboutProvidedBy.tsx';
|
|
13
13
|
import { AboutSection } from './AboutSection.tsx';
|
|
14
|
+
import type { AboutSignInProps } from './AboutSignIn.tsx';
|
|
15
|
+
import { AboutSignIn } from './AboutSignIn.tsx';
|
|
14
16
|
import { AboutVersion } from './AboutVersion.tsx';
|
|
15
17
|
|
|
16
18
|
export interface AboutPageProps {
|
|
@@ -43,6 +45,13 @@ export interface AboutPageProps {
|
|
|
43
45
|
* @default undefined
|
|
44
46
|
*/
|
|
45
47
|
logo?: ReactNode;
|
|
48
|
+
/**
|
|
49
|
+
* Where the people who run the site sign in, drawn under the build badge in
|
|
50
|
+
* the corner of the hero. A site passes it while nobody is signed in, and
|
|
51
|
+
* leaves it out once somebody is: the account is then in the bar.
|
|
52
|
+
* @default undefined — the site has no account to sign in to
|
|
53
|
+
*/
|
|
54
|
+
signIn?: AboutSignInProps;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/**
|
|
@@ -58,10 +67,12 @@ export interface AboutPageProps {
|
|
|
58
67
|
* @throws {Error} When the record names a site or a credit that does not exist.
|
|
59
68
|
*/
|
|
60
69
|
export function AboutPage(props: AboutPageProps): ReactElement {
|
|
61
|
-
const { content, className, children, logo, mark } = props;
|
|
70
|
+
const { content, className, children, logo, mark, signIn } = props;
|
|
62
71
|
const t = useChromeT();
|
|
63
72
|
const about = resolveAbout(content);
|
|
64
73
|
const site = about.site;
|
|
74
|
+
const hasCorner =
|
|
75
|
+
buildLabel(about.build) !== undefined || signIn !== undefined;
|
|
65
76
|
|
|
66
77
|
return (
|
|
67
78
|
<div
|
|
@@ -83,7 +94,12 @@ export function AboutPage(props: AboutPageProps): ReactElement {
|
|
|
83
94
|
</p>
|
|
84
95
|
<p style={WHAT_STYLE}>{about.what}</p>
|
|
85
96
|
</div>
|
|
86
|
-
|
|
97
|
+
{hasCorner ? (
|
|
98
|
+
<div className="about-corner" style={CORNER_STYLE}>
|
|
99
|
+
<AboutVersion build={about.build} />
|
|
100
|
+
{signIn === undefined ? null : <AboutSignIn {...signIn} />}
|
|
101
|
+
</div>
|
|
102
|
+
) : null}
|
|
87
103
|
</header>
|
|
88
104
|
|
|
89
105
|
{about.people.length === 0 && about.providedBy.length === 0 ? null : (
|
|
@@ -127,16 +143,6 @@ export function AboutPage(props: AboutPageProps): ReactElement {
|
|
|
127
143
|
<AboutCitations works={about.cite} />
|
|
128
144
|
</AboutSection>
|
|
129
145
|
)}
|
|
130
|
-
|
|
131
|
-
{/*
|
|
132
|
-
The build the reader has open, last and under no heading: it is what a
|
|
133
|
-
report has to name, not something anybody came to the page to read.
|
|
134
|
-
*/}
|
|
135
|
-
{about.build === undefined ? null : (
|
|
136
|
-
<AboutSection className="about-build">
|
|
137
|
-
<AboutBuild build={about.build} />
|
|
138
|
-
</AboutSection>
|
|
139
|
-
)}
|
|
140
146
|
</div>
|
|
141
147
|
);
|
|
142
148
|
}
|
|
@@ -166,6 +172,14 @@ const HERO_STYLE = {
|
|
|
166
172
|
lineHeight: 1.55,
|
|
167
173
|
} as const satisfies CSSProperties;
|
|
168
174
|
|
|
175
|
+
const CORNER_STYLE = {
|
|
176
|
+
display: 'flex',
|
|
177
|
+
flex: '0 0 auto',
|
|
178
|
+
flexDirection: 'column',
|
|
179
|
+
alignItems: 'flex-end',
|
|
180
|
+
gap: 2,
|
|
181
|
+
} as const satisfies CSSProperties;
|
|
182
|
+
|
|
169
183
|
const NAME_STYLE = {
|
|
170
184
|
margin: 0,
|
|
171
185
|
fontSize: 26,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { isModifiedClick } from '../../chrome/ui/navItem.ts';
|
|
4
|
+
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
5
|
+
|
|
6
|
+
export interface AboutSignInProps {
|
|
7
|
+
/**
|
|
8
|
+
* The address signing in happens at. Writing it keeps the entry a real link,
|
|
9
|
+
* so a middle click opens a tab.
|
|
10
|
+
* @default undefined
|
|
11
|
+
*/
|
|
12
|
+
href?: string;
|
|
13
|
+
/**
|
|
14
|
+
* What the site does when the entry is picked — route to that page, or open
|
|
15
|
+
* a credentials dialog for a site that has no page. A modified click is left
|
|
16
|
+
* to the browser.
|
|
17
|
+
* @default undefined
|
|
18
|
+
*/
|
|
19
|
+
onSignIn?: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* What the entry reads, for a site whose accounts are for one kind of person.
|
|
22
|
+
* @default the chrome's own word for it, in the language of the page
|
|
23
|
+
*/
|
|
24
|
+
label?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Where the people who run a site sign in: one quiet line under the build
|
|
29
|
+
* badge, in the corner of the About.
|
|
30
|
+
*
|
|
31
|
+
* A visitor has nothing to sign in to, so the bar carries no invitation; the
|
|
32
|
+
* About is the page the operators already know, and the corner is where they
|
|
33
|
+
* already look for the build they run.
|
|
34
|
+
* @param props - See {@link AboutSignInProps}.
|
|
35
|
+
* @returns The entry.
|
|
36
|
+
*/
|
|
37
|
+
export function AboutSignIn(props: AboutSignInProps): ReactElement {
|
|
38
|
+
const { href, onSignIn, label } = props;
|
|
39
|
+
const t = useChromeT();
|
|
40
|
+
const text = label ?? t('account.signIn');
|
|
41
|
+
|
|
42
|
+
if (href === undefined) {
|
|
43
|
+
return (
|
|
44
|
+
<button type="button" className="about-sign-in" onClick={onSignIn}>
|
|
45
|
+
{text}
|
|
46
|
+
</button>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<a
|
|
52
|
+
className="about-sign-in"
|
|
53
|
+
href={href}
|
|
54
|
+
onClick={(event) => {
|
|
55
|
+
if (onSignIn === undefined || isModifiedClick(event)) return;
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
onSignIn();
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
{text}
|
|
61
|
+
</a>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -4,18 +4,22 @@
|
|
|
4
4
|
* A report of something going wrong is worth answering only when we know what
|
|
5
5
|
* was running, and that is the one thing an About page cannot say from a
|
|
6
6
|
* hand-written record. It sits at the top of the page rather than at the
|
|
7
|
-
* bottom because it is the one line a reader is asked to quote back
|
|
7
|
+
* bottom because it is the one line a reader is asked to quote back, and it is
|
|
8
|
+
* said once: nothing further down the page repeats it.
|
|
8
9
|
*/
|
|
9
10
|
|
|
11
|
+
import { Tooltip } from '@blueprintjs/core';
|
|
10
12
|
import type { CSSProperties, ReactElement } from 'react';
|
|
11
13
|
|
|
12
14
|
import type { BuildInfo } from '../../build/core/buildInfo.ts';
|
|
13
15
|
import {
|
|
14
16
|
buildLabel,
|
|
15
17
|
buildStamp,
|
|
16
|
-
|
|
18
|
+
releasedVersion,
|
|
17
19
|
} from '../../build/core/buildInfo.ts';
|
|
18
20
|
|
|
21
|
+
import { AboutBuildDetails } from './AboutBuildDetails.tsx';
|
|
22
|
+
|
|
19
23
|
export interface AboutVersionProps {
|
|
20
24
|
/** What the build published about itself, or `undefined` when it published none. */
|
|
21
25
|
build: BuildInfo | undefined;
|
|
@@ -23,7 +27,8 @@ export interface AboutVersionProps {
|
|
|
23
27
|
|
|
24
28
|
/**
|
|
25
29
|
* The build badge: the release a site is serving, or the commit it was built
|
|
26
|
-
* from while it has no release yet, and when it was made.
|
|
30
|
+
* from while it has no release yet, and when it was made. A release names its
|
|
31
|
+
* commit and the exact instant in the card its hover opens.
|
|
27
32
|
* @param props - See {@link AboutVersionProps}.
|
|
28
33
|
* @returns The badge, or `null` when the build says nothing worth showing.
|
|
29
34
|
*/
|
|
@@ -34,15 +39,28 @@ export function AboutVersion(props: AboutVersionProps): ReactElement | null {
|
|
|
34
39
|
// The instant belongs on the badge rather than in its hover: a version
|
|
35
40
|
// nobody can date says nothing about how old the page they read is.
|
|
36
41
|
const badge = `${label} · ${buildStamp(build.builtAt)}`;
|
|
42
|
+
const version = releasedVersion(build);
|
|
43
|
+
|
|
44
|
+
if (version === undefined) {
|
|
45
|
+
return (
|
|
46
|
+
<span style={BADGE_STYLE} className="about-version">
|
|
47
|
+
{badge}
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
37
51
|
|
|
38
52
|
return (
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
<Tooltip
|
|
54
|
+
content={<AboutBuildDetails build={build} version={version} />}
|
|
55
|
+
// To the left, so the card never covers the way to sign in under it.
|
|
56
|
+
placement="left-start"
|
|
57
|
+
popoverClassName="help-tooltip build-tooltip"
|
|
58
|
+
hoverOpenDelay={150}
|
|
43
59
|
>
|
|
44
|
-
{
|
|
45
|
-
|
|
60
|
+
<span style={BADGE_STYLE} className="about-version" tabIndex={0}>
|
|
61
|
+
{badge}
|
|
62
|
+
</span>
|
|
63
|
+
</Tooltip>
|
|
46
64
|
);
|
|
47
65
|
}
|
|
48
66
|
|
package/src/about/ui/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export type { AboutBuildProps } from './AboutBuild.tsx';
|
|
2
|
-
export { AboutBuild } from './AboutBuild.tsx';
|
|
3
1
|
export type { AboutVersionProps } from './AboutVersion.tsx';
|
|
4
2
|
export { AboutVersion } from './AboutVersion.tsx';
|
|
5
3
|
export type { AboutCitationsProps } from './AboutCitations.tsx';
|
|
@@ -10,3 +8,5 @@ export type { AboutProvidedByProps } from './AboutProvidedBy.tsx';
|
|
|
10
8
|
export { AboutProvidedBy } from './AboutProvidedBy.tsx';
|
|
11
9
|
export type { AboutSectionProps } from './AboutSection.tsx';
|
|
12
10
|
export { AboutSection } from './AboutSection.tsx';
|
|
11
|
+
export type { AboutSignInProps } from './AboutSignIn.tsx';
|
|
12
|
+
export { AboutSignIn } from './AboutSignIn.tsx';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { NavLink } from './NavLink.tsx';
|
|
4
|
+
import { NavMenuButton } from './NavMenuButton.tsx';
|
|
5
|
+
import type { NavItem } from './navItem.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One page of the bar: a link, or the menu it opens when the entry folds pages
|
|
9
|
+
* of its own. A menu stands where its pages would, among them and not among the
|
|
10
|
+
* utilities, because picking one of them changes what the page shows.
|
|
11
|
+
* @param props - The entry and the page on show.
|
|
12
|
+
* @param props.item - The entry.
|
|
13
|
+
* @param props.activeId - The page on show.
|
|
14
|
+
* @returns The entry.
|
|
15
|
+
*/
|
|
16
|
+
export function NavEntry(props: {
|
|
17
|
+
item: NavItem;
|
|
18
|
+
activeId: string | undefined;
|
|
19
|
+
}): ReactElement {
|
|
20
|
+
const { item, activeId } = props;
|
|
21
|
+
if (item.items === undefined) {
|
|
22
|
+
return <NavLink item={item} active={item.id === activeId} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<NavMenuButton
|
|
27
|
+
label={menuLabel(item)}
|
|
28
|
+
icon={typeof item.icon === 'string' ? item.icon : undefined}
|
|
29
|
+
items={item.items}
|
|
30
|
+
activeId={activeId}
|
|
31
|
+
placement="bottom-start"
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The trigger names itself to a screen reader, so an entry drawn as a glyph
|
|
37
|
+
// alone says what it opens through its title rather than through its id.
|
|
38
|
+
function menuLabel(item: NavItem): string {
|
|
39
|
+
if (typeof item.label === 'string') return item.label;
|
|
40
|
+
return item.title ?? item.id;
|
|
41
|
+
}
|