mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-18 03:20:35 -05:00
feat: Support HEIC format for images
This commit is contained in:
@@ -114,7 +114,7 @@ export const EditWelcomeCard = ({
|
||||
<div className="mt-3 flex w-full items-center justify-center">
|
||||
<FileInput
|
||||
id="welcome-card-image"
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
|
||||
environmentId={environmentId}
|
||||
onFileUpload={(url: string[]) => {
|
||||
updateSurvey({ fileUrl: url[0] });
|
||||
|
||||
@@ -16,7 +16,7 @@ export const UploadImageSurveyBg = ({
|
||||
<div className="flex w-full items-center justify-center">
|
||||
<FileInput
|
||||
id="survey-bg-file-input"
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
|
||||
environmentId={environmentId}
|
||||
onFileUpload={(url: string[]) => {
|
||||
if (url.length > 0) {
|
||||
|
||||
@@ -131,7 +131,7 @@ export const PictureSelectionForm = ({
|
||||
<div className="mt-3 flex w-full items-center justify-center">
|
||||
<FileInput
|
||||
id="choices-file-input"
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
|
||||
environmentId={environmentId}
|
||||
onFileUpload={handleFileInputChanges}
|
||||
fileUrl={question?.choices?.map((choice) => choice.imageUrl)}
|
||||
|
||||
@@ -138,7 +138,7 @@ export const EditLogo = ({ project, environmentId, isReadOnly }: EditLogoProps)
|
||||
) : (
|
||||
<FileInput
|
||||
id="logo-input"
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
|
||||
environmentId={environmentId}
|
||||
onFileUpload={(files: string[]) => {
|
||||
setLogoUrl(files[0]);
|
||||
|
||||
@@ -307,7 +307,7 @@ export const QuestionFormInput = ({
|
||||
{showImageUploader && id === "headline" && (
|
||||
<FileInput
|
||||
id="question-image"
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
|
||||
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
|
||||
environmentId={localSurvey.environmentId}
|
||||
onFileUpload={(url: string[] | undefined, fileType: "image" | "video") => {
|
||||
if (url) {
|
||||
|
||||
@@ -68,7 +68,7 @@ export const FileInput = ({
|
||||
toast.error(t("common.only_one_file_allowed"));
|
||||
}
|
||||
|
||||
const allowedFiles = getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
|
||||
const allowedFiles = await getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
|
||||
|
||||
if (allowedFiles.length === 0) {
|
||||
return;
|
||||
@@ -137,7 +137,7 @@ export const FileInput = ({
|
||||
};
|
||||
|
||||
const handleUploadMore = async (files: File[]) => {
|
||||
const allowedFiles = getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
|
||||
const allowedFiles = await getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
|
||||
if (allowedFiles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
26
apps/web/modules/ui/components/file-input/lib/action.ts
Normal file
26
apps/web/modules/ui/components/file-input/lib/action.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
"use server";
|
||||
export const convertHeicToJpeg = async (file: File): Promise<File | null> => {
|
||||
if (!file || !file.name.endsWith(".heic")) return file;
|
||||
|
||||
try {
|
||||
const convert = (await import("heic-convert")).default;
|
||||
|
||||
// Convert File to Buffer
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
const convertedBuffer = await convert({
|
||||
buffer,
|
||||
format: "JPEG",
|
||||
quality: 0.9,
|
||||
});
|
||||
|
||||
// Convert back to File
|
||||
return new File([convertedBuffer], file.name.replace(/\.heic$/, ".jpg"), {
|
||||
type: "image/jpeg",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error converting HEIC to JPEG:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { toast } from "react-hot-toast";
|
||||
import { TAllowedFileExtension } from "@formbricks/types/common";
|
||||
import { convertHeicToJpeg } from "./action";
|
||||
|
||||
export const uploadFile = async (
|
||||
file: File | Blob,
|
||||
@@ -96,32 +97,53 @@ export const uploadFile = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllowedFiles = (
|
||||
export const getAllowedFiles = async (
|
||||
files: File[],
|
||||
allowedFileExtensions: string[],
|
||||
maxSizeInMB?: number
|
||||
): File[] => {
|
||||
): Promise<File[]> => {
|
||||
const sizeExceedFiles: string[] = [];
|
||||
const unsupportedExtensionFiles: string[] = [];
|
||||
const convertedFiles: File[] = [];
|
||||
|
||||
const allowedFiles = files.filter((file) => {
|
||||
for (const file of files) {
|
||||
if (!file || !file.type) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const extension = file.name.split(".").pop();
|
||||
const fileSizeInMB = file.size / 1000000; // Kb -> Mb
|
||||
const extension = file.name.split(".").pop()?.toLowerCase();
|
||||
const fileSizeInMB = file.size / 1000000;
|
||||
console.log("going inside");
|
||||
if (extension === "heic") {
|
||||
try {
|
||||
console.log("converting heic file", file.name);
|
||||
const convertedFile = await convertHeicToJpeg(file);
|
||||
if (convertedFile) {
|
||||
console.log("converted file", convertedFile);
|
||||
convertedFiles.push(
|
||||
new File([convertedFile], file.name.replace(/\.heic$/i, ".jpg"), {
|
||||
type: "image/jpeg",
|
||||
})
|
||||
);
|
||||
}
|
||||
continue;
|
||||
} catch (error) {
|
||||
console.error("Error converting HEIC file:", error);
|
||||
unsupportedExtensionFiles.push(file.name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowedFileExtensions.includes(extension as TAllowedFileExtension)) {
|
||||
unsupportedExtensionFiles.push(file.name);
|
||||
return false; // Exclude file if extension not allowed
|
||||
continue;
|
||||
} else if (maxSizeInMB && fileSizeInMB > maxSizeInMB) {
|
||||
sizeExceedFiles.push(file.name);
|
||||
return false; // Exclude files larger than the maximum size
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
convertedFiles.push(file);
|
||||
}
|
||||
|
||||
// Constructing toast messages based on the issues found
|
||||
let toastMessage = "";
|
||||
@@ -134,7 +156,7 @@ export const getAllowedFiles = (
|
||||
if (toastMessage) {
|
||||
toast.error(toastMessage);
|
||||
}
|
||||
return allowedFiles;
|
||||
return convertedFiles;
|
||||
};
|
||||
|
||||
export const checkForYoutubePrivacyMode = (url: string): boolean => {
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"file-loader": "6.2.0",
|
||||
"framer-motion": "11.15.0",
|
||||
"googleapis": "144.0.0",
|
||||
"heic-convert": "2.1.0",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"jiti": "2.4.1",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
|
||||
@@ -19,6 +19,7 @@ export const ZPlacement = z.enum(["bottomLeft", "bottomRight", "topLeft", "topRi
|
||||
export type TPlacement = z.infer<typeof ZPlacement>;
|
||||
|
||||
export const ZAllowedFileExtension = z.enum([
|
||||
"heic",
|
||||
"png",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
|
||||
Reference in New Issue
Block a user