commit 165be27c1737db92cd10fd38b995c0945b833179
parent e75348762ea8bc4178051a2acbe64e7fe0ec06a2
Author: MTRNord <mtrnord1@gmail.com>
Date: Tue, 25 Jan 2022 01:11:48 +0100
Add rough Initial Submit page design
Diffstat:
8 files changed, 276 insertions(+), 56 deletions(-)
diff --git a/components/submit_page/main.tsx b/components/submit_page/main.tsx
@@ -0,0 +1,188 @@
+import { PureComponent } from "react";
+import { DropCallbacks } from "./start";
+import PropTypes from 'prop-types';
+import { PreviewWithDataFile } from "../../pages/submit";
+import { ClientContext } from "../ClientContext";
+import Dropzone from "react-dropzone";
+
+interface Props extends DropCallbacks {
+ files: PreviewWithDataFile[];
+};
+
+type State = {
+ currentFileIndex: number;
+ hasSubmit: boolean;
+ hasBack: boolean;
+ [key: string]: any;
+};
+class MainSubmissionForm extends PureComponent<Props, State> {
+ declare context: React.ContextType<typeof ClientContext>;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ currentFileIndex: 0,
+ hasSubmit: props.files.length === 1 ? true : false,
+ hasBack: false
+ };
+ }
+ static propTypes = {
+ onDrop: PropTypes.func,
+ onDropError: PropTypes.func,
+ files: PropTypes.array,
+ hasSubmit: PropTypes.bool,
+ hasBack: PropTypes.bool
+ };
+
+ renderThumb() {
+ const file = this.props.files[this.state.currentFileIndex];
+ return (
+ <div className="relative" style={{ height: "280px" }} key={file.name}>
+ <img alt={file.name} className="h-auto w-full object-cover max-w-full align-bottom" style={{ height: "280px" }} src={file.preview_url} />
+ </div>
+ );
+ }
+
+ renderThumbs() {
+ return this.props.files.map((file, index) => {
+ const setIndex = () => { this.setState({ currentFileIndex: index }); };
+ return (
+ <div className="relative cursor-pointer w-auto aspect-video" onClick={setIndex} style={{ height: "140px" }} key={file.name}>
+ <img alt={file.name} className="h-auto w-full object-cover max-w-full align-bottom aspect-video" style={{ height: "140px" }} src={file.preview_url} />
+ </div>
+ );
+ });
+ }
+
+ onNext(ev: { preventDefault: () => void; }) {
+ ev.preventDefault();
+ const newIndex = this.state.currentFileIndex + 1;
+ if (this.props.files.length - 1 > newIndex) {
+ const hasBack = newIndex > 0 ? true : false;
+ this.setState({ currentFileIndex: newIndex, hasBack });
+ } else if (this.props.files.length - 1 === newIndex) {
+ this.setState({ currentFileIndex: newIndex, hasSubmit: true });
+ }
+ }
+
+ onPrev(ev: { preventDefault: () => void; }) {
+ ev.preventDefault();
+ const newIndex = this.state.currentFileIndex - 1;
+ if (this.props.files.length - 1 > newIndex) {
+ const hasBack = newIndex > 0 ? true : false;
+ this.setState({ currentFileIndex: newIndex, hasBack });
+ } else if (this.props.files.length - 1 === newIndex) {
+ this.setState({ currentFileIndex: newIndex, hasSubmit: true });
+ }
+ }
+
+
+ async handleSubmit(event: { preventDefault: () => void; }) {
+ const range = [...Array(this.props.files.length - 1).keys()]; // eslint-disable-line unicorn/new-for-builtins
+ for (const index of range) {
+ const title = `${index}_title`;
+ console.log(`${index}: ${this.state[title]}`);
+ const description = `${index}_description`;
+ console.log(`${index}: ${this.state[description]}`);
+ const tags = `${index}_tags`;
+ console.log(`${index}: ${this.state[tags]}`);
+ const license = `${index}_license`;
+ console.log(`${index}: ${this.state[license]}`);
+ const nsfw = `${index}_nsfw`;
+ console.log(`${index}: ${this.state[nsfw]}`);
+ }
+ }
+
+ handleInputChange(event: { target: any; }) {
+ const target = event.target;
+ const value = target.type === 'checkbox' ? target.checked : target.value;
+ const name = `${this.state.currentFileIndex}_${target.name}`;
+ this.setState({
+ [name]: value
+ } as State);
+ }
+
+ render() {
+ 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">
+ <div className="flex flex-row my-4">
+ {this.renderThumb()}
+ </div>
+ <div className="flex flex-row w-full mb-4">
+ <form className="flex flex-col w-full">
+ <label className="inner-flex flex-col">
+ <span className="text-xl text-gray-900 dark:text-gray-200 font-bold">Image Title</span>
+ <input required name="title" value={this.state[`${this.state.currentFileIndex}_title`] || ""} type="text" placeholder="Set an image title" className="min-w-full placeholder:text-gray-900 text-gray-900" onChange={this.handleInputChange.bind(this)} />
+ </label>
+
+ <span className="h-4"></span>
+
+ <label className="inner-flex flex-col">
+ <span className="text-xl text-gray-900 dark:text-gray-200 font-bold">Description</span>
+ <textarea name="description" value={this.state[`${this.state.currentFileIndex}_description`] || ""} placeholder="Description Text of the current image" className="min-w-full placeholder:text-gray-900 text-gray-900" onChange={this.handleInputChange.bind(this)} />
+ </label>
+
+ <span className="h-4"></span>
+
+ <label className="inner-flex flex-col">
+ <span className="text-xl text-gray-900 dark:text-gray-200 font-bold">Image Tags</span>
+ <input name="tags" type="text" value={this.state[`${this.state.currentFileIndex}_tags`] || ""} placeholder="Enter tags (Seperate with a comma)" className="min-w-full placeholder:text-gray-900 text-gray-900" onChange={this.handleInputChange.bind(this)} />
+ </label>
+
+ <span className="h-4"></span>
+
+ <label className="inner-flex flex-col">
+ <span className="text-xl text-gray-900 dark:text-gray-200 font-bold">License</span>
+ <select required name="license" value={this.state[`${this.state.currentFileIndex}_license`] || ""} placeholder="Enter tags (Confirm by pressing enter)" className="min-w-full placeholder:text-gray-900 text-gray-900" onChange={this.handleInputChange.bind(this)}>
+ <option value="" disabled selected>Select an Creative Commons License</option>
+ </select>
+ </label>
+
+ <span className="h-4"></span>
+
+ <label className="inner-flex flex-col">
+ <span className="text-xl text-gray-900 dark:text-gray-200 font-bold">Mature/NSFW Content?</span>
+ <select required name="nsfw" value={this.state[`${this.state.currentFileIndex}_nsfw`] || ""} placeholder="Enter tags (Confirm by pressing enter)" className="min-w-full placeholder:text-gray-900 text-gray-900" onChange={this.handleInputChange.bind(this)}>
+ <option value="" disabled selected>Select Yes or No</option>
+ <option value="no">No</option>
+ <option value="yes">Yes</option>
+ </select>
+ </label>
+ </form>
+ </div>
+ <div className="flex justify-between w-full flex-row-reverse">
+ {
+ this.state.hasSubmit ?
+ <button className="text-base text-gray-900 dark:text-gray-200" onClick={this.handleSubmit.bind(this)}>Submit Posts</button>
+ :
+ <button className="text-base text-gray-900 dark:text-gray-200" onClick={this.onNext.bind(this)}>Next Image</button>
+ }
+ {this.state.hasBack ? <button className="text-base text-gray-900 dark:text-gray-200" onClick={this.onPrev.bind(this)}>Prev Image</button> : undefined}
+ </div>
+ </section>
+ {/* Warning for further readers. This css is easy to explode. Reasons are unknown. Dont touch it unless you know the fix! */}
+ <section className="max-w-full flex flex-row justify-start items-start ">
+ <div className="overflow-x-scroll mr-4 ml-8">
+ <div className="flex gap-1">
+ {this.renderThumbs()}
+ </div>
+ </div>
+ <Dropzone accept='image/*' onDropAccepted={this.props.onDrop} onDropRejected={this.props.onDropError}>
+ {({ getRootProps, getInputProps }) => (
+ <div className="flex aspect-square justify-center items-center border border-dashed dark:border-white border-black cursor-pointer h-[140px] w-[140px] mr-8" {...getRootProps()}>
+ <input {...getInputProps()} />
+ <svg className="dark:fill-gray-200 fill-gray-900" xmlns="http://www.w3.org/2000/svg" height="5rem" viewBox="0 0 24 24" width="5rem" fill="inherit"><path d="M0 0h24v24H0V0z" fill="none" /><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /></svg>
+ </div>
+ )}
+ </Dropzone>
+ </section>
+ </main>
+ );
+ }
+}
+
+
+MainSubmissionForm.contextType = ClientContext;
+
+export default MainSubmissionForm;
+\ No newline at end of file
diff --git a/components/submit_page/start.tsx b/components/submit_page/start.tsx
@@ -0,0 +1,36 @@
+import { PureComponent } from "react";
+import Dropzone, { FileRejection } from "react-dropzone";
+import PropTypes from 'prop-types';
+
+export interface DropCallbacks {
+ onDrop: (files: File[]) => void;
+ onDropError: (fileRejections: FileRejection[]) => void;
+}
+
+interface Props extends DropCallbacks { }
+
+export class StartSubmit extends PureComponent<Props> {
+ static propTypes = {
+ onDrop: PropTypes.func,
+ onDropError: PropTypes.func
+ };
+
+ render() {
+ return (
+ <main className='min-h-full flex flex-col justify-center lg:pt-20 pt-52 z-0 bottom-0 relative flex-grow mb-8'>
+ <div className="flex justify-center items-center mt-4">
+ <Dropzone accept='image/*' onDropAccepted={this.props.onDrop} onDropRejected={this.props.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 hover:border-[#2196f3] duration-[24ms] transition" {...getRootProps()}>
+ <input {...getInputProps()} />
+ <p className="text-base text-gray-900 dark:text-gray-200">Drag 'n' drop some images here, or click to select images</p>
+ </div>
+ </section>
+ )}
+ </Dropzone>
+ </div>
+ </main>
+ );
+ }
+}
+\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
@@ -14,6 +14,7 @@
"next": "12.0.8",
"node-localstorage": "2.2.1",
"pouchdb": "7.2.2",
+ "prop-types": "^15.8.1",
"react": "17.0.2",
"react-blurhash": "0.1.3",
"react-dom": "17.0.2",
diff --git a/package.json b/package.json
@@ -25,6 +25,7 @@
"next": "12.0.8",
"node-localstorage": "2.2.1",
"pouchdb": "7.2.2",
+ "prop-types": "15.8.1",
"react": "17.0.2",
"react-blurhash": "0.1.3",
"react-dom": "17.0.2",
diff --git a/pages/index.tsx b/pages/index.tsx
@@ -95,7 +95,7 @@ class Home extends PureComponent<Props, State>{
</Head>
<Header></Header>
<main className='mb-auto lg:pt-20 pt-52 z-0'>
- <div className='z-[100] sticky lg:top-20 top-[12.5rem] bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'>
+ <div className='z-[100] sticky lg:top-20 top-[12.5rem] bg-[#fefefe]/[.95] dark:bg-[#12161D]'>
<div className='h-16 px-10 w-full relative grid grid-cols-[1fr_auto_1fr] items-center' id='section-grid'>
<h1 className='text-xl text-gray-900 dark:text-gray-200 font-bold'>Home</h1>
</div>
diff --git a/pages/login.tsx b/pages/login.tsx
@@ -28,11 +28,11 @@ class Login extends PureComponent<Props, State> {
this.state = {
showServerField: false,
generateProfile: false,
- mxid: undefined,
- password: undefined,
+ mxid: "",
+ password: "",
serverUrl: constMatrixArtServer,
loading: false,
- error: undefined
+ error: ""
} as State;
this.handleInputChange = this.handleInputChange.bind(this);
diff --git a/pages/submit.tsx b/pages/submit.tsx
@@ -1,9 +1,11 @@
import Head from "next/head";
import { PureComponent, ReactNode } from "react";
-import Dropzone, { FileRejection } from "react-dropzone";
+import { FileRejection } from "react-dropzone";
import { ClientContext } from "../components/ClientContext";
import Footer from "../components/Footer";
import Header from "../components/Header";
+import MainSubmissionForm from "../components/submit_page/main";
+import { DropCallbacks, StartSubmit } from "../components/submit_page/start";
type Props = {};
@@ -11,14 +13,15 @@ type State = {
files: PreviewWithDataFile[];
fileRejections: FileRejection[];
error?: string;
+ submitState: "start" | "editing";
};
-type PreviewWithDataFile = File & {
- preview: HTMLImageElement;
+export type PreviewWithDataFile = File & {
+ preview_url: string;
data: ArrayBuffer;
};
-class Submit extends PureComponent<Props, State> {
+class Submit extends PureComponent<Props, State> implements DropCallbacks {
declare context: React.ContextType<typeof ClientContext>;
onDrop: (files: File[]) => void;
@@ -26,36 +29,32 @@ class Submit extends PureComponent<Props, State> {
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),
+ preview_url: URL.createObjectURL(file),
data: await this.readFileToArray(file),
}))) as PreviewWithDataFile[];
+ let newState: "start" | "editing";
+ switch (this.state.submitState) {
+ case "start": {
+ newState = "editing";
+ break;
+ }
+ default: {
+ newState = this.state.submitState;
+ break;
+ }
+ }
this.setState({
+ submitState: newState,
files: [...this.state.files, ...files_mapped]
});
};
this.state = {
files: [],
- fileRejections: []
+ fileRejections: [],
+ submitState: "start"
};
}
- readFileToSizes(file: File): Promise<HTMLImageElement> {
- return new Promise((resolve, reject) => {
- var fr = new FileReader();
- fr.addEventListener("load", () => {
- const img = new Image();
- // Save as we use readAsDataURL
- img.src = fr.result as string;
-
- resolve(img);
- });
- fr.addEventListener("error", (error) => {
- reject(error);
- });
- fr.readAsDataURL(file);
- });
- }
-
readFileToArray(file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
var fr = new FileReader();
@@ -87,17 +86,11 @@ class Submit extends PureComponent<Props, State> {
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 alt={file.name} 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='min-h-full flex flex-col justify-between bg-[#f8f8f8] dark:bg-[#06070D]'>
+ <div className='min-h-full flex flex-col bg-[#f8f8f8] dark:bg-[#06070D]'>
<Head>
<title key="title">Matrix Art | Submit Post</title>
<meta property="og:title" content="Matrix Art | Submit Post" key="og-title" />
@@ -106,35 +99,29 @@ class Submit extends PureComponent<Props, State> {
<meta property="og:type" content="website" key="og-type" />
</Head>
<Header></Header>
- <div className='z-[100] sticky lg:top-20 top-[12.5rem] bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'>
+ <div className='z-[100] sticky lg:top-[4.9rem] top-[12.5rem] bg-[#fefefe]/[.95] dark:bg-[#12161D]'>
<div className='h-16 px-10 w-full relative grid grid-cols-[1fr_auto_1fr] items-center' id='section-grid'>
<h1 className='text-xl text-gray-900 dark:text-gray-200 font-bold'>Submit Image</h1>
</div>
</div>
- <main className='min-h-full lg:pt-20 pt-52 z-0 bottom-0 relative'>
- <div className="min-h-full flex justify-center items-center">
- <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 hover:border-[#2196f3] duration-[24ms] transition" {...getRootProps()}>
- <input {...getInputProps()} />
- <p className="text-base text-gray-900 dark:text-gray-200">Drag 'n' drop some images here, or click to select images</p>
- </div>
- <ul className="flex flex-row flex-wrap gap-1 mt-4 max-w-[50rem] justify-evenly">
- {this.renderThumbs()}
- <li className='grow-[10]'></li>
- </ul>
- </section>
- )}
- </Dropzone>
- </div>
- </div>
- </main>
+ {this.renderStage()}
+ <div className="flex-grow"></div>
<Footer></Footer>
</div>
);
}
+ renderStage(): ReactNode {
+ switch (this.state.submitState) {
+ case "start": {
+ return <StartSubmit onDrop={this.onDrop} onDropError={this.onDropError} />;
+ }
+ case "editing":
+ return <MainSubmissionForm onDrop={this.onDrop} onDropError={this.onDropError} files={this.state.files} />;
+ default: {
+ return <></>;
+ }
+ }
+ }
}
Submit.contextType = ClientContext;
diff --git a/styles/globals.css b/styles/globals.css
@@ -126,4 +126,9 @@ html, body, #__next {
.gap-1 > * {
margin: 0.25rem;
}
+}
+
+
+option[value=""][disabled] {
+ display: none;
}
\ No newline at end of file