diff --git a/web/src/components/MemoMetadata/Attachment/AudioAttachmentItem.tsx b/web/src/components/MemoMetadata/Attachment/AudioAttachmentItem.tsx index c17df520a..c3b801c72 100644 --- a/web/src/components/MemoMetadata/Attachment/AudioAttachmentItem.tsx +++ b/web/src/components/MemoMetadata/Attachment/AudioAttachmentItem.tsx @@ -1,21 +1,30 @@ -import { FileAudioIcon, PauseIcon, PlayIcon } from "lucide-react"; +import { PauseIcon, PlayIcon } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { formatFileSize, getFileTypeLabel } from "@/utils/format"; import { formatAudioTime } from "./attachmentHelpers"; const AUDIO_PLAYBACK_RATES = [1, 1.5, 2] as const; +const UNKNOWN_DURATION_LABEL = "--:--"; + +const getDurationLabel = (duration: number): string => (duration > 0 ? formatAudioTime(duration) : UNKNOWN_DURATION_LABEL); + +const getNextPlaybackRate = (currentRate: (typeof AUDIO_PLAYBACK_RATES)[number]): (typeof AUDIO_PLAYBACK_RATES)[number] => { + const currentRateIndex = AUDIO_PLAYBACK_RATES.findIndex((rate) => rate === currentRate); + return AUDIO_PLAYBACK_RATES[(currentRateIndex + 1) % AUDIO_PLAYBACK_RATES.length]; +}; interface AudioProgressBarProps { filename: string; currentTime: number; duration: number; progressPercent: number; - onSeek: (value: string) => void; + onSeek: (value: number) => void; + className?: string; } -const AudioProgressBar = ({ filename, currentTime, duration, progressPercent, onSeek }: AudioProgressBarProps) => ( -
-
+const AudioProgressBar = ({ filename, currentTime, duration, progressPercent, onSeek, className }: AudioProgressBarProps) => ( +
+
onSeek(e.target.value)} + onChange={(e) => onSeek(Number(e.target.value))} aria-label={`Seek ${filename}`} - className="relative z-10 h-4 w-full cursor-pointer appearance-none bg-transparent outline-none disabled:cursor-default + className="relative z-10 h-3.5 w-full cursor-pointer appearance-none bg-transparent outline-none disabled:cursor-default [&::-webkit-slider-runnable-track]:h-1 [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-transparent - [&::-webkit-slider-thumb]:mt-[-3px] [&::-webkit-slider-thumb]:size-2 [&::-webkit-slider-thumb]:appearance-none + [&::-webkit-slider-thumb]:mt-[-2.5px] [&::-webkit-slider-thumb]:size-2 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-border/50 [&::-webkit-slider-thumb]:bg-background/95 [&::-moz-range-track]:h-1 [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-transparent @@ -38,9 +47,6 @@ const AudioProgressBar = ({ filename, currentTime, duration, progressPercent, on disabled={duration === 0} />
-
- {formatAudioTime(currentTime)} / {duration > 0 ? formatAudioTime(duration) : "--:--"} -
); @@ -62,6 +68,9 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud const fileTypeLabel = getFileTypeLabel(mimeType); const fileSizeLabel = size ? formatFileSize(size) : undefined; const progressPercent = duration > 0 ? (currentTime / duration) * 100 : 0; + const currentTimeLabel = formatAudioTime(currentTime); + const durationLabel = getDurationLabel(duration); + const timeLabel = `${currentTimeLabel} / ${durationLabel}`; useEffect(() => { if (!audioRef.current) { @@ -90,9 +99,8 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud audio.pause(); }; - const handleSeek = (value: string) => { + const handleSeek = (nextTime: number) => { const audio = audioRef.current; - const nextTime = Number(value); if (!audio || Number.isNaN(nextTime)) { return; @@ -103,9 +111,7 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud }; const handlePlaybackRateChange = () => { - const currentRateIndex = AUDIO_PLAYBACK_RATES.findIndex((rate) => rate === playbackRate); - const nextRate = AUDIO_PLAYBACK_RATES[(currentRateIndex + 1) % AUDIO_PLAYBACK_RATES.length]; - setPlaybackRate(nextRate); + setPlaybackRate((currentRate) => getNextPlaybackRate(currentRate)); }; const handleDuration = (value: number) => { @@ -113,56 +119,49 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud }; return ( -
-
-
- -
- -
-
-
- {displayTitle} -
-
- {fileTypeLabel} - {fileSizeLabel && ( - <> - - {fileSizeLabel} - - )} -
+
+
+
+
+ {displayTitle}
- -
- - +
+ {fileTypeLabel} + {fileSizeLabel ? ` · ${fileSizeLabel}` : ""}
+ +
- +
+ + +
{timeLabel}
+ + +