Răsfoiți Sursa

fix(frontend): replace all Material Icons with Lucide SVG icons in Layout

Replace bottom navigation icons and all remaining Material Icons in Layout.tsx
with Lucide React icons. This ensures icons render properly on Raspberry Pi
which has issues loading the Material Icons web font.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris 1 săptămână în urmă
părinte
comite
023631ee72
1 a modificat fișierele cu 77 adăugiri și 63 ștergeri
  1. 77 63
      frontend/src/components/layout/Layout.tsx

+ 77 - 63
frontend/src/components/layout/Layout.tsx

@@ -1,6 +1,36 @@
 import { Outlet, Link, useLocation } from 'react-router-dom'
 import { Outlet, Link, useLocation } from 'react-router-dom'
 import { useEffect, useState, useRef, useCallback } from 'react'
 import { useEffect, useState, useRef, useCallback } from 'react'
 import { toast } from 'sonner'
 import { toast } from 'sonner'
+import {
+  Power,
+  RotateCcw,
+  RefreshCw,
+  Loader2,
+  CheckCircle,
+  Terminal,
+  Copy,
+  Eye,
+  EyeOff,
+  X,
+  Menu,
+  FileText,
+  Download,
+  HardDriveDownload,
+  Grid,
+  ListMusic,
+  SlidersHorizontal,
+  Lightbulb,
+  Settings as SettingsIcon,
+  Wifi,
+  WifiOff,
+  AlertCircle,
+  Home,
+  Sun,
+  Moon,
+  ChevronDown,
+  PlayCircle,
+  StopCircle,
+} from 'lucide-react'
 import { NowPlayingBar } from '@/components/NowPlayingBar'
 import { NowPlayingBar } from '@/components/NowPlayingBar'
 import { Button } from '@/components/ui/button'
 import { Button } from '@/components/ui/button'
 import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
 import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
@@ -12,11 +42,11 @@ import { apiClient } from '@/lib/apiClient'
 import ShinyText from '@/components/ShinyText'
 import ShinyText from '@/components/ShinyText'
 
 
 const navItems = [
 const navItems = [
-  { path: '/', label: 'Browse', icon: 'grid_view', title: 'Browse Patterns' },
-  { path: '/playlists', label: 'Playlists', icon: 'playlist_play', title: 'Playlists' },
-  { path: '/table-control', label: 'Control', icon: 'tune', title: 'Table Control' },
-  { path: '/led', label: 'LED', icon: 'lightbulb', title: 'LED Control' },
-  { path: '/settings', label: 'Settings', icon: 'settings', title: 'Settings' },
+  { path: '/', label: 'Browse', icon: Grid, title: 'Browse Patterns' },
+  { path: '/playlists', label: 'Playlists', icon: ListMusic, title: 'Playlists' },
+  { path: '/table-control', label: 'Control', icon: SlidersHorizontal, title: 'Table Control' },
+  { path: '/led', label: 'LED', icon: Lightbulb, title: 'LED Control' },
+  { path: '/settings', label: 'Settings', icon: SettingsIcon, title: 'Settings' },
 ]
 ]
 
 
 const DEFAULT_APP_NAME = 'Dune Weaver'
 const DEFAULT_APP_NAME = 'Dune Weaver'
@@ -997,9 +1027,7 @@ export function Layout() {
             <div className="p-6">
             <div className="p-6">
               <div className="text-center space-y-4">
               <div className="text-center space-y-4">
                 <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-destructive/10 mb-2">
                 <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-destructive/10 mb-2">
-                  <span className="material-icons-outlined text-4xl text-destructive">
-                    error_outline
-                  </span>
+<AlertCircle className="h-10 w-10 text-destructive" />
                 </div>
                 </div>
                 <h2 className="text-xl font-semibold">Sensor Homing Failed</h2>
                 <h2 className="text-xl font-semibold">Sensor Homing Failed</h2>
                 <p className="text-muted-foreground text-sm">
                 <p className="text-muted-foreground text-sm">
@@ -1029,7 +1057,7 @@ export function Layout() {
                       onClick={() => handleSensorHomingRecovery(false)}
                       onClick={() => handleSensorHomingRecovery(false)}
                       className="w-full gap-2"
                       className="w-full gap-2"
                     >
                     >
-                      <span className="material-icons text-base">refresh</span>
+                      <RefreshCw className="h-4 w-4" />
                       Retry Sensor Homing
                       Retry Sensor Homing
                     </Button>
                     </Button>
                     <Button
                     <Button
@@ -1037,7 +1065,7 @@ export function Layout() {
                       onClick={() => handleSensorHomingRecovery(true)}
                       onClick={() => handleSensorHomingRecovery(true)}
                       className="w-full gap-2"
                       className="w-full gap-2"
                     >
                     >
-                      <span className="material-icons text-base">sync_alt</span>
+                      <RefreshCw className="h-4 w-4" />
                       Switch to Crash Homing
                       Switch to Crash Homing
                     </Button>
                     </Button>
                     <p className="text-xs text-muted-foreground">
                     <p className="text-xs text-muted-foreground">
@@ -1046,7 +1074,7 @@ export function Layout() {
                   </div>
                   </div>
                 ) : (
                 ) : (
                   <div className="flex items-center justify-center gap-2 py-4">
                   <div className="flex items-center justify-center gap-2 py-4">
-                    <span className="material-icons-outlined text-primary animate-spin">sync</span>
+                    <Loader2 className="h-5 w-5 text-primary animate-spin" />
                     <span className="text-muted-foreground">Attempting recovery...</span>
                     <span className="text-muted-foreground">Attempting recovery...</span>
                   </div>
                   </div>
                 )}
                 )}
@@ -1062,9 +1090,7 @@ export function Layout() {
           <div className="w-full max-w-md space-y-6">
           <div className="w-full max-w-md space-y-6">
             <div className="text-center space-y-4">
             <div className="text-center space-y-4">
               <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-2">
               <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-2">
-                <span className="material-icons-outlined text-4xl text-primary animate-pulse">
-                  cached
-                </span>
+<RefreshCw className="h-10 w-10 text-primary animate-pulse" />
               </div>
               </div>
               <h2 className="text-2xl font-bold">Initializing Pattern Cache</h2>
               <h2 className="text-2xl font-bold">Initializing Pattern Cache</h2>
               <p className="text-muted-foreground">
               <p className="text-muted-foreground">
@@ -1113,9 +1139,7 @@ export function Layout() {
             <div className="p-6">
             <div className="p-6">
               <div className="text-center space-y-4">
               <div className="text-center space-y-4">
                 <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-primary/10 mb-2">
                 <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-primary/10 mb-2">
-                  <span className="material-icons-outlined text-2xl text-primary">
-                    download_for_offline
-                  </span>
+<HardDriveDownload className="h-6 w-6 text-primary" />
                 </div>
                 </div>
                 <h2 className="text-xl font-semibold">Cache All Pattern Previews?</h2>
                 <h2 className="text-xl font-semibold">Cache All Pattern Previews?</h2>
                 <p className="text-muted-foreground text-sm">
                 <p className="text-muted-foreground text-sm">
@@ -1135,7 +1159,7 @@ export function Layout() {
                       Skip for now
                       Skip for now
                     </Button>
                     </Button>
                     <Button variant="secondary" onClick={handleCacheAllPreviews} className="gap-2">
                     <Button variant="secondary" onClick={handleCacheAllPreviews} className="gap-2">
-                      <span className="material-icons-outlined text-lg">cached</span>
+                      <RefreshCw className="h-5 w-5" />
                       Cache All
                       Cache All
                     </Button>
                     </Button>
                   </div>
                   </div>
@@ -1163,7 +1187,7 @@ export function Layout() {
                 {cacheAllProgress?.done && (
                 {cacheAllProgress?.done && (
                   <div className="space-y-4">
                   <div className="space-y-4">
                     <p className="text-green-600 dark:text-green-400 flex items-center justify-center gap-2">
                     <p className="text-green-600 dark:text-green-400 flex items-center justify-center gap-2">
-                      <span className="material-icons text-base">check_circle</span>
+                      <CheckCircle className="h-4 w-4" />
                       All {cacheAllProgress.total} previews cached successfully!
                       All {cacheAllProgress.total} previews cached successfully!
                     </p>
                     </p>
                     <Button onClick={handleCloseCacheAllDone} className="w-full">
                     <Button onClick={handleCloseCacheAllDone} className="w-full">
@@ -1191,15 +1215,13 @@ export function Layout() {
                     ? 'bg-primary/10'
                     ? 'bg-primary/10'
                     : 'bg-amber-500/10'
                     : 'bg-amber-500/10'
               }`}>
               }`}>
-                <span className={`material-icons-outlined text-4xl ${
-                  homingJustCompleted
-                    ? 'text-green-500'
-                    : isHoming
-                      ? 'text-primary animate-spin'
-                      : 'text-amber-500 animate-pulse'
-                }`}>
-                  {homingJustCompleted ? 'check_circle' : 'sync'}
-                </span>
+{homingJustCompleted ? (
+                  <CheckCircle className="h-10 w-10 text-green-500" />
+                ) : isHoming ? (
+                  <Loader2 className="h-10 w-10 text-primary animate-spin" />
+                ) : (
+                  <Home className="h-10 w-10 text-amber-500 animate-pulse" />
+                )}
               </div>
               </div>
               <h2 className="text-2xl font-bold">
               <h2 className="text-2xl font-bold">
                 {homingJustCompleted
                 {homingJustCompleted
@@ -1244,7 +1266,7 @@ export function Layout() {
             <div className="bg-muted/50 rounded-lg border overflow-hidden">
             <div className="bg-muted/50 rounded-lg border overflow-hidden">
               <div className="flex items-center justify-between px-4 py-2 border-b bg-muted">
               <div className="flex items-center justify-between px-4 py-2 border-b bg-muted">
                 <div className="flex items-center gap-2">
                 <div className="flex items-center gap-2">
-                  <span className="material-icons-outlined text-base">terminal</span>
+                  <Terminal className="h-4 w-4" />
                   <span className="text-sm font-medium">
                   <span className="text-sm font-medium">
                     {isHoming || homingJustCompleted ? 'Homing Log' : 'Connection Log'}
                     {isHoming || homingJustCompleted ? 'Homing Log' : 'Connection Log'}
                   </span>
                   </span>
@@ -1260,7 +1282,7 @@ export function Layout() {
                     className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1 transition-colors"
                     className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1 transition-colors"
                     title="Copy logs to clipboard"
                     title="Copy logs to clipboard"
                   >
                   >
-                    <span className="material-icons text-sm">content_copy</span>
+                    <Copy className="h-3.5 w-3.5" />
                     Copy
                     Copy
                   </button>
                   </button>
                   <span className="text-xs text-muted-foreground">
                   <span className="text-xs text-muted-foreground">
@@ -1301,7 +1323,7 @@ export function Layout() {
                       onClick={() => setKeepHomingLogsOpen(true)}
                       onClick={() => setKeepHomingLogsOpen(true)}
                       className="gap-2"
                       className="gap-2"
                     >
                     >
-                      <span className="material-icons text-base">visibility</span>
+                      <Eye className="h-4 w-4" />
                       Keep Open
                       Keep Open
                     </Button>
                     </Button>
                     <Button
                     <Button
@@ -1311,7 +1333,7 @@ export function Layout() {
                       }}
                       }}
                       className="gap-2"
                       className="gap-2"
                     >
                     >
-                      <span className="material-icons text-base">close</span>
+                      <X className="h-4 w-4" />
                       Dismiss
                       Dismiss
                     </Button>
                     </Button>
                   </>
                   </>
@@ -1323,7 +1345,7 @@ export function Layout() {
                     }}
                     }}
                     className="gap-2"
                     className="gap-2"
                   >
                   >
-                    <span className="material-icons text-base">close</span>
+                    <X className="h-4 w-4" />
                     Close Logs
                     Close Logs
                   </Button>
                   </Button>
                 )}
                 )}
@@ -1338,7 +1360,7 @@ export function Layout() {
                   onClick={() => setHomingDismissed(true)}
                   onClick={() => setHomingDismissed(true)}
                   className="gap-2 text-muted-foreground"
                   className="gap-2 text-muted-foreground"
                 >
                 >
-                  <span className="material-icons text-base">visibility_off</span>
+                  <EyeOff className="h-4 w-4" />
                   Dismiss
                   Dismiss
                 </Button>
                 </Button>
               </div>
               </div>
@@ -1383,9 +1405,7 @@ export function Layout() {
                   shineColor={isDark ? '#ffffff' : '#999999'}
                   shineColor={isDark ? '#ffffff' : '#999999'}
                   spread={75}
                   spread={75}
                 />
                 />
-                <span className="material-icons-outlined text-muted-foreground text-sm group-hover:text-foreground transition-colors">
-                  expand_more
-                </span>
+<ChevronDown className="h-3.5 w-3.5 text-muted-foreground group-hover:text-foreground transition-colors" />
                 <span
                 <span
                   className={`w-2 h-2 rounded-full ${
                   className={`w-2 h-2 rounded-full ${
                     !isBackendConnected
                     !isBackendConnected
@@ -1416,7 +1436,7 @@ export function Layout() {
                   className="rounded-full"
                   className="rounded-full"
                   aria-label="Open menu"
                   aria-label="Open menu"
                 >
                 >
-                  <span className="material-icons-outlined">menu</span>
+                  <Menu className="h-5 w-5" />
                 </Button>
                 </Button>
               </PopoverTrigger>
               </PopoverTrigger>
               <PopoverContent align="end" className="w-56 p-2">
               <PopoverContent align="end" className="w-56 p-2">
@@ -1425,16 +1445,14 @@ export function Layout() {
                     onClick={() => setIsDark(!isDark)}
                     onClick={() => setIsDark(!isDark)}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">
-                      {isDark ? 'light_mode' : 'dark_mode'}
-                    </span>
+                    {isDark ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
                     {isDark ? 'Light Mode' : 'Dark Mode'}
                     {isDark ? 'Light Mode' : 'Dark Mode'}
                   </button>
                   </button>
                   <button
                   <button
                     onClick={handleToggleLogs}
                     onClick={handleToggleLogs}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">article</span>
+                    <FileText className="h-5 w-5" />
                     View Logs
                     View Logs
                   </button>
                   </button>
                   <Separator className="my-1" />
                   <Separator className="my-1" />
@@ -1442,14 +1460,14 @@ export function Layout() {
                     onClick={handleRestart}
                     onClick={handleRestart}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-amber-500"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-amber-500"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">restart_alt</span>
+                    <RotateCcw className="h-5 w-5" />
                     Restart Docker
                     Restart Docker
                   </button>
                   </button>
                   <button
                   <button
                     onClick={handleShutdown}
                     onClick={handleShutdown}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-red-500"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-red-500"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">power_settings_new</span>
+                    <Power className="h-5 w-5" />
                     Shutdown
                     Shutdown
                   </button>
                   </button>
                 </div>
                 </div>
@@ -1467,9 +1485,7 @@ export function Layout() {
                   className="rounded-full"
                   className="rounded-full"
                   aria-label="Open menu"
                   aria-label="Open menu"
                 >
                 >
-                  <span className="material-icons-outlined">
-                    {isMobileMenuOpen ? 'close' : 'menu'}
-                  </span>
+{isMobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
                 </Button>
                 </Button>
               </PopoverTrigger>
               </PopoverTrigger>
               <PopoverContent align="end" className="w-56 p-2">
               <PopoverContent align="end" className="w-56 p-2">
@@ -1481,9 +1497,7 @@ export function Layout() {
                     }}
                     }}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">
-                      {isDark ? 'light_mode' : 'dark_mode'}
-                    </span>
+                    {isDark ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
                     {isDark ? 'Light Mode' : 'Dark Mode'}
                     {isDark ? 'Light Mode' : 'Dark Mode'}
                   </button>
                   </button>
                   <button
                   <button
@@ -1493,7 +1507,7 @@ export function Layout() {
                     }}
                     }}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">article</span>
+                    <FileText className="h-5 w-5" />
                     View Logs
                     View Logs
                   </button>
                   </button>
                   <Separator className="my-1" />
                   <Separator className="my-1" />
@@ -1504,7 +1518,7 @@ export function Layout() {
                     }}
                     }}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-amber-500"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-amber-500"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">restart_alt</span>
+                    <RotateCcw className="h-5 w-5" />
                     Restart Docker
                     Restart Docker
                   </button>
                   </button>
                   <button
                   <button
@@ -1514,7 +1528,7 @@ export function Layout() {
                     }}
                     }}
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-red-500"
                     className="flex items-center gap-3 w-full px-3 py-2 text-sm rounded-md hover:bg-accent transition-colors text-red-500"
                   >
                   >
-                    <span className="material-icons-outlined text-xl">power_settings_new</span>
+                    <Power className="h-5 w-5" />
                     Shutdown
                     Shutdown
                   </button>
                   </button>
                 </div>
                 </div>
@@ -1603,7 +1617,7 @@ export function Layout() {
                   className="rounded-full"
                   className="rounded-full"
                   title="Copy logs"
                   title="Copy logs"
                 >
                 >
-                  <span className="material-icons-outlined text-base">content_copy</span>
+                  <Copy className="h-4 w-4" />
                 </Button>
                 </Button>
                 <Button
                 <Button
                   variant="ghost"
                   variant="ghost"
@@ -1612,7 +1626,7 @@ export function Layout() {
                   className="rounded-full"
                   className="rounded-full"
                   title="Download logs"
                   title="Download logs"
                 >
                 >
-                  <span className="material-icons-outlined text-base">download</span>
+                  <Download className="h-4 w-4" />
                 </Button>
                 </Button>
                 <Button
                 <Button
                   variant="ghost"
                   variant="ghost"
@@ -1621,7 +1635,7 @@ export function Layout() {
                   className="rounded-full"
                   className="rounded-full"
                   title="Close"
                   title="Close"
                 >
                 >
-                  <span className="material-icons-outlined text-base">close</span>
+                  <X className="h-4 w-4" />
                 </Button>
                 </Button>
               </div>
               </div>
             </div>
             </div>
@@ -1634,7 +1648,7 @@ export function Layout() {
               {/* Loading indicator for older logs */}
               {/* Loading indicator for older logs */}
               {isLoadingMoreLogs && (
               {isLoadingMoreLogs && (
                 <div className="flex items-center justify-center gap-2 py-2 text-muted-foreground">
                 <div className="flex items-center justify-center gap-2 py-2 text-muted-foreground">
-                  <span className="material-icons-outlined text-sm animate-spin">sync</span>
+                  <Loader2 className="h-3.5 w-3.5 animate-spin" />
                   <span>Loading older logs...</span>
                   <span>Loading older logs...</span>
                 </div>
                 </div>
               )}
               )}
@@ -1677,9 +1691,11 @@ export function Layout() {
           style={{ bottom: 'calc(4.5rem + env(safe-area-inset-bottom, 0px))' }}
           style={{ bottom: 'calc(4.5rem + env(safe-area-inset-bottom, 0px))' }}
           aria-label={isCurrentlyPlaying ? 'Now Playing' : 'Not Playing'}
           aria-label={isCurrentlyPlaying ? 'Now Playing' : 'Not Playing'}
         >
         >
-          <span className={`material-icons-outlined text-xl ${isCurrentlyPlaying ? 'text-primary' : 'text-muted-foreground'}`}>
-            {isCurrentlyPlaying ? 'play_circle' : 'stop_circle'}
-          </span>
+{isCurrentlyPlaying ? (
+            <PlayCircle className="h-5 w-5 text-primary" />
+          ) : (
+            <StopCircle className="h-5 w-5 text-muted-foreground" />
+          )}
           <span className="text-sm font-medium">
           <span className="text-sm font-medium">
             {isCurrentlyPlaying ? 'Now Playing' : 'Not Playing'}
             {isCurrentlyPlaying ? 'Now Playing' : 'Not Playing'}
           </span>
           </span>
@@ -1705,9 +1721,7 @@ export function Layout() {
                 {isActive && (
                 {isActive && (
                   <span className="absolute -top-0.5 w-8 h-1 rounded-full bg-primary" />
                   <span className="absolute -top-0.5 w-8 h-1 rounded-full bg-primary" />
                 )}
                 )}
-                <span className={`text-xl ${isActive ? 'material-icons' : 'material-icons-outlined'}`}>
-                  {item.icon}
-                </span>
+                <item.icon className="h-5 w-5" strokeWidth={isActive ? 2.5 : 1.5} />
                 <span className="text-xs font-medium">{item.label}</span>
                 <span className="text-xs font-medium">{item.label}</span>
               </Link>
               </Link>
             )
             )