mirror of
https://github.com/sigmasternchen/threadule
synced 2025-03-15 08:09:01 +00:00
feat: added drag and drop in tweet list; mark tweets on error
This commit is contained in:
parent
5d3f35234d
commit
75fca7d41b
3 changed files with 90 additions and 42 deletions
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue