diff --git a/index.html b/index.html index b1f881b..0eb7dd7 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + KNeuron diff --git a/package-lock.json b/package-lock.json index ea6c6de..ef4f400 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.2.7", "license": "UNLICENSED", "dependencies": { + "@fontsource/barlow": "^5.3.0", + "@fontsource/barlow-semi-condensed": "^5.3.0", "@tauri-apps/api": "^2.8.0", "@tauri-apps/plugin-shell": "^2.4.0", "lucide-react": "^0.468.0", @@ -1173,6 +1175,24 @@ } } }, + "node_modules/@fontsource/barlow": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/barlow/-/barlow-5.3.0.tgz", + "integrity": "sha512-tGR0y6QObRtKv71MOth44222+mdNFM/ZWFWDV1VLzNbG4oK9PNRlyJzmkck4vHAi9ibHBXgt4SlY51amFP55CA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/barlow-semi-condensed": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/barlow-semi-condensed/-/barlow-semi-condensed-5.3.0.tgz", + "integrity": "sha512-EdpQ1GlLMofGqDsQhEw+g6DonNKmzt21p8y/oNbTl4BFzEkqYkFyE2fj25YGxmZBL/LZZGAxe74FlvEWcgc31w==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", diff --git a/package.json b/package.json index c141132..8fa9c7f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "quality:full": "npm run quality && cargo check --manifest-path src-tauri/Cargo.toml" }, "dependencies": { + "@fontsource/barlow": "^5.3.0", + "@fontsource/barlow-semi-condensed": "^5.3.0", "@tauri-apps/api": "^2.8.0", "@tauri-apps/plugin-shell": "^2.4.0", "lucide-react": "^0.468.0", diff --git a/public/brain-logo.png b/public/brain-logo.png new file mode 100644 index 0000000..8c2a61c Binary files /dev/null and b/public/brain-logo.png differ diff --git a/public/devices/brainaccess-maxi.png b/public/devices/brainaccess-maxi.png new file mode 100644 index 0000000..46c8623 Binary files /dev/null and b/public/devices/brainaccess-maxi.png differ diff --git a/public/devices/brainlink-lite.png b/public/devices/brainlink-lite.png new file mode 100644 index 0000000..71fa3f9 Binary files /dev/null and b/public/devices/brainlink-lite.png differ diff --git a/public/modules/cortex/thumbnail.png b/public/modules/cortex/thumbnail.png new file mode 100644 index 0000000..7c584ab Binary files /dev/null and b/public/modules/cortex/thumbnail.png differ diff --git a/public/modules/miner/thumbnail.png b/public/modules/miner/thumbnail.png new file mode 100644 index 0000000..8f14962 Binary files /dev/null and b/public/modules/miner/thumbnail.png differ diff --git a/public/modules/neuorrun/thumbnail.png b/public/modules/neuorrun/thumbnail.png new file mode 100644 index 0000000..4ea0458 Binary files /dev/null and b/public/modules/neuorrun/thumbnail.png differ diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7f9d229..ca68635 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -22,7 +22,7 @@ "decorations": false, "shadow": true, "theme": "Dark", - "backgroundColor": "#030A12" + "backgroundColor": "#090909" } ], "security": { diff --git a/src/App.tsx b/src/App.tsx index 3a40f86..c083bc7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -176,7 +176,7 @@ export default function App() { } } - const activeSidebarPage: ShellPage = route.kind === "shell" ? route.page : "dashboard"; + const activeSidebarPage: ShellPage | null = route.kind === "shell" ? route.page : null; const isModuleOpen = route.kind === "module"; diff --git a/src/components/BrainLogo.tsx b/src/components/BrainLogo.tsx index 42c7143..4912133 100644 --- a/src/components/BrainLogo.tsx +++ b/src/components/BrainLogo.tsx @@ -1,22 +1,20 @@ -export function BrainLogo({ size = 54 }: { size?: number }) { +interface BrainLogoProps { + size?: number; +} + +export function BrainLogo({ size = 54 }: BrainLogoProps) { return ( - + ); } diff --git a/src/components/DevicePanel.tsx b/src/components/DevicePanel.tsx index 6e6baea..793041a 100644 --- a/src/components/DevicePanel.tsx +++ b/src/components/DevicePanel.tsx @@ -1,5 +1,12 @@ +import { MonitorOff } from "lucide-react"; + import { useDevice } from "../features/device/useDevice"; +import { ModuleThumbnail } from "./ModuleThumbnail"; + +/** + * Right-hand panel with the active device. + */ export function DevicePanel() { const { snapshot, streamInfoByDeviceId } = useDevice(); @@ -7,63 +14,62 @@ export function DevicePanel() { const activeStatus = snapshot.activeStatus; + const state = activeStatus?.state ?? "disconnected"; + const streamInfo = snapshot.activeDeviceId ? streamInfoByDeviceId[snapshot.activeDeviceId] : undefined; return ( - diff --git a/src/components/ModuleThumbnail.tsx b/src/components/ModuleThumbnail.tsx index f91f3eb..06376ce 100644 --- a/src/components/ModuleThumbnail.tsx +++ b/src/components/ModuleThumbnail.tsx @@ -3,32 +3,28 @@ import { useState } from "react"; interface ModuleThumbnailProps { src?: string; name: string; + /** + * Extra class for placement, e.g. on device cards. + */ + className?: string; } /** - * Displays a module thumbnail with a deterministic fallback. + * Displays a module or device thumbnail. * - * A broken or missing thumbnail must never break the launcher layout. + * Renders nothing when no image is set or it fails to load, so the layout + * closes the gap instead of showing a placeholder. */ -export function ModuleThumbnail({ src, name }: ModuleThumbnailProps) { +export function ModuleThumbnail({ src, name, className }: ModuleThumbnailProps) { const [imageFailed, setImageFailed] = useState(false); - const showFallback = !src || imageFailed; - - if (showFallback) { - return ( -
- {name.charAt(0).toUpperCase()} -
- ); + if (!src || imageFailed) { + return null; } return ( {`${name} = [ - { id: "dashboard", label: "Dashboard", icon: LayoutDashboard }, - { id: "device", label: "Device", icon: Cpu }, +const items: Array<{ id: ShellPage; label: string; icon: LucideIcon }> = [ + { id: "dashboard", label: "Dashboard", icon: LayoutGrid }, + { id: "device", label: "Device", icon: Monitor }, { id: "settings", label: "Settings", icon: Settings }, ]; +/** + * Left navigation with the app version at the bottom. + * + * `page` is null while a module is open, so no item is marked current. + */ export function Sidebar({ page, onNavigate, }: { - page: ShellPage; + page: ShellPage | null; onNavigate: (page: ShellPage) => void; }) { return ( @@ -21,28 +28,23 @@ export function Sidebar({ {items.map(({ id, label, icon: Icon }) => ( ))} -
-
- Type - - {formatDeviceKind(device.kind)} -
-
Transport - {formatTransport(device.transport)}
- - {streamInfo && ( - <> -
- Sampling rate - - {streamInfo.sampleRateHz} Hz -
- -
- Channels - - {streamInfo.channels.length} -
- - )}
{active && status.message &&

{status.message}

} - {active && status.error &&

{status.error}

}