Просмотр исходного кода

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 неделя назад
Родитель
Сommit
023631ee72
1 измененных файлов с 77 добавлено и 63 удалено
  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 { useEffect, useState, useRef, useCallback } from 'react'
 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 { Button } from '@/components/ui/button'
 import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
@@ -12,11 +42,11 @@ import { apiClient } from '@/lib/apiClient'
 import ShinyText from '@/components/ShinyText'
 
 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'
@@ -997,9 +1027,7 @@ export function Layout() {
             <div className="p-6">
               <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">
-                  <span className="material-icons-outlined text-4xl text-destructive">
-                    error_outline
-                  </span>
+<AlertCircle className="h-10 w-10 text-destructive" />
                 </div>
                 <h2 className="text-xl font-semibold">Sensor Homing Failed</h2>
                 <p className="text-muted-foreground text-sm">
@@ -1029,7 +1057,7 @@ export function Layout() {
                       onClick={() => handleSensorHomingRecovery(false)}
                       className="w-full gap-2"
                     >
-                      <span className="material-icons text-base">refresh</span>
+                      <RefreshCw className="h-4 w-4" />
                       Retry Sensor Homing
                     </Button>
                     <Button
@@ -1037,7 +1065,7 @@ export function Layout() {
                       onClick={() => handleSensorHomingRecovery(true)}
                       className="w-full gap-2"
                     >
-                      <span className="material-icons text-base">sync_alt</span>
+                      <RefreshCw className="h-4 w-4" />
                       Switch to Crash Homing
                     </Button>
                     <p className="text-xs text-muted-foreground">
@@ -1046,7 +1074,7 @@ export function Layout() {
                   </div>
                 ) : (
                   <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>
                   </div>
                 )}
@@ -1062,9 +1090,7 @@ export function Layout() {
           <div className="w-full max-w-md space-y-6">
             <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">
-                <span className="material-icons-outlined text-4xl text-primary animate-pulse">
-                  cached
-                </span>
+<RefreshCw className="h-10 w-10 text-primary animate-pulse" />
               </div>
               <h2 className="text-2xl font-bold">Initializing Pattern Cache</h2>
               <p className="text-muted-foreground">
@@ -1113,9 +1139,7 @@ export function Layout() {
             <div className="p-6">
               <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">
-                  <span className="material-icons-outlined text-2xl text-primary">
-                    download_for_offline
-                  </span>
+<HardDriveDownload className="h-6 w-6 text-primary" />
                 </div>
                 <h2 className="text-xl font-semibold">Cache All Pattern Previews?</h2>
                 <p className="text-muted-foreground text-sm">
@@ -1135,7 +1159,7 @@ export function Layout() {
                       Skip for now
                     </Button>
                     <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
                     </Button>
                   </div>
@@ -1163,7 +1187,7 @@ export function Layout() {
                 {cacheAllProgress?.done && (
                   <div className="space-y-4">
                     <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!
                     </p>
                     <Button onClick={handleCloseCacheAllDone} className="w-full">
@@ -1191,15 +1215,13 @@ export function Layout() {
                     ? 'bg-primary/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>
               <h2 className="text-2xl font-bold">
                 {homingJustCompleted
@@ -1244,7 +1266,7 @@ export function Layout() {
             <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 gap-2">
-                  <span className="material-icons-outlined text-base">terminal</span>
+                  <Terminal className="h-4 w-4" />
                   <span className="text-sm font-medium">
                     {isHoming || homingJustCompleted ? 'Homing Log' : 'Connection Log'}
                   </span>
@@ -1260,7 +1282,7 @@ export function Layout() {
                     className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1 transition-colors"
                     title="Copy logs to clipboard"
                   >
-                    <span className="material-icons text-sm">content_copy</span>
+                    <Copy className="h-3.5 w-3.5" />
                     Copy
                   </button>
                   <span className="text-xs text-muted-foreground">
@@ -1301,7 +1323,7 @@ export function Layout() {
                       onClick={() => setKeepHomingLogsOpen(true)}
                       className="gap-2"
                     >
-                      <span className="material-icons text-base">visibility</span>
+                      <Eye className="h-4 w-4" />
                       Keep Open
                     </Button>
                     <Button
@@ -1311,7 +1333,7 @@ export function Layout() {
                       }}
                       className="gap-2"
                     >
-                      <span className="material-icons text-base">close</span>
+                      <X className="h-4 w-4" />
                       Dismiss
                     </Button>
                   </>
@@ -1323,7 +1345,7 @@ export function Layout() {
                     }}
                     className="gap-2"
                   >
-                    <span className="material-icons text-base">close</span>
+                    <X className="h-4 w-4" />
                     Close Logs
                   </Button>
                 )}
@@ -1338,7 +1360,7 @@ export function Layout() {
                   onClick={() => setHomingDismissed(true)}
                   className="gap-2 text-muted-foreground"
                 >
-                  <span className="material-icons text-base">visibility_off</span>
+                  <EyeOff className="h-4 w-4" />
                   Dismiss
                 </Button>
               </div>
@@ -1383,9 +1405,7 @@ export function Layout() {
                   shineColor={isDark ? '#ffffff' : '#999999'}
                   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
                   className={`w-2 h-2 rounded-full ${
                     !isBackendConnected
@@ -1416,7 +1436,7 @@ export function Layout() {
                   className="rounded-full"
                   aria-label="Open menu"
                 >
-                  <span className="material-icons-outlined">menu</span>
+                  <Menu className="h-5 w-5" />
                 </Button>
               </PopoverTrigger>
               <PopoverContent align="end" className="w-56 p-2">
@@ -1425,16 +1445,14 @@ export function Layout() {
                     onClick={() => setIsDark(!isDark)}
                     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'}
                   </button>
                   <button
                     onClick={handleToggleLogs}
                     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
                   </button>
                   <Separator className="my-1" />
@@ -1442,14 +1460,14 @@ export function Layout() {
                     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"
                   >
-                    <span className="material-icons-outlined text-xl">restart_alt</span>
+                    <RotateCcw className="h-5 w-5" />
                     Restart Docker
                   </button>
                   <button
                     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"
                   >
-                    <span className="material-icons-outlined text-xl">power_settings_new</span>
+                    <Power className="h-5 w-5" />
                     Shutdown
                   </button>
                 </div>
@@ -1467,9 +1485,7 @@ export function Layout() {
                   className="rounded-full"
                   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>
               </PopoverTrigger>
               <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"
                   >
-                    <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'}
                   </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"
                   >
-                    <span className="material-icons-outlined text-xl">article</span>
+                    <FileText className="h-5 w-5" />
                     View Logs
                   </button>
                   <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"
                   >
-                    <span className="material-icons-outlined text-xl">restart_alt</span>
+                    <RotateCcw className="h-5 w-5" />
                     Restart Docker
                   </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"
                   >
-                    <span className="material-icons-outlined text-xl">power_settings_new</span>
+                    <Power className="h-5 w-5" />
                     Shutdown
                   </button>
                 </div>
@@ -1603,7 +1617,7 @@ export function Layout() {
                   className="rounded-full"
                   title="Copy logs"
                 >
-                  <span className="material-icons-outlined text-base">content_copy</span>
+                  <Copy className="h-4 w-4" />
                 </Button>
                 <Button
                   variant="ghost"
@@ -1612,7 +1626,7 @@ export function Layout() {
                   className="rounded-full"
                   title="Download logs"
                 >
-                  <span className="material-icons-outlined text-base">download</span>
+                  <Download className="h-4 w-4" />
                 </Button>
                 <Button
                   variant="ghost"
@@ -1621,7 +1635,7 @@ export function Layout() {
                   className="rounded-full"
                   title="Close"
                 >
-                  <span className="material-icons-outlined text-base">close</span>
+                  <X className="h-4 w-4" />
                 </Button>
               </div>
             </div>
@@ -1634,7 +1648,7 @@ export function Layout() {
               {/* Loading indicator for older logs */}
               {isLoadingMoreLogs && (
                 <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>
                 </div>
               )}
@@ -1677,9 +1691,11 @@ export function Layout() {
           style={{ bottom: 'calc(4.5rem + env(safe-area-inset-bottom, 0px))' }}
           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">
             {isCurrentlyPlaying ? 'Now Playing' : 'Not Playing'}
           </span>
@@ -1705,9 +1721,7 @@ export function Layout() {
                 {isActive && (
                   <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>
               </Link>
             )