Compare commits
2 Commits
fb80806819
...
7ebeac1280
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ebeac1280 | ||
|
|
ccd5973db9 |
87
src/components/ui/number-flow-duration.tsx
Normal file
87
src/components/ui/number-flow-duration.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import NumberFlow from '@number-flow/react'
|
||||||
|
import { formatDuration } from '@/utils/format-duration'
|
||||||
|
|
||||||
|
interface NumberFlowDurationProps {
|
||||||
|
duration: number
|
||||||
|
variant?: 'clock' | 'wordy'
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberFlowDuration({
|
||||||
|
duration,
|
||||||
|
variant = 'clock',
|
||||||
|
className
|
||||||
|
}: NumberFlowDurationProps) {
|
||||||
|
const formatted = formatDuration(duration)
|
||||||
|
|
||||||
|
// Split the formatted duration to get individual parts
|
||||||
|
const parts = formatted.split(':')
|
||||||
|
|
||||||
|
if (variant === 'clock') {
|
||||||
|
// For clock variant
|
||||||
|
if (parts.length === 2) {
|
||||||
|
// Format: MM:SS
|
||||||
|
const [minutes, seconds] = parts
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<NumberFlow
|
||||||
|
value={parseInt(minutes)}
|
||||||
|
digits={{ 1: { max: 5 } }}
|
||||||
|
format={{ minimumIntegerDigits: 2 }}
|
||||||
|
/>
|
||||||
|
<NumberFlow
|
||||||
|
value={parseInt(seconds)}
|
||||||
|
prefix=":"
|
||||||
|
digits={{ 1: { max: 5 } }}
|
||||||
|
format={{ minimumIntegerDigits: 2 }}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Format: HH:MM:SS
|
||||||
|
const [hours, minutes, seconds] = parts
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<NumberFlow
|
||||||
|
value={parseInt(hours)}
|
||||||
|
format={{ minimumIntegerDigits: 2 }}
|
||||||
|
/>
|
||||||
|
<NumberFlow
|
||||||
|
value={parseInt(minutes)}
|
||||||
|
prefix=":"
|
||||||
|
digits={{ 1: { max: 5 } }}
|
||||||
|
format={{ minimumIntegerDigits: 2 }}
|
||||||
|
/>
|
||||||
|
<NumberFlow
|
||||||
|
value={parseInt(seconds)}
|
||||||
|
prefix=":"
|
||||||
|
digits={{ 1: { max: 5 } }}
|
||||||
|
format={{ minimumIntegerDigits: 2 }}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For wordy variant
|
||||||
|
if (parts.length === 2) {
|
||||||
|
// Format: MM:SS
|
||||||
|
const [minutes, seconds] = parts
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<NumberFlow value={parseInt(minutes)} />m{' '}
|
||||||
|
<NumberFlow value={parseInt(seconds)} />s
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Format: HH:MM:SS
|
||||||
|
const [hours, minutes, seconds] = parts
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<NumberFlow value={parseInt(hours)} />h{' '}
|
||||||
|
<NumberFlow value={parseInt(minutes)} />m{' '}
|
||||||
|
<NumberFlow value={parseInt(seconds)} />s
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/components/ui/number-flow-size.tsx
Normal file
22
src/components/ui/number-flow-size.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import NumberFlow from '@number-flow/react'
|
||||||
|
import { formatSizeObject } from '@/utils/format-size'
|
||||||
|
|
||||||
|
interface NumberFlowSizeProps {
|
||||||
|
size: number
|
||||||
|
binary?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberFlowSize({
|
||||||
|
size,
|
||||||
|
binary = true,
|
||||||
|
className
|
||||||
|
}: NumberFlowSizeProps) {
|
||||||
|
const sizeObj = formatSizeObject(size, binary)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<NumberFlow value={sizeObj.value} /> {sizeObj.unit}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* Utility functions for formatting data for display
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function formatDuration(durationMs: number): string {
|
|
||||||
if (!durationMs) return "0s"
|
|
||||||
|
|
||||||
const totalSeconds = Math.floor(durationMs / 1000)
|
|
||||||
const hours = Math.floor(totalSeconds / 3600)
|
|
||||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
|
||||||
const seconds = totalSeconds % 60
|
|
||||||
|
|
||||||
if (hours > 0) {
|
|
||||||
return `${hours}h ${minutes}m ${seconds}s`
|
|
||||||
}
|
|
||||||
if (minutes > 0) {
|
|
||||||
return `${minutes}m ${seconds}s`
|
|
||||||
}
|
|
||||||
return `${seconds}s`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatFileSize(sizeBytes: number): string {
|
|
||||||
if (!sizeBytes) return "0 B"
|
|
||||||
|
|
||||||
const units = ["B", "KB", "MB", "GB", "TB"]
|
|
||||||
let size = sizeBytes
|
|
||||||
let unitIndex = 0
|
|
||||||
|
|
||||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
||||||
size /= 1024
|
|
||||||
unitIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unitIndex === 0) {
|
|
||||||
return `${Math.floor(size)} ${units[unitIndex]}`
|
|
||||||
}
|
|
||||||
return `${size.toFixed(1)} ${units[unitIndex]}`
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,9 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Volume2, Play, Clock, HardDrive, Music, Trophy, Loader2, RefreshCw } from 'lucide-react'
|
import { Volume2, Play, Clock, HardDrive, Music, Trophy, Loader2, RefreshCw } from 'lucide-react'
|
||||||
import { formatDuration } from '@/lib/format'
|
|
||||||
import NumberFlow from '@number-flow/react'
|
import NumberFlow from '@number-flow/react'
|
||||||
import { formatSizeObject } from '@/utils/format-size'
|
import { NumberFlowDuration } from '@/components/ui/number-flow-duration'
|
||||||
|
import { NumberFlowSize } from '@/components/ui/number-flow-size'
|
||||||
|
|
||||||
interface SoundboardStatistics {
|
interface SoundboardStatistics {
|
||||||
sound_count: number
|
sound_count: number
|
||||||
@@ -298,7 +298,9 @@ export function DashboardPage() {
|
|||||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{formatDuration(soundboardStatistics.total_duration)}</div>
|
<div className="text-2xl font-bold">
|
||||||
|
<NumberFlowDuration duration={soundboardStatistics.total_duration} variant='wordy' />
|
||||||
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Combined audio duration
|
Combined audio duration
|
||||||
</p>
|
</p>
|
||||||
@@ -312,14 +314,7 @@ export function DashboardPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">
|
<div className="text-2xl font-bold">
|
||||||
{(() => {
|
<NumberFlowSize size={soundboardStatistics.total_size} binary={true} />
|
||||||
const sizeObj = formatSizeObject(soundboardStatistics.total_size, true)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NumberFlow value={sizeObj.value} /> {sizeObj.unit}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Original + normalized files
|
Original + normalized files
|
||||||
@@ -365,7 +360,9 @@ export function DashboardPage() {
|
|||||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">{formatDuration(trackStatistics.total_duration)}</div>
|
<div className="text-2xl font-bold">
|
||||||
|
<NumberFlowDuration duration={trackStatistics.total_duration} variant='wordy' />
|
||||||
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Combined track duration
|
Combined track duration
|
||||||
</p>
|
</p>
|
||||||
@@ -379,14 +376,7 @@ export function DashboardPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">
|
<div className="text-2xl font-bold">
|
||||||
{(() => {
|
<NumberFlowSize size={trackStatistics.total_size} binary={true} />
|
||||||
const sizeObj = formatSizeObject(trackStatistics.total_size, true)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NumberFlow value={sizeObj.value} /> {sizeObj.unit}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Original + normalized files
|
Original + normalized files
|
||||||
@@ -478,7 +468,7 @@ export function DashboardPage() {
|
|||||||
{sound.duration && (
|
{sound.duration && (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Clock className="h-3 w-3" />
|
<Clock className="h-3 w-3" />
|
||||||
{formatDuration(sound.duration)}
|
<NumberFlowDuration duration={sound.duration} variant='wordy' />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="px-1.5 py-0.5 bg-secondary rounded text-xs">
|
<span className="px-1.5 py-0.5 bg-secondary rounded text-xs">
|
||||||
|
|||||||
Reference in New Issue
Block a user