diff --git a/Dockerfile b/Dockerfile index c6a5f5f..ffa10c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,9 @@ COPY package.json package-lock.json ./ RUN npm ci COPY . . ARG BASE_PATH=/ -RUN BASE_PATH="$BASE_PATH" npm run build +# .git isn't in the build context, so pass the version in: --build-arg APP_VERSION=v1.2.0 +ARG APP_VERSION=dev +RUN BASE_PATH="$BASE_PATH" APP_VERSION="$APP_VERSION" npm run build FROM nginxinc/nginx-unprivileged:1.29-alpine AS serve ARG BASE_PATH=/ diff --git a/src/App.css b/src/App.css index c246761..02bf4bf 100644 --- a/src/App.css +++ b/src/App.css @@ -31,6 +31,8 @@ --track: #dbe2ec; --grid-line: rgba(15, 27, 42, 0.07); --scrim: rgba(15, 27, 42, 0.42); + --libreble-ble: #c2410c; + --libreble-led: #f26b1d; } @media (prefers-color-scheme: dark) { :root { @@ -58,6 +60,8 @@ --track: #1c2836; --grid-line: rgba(231, 238, 246, 0.08); --scrim: rgba(5, 9, 14, 0.62); + --libreble-ble: #ffa259; + --libreble-led: #ff8a3d; } } /* explicit toggle overrides — must win over the media query in both directions */ @@ -86,6 +90,8 @@ --track: #dbe2ec; --grid-line: rgba(15, 27, 42, 0.07); --scrim: rgba(15, 27, 42, 0.42); + --libreble-ble: #c2410c; + --libreble-led: #f26b1d; } :root[data-theme='dark'] { --bg: #0b1017; @@ -112,6 +118,8 @@ --track: #1c2836; --grid-line: rgba(231, 238, 246, 0.08); --scrim: rgba(5, 9, 14, 0.62); + --libreble-ble: #ffa259; + --libreble-led: #ff8a3d; } * { @@ -1244,17 +1252,46 @@ body { color: var(--accent-2); } -.footnote { +/* libreble footer: one centered line — "Part of libreble" · GitHub · Report an issue · version */ +.app-footer { margin-top: 34px; padding-top: 16px; border-top: 1px solid var(--border); color: var(--ink-3); - font-size: 12px; - line-height: 1.6; + font-size: 11.5px; + line-height: 1.5; } -.footnote b { - color: var(--ink-2); - font-weight: 600; +.app-footer p { + margin: 0; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 4px 12px; +} +.app-footer a { + display: inline-flex; + align-items: center; + gap: 4px; + color: inherit; + text-decoration: none; +} +.app-footer a:hover { + color: var(--ink); +} +.app-footer svg { + width: 14px; + height: 14px; + flex: none; +} +.libreble-name { + font-weight: 500; +} +.libreble-ble { + color: var(--libreble-ble); +} +.libreble-led { + fill: var(--libreble-led); } /* ===================== app-shell extras (beyond the prototype) ===================== */ diff --git a/src/App.tsx b/src/App.tsx index e4b4d97..2859e9b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,11 +8,12 @@ import { WebBluetoothTransport, isWebBluetoothAvailable } from './ble/webBluetoo import { useToolClient } from './hooks/useToolClient'; import { useTheme } from './theme'; import { Icon, IconSprite } from './Icons'; -import { GUIDE, NOMINAL_TABLE } from './guide'; +import { NOMINAL_TABLE } from './guide'; import Dashboard from './Dashboard'; import { AccessoryPage, GuideAccessoryGrid, GuideMaterialGrid, MaterialPage } from './Guide'; import type { GuideProps } from './Guide'; import Diagnostics from './Diagnostics'; +import AppFooter from './AppFooter'; type TransportKind = 'ble' | 'mock'; @@ -174,11 +175,7 @@ export default function App() { } /> - + ); } diff --git a/src/AppFooter.tsx b/src/AppFooter.tsx new file mode 100644 index 0000000..18f12f5 --- /dev/null +++ b/src/AppFooter.tsx @@ -0,0 +1,64 @@ +const REPO = 'https://github.com/libreble/collet'; +/** A clean tag build ("v1.2.0") links to its release; anything else is shown as plain text. */ +const RELEASE = /^v\d+\.\d+\.\d+$/.test(__APP_VERSION__) + ? `${REPO}/releases/tag/${__APP_VERSION__}` + : null; + +export default function AppFooter() { + return ( + + ); +} + +/** The libreble "open beacon": an LED inside two rings opening to the top right. */ +function LibrebleMark() { + return ( + + + + + + + + ); +} + +function GitHubMark() { + return ( + + + + ); +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..2632f11 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,4 @@ +/// + +/** Build version for the footer — see appVersion() in vite.config.ts. */ +declare const __APP_VERSION__: string; diff --git a/vite.config.ts b/vite.config.ts index a3c4ce7..5288e65 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,4 @@ +import { execSync } from 'node:child_process'; import { copyFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { defineConfig, type Plugin } from 'vite'; @@ -29,8 +30,24 @@ const base = `/${(process.env.BASE_PATH ?? '/collet/').replace(/^\/+|\/+$/g, '') '/', ); +// Shown in the footer. Tag builds in CI use the tag (v1.2.0); elsewhere `git describe` +// (v1.2.0-3-gabc1234, or a bare sha on a shallow clone); `APP_VERSION` overrides; no git → "dev". +function appVersion(): string { + if (process.env.APP_VERSION) return process.env.APP_VERSION; + if (process.env.GITHUB_REF_TYPE === 'tag' && process.env.GITHUB_REF_NAME) + return process.env.GITHUB_REF_NAME; + try { + return execSync('git describe --tags --always', { stdio: ['ignore', 'pipe', 'ignore'] }) + .toString() + .trim(); + } catch { + return 'dev'; + } +} + export default defineConfig({ base, + define: { __APP_VERSION__: JSON.stringify(appVersion()) }, plugins: [ react(), VitePWA({