feat: added drag and drop in tweet list; mark tweets on error

This commit is contained in:
overflowerror 2021-08-21 16:29:05 +02:00
parent 5d3f35234d
commit 75fca7d41b
3 changed files with 90 additions and 42 deletions

View file

@ -12,12 +12,14 @@
"@types/jest": "^26.0.15", "@types/jest": "^26.0.15",
"@types/node": "^12.0.0", "@types/node": "^12.0.0",
"@types/react": "^17.0.0", "@types/react": "^17.0.0",
"@types/react-beautiful-dnd": "^13.1.1",
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.0",
"@types/react-router-dom": "^5.1.8", "@types/react-router-dom": "^5.1.8",
"axios": "^0.21.1", "axios": "^0.21.1",
"formik": "^2.2.9", "formik": "^2.2.9",
"formik-material-ui": "^3.0.1", "formik-material-ui": "^3.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",

View file

@ -18,7 +18,7 @@ const emptyThread = (): Thread => ({
status: ThreadStatus.SCHEDULED, status: ThreadStatus.SCHEDULED,
tweets: [ tweets: [
{ {
id: "", id: "new",
text: "", text: "",
status: TweetStatus.SCHEDULED, status: TweetStatus.SCHEDULED,
tweet_id: null, tweet_id: null,

View file

@ -1,4 +1,4 @@
import {FunctionComponent, useState} from "react"; import React, {FunctionComponent, useState} from "react";
import { import {
Button, Button,
Dialog, Dialog,
@ -16,6 +16,7 @@ import AddIcon from "@material-ui/icons/Add";
import {TweetStatus} from "../../api/entities/Tweet"; import {TweetStatus} from "../../api/entities/Tweet";
import CustomDate from "../../utils/CustomDate"; import CustomDate from "../../utils/CustomDate";
import {Alert} from "@material-ui/lab"; import {Alert} from "@material-ui/lab";
import {DragDropContext, Draggable, Droppable} from "react-beautiful-dnd";
export type ThreadFormProps = { export type ThreadFormProps = {
open: boolean open: boolean
@ -34,9 +35,15 @@ const Index: FunctionComponent<ThreadFormProps> = (
onCancel onCancel
} }
) => { ) => {
const [_idCounter, _setIdCounter] = useState<number>(0)
const getId = () => {
_setIdCounter(_idCounter + 1)
return _idCounter
}
const [thread, setThread] = useState<Thread>(initial) const [thread, setThread] = useState<Thread>(initial)
const [error, setError] = useState<string|null>(null) const [error, setError] = useState<string | null>(null)
return ( return (
<Dialog open={open}> <Dialog open={open}>
@ -62,48 +69,83 @@ const Index: FunctionComponent<ThreadFormProps> = (
}} }}
/> />
</Grid> </Grid>
{ <DragDropContext onDragEnd={() => {
thread.tweets.map((tweet, index) => ( }}>
<> <Droppable droppableId={"1"}>
<Grid item xs={11}> {(provided, snapshot) => (
<TextField <Grid
id="outlined-multiline-static" container
label={"Tweet " + (index + 1)} item
multiline xs={12}
rows={3} {...provided.droppableProps}
value={tweet.text} ref={provided.innerRef}
onChange={event => { >
thread.tweets[index].text = event.target.value {
setThread({ thread.tweets.map((tweet, index) => (
...thread <Draggable draggableId={tweet.id} index={index}>
}) {(provided, snapshot) => (
}} <Grid
variant="outlined" container
fullWidth item
/> xs={12}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
background: snapshot.isDragging ? "lightgrey" : undefined
}}
>
<Grid item xs={11}>
<TextField
error={
thread.tweets[index].text.trim().length == 0 ||
thread.tweets[index].text.length > 280
}
id="outlined-multiline-static"
label={"Tweet " + (index + 1)}
multiline
rows={3}
value={tweet.text}
onChange={event => {
thread.tweets[index].text = event.target.value
setThread({
...thread
})
}}
variant="outlined"
fullWidth
/>
</Grid>
<Grid item xs={1}>
<IconButton
aria-label="delete"
color={"secondary"}
disabled={thread.tweets.length <= 1}
onClick={() => {
thread.tweets.splice(index, 1)
setThread({
...thread
})
}}
>
<DeleteIcon fontSize="medium"/>
</IconButton>
</Grid>
</Grid>
)}
</Draggable>
))
}
{provided.placeholder}
</Grid> </Grid>
<Grid item xs={1}> )}
<IconButton </Droppable>
aria-label="delete" </DragDropContext>
color={"secondary"}
disabled={thread.tweets.length <= 1}
onClick={() => {
thread.tweets.splice(index, 1)
setThread({
...thread
})
}}
>
<DeleteIcon fontSize="medium" />
</IconButton>
</Grid>
</>
))
}
<Grid item xs={12}> <Grid item xs={12}>
<IconButton aria-label="add" onClick={() => { <IconButton aria-label="add" onClick={() => {
thread.tweets.push({ thread.tweets.push({
id: "", id: "new" + getId(),
text: "", text: "",
status: TweetStatus.SCHEDULED, status: TweetStatus.SCHEDULED,
tweet_id: null, tweet_id: null,
@ -117,7 +159,7 @@ const Index: FunctionComponent<ThreadFormProps> = (
</IconButton> </IconButton>
</Grid> </Grid>
</Grid> </Grid>
{ error && <Alert severity="error">{error}</Alert> } {error && <Alert severity="error">{error}</Alert>}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => { <Button onClick={() => {
@ -131,6 +173,10 @@ const Index: FunctionComponent<ThreadFormProps> = (
setError("Empty tweets are not allowed!") setError("Empty tweets are not allowed!")
return return
} }
if (!thread.tweets.every(t => t.text.length <= 280)) {
setError("Tweets can't be longer than 280 characters!")
return
}
setError(null) setError(null)
onSubmit(thread) onSubmit(thread)
}} color="primary"> }} color="primary">