Better design audio record button

This commit is contained in:
TheNexter 2025-11-18 18:13:29 +01:00
parent abcab8d6ab
commit 9027ece395
2 changed files with 75 additions and 59 deletions

View file

@ -142,67 +142,63 @@ const InsertMenu = observer((props: Props) => {
return ( return (
<> <>
<DropdownMenu> {audioRecorder.isRecording ? (
<DropdownMenuTrigger asChild> <div className="flex flex-row items-center gap-2 mr-2">
{audioRecorder.isRecording ? ( <div className="flex flex-row items-center px-2 py-1 rounded-md bg-red-50 text-red-600 border border-red-200">
<Button variant="outline" className="text-red-500 border-red-500 hover:text-red-600 hover:bg-red-50"> <div className={`w-2 h-2 rounded-full bg-red-500 mr-2 ${!audioRecorder.isPaused ? "animate-pulse" : ""}`} />
<div className="w-2 h-2 rounded-full bg-red-500 animate-pulse mr-2" /> <span className="font-mono text-sm">{new Date(audioRecorder.recordingTime * 1000).toISOString().substr(14, 5)}</span>
{new Date(audioRecorder.recordingTime * 1000).toISOString().substr(14, 5)} </div>
</Button> <Button variant="outline" size="icon" onClick={audioRecorder.togglePause} className="shrink-0">
) : ( {audioRecorder.isPaused ? <MicIcon className="w-4 h-4" /> : <span className="font-bold text-xs">||</span>}
</Button>
<Button variant="outline" size="icon" onClick={handleStopRecording} className="shrink-0 text-red-600 hover:text-red-700">
<div className="w-3 h-3 bg-current rounded-sm" />
</Button>
<Button variant="ghost" size="icon" onClick={audioRecorder.cancelRecording} className="shrink-0">
<XIcon className="w-4 h-4" />
</Button>
</div>
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="shadow-none" disabled={isUploading}> <Button variant="outline" size="icon" className="shadow-none" disabled={isUploading}>
{isUploading ? <LoaderIcon className="size-4 animate-spin" /> : <PlusIcon className="size-4" />} {isUploading ? <LoaderIcon className="size-4 animate-spin" /> : <PlusIcon className="size-4" />}
</Button> </Button>
)} </DropdownMenuTrigger>
</DropdownMenuTrigger> <DropdownMenuContent align="start">
<DropdownMenuContent align="start"> <DropdownMenuItem onClick={handleUploadClick}>
{audioRecorder.isRecording ? ( <FileIcon className="w-4 h-4" />
<> {t("common.upload")}
<DropdownMenuItem onClick={handleStopRecording} className="text-red-500 focus:text-red-600 focus:bg-red-50"> </DropdownMenuItem>
<div className="w-2 h-2 rounded-full bg-red-500 mr-2" /> <DropdownMenuItem onClick={() => setLinkDialogOpen(true)}>
Stop Recording <LinkIcon className="w-4 h-4" />
</DropdownMenuItem> {t("tooltip.link-memo")}
<DropdownMenuItem onClick={audioRecorder.cancelRecording}> </DropdownMenuItem>
<XIcon className="w-4 h-4 mr-2" /> <DropdownMenuItem onClick={handleLocationClick}>
Cancel Recording <MapPinIcon className="w-4 h-4" />
</DropdownMenuItem> {t("tooltip.select-location")}
</> </DropdownMenuItem>
) : ( <DropdownMenuItem onClick={audioRecorder.startRecording}>
<> <MicIcon className="w-4 h-4" />
<DropdownMenuItem onClick={handleUploadClick}> Record Audio
<FileIcon className="w-4 h-4" /> </DropdownMenuItem>
{t("common.upload")} {/* View submenu with Focus Mode */}
</DropdownMenuItem> <DropdownMenuSub>
<DropdownMenuItem onClick={() => setLinkDialogOpen(true)}> <DropdownMenuSubTrigger>
<LinkIcon className="w-4 h-4" /> <MoreHorizontalIcon className="w-4 h-4" />
{t("tooltip.link-memo")} {t("common.more")}
</DropdownMenuItem> </DropdownMenuSubTrigger>
<DropdownMenuItem onClick={handleLocationClick}> <DropdownMenuSubContent>
<MapPinIcon className="w-4 h-4" /> <DropdownMenuItem onClick={props.onToggleFocusMode}>
{t("tooltip.select-location")} <Maximize2Icon className="w-4 h-4" />
</DropdownMenuItem> {t("editor.focus-mode")}
<DropdownMenuItem onClick={audioRecorder.startRecording}> <span className="ml-auto text-xs text-muted-foreground opacity-60">F</span>
<MicIcon className="w-4 h-4" /> </DropdownMenuItem>
Record Audio </DropdownMenuSubContent>
</DropdownMenuItem> </DropdownMenuSub>
{/* View submenu with Focus Mode */} </DropdownMenuContent>
<DropdownMenuSub> </DropdownMenu>
<DropdownMenuSubTrigger> )}
<MoreHorizontalIcon className="w-4 h-4" />
{t("common.more")}
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem onClick={props.onToggleFocusMode}>
<Maximize2Icon className="w-4 h-4" />
{t("editor.focus-mode")}
<span className="ml-auto text-xs text-muted-foreground opacity-60">F</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
{/* Hidden file input */} {/* Hidden file input */}
<input <input

View file

@ -33,7 +33,12 @@ export const useAudioRecorder = () => {
setState((prev: AudioRecorderState) => ({ ...prev, isRecording: true, mediaRecorder })); setState((prev: AudioRecorderState) => ({ ...prev, isRecording: true, mediaRecorder }));
timerRef.current = window.setInterval(() => { timerRef.current = window.setInterval(() => {
setState((prev: AudioRecorderState) => ({ ...prev, recordingTime: prev.recordingTime + 1 })); setState((prev) => {
if (prev.isPaused) {
return prev;
}
return { ...prev, recordingTime: prev.recordingTime + 1 };
});
}, 1000); }, 1000);
} catch (error) { } catch (error) {
console.error("Error accessing microphone:", error); console.error("Error accessing microphone:", error);
@ -93,11 +98,26 @@ export const useAudioRecorder = () => {
}); });
}; };
const togglePause = () => {
const { mediaRecorder, isPaused } = state;
if (!mediaRecorder) return;
if (isPaused) {
mediaRecorder.resume();
} else {
mediaRecorder.pause();
}
setState((prev) => ({ ...prev, isPaused: !prev.isPaused }));
};
return { return {
isRecording: state.isRecording, isRecording: state.isRecording,
isPaused: state.isPaused,
recordingTime: state.recordingTime, recordingTime: state.recordingTime,
startRecording, startRecording,
stopRecording, stopRecording,
cancelRecording, cancelRecording,
togglePause,
}; };
}; };