From 1f3e499f3db42d15436be604934770ed21e62548 Mon Sep 17 00:00:00 2001 From: LASER-Yi Date: Mon, 23 Aug 2021 11:35:04 +0800 Subject: [PATCH] Improve performance of Web UI --- frontend/src/@modules/task/index.ts | 4 +- frontend/src/DisplayItem/Episodes/index.tsx | 3 +- .../src/DisplayItem/MovieDetail/index.tsx | 2 +- .../generic/BaseItemView/index.tsx | 11 ++--- frontend/src/History/Statistics/index.tsx | 45 ++++++++++--------- frontend/src/apis/hooks.ts | 19 ++++---- .../src/components/ContentHeader/Button.tsx | 10 +++-- frontend/src/components/async.tsx | 8 ++-- frontend/src/components/inputs/Chips.tsx | 33 ++++++++------ .../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) => { 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 = ({ match }) => { { 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({ 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({ setSelections([]); }, []); - const saveItems = useCallback(() => { + const save = useCallback(() => { const form: FormType.ModifyItem = { id: [], profileid: [], @@ -140,7 +141,7 @@ function BaseItemView({ 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>(null); const [provider, setProvider] = useState>(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 = () => { History Statistics - Bazarr - - {(data) => ( + + {({ content }) => ( @@ -121,14 +122,14 @@ const HistoryStats: FunctionComponent = () => { - + @@ -140,7 +141,7 @@ const HistoryStats: FunctionComponent = () => { )} - + ); }; 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; type Return = PromiseType>; export function useAsyncRequest( - request: F, - initial: Return -): [Async.Base>, (...args: Parameters) => void] { - const [state, setState] = useState>>({ + request: F +): [Async.Item>, (...args: Parameters) => void] { + const [state, setState] = useState>>({ state: "uninitialized", - content: initial, + content: null, error: null, }); + + const requestRef = useRef(request); + const update = useCallback( (...args: Parameters) => { 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 Promise>( 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 ( ({ promise, children }: PromiseProps) { } } -type AsyncSelectorProps> = { +type AsyncSelectorProps> = { state: T; update: () => void; label: (item: V) => string; @@ -71,17 +71,17 @@ type RemovedSelectorProps = Omit< export function AsyncSelector< V, - T extends Async.Base, + T extends Async.Item, M extends boolean = false >(props: Override, RemovedSelectorProps>) { const { label, state, update, ...selector } = props; const options = useMemo[]>( () => - 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 = ({ const input = useRef(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 = (props) => { const movie = useModalPayload(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 = (props) => { )} @@ -114,8 +113,7 @@ export const EpisodeHistoryModal: FunctionComponent< const episode = useModalPayload(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< )}