mirror of https://github.com/morpheus65535/bazarr
Improve performance of Web UI
This commit is contained in:
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…
Reference in New Issue