Use gql for "sources" - preferences

This commit is contained in:
schroda
2023-09-25 23:32:26 +02:00
parent dcead5a801
commit 78049282a7
8 changed files with 147 additions and 103 deletions

View File

@@ -10,14 +10,11 @@ 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 { SourceFilters } from '@/typings';
import { ExtractByKeyValue, 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: PickByName<SourceFilters, 'GroupFilter'>['filters'];
state: ExtractByKeyValue<SourceFilters, '__typename', 'GroupFilter'>['filters'];
name: string;
position: number;
updateFilterValue: Function;

View File

@@ -22,22 +22,29 @@ import { EditTextPreferenceProps } from '@/typings';
export default function EditTextPreference(props: EditTextPreferenceProps) {
const { t } = useTranslation();
const { title, summary, dialogTitle, dialogMessage, currentValue, updateValue } = props;
const {
EditTextPreferenceTitle: title,
summary,
dialogTitle,
dialogMessage,
EditTextPreferenceCurrentValue: currentValue,
updateValue,
} = props;
const [internalCurrentValue, setInternalCurrentValue] = useState<string>(currentValue);
const [internalCurrentValue, setInternalCurrentValue] = useState<string>(currentValue ?? '');
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const handleDialogCancel = () => {
setDialogOpen(false);
// reset the dialog
setInternalCurrentValue(currentValue);
setInternalCurrentValue(currentValue ?? '');
};
const handleDialogSubmit = () => {
setDialogOpen(false);
updateValue(internalCurrentValue);
updateValue('editTextState', internalCurrentValue);
};
return (

View File

@@ -85,12 +85,20 @@ function ListDialog(props: IListDialogProps) {
}
export default function ListPreference(props: ListPreferenceProps) {
const { title, summary, currentValue, updateValue, entryValues, entries } = props;
const [internalCurrentValue, setInternalCurrentValue] = useState<string>(currentValue);
const {
ListPreferenceTitle: title,
summary,
ListPreferenceCurrentValue: currentValue,
ListPreferenceDefault: defaultValue,
updateValue,
entryValues,
entries,
} = props;
const [internalCurrentValue, setInternalCurrentValue] = useState(currentValue ?? defaultValue ?? '');
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useEffect(() => {
setInternalCurrentValue(currentValue);
setInternalCurrentValue(currentValue ?? defaultValue ?? '');
}, [currentValue]);
const findEntryOf = (value: string) => {
@@ -104,6 +112,10 @@ export default function ListPreference(props: ListPreferenceProps) {
};
const getSummary = () => {
if (currentValue == null) {
return '';
}
if (summary === '%s') {
return findEntryOf(currentValue);
}
@@ -112,7 +124,7 @@ export default function ListPreference(props: ListPreferenceProps) {
const handleDialogClose = (newValue: string | null) => {
if (newValue !== null) {
updateValue(findEntryValueOf(newValue));
updateValue('listState', findEntryValueOf(newValue));
// appear smooth
setInternalCurrentValue(newValue);
@@ -127,7 +139,7 @@ export default function ListPreference(props: ListPreferenceProps) {
<ListItemText primary={title} secondary={getSummary()} />
</ListItemButton>
<ListDialog
title={title}
title={title ?? ''}
open={dialogOpen}
onClose={handleDialogClose}
value={findEntryOf(internalCurrentValue)}

View File

@@ -99,32 +99,40 @@ function ListDialog(props: IListDialogProps) {
}
export default function MultiSelectListPreference(props: MultiSelectListPreferenceProps) {
const { title, summary, currentValue, updateValue, entryValues, entries } = props;
const [internalCurrentValue, setInternalCurrentValue] = useState<string[]>(currentValue);
const {
MultiSelectListPreferenceTitle: title,
summary,
MultiSelectListPreferenceCurrentValue: currentValue,
MultiSelectListPreferenceDefault: defaultValue,
updateValue,
entryValues,
entries,
} = props;
const [internalCurrentValue, setInternalCurrentValue] = useState(currentValue ?? defaultValue);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useEffect(() => {
setInternalCurrentValue(currentValue);
}, [currentValue]);
const findEntriesOf = (values: string[]) =>
values.map((value) => {
const findEntriesOf = (values?: string[] | null) =>
values?.map((value) => {
const idx = entryValues.indexOf(value);
return entries[idx];
});
}) ?? [];
const findEntryValuesOf = (values: string[]) =>
values.map((value) => {
const findEntryValuesOf = (values?: string[] | null) =>
values?.map((value) => {
const idx = entries.indexOf(value);
return entryValues[idx];
});
}) ?? [];
const getSummary = () => summary;
const handleDialogClose = (newValue: string[] | null) => {
if (newValue !== null) {
// console.log(newValue);
updateValue(findEntryValuesOf(newValue));
updateValue('multiSelectState', findEntryValuesOf(newValue));
// appear smooth
setInternalCurrentValue(newValue);
@@ -139,7 +147,7 @@ export default function MultiSelectListPreference(props: MultiSelectListPreferen
<ListItemText primary={title} secondary={getSummary()} />
</ListItemButton>
<ListDialog
title={title}
title={title ?? ''}
open={dialogOpen}
onClose={handleDialogClose}
selectedValues={findEntriesOf(internalCurrentValue)}

View File

@@ -21,23 +21,48 @@ function getTwoStateType(type: 'Checkbox' | 'Switch') {
return Checkbox;
}
const getTwoStateValues = (
props: TwoStatePreferenceProps,
): {
title: string;
defaultValue: boolean;
currentValue?: boolean | null | undefined;
} => {
if (props.type === 'CheckBoxPreference') {
return {
title: props.CheckBoxTitle,
defaultValue: props.CheckBoxDefault,
currentValue: props.CheckBoxCheckBoxCurrentValue,
};
}
return {
title: props.SwitchPreferenceTitle,
defaultValue: props.SwitchPreferenceDefault,
currentValue: props.SwitchPreferenceCurrentValue,
};
};
function TwoSatePreference(props: TwoStatePreferenceProps) {
const { title, summary, currentValue, updateValue, type } = props;
const [internalCurrentValue, setInternalCurrentValue] = useState<boolean>(currentValue);
const { title, defaultValue, currentValue, summary, updateValue, twoStateType } = {
...props,
...getTwoStateValues(props),
};
const [internalCurrentValue, setInternalCurrentValue] = useState(currentValue ?? defaultValue);
useEffect(() => {
setInternalCurrentValue(currentValue);
setInternalCurrentValue(currentValue ?? defaultValue);
}, [currentValue]);
return (
<ListItem>
<ListItemText primary={title} secondary={summary} />
<ListItemSecondaryAction>
{createElement(getTwoStateType(type), {
{createElement(getTwoStateType(twoStateType), {
edge: 'end',
checked: internalCurrentValue,
onChange: () => {
updateValue(!currentValue);
updateValue(twoStateType === 'Switch' ? 'switchState' : 'checkBoxState', !currentValue);
// appear smooth
setInternalCurrentValue(!currentValue);
@@ -49,11 +74,11 @@ function TwoSatePreference(props: TwoStatePreferenceProps) {
}
export function CheckBoxPreference(props: CheckBoxPreferenceProps) {
return <TwoSatePreference {...props} type="Checkbox" />;
return <TwoSatePreference {...props} twoStateType="Checkbox" />;
}
export function SwitchPreferenceCompat(props: SwitchPreferenceCompatProps) {
return <TwoSatePreference {...props} type="Switch" />;
return <TwoSatePreference {...props} twoStateType="Switch" />;
}
export default { CheckBoxPreference, SwitchPreferenceCompat };

View File

@@ -24,13 +24,7 @@ import {
} from '@apollo/client';
import { OperationVariables } from '@apollo/client/core';
import { useEffect, useRef, useState } from 'react';
import {
BackupValidationResult,
IChapter,
IMangaChapter,
PaginatedList,
SourcePreferences,
} from '@/typings.ts';
import { BackupValidationResult, IChapter, IMangaChapter, PaginatedList } from '@/typings.ts';
import { HttpMethod as DefaultHttpMethod, IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts';
import storage from '@/util/localStorage.tsx';
import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts';
@@ -95,6 +89,7 @@ import {
SetGlobalMetadataMutation,
SetGlobalMetadataMutationVariables,
SetMangaMetadataMutation,
SourcePreferenceChangeInput,
StartDownloaderMutation,
StartDownloaderMutationVariables,
StopDownloaderMutation,
@@ -123,6 +118,8 @@ import {
UpdateMangaMutation,
UpdateMangaMutationVariables,
UpdateMangaPatchInput,
UpdateSourcePreferencesMutation,
UpdateSourcePreferencesMutationVariables,
} from '@/lib/graphql/generated/graphql.ts';
import { GET_GLOBAL_METADATA, GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
@@ -142,7 +139,7 @@ import {
} from '@/lib/graphql/mutations/MangaMutation.ts';
import { GET_MANGA, GET_MANGAS } from '@/lib/graphql/queries/MangaQuery.ts';
import { GET_CATEGORIES, GET_CATEGORY, GET_CATEGORY_MANGAS } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_SOURCE_MANGAS_FETCH } from '@/lib/graphql/mutations/SourceMutation.ts';
import { GET_SOURCE_MANGAS_FETCH, UPDATE_SOURCE_PREFERENCES } from '@/lib/graphql/mutations/SourceMutation.ts';
import {
CLEAR_DOWNLOADER,
DELETE_DOWNLOADED_CHAPTER,
@@ -1195,15 +1192,20 @@ export class RequestManager {
);
}
public useGetSourcePreferences(
sourceId: string,
swrOptions?: SWROptions<SourcePreferences[]>,
): AbortableSWRResponse<SourcePreferences[]> {
return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/preferences`, { swrOptions });
}
public setSourcePreferences(sourceId: string, position: number, value: string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `source/${sourceId}/preferences`, { data: { position, value } });
public setSourcePreferences(
source: string,
change: SourcePreferenceChangeInput,
options?: MutationOptions<UpdateSourcePreferencesMutation, UpdateSourcePreferencesMutationVariables>,
): AbortableApolloMutationResponse<UpdateSourcePreferencesMutation> {
return this.doRequestNew(
GQLMethod.MUTATION,
UPDATE_SOURCE_PREFERENCES,
{ input: { source, change } },
{
refetchQueries: [GET_SOURCE],
...options,
},
);
}
public useSourceSearch(

View File

@@ -17,12 +17,13 @@ import { SwitchPreferenceCompat, CheckBoxPreference } from '@/components/sourceC
import ListPreference from '@/components/sourceConfiguration/ListPreference';
import EditTextPreference from '@/components/sourceConfiguration/EditTextPreference';
import MultiSelectListPreference from '@/components/sourceConfiguration/MultiSelectListPreference';
import { PreferenceProps } from '@/typings.ts';
function getPrefComponent(type: string) {
switch (type) {
case 'CheckBoxPreference':
return CheckBoxPreference;
case 'SwitchPreferenceCompat':
case 'SwitchPreference':
return SwitchPreferenceCompat;
case 'ListPreference':
return ListPreference;
@@ -31,7 +32,7 @@ function getPrefComponent(type: string) {
case 'MultiSelectListPreference':
return MultiSelectListPreference;
default:
return CheckBoxPreference;
throw new Error(`Unexpected preference type "${type}"`);
}
}
@@ -45,33 +46,26 @@ export default function SourceConfigure() {
}, [t]);
const { sourceId } = useParams<{ sourceId: string }>();
const { data: sourcePreferences = [], mutate } = requestManager.useGetSourcePreferences(sourceId);
const { data } = requestManager.useGetSource(sourceId);
const sourcePreferences = data?.source.preferences ?? [];
const convertToString = (position: number, value: any): string => {
switch (sourcePreferences[position].props.defaultValueType) {
case 'Set<String>':
return JSON.stringify(value);
default:
return value.toString();
}
};
const updateValue = (position: number) => (value: any) => {
requestManager
.setSourcePreferences(sourceId, position, convertToString(position, value))
.response.then(() => mutate());
};
const updateValue =
(position: number): PreferenceProps['updateValue'] =>
(type, value) => {
requestManager.setSourcePreferences(sourceId, { position, [type]: value });
};
return (
<List sx={{ padding: 0 }}>
{sourcePreferences.map((it, index) => {
const props = cloneObject(it.props);
props.updateValue = updateValue(index);
props.key = index;
const props = cloneObject(it);
// TypeScript is dumb in detecting extra props
// @ts-ignore
return createElement(getPrefComponent(it.type), props);
return createElement(getPrefComponent(it.type), {
...props,
updateValue: updateValue(index),
});
})}
</List>
);

View File

@@ -10,7 +10,19 @@ 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, GetSourceQuery, MangaType, MetaType } from '@/lib/graphql/generated/graphql.ts';
import {
ExtensionType,
GetSourceQuery,
MangaType,
MetaType,
SourcePreferenceChangeInput,
} from '@/lib/graphql/generated/graphql.ts';
export type ExtractByKeyValue<T, Key extends keyof T, Value extends T[Key]> = T extends
| Record<Key, Value>
| Partial<Record<Key, Value>>
? T
: never;
export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
@@ -253,47 +265,34 @@ export interface IUpdateStatus {
};
}
export type SourcePreferences = GetSourceQuery['source']['preferences'][number];
export interface PreferenceProps {
key: string;
title: string;
summary: string;
defaultValue: any;
currentValue: any;
defaultValueType: string;
updateValue: <Key extends keyof Omit<SourcePreferenceChangeInput, 'position'>>(
type: Key,
value: SourcePreferenceChangeInput[Key],
) => void;
}
export type TwoStatePreferenceProps = (CheckBoxPreferenceProps | SwitchPreferenceCompatProps) & {
// intetnal props
updateValue: any;
}
twoStateType: 'Switch' | 'Checkbox';
};
export interface TwoStatePreferenceProps extends PreferenceProps {
// intetnal props
type: 'Switch' | 'Checkbox';
}
export type CheckBoxPreferenceProps = PreferenceProps &
ExtractByKeyValue<SourcePreferences, '__typename', 'CheckBoxPreference'>;
export interface CheckBoxPreferenceProps extends PreferenceProps {}
export type SwitchPreferenceCompatProps = PreferenceProps &
ExtractByKeyValue<SourcePreferences, '__typename', 'SwitchPreference'>;
export interface SwitchPreferenceCompatProps extends PreferenceProps {}
export type ListPreferenceProps = PreferenceProps &
ExtractByKeyValue<SourcePreferences, '__typename', 'ListPreference'>;
export interface ListPreferenceProps extends PreferenceProps {
entries: string[];
entryValues: string[];
}
export type MultiSelectListPreferenceProps = PreferenceProps &
ExtractByKeyValue<SourcePreferences, '__typename', 'MultiSelectListPreference'>;
export interface MultiSelectListPreferenceProps extends PreferenceProps {
entries: string[];
entryValues: string[];
}
export interface EditTextPreferenceProps extends PreferenceProps {
dialogTitle: string;
dialogMessage: string;
text: string;
}
export interface SourcePreferences {
type: string;
props: any;
}
export type EditTextPreferenceProps = PreferenceProps &
ExtractByKeyValue<SourcePreferences, '__typename', 'EditTextPreference'>;
export interface NavbarItem {
path: string;