Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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=/
Expand Down
49 changes: 43 additions & 6 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

* {
Expand Down Expand Up @@ -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) ===================== */
Expand Down
9 changes: 3 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -174,11 +175,7 @@ export default function App() {
<Route path="*" element={<Navigate to="/guide" replace />} />
</Routes>

<footer className="footnote">
<b>COLLET</b> — an unofficial companion for the Dremel 8260. {GUIDE.meta.attribution} The
BLE protocol is documented for interoperability in <code>PROTOCOL.md</code>; no hardware
needed in <b>Demo</b> mode.
</footer>
<AppFooter />
</div>
);
}
Expand Down
64 changes: 64 additions & 0 deletions src/AppFooter.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<footer className="app-footer">
<p>
<a href="https://libreble.github.io/">
<LibrebleMark />
<span>
Part of{' '}
<span className="libreble-name">
libre<span className="libreble-ble">ble</span>
</span>
</span>
</a>
<a href={REPO} target="_blank" rel="noopener noreferrer">
<GitHubMark />
GitHub
</a>
<a href={`${REPO}/issues/new`} target="_blank" rel="noopener noreferrer">
Report an issue
</a>
{RELEASE ? (
<a href={RELEASE} target="_blank" rel="noopener noreferrer" className="mono">
{__APP_VERSION__}
</a>
) : (
<span className="mono">{__APP_VERSION__}</span>
)}
</p>
</footer>
);
}

/** The libreble "open beacon": an LED inside two rings opening to the top right. */
function LibrebleMark() {
return (
<svg viewBox="0 0 64 64" aria-hidden>
<g
transform="translate(32 32) rotate(-45) translate(-32 -32)"
fill="none"
stroke="currentColor"
strokeWidth="6"
strokeLinecap="round"
>
<path d="M44.36 40.50A15 15 0 1 1 44.36 23.50" />
<path d="M54.69 42.50A25 25 0 1 1 54.69 21.50" />
</g>
<circle cx="32" cy="32" r="6.5" className="libreble-led" />
</svg>
);
}

function GitHubMark() {
return (
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden>
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" />
</svg>
);
}
4 changes: 4 additions & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// <reference types="vite/client" />

/** Build version for the footer — see appVersion() in vite.config.ts. */
declare const __APP_VERSION__: string;
17 changes: 17 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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({
Expand Down
Loading