Selaa lähdekoodia

Improve Now Playing bar UX and timezone input

Now Playing bar:
- Fix race condition where auto-collapse undid expansion on playlist start
- Listen to playback-started event for more reliable expansion
- Add 500ms delay before auto-collapse to prevent race conditions
- Click preview image in mini view to expand
- Click canvas in expanded view to collapse back

Settings (Still Sands timezone):
- Replace Select with Input + datalist for custom timezone support
- Add UTC offset options (UTC-12 to UTC+12)
- Users can now type any IANA timezone (e.g., Asia/Kolkata)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris 3 viikkoa sitten
vanhempi
sitoutus
f1f76db3e9
2 muutettua tiedostoa jossa 95 lisäystä ja 31 poistoa
  1. 40 7
      frontend/src/components/NowPlayingBar.tsx
  2. 55 24
      frontend/src/pages/SettingsPage.tsx

+ 40 - 7
frontend/src/components/NowPlayingBar.tsx

@@ -110,11 +110,36 @@ export function NowPlayingBar({ isLogsOpen = false, isVisible, openExpanded = fa
     }
   }, [openExpanded, isVisible])
 
-  // Auto-collapse when nothing is playing
+  // Listen for playback-started event from Layout (more reliable than prop)
+  useEffect(() => {
+    const handlePlaybackStarted = () => {
+      setIsExpanded(true)
+    }
+    window.addEventListener('playback-started', handlePlaybackStarted)
+    return () => window.removeEventListener('playback-started', handlePlaybackStarted)
+  }, [])
+
+  // Auto-collapse when nothing is playing (with delay to avoid race condition)
   const isPlaying = status?.is_running || status?.is_paused
+  const collapseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
   useEffect(() => {
+    // Clear any pending collapse
+    if (collapseTimeoutRef.current) {
+      clearTimeout(collapseTimeoutRef.current)
+      collapseTimeoutRef.current = null
+    }
+
     if (!isPlaying && isExpanded) {
-      setIsExpanded(false)
+      // Delay collapse to avoid race condition with playback-started
+      collapseTimeoutRef.current = setTimeout(() => {
+        setIsExpanded(false)
+      }, 500)
+    }
+
+    return () => {
+      if (collapseTimeoutRef.current) {
+        clearTimeout(collapseTimeoutRef.current)
+      }
     }
   }, [isPlaying, isExpanded])
 
@@ -571,8 +596,12 @@ export function NowPlayingBar({ isLogsOpen = false, isVisible, openExpanded = fa
             <div className="flex-1 flex flex-col">
               {/* Main row with preview and controls */}
               <div className="flex-1 flex items-center gap-6 px-6">
-                {/* Current Pattern Preview - Rounded */}
-                <div className="w-48 h-48 rounded-full overflow-hidden bg-muted shrink-0 border-2">
+                {/* Current Pattern Preview - Rounded (click to expand) */}
+                <div
+                  className="w-48 h-48 rounded-full overflow-hidden bg-muted shrink-0 border-2 cursor-pointer hover:border-primary transition-colors"
+                  onClick={() => isPlaying && setIsExpanded(true)}
+                  title={isPlaying ? 'Click to expand' : undefined}
+                >
                   {previewUrl && isPlaying ? (
                     <img
                       src={previewUrl}
@@ -709,13 +738,17 @@ export function NowPlayingBar({ isLogsOpen = false, isVisible, openExpanded = fa
           {isExpanded && isPlaying && (
             <div className="flex-1 flex flex-col md:items-center md:justify-center px-4 py-2 md:py-4 overflow-hidden">
               <div className="w-full max-w-5xl mx-auto flex flex-col md:flex-row md:items-center md:justify-center gap-3 md:gap-6">
-                {/* Canvas - full width on mobile */}
-                <div className="flex items-center justify-center flex-1 md:flex-none min-h-0">
+                {/* Canvas - full width on mobile (click to collapse) */}
+                <div
+                  className="flex items-center justify-center flex-1 md:flex-none min-h-0 cursor-pointer"
+                  onClick={() => setIsExpanded(false)}
+                  title="Click to collapse"
+                >
                   <canvas
                     ref={canvasRef}
                     width={600}
                     height={600}
-                    className="w-full max-h-full rounded-full border-2 md:w-auto"
+                    className="w-full max-h-full rounded-full border-2 md:w-auto hover:border-primary transition-colors"
                     style={{ aspectRatio: '1/1', maxHeight: '40vh' }}
                   />
                 </div>

+ 55 - 24
frontend/src/pages/SettingsPage.tsx

@@ -1915,33 +1915,64 @@ export function SettingsPage() {
                       <div>
                         <p className="text-sm font-medium">Timezone</p>
                         <p className="text-xs text-muted-foreground">
-                          Select a timezone for still periods
+                          Select or type a timezone (e.g., UTC+5, America/New_York)
                         </p>
                       </div>
                     </div>
-                    <Select
-                      value={stillSandsSettings.timezone || '__system__'}
-                      onValueChange={(value) =>
-                        setStillSandsSettings({ ...stillSandsSettings, timezone: value === '__system__' ? '' : value })
-                      }
-                    >
-                      <SelectTrigger className="w-[200px]">
-                        <SelectValue placeholder="System Default" />
-                      </SelectTrigger>
-                      <SelectContent>
-                        <SelectItem value="__system__">System Default</SelectItem>
-                        <SelectItem value="America/New_York">Eastern Time</SelectItem>
-                        <SelectItem value="America/Chicago">Central Time</SelectItem>
-                        <SelectItem value="America/Denver">Mountain Time</SelectItem>
-                        <SelectItem value="America/Los_Angeles">Pacific Time</SelectItem>
-                        <SelectItem value="Europe/London">London</SelectItem>
-                        <SelectItem value="Europe/Paris">Paris</SelectItem>
-                        <SelectItem value="Europe/Berlin">Berlin</SelectItem>
-                        <SelectItem value="Asia/Tokyo">Tokyo</SelectItem>
-                        <SelectItem value="Asia/Shanghai">Shanghai</SelectItem>
-                        <SelectItem value="Australia/Sydney">Sydney</SelectItem>
-                      </SelectContent>
-                    </Select>
+                    <div className="relative">
+                      <Input
+                        list="timezone-options"
+                        value={stillSandsSettings.timezone || ''}
+                        onChange={(e) =>
+                          setStillSandsSettings({ ...stillSandsSettings, timezone: e.target.value })
+                        }
+                        placeholder="System Default"
+                        className="w-[200px]"
+                      />
+                      <datalist id="timezone-options">
+                        {/* UTC Offsets */}
+                        <option value="Etc/GMT+12">UTC-12</option>
+                        <option value="Etc/GMT+11">UTC-11</option>
+                        <option value="Etc/GMT+10">UTC-10</option>
+                        <option value="Etc/GMT+9">UTC-9</option>
+                        <option value="Etc/GMT+8">UTC-8</option>
+                        <option value="Etc/GMT+7">UTC-7</option>
+                        <option value="Etc/GMT+6">UTC-6</option>
+                        <option value="Etc/GMT+5">UTC-5</option>
+                        <option value="Etc/GMT+4">UTC-4</option>
+                        <option value="Etc/GMT+3">UTC-3</option>
+                        <option value="Etc/GMT+2">UTC-2</option>
+                        <option value="Etc/GMT+1">UTC-1</option>
+                        <option value="UTC">UTC</option>
+                        <option value="Etc/GMT-1">UTC+1</option>
+                        <option value="Etc/GMT-2">UTC+2</option>
+                        <option value="Etc/GMT-3">UTC+3</option>
+                        <option value="Etc/GMT-4">UTC+4</option>
+                        <option value="Etc/GMT-5">UTC+5</option>
+                        <option value="Etc/GMT-6">UTC+6</option>
+                        <option value="Etc/GMT-7">UTC+7</option>
+                        <option value="Etc/GMT-8">UTC+8</option>
+                        <option value="Etc/GMT-9">UTC+9</option>
+                        <option value="Etc/GMT-10">UTC+10</option>
+                        <option value="Etc/GMT-11">UTC+11</option>
+                        <option value="Etc/GMT-12">UTC+12</option>
+                        {/* Americas */}
+                        <option value="America/New_York">America/New_York (Eastern)</option>
+                        <option value="America/Chicago">America/Chicago (Central)</option>
+                        <option value="America/Denver">America/Denver (Mountain)</option>
+                        <option value="America/Los_Angeles">America/Los_Angeles (Pacific)</option>
+                        {/* Europe */}
+                        <option value="Europe/London">Europe/London</option>
+                        <option value="Europe/Paris">Europe/Paris</option>
+                        <option value="Europe/Berlin">Europe/Berlin</option>
+                        {/* Asia */}
+                        <option value="Asia/Tokyo">Asia/Tokyo</option>
+                        <option value="Asia/Shanghai">Asia/Shanghai</option>
+                        <option value="Asia/Singapore">Asia/Singapore</option>
+                        {/* Australia */}
+                        <option value="Australia/Sydney">Australia/Sydney</option>
+                      </datalist>
+                    </div>
                   </div>
                 </div>