frigate/web/src/api/index.jsx

162 lines
4.8 KiB
React
Raw Normal View History

2021-02-11 18:13:21 +03:00
import { baseUrl } from './baseUrl';
2021-01-26 18:04:03 +03:00
import { h, createContext } from 'preact';
2021-02-16 07:10:20 +03:00
import { MqttProvider } from './mqtt';
2021-01-26 18:04:03 +03:00
import produce from 'immer';
import { useContext, useEffect, useReducer } from 'preact/hooks';
2021-01-26 18:04:03 +03:00
export const FetchStatus = {
NONE: 'none',
LOADING: 'loading',
LOADED: 'loaded',
ERROR: 'error',
};
const initialState = Object.freeze({
2021-02-11 18:13:21 +03:00
host: baseUrl,
2021-01-26 18:04:03 +03:00
queries: {},
});
2021-02-11 18:13:21 +03:00
const Api = createContext(initialState);
2021-01-26 18:04:03 +03:00
function reducer(state, { type, payload, meta }) {
switch (type) {
2021-02-11 18:16:35 +03:00
case 'REQUEST': {
const { url, fetchId } = payload;
const data = state.queries[url]?.data || null;
return produce(state, (draftState) => {
draftState.queries[url] = { status: FetchStatus.LOADING, data, fetchId };
});
}
2021-01-26 18:04:03 +03:00
2021-02-11 18:16:35 +03:00
case 'RESPONSE': {
const { url, ok, data, fetchId } = payload;
return produce(state, (draftState) => {
draftState.queries[url] = { status: ok ? FetchStatus.LOADED : FetchStatus.ERROR, data, fetchId };
});
}
2021-07-03 13:55:56 +03:00
case 'DELETE': {
const { eventId } = payload;
2021-01-26 18:04:03 +03:00
2021-07-03 13:55:56 +03:00
return produce(state, (draftState) => {
2021-07-04 12:51:31 +03:00
Object.keys(draftState.queries).map((url, index) => {
// If data has no array length then just return state.
if (!('data' in draftState.queries[url]) || !draftState.queries[url].data.length) return state;
2021-07-03 13:55:56 +03:00
//Find the index to remove
const removeIndex = draftState.queries[url].data.map((event) => event.id).indexOf(eventId);
2021-07-04 12:51:31 +03:00
if (removeIndex === -1) return state;
2021-07-03 13:55:56 +03:00
2021-07-04 12:51:31 +03:00
// We need to keep track of deleted items, This will be used to re-calculate "ReachEnd" for auto load new events. Events.jsx
2021-07-03 13:55:56 +03:00
const totDeleted = state.queries[url].deleted || 0;
// Splice the deleted index.
draftState.queries[url].data.splice(removeIndex, 1);
draftState.queries[url].deleted = totDeleted + 1;
});
});
}
2021-02-11 18:16:35 +03:00
default:
return state;
2021-01-26 18:04:03 +03:00
}
}
export function ApiProvider({ children }) {
2021-01-26 18:04:03 +03:00
const [state, dispatch] = useReducer(reducer, initialState);
2021-02-16 07:10:20 +03:00
return (
<Api.Provider value={{ state, dispatch }}>
<MqttWithConfig>{children}</MqttWithConfig>
2021-02-16 07:10:20 +03:00
</Api.Provider>
);
}
function MqttWithConfig({ children }) {
const { data, status } = useConfig();
return status === FetchStatus.LOADED ? <MqttProvider config={data}>{children}</MqttProvider> : children;
}
2021-01-26 18:04:03 +03:00
function shouldFetch(state, url, fetchId = null) {
if ((fetchId && url in state.queries && state.queries[url].fetchId !== fetchId) || !(url in state.queries)) {
2021-01-26 18:04:03 +03:00
return true;
}
const { status } = state.queries[url];
return status !== FetchStatus.LOADING && status !== FetchStatus.LOADED;
}
export function useFetch(url, fetchId) {
2021-01-26 18:04:03 +03:00
const { state, dispatch } = useContext(Api);
useEffect(() => {
if (!shouldFetch(state, url, fetchId)) {
2021-01-26 18:04:03 +03:00
return;
}
async function fetchData() {
await dispatch({ type: 'REQUEST', payload: { url, fetchId } });
2021-01-26 18:04:03 +03:00
const response = await fetch(`${state.host}${url}`);
2021-02-11 18:13:21 +03:00
try {
const data = await response.json();
await dispatch({ type: 'RESPONSE', payload: { url, ok: response.ok, data, fetchId } });
} catch (e) {
await dispatch({ type: 'RESPONSE', payload: { url, ok: false, data: null, fetchId } });
}
2021-01-26 18:04:03 +03:00
}
fetchData();
}, [url, fetchId, state, dispatch]);
2021-01-26 18:04:03 +03:00
if (!(url in state.queries)) {
return { data: null, status: FetchStatus.NONE };
}
const data = state.queries[url].data || null;
const status = state.queries[url].status;
2021-07-03 13:55:56 +03:00
const deleted = state.queries[url].deleted || 0;
return { data, status, deleted };
}
export function useDelete() {
const { dispatch, state } = useContext(Api);
async function deleteEvent(eventId) {
2021-07-04 12:51:31 +03:00
if (!eventId) return null;
2021-07-03 13:55:56 +03:00
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 });
}
2021-01-26 18:04:03 +03:00
2021-07-03 13:55:56 +03:00
return deleteEvent;
2021-01-26 18:04:03 +03:00
}
export function useApiHost() {
const { state } = useContext(Api);
2021-01-26 18:04:03 +03:00
return state.host;
}
export function useEvents(searchParams, fetchId) {
2021-01-26 18:04:03 +03:00
const url = `/api/events${searchParams ? `?${searchParams.toString()}` : ''}`;
return useFetch(url, fetchId);
2021-01-26 18:04:03 +03:00
}
export function useEvent(eventId, fetchId) {
2021-01-26 18:04:03 +03:00
const url = `/api/events/${eventId}`;
return useFetch(url, fetchId);
2021-01-26 18:04:03 +03:00
}
2021-05-28 20:13:48 +03:00
export function useRecording(camera, fetchId) {
const url = `/api/${camera}/recordings`;
return useFetch(url, fetchId);
}
export function useConfig(searchParams, fetchId) {
2021-01-26 18:04:03 +03:00
const url = `/api/config${searchParams ? `?${searchParams.toString()}` : ''}`;
return useFetch(url, fetchId);
2021-01-26 18:04:03 +03:00
}
export function useStats(searchParams, fetchId) {
2021-01-26 18:04:03 +03:00
const url = `/api/stats${searchParams ? `?${searchParams.toString()}` : ''}`;
return useFetch(url, fetchId);
2021-01-26 18:04:03 +03:00
}