mirror of
https://github.com/usememos/memos.git
synced 2025-12-18 22:59:24 +08:00
Better design audio record button
This commit is contained in:
parent
abcab8d6ab
commit
9027ece395
2 changed files with 75 additions and 59 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue