recursion

This commit is contained in:
2024-06-12 19:18:37 +02:00
parent 9ddea4d53d
commit 9a515baf37
6 changed files with 131 additions and 115 deletions

View File

@@ -1,18 +1,16 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import PostCommentList from './components/PostCommentList';
import NewPostForm from './components/NewPostForm'; import NewPostForm from './components/NewPostForm';
import './App.scss'; import './App.scss';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faTimes} from "@fortawesome/free-solid-svg-icons"; import {faTimes} from "@fortawesome/free-solid-svg-icons";
import {postApi} from "./api/posts.api"; import {postApi} from "./api/posts.api";
import {locationUtils} from "./utils/location"; import {locationUtils} from "./utils/location";
import PostComment from "./components/PostComment"; import Post from "./components/Post";
import keycloak from "./Authentification/Keycloak"; import keycloak from "./Authentification/Keycloak";
const App = () => { const App = () => {
const [posts, setPosts] = useState([]); const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]); const [selectedPost, selectPost] = useState(null);
const [selectedPost, setSelectedPost] = useState(null);
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
useEffect(() => { useEffect(() => {
@@ -23,15 +21,10 @@ const App = () => {
useEffect(() => { useEffect(() => {
if(selectedPost) { if(selectedPost) {
setSelectedPost(posts.find(post => post.id === selectedPost.id)); selectPost(posts.find(post => post.id === selectedPost.id));
} }
}, [posts]); }, [posts]);
useEffect(() => {
if(selectedPost) {
setComments(selectedPost.comments);
}
}, [selectedPost]);
const reload = async () => { const reload = async () => {
await loadPosts().then(data => { await loadPosts().then(data => {
@@ -53,44 +46,28 @@ const App = () => {
await postApi.createNewPost("", commentText, postId).then(await reload); await postApi.createNewPost("", commentText, postId).then(await reload);
}; };
const vote = (postId, reaction) => {
// TODO postApi.reactToPost(postId)
console.log(postId, reaction);
};
const handlePostClick = (post) => {
setSelectedPost(post);
};
const handleCommentSubmit = async (e) => { const handleCommentSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
if (comment.trim() && selectedPost) { if (comment.trim() && selectedPost) {
await addComment(selectedPost.id, comment); await addComment(selectedPost.id, comment);
setComment(''); setComment('');
await reload(); await reload();
handlePostClick(selectedPost); selectPost(selectedPost);
} }
}; };
const closePost = () => { const closePost = () => {
setSelectedPost(null); selectPost(null);
setComment('');
}; };
return ( return (
<div className="app"> <div className="app">
<button onClick={() => keycloak.logout()}>Logout</button> <button className="logout" onClick={() => keycloak.logout()}>Logout</button>
<h1>SWA - Jodel</h1> <h1>SWA - Jodel</h1>
{!selectedPost && ( {!selectedPost && (
<div> <div>
<NewPostForm addPost={addPost}/> <NewPostForm addPost={addPost}/>
<PostCommentList {posts.map(post => <Post key={post.id} post={post} recursionDepth={5} selectPost={selectPost} reload={reload} />)}
posts={posts}
addComment={addComment}
vote={vote}
onPostClick={handlePostClick}
type={"posts"}
/>
</div> </div>
)} )}
{selectedPost && ( {selectedPost && (
@@ -98,7 +75,7 @@ const App = () => {
<div className="close-post"> <div className="close-post">
<FontAwesomeIcon className={"close-button"} onClick={closePost} icon={faTimes}/> <FontAwesomeIcon className={"close-button"} onClick={closePost} icon={faTimes}/>
</div> </div>
<PostComment post={selectedPost} vote={vote} type={"post"}></PostComment> <Post key={selectedPost.id} post={selectedPost} recursionDepth={0} selectPost={null} reload={reload}/>
<form className="comment-form" onSubmit={handleCommentSubmit}> <form className="comment-form" onSubmit={handleCommentSubmit}>
<div className="textarea-container"> <div className="textarea-container">
<textarea <textarea
@@ -117,8 +94,7 @@ const App = () => {
<button type="submit">Kommentieren</button> <button type="submit">Kommentieren</button>
</form> </form>
<div className="comments"> <div className="comments">
{comments && <PostCommentList posts={comments} onPostClick={null} vote={vote} {selectedPost.comments.map(post => <Post key={selectedPost.id} post={post} recursionDepth={0} selectPost={null} reload={reload} />)}
type={"comments"}></PostCommentList>}
</div> </div>
</div> </div>
)} )}

View File

@@ -241,3 +241,33 @@ button {
background-color: #e68a00; background-color: #e68a00;
} }
} }
.comment {
padding-left: 30px;
}
.post-container {
width: 100%;
}
.nrOfComments {
height: 100%;
display: flex;
align-self: flex-end;
align-items: center;
flex-direction: row;
font-size: 1.5em;
padding: 0.35em;
* {
padding-right: 0.25em;
}
}
.icon {
color: #ff9908;
}
.logout {
margin-left: 75%;
width: 25%
}

View File

@@ -2,22 +2,22 @@ import axios from "axios"
import {locationUtils} from "../utils/location"; import {locationUtils} from "../utils/location";
import keycloak from "../Authentification/Keycloak"; import keycloak from "../Authentification/Keycloak";
const path = "https://api.jodel.anxietyprime.de/post"; const path = "https://api.jodel.anxietyprime.de";
export const postApi = { export const postApi = {
async getPostsByCoords (lon, lat) { async getPostsByCoords (lon, lat) {
if (!keycloak.authenticated) return []; if (!keycloak.authenticated) return [];
return (await axios.get(`${path}s/${lon}/${lat}`)).data; return (await axios.get(`${path}/posts/${lon}/${lat}`)).data;
}, },
async getPostByID (id) { async getPostByID (id) {
if (!keycloak.authenticated) return []; if (!keycloak.authenticated) return [];
return (await axios.get(`${path}/${id}`)).data; return (await axios.get(`${path}/post/${id}`)).data;
}, },
async createNewPost(title, content, parent = null) { async createNewPost(title, content, parent = null) {
const location = await locationUtils.getCurrentLocation(); const location = await locationUtils.getCurrentLocation();
if(location.lon && location.lat) { if(location.lon && location.lat) {
if(parent !== null) { if(parent !== null) {
await axios.post(`${path}s`, { await axios.post(`${path}/posts`, {
"authorID": 1, "authorID": 1,
"title": title, "title": title,
"content": content, "content": content,
@@ -29,7 +29,7 @@ export const postApi = {
"parent": parent "parent": parent
}); });
} else { } else {
await axios.post(`${path}s`, { await axios.post(`${path}/posts`, {
"authorID": 1, "authorID": 1,
"title": title, "title": title,
"content": content, "content": content,
@@ -45,8 +45,6 @@ export const postApi = {
} }
}, },
async reactToPost(id, reaction) { async reactToPost(id, reaction) {
await axios.put(`${path}/${id}`, { await axios.patch(`${path}/post/${id}`, {reaction: reaction});
"reaction": reaction,
})
} }
} }

86
src/components/Post.js Normal file
View File

@@ -0,0 +1,86 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faCaretUp,
faCaretDown,
faCommentAlt,
faComments,
faAngleUp,
faAngleDown
} from '@fortawesome/free-solid-svg-icons';
import {faMessage} from "@fortawesome/free-solid-svg-icons/faMessage";
import {postApi} from "../api/posts.api";
const Post = ({ post, recursionDepth, selectPost, reload }) => {
const [showComments, setShowComments] = React.useState(false);
const upvote = () => {
if (post.reaction === true) {
revokeVote();
return;
}
postApi.reactToPost(post.id, true).then(reload);
}
const downVote = () => {
if (post.reaction === false) {
revokeVote();
return;
}
postApi.reactToPost(post.id, false).then(reload);
}
const revokeVote = () => {
postApi.reactToPost(post.id, null).then(reload);
}
return (
<div>
<div className="post" onClick={() => setShowComments(!showComments)}>
<div className="post-container">
{post.id === post.parent && <div className="post-title">
<h3>{post.title}</h3>
</div>}
<div className="post-content">
<p>{post.content}</p>
</div>
</div>
<div className="nrOfComments">
<FontAwesomeIcon className="icon" icon={faComments}/>
<span className="">{post.comments.length}</span>
</div>
<div className="post-right">
<div className="post-date">
{post.date.slice(8, 10) + "." + post.date.slice(5, 7) + "." + post.date.slice(0, 4)}
</div>
<div className="row">
<div className="votes">
<button onClick={(e) => {
e.stopPropagation();
upvote();
}}>
{post.reaction === true ? <FontAwesomeIcon icon={faCaretUp}/> : <FontAwesomeIcon icon={faAngleUp} />}
</button>
<span>{post.reactions.positive - post.reactions.negative}</span>
<button onClick={(e) => {
e.stopPropagation();
downVote();
}}>
{post.reaction === false ? <FontAwesomeIcon icon={faCaretDown}/> : <FontAwesomeIcon icon={faAngleDown} />}
</button>
</div>
</div>
{recursionDepth !== 0 && <button onClick={() => selectPost(post)}>Antworten</button>}
</div>
</div>
<div className="comment">
{recursionDepth !== 0 && showComments && post.comments.map(post => <Post key={post.id} post={post}
recursionDepth={recursionDepth - 1}
selectPost={selectPost}
reload={reload} />)}
</div>
</div>
)
;
};
export default Post;

View File

@@ -1,42 +0,0 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCaretUp, faCaretDown } from '@fortawesome/free-solid-svg-icons';
const PostComment = ({ post, vote, type }) => {
return (
<div className="post">
<div className="post-container">
{type === "post" && <div className="post-title">
<h3>{post.title}</h3>
</div>}
<div className="post-content">
<p>{post.content}</p>
</div>
</div>
<div className="post-right">
<div className="post-date">
{post.date.slice(8, 10) + "." + post.date.slice(5, 7) + "." + post.date.slice(0, 4)}
</div>
<div className="row">
<div className="votes">
<button onClick={(e) => {
e.stopPropagation();
vote(post.id, post.reaction !== null ? null : true);
}}>
<FontAwesomeIcon icon={faCaretUp}/>
</button>
<span>{post.reactions.positive - post.reactions.negative}</span>
<button onClick={(e) => {
e.stopPropagation();
vote(post.id, post.reaction !== null ? null : false);
}}>
<FontAwesomeIcon icon={faCaretDown}/>
</button>
</div>
</div>
</div>
</div>
);
};
export default PostComment;

View File

@@ -1,32 +0,0 @@
import React from 'react';
import PostComment from './PostComment';
const PostCommentList = ({ posts, vote, onPostClick, type }) => {
return (
<div className="post-list">
<h3>{type === "posts" ? "Posts" : "Kommentare"}</h3>
{type === "posts" && posts.map(post => (
post.id === post.parent && (
<div key={post.id} onClick={() => onPostClick(post)}>
<PostComment
post={post}
vote={vote}
type={"post"}
/>
</div>
)
))}
{type === "comments" && posts.map(comment => (
<div key={comment.id}>
<PostComment
post={comment}
vote={vote}
type={"comment"}
/>
</div>
))}
</div>
);
};
export default PostCommentList;