Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/delete from local storage #41

Merged
merged 4 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion components/experiment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ export default function Experiment() {
</Card>

<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={isSnackbarOpen}
autoHideDuration={3000}
onClose={handleCloseSnackbar}>
Expand Down
87 changes: 76 additions & 11 deletions components/home.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { Box, Card, CardContent, List, ListItem, ListItemText, Typography } from "@material-ui/core";
import { useCallback, useState } from "react";
import { Box, Button, Card, CardContent, IconButton, List, ListItem, ListItemIcon, ListItemText, Snackbar, Typography } from "@material-ui/core";
import { MouseEvent, useCallback, useReducer, useState } from "react";
import { useDropzone } from 'react-dropzone';
import Layout from "../components/layout";
import useStyles from "../styles/home.style";
import { NextRouter, useRouter } from 'next/router'
import SystemUpdateAltIcon from '@material-ui/icons/SystemUpdateAlt';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import DeleteIcon from '@material-ui/icons/Delete';
import { paths } from "../paths";
import { ExperimentType } from "../types/common";
import { useGlobal } from "../context/global-context";
import { saveExperiment } from '../context/experiment-context';
import { v4 as uuid } from 'uuid';
import { isEmpty } from "../utility/string-util";
import { reducer } from "../reducers/home-reducer";

export default function Home() {
const classes = useStyles()
const router: NextRouter = useRouter()
const [uploadMessage, setUploadMessage] = useState("Drag file here")
const { state } = useGlobal()
const { state, dispatch } = useGlobal()
const [isSnackbarOpen, setSnackbarOpen] = useState(false)
const [deletionState, dispatchDeletion] = useReducer(reducer, { experimentsToDelete: [] })

const onDrop = useCallback(acceptedFiles => {
const reader = new FileReader()
Expand Down Expand Up @@ -62,10 +66,12 @@ export default function Home() {
}

const createNewExperiment = () => {
deleteExperiments()
router.push(`${paths.experiment}/${uuid()}`)
}

const openSavedExperiment = (key: string) => {
deleteExperiments()
router.push(`${paths.experiment}/${key}`)
}

Expand All @@ -80,6 +86,33 @@ export default function Home() {
return key
}

const deleteExperiment = (e: MouseEvent, id: string) => {
e.stopPropagation()
dispatchDeletion( { type: "addExperimentForDeletion", payload: id } )
setSnackbarOpen(true)
}

const handleCloseSnackbar = () => {
setSnackbarOpen(false)
deleteExperiments()
}

const deleteExperiments = () => {
const experimentsToDelete: string[] = deletionState.experimentsToDelete
if (experimentsToDelete.length > 0) {
experimentsToDelete.forEach(id => {
dispatch({ type: 'deleteExperimentId', payload: id })
localStorage.removeItem(id)
})
dispatchDeletion({ type: 'resetExperimentsForDeletion' })
}
}

const undoDeleteExperiment = () => {
dispatchDeletion({ type: 'resetExperimentsForDeletion' })
setSnackbarOpen(false)
}

return (
<Layout>
<Card className={classes.mainContainer}>
Expand Down Expand Up @@ -123,14 +156,25 @@ export default function Home() {
<Box mb={1}>
{state.experimentsInLocalStorage.length > 0 ?
<List component="nav">
{state.experimentsInLocalStorage.map((k, i) =>
<ListItem key={i} button onClick={() => openSavedExperiment(k)}>
<ListItemText
primary={getExperimentName(k)}
secondary={k}
secondaryTypographyProps={{ color: "inherit" }} />
<ChevronRightIcon />
</ListItem>
{state.experimentsInLocalStorage
.filter(id => deletionState.experimentsToDelete.indexOf(id) === -1)
.map((id, i) =>
<ListItem key={i} button onClick={() => openSavedExperiment(id)}>
<ListItemIcon>
<IconButton
edge="start"
onClick={(e: MouseEvent) => deleteExperiment(e, id)}>
<DeleteIcon
color="secondary"
fontSize="small"/>
</IconButton>
</ListItemIcon>
<ListItemText
primary={getExperimentName(id)}
secondary={id}
secondaryTypographyProps={{ color: "inherit" }} />
<ChevronRightIcon />
</ListItem>
)}
</List>
:
Expand All @@ -144,6 +188,27 @@ export default function Home() {

</CardContent>
</Card>

<Snackbar
open={isSnackbarOpen}
autoHideDuration={4000}
onClose={handleCloseSnackbar}
message={
<>
<Typography variant="body1">
{`Experiment${deletionState.experimentsToDelete.length > 1 ? 's' : ''} deleted:`}
</Typography>
{deletionState.experimentsToDelete.map(e =>
<Typography variant="body2">{e}</Typography>
)}
</>
}
action={
<Button
color="secondary"
size="small"
onClick={() => undoDeleteExperiment()}>Undo</Button>
}/>
</Layout>
);
}
28 changes: 20 additions & 8 deletions reducers/global-reducer.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { reducer, State } from "./global-reducer"

describe("storeExperimentId", () => {
const initState: State = {
debug: false,
useLocalStorage: true,
experimentsInLocalStorage: []
}
const initState: State = {
debug: false,
useLocalStorage: true,
experimentsInLocalStorage: []
}

describe("storeExperimentId", () => {
it("should store id", async () => {
const payload = '1234'
expect(reducer(initState, { type: 'storeExperimentId', payload })).toEqual({...initState, experimentsInLocalStorage: [payload]})
Expand All @@ -16,7 +16,19 @@ describe("storeExperimentId", () => {
const payload = '1234'
expect(
reducer({...initState, experimentsInLocalStorage: [payload]}, { type: 'storeExperimentId', payload }))
.toEqual({...initState, experimentsInLocalStorage: [payload]}
)
.toEqual({...initState, experimentsInLocalStorage: [payload]})
})
})

describe("deleteExperimentId", () => {
it("should delete id", async () => {
expect(
reducer({...initState, experimentsInLocalStorage: ['1234', '5678'] }, { type: 'deleteExperimentId', payload: '1234' }))
.toEqual({...initState, experimentsInLocalStorage: ['5678']})
})
it("should return empty array when no ids to delete", async () => {
expect(
reducer(initState, { type: 'deleteExperimentId', payload: '1234' }))
.toEqual(initState)
})
})
9 changes: 9 additions & 0 deletions reducers/global-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export type Action = {
type:'storeExperimentId'
payload: string
}
| {
type:'deleteExperimentId'
payload: string
}

export type Dispatch = (action: Action) => void

Expand All @@ -41,6 +45,11 @@ export const reducer = (state: State, action: Action) => {
} else {
return state
}
case 'deleteExperimentId':
let idsAfterDelete: string[] = state.experimentsInLocalStorage.slice()
let indexOfDelete = state.experimentsInLocalStorage.indexOf(action.payload)
idsAfterDelete.splice(indexOfDelete, 1)
return {...state, experimentsInLocalStorage: idsAfterDelete }
default:
return state
}
Expand Down
21 changes: 21 additions & 0 deletions reducers/home-reducer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { reducer, State } from "./home-reducer"

const initState: State = {
experimentsToDelete: []
}

describe("addExperimentForDeletion", () => {
it("should add experiment", async () => {
expect(
reducer(initState, { type: 'addExperimentForDeletion', payload: '1234' } ))
.toEqual({ ...initState, experimentsToDelete: ['1234'] })
})
})

describe("resetExperimentsForDeletion", () => {
it("should reset experiments", async () => {
expect(
reducer({ ...initState, experimentsToDelete: ['1234', '5678'] }, { type: 'resetExperimentsForDeletion' } ))
.toEqual({ ...initState })
})
})
25 changes: 25 additions & 0 deletions reducers/home-reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export type Action = {
type: 'addExperimentForDeletion'
payload: string
}
| {
type: 'resetExperimentsForDeletion'
}

export type State = {
experimentsToDelete: string[]
}

export const reducer = (state: State, action: Action) => {
switch (action.type) {
case 'addExperimentForDeletion':
const experimentsToDelete: string[] = state.experimentsToDelete
let experimentsAfterAdd: string[] = experimentsToDelete.slice()
experimentsAfterAdd.splice(experimentsToDelete.length, 0, action.payload)
return { ...state, experimentsToDelete: experimentsAfterAdd }
case 'resetExperimentsForDeletion':
return { ...state, experimentsToDelete: [] }
default:
return state
}
}