matrix-art

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

commit 573dc1ca59cbcbb04dede13c8f3fbd8e7d114372
parent 77003f540b7fa2a52f06ae3b88bc095bb2115c5c
Author: MTRNord <mtrnord1@gmail.com>
Date:   Thu, 27 Jan 2022 23:19:20 +0100

Add loading animation and error messages to the submit dialog. Also block multiple submission calls

Diffstat:
Mcomponents/submit_page/main.tsx | 44+++++++++++++++++++++++++++++++++++++-------
Mpackage-lock.json | 34++++++++++++++++++++++++++++++++++
Mpackage.json | 4++--
Mpages/_app.tsx | 16+++++++++++++++-
Mpages/login.tsx | 4++--
Mpages/post/[id].tsx | 2+-
Mpages/submit.tsx | 8++++++++
7 files changed, 99 insertions(+), 13 deletions(-)

diff --git a/components/submit_page/main.tsx b/components/submit_page/main.tsx @@ -9,6 +9,7 @@ import { withRouter } from "next/router"; import { WithRouterProps } from "next/dist/client/with-router"; import { BlurhashEncoder } from "../../helpers/BlurhashEncoder"; import { ImageEventContent, MatrixContents, ThumbnailData } from "../../helpers/event_types"; +import { toast } from "react-toastify"; type ThumbnailableElement = HTMLImageElement | HTMLVideoElement; type ThumbnailTransmissionData = { @@ -31,6 +32,7 @@ type State = { currentFileIndex: number; hasSubmit: boolean; hasBack: boolean; + submit_in_process: boolean; [key: string]: any; }; class MainSubmissionForm extends PureComponent<Props, State> { @@ -42,7 +44,8 @@ class MainSubmissionForm extends PureComponent<Props, State> { this.state = { currentFileIndex: 0, hasSubmit: props.files.length === 1 ? true : false, - hasBack: false + hasBack: false, + submit_in_process: false, }; const range = [...Array(this.props.files.length).keys()]; // eslint-disable-line unicorn/new-for-builtins for (const index of range) { @@ -131,19 +134,35 @@ class MainSubmissionForm extends PureComponent<Props, State> { } - async handleSubmit(event: { preventDefault: () => void; }) { + async handleSubmit() { + if (this.state.submit_in_process) { + return; + } + this.setState({ submit_in_process: true }); + const range = [...Array(this.props.files.length).keys()]; // eslint-disable-line unicorn/new-for-builtins const posts_for_search: SearchMedia[] = []; + // Clear all errors before showing new + toast.dismiss(); + + if (this.context.client.isGuest) { + toast.error(() => <div><h2 className="text-xl text-white">Error</h2><br />You are not logged in!</div>, { + autoClose: false + }); + this.setState({ submit_in_process: false }); return; } // If any image is invalid do exit submit for now. - // TODO show an error for (const index of range) { - const valid = `${index}_valid`; + const valid = this.state[`${index}_valid`]; if (!valid) { + toast.error(() => <div><h2 className="text-xl text-white">Error</h2><br />You did not fill the required fields for all images. Please fix this!</div>, { + autoClose: false + }); + this.setState({ submit_in_process: false }); return; } } @@ -154,8 +173,6 @@ class MainSubmissionForm extends PureComponent<Props, State> { // Handle uploads for (const index of range) { - console.log(index); - console.log(this.context.client.profileRoomId); const title = `${index}_title`; const description = `${index}_description`; const tags = `${index}_tags`; @@ -164,6 +181,10 @@ class MainSubmissionForm extends PureComponent<Props, State> { const file = this.props.files[index]; if (!this.context.client.profileRoomId) { + toast.error(() => <div><h2 className="text-xl text-white">Error</h2><br />Unable to find your profile Room. Please log in again!</div>, { + autoClose: false + }); + this.setState({ submit_in_process: false }); return; } @@ -207,6 +228,7 @@ class MainSubmissionForm extends PureComponent<Props, State> { const token = await this.context.client.getOpenidToken(); await fetch("/api/submitSearch", { method: "POST", body: JSON.stringify({ access_token: token, user_id: this.context.client.userId, docs: posts_for_search }) }); await this.props.router.replace("/"); + this.setState({ submit_in_process: false }); } // THis is aken from matrix-react-sdk commit efa1667d7e9de9e429a72396a5105d0219006db2 @@ -292,7 +314,6 @@ class MainSubmissionForm extends PureComponent<Props, State> { file.type ); if (!thumbnail_data) { - // TODO this causes issues continue; } @@ -353,6 +374,15 @@ class MainSubmissionForm extends PureComponent<Props, State> { } render() { + if (this.state.submit_in_process) { + return ( + <main className="max-h-full max-w-full lg:pt-20 pt-56"> + <div className="mx-auto w-full"> + <div className="loader">Loading...</div> + </div> + </main> + ); + } return ( <main className='min-h-full max-w-full flex flex-col justify-start items-center lg:pt-20 pt-52 z-0 bottom-0 relative mb-8'> <section className="flex flex-col items-start mb-4"> diff --git a/package-lock.json b/package-lock.json @@ -18,6 +18,7 @@ "react-blurhash": "0.1.3", "react-dom": "17.0.2", "react-dropzone": "11.5.1", + "react-toastify": "^8.1.0", "reflect-metadata": "0.1.13", "sequelize": "6.14.1", "sequelize-typescript": "2.1.2", @@ -2264,6 +2265,14 @@ "node": ">=4" } }, + "node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -6089,6 +6098,18 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/react-toastify": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.1.0.tgz", + "integrity": "sha512-M+Q3rTmEw/53Csr7NsV/YnldJe4c7uERcY7Tma9mvLU98QT2VhIkKwjBzzxZkJRk/oBKyUAtkyMjMgO00hx6gQ==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -9117,6 +9138,11 @@ "escape-string-regexp": "^1.0.5" } }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -11984,6 +12010,14 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "react-toastify": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.1.0.tgz", + "integrity": "sha512-M+Q3rTmEw/53Csr7NsV/YnldJe4c7uERcY7Tma9mvLU98QT2VhIkKwjBzzxZkJRk/oBKyUAtkyMjMgO00hx6gQ==", + "requires": { + "clsx": "^1.1.1" + } + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", diff --git a/package.json b/package.json @@ -29,6 +29,7 @@ "react-blurhash": "0.1.3", "react-dom": "17.0.2", "react-dropzone": "11.5.1", + "react-toastify": "^8.1.0", "reflect-metadata": "0.1.13", "sequelize": "6.14.1", "sequelize-typescript": "2.1.2", @@ -57,4 +58,4 @@ "optionalDependencies": { "fsevents": "2.3.2" } -} -\ No newline at end of file +} diff --git a/pages/_app.tsx b/pages/_app.tsx @@ -1,9 +1,11 @@ import '../styles/globals.css'; +import 'react-toastify/dist/ReactToastify.min.css'; import type { AppProps } from 'next/app'; import { useState } from 'react'; import { client, ClientContext, guest_client } from '../components/ClientContext'; import React from 'react'; import Head from 'next/head'; +import { ToastContainer } from 'react-toastify'; function MyApp({ Component, pageProps }: AppProps) { const [value] = useState({ @@ -22,9 +24,21 @@ function MyApp({ Component, pageProps }: AppProps) { <ClientContext.Provider value={value} > - <Component {...pageProps}/> + <Component {...pageProps} /> </ClientContext.Provider> </React.StrictMode> + <ToastContainer + position="top-center" + autoClose={5000} + hideProgressBar={false} + newestOnTop={false} + closeOnClick + rtl={false} + pauseOnFocusLoss + draggable + pauseOnHover + theme="colored" + /> </> ); } diff --git a/pages/login.tsx b/pages/login.tsx @@ -113,8 +113,8 @@ class Login extends PureComponent<Props, State> { <h1 className='text-xl text-gray-900 dark:text-gray-200 font-bold'>Log In</h1> </div> </div> - <div className="m-0 w-full"> - <div className="loader fixed top-[50%] left-[50%] transform translate-x-[-50%] translate-y-[-50%]">Loading...</div> + <div className="mx-auto w-full"> + <div className="loader">Loading...</div> </div> </main> <Footer></Footer> diff --git a/pages/post/[id].tsx b/pages/post/[id].tsx @@ -136,7 +136,7 @@ class Post extends PureComponent<Props, State> { <Header></Header> <main className='w-full lg:pt-20 pt-52 z-0'> <div className="m-0 w-full"> - <div className="loader fixed top-[50%] left-[50%] transform translate-x-[-50%] translate-y-[-50%]">Loading...</div> + <div className="loader">Loading...</div> </div> </main> </div> diff --git a/pages/submit.tsx b/pages/submit.tsx @@ -2,6 +2,7 @@ import Head from "next/head"; import { NextRouter, withRouter } from "next/router"; import { PureComponent, ReactNode } from "react"; import { FileRejection } from "react-dropzone"; +import { toast } from "react-toastify"; import { ClientContext } from "../components/ClientContext"; import Footer from "../components/Footer"; import Header from "../components/Header"; @@ -80,6 +81,13 @@ class Submit extends PureComponent<Props, State> implements DropCallbacks { submitState: "start" }; } + componentDidUpdate(prevProps: Props, prevState: State) { + if (this.state.error && this.state.error !== prevState.error) { + toast(() => <div><h2 className="text-xl text-white">Error</h2><br />{this.state.error}</div>, { + autoClose: false + }); + } + } render(): ReactNode { return (