feat: add media save guide

This commit is contained in:
TerryM
2026-06-01 23:00:28 +08:00
parent 7b48f9780c
commit e096d59fa6
16 changed files with 437 additions and 102 deletions

View File

@@ -3,8 +3,12 @@ import { DownloadCloudIcon } from "../icons/DownloadCloudIcon";
import { useState, type MouseEvent } from "react";
import { useI18n } from "../../i18n";
import type { Attachment } from "../../types/post";
import { downloadAttachment } from "./utils/downloadFile";
import { downloadAttachment, pauseActiveVideos } from "./utils/downloadFile";
import { formatBytes } from "./utils/formatBytes";
import {
mediaSaveKindFromAttachment,
useSaveToAlbumGuide,
} from "../SaveToAlbumGuide";
import { useToast } from "../Toast";
type AttachmentDownloadPillProps = {
@@ -40,14 +44,18 @@ export function AttachmentDownloadPill({
}: AttachmentDownloadPillProps) {
const { t } = useI18n();
const { showToast } = useToast();
const { showSaveToAlbumGuide } = useSaveToAlbumGuide();
const [isDownloading, setIsDownloading] = useState(false);
const handleDownload = async (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
if (isDownloading) return;
pauseActiveVideos();
setIsDownloading(true);
try {
await downloadAttachment(postId, attachment.id, attachment.filename);
const mediaKind = mediaSaveKindFromAttachment(attachment);
if (mediaKind) showSaveToAlbumGuide(mediaKind);
} catch {
showToast(t("downloadFail"), "error");
} finally {
@@ -84,6 +92,7 @@ export function AttachmentDownloadPill({
return (
<button
type="button"
onPointerDown={(e) => e.stopPropagation()}
onClick={handleDownload}
disabled={isDownloading}
className={`group z-10 inline-flex overflow-hidden rounded-full bg-black/80 ${fontCls} text-white shadow-lg ring-1 ring-inset ring-white/20 backdrop-blur-md transition hover:bg-black/90 disabled:cursor-wait ${className}`}

View File

@@ -9,11 +9,16 @@ import { filenameWithExtension, splitFilename } from "../utils/filenameDisplay";
import { formatBytes } from "../utils/formatBytes";
import { postDisplayText } from "../utils/postText";
import { CollapsibleText } from "../CollapsibleText";
import {
mediaSaveKindFromAttachment,
useSaveToAlbumGuide,
} from "../../SaveToAlbumGuide";
import { useToast } from "../../Toast";
function AttachmentRow({ postId, att }: { postId: string; att: Attachment }) {
const { t } = useI18n();
const { showToast } = useToast();
const { showSaveToAlbumGuide } = useSaveToAlbumGuide();
const { Icon, color } = fileIcon({ mime: att.mime, filename: att.filename });
const displayFilename = filenameWithExtension(att.filename, att.mime);
const [isDownloading, setIsDownloading] = useState(false);
@@ -24,6 +29,8 @@ function AttachmentRow({ postId, att }: { postId: string; att: Attachment }) {
setIsDownloading(true);
try {
await downloadAttachment(postId, att.id, displayFilename);
const mediaKind = mediaSaveKindFromAttachment(att);
if (mediaKind) showSaveToAlbumGuide(mediaKind);
} catch {
showToast(t("downloadFail"), "error");
} finally {

View File

@@ -13,13 +13,14 @@ import {
import { useVideoPlayer } from "../overlays/VideoPlayer";
import { autolink } from "../utils/autolink";
import { CollapsibleText } from "../CollapsibleText";
import { downloadAttachment } from "../utils/downloadFile";
import { downloadAttachment, pauseActiveVideos } from "../utils/downloadFile";
import { formatBytes } from "../utils/formatBytes";
import { postDisplayText } from "../utils/postText";
import {
videoMetadataPreviewSource,
videoPreviewSource,
} from "../utils/videoPreviewSource";
import { useSaveToAlbumGuide } from "../../SaveToAlbumGuide";
import { useToast } from "../../Toast";
const MAX_VISIBLE = 4;
@@ -167,13 +168,16 @@ function AttachmentListDownloadButton({
}) {
const { t } = useI18n();
const { showToast } = useToast();
const { showSaveToAlbumGuide } = useSaveToAlbumGuide();
const [isDownloading, setIsDownloading] = useState(false);
const handleDownload = async () => {
if (isDownloading) return;
pauseActiveVideos();
setIsDownloading(true);
try {
await downloadAttachment(postId, attachment.id, attachment.filename);
showSaveToAlbumGuide("video");
} catch {
showToast(t("downloadFail"), "error");
} finally {
@@ -184,6 +188,7 @@ function AttachmentListDownloadButton({
return (
<button
type="button"
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => {
e.stopPropagation();
handleDownload();

View File

@@ -1,5 +1,11 @@
import { assetUrl } from "../../../api";
export function pauseActiveVideos() {
document.querySelectorAll("video").forEach((video) => {
video.pause();
});
}
export function attachmentDownloadUrl(postId: string, attachmentId: string) {
return assetUrl(
`/api/posts/${encodeURIComponent(postId)}/attachments/${encodeURIComponent(