aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/src/pages/views/MassEditor.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/pages/views/MassEditor.tsx')
-rw-r--r--frontend/src/pages/views/MassEditor.tsx110
1 files changed, 82 insertions, 28 deletions
diff --git a/frontend/src/pages/views/MassEditor.tsx b/frontend/src/pages/views/MassEditor.tsx
index b15a55e83..48068d1c6 100644
--- a/frontend/src/pages/views/MassEditor.tsx
+++ b/frontend/src/pages/views/MassEditor.tsx
@@ -1,18 +1,17 @@
-import { useIsAnyMutationRunning, useLanguageProfiles } from "@/apis/hooks";
-import { SimpleTable, Toolbox } from "@/components";
-import { Selector, SelectorOption } from "@/components/inputs";
-import { useCustomSelection } from "@/components/tables/plugins";
-import { GetItemId, useSelectorOptions } from "@/utilities";
+import { useCallback, useMemo, useRef, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { Box, Container, useCombobox } from "@mantine/core";
import { faCheck, faUndo } from "@fortawesome/free-solid-svg-icons";
-import { Box, Container } from "@mantine/core";
+import { UseMutationResult } from "@tanstack/react-query";
+import { ColumnDef, Table } from "@tanstack/react-table";
import { uniqBy } from "lodash";
-import { useCallback, useMemo, useState } from "react";
-import { UseMutationResult } from "react-query";
-import { useNavigate } from "react-router-dom";
-import { Column, useRowSelect } from "react-table";
+import { useIsAnyMutationRunning, useLanguageProfiles } from "@/apis/hooks";
+import { GroupedSelector, GroupedSelectorOptions, Toolbox } from "@/components";
+import SimpleTable from "@/components/tables/SimpleTable";
+import { GetItemId, useSelectorOptions } from "@/utilities";
interface MassEditorProps<T extends Item.Base = Item.Base> {
- columns: Column<T>[];
+ columns: ColumnDef<T>[];
data: T[];
mutation: UseMutationResult<void, unknown, FormType.ModifyItem>;
}
@@ -24,6 +23,7 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
const [dirties, setDirties] = useState<T[]>([]);
const hasTask = useIsAnyMutationRunning();
const { data: profiles } = useLanguageProfiles();
+ const tableRef = useRef<Table<T>>(null);
const navigate = useNavigate();
@@ -37,14 +37,25 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
const profileOptions = useSelectorOptions(profiles ?? [], (v) => v.name);
const profileOptionsWithAction = useMemo<
- SelectorOption<Language.Profile | null>[]
- >(
- () => [
- { label: "Clear", value: null, group: "Action" },
- ...profileOptions.options,
- ],
- [profileOptions.options],
- );
+ GroupedSelectorOptions<string>[]
+ >(() => {
+ return [
+ {
+ group: "Actions",
+ items: [{ label: "Clear", value: "", profileId: null }],
+ },
+ {
+ group: "Profiles",
+ items: profileOptions.options.map((a) => {
+ return {
+ value: a.value.profileId.toString(),
+ label: a.label,
+ profileId: a.value.profileId,
+ };
+ }),
+ },
+ ];
+ }, [profileOptions.options]);
const getKey = useCallback((value: Language.Profile | null) => {
if (value) {
@@ -56,11 +67,20 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
const { mutateAsync } = mutation;
+ /**
+ * Submit the form that contains the series id and the respective profile id set in chunks to prevent payloads too
+ * large when we have a high amount of series or movies being applied the profile. The chunks are executed in order
+ * since there are no much benefit on executing in parallel, also parallelism could result in high load on the server
+ * side if not throttled properly.
+ */
const save = useCallback(() => {
+ const chunkSize = 1000;
+
const form: FormType.ModifyItem = {
id: [],
profileid: [],
};
+
dirties.forEach((v) => {
const id = GetItemId(v);
if (id) {
@@ -68,32 +88,63 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
form.profileid.push(v.profileId);
}
});
- return mutateAsync(form);
+
+ const mutateInChunks = async (
+ ids: number[],
+ profileIds: (number | null)[],
+ ) => {
+ if (ids.length === 0) return;
+
+ const chunkIds = ids.slice(0, chunkSize);
+ const chunkProfileIds = profileIds.slice(0, chunkSize);
+
+ await mutateAsync({
+ id: chunkIds,
+ profileid: chunkProfileIds,
+ });
+
+ await mutateInChunks(ids.slice(chunkSize), profileIds.slice(chunkSize));
+ };
+
+ return mutateInChunks(form.id, form.profileid);
}, [dirties, mutateAsync]);
const setProfiles = useCallback(
- (profile: Language.Profile | null) => {
- const id = profile?.profileId ?? null;
+ (id: number | null) => {
const newItems = selections.map((v) => ({ ...v, profileId: id }));
setDirties((dirty) => {
return uniqBy([...newItems, ...dirty], GetItemId);
});
+
+ tableRef.current?.toggleAllRowsSelected(false);
},
[selections],
);
+
+ const combobox = useCombobox();
+
return (
<Container fluid px={0}>
<Toolbox>
<Box>
- <Selector
- allowDeselect
+ <GroupedSelector
+ onClick={() => combobox.openDropdown()}
+ onDropdownClose={() => {
+ combobox.resetSelectedOption();
+ }}
placeholder="Change Profile"
+ withCheckIcon={false}
options={profileOptionsWithAction}
getkey={getKey}
disabled={selections.length === 0}
- onChange={setProfiles}
- ></Selector>
+ comboboxProps={{
+ store: combobox,
+ onOptionSubmit: (value) => {
+ setProfiles(value ? +value : null);
+ },
+ }}
+ ></GroupedSelector>
</Box>
<Box>
<Toolbox.Button icon={faUndo} onClick={onEnded}>
@@ -110,10 +161,13 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
</Box>
</Toolbox>
<SimpleTable
+ instanceRef={tableRef}
columns={columns}
data={data}
- onSelect={setSelections}
- plugins={[useRowSelect, useCustomSelection]}
+ enableRowSelection
+ onRowSelectionChanged={(row) => {
+ setSelections(row.map((r) => r.original));
+ }}
></SimpleTable>
</Container>
);