matrix-art

An image gallery for Matrix
git clone git://archive.git.mtrnord.blog/MTRNord/matrix-art.git
Log | Files | Refs | README | LICENSE

commit 2f84475320e9565860dad4948715497e2a557dd1
parent d6fb88bcac5e3b0e1e567be455a9e501ff37284a
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sun, 23 Jan 2022 01:09:34 +0100

Implement submit page dropzone

Diffstat:
Mpackage-lock.json | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mpackage.json | 3++-
Apages/submit.tsx | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 206 insertions(+), 8 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -16,7 +16,8 @@ "pouchdb": "7.2.2", "react": "17.0.2", "react-blurhash": "0.1.3", - "react-dom": "17.0.2" + "react-dom": "17.0.2", + "react-dropzone": "11.5.1" }, "devDependencies": { "@babel/plugin-transform-typescript": "7.16.8", @@ -1821,6 +1822,14 @@ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", @@ -3231,6 +3240,22 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-selector": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz", + "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/file-selector/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5442,7 +5467,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -5452,8 +5476,7 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/proper-lockfile": { "version": "4.1.2", @@ -5566,6 +5589,22 @@ "react": "17.0.2" } }, + "node_modules/react-dropzone": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.5.1.tgz", + "integrity": "sha512-eNhttdq4ZDe3eKbXAe54Opt+sbtqmNK5NWTHf/l5d+1TdZqShJ8gMjBrya00qx5zkI//TYxRhu1d9pemTgaWwg==", + "dependencies": { + "attr-accept": "^2.2.1", + "file-selector": "^0.2.2", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 16.8" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -8109,6 +8148,11 @@ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, + "attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" + }, "autoprefixer": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", @@ -9175,6 +9219,21 @@ "flat-cache": "^3.0.4" } }, + "file-selector": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz", + "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==", + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -10775,7 +10834,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -10785,8 +10843,7 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" } } }, @@ -10869,6 +10926,16 @@ "scheduler": "^0.20.2" } }, + "react-dropzone": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.5.1.tgz", + "integrity": "sha512-eNhttdq4ZDe3eKbXAe54Opt+sbtqmNK5NWTHf/l5d+1TdZqShJ8gMjBrya00qx5zkI//TYxRhu1d9pemTgaWwg==", + "requires": { + "attr-accept": "^2.2.1", + "file-selector": "^0.2.2", + "prop-types": "^15.7.2" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json @@ -27,7 +27,8 @@ "pouchdb": "7.2.2", "react": "17.0.2", "react-blurhash": "0.1.3", - "react-dom": "17.0.2" + "react-dom": "17.0.2", + "react-dropzone": "11.5.1" }, "devDependencies": { "@babel/plugin-transform-typescript": "7.16.8", diff --git a/pages/submit.tsx b/pages/submit.tsx @@ -0,0 +1,129 @@ +import Head from "next/head"; +import { PureComponent, ReactNode } from "react"; +import Dropzone, { FileRejection } from "react-dropzone"; +import { ClientContext } from "../components/ClientContext"; +import Header from "../components/Header"; + +type Props = {}; + +type State = { + files: PreviewWithDataFile[]; + fileRejections: FileRejection[]; + error?: string; +}; + +type PreviewWithDataFile = File & { + preview: HTMLImageElement; + data: ArrayBuffer; +}; + +class Submit extends PureComponent<Props, State> { + declare context: React.ContextType<typeof ClientContext>; + onDrop: (files: File[]) => void; + + constructor(props: Props) { + super(props); + this.onDrop = async (files: File[]) => { + const files_mapped = await Promise.all(files.map(async file => Object.assign(file, { + preview: await this.readFileToSizes(file), + data: await this.readFileToArray(file), + }))) as PreviewWithDataFile[]; + this.setState({ + files: [...this.state.files, ...files_mapped] + }); + }; + this.state = { + files: [], + fileRejections: [] + }; + } + + readFileToSizes(file: File): Promise<HTMLImageElement> { + return new Promise((resolve, reject) => { + var fr = new FileReader(); + fr.onload = () => { + const img = new Image(); + // Save as we use readAsDataURL + img.src = fr.result as string; + + resolve(img); + }; + fr.onerror = reject; + fr.readAsDataURL(file); + }); + } + + readFileToArray(file: File): Promise<ArrayBuffer> { + return new Promise((resolve, reject) => { + var fr = new FileReader(); + fr.onload = () => { + resolve(fr.result as ArrayBuffer); + }; + fr.onerror = reject; + fr.readAsArrayBuffer(file); + }); + } + + onDropError(fileRejections: FileRejection[]) { + const newRejections = fileRejections.filter(x => !this.state.fileRejections.includes(x)); + if (newRejections.length > 0) { + let errors = ""; + for (const rejection of newRejections) { + if (errors === "") { + errors = `${rejection.file.name} - ${rejection.errors.join(" & ")}`; + } else { + errors = `${errors}\n${rejection.file.name} - ${rejection.errors.join(" & ")}`; + } + } + this.setState({ + error: errors + }); + } + this.setState({ fileRejections: newRejections }); + } + + renderThumbs() { + return this.state.files.map(file => ( + <div className="relative cursor-pointer border-2 border-solid border-[#eeeeea] p-1" style={{ height: "212px" }} key={file.name}> + <img className="h-auto w-full object-cover max-w-full align-bottom" style={{ height: "200px" }} src={file.preview.src} width={file.preview.width} height={file.preview.height} /> + </div> + )); + } + + render(): ReactNode { + return ( + <div className='h-full bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'> + <Head> + <title key="title">Matrix Art | Submit Post</title> + <meta property="og:title" content="Matrix Art | Submit Post" key="og-title" /> + <meta name="twitter:card" content="summary_large_image" key="og-twitter" /> + <meta name="twitter:title" content="Matrix Art | Submit Post" key="og-twitter-title" /> + <meta property="og:type" content="website" key="og-type" /> + </Head> + <Header></Header> + <main className='w-full lg:pt-20 pt-52 z-0'> + <div className="flex justify-center items-center mt-4"> + <Dropzone accept='image/*' onDropAccepted={this.onDrop} onDropRejected={this.onDropError}> + {({ getRootProps, getInputProps }) => ( + <section className="flex flex-col"> + <div className="dark:bg-[#fefefe]/[.25] bg-[#14181E]/[.25] min-h-[11.5rem] min-w-[39.5rem] flex-[1] flex flex-col items-center justify-center p-5 border-2 rounded-sm border-[#eeeeee] border-dashed outline-none focus:border-[#2196f3] duration-[24ms] transition" {...getRootProps()}> + <input {...getInputProps()} /> + <p className="text-base text-gray-900 dark:text-gray-200">Drag &apos;n&apos; drop some images here, or click to select images</p> + </div> + <ul className="flex flex-row flex-wrap gap-1 mt-4"> + {this.renderThumbs()} + <li className='grow-[10]'></li> + </ul> + </section> + )} + </Dropzone> + </div> + </main> + </div> + ); + } +} + +Submit.contextType = ClientContext; + +export default Submit; +\ No newline at end of file