This commit is contained in:
Bernt Christian Egeland 2021-07-03 12:55:56 +02:00 committed by Blake Blackshear
parent 3712a8ab80
commit 85de881181
3 changed files with 49 additions and 10 deletions

View File

@ -34,7 +34,27 @@ function reducer(state, { type, payload, meta }) {
draftState.queries[url] = { status: ok ? FetchStatus.LOADED : FetchStatus.ERROR, data, fetchId }; draftState.queries[url] = { status: ok ? FetchStatus.LOADED : FetchStatus.ERROR, data, fetchId };
}); });
} }
case 'DELETE': {
const { eventId } = payload;
return produce(state, (draftState) => {
Object.keys(draftState.queries).map(function (url, index) {
// If no url or data has no array length then just return state.
if (!(url in draftState.queries) || !draftState.queries[url].data.length) return state;
//Find the index to remove
const removeIndex = draftState.queries[url].data.map((event) => event.id).indexOf(eventId);
if (removeIndex === -1) return;
// We need to keep track of deleted items, This will be used to calculate "ReachEnd" for auto load new events. Events.jsx
const totDeleted = state.queries[url].deleted || 0;
// Splice the deleted index.
draftState.queries[url].data.splice(removeIndex, 1);
draftState.queries[url].deleted = totDeleted + 1;
});
});
}
default: default:
return state; return state;
} }
@ -91,8 +111,23 @@ export function useFetch(url, fetchId) {
const data = state.queries[url].data || null; const data = state.queries[url].data || null;
const status = state.queries[url].status; const status = state.queries[url].status;
const deleted = state.queries[url].deleted || 0;
return { data, status }; return { data, status, deleted };
}
export function useDelete() {
const { dispatch, state } = useContext(Api);
async function deleteEvent(eventId) {
if (!eventId) return { success: false };
const response = await fetch(`${state.host}/api/events/${eventId}`, { method: 'DELETE' });
await dispatch({ type: 'DELETE', payload: { eventId } });
return await (response.status < 300 ? response.json() : { success: true });
}
return deleteEvent;
} }
export function useApiHost() { export function useApiHost() {

View File

@ -10,7 +10,7 @@ import Dialog from '../components/Dialog';
import Heading from '../components/Heading'; import Heading from '../components/Heading';
import Link from '../components/Link'; import Link from '../components/Link';
import VideoPlayer from '../components/VideoPlayer'; import VideoPlayer from '../components/VideoPlayer';
import { FetchStatus, useApiHost, useEvent } from '../api'; import { FetchStatus, useApiHost, useEvent, useDelete } from '../api';
import { Table, Thead, Tbody, Th, Tr, Td } from '../components/Table'; import { Table, Thead, Tbody, Th, Tr, Td } from '../components/Table';
export default function Event({ eventId }) { export default function Event({ eventId }) {
@ -18,6 +18,7 @@ export default function Event({ eventId }) {
const { data, status } = useEvent(eventId); const { data, status } = useEvent(eventId);
const [showDialog, setShowDialog] = useState(false); const [showDialog, setShowDialog] = useState(false);
const [deleteStatus, setDeleteStatus] = useState(FetchStatus.NONE); const [deleteStatus, setDeleteStatus] = useState(FetchStatus.NONE);
const setDeleteEvent = useDelete();
const handleClickDelete = () => { const handleClickDelete = () => {
setShowDialog(true); setShowDialog(true);
@ -30,8 +31,7 @@ export default function Event({ eventId }) {
const handleClickDeleteDialog = useCallback(async () => { const handleClickDeleteDialog = useCallback(async () => {
let success; let success;
try { try {
const response = await fetch(`${apiHost}/api/events/${eventId}`, { method: 'DELETE' }); success = await setDeleteEvent(eventId);
success = await (response.status < 300 ? response.json() : { success: true });
setDeleteStatus(success ? FetchStatus.LOADED : FetchStatus.ERROR); setDeleteStatus(success ? FetchStatus.LOADED : FetchStatus.ERROR);
} catch (e) { } catch (e) {
setDeleteStatus(FetchStatus.ERROR); setDeleteStatus(FetchStatus.ERROR);
@ -42,7 +42,7 @@ export default function Event({ eventId }) {
setShowDialog(false); setShowDialog(false);
route('/events', true); route('/events', true);
} }
}, [apiHost, eventId, setShowDialog]); }, [eventId, setShowDialog]);
if (status !== FetchStatus.LOADED) { if (status !== FetchStatus.LOADED) {
return <ActivityIndicator />; return <ActivityIndicator />;
@ -64,7 +64,11 @@ export default function Event({ eventId }) {
<Dialog <Dialog
onDismiss={handleDismissDeleteDialog} onDismiss={handleDismissDeleteDialog}
title="Delete Event?" title="Delete Event?"
text="This event will be permanently deleted along with any related clips and snapshots" text={
deleteStatus === FetchStatus.ERROR
? 'Could not delete event, please try again.'
: 'This event will be permanently deleted along with any related clips and snapshots'
}
actions={[ actions={[
deleteStatus !== FetchStatus.LOADING deleteStatus !== FetchStatus.LOADING
? { text: 'Delete', color: 'red', onClick: handleClickDeleteDialog } ? { text: 'Delete', color: 'red', onClick: handleClickDeleteDialog }

View File

@ -20,6 +20,7 @@ const reducer = (state = initialState, action) => {
meta: { searchString }, meta: { searchString },
payload, payload,
} = action; } = action;
return produce(state, (draftState) => { return produce(state, (draftState) => {
draftState.searchStrings[searchString] = true; draftState.searchStrings[searchString] = true;
draftState.events.push(...payload); draftState.events.push(...payload);
@ -56,17 +57,17 @@ export default function Events({ path: pathname, limit = API_LIMIT } = {}) {
const [{ events, reachedEnd, searchStrings }, dispatch] = useReducer(reducer, initialState); const [{ events, reachedEnd, searchStrings }, dispatch] = useReducer(reducer, initialState);
const { searchParams: initialSearchParams } = new URL(window.location); const { searchParams: initialSearchParams } = new URL(window.location);
const [searchString, setSearchString] = useState(`${defaultSearchString(limit)}&${initialSearchParams.toString()}`); const [searchString, setSearchString] = useState(`${defaultSearchString(limit)}&${initialSearchParams.toString()}`);
const { data, status } = useEvents(searchString); const { data, status, deleted } = useEvents(searchString);
useEffect(() => { useEffect(() => {
if (data && !(searchString in searchStrings)) { if (data && !(searchString in searchStrings)) {
dispatch({ type: 'APPEND_EVENTS', payload: data, meta: { searchString } }); dispatch({ type: 'APPEND_EVENTS', payload: data, meta: { searchString } });
} }
if (data && Array.isArray(data) && data.length < limit) { if (data && Array.isArray(data) && data.length + deleted < limit) {
dispatch({ type: 'REACHED_END', meta: { searchString } }); dispatch({ type: 'REACHED_END', meta: { searchString } });
} }
}, [data, limit, searchString, searchStrings]); }, [data, limit, searchString, searchStrings, deleted]);
const [entry, setIntersectNode] = useIntersectionObserver(); const [entry, setIntersectNode] = useIntersectionObserver();
@ -100,7 +101,6 @@ export default function Events({ path: pathname, limit = API_LIMIT } = {}) {
); );
const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]); const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]);
return ( return (
<div className="space-y-4 w-full"> <div className="space-y-4 w-full">
<Heading>Events</Heading> <Heading>Events</Heading>