Improve performance of Web UI

pull/1515/head
LASER-Yi 3 years ago
parent 5ceb876171
commit 1f3e499f3d

@ -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]));

@ -124,8 +124,7 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
disabled={
serie.episodeFileCount === 0 ||
serie.profileId === null ||
!available ||
hasTask
!available
}
>
Search

@ -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,

@ -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

@ -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>
);
};

@ -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];

@ -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

@ -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]
);

@ -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(() => {

@ -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>

Loading…
Cancel
Save