Line data Source code
1 : import * as React from 'react';
2 :
3 : import { AnchorAlignment, AnchorSide } from '../ui/foundations/anchored_position.js';
4 : import { DASHQL_VERSION, HYPERDB_WASM_VERSION } from '../globals.js';
5 : import { AnchoredOverlay } from '../ui/foundations/anchored_overlay.js';
6 : import { ButtonVariant, IconButton } from '../ui/foundations/button.js';
7 : import { FlavorLogo, useRequestedAppFlavor } from '../config/flavor_provider.js';
8 : import { Tag, TagVariant } from '../ui/foundations/tag.js';
9 : import { ShellInternals } from './internals.js';
10 :
11 : import { XIcon } from '../ui/foundations/symbol_icon.js';
12 : import symbols from '@ankoh/dashql-svg-symbols';
13 : import * as styles from './shell_navbar.module.css';
14 :
15 : interface VersionButtonProps {
16 : engineVersion: string | null;
17 : }
18 :
19 1 : const VersionButton: React.FC<VersionButtonProps> = (props: VersionButtonProps) => {
20 1 : const [isOpen, setIsOpen] = React.useState(false);
21 :
22 1 : return (
23 1 : <AnchoredOverlay
24 1 : open={isOpen}
25 1 : onOpen={() => setIsOpen(true)}
26 1 : onClose={() => setIsOpen(false)}
27 1 : side={AnchorSide.OutsideBottom}
28 1 : align={AnchorAlignment.End}
29 1 : anchorOffset={16}
30 1 : renderAnchor={(anchorProps: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
31 1 : <button
32 1 : {...anchorProps}
33 1 : type="button"
34 1 : className={styles.actionButton}
35 1 : aria-label={`HyperDB WASM version ${HYPERDB_WASM_VERSION}`}
36 : >
37 1 : <svg className={styles.actionIcon} width="16px" height="16px" aria-hidden="true">
38 1 : <use xlinkHref={`${symbols}#package`} />
39 1 : </svg>
40 1 : <span className={styles.actionLabel}>{HYPERDB_WASM_VERSION}</span>
41 1 : </button>
42 : )}
43 : >
44 1 : <div className={styles.versionOverlay}>
45 1 : <div className={styles.versionHeader}>
46 1 : <div className={styles.versionTitle}>Version</div>
47 1 : <IconButton
48 1 : variant={ButtonVariant.Invisible}
49 1 : aria-label="Close"
50 1 : onClick={() => setIsOpen(false)}
51 : >
52 1 : <XIcon />
53 1 : </IconButton>
54 1 : </div>
55 1 : <div className={styles.versionInfo}>
56 1 : <div className={styles.versionLabel}>
57 1 : <svg width="16px" height="16px" aria-hidden="true">
58 1 : <use xlinkHref={`${symbols}#hyper_outlines`} />
59 1 : </svg>
60 1 : <span>hyperdb</span>
61 1 : </div>
62 1 : <div>{props.engineVersion ?? 'Loading...'}</div>
63 1 : <a
64 1 : className={styles.versionLinkButton}
65 1 : href="https://www.npmjs.com/package/hyperdb-wasm"
66 1 : target="_blank"
67 1 : rel="noopener noreferrer"
68 1 : aria-label="Open hyperdb-wasm on npm"
69 : >
70 1 : <svg width="16px" height="16px" aria-hidden="true">
71 1 : <use xlinkHref={`${symbols}#npmjs`} />
72 1 : </svg>
73 1 : <span>hyperdb-wasm</span>
74 1 : </a>
75 1 : <div>{HYPERDB_WASM_VERSION}</div>
76 1 : <a
77 1 : className={styles.versionLinkButton}
78 1 : href="https://github.com/ankoh/dashql"
79 1 : target="_blank"
80 1 : rel="noopener noreferrer"
81 1 : aria-label="Open DashQL on GitHub"
82 : >
83 1 : <svg width="16px" height="16px" aria-hidden="true">
84 1 : <use xlinkHref={`${symbols}#github`} />
85 1 : </svg>
86 1 : <span>dashql</span>
87 1 : </a>
88 1 : <div>{DASHQL_VERSION}</div>
89 1 : </div>
90 1 : </div>
91 1 : </AnchoredOverlay>
92 : );
93 1 : };
94 :
95 : /// The height of a wordmark in the navbar
96 1 : const WORDMARK_HEIGHT = 16;
97 :
98 : /// The brand of the shell.
99 : /// The shell is branded for Hyper, a flavor requested by the url replaces the Hyper banner with its logo and tagline.
100 1 : const ShellBrand: React.FC = () => {
101 1 : const flavor = useRequestedAppFlavor();
102 1 : if (flavor == null) {
103 1 : return (
104 1 : <svg className={styles.brand_logo} width="100%" height="100%" aria-hidden="true">
105 1 : <use xlinkHref={`${symbols}#hyper_banner`} />
106 1 : </svg>
107 : );
108 : }
109 1 : return (
110 1 : <span className={styles.brand_mark} data-testid="shell-flavor-brand">
111 1 : {flavor.logo.wordmark
112 1 : ? <FlavorLogo height={WORDMARK_HEIGHT} ariaHidden />
113 0 : : <>
114 0 : <FlavorLogo height={24} ariaHidden />
115 0 : <span>{flavor.displayName}</span>
116 0 : </>}
117 1 : {flavor.tagline != null && (
118 1 : <Tag variant={TagVariant.Outline} className={styles.brand_tagline}>{flavor.tagline}</Tag>
119 : )}
120 1 : </span>
121 : );
122 1 : };
123 :
124 : interface ShellNavBarProps {
125 : engineVersion: string | null;
126 : onReset: () => void;
127 : Internals?: React.ComponentType;
128 : }
129 :
130 1 : export const ShellNavBar: React.FC<ShellNavBarProps> = (props: ShellNavBarProps) => {
131 1 : const Internals = props.Internals ?? ShellInternals;
132 1 : return (
133 1 : <header className={styles.navbar}>
134 1 : <button
135 1 : type="button"
136 1 : className={styles.brand}
137 1 : aria-label="Reset shell"
138 1 : data-testid="shell-reset"
139 1 : onClick={props.onReset}
140 : >
141 1 : <ShellBrand />
142 1 : </button>
143 1 : <nav className={styles.actions} aria-label="Shell utilities">
144 1 : <Internals />
145 1 : <VersionButton engineVersion={props.engineVersion} />
146 1 : </nav>
147 1 : </header>
148 : );
149 1 : };
|