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