Make gallery upload work; improve typing

This commit is contained in:
Maximilian Harz 2025-06-05 12:44:36 +02:00
parent 6a68300cea
commit b87a12a1d8
4 changed files with 76 additions and 23 deletions

View File

@ -2,6 +2,8 @@ import { describe, it, expect, vi } from 'vitest'
import { linkItem } from './itemFunctions' import { linkItem } from './itemFunctions'
import type { Item } from '#types/Item'
const toastErrorMock: (t: string) => void = vi.fn() const toastErrorMock: (t: string) => void = vi.fn()
const toastSuccessMock: (t: string) => void = vi.fn() const toastSuccessMock: (t: string) => void = vi.fn()
@ -14,8 +16,45 @@ vi.mock('react-toastify', () => ({
describe('linkItem', () => { describe('linkItem', () => {
const id = 'some-id' const id = 'some-id'
let updateApi: () => void = vi.fn() let updateApi: (item: Partial<Item>) => Promise<Item> = vi.fn()
const item = { layer: { api: { updateItem: () => updateApi() } } } const item: Item = {
layer: {
api: {
updateItem: (item) => updateApi(item),
getItems: vi.fn(),
},
name: '',
menuIcon: '',
menuColor: '',
menuText: '',
markerIcon: {
image: '',
},
markerShape: 'square',
markerDefaultColor: '',
itemType: {
name: 'Test Item Type',
show_name_input: true,
show_profile_button: false,
show_start_end: true,
show_start_end_input: true,
show_text: true,
show_text_input: true,
custom_text: 'This is a custom text for the item type.',
profileTemplate: [
{ collection: 'users', id: null, item: {} },
{ collection: 'posts', id: '123', item: {} },
],
offers_and_needs: true,
icon_as_labels: {},
relations: true,
template: 'default',
questlog: false,
},
},
id: '',
name: '',
}
const updateItem = vi.fn() const updateItem = vi.fn()
beforeEach(() => { beforeEach(() => {

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
@ -200,8 +199,8 @@ export function ProfileForm() {
state={state} state={state}
setState={setState} setState={setState}
updatePermission={updatePermission} updatePermission={updatePermission}
linkItem={(id) => linkItem(id, item, updateItem)} linkItem={(id: string) => linkItem(id, item, updateItem)}
unlinkItem={(id) => unlinkItem(id, item, updateItem)} unlinkItem={(id: string) => unlinkItem(id, item, updateItem)}
setUrlParams={setUrlParams} setUrlParams={setUrlParams}
></TabsForm> ></TabsForm>
)} )}

View File

@ -1,3 +1,4 @@
import { useState } from 'react'
import { useDropzone } from 'react-dropzone' import { useDropzone } from 'react-dropzone'
import { useAppState } from '#components/AppShell/hooks/useAppState' import { useAppState } from '#components/AppShell/hooks/useAppState'
@ -12,7 +13,11 @@ interface Props {
export const GalleryForm = ({ state, setState }: Props) => { export const GalleryForm = ({ state, setState }: Props) => {
const appState = useAppState() const appState = useAppState()
const [isUploading, setUploading] = useState(false)
const upload = async (acceptedFiles: File[]) => { const upload = async (acceptedFiles: File[]) => {
setUploading(true)
const assets = await Promise.all( const assets = await Promise.all(
acceptedFiles.map(async (file) => { acceptedFiles.map(async (file) => {
return appState.assetsApi.upload(file, 'gallery') return appState.assetsApi.upload(file, 'gallery')
@ -22,11 +27,13 @@ export const GalleryForm = ({ state, setState }: Props) => {
const newGalleryItems = assets.map((asset) => ({ const newGalleryItems = assets.map((asset) => ({
directus_files_id: { directus_files_id: {
id: asset.id, id: asset.id,
width: 600, // TODO determine width: 600, // TODO map to ids only
height: 400, // TODO determine height: 400,
}, },
})) }))
setUploading(false)
setState((prevState) => ({ setState((prevState) => ({
...prevState, ...prevState,
gallery: [...prevState.gallery, ...newGalleryItems], gallery: [...prevState.gallery, ...newGalleryItems],
@ -36,6 +43,9 @@ export const GalleryForm = ({ state, setState }: Props) => {
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
onDrop: upload, onDrop: upload,
accept: {
'image/jpeg': [],
},
}) })
const images = state.gallery.map((i, j) => ({ const images = state.gallery.map((i, j) => ({
@ -62,6 +72,8 @@ export const GalleryForm = ({ state, setState }: Props) => {
Drop here Drop here
</div> </div>
{isUploading && <div className='tw:mt-2 tw:text-gray-500'>Uploading...</div>}
{images.map((image, index) => ( {images.map((image, index) => (
<div key={index} className='tw:mb-2'> <div key={index} className='tw:mb-2'>
<img <img

View File

@ -15,6 +15,7 @@ import { encodeTag } from '#utils/FormatTags'
import { hashTagRegex } from '#utils/HashTagRegex' import { hashTagRegex } from '#utils/HashTagRegex'
import { randomColor } from '#utils/RandomColor' import { randomColor } from '#utils/RandomColor'
import type { FormState } from '#types/FormState'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
// eslint-disable-next-line promise/avoid-new // eslint-disable-next-line promise/avoid-new
@ -77,8 +78,8 @@ export const submitNewItem = async (
setAddItemPopupType('') setAddItemPopupType('')
} }
export const linkItem = async (id: string, item, updateItem) => { export const linkItem = async (id: string, item: Item, updateItem) => {
const newRelations = item.relations || [] const newRelations = item.relations ?? []
newRelations?.push({ items_id: item.id, related_items_id: id }) newRelations?.push({ items_id: item.id, related_items_id: id })
const updatedItem = { id: item.id, relations: newRelations } const updatedItem = { id: item.id, relations: newRelations }
@ -96,7 +97,7 @@ export const linkItem = async (id: string, item, updateItem) => {
} }
} }
export const unlinkItem = async (id: string, item, updateItem) => { export const unlinkItem = async (id: string, item: Item, updateItem) => {
const newRelations = item.relations?.filter((r) => r.related_items_id !== id) const newRelations = item.relations?.filter((r) => r.related_items_id !== id)
const updatedItem = { id: item.id, relations: newRelations } const updatedItem = { id: item.id, relations: newRelations }
@ -116,7 +117,7 @@ export const unlinkItem = async (id: string, item, updateItem) => {
export const handleDelete = async ( export const handleDelete = async (
event: React.MouseEvent<HTMLElement>, event: React.MouseEvent<HTMLElement>,
item, item: Item,
setLoading, setLoading,
removeItem, removeItem,
map, map,
@ -144,8 +145,8 @@ export const handleDelete = async (
} }
export const onUpdateItem = async ( export const onUpdateItem = async (
state, state: FormState,
item, item: Item,
tags, tags,
addTag, addTag,
setLoading, setLoading,
@ -159,19 +160,20 @@ export const onUpdateItem = async (
const offerUpdates: any[] = [] const offerUpdates: any[] = []
// check for new offers // check for new offers
await state.offers?.map((o) => { state.offers?.map((o) => {
const existingOffer = item?.offers?.find((t) => t.tags_id === o.id) const existingOffer = item?.offers?.find((t) => t.tags_id === o.id)
existingOffer && offerUpdates.push(existingOffer.id) existingOffer && offerUpdates.push(existingOffer.tags_id)
if (!existingOffer && !tags.some((t) => t.id === o.id)) addTag({ ...o, offer_or_need: true }) if (!existingOffer && !tags.some((t: { id: string }) => t.id === o.id))
addTag({ ...o, offer_or_need: true })
!existingOffer && offerUpdates.push({ items_id: item?.id, tags_id: o.id }) !existingOffer && offerUpdates.push({ items_id: item?.id, tags_id: o.id })
return null return null
}) })
const needsUpdates: any[] = [] const needsUpdates: any[] = []
await state.needs?.map((n) => { state.needs?.map((n) => {
const existingNeed = item?.needs?.find((t) => t.tags_id === n.id) const existingNeed = item?.needs?.find((t) => t.tags_id === n.id)
existingNeed && needsUpdates.push(existingNeed.id) existingNeed && needsUpdates.push(existingNeed.tags_id)
!existingNeed && needsUpdates.push({ items_id: item?.id, tags_id: n.id }) !existingNeed && needsUpdates.push({ items_id: item?.id, tags_id: n.id })
!existingNeed && !tags.some((t) => t.id === n.id) && addTag({ ...n, offer_or_need: true }) !existingNeed && !tags.some((t) => t.id === n.id) && addTag({ ...n, offer_or_need: true })
return null return null
@ -197,6 +199,7 @@ export const onUpdateItem = async (
...(state.offers.length > 0 && { offers: offerUpdates }), ...(state.offers.length > 0 && { offers: offerUpdates }),
...(state.needs.length > 0 && { needs: needsUpdates }), ...(state.needs.length > 0 && { needs: needsUpdates }),
...(state.openCollectiveSlug && { openCollectiveSlug: state.openCollectiveSlug }), ...(state.openCollectiveSlug && { openCollectiveSlug: state.openCollectiveSlug }),
gallery: state.gallery,
} }
const offersState: any[] = [] const offersState: any[] = []
@ -216,7 +219,7 @@ export const onUpdateItem = async (
setLoading(true) setLoading(true)
await state.text state.text
.toLocaleLowerCase() .toLocaleLowerCase()
.match(hashTagRegex) .match(hashTagRegex)
?.map((tag) => { ?.map((tag) => {
@ -234,7 +237,7 @@ export const onUpdateItem = async (
await sleep(200) await sleep(200)
if (!item.new) { if (!item.new) {
item?.layer?.api?.updateItem && await (item?.layer?.api?.updateItem &&
toast toast
.promise(item?.layer?.api?.updateItem(changedItem), { .promise(item?.layer?.api?.updateItem(changedItem), {
pending: 'updating Item ...', pending: 'updating Item ...',
@ -251,10 +254,10 @@ export const onUpdateItem = async (
setLoading(false) setLoading(false)
navigate(`/item/${item.id}${params && '?' + params}`) navigate(`/item/${item.id}${params && '?' + params}`)
return null return null
}) }))
} else { } else {
item.new = false item.new = false
item.layer?.api?.createItem && await (item.layer?.api?.createItem &&
toast toast
.promise(item.layer?.api?.createItem(changedItem), { .promise(item.layer?.api?.createItem(changedItem), {
pending: 'updating Item ...', pending: 'updating Item ...',
@ -280,6 +283,6 @@ export const onUpdateItem = async (
setLoading(false) setLoading(false)
navigate(`/${params && '?' + params}`) navigate(`/${params && '?' + params}`)
return null return null
}) }))
} }
} }