diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 18:04:18 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 18:04:18 +1100 |
| commit | 93dfe2be64e8658839bcfe5356adf35f8cde7075 (patch) | |
| tree | c60b1e20d569b74dbde85123e1b2bf3590c66244 /src/web/components/posts | |
initial commit
Diffstat (limited to 'src/web/components/posts')
| -rw-r--r-- | src/web/components/posts/Posts.css | 84 | ||||
| -rw-r--r-- | src/web/components/posts/Posts.jsx | 163 |
2 files changed, 247 insertions, 0 deletions
diff --git a/src/web/components/posts/Posts.css b/src/web/components/posts/Posts.css new file mode 100644 index 0000000..1bee23a --- /dev/null +++ b/src/web/components/posts/Posts.css @@ -0,0 +1,84 @@ +.container { + position: relative; + + display: flex; + flex-direction: column; + justify-content: stretch; + + width: 100%; +} +.containerPadding { + padding: 1rem 1rem 0 1rem; + overflow: scroll; +} +.postContainer { + flex-basis: 100%; + transition: opacity var(--transition-time) ease-in-out; +} +.postContainerActive { + cursor: pointer; + opacity: 50%; +} +.blockEvents { + pointer-events: none; +} + +.container > h1 { + font-size: 1.8em; + + margin-top: 1rem; + margin-bottom: 1rem; + + color: white; + font-weight: bolder; + text-align: center; +} +.container > h2 { + color: var(--black-95); + text-align: center; +} +.container > h3 { + margin-top: 1em; + padding: 1.5em 2em; + + color: var(--brand-100); + opacity: 0.5; + background-color: var(--black-5); + border-radius: 1em; +} +.container > button { + align-self: center; + + margin-top: 1.5em; + + text-transform: uppercase; + font-weight: 600; + cursor: pointer; +} + +.containerFade { + animation: containerFade var(--transition-time) linear; +} +@keyframes containerFade { + 0% { + opacity: 0%; + } + 100% { + opacity: 100%; + } +} + +.containerLoading { + filter: blur(1px); + pointer-events: none; + animation: containerLoading 0.6s infinite; + mask-image: linear-gradient(to bottom, black 0%, transparent 22em); +} +@keyframes containerLoading { + 0%, 100% { + opacity: 100%; + } + 50% { + opacity: 80%; + } +} diff --git a/src/web/components/posts/Posts.jsx b/src/web/components/posts/Posts.jsx new file mode 100644 index 0000000..3944f67 --- /dev/null +++ b/src/web/components/posts/Posts.jsx @@ -0,0 +1,163 @@ +import React, {useState, useEffect, useRef} from "react"; +import {Route, Routes, useLocation, useNavigate} from "react-router-dom"; + + +import Button from "components/button/Button.jsx"; +import Submission from "components/submission/Submission.jsx"; +import Post from "components/post/Post.jsx"; +import {getSubreactFromLocation} from "helpers/Location.jsx"; + +import styles from "./Posts.css"; + +async function makePostsRequest(location, setPosts, setError, mounted) { + const init = { + method: "GET", + referrer: "same-origin", + } + const url = "/api/posts?" + new URLSearchParams({ + page: 0, + amount: 25, + subreact: getSubreactFromLocation(location), + }); + return fetch(url, init) + .then((response) => { + return response.json() + .catch(() => { + throw new Error("unexpected response from server") + }) + }) + .then((json) => { + if ("error" in json) { + throw new Error(json.error); + } + if (!mounted) { + return; + } + setPosts(json.posts); + }) + .catch((error) => { + console.log(error); + if (!mounted) { + return; + } + setError(error.message); + }); +} + +function PostsError({navigate, error}) { + return ( + <div className={styles.container + " " + styles.containerFade}> + <h1>Something's wrong!</h1> + <h2>Sorry, but an unexpected issue has forced us to stop early. Here are the technical details:</h2> + <h3>Error: {error}</h3> + <Button onClick={() => {navigate(0);}}> + Try again + </Button> + </div> + ); +} + +function PostsLoading() { + return ( + <div className={styles.container + " " + styles.containerLoading}> + {[...Array(3)].map((_, i) => + <Post placeholder key={i} /> + )} + </div> + ); +} + +function PostsEmpty({navigate}) { + return ( + <div className={styles.container + " " + styles.containerFade}> + <h1>There's nothing here?!?</h1> + <h2>Try changing subreacts.</h2> + <Button onClick={() => {navigate("/");}}> + Take me home + </Button> + </div> + ); +} + +function PostsStates({error, posts, navigate}) { + if (error !== null) { // Error message. + return ( + <PostsError navigate={navigate} error={error} /> + ); + } + + if (posts == null) { + return ( + <PostsLoading /> + ) + } + + + if (posts.length == 0) { + return ( + <PostsEmpty navigate={navigate} /> + ); + } + + return ( + posts.map(({uid, title, contents, time_updated, thumbnail, subreact}, i) => + <Post key={i} + uid={uid} + title={title} + contents={contents} + timeUpdated={time_updated} + image={thumbnail} + subreact={subreact} />) + ); +} + +export default function Posts() { + const navigate = useNavigate(); + const location = useLocation(); + const locationRef = useRef(location); + + const [posts, setPosts] = useState(null); + const [error, setError] = useState(null); + const [submissionActive, setSubmissionActive] = useState(false); + + + // We make a request if we're at the homepage or if we're changing subreacts. + const shouldMakeRequest = () => { + if (posts === null) { + return true; + } + const [previous, current] = + [locationRef.current, location].map((loc) => getSubreactFromLocation(loc)); + return previous != current; + }; + // This is a bit ugly, we're caching off our previous location so that we know + // if we should update or not. + useEffect(() => { + let mounted = true; + + if (shouldMakeRequest()) { + setPosts(null); + makePostsRequest(location, setPosts, setError, mounted); + } + + locationRef.current = location; + return () => { + setError(null); + mounted = false; + } + }, [location]); + + return ( + <div className={styles.container}> + <div className={styles.containerPadding}> + <div className={styles.postContainer + (submissionActive ? " " + styles.postContainerActive : "")} + onClick={(submissionActive ? () => {setSubmissionActive(false);} : null)}> + <div className={(submissionActive ? styles.blockEvents : "")}> + <PostsStates error={error} posts={posts} navigate={navigate}/> + </div> + </div> + </div> + <Submission active={submissionActive} setActive={setSubmissionActive} /> + </div> + ); +} |
