diff options
-rw-r--r-- | frontend/src/@modules/task/index.ts | 4 | ||||
-rw-r--r-- | frontend/src/DisplayItem/Episodes/index.tsx | 3 | ||||
-rw-r--r-- | frontend/src/DisplayItem/MovieDetail/index.tsx | 2 | ||||
-rw-r--r-- | frontend/src/DisplayItem/generic/BaseItemView/index.tsx | 11 | ||||
-rw-r--r-- | frontend/src/History/Statistics/index.tsx | 45 | ||||
-rw-r--r-- | frontend/src/apis/hooks.ts | 19 | ||||
-rw-r--r-- | frontend/src/components/ContentHeader/Button.tsx | 10 | ||||
-rw-r--r-- | frontend/src/components/async.tsx | 8 | ||||
-rw-r--r-- | frontend/src/components/inputs/Chips.tsx | 33 | ||||
-rw-r--r-- | frontend/src/components/modals/HistoryModal.tsx | 10 |
10 files changed, 80 insertions, 65 deletions
diff --git a/frontend/src/@modules/task/index.ts b/frontend/src/@modules/task/index.ts index 72c0ec8e0..aee555b0d 100644 --- a/frontend/src/@modules/task/index.ts +++ b/frontend/src/@modules/task/index.ts @@ -44,7 +44,9 @@ class BackgroundTask { ); try { await task.callable(...task.parameters); - } catch (error) {} + } catch (error) { + // TODO + } } delete this.groups[groupName]; store.dispatch(siteRemoveProgress([groupName])); diff --git a/frontend/src/DisplayItem/Episodes/index.tsx b/frontend/src/DisplayItem/Episodes/index.tsx index 8ac31e6c2..bda4f7cdc 100644 --- a/frontend/src/DisplayItem/Episodes/index.tsx +++ b/frontend/src/DisplayItem/Episodes/index.tsx @@ -124,8 +124,7 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => { disabled={ serie.episodeFileCount === 0 || serie.profileId === null || - !available || - hasTask + !available } > Search diff --git a/frontend/src/DisplayItem/MovieDetail/index.tsx b/frontend/src/DisplayItem/MovieDetail/index.tsx index a375953e7..9faff4fa3 100644 --- a/frontend/src/DisplayItem/MovieDetail/index.tsx +++ b/frontend/src/DisplayItem/MovieDetail/index.tsx @@ -101,7 +101,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => { </ContentHeader.Button> <ContentHeader.Button icon={faSearch} - disabled={item.profileId === null || hasTask} + disabled={item.profileId === null} onClick={() => { const task = createTask( item.title, diff --git a/frontend/src/DisplayItem/generic/BaseItemView/index.tsx b/frontend/src/DisplayItem/generic/BaseItemView/index.tsx index 4925089a6..330295cc5 100644 --- a/frontend/src/DisplayItem/generic/BaseItemView/index.tsx +++ b/frontend/src/DisplayItem/generic/BaseItemView/index.tsx @@ -77,10 +77,11 @@ function BaseItemView<T extends Item.Base>({ item.profileId = id; return item; }); - const newDirty = uniqBy([...newItems, ...dirtyItems], GetItemId); - setDirty(newDirty); + setDirty((dirty) => { + return uniqBy([...newItems, ...dirty], GetItemId); + }); }, - [selections, dirtyItems] + [selections] ); const startEdit = useCallback(() => { @@ -99,7 +100,7 @@ function BaseItemView<T extends Item.Base>({ setSelections([]); }, []); - const saveItems = useCallback(() => { + const save = useCallback(() => { const form: FormType.ModifyItem = { id: [], profileid: [], @@ -140,7 +141,7 @@ function BaseItemView<T extends Item.Base>({ <ContentHeader.AsyncButton icon={faCheck} disabled={dirtyItems.length === 0 || hasTask} - promise={saveItems} + promise={save} onSuccess={endEdit} > Save diff --git a/frontend/src/History/Statistics/index.tsx b/frontend/src/History/Statistics/index.tsx index f3444f6cd..a8474ad6b 100644 --- a/frontend/src/History/Statistics/index.tsx +++ b/frontend/src/History/Statistics/index.tsx @@ -1,5 +1,10 @@ import { merge } from "lodash"; -import React, { FunctionComponent, useCallback, useState } from "react"; +import React, { + FunctionComponent, + useCallback, + useEffect, + useState, +} from "react"; import { Col, Container } from "react-bootstrap"; import { Helmet } from "react-helmet"; import { @@ -20,10 +25,10 @@ import { useAsyncRequest, } from "../../apis"; import { + AsyncOverlay, AsyncSelector, ContentHeader, LanguageSelector, - PromiseOverlay, Selector, } from "../../components"; import { actionOptions, timeframeOptions } from "./options"; @@ -51,17 +56,16 @@ const SelectorContainer: FunctionComponent = ({ children }) => ( const HistoryStats: FunctionComponent = () => { const [languages, updateLanguages] = useAsyncRequest( - SystemApi.languages.bind(SystemApi), - [] + SystemApi.languages.bind(SystemApi) ); const [providerList, updateProviderParam] = useAsyncRequest( - ProvidersApi.providers.bind(ProvidersApi), - [] + ProvidersApi.providers.bind(ProvidersApi) ); - const updateProvider = useCallback(() => updateProviderParam(true), [ - updateProviderParam, - ]); + const updateProvider = useCallback( + () => updateProviderParam(true), + [updateProviderParam] + ); useDidMount(() => { updateLanguages(true); @@ -72,14 +76,11 @@ const HistoryStats: FunctionComponent = () => { const [lang, setLanguage] = useState<Nullable<Language.Info>>(null); const [provider, setProvider] = useState<Nullable<System.Provider>>(null); - const promise = useCallback(() => { - return HistoryApi.stats( - timeframe, - action ?? undefined, - provider?.name, - lang?.code2 - ); - }, [timeframe, lang?.code2, action, provider]); + const [stats, update] = useAsyncRequest(HistoryApi.stats.bind(HistoryApi)); + + useEffect(() => { + update(timeframe, action ?? undefined, provider?.name, lang?.code2); + }, [timeframe, action, provider?.name, lang?.code2, update]); return ( // TODO: Responsive @@ -87,8 +88,8 @@ const HistoryStats: FunctionComponent = () => { <Helmet> <title>History Statistics - Bazarr</title> </Helmet> - <PromiseOverlay promise={promise}> - {(data) => ( + <AsyncOverlay ctx={stats}> + {({ content }) => ( <React.Fragment> <ContentHeader scroll={false}> <SelectorContainer> @@ -121,14 +122,14 @@ const HistoryStats: FunctionComponent = () => { <SelectorContainer> <LanguageSelector clearable - options={languages.content} + options={languages.content ?? []} value={lang} onChange={setLanguage} ></LanguageSelector> </SelectorContainer> </ContentHeader> <ResponsiveContainer height="100%"> - <BarChart data={converter(data)}> + <BarChart data={content ? converter(content) : []}> <CartesianGrid strokeDasharray="4 2"></CartesianGrid> <XAxis dataKey="date"></XAxis> <YAxis allowDecimals={false}></YAxis> @@ -140,7 +141,7 @@ const HistoryStats: FunctionComponent = () => { </ResponsiveContainer> </React.Fragment> )} - </PromiseOverlay> + </AsyncOverlay> </Container> ); }; diff --git a/frontend/src/apis/hooks.ts b/frontend/src/apis/hooks.ts index 8b6a17788..084efb63f 100644 --- a/frontend/src/apis/hooks.ts +++ b/frontend/src/apis/hooks.ts @@ -1,27 +1,30 @@ -import { useCallback, useState } from "react"; +import { useCallback, useRef, useState } from "react"; type Request = (...args: any[]) => Promise<any>; type Return<T extends Request> = PromiseType<ReturnType<T>>; export function useAsyncRequest<F extends Request>( - request: F, - initial: Return<F> -): [Async.Base<Return<F>>, (...args: Parameters<F>) => void] { - const [state, setState] = useState<Async.Base<Return<F>>>({ + request: F +): [Async.Item<Return<F>>, (...args: Parameters<F>) => void] { + const [state, setState] = useState<Async.Item<Return<F>>>({ state: "uninitialized", - content: initial, + content: null, error: null, }); + + const requestRef = useRef(request); + const update = useCallback( (...args: Parameters<F>) => { setState((s) => ({ ...s, state: "loading" })); - request(...args) + requestRef + .current(...args) .then((res) => setState({ state: "succeeded", content: res, error: null }) ) .catch((error) => setState((s) => ({ ...s, state: "failed", error }))); }, - [request] + [requestRef] ); return [state, update]; diff --git a/frontend/src/components/ContentHeader/Button.tsx b/frontend/src/components/ContentHeader/Button.tsx index fa0480689..7036de021 100644 --- a/frontend/src/components/ContentHeader/Button.tsx +++ b/frontend/src/components/ContentHeader/Button.tsx @@ -6,6 +6,7 @@ import React, { MouseEvent, PropsWithChildren, useCallback, + useRef, useState, } from "react"; import { Button } from "react-bootstrap"; @@ -58,13 +59,16 @@ export function ContentHeaderAsyncButton<T extends () => Promise<any>>( const [updating, setUpdate] = useState(false); + const promiseRef = useRef(promise); + const successRef = useRef(onSuccess); + const click = useCallback(() => { setUpdate(true); - promise().then((val) => { + promiseRef.current().then((val) => { setUpdate(false); - onSuccess && onSuccess(val); + successRef.current && successRef.current(val); }); - }, [onSuccess, promise]); + }, [successRef, promiseRef]); return ( <ContentHeaderButton diff --git a/frontend/src/components/async.tsx b/frontend/src/components/async.tsx index 79a3b4f5c..12b87fcf0 100644 --- a/frontend/src/components/async.tsx +++ b/frontend/src/components/async.tsx @@ -58,7 +58,7 @@ export function PromiseOverlay<T>({ promise, children }: PromiseProps<T>) { } } -type AsyncSelectorProps<V, T extends Async.Base<V[]>> = { +type AsyncSelectorProps<V, T extends Async.Item<V[]>> = { state: T; update: () => void; label: (item: V) => string; @@ -71,17 +71,17 @@ type RemovedSelectorProps<T, M extends boolean> = Omit< export function AsyncSelector< V, - T extends Async.Base<V[]>, + T extends Async.Item<V[]>, M extends boolean = false >(props: Override<AsyncSelectorProps<V, T>, RemovedSelectorProps<V, M>>) { const { label, state, update, ...selector } = props; const options = useMemo<SelectorOption<V>[]>( () => - state.content.map((v) => ({ + state.content?.map((v) => ({ label: label(v), value: v, - })), + })) ?? [], [state, label] ); diff --git a/frontend/src/components/inputs/Chips.tsx b/frontend/src/components/inputs/Chips.tsx index fc9231f7a..862d020c4 100644 --- a/frontend/src/components/inputs/Chips.tsx +++ b/frontend/src/components/inputs/Chips.tsx @@ -26,27 +26,34 @@ export const Chips: FunctionComponent<ChipsProps> = ({ const input = useRef<HTMLInputElement>(null); + const changeRef = useRef(onChange); + const addChip = useCallback( (value: string) => { - const newChips = [...chips]; - newChips.push(value); - setChips(newChips); - onChange && onChange(newChips); + setChips((cp) => { + const newChips = [...cp, value]; + changeRef.current && changeRef.current(newChips); + return newChips; + }); }, - [chips, onChange] + [changeRef] ); const removeChip = useCallback( (idx?: number) => { - idx = idx ?? chips.length - 1; - if (idx !== -1) { - const newChips = [...chips]; - newChips.splice(idx, 1); - setChips(newChips); - onChange && onChange(newChips); - } + setChips((cp) => { + const index = idx ?? cp.length - 1; + if (index !== -1) { + const newChips = [...cp]; + newChips.splice(index, 1); + changeRef.current && changeRef.current(newChips); + return newChips; + } else { + return cp; + } + }); }, - [chips, onChange] + [changeRef] ); const clearInput = useCallback(() => { diff --git a/frontend/src/components/modals/HistoryModal.tsx b/frontend/src/components/modals/HistoryModal.tsx index 04a35b980..6a95547f3 100644 --- a/frontend/src/components/modals/HistoryModal.tsx +++ b/frontend/src/components/modals/HistoryModal.tsx @@ -14,8 +14,7 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => { const movie = useModalPayload<Item.Movie>(modal.modalKey); const [history, updateHistory] = useAsyncRequest( - MoviesApi.historyBy.bind(MoviesApi), - { data: [], total: 0 } + MoviesApi.historyBy.bind(MoviesApi) ); const update = useCallback(() => { @@ -98,7 +97,7 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => { <PageTable emptyText="No History Found" columns={columns} - data={content.data} + data={content?.data ?? []} ></PageTable> )} </AsyncOverlay> @@ -114,8 +113,7 @@ export const EpisodeHistoryModal: FunctionComponent< const episode = useModalPayload<Item.Episode>(props.modalKey); const [history, updateHistory] = useAsyncRequest( - EpisodesApi.historyBy.bind(EpisodesApi), - { data: [], total: 0 } + EpisodesApi.historyBy.bind(EpisodesApi) ); const update = useCallback(() => { @@ -199,7 +197,7 @@ export const EpisodeHistoryModal: FunctionComponent< <PageTable emptyText="No History Found" columns={columns} - data={content.data} + data={content?.data ?? []} ></PageTable> )} </AsyncOverlay> |