Mathias
Mathias5mo ago

How to upload images using Convex storage? (Solved)

I'm wondering if anyone has played around using Convex storage for their text editors? I'm using the Novel.sh package, and they are providing an uploadFn like this:
import { createImageUpload } from "novel/plugins"
import { toast } from "sonner"

const onUpload = async (file: File) => {
console.log("Uploading file", file)

const generateUploadUrl = useMutation(api.files.generateUploadUrl);
const { startUpload } = useUploadFiles(generateUploadUrl);

await startUpload([file])
}

export const uploadFn = createImageUpload({
onUpload,
validateFn: (file) => {
if (!file.type.includes("image/")) {
toast.error("File type not supported.")
return false
}
if (file.size / 1024 / 1024 > 20) {
toast.error("File size too big (max 20MB).")
return false
}
return true
},
})
import { createImageUpload } from "novel/plugins"
import { toast } from "sonner"

const onUpload = async (file: File) => {
console.log("Uploading file", file)

const generateUploadUrl = useMutation(api.files.generateUploadUrl);
const { startUpload } = useUploadFiles(generateUploadUrl);

await startUpload([file])
}

export const uploadFn = createImageUpload({
onUpload,
validateFn: (file) => {
if (!file.type.includes("image/")) {
toast.error("File type not supported.")
return false
}
if (file.size / 1024 / 1024 > 20) {
toast.error("File size too big (max 20MB).")
return false
}
return true
},
})
I have stripped it down to get rid of the Vercel blob storage implementation, and though I would use the uploadstuff.dev, but I really struggle to make it work within the onUpload. It does not seem to run the following lines:
const generateUploadUrl = useMutation(api.files.generateUploadUrl);
const { startUpload } = useUploadFiles(generateUploadUrl);

await startUpload([file])
const generateUploadUrl = useMutation(api.files.generateUploadUrl);
const { startUpload } = useUploadFiles(generateUploadUrl);

await startUpload([file])
If I run the console.log, I get the following:
argument #0:

'Uploading file'
file:

File {
name: 'IMG_1403.CR3',
lastModified: 1701880784000,
lastModifiedDate: new Date('2023-12-06T16:39:44.000Z'),
webkitRelativePath: '',
size: 18311314,
type: 'image/x-canon-cr3'
}
argument #0:

'Uploading file'
file:

File {
name: 'IMG_1403.CR3',
lastModified: 1701880784000,
lastModifiedDate: new Date('2023-12-06T16:39:44.000Z'),
webkitRelativePath: '',
size: 18311314,
type: 'image/x-canon-cr3'
}
So it would seem that the onUpload is running but not the Convex specific functions. What have I missed?
Novel Docs
Image Upload (New) - Novel Docs
Uploading images in the editor
1 Reply
Mathias
MathiasOP5mo ago
I figured it out and added some compression too.
const useUploadFn = () => {
const generateUploadUrl = useMutation(api.files.generateUploadUrl)
const getStorageUrl = useMutation(api.files.getStorageUrl)

const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
}

const onUpload = async (file: File) => {
const compressionToastId = toast.loading("Compressing image...")

const compressedFile = await imageCompression(file, options)

toast.dismiss(compressionToastId)

const promise = generateUploadUrl().then((postUrl) => {
return fetch(postUrl, {
method: "POST",
headers: {
"Content-Type": compressedFile?.type || "application/octet-stream",
},
body: compressedFile,
})
})

return new Promise((resolve, reject) => {
toast.promise(
promise.then(async (res) => {
if (res.status === 200) {
const { storageId } = await res.json()

const getUrl = await getStorageUrl({ storageId })

resolve(getUrl)
} else {
throw new Error("Error adding image. Please try again.")
}
}),
{
loading: "Adding image...",
success: "Image added successfully.",
error: (e) => {
reject(e)
return e.message
},
}
)
})
}
const useUploadFn = () => {
const generateUploadUrl = useMutation(api.files.generateUploadUrl)
const getStorageUrl = useMutation(api.files.getStorageUrl)

const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
}

const onUpload = async (file: File) => {
const compressionToastId = toast.loading("Compressing image...")

const compressedFile = await imageCompression(file, options)

toast.dismiss(compressionToastId)

const promise = generateUploadUrl().then((postUrl) => {
return fetch(postUrl, {
method: "POST",
headers: {
"Content-Type": compressedFile?.type || "application/octet-stream",
},
body: compressedFile,
})
})

return new Promise((resolve, reject) => {
toast.promise(
promise.then(async (res) => {
if (res.status === 200) {
const { storageId } = await res.json()

const getUrl = await getStorageUrl({ storageId })

resolve(getUrl)
} else {
throw new Error("Error adding image. Please try again.")
}
}),
{
loading: "Adding image...",
success: "Image added successfully.",
error: (e) => {
reject(e)
return e.message
},
}
)
})
}

Did you find this page helpful?