/*
* Vencord, a Discord client mod
* Copyright (c) 2026 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button } from "@components/Button";
import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import type { RenderModalProps } from "@vencord/discord-types";
import { Checkbox, FluxDispatcher, MessageActions, Modal, openModal, PendingReplyStore, showToast, Toasts, useEffect, useState } from "@webpack/common";
import { ApplicationField, BooleanField, DateTimeField, getDateTimeLocalValue, ParticipantField, TextField } from "./fields";
import { abortActiveClipUploads, type ClipMetadata, getClipCreatedAt, getClipTitleFromName, getDefaultClipTitle, getDefaultFileName, getErrorMessage, getParticipantIds, getString, isValidDate, pickClipFile, uploadClipFile } from "./upload";
export function openUploadClipFileModal(channelId: string, clip?: ClipMetadata | null) {
openModal(modalProps => (
));
}
function UploadClipFileModal({ modalProps, channelId, clip }: { modalProps: RenderModalProps; channelId: string; clip?: ClipMetadata | null; }) {
const defaultFileName = getDefaultFileName();
const [file, setFile] = useState(null);
const [fileName, setFileName] = useState(defaultFileName);
const [participants, setParticipants] = useState(getParticipantIds(clip));
const [title, setTitle] = useState(getDefaultClipTitle(clip));
const [spoiler, setSpoiler] = useState(false);
const [remix, setRemix] = useState(false);
const [thumbnail, setThumbnail] = useState(false);
const [createdAt, setCreatedAt] = useState(getDateTimeLocalValue(getClipCreatedAt(clip)));
const [message, setMessage] = useState("");
const [applicationId, setApplicationId] = useState(getString(clip?.applicationId) ?? "");
const [uploading, setUploading] = useState(false);
const [parseMetadata, setParseMetadata] = useState(false);
const canUpload = Boolean(file && fileName.trim() && title.trim() && isValidDate(createdAt)) && !uploading;
const notice = createdAt && !isValidDate(createdAt)
? { message: "Created at must be a valid date.", type: "critical" as const }
: undefined;
useEffect(() => abortActiveClipUploads, []);
async function chooseClipFile() {
let result;
try {
result = await pickClipFile(parseMetadata);
if (!result) return;
} catch (error) {
showToast(getErrorMessage(error), Toasts.Type.FAILURE);
return;
}
const { file: picked, metadata } = result;
setFile(picked);
setFileName(name => name === defaultFileName ? picked.name : name);
const appName = metadata?.applicationName;
const appUsers = metadata?.users;
const appId = metadata?.applicationId;
setTitle(currentTitle => appName || currentTitle || getClipTitleFromName(picked.name));
if (appUsers?.length) setParticipants(current => current.length ? current : appUsers);
if (appId) setApplicationId(current => current || appId);
}
async function submit() {
if (!file || !canUpload) return;
setUploading(true);
const reply = PendingReplyStore.getPendingReply(channelId);
if (reply) FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId });
const success = await uploadClipFile(file, {
fileName: fileName.trim(),
participants,
title: title.trim(),
spoiler,
remix,
thumbnail,
createdAt: new Date(createdAt).toISOString(),
message,
channelId,
applicationId: applicationId.trim() || undefined,
remoteClipId: getString(clip?.remoteClipId),
eventsTimeline: clip?.eventsTimeline,
messageReference: reply ? MessageActions.getSendMessageOptionsForReply(reply)?.messageReference : null,
});
if (success) {
modalProps.onClose();
return;
}
setUploading(false);
}
return (
void submit(),
disabled: !canUpload
}
]}
>
File
{file?.name ?? "No file selected"}
setParseMetadata(checked)}
disabled={uploading}
type="row"
>
Parse clip metadata from file
);
}