some imagely stuff
This commit is contained in:
parent
f30829a651
commit
d35619308d
3 changed files with 59 additions and 41 deletions
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@js-temporal/polyfill": "^0.5.1",
|
||||
"@tailwindcss/vite": "^4.1.5",
|
||||
"immer": "^10.1.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router": "^7.5.3",
|
||||
|
|
|
@ -5,48 +5,62 @@ interface NewPostWidgetProps {
|
|||
isSubmitting?: boolean
|
||||
}
|
||||
|
||||
interface Thumbnail {
|
||||
id: string
|
||||
objectUrl: string
|
||||
}
|
||||
|
||||
export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPostWidgetProps) {
|
||||
const [content, setContent] = useState('')
|
||||
const [media, setMedia] = useState<File[]>([])
|
||||
const [mediaPreviewUrls, setMediaPreviewUrls] = useState<string[]>([])
|
||||
const [files, setFiles] = useState<{ id: string; file: File }[]>([])
|
||||
const [thumbnails, setThumbnails] = useState<Thumbnail[]>([])
|
||||
|
||||
const handleContentChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setContent(e.target.value)
|
||||
}
|
||||
|
||||
const handleMediaChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
const newFiles = Array.from(e.target.files)
|
||||
setMedia([...media, ...newFiles])
|
||||
|
||||
// Create preview URLs for the new files
|
||||
const newPreviewUrls = newFiles.map((file) => URL.createObjectURL(file))
|
||||
setMediaPreviewUrls([...mediaPreviewUrls, ...newPreviewUrls])
|
||||
if (!(e.target.files && e.target.files.length > 0)) {
|
||||
return
|
||||
}
|
||||
|
||||
const newFiles = Array.from(e.target.files).map((file) => ({
|
||||
id: crypto.randomUUID(),
|
||||
file,
|
||||
}))
|
||||
|
||||
setFiles([...files, ...newFiles])
|
||||
|
||||
const newThumbnails = newFiles.map((file) => {
|
||||
return {
|
||||
id: file.id,
|
||||
objectUrl: URL.createObjectURL(file.file),
|
||||
}
|
||||
})
|
||||
|
||||
setThumbnails([...thumbnails, ...newThumbnails])
|
||||
;(e.target as HTMLInputElement).value = ''
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (content.trim() || media.length > 0) {
|
||||
onSubmit(content, media)
|
||||
if (!content.trim() && files.length === 0) {
|
||||
return
|
||||
}
|
||||
onSubmit(
|
||||
content,
|
||||
files.map((file) => file.file),
|
||||
)
|
||||
setContent('')
|
||||
setMedia([])
|
||||
|
||||
// Revoke object URLs to avoid memory leaks
|
||||
mediaPreviewUrls.forEach((url) => URL.revokeObjectURL(url))
|
||||
setMediaPreviewUrls([])
|
||||
}
|
||||
setFiles([])
|
||||
thumbnails.forEach(({ objectUrl }) => URL.revokeObjectURL(objectUrl))
|
||||
setThumbnails([])
|
||||
}
|
||||
|
||||
const handleRemoveMedia = (index: number) => {
|
||||
const newMedia = [...media]
|
||||
newMedia.splice(index, 1)
|
||||
setMedia(newMedia)
|
||||
|
||||
// Revoke the URL of the removed media
|
||||
URL.revokeObjectURL(mediaPreviewUrls[index])
|
||||
const newPreviewUrls = [...mediaPreviewUrls]
|
||||
newPreviewUrls.splice(index, 1)
|
||||
setMediaPreviewUrls(newPreviewUrls)
|
||||
const handleRemoveMedia = (id: string) => {
|
||||
setFiles(files.filter((file) => file.id !== id))
|
||||
const { objectUrl } = thumbnails.find((thumbnail) => thumbnail.id === id)!
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
setThumbnails(thumbnails.filter((thumbnail) => thumbnail.id !== id))
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -60,19 +74,17 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos
|
|||
disabled={isSubmitting}
|
||||
/>
|
||||
|
||||
{mediaPreviewUrls.length > 0 && (
|
||||
<div className="grid gap-2 grid-cols-3 mb-3">
|
||||
{mediaPreviewUrls.map((url, index) => (
|
||||
<div key={index} className="relative">
|
||||
<img src={url} alt="" className="w-24 h-24 object-cover rounded-md" />
|
||||
{thumbnails.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-2 mb-3">
|
||||
{thumbnails.map(({ objectUrl, id }) => (
|
||||
<button
|
||||
className="absolute top-1 right-1 bg-gray-800 bg-opacity-50 text-white rounded-full p-1 text-xs"
|
||||
onClick={() => handleRemoveMedia(index)}
|
||||
key={id}
|
||||
className="relative cursor-pointer"
|
||||
onClick={() => handleRemoveMedia(id)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
✕
|
||||
<img src={objectUrl} alt="" className="w-24 h-24 object-cover rounded-md" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
@ -92,7 +104,7 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos
|
|||
<button
|
||||
className="px-4 py-2 bg-gray-800 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting || (content.trim() === '' && media.length === 0)}
|
||||
disabled={isSubmitting || (content.trim() === '' && files.length === 0)}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
|
|
|
@ -1601,6 +1601,11 @@ ignore@^5.2.0, ignore@^5.3.1:
|
|||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
|
||||
immer@^10.1.1:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
|
||||
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
|
||||
|
||||
import-fresh@^3.2.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue