Add private tracking support

This commit is contained in:
schroda
2025-07-19 18:33:33 +02:00
parent 3c4321e625
commit 0d23d8077f
11 changed files with 118 additions and 35 deletions

View File

@@ -1603,7 +1603,9 @@
"tracking": { "tracking": {
"action": { "action": {
"button": { "button": {
"add_tracking": "Add tracking" "add_tracking": "Add tracking",
"track_privately": "Track privately",
"track_publicly": "Track publicly"
}, },
"login": { "login": {
"label": { "label": {

View File

@@ -38,6 +38,7 @@ export const TRACKER_BIND_FIELDS = gql`
icon icon
supportsPrivateTracking
supportsTrackDeletion supportsTrackDeletion
scores scores
statuses { statuses {

View File

@@ -38,5 +38,6 @@ export const TRACK_RECORD_BIND_FIELDS = gql`
displayScore displayScore
startDate startDate
finishDate finishDate
private
} }
`; `;

View File

@@ -3012,11 +3012,11 @@ export type TrackerBaseFieldsFragment = { __typename?: 'TrackerType', id: number
export type TrackerSettingFieldsFragment = { __typename?: 'TrackerType', authUrl?: string | null, id: number, name: string, icon: string, isLoggedIn: boolean, isTokenExpired: boolean }; export type TrackerSettingFieldsFragment = { __typename?: 'TrackerType', authUrl?: string | null, id: number, name: string, icon: string, isLoggedIn: boolean, isTokenExpired: boolean };
export type TrackerBindFieldsFragment = { __typename?: 'TrackerType', icon: string, supportsTrackDeletion: boolean, scores: Array<string>, id: number, name: string, isLoggedIn: boolean, isTokenExpired: boolean, statuses: Array<{ __typename?: 'TrackStatusType', name: string, value: number }>, trackRecords: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string }> } }; export type TrackerBindFieldsFragment = { __typename?: 'TrackerType', icon: string, supportsPrivateTracking: boolean, supportsTrackDeletion: boolean, scores: Array<string>, id: number, name: string, isLoggedIn: boolean, isTokenExpired: boolean, statuses: Array<{ __typename?: 'TrackStatusType', name: string, value: number }>, trackRecords: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean }> } };
export type TrackRecordSearchFieldsFragment = { __typename?: 'TrackSearchType', id: number, remoteId: string, title: string, trackingUrl: string, coverUrl: string, publishingType: string, startDate: string, publishingStatus: string, summary: string, score: number, totalChapters: number }; export type TrackRecordSearchFieldsFragment = { __typename?: 'TrackSearchType', id: number, remoteId: string, title: string, trackingUrl: string, coverUrl: string, publishingType: string, startDate: string, publishingStatus: string, summary: string, score: number, totalChapters: number };
export type TrackRecordBindFieldsFragment = { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string }; export type TrackRecordBindFieldsFragment = { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean };
export type UpdaterMangaFieldsFragment = { __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } }; export type UpdaterMangaFieldsFragment = { __typename?: 'MangaUpdateType', status: MangaJobStatus, manga: { __typename?: 'MangaType', id: number, title: string, thumbnailUrl?: string | null, unreadCount: number, downloadCount: number, bookmarkCount: number, hasDuplicateChapters: boolean, chapters: { __typename?: 'ChapterNodeList', totalCount: number }, firstUnreadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, isRead: boolean, mangaId: number } | null, lastReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number, sourceOrder: number, lastReadAt: string } | null, latestFetchedChapter?: { __typename?: 'ChapterType', id: number, fetchedAt: string } | null, latestUploadedChapter?: { __typename?: 'ChapterType', id: number, uploadDate: string } | null } };
@@ -3141,7 +3141,7 @@ export type UpdateChapterMutationVariables = Exact<{
}>; }>;
export type UpdateChapterMutation = { __typename?: 'Mutation', updateChapter?: { __typename?: 'UpdateChapterPayload', chapter: { __typename?: 'ChapterType', id: number, isBookmarked?: boolean, isRead?: boolean, lastReadAt?: string, lastPageRead?: number, manga?: { __typename?: 'MangaType', id: number, unreadCount: number, bookmarkCount: number, lastReadChapter?: { __typename?: 'ChapterType', id: number } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number } | null } } } | null, deleteDownloadedChapter?: { __typename?: 'DeleteDownloadedChapterPayload', chapters: { __typename?: 'ChapterType', id: number, isDownloaded: boolean, manga: { __typename?: 'MangaType', id: number, downloadCount: number } } } | null, trackProgress?: { __typename?: 'TrackProgressPayload', trackRecords: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string }> } | null }; export type UpdateChapterMutation = { __typename?: 'Mutation', updateChapter?: { __typename?: 'UpdateChapterPayload', chapter: { __typename?: 'ChapterType', id: number, isBookmarked?: boolean, isRead?: boolean, lastReadAt?: string, lastPageRead?: number, manga?: { __typename?: 'MangaType', id: number, unreadCount: number, bookmarkCount: number, lastReadChapter?: { __typename?: 'ChapterType', id: number } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number } | null } } } | null, deleteDownloadedChapter?: { __typename?: 'DeleteDownloadedChapterPayload', chapters: { __typename?: 'ChapterType', id: number, isDownloaded: boolean, manga: { __typename?: 'MangaType', id: number, downloadCount: number } } } | null, trackProgress?: { __typename?: 'TrackProgressPayload', trackRecords: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean }> } | null };
export type UpdateChaptersMutationVariables = Exact<{ export type UpdateChaptersMutationVariables = Exact<{
input: UpdateChaptersInput; input: UpdateChaptersInput;
@@ -3155,7 +3155,7 @@ export type UpdateChaptersMutationVariables = Exact<{
}>; }>;
export type UpdateChaptersMutation = { __typename?: 'Mutation', updateChapters?: { __typename?: 'UpdateChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, isBookmarked?: boolean, isRead?: boolean, lastReadAt?: string, lastPageRead?: number, manga?: { __typename?: 'MangaType', id: number, unreadCount: number, bookmarkCount: number, lastReadChapter?: { __typename?: 'ChapterType', id: number } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number } | null } }> } | null, deleteDownloadedChapters?: { __typename?: 'DeleteDownloadedChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, isDownloaded: boolean, manga: { __typename?: 'MangaType', id: number, downloadCount: number } }> } | null, trackProgress?: { __typename?: 'TrackProgressPayload', trackRecords: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string }> } | null }; export type UpdateChaptersMutation = { __typename?: 'Mutation', updateChapters?: { __typename?: 'UpdateChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, isBookmarked?: boolean, isRead?: boolean, lastReadAt?: string, lastPageRead?: number, manga?: { __typename?: 'MangaType', id: number, unreadCount: number, bookmarkCount: number, lastReadChapter?: { __typename?: 'ChapterType', id: number } | null, latestReadChapter?: { __typename?: 'ChapterType', id: number } | null, firstUnreadChapter?: { __typename?: 'ChapterType', id: number } | null } }> } | null, deleteDownloadedChapters?: { __typename?: 'DeleteDownloadedChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, isDownloaded: boolean, manga: { __typename?: 'MangaType', id: number, downloadCount: number } }> } | null, trackProgress?: { __typename?: 'TrackProgressPayload', trackRecords: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean }> } | null };
export type ClearDownloaderMutationVariables = Exact<{ export type ClearDownloaderMutationVariables = Exact<{
input?: InputMaybe<ClearDownloaderInput>; input?: InputMaybe<ClearDownloaderInput>;
@@ -3415,13 +3415,11 @@ export type TrackerLogoutMutationVariables = Exact<{
export type TrackerLogoutMutation = { __typename?: 'Mutation', logoutTracker: { __typename?: 'LogoutTrackerPayload', tracker: { __typename?: 'TrackerType', authUrl?: string | null, id: number, name: string, icon: string, isLoggedIn: boolean, isTokenExpired: boolean } } }; export type TrackerLogoutMutation = { __typename?: 'Mutation', logoutTracker: { __typename?: 'LogoutTrackerPayload', tracker: { __typename?: 'TrackerType', authUrl?: string | null, id: number, name: string, icon: string, isLoggedIn: boolean, isTokenExpired: boolean } } };
export type TrackerBindMutationVariables = Exact<{ export type TrackerBindMutationVariables = Exact<{
mangaId: Scalars['Int']['input']; input: BindTrackInput;
remoteId: Scalars['LongString']['input'];
trackerId: Scalars['Int']['input'];
}>; }>;
export type TrackerBindMutation = { __typename?: 'Mutation', bindTrack: { __typename?: 'BindTrackPayload', trackRecord: { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, manga: { __typename?: 'MangaType', id: number, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> } } } } }; export type TrackerBindMutation = { __typename?: 'Mutation', bindTrack: { __typename?: 'BindTrackPayload', trackRecord: { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean, manga: { __typename?: 'MangaType', id: number, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> } } } } };
export type TrackerUnbindMutationVariables = Exact<{ export type TrackerUnbindMutationVariables = Exact<{
input: UnbindTrackInput; input: UnbindTrackInput;
@@ -3435,14 +3433,14 @@ export type TrackerUpdateBindMutationVariables = Exact<{
}>; }>;
export type TrackerUpdateBindMutation = { __typename?: 'Mutation', updateTrack: { __typename?: 'UpdateTrackPayload', trackRecord?: { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, manga: { __typename?: 'MangaType', id: number, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> } } } | null } }; export type TrackerUpdateBindMutation = { __typename?: 'Mutation', updateTrack: { __typename?: 'UpdateTrackPayload', trackRecord?: { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean, manga: { __typename?: 'MangaType', id: number, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, trackerId: number }> } } } | null } };
export type TrackerFetchBindMutationVariables = Exact<{ export type TrackerFetchBindMutationVariables = Exact<{
recordId: Scalars['Int']['input']; recordId: Scalars['Int']['input'];
}>; }>;
export type TrackerFetchBindMutation = { __typename?: 'Mutation', fetchTrack: { __typename?: 'FetchTrackPayload', trackRecord: { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string } } }; export type TrackerFetchBindMutation = { __typename?: 'Mutation', fetchTrack: { __typename?: 'FetchTrackPayload', trackRecord: { __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean } } };
export type UpdateLibraryMutationVariables = Exact<{ export type UpdateLibraryMutationVariables = Exact<{
input?: InputMaybe<UpdateLibraryInput>; input?: InputMaybe<UpdateLibraryInput>;
@@ -3653,7 +3651,7 @@ export type GetMangaTrackRecordsQueryVariables = Exact<{
}>; }>;
export type GetMangaTrackRecordsQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string }> } } }; export type GetMangaTrackRecordsQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, trackRecords: { __typename?: 'TrackRecordNodeList', totalCount: number, nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean }> } } };
export type GetMangaCategoriesQueryVariables = Exact<{ export type GetMangaCategoriesQueryVariables = Exact<{
id: Scalars['Int']['input']; id: Scalars['Int']['input'];
@@ -3670,7 +3668,7 @@ export type GetMangaToMigrateQueryVariables = Exact<{
}>; }>;
export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number }> } } }; export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, private: boolean }> } } };
export type GetMangasBaseQueryVariables = Exact<{ export type GetMangasBaseQueryVariables = Exact<{
after?: InputMaybe<Scalars['Cursor']['input']>; after?: InputMaybe<Scalars['Cursor']['input']>;
@@ -3790,7 +3788,7 @@ export type GetTrackersSettingsQuery = { __typename?: 'Query', trackers: { __typ
export type GetTrackersBindQueryVariables = Exact<{ [key: string]: never; }>; export type GetTrackersBindQueryVariables = Exact<{ [key: string]: never; }>;
export type GetTrackersBindQuery = { __typename?: 'Query', trackers: { __typename?: 'TrackerNodeList', totalCount: number, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null }, nodes: Array<{ __typename?: 'TrackerType', icon: string, supportsTrackDeletion: boolean, scores: Array<string>, id: number, name: string, isLoggedIn: boolean, isTokenExpired: boolean, statuses: Array<{ __typename?: 'TrackStatusType', name: string, value: number }>, trackRecords: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string }> } }> } }; export type GetTrackersBindQuery = { __typename?: 'Query', trackers: { __typename?: 'TrackerNodeList', totalCount: number, pageInfo: { __typename?: 'PageInfo', endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null }, nodes: Array<{ __typename?: 'TrackerType', icon: string, supportsPrivateTracking: boolean, supportsTrackDeletion: boolean, scores: Array<string>, id: number, name: string, isLoggedIn: boolean, isTokenExpired: boolean, statuses: Array<{ __typename?: 'TrackStatusType', name: string, value: number }>, trackRecords: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number, remoteUrl: string, title: string, status: number, lastChapterRead: number, totalChapters: number, score: number, displayScore: string, startDate: string, finishDate: string, private: boolean }> } }> } };
export type TrackerSearchQueryVariables = Exact<{ export type TrackerSearchQueryVariables = Exact<{
query: Scalars['String']['input']; query: Scalars['String']['input'];

View File

@@ -50,8 +50,8 @@ export const TRACKER_LOGOUT = gql`
export const TRACKER_BIND = gql` export const TRACKER_BIND = gql`
${TRACK_RECORD_BIND_FIELDS} ${TRACK_RECORD_BIND_FIELDS}
mutation TRACKER_BIND($mangaId: Int!, $remoteId: LongString!, $trackerId: Int!) { mutation TRACKER_BIND($input: BindTrackInput!) {
bindTrack(input: { mangaId: $mangaId, remoteId: $remoteId, trackerId: $trackerId }) { bindTrack(input: $input) {
trackRecord { trackRecord {
...TRACK_RECORD_BIND_FIELDS ...TRACK_RECORD_BIND_FIELDS
manga { manga {

View File

@@ -108,6 +108,7 @@ export const GET_MANGA_TO_MIGRATE = gql`
id id
remoteId remoteId
trackerId trackerId
private
} }
} }
} }

View File

@@ -3184,9 +3184,15 @@ export class RequestManager {
mangaId: number, mangaId: number,
trackerId: number, trackerId: number,
remoteId: string, remoteId: string,
asPrivate: boolean,
options?: MutationOptions<TrackerBindMutation, TrackerBindMutationVariables>, options?: MutationOptions<TrackerBindMutation, TrackerBindMutationVariables>,
): AbortableApolloMutationResponse<TrackerBindMutation> { ): AbortableApolloMutationResponse<TrackerBindMutation> {
return this.doRequest(GQLMethod.MUTATION, TRACKER_BIND, { mangaId, remoteId, trackerId }, options); return this.doRequest(
GQLMethod.MUTATION,
TRACKER_BIND,
{ input: { mangaId, remoteId, trackerId, private: asPrivate } },
options,
);
} }
public unbindTracker( public unbindTracker(

View File

@@ -381,8 +381,12 @@ export class Mangas {
copy: () => copy: () =>
trackBindingsToAdd.map( trackBindingsToAdd.map(
(trackRecord) => (trackRecord) =>
requestManager.bindTracker(mangaToMigrateTo.id, trackRecord.trackerId, trackRecord.remoteId) requestManager.bindTracker(
.response, mangaToMigrateTo.id,
trackRecord.trackerId,
trackRecord.remoteId,
trackRecord.private,
).response,
), ),
cleanup: () => cleanup: () =>
mode === 'migrate' mode === 'migrate'

View File

@@ -56,12 +56,14 @@ export type TTrackRecordBind = TTrackRecordBase &
| 'displayScore' | 'displayScore'
| 'startDate' | 'startDate'
| 'finishDate' | 'finishDate'
| 'private'
>; >;
export type TTrackerManga = TrackRecordSearchFieldsFragment; export type TTrackerManga = TrackRecordSearchFieldsFragment;
export type TTrackerBase = Pick<TrackerType, 'id' | 'name' | 'icon' | 'isLoggedIn' | 'isTokenExpired'>; export type TTrackerBase = Pick<TrackerType, 'id' | 'name' | 'icon' | 'isLoggedIn' | 'isTokenExpired'>;
export type TTrackerSearch = TTrackerBase & Pick<TrackerType, 'authUrl'>; export type TTrackerSearch = TTrackerBase & Pick<TrackerType, 'authUrl'>;
export type TTrackerBind = TTrackerBase & Pick<TrackerType, 'icon' | 'supportsTrackDeletion' | 'scores' | 'statuses'>; export type TTrackerBind = TTrackerBase &
Pick<TrackerType, 'icon' | 'supportsTrackDeletion' | 'supportsPrivateTracking' | 'scores' | 'statuses'>;
export type TrackerIdInfo = Pick<TrackerType, 'id'>; export type TrackerIdInfo = Pick<TrackerType, 'id'>;
export type LoggedInInfo = Pick<TrackerType, 'isLoggedIn' | 'isTokenExpired'>; export type LoggedInInfo = Pick<TrackerType, 'isLoggedIn' | 'isTokenExpired'>;
export type TrackRecordTrackerInfo = Pick<TrackRecordType, 'trackerId'>; export type TrackRecordTrackerInfo = Pick<TrackRecordType, 'trackerId'>;

View File

@@ -21,6 +21,7 @@ import InfoIcon from '@mui/icons-material/Info';
import PopupState, { bindPopover, bindTrigger } from 'material-ui-popup-state'; import PopupState, { bindPopover, bindTrigger } from 'material-ui-popup-state';
import Popover from '@mui/material/Popover'; import Popover from '@mui/material/Popover';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/feedback/EmptyViewAbsoluteCentered.tsx'; import { EmptyViewAbsoluteCentered } from '@/modules/core/components/feedback/EmptyViewAbsoluteCentered.tsx';
import { LoadingPlaceholder } from '@/modules/core/components/feedback/LoadingPlaceholder.tsx'; import { LoadingPlaceholder } from '@/modules/core/components/feedback/LoadingPlaceholder.tsx';
@@ -35,28 +36,34 @@ import { MangaType } from '@/lib/graphql/generated/graphql.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
import { Tracker, TrackerIdInfo, TTrackerBase } from '@/modules/tracker/Tracker.types.ts'; import { Tracker, TrackerIdInfo, TTrackerBind } from '@/modules/tracker/Tracker.types.ts';
import { CustomButtonIcon } from '@/modules/core/components/buttons/CustomButtonIcon.tsx';
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
const TrackButton = ({ const TrackButton = ({
mangaId, mangaId,
selectedTrackerRemoteId, selectedTrackerRemoteId,
trackerId, trackerId,
closeSearchMode, closeSearchMode,
supportsPrivateTracking,
}: { }: {
trackerId: TrackerIdInfo['id']; trackerId: TrackerIdInfo['id'];
mangaId: MangaIdInfo['id']; mangaId: MangaIdInfo['id'];
selectedTrackerRemoteId: string | undefined; selectedTrackerRemoteId: string | undefined;
closeSearchMode: () => void; closeSearchMode: () => void;
supportsPrivateTracking: boolean;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [bindTracker, bindTrackerMutation] = requestManager.useBindTracker(); const [bindTracker, bindTrackerMutation] = requestManager.useBindTracker();
const trackManga = () => { const trackManga = (asPrivate: boolean) => {
if (selectedTrackerRemoteId === undefined) { if (selectedTrackerRemoteId === undefined) {
return; return;
} }
bindTracker({ variables: { mangaId, remoteId: selectedTrackerRemoteId, trackerId } }) bindTracker({
variables: { input: { mangaId, remoteId: selectedTrackerRemoteId, trackerId, private: asPrivate } },
})
.then(() => { .then(() => {
makeToast(t('manga.action.track.add.label.success'), 'success'); makeToast(t('manga.action.track.add.label.success'), 'success');
closeSearchMode(); closeSearchMode();
@@ -74,17 +81,34 @@ const TrackButton = ({
right: 0, right: 0,
bottom: 0, bottom: 0,
paddingBottom: DIALOG_PADDING, paddingBottom: DIALOG_PADDING,
gap: 2,
px: DIALOG_PADDING,
}} }}
> >
<Button <Button
disabled={bindTrackerMutation.loading} disabled={bindTrackerMutation.loading}
size="large" size="large"
variant="contained" variant="contained"
onClick={trackManga} onClick={() => trackManga(false)}
sx={{ width: '75%' }} sx={{ flexBasis: '65%' }}
> >
{t('manga.action.track.add.label.action')} {t('manga.action.track.add.label.action')}
</Button> </Button>
{supportsPrivateTracking && (
<CustomTooltip
title={t('tracking.action.button.track_privately')}
disabled={bindTrackerMutation.loading}
>
<CustomButtonIcon
disabled={bindTrackerMutation.loading}
sx={{ flexBasis: '10%', maxWidth: '100px' }}
variant="contained"
onClick={() => trackManga(true)}
>
<VisibilityOffIcon />
</CustomButtonIcon>
</CustomTooltip>
)}
</Stack> </Stack>
); );
}; };
@@ -96,7 +120,7 @@ export const TrackerSearch = ({
trackedId, trackedId,
}: { }: {
manga: MangaIdInfo & Pick<MangaType, 'title'>; manga: MangaIdInfo & Pick<MangaType, 'title'>;
tracker: Pick<TTrackerBase, 'id'>; tracker: TTrackerBind;
closeSearchMode: () => void; closeSearchMode: () => void;
trackedId?: string; trackedId?: string;
}) => { }) => {
@@ -221,6 +245,7 @@ export const TrackerSearch = ({
trackerId={tracker.id} trackerId={tracker.id}
closeSearchMode={closeSearchMode} closeSearchMode={closeSearchMode}
selectedTrackerRemoteId={selectedTrackerRemoteId} selectedTrackerRemoteId={selectedTrackerRemoteId}
supportsPrivateTracking={tracker.supportsPrivateTracking}
/> />
)} )}
</DialogContent> </DialogContent>

View File

@@ -27,6 +27,8 @@ import Typography from '@mui/material/Typography';
import MoreVertIcon from '@mui/icons-material/MoreVert'; import MoreVertIcon from '@mui/icons-material/MoreVert';
import PopupState, { bindDialog, bindMenu, bindTrigger } from 'material-ui-popup-state'; import PopupState, { bindDialog, bindMenu, bindTrigger } from 'material-ui-popup-state';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import Badge from '@mui/material/Badge';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx'; import { CustomTooltip } from '@/modules/core/components/CustomTooltip.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';
@@ -50,7 +52,6 @@ const TrackerActiveLink = ({ children, url }: { children: React.ReactNode; url:
); );
type TTrackerActive = Pick<TTrackerBind, 'id' | 'name' | 'icon' | 'supportsTrackDeletion'>; type TTrackerActive = Pick<TTrackerBind, 'id' | 'name' | 'icon' | 'supportsTrackDeletion'>;
type TTrackRecordActive = Pick<TTrackRecordBind, 'id' | 'remoteUrl' | 'title'>;
const TrackerActiveRemoveBind = ({ const TrackerActiveRemoveBind = ({
trackerRecordId, trackerRecordId,
tracker, tracker,
@@ -139,6 +140,33 @@ const TrackerActiveRemoveBind = ({
); );
}; };
const TrackerUpdatePrivateStatus = ({
trackRecordId,
isPrivate,
closeMenu,
}: {
trackRecordId: TrackRecordType['id'];
isPrivate: boolean;
closeMenu: () => void;
}) => {
const { t } = useTranslation();
return (
<MenuItem
onClick={() => {
requestManager
.updateTrackerBind(trackRecordId, { private: !isPrivate })
.response.catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
);
closeMenu();
}}
>
{t(isPrivate ? 'tracking.action.button.track_publicly' : 'tracking.action.button.track_privately')}
</MenuItem>
);
};
type TTrackRecordActive = Pick<TTrackRecordBind, 'id' | 'remoteUrl' | 'title' | 'private'>;
const TrackerActiveHeader = ({ const TrackerActiveHeader = ({
trackRecord, trackRecord,
tracker, tracker,
@@ -157,6 +185,15 @@ const TrackerActiveHeader = ({
alignItems: 'stretch', alignItems: 'stretch',
paddingBottom: 2, paddingBottom: 2,
}} }}
>
<Badge
badgeContent={
trackRecord.private ? (
<Stack sx={{ p: '2px 6px', backgroundColor: 'primary.main', borderRadius: 100 }}>
<VisibilityOffIcon fontSize="small" sx={{ color: 'primary.contrastText' }} />
</Stack>
) : null
}
> >
<TrackerActiveLink url={trackRecord.remoteUrl}> <TrackerActiveLink url={trackRecord.remoteUrl}>
<Avatar <Avatar
@@ -166,6 +203,7 @@ const TrackerActiveHeader = ({
sx={{ width: 64, height: 64 }} sx={{ width: 64, height: 64 }}
/> />
</TrackerActiveLink> </TrackerActiveLink>
</Badge>
<ListItemButton sx={{ flexGrow: 1 }} onClick={openSearch}> <ListItemButton sx={{ flexGrow: 1 }} onClick={openSearch}>
<CustomTooltip title={trackRecord.title}> <CustomTooltip title={trackRecord.title}>
@@ -202,6 +240,11 @@ const TrackerActiveHeader = ({
onClick={() => setHideMenu(true)} onClick={() => setHideMenu(true)}
onClose={onClose} onClose={onClose}
/>, />,
<TrackerUpdatePrivateStatus
trackRecordId={trackRecord.id}
isPrivate={trackRecord.private}
closeMenu={onClose}
/>,
]} ]}
</Menu> </Menu>
</> </>