Fallback file upload for room/space images
This commit is contained in:
parent
647375b8c9
commit
c2684497f9
7 changed files with 58 additions and 54 deletions
|
|
@ -22,7 +22,7 @@
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
import {DraftKey} from "@app/drafts"
|
import {DraftKey} from "@app/drafts"
|
||||||
import {canEnforceNip70} from "@app/relays"
|
import {canEnforceNip70} from "@app/relays"
|
||||||
import {uploadFile} from "@app/uploads"
|
import {compressFileForUpload, uploadFile} from "@app/uploads"
|
||||||
|
|
||||||
type Values = {
|
type Values = {
|
||||||
d: string
|
d: string
|
||||||
|
|
@ -103,7 +103,7 @@
|
||||||
if (typeof image === "string") {
|
if (typeof image === "string") {
|
||||||
tags.push(["image", image])
|
tags.push(["image", image])
|
||||||
} else {
|
} else {
|
||||||
const {result, error} = await uploadFile(image, {url})
|
const {result, error} = await uploadFile(await compressFileForUpload(image), {url})
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {randomId} from "@welshman/lib"
|
import {randomId} from "@welshman/lib"
|
||||||
import {preventDefault, stopPropagation, compressFile} from "@lib/html"
|
import {preventDefault, stopPropagation} from "@lib/html"
|
||||||
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
||||||
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import {uploadFile} from "@app/uploads"
|
import {compressFileForUpload, uploadFileOrFallback} from "@app/uploads"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
file?: File | undefined
|
file?: File | undefined
|
||||||
|
|
@ -43,33 +43,17 @@
|
||||||
submit(e.target.files[0])
|
submit(e.target.files[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = async (file: File) => {
|
const submit = async (newFile: File) => {
|
||||||
|
if (!newFile) {
|
||||||
|
url = initialUrl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
file = await compressFile(file, compressOptions)
|
file = await compressFileForUpload(newFile, compressOptions)
|
||||||
|
url = (await uploadFileOrFallback(file)).url
|
||||||
if (file) {
|
|
||||||
const {result} = await uploadFile(file)
|
|
||||||
|
|
||||||
if (result?.url) {
|
|
||||||
url = result.url
|
|
||||||
} else {
|
|
||||||
const reader = new FileReader()
|
|
||||||
|
|
||||||
reader.addEventListener(
|
|
||||||
"load",
|
|
||||||
() => {
|
|
||||||
url = reader.result as string
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
reader.readAsDataURL(file)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url = initialUrl
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
tagPubkey,
|
tagPubkey,
|
||||||
loadRelayList,
|
loadRelayList,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import {compressFile} from "@lib/html"
|
|
||||||
import Copy from "@assets/icons/copy.svg?dataurl"
|
import Copy from "@assets/icons/copy.svg?dataurl"
|
||||||
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||||
|
|
@ -32,7 +31,7 @@
|
||||||
import WotScore from "@app/components/WotScore.svelte"
|
import WotScore from "@app/components/WotScore.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import {updateProfile} from "@app/profiles"
|
import {updateProfile} from "@app/profiles"
|
||||||
import {uploadFile} from "@app/uploads"
|
import {compressFileForUpload, uploadFile} from "@app/uploads"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {clip, pushToast} from "@app/toast"
|
import {clip, pushToast} from "@app/toast"
|
||||||
import {goToChat} from "@app/routes"
|
import {goToChat} from "@app/routes"
|
||||||
|
|
@ -90,7 +89,7 @@
|
||||||
bannerLoading = true
|
bannerLoading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {result} = await uploadFile(await compressFile(file))
|
const {result} = await uploadFile(await compressFileForUpload(file))
|
||||||
|
|
||||||
if (result?.url) {
|
if (result?.url) {
|
||||||
await updateProfile({profile: {...$profile, banner: result.url}})
|
await updateProfile({profile: {...$profile, banner: result.url}})
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
import Modal from "@lib/components/Modal.svelte"
|
import Modal from "@lib/components/Modal.svelte"
|
||||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import {uploadFile} from "@app/uploads"
|
import {compressFileForUpload, uploadFileOrFallback} from "@app/uploads"
|
||||||
import {deriveHasLivekit} from "@app/relays"
|
import {deriveHasLivekit} from "@app/relays"
|
||||||
import {getRoomType, RoomType} from "@app/groups"
|
import {getRoomType, RoomType} from "@app/groups"
|
||||||
|
|
||||||
|
|
@ -45,17 +45,14 @@
|
||||||
room.livekit = roomType === RoomType.Voice
|
room.livekit = roomType === RoomType.Voice
|
||||||
|
|
||||||
if (imageFile) {
|
if (imageFile) {
|
||||||
const {error, result} = await uploadFile(imageFile, {
|
const compressedFile = await compressFileForUpload(imageFile, {
|
||||||
maxWidth: 256,
|
maxWidth: 128,
|
||||||
maxHeight: 256,
|
maxHeight: 128,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (error) {
|
const result = await uploadFileOrFallback(compressedFile)
|
||||||
return pushToast({theme: "error", message: error})
|
|
||||||
}
|
|
||||||
|
|
||||||
room.picture = result.url
|
room.picture = result.url
|
||||||
room.pictureMeta = result.tags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createMessage = await waitForThunkError(createRoom(url, room))
|
const createMessage = await waitForThunkError(createRoom(url, room))
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
import IconPickerButton from "@lib/components/IconPickerButton.svelte"
|
import IconPickerButton from "@lib/components/IconPickerButton.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import {clearModals} from "@app/modal"
|
import {clearModals} from "@app/modal"
|
||||||
import {uploadFile} from "@app/uploads"
|
import {compressFileForUpload, uploadFileOrFallback} from "@app/uploads"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -58,11 +58,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageFile) {
|
if (imageFile) {
|
||||||
const {error, result} = await uploadFile(imageFile, {maxWidth: 256, maxHeight: 256})
|
const compressedFile = await compressFileForUpload(imageFile, {maxWidth: 128, maxHeight: 128})
|
||||||
|
const result = await uploadFileOrFallback(compressedFile)
|
||||||
if (error) {
|
|
||||||
return pushToast({theme: "error", message: error})
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await manageRelay(url, {
|
const res = await manageRelay(url, {
|
||||||
method: ManagementMethod.ChangeRelayIcon,
|
method: ManagementMethod.ChangeRelayIcon,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import ProfileSuggestion from "@app/editor/ProfileSuggestion.svelte"
|
||||||
import {RoomReferenceExtension} from "@app/editor/RoomReferenceExtension"
|
import {RoomReferenceExtension} from "@app/editor/RoomReferenceExtension"
|
||||||
import RoomSuggestion from "@app/editor/RoomSuggestion.svelte"
|
import RoomSuggestion from "@app/editor/RoomSuggestion.svelte"
|
||||||
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
|
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
|
||||||
import {uploadFile} from "@app/uploads"
|
import {compressFileForUpload, uploadFile} from "@app/uploads"
|
||||||
import {deriveSpaceMembers} from "@app/members"
|
import {deriveSpaceMembers} from "@app/members"
|
||||||
import {makeRoomId, splitRoomId, userSpaceUrls, roomsByUrl} from "@app/groups"
|
import {makeRoomId, splitRoomId, userSpaceUrls, roomsByUrl} from "@app/groups"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
@ -137,8 +137,8 @@ export const makeEditor = async ({
|
||||||
},
|
},
|
||||||
fileUpload: {
|
fileUpload: {
|
||||||
config: {
|
config: {
|
||||||
upload: (attrs: FileAttributes) =>
|
upload: async (attrs: FileAttributes) =>
|
||||||
uploadFile(attrs.file, {url, encrypt: encryptFiles}),
|
uploadFile(await compressFileForUpload(attrs.file), {url, encrypt: encryptFiles}),
|
||||||
onDrop: () => uploading?.set(true),
|
onDrop: () => uploading?.set(true),
|
||||||
onComplete: () => uploading?.set(false),
|
onComplete: () => uploading?.set(false),
|
||||||
onUploadError(currentEditor, task) {
|
onUploadError(currentEditor, task) {
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,20 @@ export const getBlossomServer = async (options: GetBlossomServerOptions = {}) =>
|
||||||
return first(DEFAULT_BLOSSOM_SERVERS)!
|
return first(DEFAULT_BLOSSOM_SERVERS)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CompressFileOptions = {
|
||||||
|
maxWidth?: number
|
||||||
|
maxHeight?: number
|
||||||
|
quality?: number
|
||||||
|
mimeType?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webp/gif/svg either break the compressor or lose animation, pass them through untouched
|
||||||
|
export const compressFileForUpload = async (file: File, options: CompressFileOptions = {}) =>
|
||||||
|
file.type.match("image/(webp|gif|svg)") ? file : compressFile(file, options)
|
||||||
|
|
||||||
export type UploadFileOptions = {
|
export type UploadFileOptions = {
|
||||||
url?: string
|
url?: string
|
||||||
encrypt?: boolean
|
encrypt?: boolean
|
||||||
maxWidth?: number
|
|
||||||
maxHeight?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UploadFileResult = {
|
export type UploadFileResult = {
|
||||||
|
|
@ -83,10 +92,6 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
||||||
try {
|
try {
|
||||||
const {name, type} = file
|
const {name, type} = file
|
||||||
|
|
||||||
if (!type.match("image/(webp|gif|svg)")) {
|
|
||||||
file = await compressFile(file, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags: string[][] = []
|
const tags: string[][] = []
|
||||||
|
|
||||||
if (options.encrypt) {
|
if (options.encrypt) {
|
||||||
|
|
@ -140,3 +145,25 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
||||||
return {error: e.toString()}
|
return {error: e.toString()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const readFileAsDataUrl = (file: File) =>
|
||||||
|
new Promise<string>((resolve, reject) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.addEventListener("load", () => resolve(reader.result as string))
|
||||||
|
reader.addEventListener("error", () => reject(reader.error))
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const uploadFileOrFallback = async (
|
||||||
|
file: File,
|
||||||
|
options: UploadFileOptions = {},
|
||||||
|
): Promise<{url: string; tags: string[][]}> => {
|
||||||
|
const {result} = await uploadFile(file, options)
|
||||||
|
|
||||||
|
if (result?.url) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return {url: await readFileAsDataUrl(file), tags: []}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue