summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLASER-Yi <[email protected]>2021-08-23 11:35:04 +0800
committerLASER-Yi <[email protected]>2021-08-23 11:35:04 +0800
commit1f3e499f3db42d15436be604934770ed21e62548 (patch)
treedaeb8a22824a57e744bf9fc109cb178fa14ffa4f
parent5ceb876171e14f8d5b9d85a21a394710f81fc76d (diff)
downloadbazarr-1f3e499f3db42d15436be604934770ed21e62548.tar.gz
bazarr-1f3e499f3db42d15436be604934770ed21e62548.zip
Improve performance of Web UI
-rw-r--r--frontend/src/@modules/task/index.ts4
-rw-r--r--frontend/src/DisplayItem/Episodes/index.tsx3
-rw-r--r--frontend/src/DisplayItem/MovieDetail/index.tsx2
-rw-r--r--frontend/src/DisplayItem/generic/BaseItemView/index.tsx11
-rw-r--r--frontend/src/History/Statistics/index.tsx45
-rw-r--r--frontend/src/apis/hooks.ts19
-rw-r--r--frontend/src/components/ContentHeader/Button.tsx10
-rw-r--r--frontend/src/components/async.tsx8
-rw-r--r--frontend/src/components/inputs/Chips.tsx33
-rw-r--r--frontend/src/components/modals/HistoryModal.tsx10
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>