Files
simple-react-app-kit/packages/media/src/media-context-menu.tsx
T

254 lines
7.2 KiB
TypeScript
Raw Normal View History

import * as React from "react"
import {
ClipboardCopyIcon,
CloudUploadIcon,
CopyIcon,
FolderIcon,
FolderInputIcon,
HeartIcon,
InfoIcon,
LinkIcon,
PencilIcon,
Trash2Icon,
UploadIcon,
} from "@workspace/ui/components/icon"
import { useTranslate } from "@workspace/i18n"
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@workspace/ui/components/context-menu"
import { useMedia } from "./context"
import { mediaMessages } from "./messages"
import type {
MediaAsset,
MediaFolder,
MediaId,
MediaStorageTarget,
} from "./types"
import { formatMediaFileSize, getMediaFolderPath } from "./utils"
export interface MediaContextMenuProps {
asset?: MediaAsset
children: React.ReactElement
folders: readonly MediaFolder[]
storageTargets: readonly MediaStorageTarget[]
onDelete: (asset: MediaAsset) => void
onDetails: (asset: MediaAsset) => void
onFavorite: (asset: MediaAsset) => void
onMove: (asset: MediaAsset, folderId: MediaId | null) => void
onRename: (asset: MediaAsset) => void
onUpload: (target: MediaStorageTarget) => void
}
function runAfterClose(action: VoidFunction) {
window.setTimeout(action)
}
export function MediaContextMenu({
asset,
children,
folders,
storageTargets,
onDelete,
onDetails,
onFavorite,
onMove,
onRename,
onUpload,
}: MediaContextMenuProps) {
const { adapter, notify } = useMedia()
const t = useTranslate()
const resolveUrl = React.useCallback(
(item: MediaAsset) => adapter.resolveUrl?.(item) ?? item.url,
[adapter]
)
const localTargets = storageTargets.filter(
(target) => target.kind === "local"
)
const cloudTargets = storageTargets.filter(
(target) => target.kind === "cloud"
)
const copy = async (text: string, successMessage: string) => {
try {
await navigator.clipboard.writeText(text)
notify?.({ message: successMessage, tone: "success" })
} catch {
notify?.({ message: t(mediaMessages.copyFailed), tone: "error" })
}
}
return (
<ContextMenu>
<ContextMenuTrigger render={children} />
<ContextMenuContent>
{asset ? (
<>
<ContextMenuItem
onClick={() => runAfterClose(() => onDetails(asset))}
>
<InfoIcon />
{t(mediaMessages.details)}
</ContextMenuItem>
<ContextMenuItem onClick={() => onFavorite(asset)}>
<HeartIcon
className={asset.favorite ? "fill-current" : undefined}
/>
{asset.favorite
? t(mediaMessages.unfavorite)
: t(mediaMessages.favoriteAction)}
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>
<FolderInputIcon />
{t(mediaMessages.moveToFolder)}
</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem
disabled={asset.folderId === null}
onClick={() => onMove(asset, null)}
>
<FolderIcon />
{t(mediaMessages.unclassified)}
</ContextMenuItem>
{folders.map((folder) => (
<ContextMenuItem
key={folder.id}
disabled={asset.folderId === folder.id}
onClick={() => onMove(asset, folder.id)}
>
<FolderIcon />
{getMediaFolderPath(folder, folders)}
</ContextMenuItem>
))}
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuItem
onClick={() => runAfterClose(() => onRename(asset))}
>
<PencilIcon />
{t(mediaMessages.rename)}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
onClick={() => void copy(asset.name, t(mediaMessages.copyName))}
>
<CopyIcon />
{t(mediaMessages.copyName)}
</ContextMenuItem>
<ContextMenuItem
onClick={() =>
void copy(resolveUrl(asset), t(mediaMessages.copyLink))
}
>
<LinkIcon />
{t(mediaMessages.copyLink)}
</ContextMenuItem>
<ContextMenuItem
onClick={() =>
void copy(
[
`${t(mediaMessages.fileName)}: ${asset.name}`,
`${t(mediaMessages.type)}: ${asset.mimeType}`,
`${t(mediaMessages.size)}: ${formatMediaFileSize(asset.sizeBytes)}`,
`${t(mediaMessages.uploadedAt)}: ${new Date(asset.createdAt).toLocaleString()}`,
`${t(mediaMessages.copyLink)}: ${resolveUrl(asset)}`,
].join("\n"),
t(mediaMessages.copyInformation)
)
}
>
<ClipboardCopyIcon />
{t(mediaMessages.copyInformation)}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
variant="destructive"
onClick={() => runAfterClose(() => onDelete(asset))}
>
<Trash2Icon />
{t(mediaMessages.delete)}
</ContextMenuItem>
</>
) : (
<UploadItems
cloudTargets={cloudTargets}
localTargets={localTargets}
onUpload={onUpload}
/>
)}
</ContextMenuContent>
</ContextMenu>
)
}
function UploadItems({
cloudTargets,
localTargets,
onUpload,
}: {
cloudTargets: readonly MediaStorageTarget[]
localTargets: readonly MediaStorageTarget[]
onUpload: (target: MediaStorageTarget) => void
}) {
const t = useTranslate()
return (
<>
{localTargets.map((target) => (
<UploadTargetItem key={target.id} target={target} onUpload={onUpload} />
))}
{cloudTargets.length > 1 ? (
<>
{localTargets.length > 0 && <ContextMenuSeparator />}
<ContextMenuGroup>
<ContextMenuLabel>
{t(mediaMessages.chooseStorage)}
</ContextMenuLabel>
{cloudTargets.map((target) => (
<UploadTargetItem
key={target.id}
target={target}
onUpload={onUpload}
/>
))}
</ContextMenuGroup>
</>
) : (
cloudTargets.map((target) => (
<UploadTargetItem
key={target.id}
target={target}
onUpload={onUpload}
/>
))
)}
</>
)
}
function UploadTargetItem({
target,
onUpload,
}: {
target: MediaStorageTarget
onUpload: (target: MediaStorageTarget) => void
}) {
const t = useTranslate()
return (
<ContextMenuItem onClick={() => onUpload(target)}>
{target.kind === "local" ? <UploadIcon /> : <CloudUploadIcon />}
{target.kind === "local"
? t(mediaMessages.serverUpload)
: `${t(mediaMessages.cloudUpload)}: ${target.label}`}
</ContextMenuItem>
)
}