Add functionality to save source browse searches
This commit is contained in:
@@ -306,6 +306,7 @@
|
||||
"clear": "Clear",
|
||||
"copy": "Copy",
|
||||
"create": "Create",
|
||||
"delete": "Delete",
|
||||
"deselect": "Deselect",
|
||||
"dont_show_dialog_again": "Don't show this dialog again",
|
||||
"edit": "Edit",
|
||||
@@ -1116,6 +1117,20 @@
|
||||
"source_not_found": "Could not find source. Check your installed extensions."
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"save_search": {
|
||||
"dialog": {
|
||||
"label": {
|
||||
"delete": "This will delete the saved search \"{{name}}\"",
|
||||
"title": "Save current search"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"delete": "Delete search",
|
||||
"save": "Save search"
|
||||
}
|
||||
}
|
||||
},
|
||||
"local_source": {
|
||||
"label": {
|
||||
"checkout": "Check out",
|
||||
|
||||
2
src/UtilTypes.d.ts
vendored
2
src/UtilTypes.d.ts
vendored
@@ -23,3 +23,5 @@ type RecursivePartial<T> = {
|
||||
type OptionalProperty<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||
|
||||
type PropertiesNever<T> = { [key in keyof T]?: never };
|
||||
|
||||
type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never;
|
||||
|
||||
@@ -12,7 +12,19 @@ import Stack from '@mui/material/Stack';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SourceFilters } from '@/typings';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import PopupState, { bindDialog, bindTrigger } from 'material-ui-popup-state';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import { ISourceMetadata, SourceFilters } from '@/typings';
|
||||
import { OptionsPanel } from '@/components/molecules/OptionsPanel';
|
||||
import { CheckBoxFilter } from '@/components/source/filters/CheckBoxFilter';
|
||||
import { HeaderFilter } from '@/components/source/filters/HeaderFilter';
|
||||
@@ -25,6 +37,8 @@ import { TriStateFilter } from '@/components/source/filters/TriStateFilter';
|
||||
import { GroupFilter } from '@/components/source/filters/GroupFilter';
|
||||
import { SeparatorFilter } from '@/components/source/filters/SeparatorFilter';
|
||||
import { StyledFab } from '@/components/util/StyledFab';
|
||||
import { awaitConfirmation } from '@/lib/ui/AwaitableDialog.tsx';
|
||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||
|
||||
interface IFilters {
|
||||
sourceFilter: SourceFilters[];
|
||||
@@ -34,6 +48,9 @@ interface IFilters {
|
||||
}
|
||||
|
||||
interface IFilters1 {
|
||||
savedSearches: ISourceMetadata['savedSearches'];
|
||||
selectSavedSearch: (savedSearch: string) => void;
|
||||
updateSavedSearches: (savedSearch: string, updateType: 'create' | 'delete') => void;
|
||||
sourceFilter: SourceFilters[];
|
||||
updateFilterValue: Function;
|
||||
resetFilterValue: Function;
|
||||
@@ -142,6 +159,9 @@ export function Options({ sourceFilter, group, updateFilterValue, update }: IFil
|
||||
}
|
||||
|
||||
export function SourceOptions({
|
||||
savedSearches = {},
|
||||
selectSavedSearch,
|
||||
updateSavedSearches,
|
||||
sourceFilter,
|
||||
updateFilterValue,
|
||||
resetFilterValue,
|
||||
@@ -150,6 +170,10 @@ export function SourceOptions({
|
||||
}: IFilters1) {
|
||||
const { t } = useTranslation();
|
||||
const [FilterOptions, setFilterOptions] = useState(false);
|
||||
const [newSavedSearch, setNewSavedSearch] = useState('');
|
||||
|
||||
const savedSearchNames = Object.keys(savedSearches);
|
||||
const savedSearchesExist = !!savedSearchNames.length;
|
||||
|
||||
function handleReset() {
|
||||
resetFilterValue(0);
|
||||
@@ -169,12 +193,91 @@ export function SourceOptions({
|
||||
</StyledFab>
|
||||
|
||||
<OptionsPanel open={FilterOptions} onClose={() => setFilterOptions(false)}>
|
||||
<Box sx={{ display: 'flex', p: 2, pb: 0 }}>
|
||||
<Box sx={{ p: 2, pb: savedSearchesExist ? undefined : 0 }}>
|
||||
<Box sx={{ display: 'flex', pb: 1 }}>
|
||||
<Button onClick={handleReset}>{t('global.button.reset')}</Button>
|
||||
<Button sx={{ marginLeft: 'auto' }} variant="contained" onClick={handleSubmit}>
|
||||
<PopupState variant="dialog" popupId="source-browse-save-search">
|
||||
{(popupState) => (
|
||||
<>
|
||||
<Tooltip title={t('source.filter.save_search.label.save')}>
|
||||
<IconButton sx={{ marginLeft: 'auto' }} {...bindTrigger(popupState)}>
|
||||
<SaveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog {...bindDialog(popupState)} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>{t('source.filter.save_search.dialog.label.title')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
sx={{ width: '100%' }}
|
||||
inputProps={{ maxLength: 50 }}
|
||||
value={newSavedSearch}
|
||||
onChange={(e) => setNewSavedSearch(e.target.value as string)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setNewSavedSearch('');
|
||||
popupState.close();
|
||||
}}
|
||||
>
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateSavedSearches(newSavedSearch, 'create');
|
||||
setNewSavedSearch('');
|
||||
popupState.close();
|
||||
}}
|
||||
>
|
||||
{t('global.button.ok')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
</PopupState>
|
||||
|
||||
<Button variant="contained" onClick={handleSubmit}>
|
||||
{t('global.button.submit')}
|
||||
</Button>
|
||||
</Box>
|
||||
{savedSearchesExist && (
|
||||
<>
|
||||
<Typography sx={{ pb: 1 }}>Saved searches</Typography>
|
||||
<Stack sx={{ flexDirection: 'row' }}>
|
||||
{savedSearchNames.map((savedSearch) => (
|
||||
<Chip
|
||||
label={savedSearch}
|
||||
onClick={() => {
|
||||
setFilterOptions(false);
|
||||
selectSavedSearch(savedSearch);
|
||||
}}
|
||||
onDelete={() => {
|
||||
awaitConfirmation({
|
||||
title: t('global.label.are_you_sure'),
|
||||
message: t('source.filter.save_search.dialog.label.delete', {
|
||||
name: savedSearch,
|
||||
}),
|
||||
actions: {
|
||||
confirm: { title: t('global.button.delete') },
|
||||
},
|
||||
})
|
||||
.then(() => updateSavedSearches(savedSearch, 'delete'))
|
||||
.catch(defaultPromiseErrorHandler('SourceOptions::deleteSavedSearch'));
|
||||
}}
|
||||
deleteIcon={
|
||||
<Tooltip title={t('source.filter.save_search.label.delete')}>
|
||||
<DeleteIcon />
|
||||
</Tooltip>
|
||||
}
|
||||
variant="outlined"
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pb: 2,
|
||||
|
||||
@@ -65,6 +65,10 @@ export const PARTIAL_SOURCE_FIELDS = gql`
|
||||
pkgName
|
||||
repo
|
||||
}
|
||||
meta {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -83,3 +83,22 @@ export const UPDATE_SOURCE_PREFERENCES = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SET_SOURCE_METADATA = gql`
|
||||
mutation SET_SOURCE_METADATA($input: SetSourceMetaInput!) {
|
||||
setSourceMeta(input: $input) {
|
||||
clientMutationId
|
||||
meta {
|
||||
key
|
||||
value
|
||||
source {
|
||||
id
|
||||
meta {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
TCategory,
|
||||
TChapter,
|
||||
TManga,
|
||||
TPartialSource,
|
||||
} from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
@@ -54,6 +55,9 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
||||
// updates
|
||||
'webUIInformAvailableUpdate',
|
||||
'serverInformAvailableUpdate',
|
||||
|
||||
// sources
|
||||
'savedSearches',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -354,7 +358,7 @@ const wrapMetadataWithMetaKey = (wrap: boolean, metadata: Metadata): MetadataHol
|
||||
};
|
||||
};
|
||||
|
||||
type MetadataHolderType = 'manga' | 'chapter' | 'category' | 'global';
|
||||
type MetadataHolderType = 'manga' | 'chapter' | 'category' | 'global' | 'source';
|
||||
|
||||
export const requestUpdateMetadataValue = async (
|
||||
metadataHolder: GqlMetaHolder,
|
||||
@@ -377,6 +381,9 @@ export const requestUpdateMetadataValue = async (
|
||||
case 'manga':
|
||||
await requestManager.setMangaMeta((metadataHolder as TManga).id, metadataKey, value).response;
|
||||
break;
|
||||
case 'source':
|
||||
await requestManager.setSourceMeta((metadataHolder as TPartialSource).id, metadataKey, value).response;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`requestUpdateMetadataValue: unknown holderType "${holderType}"`);
|
||||
}
|
||||
@@ -406,3 +413,8 @@ export const requestUpdateCategoryMetadata = async (
|
||||
category: TCategory,
|
||||
keysToValues: MetadataKeyValuePair[],
|
||||
): Promise<void[]> => requestUpdateMetadata(category, 'category', keysToValues);
|
||||
|
||||
export const requestUpdateSourceMetadata = async (
|
||||
source: TPartialSource,
|
||||
keysToValue: MetadataKeyValuePair[],
|
||||
): Promise<void[]> => requestUpdateMetadata(source, 'source', keysToValue);
|
||||
|
||||
59
src/lib/metadata/sourceMetadata.ts
Normal file
59
src/lib/metadata/sourceMetadata.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import {
|
||||
AllowedMetadataValueTypes,
|
||||
AppMetadataKeys,
|
||||
GqlMetaHolder,
|
||||
ISourceMetadata,
|
||||
Metadata,
|
||||
SourceMetadataKeys,
|
||||
TPartialSource,
|
||||
} from '@/typings.ts';
|
||||
import { jsonSaveParse } from '@/util/HelperFunctions.ts';
|
||||
import { convertFromGqlMeta, getMetadataFrom, requestUpdateSourceMetadata } from '@/lib/metadata/metadata.ts';
|
||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||
|
||||
const convertAppMetadataToGqlMetadata = (
|
||||
metadata: Partial<ISourceMetadata>,
|
||||
): Metadata<string, AllowedMetadataValueTypes> => ({
|
||||
...metadata,
|
||||
savedSearches: metadata.savedSearches ? JSON.stringify(metadata.savedSearches) : undefined,
|
||||
});
|
||||
|
||||
export const convertGqlMetadataToAppMetadata = (
|
||||
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
||||
): ISourceMetadata => ({
|
||||
...(metadata as unknown as ISourceMetadata),
|
||||
savedSearches: jsonSaveParse<ISourceMetadata['savedSearches']>(metadata.savedSearches as string) ?? undefined,
|
||||
});
|
||||
|
||||
export const getSourceMetadata = ({ meta }: GqlMetaHolder = {}, applyMetadataMigration?: boolean): ISourceMetadata =>
|
||||
convertGqlMetadataToAppMetadata(
|
||||
getMetadataFrom({ meta: convertFromGqlMeta(meta) } ?? {}, { savedSearches: undefined }, applyMetadataMigration),
|
||||
);
|
||||
|
||||
export const updateSourceMetadata = async <
|
||||
MetadataKeys extends SourceMetadataKeys = SourceMetadataKeys,
|
||||
MetadataKey extends MetadataKeys = MetadataKeys,
|
||||
>(
|
||||
source: TPartialSource,
|
||||
metadataKey: MetadataKey,
|
||||
value: ISourceMetadata[MetadataKey],
|
||||
): Promise<void[]> =>
|
||||
requestUpdateSourceMetadata(source, [
|
||||
[metadataKey, convertAppMetadataToGqlMetadata({ [metadataKey]: value })[metadataKey]],
|
||||
]);
|
||||
|
||||
export const createUpdateSourceMetadata =
|
||||
<Settings extends SourceMetadataKeys>(
|
||||
source: TPartialSource,
|
||||
handleError: (error: any) => void = defaultPromiseErrorHandler('createUpdateSourceMetadata'),
|
||||
): ((...args: OmitFirst<Parameters<typeof updateSourceMetadata<Settings>>>) => Promise<void | void[]>) =>
|
||||
(metadataKey, value) =>
|
||||
updateSourceMetadata(source, metadataKey, value).catch(handleError);
|
||||
@@ -196,6 +196,9 @@ import {
|
||||
TrackerUnbindMutationVariables,
|
||||
TrackerFetchBindMutation,
|
||||
TrackerFetchBindMutationVariables,
|
||||
GetServerSettingsQueryVariables,
|
||||
SetSourceMetadataMutation,
|
||||
SetSourceMetadataMutationVariables,
|
||||
} from '@/lib/graphql/generated/graphql.ts';
|
||||
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
||||
import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
||||
@@ -228,7 +231,11 @@ import {
|
||||
GET_MIGRATABLE_SOURCE_MANGAS,
|
||||
} from '@/lib/graphql/queries/MangaQuery.ts';
|
||||
import { GET_CATEGORIES, GET_CATEGORY_MANGAS } from '@/lib/graphql/queries/CategoryQuery.ts';
|
||||
import { GET_SOURCE_MANGAS_FETCH, UPDATE_SOURCE_PREFERENCES } from '@/lib/graphql/mutations/SourceMutation.ts';
|
||||
import {
|
||||
GET_SOURCE_MANGAS_FETCH,
|
||||
SET_SOURCE_METADATA,
|
||||
UPDATE_SOURCE_PREFERENCES,
|
||||
} from '@/lib/graphql/mutations/SourceMutation.ts';
|
||||
import {
|
||||
CLEAR_DOWNLOADER,
|
||||
DELETE_DOWNLOADED_CHAPTER,
|
||||
@@ -1329,6 +1336,22 @@ export class RequestManager {
|
||||
return this.doRequest(GQLMethod.USE_QUERY, GET_SOURCE, { id }, options);
|
||||
}
|
||||
|
||||
public setSourceMeta(
|
||||
sourceId: string,
|
||||
key: string,
|
||||
value: any,
|
||||
options?: MutationOptions<SetSourceMetadataMutation, SetSourceMetadataMutationVariables>,
|
||||
): AbortableApolloMutationResponse<SetSourceMetadataMutation> {
|
||||
return this.doRequest(
|
||||
GQLMethod.MUTATION,
|
||||
SET_SOURCE_METADATA,
|
||||
{
|
||||
input: { meta: { sourceId, key, value: `${value}` } },
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
public useGetSourceMangas(
|
||||
input: FetchSourceMangaInput,
|
||||
initialPages: number = 1,
|
||||
@@ -2476,8 +2499,8 @@ export class RequestManager {
|
||||
}
|
||||
|
||||
public useGetServerSettings(
|
||||
options?: QueryHookOptions<GetServerSettingsQuery, GetSourcesQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<GetServerSettingsQuery, GetSourcesQueryVariables> {
|
||||
options?: QueryHookOptions<GetServerSettingsQuery, GetServerSettingsQueryVariables>,
|
||||
): AbortableApolloUseQueryResponse<GetServerSettingsQuery, GetServerSettingsQueryVariables> {
|
||||
return this.doRequest(GQLMethod.USE_QUERY, GET_SERVER_SETTINGS, undefined, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { styled } from '@mui/material/styles';
|
||||
import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import { TPartialManga, TranslationKey } from '@/typings';
|
||||
import { IPos, TPartialManga, TPartialSource, TranslationKey } from '@/typings';
|
||||
import {
|
||||
requestManager,
|
||||
AbortableApolloUseMutationPaginatedResponse,
|
||||
@@ -40,6 +40,8 @@ import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings
|
||||
import { useSessionStorage } from '@/util/useStorage.tsx';
|
||||
import { AppStorage } from '@/util/AppStorage.ts';
|
||||
import { getGridSnapshotKey } from '@/components/MangaGrid.tsx';
|
||||
import { createUpdateSourceMetadata, getSourceMetadata } from '@/lib/metadata/sourceMetadata.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
|
||||
const ContentTypeMenu = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@@ -81,13 +83,6 @@ export enum SourceContentType {
|
||||
SEARCH,
|
||||
}
|
||||
|
||||
interface IPos {
|
||||
type: 'selectState' | 'textState' | 'checkBoxState' | 'triState' | 'sortState';
|
||||
position: number;
|
||||
state: any;
|
||||
group?: number;
|
||||
}
|
||||
|
||||
const SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY: { [contentType in SourceContentType]: TranslationKey } = {
|
||||
[SourceContentType.POPULAR]: 'manga.error.label.no_mangas_found',
|
||||
[SourceContentType.LATEST]: 'manga.error.label.no_mangas_found',
|
||||
@@ -287,10 +282,53 @@ export function SourceMangas() {
|
||||
const isLoading = loading || filteredOutAllItemsOfFetchedPage;
|
||||
const mangas = data?.fetchSourceManga.mangas ?? [];
|
||||
const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false;
|
||||
|
||||
const { data: sourceData } = requestManager.useGetSource(sourceId);
|
||||
const source = sourceData?.source;
|
||||
|
||||
const filters = source?.filters ?? [];
|
||||
const { savedSearches = {} } = useMemo(() => getSourceMetadata(source), [source, source?.meta]);
|
||||
const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(
|
||||
source ?? ({ id: sourceId } as TPartialSource),
|
||||
() => makeToast(t('global.error.label.failed_to_save_changes'), 'error'),
|
||||
);
|
||||
|
||||
const selectSavedSearch = useCallback(
|
||||
(savedSearch: string) => {
|
||||
const { query: savedSearchQuery, filters: savedSearchFilters } = savedSearches[savedSearch];
|
||||
|
||||
if (savedSearchFilters) {
|
||||
setDialogFiltersToApply(savedSearchFilters);
|
||||
setFiltersToApply(savedSearchFilters);
|
||||
}
|
||||
|
||||
navigate(
|
||||
{
|
||||
pathname: '',
|
||||
search: savedSearchQuery ? `query=${savedSearchQuery}` : undefined,
|
||||
},
|
||||
{ state: { ...locationState, contentType: SourceContentType.SEARCH } },
|
||||
);
|
||||
},
|
||||
[savedSearches, locationState],
|
||||
);
|
||||
|
||||
const handleSavedSearchesUpdate = useCallback(
|
||||
(savedSearch: string, updateType: 'create' | 'delete') => {
|
||||
if (updateType === 'delete') {
|
||||
const savedSearchesCopy = { ...savedSearches };
|
||||
delete savedSearchesCopy[savedSearch];
|
||||
updateSourceMetadata('savedSearches', savedSearchesCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSavedSearches = {
|
||||
...savedSearches,
|
||||
[savedSearch]: { query: query ?? undefined, filters: filtersToApply },
|
||||
};
|
||||
updateSourceMetadata('savedSearches', updatedSavedSearches);
|
||||
},
|
||||
[savedSearches, query, filtersToApply],
|
||||
);
|
||||
|
||||
const message = !isLoading ? t(SOURCE_CONTENT_TYPE_TO_ERROR_MSG_KEY[contentType]) : undefined;
|
||||
const isLocalSource = sourceId === '0';
|
||||
@@ -335,10 +373,10 @@ export function SourceMangas() {
|
||||
loadPage(lastPageNum + 1);
|
||||
}, [lastPageNum, hasNextPage, contentType]);
|
||||
|
||||
const resetFilters = useCallback(() => {
|
||||
const resetFilters = () => {
|
||||
setDialogFiltersToApply([]);
|
||||
setFiltersToApply([]);
|
||||
}, [sourceId, contentType]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (filteredOutAllItemsOfFetchedPage && hasNextPage && !loading) {
|
||||
@@ -441,6 +479,9 @@ export function SourceMangas() {
|
||||
/>
|
||||
{contentType === SourceContentType.SEARCH && (
|
||||
<SourceOptions
|
||||
savedSearches={savedSearches}
|
||||
selectSavedSearch={selectSavedSearch}
|
||||
updateSavedSearches={handleSavedSearchesUpdate}
|
||||
sourceFilter={filters}
|
||||
updateFilterValue={setDialogFiltersToApply}
|
||||
setTriggerUpdate={() => {
|
||||
|
||||
@@ -35,6 +35,19 @@ export type TranslationKey = ParseKeys;
|
||||
|
||||
export type PartialExtension = GetExtensionQuery['extension'];
|
||||
|
||||
export interface IPos {
|
||||
type: 'selectState' | 'textState' | 'checkBoxState' | 'triState' | 'sortState';
|
||||
position: number;
|
||||
state: any;
|
||||
group?: number;
|
||||
}
|
||||
|
||||
export type SavedSourceSearch = { query?: string; filters?: IPos[] };
|
||||
|
||||
export interface ISourceMetadata {
|
||||
savedSearches?: Record<string, SavedSourceSearch>;
|
||||
}
|
||||
|
||||
export interface ISource {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -82,7 +95,9 @@ export type MangaMetadataKeys = keyof IReaderSettings;
|
||||
|
||||
export type SearchMetadataKeys = keyof ISearchSettings;
|
||||
|
||||
export type AppMetadataKeys = MetadataServerSettingKeys | MangaMetadataKeys | SearchMetadataKeys;
|
||||
export type SourceMetadataKeys = keyof ISourceMetadata;
|
||||
|
||||
export type AppMetadataKeys = MetadataServerSettingKeys | MangaMetadataKeys | SearchMetadataKeys | SourceMetadataKeys;
|
||||
|
||||
export type MetadataKeyValuePair = [AppMetadataKeys, AllowedMetadataValueTypes];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user