Bernt Christian Egeland vor 3 Jahren
Ursprung
Commit
85de881181
3 geänderte Dateien mit 49 neuen und 10 gelöschten Zeilen
  1. 36 1
      web/src/api/index.jsx
  2. 9 5
      web/src/routes/Event.jsx
  3. 4 4
      web/src/routes/Events.jsx

+ 36 - 1
web/src/api/index.jsx

@@ -34,7 +34,27 @@ function reducer(state, { type, payload, meta }) {
         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:
       return state;
   }
@@ -91,8 +111,23 @@ export function useFetch(url, fetchId) {
 
   const data = state.queries[url].data || null;
   const status = state.queries[url].status;
+  const deleted = state.queries[url].deleted || 0;
+
+  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 { data, status };
+  return deleteEvent;
 }
 
 export function useApiHost() {

+ 9 - 5
web/src/routes/Event.jsx

@@ -10,7 +10,7 @@ import Dialog from '../components/Dialog';
 import Heading from '../components/Heading';
 import Link from '../components/Link';
 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';
 
 export default function Event({ eventId }) {
@@ -18,6 +18,7 @@ export default function Event({ eventId }) {
   const { data, status } = useEvent(eventId);
   const [showDialog, setShowDialog] = useState(false);
   const [deleteStatus, setDeleteStatus] = useState(FetchStatus.NONE);
+  const setDeleteEvent = useDelete();
 
   const handleClickDelete = () => {
     setShowDialog(true);
@@ -30,8 +31,7 @@ export default function Event({ eventId }) {
   const handleClickDeleteDialog = useCallback(async () => {
     let success;
     try {
-      const response = await fetch(`${apiHost}/api/events/${eventId}`, { method: 'DELETE' });
-      success = await (response.status < 300 ? response.json() : { success: true });
+      success = await setDeleteEvent(eventId);
       setDeleteStatus(success ? FetchStatus.LOADED : FetchStatus.ERROR);
     } catch (e) {
       setDeleteStatus(FetchStatus.ERROR);
@@ -42,7 +42,7 @@ export default function Event({ eventId }) {
       setShowDialog(false);
       route('/events', true);
     }
-  }, [apiHost, eventId, setShowDialog]);
+  }, [eventId, setShowDialog]);
 
   if (status !== FetchStatus.LOADED) {
     return <ActivityIndicator />;
@@ -64,7 +64,11 @@ export default function Event({ eventId }) {
           <Dialog
             onDismiss={handleDismissDeleteDialog}
             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={[
               deleteStatus !== FetchStatus.LOADING
                 ? { text: 'Delete', color: 'red', onClick: handleClickDeleteDialog }

+ 4 - 4
web/src/routes/Events.jsx

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