Cleanup deprecated metadata after migration
- delete deprecated metadata - save applied migration id per metadata holder
This commit is contained in:
@@ -2826,7 +2826,7 @@ export type DeleteCategoryMetadataMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type DeleteCategoryMetadataMutation = { __typename?: 'Mutation', deleteCategoryMeta?: { __typename?: 'DeleteCategoryMetaPayload', meta?: { __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string } | null, category: { __typename?: 'CategoryType', id: number, meta: Array<{ __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string }> } } | null };
|
export type DeleteCategoryMetadataMutation = { __typename?: 'Mutation', deleteCategoryMeta?: { __typename?: 'DeleteCategoryMetaPayload', meta?: { __typename?: 'CategoryMetaType', categoryId: number, key: string, value: string } | null } | null };
|
||||||
|
|
||||||
export type SetCategoryMetadataMutationVariables = Exact<{
|
export type SetCategoryMetadataMutationVariables = Exact<{
|
||||||
input: SetCategoryMetaInput;
|
input: SetCategoryMetaInput;
|
||||||
@@ -2869,7 +2869,7 @@ export type DeleteChapterMetadataMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type DeleteChapterMetadataMutation = { __typename?: 'Mutation', deleteChapterMeta?: { __typename?: 'DeleteChapterMetaPayload', meta?: { __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string } | null, chapter: { __typename?: 'ChapterType', id: number, meta: Array<{ __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string }> } } | null };
|
export type DeleteChapterMetadataMutation = { __typename?: 'Mutation', deleteChapterMeta?: { __typename?: 'DeleteChapterMetaPayload', meta?: { __typename?: 'ChapterMetaType', chapterId: number, key: string, value: string } | null } | null };
|
||||||
|
|
||||||
export type GetChapterPagesFetchMutationVariables = Exact<{
|
export type GetChapterPagesFetchMutationVariables = Exact<{
|
||||||
input: FetchChapterPagesInput;
|
input: FetchChapterPagesInput;
|
||||||
@@ -3044,7 +3044,7 @@ export type DeleteMangaMetadataMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type DeleteMangaMetadataMutation = { __typename?: 'Mutation', deleteMangaMeta?: { __typename?: 'DeleteMangaMetaPayload', meta?: { __typename?: 'MangaMetaType', mangaId: number, key: string, value: string } | null, manga: { __typename?: 'MangaType', id: number, meta: Array<{ __typename?: 'MangaMetaType', mangaId: number, key: string, value: string }> } } | null };
|
export type DeleteMangaMetadataMutation = { __typename?: 'Mutation', deleteMangaMeta?: { __typename?: 'DeleteMangaMetaPayload', meta?: { __typename?: 'MangaMetaType', mangaId: number, key: string, value: string } | null } | null };
|
||||||
|
|
||||||
export type GetMangaFetchMutationVariables = Exact<{
|
export type GetMangaFetchMutationVariables = Exact<{
|
||||||
input: FetchMangaInput;
|
input: FetchMangaInput;
|
||||||
@@ -3149,6 +3149,13 @@ export type SetSourceMetadataMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type SetSourceMetadataMutation = { __typename?: 'Mutation', setSourceMeta?: { __typename?: 'SetSourceMetaPayload', meta: { __typename?: 'SourceMetaType', sourceId: string, key: string, value: string } } | null };
|
export type SetSourceMetadataMutation = { __typename?: 'Mutation', setSourceMeta?: { __typename?: 'SetSourceMetaPayload', meta: { __typename?: 'SourceMetaType', sourceId: string, key: string, value: string } } | null };
|
||||||
|
|
||||||
|
export type DeleteSourceMetadataMutationVariables = Exact<{
|
||||||
|
input: DeleteSourceMetaInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type DeleteSourceMetadataMutation = { __typename?: 'Mutation', deleteSourceMeta?: { __typename?: 'DeleteSourceMetaPayload', meta?: { __typename?: 'SourceMetaType', sourceId: string, key: string, value: string } | null } | null };
|
||||||
|
|
||||||
export type TrackerLoginOauthMutationVariables = Exact<{
|
export type TrackerLoginOauthMutationVariables = Exact<{
|
||||||
input: LoginTrackerOAuthInput;
|
input: LoginTrackerOAuthInput;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
@@ -38,12 +38,6 @@ export const DELETE_CATEGORY_METADATA = gql`
|
|||||||
meta {
|
meta {
|
||||||
...CATEGORY_META_FIELDS
|
...CATEGORY_META_FIELDS
|
||||||
}
|
}
|
||||||
category {
|
|
||||||
id
|
|
||||||
meta {
|
|
||||||
...CATEGORY_META_FIELDS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -19,12 +19,6 @@ export const DELETE_CHAPTER_METADATA = gql`
|
|||||||
meta {
|
meta {
|
||||||
...CHAPTER_META_FIELDS
|
...CHAPTER_META_FIELDS
|
||||||
}
|
}
|
||||||
chapter {
|
|
||||||
id
|
|
||||||
meta {
|
|
||||||
...CHAPTER_META_FIELDS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -17,12 +17,6 @@ export const DELETE_MANGA_METADATA = gql`
|
|||||||
meta {
|
meta {
|
||||||
...MANGA_META_FIELDS
|
...MANGA_META_FIELDS
|
||||||
}
|
}
|
||||||
manga {
|
|
||||||
id
|
|
||||||
meta {
|
|
||||||
...MANGA_META_FIELDS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -46,3 +46,15 @@ export const SET_SOURCE_METADATA = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const DELETE_SOURCE_METADATA = gql`
|
||||||
|
${SOURCE_META_FIELDS}
|
||||||
|
|
||||||
|
mutation DELETE_SOURCE_METADATA($input: DeleteSourceMetaInput!) {
|
||||||
|
deleteSourceMeta(input: $input) {
|
||||||
|
meta {
|
||||||
|
...SOURCE_META_FIELDS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -46,12 +46,22 @@ import {
|
|||||||
CreateCategoryInput,
|
CreateCategoryInput,
|
||||||
CreateCategoryMutation,
|
CreateCategoryMutation,
|
||||||
CreateCategoryMutationVariables,
|
CreateCategoryMutationVariables,
|
||||||
|
DeleteCategoryMetadataMutation,
|
||||||
|
DeleteCategoryMetadataMutationVariables,
|
||||||
DeleteCategoryMutation,
|
DeleteCategoryMutation,
|
||||||
DeleteCategoryMutationVariables,
|
DeleteCategoryMutationVariables,
|
||||||
|
DeleteChapterMetadataMutation,
|
||||||
|
DeleteChapterMetadataMutationVariables,
|
||||||
DeleteDownloadedChapterMutation,
|
DeleteDownloadedChapterMutation,
|
||||||
DeleteDownloadedChapterMutationVariables,
|
DeleteDownloadedChapterMutationVariables,
|
||||||
DeleteDownloadedChaptersMutation,
|
DeleteDownloadedChaptersMutation,
|
||||||
DeleteDownloadedChaptersMutationVariables,
|
DeleteDownloadedChaptersMutationVariables,
|
||||||
|
DeleteGlobalMetadataMutation,
|
||||||
|
DeleteGlobalMetadataMutationVariables,
|
||||||
|
DeleteMangaMetadataMutation,
|
||||||
|
DeleteMangaMetadataMutationVariables,
|
||||||
|
DeleteSourceMetadataMutation,
|
||||||
|
DeleteSourceMetadataMutationVariables,
|
||||||
DequeueChapterDownloadMutation,
|
DequeueChapterDownloadMutation,
|
||||||
DequeueChapterDownloadMutationVariables,
|
DequeueChapterDownloadMutationVariables,
|
||||||
DequeueChapterDownloadsMutation,
|
DequeueChapterDownloadsMutation,
|
||||||
@@ -199,7 +209,7 @@ import {
|
|||||||
WebuiUpdateSubscription,
|
WebuiUpdateSubscription,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
|
||||||
import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
import { DELETE_GLOBAL_METADATA, SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
|
||||||
import {
|
import {
|
||||||
CHECK_FOR_SERVER_UPDATES,
|
CHECK_FOR_SERVER_UPDATES,
|
||||||
CHECK_FOR_WEBUI_UPDATE,
|
CHECK_FOR_WEBUI_UPDATE,
|
||||||
@@ -214,6 +224,7 @@ import {
|
|||||||
} from '@/lib/graphql/mutations/ExtensionMutation.ts';
|
} from '@/lib/graphql/mutations/ExtensionMutation.ts';
|
||||||
import { GET_MIGRATABLE_SOURCES, GET_SOURCES_LIST } from '@/lib/graphql/queries/SourceQuery.ts';
|
import { GET_MIGRATABLE_SOURCES, GET_SOURCES_LIST } from '@/lib/graphql/queries/SourceQuery.ts';
|
||||||
import {
|
import {
|
||||||
|
DELETE_MANGA_METADATA,
|
||||||
GET_MANGA_FETCH,
|
GET_MANGA_FETCH,
|
||||||
GET_MANGA_TO_MIGRATE_TO_FETCH,
|
GET_MANGA_TO_MIGRATE_TO_FETCH,
|
||||||
SET_MANGA_METADATA,
|
SET_MANGA_METADATA,
|
||||||
@@ -235,6 +246,7 @@ import {
|
|||||||
GET_CATEGORY_MANGAS,
|
GET_CATEGORY_MANGAS,
|
||||||
} from '@/lib/graphql/queries/CategoryQuery.ts';
|
} from '@/lib/graphql/queries/CategoryQuery.ts';
|
||||||
import {
|
import {
|
||||||
|
DELETE_SOURCE_METADATA,
|
||||||
GET_SOURCE_MANGAS_FETCH,
|
GET_SOURCE_MANGAS_FETCH,
|
||||||
SET_SOURCE_METADATA,
|
SET_SOURCE_METADATA,
|
||||||
UPDATE_SOURCE_PREFERENCES,
|
UPDATE_SOURCE_PREFERENCES,
|
||||||
@@ -257,6 +269,7 @@ import {
|
|||||||
GET_MANGAS_CHAPTER_IDS_WITH_STATE,
|
GET_MANGAS_CHAPTER_IDS_WITH_STATE,
|
||||||
} from '@/lib/graphql/queries/ChapterQuery.ts';
|
} from '@/lib/graphql/queries/ChapterQuery.ts';
|
||||||
import {
|
import {
|
||||||
|
DELETE_CHAPTER_METADATA,
|
||||||
GET_CHAPTER_PAGES_FETCH,
|
GET_CHAPTER_PAGES_FETCH,
|
||||||
GET_MANGA_CHAPTERS_FETCH,
|
GET_MANGA_CHAPTERS_FETCH,
|
||||||
SET_CHAPTER_METADATA,
|
SET_CHAPTER_METADATA,
|
||||||
@@ -266,6 +279,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
CREATE_CATEGORY,
|
CREATE_CATEGORY,
|
||||||
DELETE_CATEGORY,
|
DELETE_CATEGORY,
|
||||||
|
DELETE_CATEGORY_METADATA,
|
||||||
SET_CATEGORY_METADATA,
|
SET_CATEGORY_METADATA,
|
||||||
UPDATE_CATEGORY,
|
UPDATE_CATEGORY,
|
||||||
UPDATE_CATEGORY_ORDER,
|
UPDATE_CATEGORY_ORDER,
|
||||||
@@ -1152,6 +1166,34 @@ export class RequestManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public deleteGlobalMeta(
|
||||||
|
key: string,
|
||||||
|
options?: MutationOptions<DeleteGlobalMetadataMutation, DeleteGlobalMetadataMutationVariables>,
|
||||||
|
): AbortableApolloMutationResponse<DeleteGlobalMetadataMutation> {
|
||||||
|
return this.doRequest(
|
||||||
|
GQLMethod.MUTATION,
|
||||||
|
DELETE_GLOBAL_METADATA,
|
||||||
|
{ input: { key } },
|
||||||
|
{
|
||||||
|
optimisticResponse: {
|
||||||
|
__typename: 'Mutation',
|
||||||
|
deleteGlobalMeta: {
|
||||||
|
__typename: 'DeleteGlobalMetaPayload',
|
||||||
|
meta: {
|
||||||
|
__typename: 'GlobalMetaType',
|
||||||
|
key,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update(cache) {
|
||||||
|
cache.evict({ id: cache.identify({ __typename: 'GlobalMetaType', key }) });
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public useGetAbout(
|
public useGetAbout(
|
||||||
options?: QueryHookOptions<GetAboutQuery, GetAboutQueryVariables>,
|
options?: QueryHookOptions<GetAboutQuery, GetAboutQueryVariables>,
|
||||||
): AbortableApolloUseQueryResponse<GetAboutQuery, GetAboutQueryVariables> {
|
): AbortableApolloUseQueryResponse<GetAboutQuery, GetAboutQueryVariables> {
|
||||||
@@ -1448,6 +1490,36 @@ export class RequestManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public deleteSourceMeta(
|
||||||
|
sourceId: string,
|
||||||
|
key: string,
|
||||||
|
options?: MutationOptions<DeleteSourceMetadataMutation, DeleteSourceMetadataMutationVariables>,
|
||||||
|
): AbortableApolloMutationResponse<DeleteSourceMetadataMutation> {
|
||||||
|
return this.doRequest(
|
||||||
|
GQLMethod.MUTATION,
|
||||||
|
DELETE_SOURCE_METADATA,
|
||||||
|
{ input: { sourceId, key } },
|
||||||
|
{
|
||||||
|
optimisticResponse: {
|
||||||
|
__typename: 'Mutation',
|
||||||
|
deleteSourceMeta: {
|
||||||
|
__typename: 'DeleteSourceMetaPayload',
|
||||||
|
meta: {
|
||||||
|
__typename: 'SourceMetaType',
|
||||||
|
sourceId,
|
||||||
|
key,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update(cache) {
|
||||||
|
cache.evict({ id: cache.identify({ __typename: 'SourceMetaType', id: sourceId, key }) });
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public useGetSourceMangas(
|
public useGetSourceMangas(
|
||||||
input: FetchSourceMangaInput,
|
input: FetchSourceMangaInput,
|
||||||
initialPages: number = 1,
|
initialPages: number = 1,
|
||||||
@@ -1942,6 +2014,36 @@ export class RequestManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public deleteMangaMeta(
|
||||||
|
mangaId: number,
|
||||||
|
key: string,
|
||||||
|
options?: MutationOptions<DeleteMangaMetadataMutation, DeleteMangaMetadataMutationVariables>,
|
||||||
|
): AbortableApolloMutationResponse<DeleteMangaMetadataMutation> {
|
||||||
|
return this.doRequest(
|
||||||
|
GQLMethod.MUTATION,
|
||||||
|
DELETE_MANGA_METADATA,
|
||||||
|
{ input: { mangaId, key } },
|
||||||
|
{
|
||||||
|
optimisticResponse: {
|
||||||
|
__typename: 'Mutation',
|
||||||
|
deleteMangaMeta: {
|
||||||
|
__typename: 'DeleteMangaMetaPayload',
|
||||||
|
meta: {
|
||||||
|
__typename: 'MangaMetaType',
|
||||||
|
mangaId,
|
||||||
|
key,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update(cache) {
|
||||||
|
cache.evict({ id: cache.identify({ __typename: 'MangaMetaType', id: mangaId, key }) });
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public useGetChapters<Data, Variables extends OperationVariables>(
|
public useGetChapters<Data, Variables extends OperationVariables>(
|
||||||
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
document: DocumentNode | TypedDocumentNode<Data, Variables>,
|
||||||
variables: Variables,
|
variables: Variables,
|
||||||
@@ -2140,6 +2242,36 @@ export class RequestManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public deleteChapterMeta(
|
||||||
|
chapterId: number,
|
||||||
|
key: string,
|
||||||
|
options?: MutationOptions<DeleteChapterMetadataMutation, DeleteChapterMetadataMutationVariables>,
|
||||||
|
): AbortableApolloMutationResponse<DeleteChapterMetadataMutation> {
|
||||||
|
return this.doRequest(
|
||||||
|
GQLMethod.MUTATION,
|
||||||
|
DELETE_CHAPTER_METADATA,
|
||||||
|
{ input: { chapterId, key } },
|
||||||
|
{
|
||||||
|
optimisticResponse: {
|
||||||
|
__typename: 'Mutation',
|
||||||
|
deleteChapterMeta: {
|
||||||
|
__typename: 'DeleteChapterMetaPayload',
|
||||||
|
meta: {
|
||||||
|
__typename: 'ChapterMetaType',
|
||||||
|
chapterId,
|
||||||
|
key,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update(cache) {
|
||||||
|
cache.evict({ id: cache.identify({ __typename: 'ChapterMetaType', id: chapterId, key }) });
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public getChapterPageUrl(mangaId: number | string, chapterIndex: number | string, page: number): string {
|
public getChapterPageUrl(mangaId: number | string, chapterIndex: number | string, page: number): string {
|
||||||
return this.getValidImgUrlFor(
|
return this.getValidImgUrlFor(
|
||||||
`manga/${mangaId}/chapter/${chapterIndex}/page/${page}`,
|
`manga/${mangaId}/chapter/${chapterIndex}/page/${page}`,
|
||||||
@@ -2389,6 +2521,36 @@ export class RequestManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public deleteCategoryMeta(
|
||||||
|
categoryId: number,
|
||||||
|
key: string,
|
||||||
|
options?: MutationOptions<DeleteCategoryMetadataMutation, DeleteCategoryMetadataMutationVariables>,
|
||||||
|
): AbortableApolloMutationResponse<DeleteCategoryMetadataMutation> {
|
||||||
|
return this.doRequest(
|
||||||
|
GQLMethod.MUTATION,
|
||||||
|
DELETE_CATEGORY_METADATA,
|
||||||
|
{ input: { categoryId, key } },
|
||||||
|
{
|
||||||
|
optimisticResponse: {
|
||||||
|
__typename: 'Mutation',
|
||||||
|
deleteCategoryMeta: {
|
||||||
|
__typename: 'DeleteCategoryMetaPayload',
|
||||||
|
meta: {
|
||||||
|
__typename: 'CategoryMetaType',
|
||||||
|
categoryId,
|
||||||
|
key,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update(cache) {
|
||||||
|
cache.evict({ id: cache.identify({ __typename: 'CategoryMetaType', id: categoryId, key }) });
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public restoreBackupFile(
|
public restoreBackupFile(
|
||||||
file: File,
|
file: File,
|
||||||
options?: MutationOptions<RestoreBackupMutation, RestoreBackupMutationVariables>,
|
options?: MutationOptions<RestoreBackupMutation, RestoreBackupMutationVariables>,
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
||||||
import { requestUpdateCategoryMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
import { requestUpdateCategoryMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { LibraryOptions } from '@/modules/library/Library.types.ts';
|
import { LibraryOptions } from '@/modules/library/Library.types.ts';
|
||||||
import { CategoryIdInfo, CategoryMetadataKeys, ICategoryMetadata } from '@/modules/category/Category.types.ts';
|
import { CategoryIdInfo, CategoryMetadataKeys, ICategoryMetadata } from '@/modules/category/Category.types.ts';
|
||||||
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
||||||
@@ -19,9 +19,11 @@ import {
|
|||||||
AppMetadataKeys,
|
AppMetadataKeys,
|
||||||
GqlMetaHolder,
|
GqlMetaHolder,
|
||||||
Metadata,
|
Metadata,
|
||||||
|
MetadataHolder,
|
||||||
} from '@/modules/metadata/Metadata.types.ts';
|
} from '@/modules/metadata/Metadata.types.ts';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
export const getDefaultCategoryMetadata = (): ICategoryMetadata => ({
|
export const DEFAULT_CATEGORY_METADATA: ICategoryMetadata = {
|
||||||
// display options
|
// display options
|
||||||
showContinueReadingButton: false,
|
showContinueReadingButton: false,
|
||||||
showDownloadBadge: false,
|
showDownloadBadge: false,
|
||||||
@@ -39,7 +41,7 @@ export const getDefaultCategoryMetadata = (): ICategoryMetadata => ({
|
|||||||
hasDuplicateChapters: undefined,
|
hasDuplicateChapters: undefined,
|
||||||
hasTrackerBinding: {},
|
hasTrackerBinding: {},
|
||||||
hasStatus: {} as LibraryOptions['hasStatus'],
|
hasStatus: {} as LibraryOptions['hasStatus'],
|
||||||
});
|
};
|
||||||
|
|
||||||
const convertAppMetadataToGqlMetadata = (
|
const convertAppMetadataToGqlMetadata = (
|
||||||
metadata: Partial<ICategoryMetadata>,
|
metadata: Partial<ICategoryMetadata>,
|
||||||
@@ -60,20 +62,37 @@ const convertGqlMetadataToAppMetadata = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getCategoryMetadataWithDefaultValueFallback = (
|
const getCategoryMetadataWithDefaultValueFallback = (
|
||||||
meta?: Metadata,
|
meta: CategoryIdInfo & MetadataHolder,
|
||||||
defaultMetadata: ICategoryMetadata = getDefaultCategoryMetadata(),
|
defaultMetadata: ICategoryMetadata = DEFAULT_CATEGORY_METADATA,
|
||||||
applyMetadataMigration: boolean = true,
|
useEffectFn?: typeof useEffect,
|
||||||
): ICategoryMetadata =>
|
): ICategoryMetadata =>
|
||||||
convertGqlMetadataToAppMetadata(
|
convertGqlMetadataToAppMetadata(
|
||||||
getMetadataFrom({ meta }, convertAppMetadataToGqlMetadata(defaultMetadata), applyMetadataMigration),
|
getMetadataFrom('category', meta, convertAppMetadataToGqlMetadata(defaultMetadata), true, useEffectFn),
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMetadata = (
|
||||||
|
metaHolder: CategoryIdInfo & GqlMetaHolder,
|
||||||
|
defaultMetadata?: ICategoryMetadata,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
) =>
|
||||||
|
getCategoryMetadataWithDefaultValueFallback(
|
||||||
|
{ ...metaHolder, meta: convertFromGqlMeta(metaHolder.meta) },
|
||||||
|
defaultMetadata,
|
||||||
|
useEffectFn,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getCategoryMetadata = (
|
export const getCategoryMetadata = (
|
||||||
{ meta }: GqlMetaHolder = {},
|
metaHolder: CategoryIdInfo & GqlMetaHolder,
|
||||||
defaultMetadata?: ICategoryMetadata,
|
defaultMetadata?: ICategoryMetadata,
|
||||||
applyMetadataMigration?: boolean,
|
): ICategoryMetadata => getMetadata(metaHolder, defaultMetadata);
|
||||||
): ICategoryMetadata =>
|
|
||||||
getCategoryMetadataWithDefaultValueFallback(convertFromGqlMeta(meta), defaultMetadata, applyMetadataMigration);
|
export const useGetCategoryMetadata = (
|
||||||
|
metaHolder: CategoryIdInfo & GqlMetaHolder,
|
||||||
|
defaultMetadata?: ICategoryMetadata,
|
||||||
|
): ICategoryMetadata => {
|
||||||
|
const metadata = getMetadata(metaHolder, defaultMetadata, useEffect);
|
||||||
|
return useMemo(() => metadata, [metaHolder, defaultMetadata]);
|
||||||
|
};
|
||||||
|
|
||||||
export const updateCategoryMetadata = async <
|
export const updateCategoryMetadata = async <
|
||||||
MetadataKeys extends CategoryMetadataKeys = CategoryMetadataKeys,
|
MetadataKeys extends CategoryMetadataKeys = CategoryMetadataKeys,
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import Tooltip from '@mui/material/Tooltip';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
// TODO: clean up this to use a FormControl, and remove dependency on name o radio button
|
// TODO: clean up this to use a FormControl, and remove dependency on name o radio button
|
||||||
export function GridLayouts({
|
export function GridLayouts({
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
|
|
||||||
import React, { useLayoutEffect } from 'react';
|
import React, { useLayoutEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { GridLayout, useLibraryOptionsContext } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
import { useLibraryOptionsContext } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
||||||
import { IMangaGridProps, MangaGrid } from '@/modules/manga/components/MangaGrid.tsx';
|
import { IMangaGridProps, MangaGrid } from '@/modules/manga/components/MangaGrid.tsx';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
interface LibraryMangaGridProps
|
interface LibraryMangaGridProps
|
||||||
extends Required<Pick<IMangaGridProps, 'isSelectModeActive' | 'selectedMangaIds' | 'handleSelection' | 'mangas'>>,
|
extends Required<Pick<IMangaGridProps, 'isSelectModeActive' | 'selectedMangaIds' | 'handleSelection' | 'mangas'>>,
|
||||||
|
|||||||
@@ -8,19 +8,17 @@
|
|||||||
|
|
||||||
import FormLabel from '@mui/material/FormLabel';
|
import FormLabel from '@mui/material/FormLabel';
|
||||||
import RadioGroup from '@mui/material/RadioGroup';
|
import RadioGroup from '@mui/material/RadioGroup';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
import { RadioInput } from '@/modules/core/components/inputs/RadioInput.tsx';
|
import { RadioInput } from '@/modules/core/components/inputs/RadioInput.tsx';
|
||||||
import { SortRadioInput } from '@/modules/core/components/inputs/SortRadioInput.tsx';
|
import { SortRadioInput } from '@/modules/core/components/inputs/SortRadioInput.tsx';
|
||||||
import { ThreeStateCheckboxInput } from '@/modules/core/components/inputs/ThreeStateCheckboxInput.tsx';
|
import { ThreeStateCheckboxInput } from '@/modules/core/components/inputs/ThreeStateCheckboxInput.tsx';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { OptionsTabs } from '@/modules/core/components/OptionsTabs.tsx';
|
import { OptionsTabs } from '@/modules/core/components/OptionsTabs.tsx';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { Trackers } from '@/modules/tracker/services/Trackers.ts';
|
import { Trackers } from '@/modules/tracker/services/Trackers.ts';
|
||||||
import { GetTrackersSettingsQuery, MangaStatus } from '@/lib/graphql/generated/graphql.ts';
|
import { GetTrackersSettingsQuery, MangaStatus } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts';
|
import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts';
|
||||||
import { createUpdateCategoryMetadata, getCategoryMetadata } from '@/modules/category/services/CategoryMetadata.ts';
|
import { createUpdateCategoryMetadata, useGetCategoryMetadata } from '@/modules/category/services/CategoryMetadata.ts';
|
||||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||||
import {
|
import {
|
||||||
createUpdateMetadataServerSettings,
|
createUpdateMetadataServerSettings,
|
||||||
@@ -30,6 +28,7 @@ import { TranslationKey } from '@/Base.types.ts';
|
|||||||
import { LibrarySortMode } from '@/modules/library/Library.types.ts';
|
import { LibrarySortMode } from '@/modules/library/Library.types.ts';
|
||||||
import { CategoryMetadataInfo } from '@/modules/category/Category.types.ts';
|
import { CategoryMetadataInfo } from '@/modules/category/Category.types.ts';
|
||||||
import { statusToTranslationKey } from '@/modules/manga/Manga.constants.ts';
|
import { statusToTranslationKey } from '@/modules/manga/Manga.constants.ts';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = {
|
const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = {
|
||||||
filter: 'global.label.filter',
|
filter: 'global.label.filter',
|
||||||
@@ -61,7 +60,7 @@ export const LibraryOptionsPanel = ({
|
|||||||
const trackerList = requestManager.useGetTrackerList<GetTrackersSettingsQuery>(GET_TRACKERS_SETTINGS);
|
const trackerList = requestManager.useGetTrackerList<GetTrackersSettingsQuery>(GET_TRACKERS_SETTINGS);
|
||||||
const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []);
|
const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []);
|
||||||
|
|
||||||
const categoryLibraryOptions = useMemo(() => getCategoryMetadata(category), [category]);
|
const categoryLibraryOptions = useGetCategoryMetadata(category);
|
||||||
const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, () =>
|
const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, () =>
|
||||||
makeToast(t('global.error.label.failed_to_save_changes', 'error')),
|
makeToast(t('global.error.label.failed_to_save_changes', 'error')),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { getDefaultCategoryMetadata } from '@/modules/category/services/CategoryMetadata.ts';
|
import { DEFAULT_CATEGORY_METADATA } from '@/modules/category/services/CategoryMetadata.ts';
|
||||||
import { LibraryOptions } from '@/modules/library/Library.types.ts';
|
import { LibraryOptions } from '@/modules/library/Library.types.ts';
|
||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
@@ -15,14 +15,8 @@ type ContextType = {
|
|||||||
setOptions: React.Dispatch<React.SetStateAction<LibraryOptions>>;
|
setOptions: React.Dispatch<React.SetStateAction<LibraryOptions>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum GridLayout {
|
|
||||||
Compact = 0,
|
|
||||||
Comfortable = 1,
|
|
||||||
List = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LibraryOptionsContext = React.createContext<ContextType>({
|
export const LibraryOptionsContext = React.createContext<ContextType>({
|
||||||
options: getDefaultCategoryMetadata(),
|
options: DEFAULT_CATEGORY_METADATA,
|
||||||
setOptions: () => {},
|
setOptions: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
import { LibraryOptionsContext } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
import { LibraryOptionsContext } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
||||||
import { getDefaultCategoryMetadata } from '@/modules/category/services/CategoryMetadata.ts';
|
import { DEFAULT_CATEGORY_METADATA } from '@/modules/category/services/CategoryMetadata.ts';
|
||||||
import { LibraryOptions } from '@/modules/library/Library.types.ts';
|
import { LibraryOptions } from '@/modules/library/Library.types.ts';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@@ -17,10 +17,10 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LibraryOptionsContextProvider: React.FC<IProps> = ({ children }) => {
|
export const LibraryOptionsContextProvider: React.FC<IProps> = ({ children }) => {
|
||||||
const [options, setOptions] = useLocalStorage<LibraryOptions>('libraryOptions', getDefaultCategoryMetadata());
|
const [options, setOptions] = useLocalStorage<LibraryOptions>('libraryOptions', DEFAULT_CATEGORY_METADATA);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({ options: { ...getDefaultCategoryMetadata(), ...options }, setOptions }),
|
() => ({ options: { ...DEFAULT_CATEGORY_METADATA, ...options }, setOptions }),
|
||||||
[options, setOptions],
|
[options, setOptions],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { useMemo } from 'react';
|
|||||||
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
||||||
import { ChapterType, MangaType, SourceType, TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
|
import { ChapterType, MangaType, SourceType, TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { enhancedCleanup } from '@/util/Strings.ts';
|
import { enhancedCleanup } from '@/util/Strings.ts';
|
||||||
import { getCategoryMetadata } from '@/modules/category/services/CategoryMetadata.ts';
|
import { useGetCategoryMetadata } from '@/modules/category/services/CategoryMetadata.ts';
|
||||||
import { NullAndUndefined } from '@/Base.types.ts';
|
import { NullAndUndefined } from '@/Base.types.ts';
|
||||||
import { LibraryOptions, LibrarySortMode } from '@/modules/library/Library.types.ts';
|
import { LibraryOptions, LibrarySortMode } from '@/modules/library/Library.types.ts';
|
||||||
import { CategoryMetadataInfo } from '@/modules/category/Category.types.ts';
|
import { CategoryIdInfo, CategoryMetadataInfo } from '@/modules/category/Category.types.ts';
|
||||||
import { MangaChapterCountInfo, MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaChapterCountInfo, MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
|
||||||
const triStateFilter = (
|
const triStateFilter = (
|
||||||
@@ -198,6 +198,7 @@ const sortManga = <Manga extends TMangaSort>(
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_CATEGORY: CategoryIdInfo = { id: -1 };
|
||||||
export const useGetVisibleLibraryMangas = <Manga extends MangaIdInfo & TMangasFilter & TMangaSort>(
|
export const useGetVisibleLibraryMangas = <Manga extends MangaIdInfo & TMangasFilter & TMangaSort>(
|
||||||
mangas: Manga[],
|
mangas: Manga[],
|
||||||
category?: CategoryMetadataInfo,
|
category?: CategoryMetadataInfo,
|
||||||
@@ -206,7 +207,7 @@ export const useGetVisibleLibraryMangas = <Manga extends MangaIdInfo & TMangasFi
|
|||||||
showFilteredOutMessage: boolean;
|
showFilteredOutMessage: boolean;
|
||||||
} => {
|
} => {
|
||||||
const [query] = useQueryParam('query', StringParam);
|
const [query] = useQueryParam('query', StringParam);
|
||||||
const options = getCategoryMetadata(category);
|
const options = useGetCategoryMetadata(category ?? DEFAULT_CATEGORY);
|
||||||
const {
|
const {
|
||||||
hasUnreadChapters,
|
hasUnreadChapters,
|
||||||
hasDownloadedChapters,
|
hasDownloadedChapters,
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export function Library() {
|
|||||||
const activeTab: (typeof tabs)[number] | undefined = tabs.find((tab) => tab.order === tabSearchParam) ?? tabs[0];
|
const activeTab: (typeof tabs)[number] | undefined = tabs.find((tab) => tab.order === tabSearchParam) ?? tabs[0];
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setOptions(getCategoryMetadata(activeTab));
|
setOptions(getCategoryMetadata(activeTab ?? { id: -1 }));
|
||||||
}, [activeTab]);
|
}, [activeTab]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -169,7 +169,7 @@ export function Library() {
|
|||||||
setTitle(navBarTitle, title);
|
setTitle(navBarTitle, title);
|
||||||
setAction(
|
setAction(
|
||||||
<>
|
<>
|
||||||
{!isSelectModeActive && (
|
{!isSelectModeActive && activeTab && (
|
||||||
<>
|
<>
|
||||||
<AppbarSearch />
|
<AppbarSearch />
|
||||||
<LibraryToolbarMenu category={activeTab} />
|
<LibraryToolbarMenu category={activeTab} />
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import Box from '@mui/material/Box';
|
|||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { GridLayouts } from '@/modules/core/components/GridLayouts.tsx';
|
import { GridLayouts } from '@/modules/core/components/GridLayouts.tsx';
|
||||||
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
@@ -33,6 +32,7 @@ import { IMangaGridProps } from '@/modules/manga/components/MangaGrid.tsx';
|
|||||||
import { StyledGroupItemWrapper } from '@/modules/core/components/virtuoso/StyledGroupItemWrapper.tsx';
|
import { StyledGroupItemWrapper } from '@/modules/core/components/virtuoso/StyledGroupItemWrapper.tsx';
|
||||||
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
|
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
|
||||||
import { LibraryDuplicatesWorkerInput, TMangaDuplicate, TMangaDuplicates } from '@/modules/library/Library.types.ts';
|
import { LibraryDuplicatesWorkerInput, TMangaDuplicate, TMangaDuplicates } from '@/modules/library/Library.types.ts';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
export const LibraryDuplicates = () => {
|
export const LibraryDuplicates = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
|
|
||||||
import { LongPressPointerHandlers, LongPressResult } from 'use-long-press/lib/use-long-press.types';
|
import { LongPressPointerHandlers, LongPressResult } from 'use-long-press/lib/use-long-press.types';
|
||||||
import { PopupState } from 'material-ui-popup-state/hooks';
|
import { PopupState } from 'material-ui-popup-state/hooks';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
||||||
import { useManageMangaLibraryState } from '@/modules/manga/hooks/useManageMangaLibraryState.tsx';
|
import { useManageMangaLibraryState } from '@/modules/manga/hooks/useManageMangaLibraryState.tsx';
|
||||||
import { ChapterType, MangaReaderFieldsFragment, MangaType, TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
|
import { ChapterType, MangaReaderFieldsFragment, MangaType, TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { SingleModeProps } from '@/modules/manga/components/MangaActionMenuItems.tsx';
|
import { SingleModeProps } from '@/modules/manga/components/MangaActionMenuItems.tsx';
|
||||||
import { IReaderSettings } from '@/modules/reader/Reader.types';
|
import { IReaderSettings } from '@/modules/reader/Reader.types';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate';
|
export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate';
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
import { MangaCard } from '@/modules/manga/components/cards/MangaCard.tsx';
|
import { MangaCard } from '@/modules/manga/components/cards/MangaCard.tsx';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { useLocalStorage, useSessionStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage, useSessionStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
||||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/modules/core/components/buttons/StyledFab.tsx';
|
import { DEFAULT_FULL_FAB_HEIGHT } from '@/modules/core/components/buttons/StyledFab.tsx';
|
||||||
@@ -33,6 +32,7 @@ import { MangaCardProps } from '@/modules/manga/Manga.types.ts';
|
|||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
|
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
|
||||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
|
const GridContainer = React.forwardRef<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
|
||||||
<Grid {...props} ref={ref} container spacing={1}>
|
<Grid {...props} ref={ref} container spacing={1}>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import PopupState, { bindMenu } from 'material-ui-popup-state';
|
import PopupState, { bindMenu } from 'material-ui-popup-state';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
import { GridLayout, useLibraryOptionsContext } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
import { useLibraryOptionsContext } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
||||||
import { MangaActionMenuItems, SingleModeProps } from '@/modules/manga/components/MangaActionMenuItems.tsx';
|
import { MangaActionMenuItems, SingleModeProps } from '@/modules/manga/components/MangaActionMenuItems.tsx';
|
||||||
import { Menu } from '@/modules/core/components/menu/Menu.tsx';
|
import { Menu } from '@/modules/core/components/menu/Menu.tsx';
|
||||||
import { MigrateDialog } from '@/modules/migration/components/MigrateDialog.tsx';
|
import { MigrateDialog } from '@/modules/migration/components/MigrateDialog.tsx';
|
||||||
@@ -19,6 +19,7 @@ import { MangaListCard } from '@/modules/manga/components/cards/MangaListCard.ts
|
|||||||
import { MangaCardMode, MangaCardProps } from '@/modules/manga/Manga.types.ts';
|
import { MangaCardMode, MangaCardProps } from '@/modules/manga/Manga.types.ts';
|
||||||
import { ContinueReadingButton } from '@/modules/manga/components/ContinueReadingButton.tsx';
|
import { ContinueReadingButton } from '@/modules/manga/components/ContinueReadingButton.tsx';
|
||||||
import { MangaBadges } from '@/modules/manga/components/MangaBadges.tsx';
|
import { MangaBadges } from '@/modules/manga/components/MangaBadges.tsx';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
const getMangaLinkTo = (
|
const getMangaLinkTo = (
|
||||||
mode: MangaCardMode,
|
mode: MangaCardMode,
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ import { styled } from '@mui/material/styles';
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
|
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
|
||||||
import { MangaOptionButton } from '@/modules/manga/components/MangaOptionButton.tsx';
|
import { MangaOptionButton } from '@/modules/manga/components/MangaOptionButton.tsx';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { Mangas } from '@/modules/manga/services/Mangas.ts';
|
import { Mangas } from '@/modules/manga/services/Mangas.ts';
|
||||||
import { SpecificMangaCardProps } from '@/modules/manga/Manga.types.ts';
|
import { SpecificMangaCardProps } from '@/modules/manga/Manga.types.ts';
|
||||||
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
|
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
|
||||||
import { MANGA_COVER_ASPECT_RATIO } from '@/modules/manga/Manga.constants.ts';
|
import { MANGA_COVER_ASPECT_RATIO } from '@/modules/manga/Manga.constants.ts';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
const BottomGradient = styled('div')({
|
const BottomGradient = styled('div')({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -10,6 +10,55 @@ import { AppMetadataKeys, IMetadataMigration } from '@/modules/metadata/Metadata
|
|||||||
|
|
||||||
export const APP_METADATA_KEY_PREFIX = 'webUI_';
|
export const APP_METADATA_KEY_PREFIX = 'webUI_';
|
||||||
|
|
||||||
|
const APP_METADATA_OBJECT: Record<AppMetadataKeys, undefined> = {
|
||||||
|
staticNav: undefined,
|
||||||
|
showPageNumber: undefined,
|
||||||
|
loadNextOnEnding: undefined,
|
||||||
|
skipDupChapters: undefined,
|
||||||
|
fitPageToWindow: undefined,
|
||||||
|
scalePage: undefined,
|
||||||
|
readerType: undefined,
|
||||||
|
offsetFirstPage: undefined,
|
||||||
|
migration: undefined,
|
||||||
|
deleteChaptersManuallyMarkedRead: undefined,
|
||||||
|
deleteChaptersWhileReading: undefined,
|
||||||
|
deleteChaptersWithBookmark: undefined,
|
||||||
|
downloadAheadLimit: undefined,
|
||||||
|
showAddToLibraryCategorySelectDialog: undefined,
|
||||||
|
ignoreFilters: undefined,
|
||||||
|
removeMangaFromCategories: undefined,
|
||||||
|
showTabSize: undefined,
|
||||||
|
devices: undefined,
|
||||||
|
migrateChapters: undefined,
|
||||||
|
migrateCategories: undefined,
|
||||||
|
migrateTracking: undefined,
|
||||||
|
deleteChapters: undefined,
|
||||||
|
migrateSortSettings: undefined,
|
||||||
|
hideLibraryEntries: undefined,
|
||||||
|
updateProgressAfterReading: undefined,
|
||||||
|
updateProgressManualMarkRead: undefined,
|
||||||
|
webUIInformAvailableUpdate: undefined,
|
||||||
|
serverInformAvailableUpdate: undefined,
|
||||||
|
readerWidth: undefined,
|
||||||
|
savedSearches: undefined,
|
||||||
|
showContinueReadingButton: undefined,
|
||||||
|
showDownloadBadge: undefined,
|
||||||
|
showUnreadBadge: undefined,
|
||||||
|
gridLayout: undefined,
|
||||||
|
sortBy: undefined,
|
||||||
|
sortDesc: undefined,
|
||||||
|
hasDownloadedChapters: undefined,
|
||||||
|
hasBookmarkedChapters: undefined,
|
||||||
|
hasUnreadChapters: undefined,
|
||||||
|
hasDuplicateChapters: undefined,
|
||||||
|
hasTrackerBinding: undefined,
|
||||||
|
hasStatus: undefined,
|
||||||
|
customThemes: undefined,
|
||||||
|
mangaThumbnailBackdrop: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_OBJECT);
|
||||||
|
|
||||||
export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
||||||
// downloads
|
// downloads
|
||||||
'deleteChaptersManuallyMarkedRead',
|
'deleteChaptersManuallyMarkedRead',
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ export type MetadataHolder<Keys extends string = string, Values = string> = {
|
|||||||
|
|
||||||
export type AllowedMetadataValueTypes = string | boolean | number | undefined | null;
|
export type AllowedMetadataValueTypes = string | boolean | number | undefined | null;
|
||||||
|
|
||||||
|
interface MetadataAppliedMigration {
|
||||||
|
migration: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type AppMetadataKeys =
|
export type AppMetadataKeys =
|
||||||
|
| keyof MetadataAppliedMigration
|
||||||
| MetadataServerSettingKeys
|
| MetadataServerSettingKeys
|
||||||
| MangaMetadataKeys
|
| MangaMetadataKeys
|
||||||
| SearchMetadataKeys
|
| SearchMetadataKeys
|
||||||
@@ -46,3 +51,5 @@ export type AppMetadataKeys =
|
|||||||
| CategoryMetadataKeys;
|
| CategoryMetadataKeys;
|
||||||
|
|
||||||
export type MetadataKeyValuePair = [AppMetadataKeys, AllowedMetadataValueTypes];
|
export type MetadataKeyValuePair = [AppMetadataKeys, AllowedMetadataValueTypes];
|
||||||
|
|
||||||
|
export type MetadataHolderType = 'manga' | 'chapter' | 'category' | 'global' | 'source';
|
||||||
|
|||||||
33
src/modules/metadata/services/MetadataApolloCacheHandler.ts
Normal file
33
src/modules/metadata/services/MetadataApolloCacheHandler.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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 { ReadFieldFunction } from '@apollo/client/cache/core/types/common';
|
||||||
|
import { Reference } from '@apollo/client/utilities';
|
||||||
|
|
||||||
|
export const updateMetadataList = (
|
||||||
|
key: string,
|
||||||
|
existingMetas: Reference[] | undefined,
|
||||||
|
readField: ReadFieldFunction,
|
||||||
|
createMetaRef: () => Reference | undefined,
|
||||||
|
deleted: boolean = false,
|
||||||
|
): (Reference | undefined)[] | undefined => {
|
||||||
|
if (!existingMetas) {
|
||||||
|
return existingMetas;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleted) {
|
||||||
|
return existingMetas.filter((metaRef: Reference) => readField('key', metaRef) !== key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists = existingMetas.some((metaRef: Reference) => readField('key', metaRef) === key);
|
||||||
|
if (exists) {
|
||||||
|
return existingMetas;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...existingMetas, createMetaRef()];
|
||||||
|
};
|
||||||
@@ -111,7 +111,8 @@ export const applyMetadataMigrations = (meta?: Metadata): Metadata | undefined =
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const migrationToMetadata: [number, Metadata][] = [[0, meta]];
|
const appliedMigrationId = Number.isNaN(Number(meta.migration)) ? 0 : Math.max(0, Number(meta.migration) - 1);
|
||||||
|
const migrationToMetadata: [number, Metadata][] = [[appliedMigrationId, meta]];
|
||||||
|
|
||||||
METADATA_MIGRATIONS.forEach((migration, index) => {
|
METADATA_MIGRATIONS.forEach((migration, index) => {
|
||||||
const migrationId = index + 1;
|
const migrationId = index + 1;
|
||||||
|
|||||||
@@ -6,16 +6,43 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { APP_METADATA_KEY_PREFIX, GLOBAL_METADATA_KEYS } from '@/modules/metadata/Metadata.constants.ts';
|
import { useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
APP_METADATA_KEY_PREFIX,
|
||||||
|
GLOBAL_METADATA_KEYS,
|
||||||
|
METADATA_MIGRATIONS,
|
||||||
|
VALID_APP_METADATA_KEYS,
|
||||||
|
} from '@/modules/metadata/Metadata.constants.ts';
|
||||||
import { DEFAULT_DEVICE, getActiveDevice } from '@/modules/device/services/Device.ts';
|
import { DEFAULT_DEVICE, getActiveDevice } from '@/modules/device/services/Device.ts';
|
||||||
import { applyMetadataMigrations } from '@/modules/metadata/services/MetadataMigrations.ts';
|
import { applyMetadataMigrations } from '@/modules/metadata/services/MetadataMigrations.ts';
|
||||||
import { convertValueFromMetadata } from '@/modules/metadata/services/MetadataConverter.ts';
|
import { convertToGqlMeta, convertValueFromMetadata } from '@/modules/metadata/services/MetadataConverter.ts';
|
||||||
import {
|
import {
|
||||||
AllowedMetadataValueTypes,
|
AllowedMetadataValueTypes,
|
||||||
AppMetadataKeys,
|
AppMetadataKeys,
|
||||||
Metadata,
|
Metadata,
|
||||||
MetadataHolder,
|
MetadataHolder,
|
||||||
|
MetadataHolderType,
|
||||||
|
MetadataKeyValuePair,
|
||||||
} from '@/modules/metadata/Metadata.types.ts';
|
} from '@/modules/metadata/Metadata.types.ts';
|
||||||
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
import {
|
||||||
|
requestDeleteCategoryMetadata,
|
||||||
|
requestDeleteChapterMetadata,
|
||||||
|
requestDeleteMangaMetadata,
|
||||||
|
requestDeleteServerMetadata,
|
||||||
|
requestDeleteSourceMetadata,
|
||||||
|
requestUpdateCategoryMetadata,
|
||||||
|
requestUpdateChapterMetadata,
|
||||||
|
requestUpdateMangaMetadata,
|
||||||
|
requestUpdateServerMetadata,
|
||||||
|
requestUpdateSourceMetadata,
|
||||||
|
} from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
||||||
|
import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
|
||||||
|
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
|
export const extractOriginalKey = (key: string): string => key.split('_').slice(-1)[0];
|
||||||
|
|
||||||
export const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => {
|
export const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => {
|
||||||
const isGlobalMetadataKey = GLOBAL_METADATA_KEYS.includes(key as AppMetadataKeys);
|
const isGlobalMetadataKey = GLOBAL_METADATA_KEYS.includes(key as AppMetadataKeys);
|
||||||
@@ -33,7 +60,9 @@ export const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends
|
|||||||
defaultValue?: Value,
|
defaultValue?: Value,
|
||||||
applyMigrations: boolean = true,
|
applyMigrations: boolean = true,
|
||||||
): Value | undefined => {
|
): Value | undefined => {
|
||||||
const metadata = applyMigrations ? applyMetadataMigrations(meta) : meta;
|
const requiresMigration = Number(meta?.migration) !== METADATA_MIGRATIONS.length;
|
||||||
|
const doMigration = requiresMigration && applyMigrations;
|
||||||
|
const metadata = doMigration ? applyMetadataMigrations(meta) : meta;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
metadata === undefined ||
|
metadata === undefined ||
|
||||||
@@ -46,24 +75,187 @@ export const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends
|
|||||||
return convertValueFromMetadata(metadata[getMetadataKey(key)]);
|
return convertValueFromMetadata(metadata[getMetadataKey(key)]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMetadataFrom = <METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
const getMetadataUpdateFunction = (
|
||||||
{ meta }: MetadataHolder,
|
type: MetadataHolderType,
|
||||||
|
metadataHolder:
|
||||||
|
| MetadataHolder
|
||||||
|
| (MangaIdInfo & MetadataHolder)
|
||||||
|
| (ChapterIdInfo & MetadataHolder)
|
||||||
|
| (CategoryIdInfo & MetadataHolder)
|
||||||
|
| (Pick<SourceType, 'id'> & MetadataHolder),
|
||||||
|
): ((appliedMigration: [MetadataKeyValuePair]) => Promise<void[]>) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'global':
|
||||||
|
return (appliedMigration) => requestUpdateServerMetadata(appliedMigration);
|
||||||
|
case 'manga':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestUpdateMangaMetadata(
|
||||||
|
{ id: (metadataHolder as MangaIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
case 'chapter':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestUpdateChapterMetadata(
|
||||||
|
{ id: (metadataHolder as ChapterIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
case 'category':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestUpdateCategoryMetadata(
|
||||||
|
{ id: (metadataHolder as CategoryIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
case 'source':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestUpdateSourceMetadata(
|
||||||
|
{
|
||||||
|
id: (metadataHolder as Pick<SourceType, 'id'>).id,
|
||||||
|
meta: convertToGqlMeta(metadataHolder.meta),
|
||||||
|
},
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "type" (${type})`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMetadataDeleteFunction = (
|
||||||
|
type: MetadataHolderType,
|
||||||
|
metadataHolder:
|
||||||
|
| MetadataHolder
|
||||||
|
| (MangaIdInfo & MetadataHolder)
|
||||||
|
| (ChapterIdInfo & MetadataHolder)
|
||||||
|
| (CategoryIdInfo & MetadataHolder)
|
||||||
|
| (Pick<SourceType, 'id'> & MetadataHolder),
|
||||||
|
): ((metadataToDelete: AppMetadataKeys[]) => Promise<void[]>) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'global':
|
||||||
|
return (appliedMigration) => requestDeleteServerMetadata(appliedMigration);
|
||||||
|
case 'manga':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestDeleteMangaMetadata(
|
||||||
|
{ id: (metadataHolder as MangaIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
case 'chapter':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestDeleteChapterMetadata(
|
||||||
|
{ id: (metadataHolder as ChapterIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
case 'category':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestDeleteCategoryMetadata(
|
||||||
|
{ id: (metadataHolder as CategoryIdInfo).id, meta: convertToGqlMeta(metadataHolder.meta) },
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
case 'source':
|
||||||
|
return (appliedMigration) =>
|
||||||
|
requestDeleteSourceMetadata(
|
||||||
|
{
|
||||||
|
id: (metadataHolder as Pick<SourceType, 'id'>).id,
|
||||||
|
meta: convertToGqlMeta(metadataHolder.meta),
|
||||||
|
},
|
||||||
|
appliedMigration,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "type" (${type})`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent spamming requests due to frequent metadata reads while the migration hasn't been commited to the server yet
|
||||||
|
*/
|
||||||
|
const commitedMigrations = new Set<string>();
|
||||||
|
|
||||||
|
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||||
|
type: 'global',
|
||||||
|
metadataHolder: MetadataHolder,
|
||||||
metadataWithDefaultValues: METADATA,
|
metadataWithDefaultValues: METADATA,
|
||||||
applyMigrations?: boolean,
|
applyMigrations?: boolean,
|
||||||
): METADATA => {
|
useEffectFn?: typeof useEffect,
|
||||||
|
): METADATA;
|
||||||
|
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||||
|
type: 'manga',
|
||||||
|
metadataHolder: MangaIdInfo & MetadataHolder,
|
||||||
|
metadataWithDefaultValues: METADATA,
|
||||||
|
applyMigrations?: boolean,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): METADATA;
|
||||||
|
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||||
|
type: 'chapter',
|
||||||
|
metadataHolder: ChapterIdInfo & MetadataHolder,
|
||||||
|
metadataWithDefaultValues: METADATA,
|
||||||
|
applyMigrations?: boolean,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): METADATA;
|
||||||
|
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||||
|
type: 'category',
|
||||||
|
metadataHolder: CategoryIdInfo & MetadataHolder,
|
||||||
|
metadataWithDefaultValues: METADATA,
|
||||||
|
applyMigrations?: boolean,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): METADATA;
|
||||||
|
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||||
|
type: 'source',
|
||||||
|
metadataHolder: Pick<SourceType, 'id'> & MetadataHolder,
|
||||||
|
metadataWithDefaultValues: METADATA,
|
||||||
|
applyMigrations?: boolean,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): METADATA;
|
||||||
|
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||||
|
type: MetadataHolderType,
|
||||||
|
metadataHolder:
|
||||||
|
| MetadataHolder
|
||||||
|
| (MangaIdInfo & MetadataHolder)
|
||||||
|
| (ChapterIdInfo & MetadataHolder)
|
||||||
|
| (CategoryIdInfo & MetadataHolder)
|
||||||
|
| (Pick<SourceType, 'id'> & MetadataHolder),
|
||||||
|
metadataWithDefaultValues: METADATA,
|
||||||
|
applyMigrations?: boolean,
|
||||||
|
useEffectFn: typeof useEffect = (fn: () => void) => fn(),
|
||||||
|
): METADATA {
|
||||||
|
const wasMigrated =
|
||||||
|
!!metadataHolder?.meta &&
|
||||||
|
!!applyMigrations &&
|
||||||
|
Number(getMetadataValueFrom(metadataHolder, 'migration')) !== METADATA_MIGRATIONS.length;
|
||||||
const appMetadata = {} as METADATA;
|
const appMetadata = {} as METADATA;
|
||||||
|
|
||||||
Object.entries(metadataWithDefaultValues).forEach(([key, defaultValue]) => {
|
Object.entries(metadataWithDefaultValues).forEach(([key, defaultValue]) => {
|
||||||
appMetadata[key as AppMetadataKeys] = getMetadataValueFrom(
|
appMetadata[key as AppMetadataKeys] = getMetadataValueFrom(
|
||||||
{ meta },
|
metadataHolder,
|
||||||
key as AppMetadataKeys,
|
key as AppMetadataKeys,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
applyMigrations,
|
applyMigrations,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffectFn(() => {
|
||||||
|
const itemMigrationKey = `${type}_${type === 'global' ? '' : (metadataHolder as { id: any }).id}`;
|
||||||
|
const commitMigration = !commitedMigrations.has(itemMigrationKey);
|
||||||
|
if (wasMigrated && commitMigration) {
|
||||||
|
const metadataToDelete = Object.keys(metadataHolder.meta ?? {})
|
||||||
|
.map(extractOriginalKey)
|
||||||
|
.filter((key) => !VALID_APP_METADATA_KEYS.includes(key)) as AppMetadataKeys[];
|
||||||
|
|
||||||
|
commitedMigrations.add(itemMigrationKey);
|
||||||
|
|
||||||
|
getMetadataDeleteFunction(
|
||||||
|
type,
|
||||||
|
metadataHolder,
|
||||||
|
)(metadataToDelete)
|
||||||
|
.then(() =>
|
||||||
|
getMetadataUpdateFunction(type, metadataHolder)([['migration', METADATA_MIGRATIONS.length]]),
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
defaultPromiseErrorHandler(`MetadataReader#getMetadataFromServer`)(error);
|
||||||
|
commitedMigrations.delete(itemMigrationKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return appMetadata;
|
return appMetadata;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getAppMetadataFrom = (meta: Metadata, appPrefix: string = APP_METADATA_KEY_PREFIX): Metadata => {
|
export const getAppMetadataFrom = (meta: Metadata, appPrefix: string = APP_METADATA_KEY_PREFIX): Metadata => {
|
||||||
const appMetadata: Metadata = {};
|
const appMetadata: Metadata = {};
|
||||||
|
|||||||
@@ -15,12 +15,11 @@ import {
|
|||||||
AllowedMetadataValueTypes,
|
AllowedMetadataValueTypes,
|
||||||
AppMetadataKeys,
|
AppMetadataKeys,
|
||||||
GqlMetaHolder,
|
GqlMetaHolder,
|
||||||
|
MetadataHolderType,
|
||||||
MetadataKeyValuePair,
|
MetadataKeyValuePair,
|
||||||
} from '@/modules/metadata/Metadata.types.ts';
|
} from '@/modules/metadata/Metadata.types.ts';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
|
||||||
type MetadataHolderType = 'manga' | 'chapter' | 'category' | 'global' | 'source';
|
|
||||||
|
|
||||||
export const requestUpdateMetadataValue = async (
|
export const requestUpdateMetadataValue = async (
|
||||||
metadataHolder: GqlMetaHolder,
|
metadataHolder: GqlMetaHolder,
|
||||||
holderType: MetadataHolderType,
|
holderType: MetadataHolderType,
|
||||||
@@ -80,3 +79,62 @@ export const requestUpdateSourceMetadata = async (
|
|||||||
source: Pick<SourceType, 'id'> & GqlMetaHolder,
|
source: Pick<SourceType, 'id'> & GqlMetaHolder,
|
||||||
keysToValue: MetadataKeyValuePair[],
|
keysToValue: MetadataKeyValuePair[],
|
||||||
): Promise<void[]> => requestUpdateMetadata(source, 'source', keysToValue);
|
): Promise<void[]> => requestUpdateMetadata(source, 'source', keysToValue);
|
||||||
|
|
||||||
|
export const requestDeleteMetadataValue = async (
|
||||||
|
metadataHolder: GqlMetaHolder,
|
||||||
|
holderType: MetadataHolderType,
|
||||||
|
key: AppMetadataKeys,
|
||||||
|
): Promise<void> => {
|
||||||
|
const metadataKey = getMetadataKey(key);
|
||||||
|
|
||||||
|
switch (holderType) {
|
||||||
|
case 'category':
|
||||||
|
await requestManager.deleteCategoryMeta((metadataHolder as CategoryIdInfo).id, metadataKey).response;
|
||||||
|
break;
|
||||||
|
case 'chapter':
|
||||||
|
await requestManager.deleteChapterMeta((metadataHolder as ChapterIdInfo).id, metadataKey).response;
|
||||||
|
break;
|
||||||
|
case 'global':
|
||||||
|
await requestManager.deleteGlobalMeta(metadataKey).response;
|
||||||
|
break;
|
||||||
|
case 'manga':
|
||||||
|
await requestManager.deleteMangaMeta((metadataHolder as MangaIdInfo).id, metadataKey).response;
|
||||||
|
break;
|
||||||
|
case 'source':
|
||||||
|
await requestManager.deleteSourceMeta((metadataHolder as Pick<SourceType, 'id'>).id, metadataKey).response;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`requestDeleteMetadataValue: unknown holderType "${holderType}"`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function requestDeleteMetadata(
|
||||||
|
metadataHolder: GqlMetaHolder,
|
||||||
|
holderType: MetadataHolderType,
|
||||||
|
keys: AppMetadataKeys[],
|
||||||
|
): Promise<void[]> {
|
||||||
|
return Promise.all(keys.map((key) => requestDeleteMetadataValue(metadataHolder, holderType, key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestDeleteServerMetadata = async (keys: AppMetadataKeys[]): Promise<void[]> =>
|
||||||
|
requestDeleteMetadata({}, 'global', keys);
|
||||||
|
|
||||||
|
export const requestDeleteMangaMetadata = async (
|
||||||
|
manga: MangaIdInfo & GqlMetaHolder,
|
||||||
|
keys: AppMetadataKeys[],
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(manga, 'manga', keys);
|
||||||
|
|
||||||
|
export const requestDeleteChapterMetadata = async (
|
||||||
|
chapter: ChapterIdInfo & GqlMetaHolder,
|
||||||
|
keys: AppMetadataKeys[],
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(chapter, 'chapter', keys);
|
||||||
|
|
||||||
|
export const requestDeleteCategoryMetadata = async (
|
||||||
|
category: CategoryIdInfo & GqlMetaHolder,
|
||||||
|
keys: AppMetadataKeys[],
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(category, 'category', keys);
|
||||||
|
|
||||||
|
export const requestDeleteSourceMetadata = async (
|
||||||
|
source: Pick<SourceType, 'id'> & GqlMetaHolder,
|
||||||
|
keys: AppMetadataKeys[],
|
||||||
|
): Promise<void[]> => requestDeleteMetadata(source, 'source', keys);
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import { LoadingPlaceholder } from '@/modules/core/components/placeholder/Loadin
|
|||||||
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { GridLayouts } from '@/modules/core/components/GridLayouts.tsx';
|
import { GridLayouts } from '@/modules/core/components/GridLayouts.tsx';
|
||||||
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { GetSourceMigratableQuery, GetSourceMigratableQueryVariables } from '@/lib/graphql/generated/graphql.ts';
|
import { GetSourceMigratableQuery, GetSourceMigratableQueryVariables } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { GET_SOURCE_MIGRATABLE } from '@/lib/graphql/queries/SourceQuery.ts';
|
import { GET_SOURCE_MIGRATABLE } from '@/lib/graphql/queries/SourceQuery.ts';
|
||||||
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
import { SOURCE_BASE_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
||||||
import { BaseMangaGrid } from '@/modules/manga/components/BaseMangaGrid.tsx';
|
import { BaseMangaGrid } from '@/modules/manga/components/BaseMangaGrid.tsx';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
export const Migrate = () => {
|
export const Migrate = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ export interface IReaderSettings {
|
|||||||
readerWidth: number;
|
readerWidth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UndefinedReaderSettings = {
|
|
||||||
[setting in keyof IReaderSettings]: IReaderSettings[setting] | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IReaderPage {
|
export interface IReaderPage {
|
||||||
index: number;
|
index: number;
|
||||||
src: string;
|
src: string;
|
||||||
|
|||||||
@@ -9,19 +9,14 @@
|
|||||||
import { useContext, useLayoutEffect } from 'react';
|
import { useContext, useLayoutEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { requestUpdateServerMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
import { requestUpdateServerMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
import {
|
import { useDefaultReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
||||||
checkAndHandleMissingStoredReaderSettings,
|
|
||||||
useDefaultReaderSettings,
|
|
||||||
} from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
|
||||||
import { ReaderSettingsOptions } from '@/modules/reader/components/ReaderSettingsOptions.tsx';
|
import { ReaderSettingsOptions } from '@/modules/reader/components/ReaderSettingsOptions.tsx';
|
||||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||||
import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/Reader.constants.ts';
|
|
||||||
import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
|
import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
|
||||||
import { convertToGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
|
||||||
import { AllowedMetadataValueTypes } from '@/modules/metadata/Metadata.types.ts';
|
import { AllowedMetadataValueTypes } from '@/modules/metadata/Metadata.types.ts';
|
||||||
|
|
||||||
export function DefaultReaderSettings() {
|
export function DefaultReaderSettings() {
|
||||||
@@ -38,7 +33,6 @@ export function DefaultReaderSettings() {
|
|||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
metadata,
|
|
||||||
settings,
|
settings,
|
||||||
loading,
|
loading,
|
||||||
request: { error, refetch },
|
request: { error, refetch },
|
||||||
@@ -66,12 +60,6 @@ export function DefaultReaderSettings() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAndHandleMissingStoredReaderSettings(
|
|
||||||
{ meta: convertToGqlMeta(metadata)! },
|
|
||||||
'server',
|
|
||||||
DEFAULT_READER_SETTINGS,
|
|
||||||
).catch(defaultPromiseErrorHandler('DefaultReaderSettings::checkAndHandleMissingStoredReaderSettings'));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReaderSettingsOptions
|
<ReaderSettingsOptions
|
||||||
setSettingValue={setSettingValue}
|
setSettingValue={setSettingValue}
|
||||||
|
|||||||
@@ -12,11 +12,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import {
|
import { getReaderSettingsFor, useDefaultReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
||||||
checkAndHandleMissingStoredReaderSettings,
|
|
||||||
getReaderSettingsFor,
|
|
||||||
useDefaultReaderSettings,
|
|
||||||
} from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
|
||||||
import { requestUpdateMangaMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
import { requestUpdateMangaMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
import { HorizontalPager } from '@/modules/reader/components/pager/HorizontalPager.tsx';
|
import { HorizontalPager } from '@/modules/reader/components/pager/HorizontalPager.tsx';
|
||||||
import { PageNumber } from '@/modules/reader/components/page/PageNumber.tsx';
|
import { PageNumber } from '@/modules/reader/components/page/PageNumber.tsx';
|
||||||
@@ -219,7 +215,7 @@ export function Reader() {
|
|||||||
const error = mangaError ?? chapterError ?? chaptersError ?? pagesError;
|
const error = mangaError ?? chapterError ?? chaptersError ?? pagesError;
|
||||||
|
|
||||||
const { settings: defaultSettings, loading: areDefaultSettingsLoading } = useDefaultReaderSettings();
|
const { settings: defaultSettings, loading: areDefaultSettingsLoading } = useDefaultReaderSettings();
|
||||||
const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings));
|
const [settings, setSettings] = useState(defaultSettings);
|
||||||
|
|
||||||
const { settings: metadataSettings } = useMetadataServerSettings();
|
const { settings: metadataSettings } = useMetadataServerSettings();
|
||||||
|
|
||||||
@@ -419,9 +415,6 @@ export function Reader() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!areDefaultSettingsLoading && !isMangaLoading) {
|
if (!areDefaultSettingsLoading && !isMangaLoading) {
|
||||||
checkAndHandleMissingStoredReaderSettings(manga, 'manga', defaultSettings).catch(
|
|
||||||
defaultPromiseErrorHandler('Reader::checkAndHandleMissingStoredReaderSettings'),
|
|
||||||
);
|
|
||||||
setSettings(getReaderSettingsFor(manga, defaultSettings));
|
setSettings(getReaderSettingsFor(manga, defaultSettings));
|
||||||
}
|
}
|
||||||
}, [areDefaultSettingsLoading, isMangaLoading]);
|
}, [areDefaultSettingsLoading, isMangaLoading]);
|
||||||
|
|||||||
@@ -6,37 +6,69 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import {
|
|
||||||
requestUpdateMangaMetadata,
|
|
||||||
requestUpdateServerMetadata,
|
|
||||||
} from '@/modules/metadata/services/MetadataUpdater.ts';
|
|
||||||
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
|
||||||
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/Reader.constants.ts';
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/Reader.constants.ts';
|
||||||
import { IReaderSettings, UndefinedReaderSettings } from '@/modules/reader/Reader.types.ts';
|
import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
|
||||||
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
||||||
import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
|
import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
|
||||||
import { GqlMetaHolder, Metadata, MetadataKeyValuePair } from '@/modules/metadata/Metadata.types.ts';
|
import { GqlMetaHolder, Metadata, MetadataHolder, MetadataHolderType } from '@/modules/metadata/Metadata.types.ts';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
|
||||||
const getReaderSettingsWithDefaultValueFallback = <DefaultSettings extends IReaderSettings | UndefinedReaderSettings>(
|
function getReaderSettingsWithDefaultValueFallback<DefaultSettings extends IReaderSettings>(
|
||||||
meta?: Metadata,
|
type: 'global',
|
||||||
|
metadataHolder: MetadataHolder,
|
||||||
|
defaultSettings?: DefaultSettings,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): DefaultSettings;
|
||||||
|
function getReaderSettingsWithDefaultValueFallback<DefaultSettings extends IReaderSettings>(
|
||||||
|
type: 'manga',
|
||||||
|
metadataHolder: MangaIdInfo & MetadataHolder,
|
||||||
|
defaultSettings?: DefaultSettings,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): DefaultSettings;
|
||||||
|
function getReaderSettingsWithDefaultValueFallback<DefaultSettings extends IReaderSettings>(
|
||||||
|
type: Extract<MetadataHolderType, 'global' | 'manga'>,
|
||||||
|
metadataHolder: (MangaIdInfo & MetadataHolder) | MetadataHolder,
|
||||||
defaultSettings: DefaultSettings = DEFAULT_READER_SETTINGS as DefaultSettings,
|
defaultSettings: DefaultSettings = DEFAULT_READER_SETTINGS as DefaultSettings,
|
||||||
applyMetadataMigration: boolean = true,
|
useEffectFn?: typeof useEffect,
|
||||||
): DefaultSettings => getMetadataFrom({ meta }, defaultSettings, applyMetadataMigration);
|
): DefaultSettings {
|
||||||
|
return getMetadataFrom(
|
||||||
|
type as Parameters<typeof getMetadataFrom>[0],
|
||||||
|
metadataHolder as Parameters<typeof getMetadataFrom>[1],
|
||||||
|
defaultSettings,
|
||||||
|
true,
|
||||||
|
useEffectFn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const getReaderSettingsFromMetadata = (
|
const getSettings = (
|
||||||
meta?: Metadata,
|
metaHolder: MangaIdInfo & GqlMetaHolder,
|
||||||
defaultSettings?: IReaderSettings,
|
defaultSettings?: IReaderSettings,
|
||||||
applyMetadataMigration?: boolean,
|
useEffectFn?: typeof useEffect,
|
||||||
): IReaderSettings => getReaderSettingsWithDefaultValueFallback(meta, defaultSettings, applyMetadataMigration);
|
) =>
|
||||||
|
getReaderSettingsWithDefaultValueFallback(
|
||||||
|
'manga',
|
||||||
|
{
|
||||||
|
...metaHolder,
|
||||||
|
meta: convertFromGqlMeta(metaHolder.meta),
|
||||||
|
},
|
||||||
|
defaultSettings,
|
||||||
|
useEffectFn,
|
||||||
|
);
|
||||||
|
|
||||||
export const getReaderSettingsFor = (
|
export const getReaderSettingsFor = (
|
||||||
{ meta }: GqlMetaHolder = {},
|
metaHolder: MangaIdInfo & GqlMetaHolder,
|
||||||
defaultSettings?: IReaderSettings,
|
defaultSettings: IReaderSettings,
|
||||||
applyMetadataMigration?: boolean,
|
): IReaderSettings => getSettings(metaHolder, defaultSettings);
|
||||||
): IReaderSettings => getReaderSettingsFromMetadata(convertFromGqlMeta(meta), defaultSettings, applyMetadataMigration);
|
|
||||||
|
export const useGetReaderSettingsFor = (
|
||||||
|
metaHolder: MangaIdInfo & GqlMetaHolder,
|
||||||
|
defaultSettings: IReaderSettings,
|
||||||
|
): IReaderSettings => {
|
||||||
|
const settings = getSettings(metaHolder, defaultSettings, useEffect);
|
||||||
|
return useMemo(() => settings, [metaHolder, defaultSettings]);
|
||||||
|
};
|
||||||
|
|
||||||
export const useDefaultReaderSettings = (): {
|
export const useDefaultReaderSettings = (): {
|
||||||
metadata?: Metadata;
|
metadata?: Metadata;
|
||||||
@@ -47,7 +79,9 @@ export const useDefaultReaderSettings = (): {
|
|||||||
const request = requestManager.useGetGlobalMeta({ notifyOnNetworkStatusChange: true });
|
const request = requestManager.useGetGlobalMeta({ notifyOnNetworkStatusChange: true });
|
||||||
const { data, loading } = request;
|
const { data, loading } = request;
|
||||||
const metadata = useMemo(() => convertFromGqlMeta(data?.metas.nodes), [data?.metas.nodes]);
|
const metadata = useMemo(() => convertFromGqlMeta(data?.metas.nodes), [data?.metas.nodes]);
|
||||||
const settings = useMemo(() => getReaderSettingsWithDefaultValueFallback<IReaderSettings>(metadata), [metadata]);
|
const metaHolder: MetadataHolder = useMemo(() => ({ meta: metadata }), [metadata]);
|
||||||
|
const tmpSettings = getReaderSettingsWithDefaultValueFallback('global', metaHolder, undefined, useEffect);
|
||||||
|
const settings = useMemo(() => tmpSettings, [metaHolder]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -59,55 +93,3 @@ export const useDefaultReaderSettings = (): {
|
|||||||
[metadata, settings, loading, request],
|
[metadata, settings, loading, request],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves all missing reader settings from the metadata to the server
|
|
||||||
*
|
|
||||||
* @param metadataHolder
|
|
||||||
* @param metadataHolderType
|
|
||||||
* @param defaultSettings
|
|
||||||
*/
|
|
||||||
export const checkAndHandleMissingStoredReaderSettings = async (
|
|
||||||
metadataHolder: Required<GqlMetaHolder> | MetaType[],
|
|
||||||
metadataHolderType: 'manga' | 'server',
|
|
||||||
defaultSettings: IReaderSettings,
|
|
||||||
): Promise<void | void[]> => {
|
|
||||||
const getMeta = () => (Array.isArray(metadataHolder) ? metadataHolder : metadataHolder.meta);
|
|
||||||
const meta = convertFromGqlMeta(getMeta())!;
|
|
||||||
const settingsToCheck = getReaderSettingsWithDefaultValueFallback(
|
|
||||||
meta,
|
|
||||||
{
|
|
||||||
staticNav: undefined,
|
|
||||||
showPageNumber: undefined,
|
|
||||||
loadNextOnEnding: undefined,
|
|
||||||
skipDupChapters: undefined,
|
|
||||||
fitPageToWindow: undefined,
|
|
||||||
scalePage: undefined,
|
|
||||||
readerType: undefined,
|
|
||||||
offsetFirstPage: undefined,
|
|
||||||
readerWidth: undefined,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
const newSettings = getReaderSettingsFor({ meta: getMeta() }, defaultSettings);
|
|
||||||
|
|
||||||
const undefinedSettings = Object.entries(settingsToCheck).filter((setting) => setting[1] === undefined);
|
|
||||||
|
|
||||||
const settingsToUpdate: MetadataKeyValuePair[] = [];
|
|
||||||
undefinedSettings.forEach((setting) => {
|
|
||||||
const key = setting[0] as keyof IReaderSettings;
|
|
||||||
|
|
||||||
settingsToUpdate.push([key, newSettings[key]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!undefinedSettings.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadataHolderType === 'manga') {
|
|
||||||
await requestUpdateMangaMetadata(metadataHolder as MangaIdInfo & GqlMetaHolder, settingsToUpdate);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await requestUpdateServerMetadata(settingsToUpdate);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { requestUpdateServerMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
import { requestUpdateServerMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
||||||
@@ -48,10 +48,10 @@ export const convertMetadataToSettings = (
|
|||||||
const getMetadataServerSettingsWithDefaultFallback = (
|
const getMetadataServerSettingsWithDefaultFallback = (
|
||||||
meta?: Metadata,
|
meta?: Metadata,
|
||||||
defaultSettings: MetadataServerSettings = SERVER_SETTINGS_METADATA_DEFAULT,
|
defaultSettings: MetadataServerSettings = SERVER_SETTINGS_METADATA_DEFAULT,
|
||||||
applyMetadataMigration: boolean = true,
|
useEffectFn?: typeof useEffect,
|
||||||
): MetadataServerSettings =>
|
): MetadataServerSettings =>
|
||||||
convertMetadataToSettings(
|
convertMetadataToSettings(
|
||||||
getMetadataFrom({ meta }, convertSettingsToMetadata(defaultSettings), applyMetadataMigration),
|
getMetadataFrom('global', { meta }, convertSettingsToMetadata(defaultSettings), true, useEffectFn),
|
||||||
);
|
);
|
||||||
export const useMetadataServerSettings = (): {
|
export const useMetadataServerSettings = (): {
|
||||||
metadata?: Metadata;
|
metadata?: Metadata;
|
||||||
@@ -62,7 +62,8 @@ export const useMetadataServerSettings = (): {
|
|||||||
const request = requestManager.useGetGlobalMeta({ notifyOnNetworkStatusChange: true });
|
const request = requestManager.useGetGlobalMeta({ notifyOnNetworkStatusChange: true });
|
||||||
const { data, loading } = request;
|
const { data, loading } = request;
|
||||||
const metadata = useMemo(() => convertFromGqlMeta(data?.metas.nodes), [data?.metas.nodes]);
|
const metadata = useMemo(() => convertFromGqlMeta(data?.metas.nodes), [data?.metas.nodes]);
|
||||||
const settings = useMemo(() => getMetadataServerSettingsWithDefaultFallback(metadata), [metadata]);
|
const tmpSettings = getMetadataServerSettingsWithDefaultFallback(metadata, undefined, useEffect);
|
||||||
|
const settings = useMemo(() => tmpSettings, [metadata]);
|
||||||
|
|
||||||
return useMemo(() => ({ metadata, settings, loading, request }), [metadata, settings, loading, request]);
|
return useMemo(() => ({ metadata, settings, loading, request }), [metadata, settings, loading, request]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { GridLayouts } from '@/modules/core/components/GridLayouts.tsx';
|
import { GridLayouts } from '@/modules/core/components/GridLayouts.tsx';
|
||||||
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
export function SourceGridLayout() {
|
export function SourceGridLayout() {
|
||||||
const [sourceGridLayout, setSourceGridLayout] = useLocalStorage('source-grid-layout', GridLayout.Compact);
|
const [sourceGridLayout, setSourceGridLayout] = useLocalStorage('source-grid-layout', GridLayout.Compact);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
AbortableApolloUseMutationPaginatedResponse,
|
AbortableApolloUseMutationPaginatedResponse,
|
||||||
SPECIAL_ED_SOURCES,
|
SPECIAL_ED_SOURCES,
|
||||||
} from '@/lib/requests/RequestManager.ts';
|
} from '@/lib/requests/RequestManager.ts';
|
||||||
import { GridLayout } from '@/modules/library/contexts/LibraryOptionsContext.tsx';
|
|
||||||
import { SourceGridLayout } from '@/modules/source/components/SourceGridLayout.tsx';
|
import { SourceGridLayout } from '@/modules/source/components/SourceGridLayout.tsx';
|
||||||
import { AppbarSearch } from '@/modules/core/components/AppbarSearch.tsx';
|
import { AppbarSearch } from '@/modules/core/components/AppbarSearch.tsx';
|
||||||
import { SourceOptions } from '@/modules/source/components/SourceOptions.tsx';
|
import { SourceOptions } from '@/modules/source/components/SourceOptions.tsx';
|
||||||
@@ -35,13 +34,14 @@ import {
|
|||||||
GetSourceBrowseQueryVariables,
|
GetSourceBrowseQueryVariables,
|
||||||
GetSourceMangasFetchMutation,
|
GetSourceMangasFetchMutation,
|
||||||
GetSourceMangasFetchMutationVariables,
|
GetSourceMangasFetchMutationVariables,
|
||||||
|
SourceType,
|
||||||
} from '@/lib/graphql/generated/graphql.ts';
|
} from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
||||||
import { useLocalStorage, useSessionStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage, useSessionStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||||
import { getGridSnapshotKey } from '@/modules/manga/components/MangaGrid.tsx';
|
import { getGridSnapshotKey } from '@/modules/manga/components/MangaGrid.tsx';
|
||||||
import { createUpdateSourceMetadata, getSourceMetadata } from '@/modules/source/services/SourceMetadata.ts';
|
import { createUpdateSourceMetadata, useGetSourceMetadata } from '@/modules/source/services/SourceMetadata.ts';
|
||||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||||
import { GET_SOURCE_BROWSE } from '@/lib/graphql/queries/SourceQuery.ts';
|
import { GET_SOURCE_BROWSE } from '@/lib/graphql/queries/SourceQuery.ts';
|
||||||
import { TranslationKey } from '@/Base.types.ts';
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
@@ -50,6 +50,9 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'
|
|||||||
import { EmptyView } from '@/modules/core/components/placeholder/EmptyView.tsx';
|
import { EmptyView } from '@/modules/core/components/placeholder/EmptyView.tsx';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
import { GridLayout } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
|
const DEFAULT_SOURCE: Pick<SourceType, 'id'> = { id: '-1' };
|
||||||
|
|
||||||
const ContentTypeMenu = styled('div')(({ theme }) => ({
|
const ContentTypeMenu = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -285,8 +288,8 @@ export function SourceMangas() {
|
|||||||
const source = sourceData?.source;
|
const source = sourceData?.source;
|
||||||
|
|
||||||
const filters = source?.filters ?? [];
|
const filters = source?.filters ?? [];
|
||||||
const { savedSearches = {} } = useMemo(() => getSourceMetadata(source), [source, source?.meta]);
|
const { savedSearches = {} } = useGetSourceMetadata(source ?? DEFAULT_SOURCE);
|
||||||
const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(source ?? { id: sourceId }, () =>
|
const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(source ?? { id: '-1' }, () =>
|
||||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error'),
|
makeToast(t('global.error.label.failed_to_save_changes'), 'error'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
||||||
import { requestUpdateSourceMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
import { requestUpdateSourceMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
@@ -20,6 +21,10 @@ import {
|
|||||||
Metadata,
|
Metadata,
|
||||||
} from '@/modules/metadata/Metadata.types.ts';
|
} from '@/modules/metadata/Metadata.types.ts';
|
||||||
|
|
||||||
|
const DEFAULT_SOURCE_METADATA: ISourceMetadata = {
|
||||||
|
savedSearches: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const convertAppMetadataToGqlMetadata = (
|
const convertAppMetadataToGqlMetadata = (
|
||||||
metadata: Partial<ISourceMetadata>,
|
metadata: Partial<ISourceMetadata>,
|
||||||
): Metadata<string, AllowedMetadataValueTypes> => ({
|
): Metadata<string, AllowedMetadataValueTypes> => ({
|
||||||
@@ -27,18 +32,35 @@ const convertAppMetadataToGqlMetadata = (
|
|||||||
savedSearches: metadata.savedSearches ? JSON.stringify(metadata.savedSearches) : undefined,
|
savedSearches: metadata.savedSearches ? JSON.stringify(metadata.savedSearches) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const convertGqlMetadataToAppMetadata = (
|
const convertGqlMetadataToAppMetadata = (
|
||||||
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
||||||
): ISourceMetadata => ({
|
): ISourceMetadata => ({
|
||||||
...(metadata as unknown as ISourceMetadata),
|
...(metadata as unknown as ISourceMetadata),
|
||||||
savedSearches: jsonSaveParse<ISourceMetadata['savedSearches']>(metadata.savedSearches as string) ?? undefined,
|
savedSearches: jsonSaveParse<ISourceMetadata['savedSearches']>(metadata.savedSearches as string) ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getSourceMetadata = ({ meta }: GqlMetaHolder = {}, applyMetadataMigration?: boolean): ISourceMetadata =>
|
const getMetadata = (
|
||||||
|
metaHolder: Pick<SourceType, 'id'> & GqlMetaHolder,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): ISourceMetadata =>
|
||||||
convertGqlMetadataToAppMetadata(
|
convertGqlMetadataToAppMetadata(
|
||||||
getMetadataFrom({ meta: convertFromGqlMeta(meta) }, { savedSearches: undefined }, applyMetadataMigration),
|
getMetadataFrom(
|
||||||
|
'source',
|
||||||
|
{ ...metaHolder, meta: convertFromGqlMeta(metaHolder.meta) },
|
||||||
|
convertAppMetadataToGqlMetadata(DEFAULT_SOURCE_METADATA),
|
||||||
|
true,
|
||||||
|
useEffectFn,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getSourceMetadata = (metaHolder: Pick<SourceType, 'id'> & GqlMetaHolder): ISourceMetadata =>
|
||||||
|
getMetadata(metaHolder);
|
||||||
|
|
||||||
|
export const useGetSourceMetadata = (metaHolder: Pick<SourceType, 'id'> & GqlMetaHolder): ISourceMetadata => {
|
||||||
|
const metadata = getMetadata(metaHolder, useEffect);
|
||||||
|
return useMemo(() => metadata, [metaHolder]);
|
||||||
|
};
|
||||||
|
|
||||||
export const updateSourceMetadata = async <
|
export const updateSourceMetadata = async <
|
||||||
MetadataKeys extends SourceMetadataKeys = SourceMetadataKeys,
|
MetadataKeys extends SourceMetadataKeys = SourceMetadataKeys,
|
||||||
MetadataKey extends MetadataKeys = MetadataKeys,
|
MetadataKey extends MetadataKeys = MetadataKeys,
|
||||||
|
|||||||
Reference in New Issue
Block a user