Use gql for "mangas" VI - source mangas filter

This commit is contained in:
schroda
2023-09-24 21:32:23 +02:00
parent 3297c7d96c
commit 12dd973179
10 changed files with 113 additions and 215 deletions

View File

@@ -10,7 +10,7 @@ import FilterListIcon from '@mui/icons-material/FilterList';
import { Button, Stack, Box } from '@mui/material';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ISourceFilters, IState } from '@/typings';
import { SourceFilters } from '@/typings';
import OptionsPanel from '@/components/molecules/OptionsPanel';
import CheckBoxFilter from '@/components/source/filters/CheckBoxFilter';
import HeaderFilter from '@/components/source/filters/HeaderFilter';
@@ -25,14 +25,14 @@ import SeperatorFilter from '@/components/source/filters/SeparatorFilter';
import StyledFab from '@/components/util/StyledFab';
interface IFilters {
sourceFilter: ISourceFilters[];
sourceFilter: SourceFilters[];
updateFilterValue: Function;
group: number | undefined;
update: any;
}
interface IFilters1 {
sourceFilter: ISourceFilters[];
sourceFilter: SourceFilters[];
updateFilterValue: Function;
resetFilterValue: Function;
setTriggerUpdate: Function;
@@ -42,85 +42,89 @@ interface IFilters1 {
export function Options({ sourceFilter, group, updateFilterValue, update }: IFilters) {
return (
<Stack key={`filters ${group}`}>
{sourceFilter.map((e: ISourceFilters, index) => {
{sourceFilter.map((e, index) => {
let checkif = update.find(
(el: { group: number | undefined; position: number }) =>
el.group === group && el.position === index,
);
checkif = checkif ? checkif.state : checkif;
switch (e.type) {
case 'CheckBox':
case 'CheckBoxFilter':
return (
<CheckBoxFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={checkif != null ? checkif === 'true' : (e.filter.state as boolean)}
key={`filters ${e.name}`}
name={e.name}
state={checkif ?? e.CheckBoxFilterDefault}
position={index}
group={group}
updateFilterValue={updateFilterValue}
update={update}
/>
);
case 'Group':
case 'GroupFilter':
return (
<GroupFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={e.filter.state as ISourceFilters[]}
key={`filters ${e.name}`}
name={e.name}
state={e.filters}
position={index}
updateFilterValue={updateFilterValue}
update={update}
/>
);
case 'Header':
return <HeaderFilter key={`filters ${e.filter.name}`} name={e.filter.name} />;
case 'Select':
case 'HeaderFilter':
return <HeaderFilter key={`filters ${e.name}`} name={e.name} />;
case 'SelectFilter':
return (
<SelectFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
values={e.filter.displayValues}
state={checkif != null ? parseInt(checkif, 10) : (e.filter.state as number)}
selected={e.filter.selected}
key={`filters ${e.name}`}
name={e.name}
values={e.values}
state={checkif != null ? parseInt(checkif, 10) : e.SelectFilterDefault}
position={index}
group={group}
updateFilterValue={updateFilterValue}
update={update}
/>
);
case 'Separator':
return <SeperatorFilter key={`filters ${e.filter.name}`} name={e.filter.name} />;
case 'Sort':
case 'SeparatorFilter':
return <SeperatorFilter key={`filters ${e.name}`} name={e.name} />;
case 'SortFilter':
return (
<SortFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
values={e.filter.values}
state={checkif ? JSON.parse(checkif) : { ...(e.filter.state as IState) }}
key={`filters ${e.name}`}
name={e.name}
values={e.values}
state={
checkif ?? {
ascending: e.SortFilterDefault?.ascending,
index: e.SortFilterDefault?.index,
}
}
position={index}
group={group}
updateFilterValue={updateFilterValue}
update={update}
/>
);
case 'Text':
case 'TextFilter':
return (
<TextFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={checkif ?? (e.filter.state as string)}
key={`filters ${e.name}`}
name={e.name}
state={checkif ?? e.TextFilterDefault}
position={index}
group={group}
updateFilterValue={updateFilterValue}
update={update}
/>
);
case 'TriState':
case 'TriStateFilter':
return (
<TriStateFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={checkif != null ? parseInt(checkif, 10) : (e.filter.state as number)}
key={`filters ${e.name}`}
name={e.name}
state={checkif != null ? checkif : e.TriStateFilterDefault}
position={index}
group={group}
updateFilterValue={updateFilterValue}
@@ -128,7 +132,7 @@ export function Options({ sourceFilter, group, updateFilterValue, update }: IFil
/>
);
default:
return <Box key={`${e.filter.name}null`} />;
throw new Error(`Unknown source filter "${e}"`);
}
})}
</Stack>

View File

@@ -27,7 +27,7 @@ const CheckBoxFilter: React.FC<Props> = (props: Props) => {
const upd = update.filter(
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
);
updateFilterValue([...upd, { position, state: event.target.checked.toString(), group }]);
updateFilterValue([...upd, { type: 'checkBoxState', position, state: event.target.checked, group }]);
};
if (state !== undefined) {

View File

@@ -10,11 +10,14 @@ import { ExpandLess, ExpandMore } from '@mui/icons-material';
import { Collapse, ListItemButton, ListItemText, Stack, Box } from '@mui/material';
import React from 'react';
// eslint-disable-next-line import/no-cycle
import { ISourceFilters } from '@/typings';
import { SourceFilters } from '@/typings';
import { Options } from '@/components/source/SourceOptions';
// type ExcludeByName<T, K extends string> = T extends { __typename?: K } ? never : T;
type PickByName<T, K extends string> = T extends { __typename?: K } ? T : never;
interface Props {
state: ISourceFilters[];
state: PickByName<SourceFilters, 'GroupFilter'>['filters'];
name: string;
position: number;
updateFilterValue: Function;
@@ -36,7 +39,7 @@ const GroupFilter: React.FC<Props> = (props: Props) => {
{/* Container is moved outside 2, so content has to go inside 4 */}
<Stack sx={{ mx: 4 }}>
<Options
sourceFilter={state}
sourceFilter={state as SourceFilters[]}
group={position}
updateFilterValue={updateFilterValue}
update={update}

View File

@@ -16,56 +16,12 @@ interface Props {
values: any;
name: string;
state: number;
selected: Selected | undefined;
position: number;
updateFilterValue: Function;
group: number | undefined;
update: any;
}
interface Selected {
displayname: string;
value: string;
_value: string;
}
function hasSelect(
values: Selected[],
name: string,
state: number,
position: number,
updateFilterValue: Function,
update: any,
group?: number,
) {
const [val, setval] = React.useState(state);
if (values) {
const handleChange = (event: { target: { name: any; value: any } }) => {
const vall = values.map((e) => e.displayname).indexOf(`${event.target.value}`);
setval(vall);
const upd = update.filter(
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
);
updateFilterValue([...upd, { position, state: vall.toString(), group }]);
};
const rett = values.map((e: Selected) => (
<MenuItem key={`${name} ${e.displayname}`} value={e.displayname}>
{e.displayname}
</MenuItem>
));
return (
<FormControl sx={{ my: 1 }} variant="standard">
<InputLabel>{name}</InputLabel>
<Select name={name} value={values[val].displayname} label={name} onChange={handleChange}>
{rett}
</Select>
</FormControl>
);
}
return null;
}
function noSelect(
values: string[],
name: string,
@@ -84,7 +40,7 @@ function noSelect(
const upd = update.filter(
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
);
updateFilterValue([...upd, { position, state: vall.toString(), group }]);
updateFilterValue([...upd, { type: 'selectState', position, state: vall, group }]);
};
const rett = values.map((value: string) => (
@@ -104,21 +60,7 @@ function noSelect(
return null;
}
const SelectFilter: React.FC<Props> = ({
values,
name,
state,
selected,
position,
updateFilterValue,
update,
group,
}) => {
if (selected === undefined) {
return noSelect(values, name, state, position, updateFilterValue, update, group);
}
return hasSelect(values, name, state, position, updateFilterValue, update, group);
};
const SelectFilter: React.FC<Props> = ({ values, name, state, position, updateFilterValue, update, group }) =>
noSelect(values, name, state, position, updateFilterValue, update, group);
export default SelectFilter;

View File

@@ -9,13 +9,13 @@
import { ExpandLess, ExpandMore } from '@mui/icons-material';
import { Collapse, ListItemButton, ListItemText, Stack, Box } from '@mui/material';
import React from 'react';
import { IState } from '@/typings';
import SortRadioInput from '@/components/atoms/SortRadioInput';
import { SortSelectionInput } from '@/lib/graphql/generated/graphql.ts';
interface Props {
values: any;
name: string;
state: IState;
state: SortSelectionInput;
position: number;
group: number | undefined;
updateFilterValue: Function;
@@ -45,7 +45,7 @@ const SortFilter: React.FC<Props> = (props: Props) => {
const upd = update.filter(
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
);
updateFilterValue([...upd, { position, state: JSON.stringify(tmp), group }]);
updateFilterValue([...upd, { type: 'sortState', position, state: tmp, group }]);
};
return (

View File

@@ -29,7 +29,7 @@ const TextFilter: React.FC<Props> = (props) => {
const upd = update.filter(
(el: { position: number; group: number | undefined }) => !(position === el.position && group === el.group),
);
updateFilterValue([...upd, { position, state: inputText, group }]);
updateFilterValue([...upd, { type: 'textState', position, state: inputText, group }]);
}, [inputText]);
if (state !== undefined) {

View File

@@ -8,9 +8,10 @@
import React from 'react';
import ThreeStateCheckboxInput from '@/components/atoms/ThreeStateCheckboxInput';
import { TriState } from '@/lib/graphql/generated/graphql.ts';
interface Props {
state: number;
state: TriState;
name: string;
position: number;
group: number | undefined;
@@ -18,9 +19,35 @@ interface Props {
update: any;
}
const convertTriStateToNumber = (triState: TriState): number => {
switch (triState) {
case TriState.Ignore:
return 0;
case TriState.Include:
return 1;
case TriState.Exclude:
return 2;
default:
throw new Error(`Unexpected TriState ${triState}`);
}
};
const convertNumberToTriState = (state: number): TriState => {
switch (state) {
case 0:
return TriState.Ignore;
case 1:
return TriState.Include;
case 2:
return TriState.Exclude;
default:
throw new Error(`Unexpected state number ${state}`);
}
};
const TriStateFilter: React.FC<Props> = (props) => {
const { state, name, position, group, updateFilterValue, update } = props;
const [val, setval] = React.useState<number>(Number(state));
const [val, setval] = React.useState(convertTriStateToNumber(state));
const handleChange = (checked: boolean | null | undefined) => {
// eslint-disable-next-line no-nested-ternary
@@ -32,8 +59,9 @@ const TriStateFilter: React.FC<Props> = (props) => {
updateFilterValue([
...upd,
{
type: 'triState',
position,
state: newState.toString(),
state: convertNumberToTriState(newState),
group,
},
]);

View File

@@ -28,10 +28,8 @@ import {
BackupValidationResult,
IChapter,
IMangaChapter,
ISourceFilters,
PaginatedList,
SourcePreferences,
SourceSearchResult,
} from '@/typings.ts';
import { HttpMethod as DefaultHttpMethod, IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts';
import storage from '@/util/localStorage.tsx';
@@ -1208,22 +1206,6 @@ export class RequestManager {
return this.doRequest(HttpMethod.POST, `source/${sourceId}/preferences`, { data: { position, value } });
}
public useGetSourceFilters(
sourceId: string,
reset?: boolean,
swrOptions?: SWROptions<ISourceFilters[]>,
): AbortableSWRResponse<ISourceFilters[]> {
return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/filters`, { swrOptions });
}
public setSourceFilters(sourceId: string, filters: { position: number; state: string }[]): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `source/${sourceId}/filters`, { data: filters });
}
public resetSourceFilters(sourceId: string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, `source/${sourceId}/filters?reset=true`);
}
public useSourceSearch(
source: string,
query?: string,
@@ -1241,26 +1223,6 @@ export class RequestManager {
);
}
public useSourceQuickSearch(
sourceId: string,
searchTerm: string,
filters: { position: number; state: string }[],
initialPages?: number,
swrOptions?: SWRInfiniteOptions<SourceSearchResult>,
): AbortableSWRInfiniteResponse<SourceSearchResult> {
return this.doRequest(HttpMethod.SWR_POST_INFINITE, '', {
data: { searchTerm, filter: filters },
swrOptions: {
getEndpoint: (page, previousData) =>
previousData?.hasNextPage ?? true
? `source/${sourceId}/quick-search?searchTerm=${searchTerm}&pageNum=${page + 1}`
: null,
initialSize: initialPages,
...swrOptions,
} as typeof swrOptions,
});
}
public useGetManga(
mangaId: number | string,
options?: QueryHookOptions<GetMangaQuery, GetMangaQueryVariables>,

View File

@@ -74,6 +74,7 @@ export enum SourceContentType {
}
interface IPos {
type: 'selectState' | 'textState' | 'checkBoxState' | 'triState' | 'sortState';
position: number;
state: any;
group?: number;
@@ -127,30 +128,30 @@ const useSourceManga = (
result = requestManager.useSourceSearch(sourceId, searchTerm ?? '', undefined, initialPages);
break;
case SourceContentType.FILTER:
result = requestManager.useSourceSearch(sourceId, undefined, [], initialPages);
// TODO - update filters to gql
// result = requestManager.useSourceQuickSearch(
// sourceId,
// '',
// filters.map((filter) => {
// const { position, state, group } = filter;
//
// const isPartOfGroup = group !== undefined;
// if (isPartOfGroup) {
// return {
// position: group,
// state: JSON.stringify({
// position,
// state,
// }),
// };
// }
//
// return filter;
// }),
// initialPages,
// { disableCache: true },
// );
result = requestManager.useSourceSearch(
sourceId,
undefined,
filters.map((filter) => {
const { position, state, group } = filter;
const isPartOfGroup = group !== undefined;
if (isPartOfGroup) {
return {
position: group,
groupChange: {
position,
[filter.type]: state,
},
};
}
return {
position,
[filter.type]: state,
};
}),
initialPages,
);
break;
default:
throw new Error(`Unknown ContentType "${contentType}"`);
@@ -222,10 +223,9 @@ export default function SourceMangas() {
const mangas = (data?.fetchSourceManga.mangas as MangaType[]) ?? [];
const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false;
const { data: filters = [], mutate: mutateFilters } = requestManager.useGetSourceFilters(sourceId);
const { data: sourceData } = requestManager.useGetSource(sourceId);
const source = sourceData?.source;
const [triggerDataRefresh, setTriggerDataRefresh] = useState(false);
const filters = source?.filters ?? [];
const message = !isLoading ? t(SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY[contentType]) : undefined;
const isLocalSource = sourceId === '0';
@@ -272,14 +272,6 @@ export default function SourceMangas() {
const resetFilters = useCallback(async () => {
setDialogFiltersToApply([]);
setFiltersToApply([]);
try {
// required since previous implementation used to set the filters on server side (server caches them), thus, it has to be made sure that they are reset
await requestManager.resetSourceFilters(sourceId);
mutateFilters();
} catch (error) {
// ignore
}
setTriggerDataRefresh(true);
}, [sourceId]);
useEffect(
@@ -297,16 +289,6 @@ export default function SourceMangas() {
[searchTerm, contentType],
);
// TODO - check when fixing filters
useEffect(() => {
if (!triggerDataRefresh) {
return;
}
// refreshData();
setTriggerDataRefresh(false);
}, [triggerDataRefresh]);
useEffect(() => {
setTitle(source?.displayName ?? t('source.title'));
setAction(
@@ -378,7 +360,6 @@ export default function SourceMangas() {
updateFilterValue={setDialogFiltersToApply}
setTriggerUpdate={() => {
setFiltersToApply(dialogFiltersToApply);
setTriggerDataRefresh(true);
}}
resetFilterValue={resetFilters}
update={dialogFiltersToApply}

View File

@@ -10,7 +10,7 @@ import { OverridableComponent } from '@mui/material/OverridableComponent';
import { SvgIconTypeMap } from '@mui/material/SvgIcon/SvgIcon';
import { ParseKeys } from 'i18next';
import { Location } from 'react-router-dom';
import { ExtensionType, MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts';
import { ExtensionType, GetSourceQuery, MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts';
export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
@@ -43,29 +43,7 @@ export interface ISource {
displayName: string;
}
export interface ISourceFilters {
type: string;
filter: ISourceFilter;
}
export interface ISourceFilter {
name: string;
state: number | string | boolean | ISourceFilters[] | IState;
values?: string[];
displayValues?: string[];
selected?: ISelected;
}
export interface ISelected {
displayname: string;
value: string;
_value: string;
}
export interface IState {
ascending: boolean;
index: number;
}
export type SourceFilters = GetSourceQuery['source']['filters'][number];
export interface IMetadataMigration {
appKeyPrefix?: { oldPrefix: string; newPrefix: string };