diff --git a/web-old/src/icons/Cleanup.jsx b/web-old/src/icons/Cleanup.jsx new file mode 100644 index 000000000..b8e7a454f --- /dev/null +++ b/web-old/src/icons/Cleanup.jsx @@ -0,0 +1,26 @@ +import { h } from 'preact'; +import { memo } from 'preact/compat'; + +export function Cleanup({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', title = "", onClick = () => {} }) { + return ( + + ); +} + +export default memo(Cleanup); diff --git a/web-old/src/index.css b/web-old/src/index.css index f45694699..3999783d9 100644 --- a/web-old/src/index.css +++ b/web-old/src/index.css @@ -53,3 +53,33 @@ display: none; } } + +/* + loader used for cleanup operation progression +*/ +.loader { + width: 50px; + padding: 8px; + aspect-ratio: 1; + border-radius: 50%; + background: #fff; + --_m: + conic-gradient(#0000 10%,#000), + linear-gradient(#000 0 0) content-box; + -webkit-mask: var(--_m); + mask: var(--_m); + -webkit-mask-composite: source-out; + mask-composite: subtract; + animation: l3 1s infinite linear; + margin: auto; +} +@keyframes l3 {to{transform: rotate(1turn)}} + +.batch-actions { + border: 1px solid #fff; + border-radius: 6px; +} + +.batch-actions div { + padding: 3px; +} diff --git a/web-old/src/routes/Events.jsx b/web-old/src/routes/Events.jsx index 0777829a8..f91d90b71 100644 --- a/web-old/src/routes/Events.jsx +++ b/web-old/src/routes/Events.jsx @@ -17,6 +17,7 @@ import { UploadPlus } from '../icons/UploadPlus'; import { Clip } from '../icons/Clip'; import { Zone } from '../icons/Zone'; import { Camera } from '../icons/Camera'; +import { Cleanup } from '../icons/Cleanup'; import { Clock } from '../icons/Clock'; import { Delete } from '../icons/Delete'; import { Download } from '../icons/Download'; @@ -97,6 +98,14 @@ export default function Events({ path, ...props }) { showDeleteFavorite: false, }); + const [clearUnretainedState, setClearUnretainedState] = useState({ + deletableEventList: [], + favoriteCount: 0, + showConfirmation: false, + showProgress: false, + showFeedback: false, + }); + const [showInProgress, setShowInProgress] = useState((props.event || props.cameras || props.labels) == null); const eventsFetcher = useCallback( @@ -136,6 +145,7 @@ export default function Events({ path, ...props }) { isValidating, } = useSWRInfinite(getKey, eventsFetcher); const mutate = () => { + console.log("mutating refresh events"); refreshEvents(); refreshOngoingEvents(); }; @@ -293,6 +303,58 @@ export default function Events({ path, ...props }) { [path, searchParams, setSearchParams] ); + /** + * Invoked when the Cleanup button is clicked to batch delete all unsaved + * Events. + * + * Only iterates through the currently loaded Events in the eventPages array. + * This is to avoid user confusion that could result in deleting Events that + * are not displayed already. + * + * Blocks on loading the newest page if eventPages isn't loaded already. + * + */ + const onClearUnretained = useCallback( + async (e) => { + e.stopPropagation(); + console.log("clear unretained button clicked"); + if (!eventPages) { + // unless output like this, eventPages is undefined. Not sure, I don't know web + // console.log(eventPages); + // eventPages?.map((page, i) => { + // console.log("found ", page.length, " events in page ", i); + // }); + // } else { + console.log("refreshing events"); + // console.debug(eventPages); + // const refreshedEvents = eventsFetcher('events'. searchParams).then((res) => res.data); + // console.log("refreshed: ", refreshedEvents); + await mutate(['events', searchParams]); + } + + let favorites = []; + let deletables = []; + eventPages?.map((page, i) => { + for (let ev of page) { + if (ev.retain_indefinitely) { + favorites.push(ev.id); + } else { + console.log("adding deletable event id: ", ev.id); + deletables.push(ev.id); + } + } + // console.log("found ", page.length, " events in page ", i); + }); + + console.log("found ", favorites.length, " favorites"); + console.log("found ", deletables.length, " events to clear"); + // console.log("eventPages is undefined"); + + setClearUnretainedState({...state, deletableEventList: deletables, favoriteCount: favorites.length, showConfirmation: true, showFeedback: false, showProgress: false}); + }, + [eventPages, mutate, searchParams, setClearUnretainedState] + ); + const onClickFilterSubmitted = useCallback(() => { if (++searchParams.is_submitted > 1) { searchParams.is_submitted = -1; @@ -449,6 +511,18 @@ export default function Events({ path, ...props }) { onClick={() => setState({ ...state, showDatePicker: true })} /> + +