Commit 124708f1 authored by boojack's avatar boojack

chore: refactor attachment media layout and insert menu organization

parent 7e21b728
......@@ -2,6 +2,7 @@ import { LatLng } from "leaflet";
import { uniqBy } from "lodash-es";
import {
FileIcon,
ImageIcon,
LinkIcon,
LoaderIcon,
type LucideIcon,
......@@ -131,14 +132,22 @@ const InsertMenu = (props: InsertMenuProps) => {
setMoreSubmenuOpen(false);
}, [onToggleFocusMode]);
const handleMediaUploadClick = useCallback(() => {
handleUploadClick("image/*,video/*");
}, [handleUploadClick]);
const handleFileUploadClick = useCallback(() => {
handleUploadClick();
}, [handleUploadClick]);
const menuItems = useMemo(
() =>
[
{
key: "upload",
label: t("editor.insert-menu.upload-file"),
icon: FileIcon,
onClick: handleUploadClick,
key: "upload-media",
label: t("attachment-library.tabs.media"),
icon: ImageIcon,
onClick: handleMediaUploadClick,
},
{
key: "record-audio",
......@@ -146,6 +155,12 @@ const InsertMenu = (props: InsertMenuProps) => {
icon: MicIcon,
onClick: () => props.onAudioRecorderClick?.(),
},
{
key: "upload-file",
label: t("common.file"),
icon: FileIcon,
onClick: handleFileUploadClick,
},
{
key: "link",
label: t("editor.insert-menu.link-memo"),
......@@ -159,7 +174,7 @@ const InsertMenu = (props: InsertMenuProps) => {
onClick: handleLocationClick,
},
] satisfies Array<{ key: string; label: string; icon: LucideIcon; onClick: () => void }>,
[handleLocationClick, handleOpenLinkDialog, handleUploadClick, props, t],
[handleFileUploadClick, handleLocationClick, handleMediaUploadClick, handleOpenLinkDialog, props, t],
);
return (
......@@ -171,14 +186,14 @@ const InsertMenu = (props: InsertMenuProps) => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
{menuItems.slice(0, 2).map((item) => (
{menuItems.slice(0, 3).map((item) => (
<DropdownMenuItem key={item.key} onClick={item.onClick}>
<item.icon className="w-4 h-4" />
{item.label}
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
{menuItems.slice(2).map((item) => (
{menuItems.slice(3).map((item) => (
<DropdownMenuItem key={item.key} onClick={item.onClick}>
<item.icon className="w-4 h-4" />
{item.label}
......
......@@ -26,8 +26,13 @@ export const useFileUpload = (onFilesSelected: (localFiles: LocalFile[]) => void
if (fileInputRef.current) fileInputRef.current.value = "";
};
const handleUploadClick = () => {
fileInputRef.current?.click();
const handleUploadClick = (accept = "*") => {
if (!fileInputRef.current) {
return;
}
fileInputRef.current.accept = accept;
fileInputRef.current.click();
};
return {
......
import { PauseIcon, PlayIcon } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { formatFileSize, getFileTypeLabel } from "@/utils/format";
import { formatAudioTime } from "./attachmentHelpers";
......@@ -56,9 +57,11 @@ interface AudioAttachmentItemProps {
mimeType: string;
size?: number;
title?: string;
compact?: boolean;
className?: string;
}
const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: AudioAttachmentItemProps) => {
const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title, compact = false, className }: AudioAttachmentItemProps) => {
const audioRef = useRef<HTMLAudioElement>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
......@@ -119,9 +122,9 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud
};
return (
<div className="rounded-xl border border-border/40 bg-background/75 px-2 py-1.5">
<div className="flex items-center justify-between gap-1.5">
<div className="min-w-0 flex flex-1 items-baseline gap-1">
<div className={cn("rounded-xl border border-border/40 bg-background/75", compact ? "px-3 py-2.5" : "px-2 py-1.5", className)}>
<div className="flex items-start justify-between gap-2">
<div className={cn("min-w-0 flex flex-1", compact ? "flex-col gap-0.5" : "items-baseline gap-1")}>
<div className="truncate text-sm font-medium leading-5 text-foreground" title={filename}>
{displayTitle}
</div>
......@@ -141,7 +144,7 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud
</button>
</div>
<div className="mt-1 flex items-center gap-1">
<div className={cn("mt-1", compact ? "space-y-1.5" : "flex items-center gap-1")}>
<AudioProgressBar
filename={filename}
currentTime={currentTime}
......@@ -151,16 +154,18 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud
className="min-w-0 flex-1"
/>
<div className="shrink-0 text-[10px] tabular-nums text-muted-foreground">{timeLabel}</div>
<button
type="button"
onClick={handlePlaybackRateChange}
className="inline-flex h-5 shrink-0 items-center justify-center rounded-md border border-transparent px-1 text-[10px] font-medium text-muted-foreground transition-colors hover:border-border/40 hover:text-foreground"
aria-label={`Playback speed ${playbackRate}x for ${displayTitle}`}
>
{playbackRate}x
</button>
<div className={cn("flex items-center", compact ? "justify-between gap-2" : "shrink-0 gap-1")}>
<div className="shrink-0 text-[10px] tabular-nums text-muted-foreground">{timeLabel}</div>
<button
type="button"
onClick={handlePlaybackRateChange}
className="inline-flex h-5 shrink-0 items-center justify-center rounded-md border border-transparent px-1 text-[10px] font-medium text-muted-foreground transition-colors hover:border-border/40 hover:text-foreground"
aria-label={`Playback speed ${playbackRate}x for ${displayTitle}`}
>
{playbackRate}x
</button>
</div>
</div>
<audio
......
......@@ -8,6 +8,8 @@ interface MotionPhotoPlayerProps {
presentationTimestampUs?: bigint;
containerClassName?: string;
mediaClassName?: string;
posterClassName?: string;
videoClassName?: string;
active?: boolean;
loop?: boolean;
}
......@@ -19,6 +21,8 @@ const MotionPhotoPlayer = ({
presentationTimestampUs,
containerClassName,
mediaClassName,
posterClassName,
videoClassName,
active,
loop = false,
}: MotionPhotoPlayerProps) => {
......@@ -87,7 +91,7 @@ const MotionPhotoPlayer = ({
<img
src={posterUrl}
alt={alt}
className={cn("block max-h-full max-w-full select-none object-cover", mediaClassName)}
className={cn("block max-h-full max-w-full select-none object-cover", mediaClassName, posterClassName)}
draggable={false}
loading="lazy"
decoding="async"
......@@ -100,6 +104,7 @@ const MotionPhotoPlayer = ({
"pointer-events-none absolute inset-0 h-full w-full object-cover transition-opacity duration-200",
isPlaying ? "opacity-100" : "opacity-0",
mediaClassName,
videoClassName,
)}
muted
playsInline
......
......@@ -9,6 +9,8 @@ interface MotionPhotoPreviewProps {
presentationTimestampUs?: bigint;
containerClassName?: string;
mediaClassName?: string;
posterClassName?: string;
videoClassName?: string;
badgeClassName?: string;
loop?: boolean;
}
......@@ -20,6 +22,8 @@ const MotionPhotoPreview = ({
presentationTimestampUs,
containerClassName,
mediaClassName,
posterClassName,
videoClassName,
badgeClassName,
loop = false,
}: MotionPhotoPreviewProps) => {
......@@ -40,6 +44,8 @@ const MotionPhotoPreview = ({
loop={loop}
containerClassName={cn("max-w-full max-h-full", containerClassName)}
mediaClassName={mediaClassName}
posterClassName={posterClassName}
videoClassName={videoClassName}
/>
<div
role="button"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment