From 6bdf59db6ab0fd1719004f064922b8ec4e624609 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Mon, 23 Mar 2026 01:12:14 -0500 Subject: [PATCH] Feat: Implemented Basic Tracker Support (Anilist, Mal, Etc) --- src/components/layout/Layout.svelte | 3 + src/components/layout/Sidebar.svelte | 3 +- src/components/pages/SeriesDetail.svelte | 150 +++++- src/components/pages/Tracking.svelte | 521 ++++++++++++++++++++ src/components/settings/Settings.svelte | 251 +++++++++- src/components/shared/TrackingPanel.svelte | 543 +++++++++++++++++++++ src/lib/queries.ts | 241 ++++++++- src/lib/types.ts | 48 +- src/store/state.svelte.ts | 2 +- 9 files changed, 1749 insertions(+), 13 deletions(-) create mode 100644 src/components/pages/Tracking.svelte create mode 100644 src/components/shared/TrackingPanel.svelte diff --git a/src/components/layout/Layout.svelte b/src/components/layout/Layout.svelte index 4c7a585..7650ecc 100644 --- a/src/components/layout/Layout.svelte +++ b/src/components/layout/Layout.svelte @@ -10,6 +10,7 @@ import GenreDrillPage from "../pages/GenreDrillPage.svelte"; import Downloads from "../pages/Downloads.svelte"; import Extensions from "../pages/Extensions.svelte"; + import Tracking from "../pages/Tracking.svelte";
@@ -33,6 +34,8 @@ {:else if store.navPage === "extensions"} + {:else if store.navPage === "tracking"} + {:else} {/if} diff --git a/src/components/layout/Sidebar.svelte b/src/components/layout/Sidebar.svelte index 60f9e72..95ef3ae 100644 --- a/src/components/layout/Sidebar.svelte +++ b/src/components/layout/Sidebar.svelte @@ -1,5 +1,5 @@ {#if store.activeManga} @@ -369,6 +413,26 @@ {/if} +
+ + +
+

{totalCount} {totalCount === 1 ? "chapter" : "chapters"}{readCount > 0 ? ` · ${readCount} read` : ""}

{#if !loadingManga && manga?.source} @@ -607,12 +671,59 @@ onMigrated={(newManga) => { setActiveManga(newManga); migrateOpen = false; cache.clear(CACHE_KEYS.LIBRARY); }} /> {/if} + +{#if trackingOpen && store.activeManga} + trackingOpen = false} + /> +{/if} + +{#if linkPickerOpen} + +{/if} {/if} diff --git a/src/components/settings/Settings.svelte b/src/components/settings/Settings.svelte index 353c55b..360fc5d 100644 --- a/src/components/settings/Settings.svelte +++ b/src/components/settings/Settings.svelte @@ -1,19 +1,20 @@ diff --git a/src/components/shared/TrackingPanel.svelte b/src/components/shared/TrackingPanel.svelte new file mode 100644 index 0000000..f9eb635 --- /dev/null +++ b/src/components/shared/TrackingPanel.svelte @@ -0,0 +1,543 @@ + + + e.key === "Escape" && onClose()} /> + + + + + + diff --git a/src/lib/queries.ts b/src/lib/queries.ts index c02397e..c287c5b 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -436,6 +436,7 @@ export const INSTALL_EXTERNAL_EXTENSION = ` } } `; + // ── Settings ────────────────────────────────────────────────────────────────── export const GET_SETTINGS = ` @@ -454,4 +455,242 @@ export const SET_EXTENSION_REPOS = ` } } } -`; \ No newline at end of file +`; + +// ── Trackers ────────────────────────────────────────────────────────────────── + +export const GET_TRACKERS = ` + query GetTrackers { + trackers { + nodes { + id + name + icon + isLoggedIn + authUrl + supportsPrivateTracking + scores + statuses { + value + name + } + } + } + } +`; + +export const GET_MANGA_TRACK_RECORDS = ` + query GetMangaTrackRecords($mangaId: Int!) { + manga(id: $mangaId) { + trackRecords { + nodes { + id + trackerId + remoteId + title + status + score + displayScore + lastChapterRead + totalChapters + remoteUrl + startDate + finishDate + private + } + } + } + } +`; + +export const SEARCH_TRACKER = ` + query SearchTracker($trackerId: Int!, $query: String!) { + searchTracker(input: { trackerId: $trackerId, query: $query }) { + trackSearches { + id + trackerId + remoteId + title + coverUrl + summary + publishingStatus + publishingType + startDate + totalChapters + trackingUrl + } + } + } +`; + +export const BIND_TRACK = ` + mutation BindTrack($mangaId: Int!, $trackerId: Int!, $remoteId: LongString!) { + bindTrack(input: { mangaId: $mangaId, trackerId: $trackerId, remoteId: $remoteId }) { + trackRecord { + id + trackerId + remoteId + title + status + score + displayScore + lastChapterRead + totalChapters + remoteUrl + startDate + finishDate + private + } + } + } +`; + +export const UPDATE_TRACK = ` + mutation UpdateTrack($recordId: Int!, $status: Int, $lastChapterRead: Float, $scoreString: String, $startDate: LongString, $finishDate: LongString, $private: Boolean) { + updateTrack(input: { recordId: $recordId, status: $status, lastChapterRead: $lastChapterRead, scoreString: $scoreString, startDate: $startDate, finishDate: $finishDate, private: $private }) { + trackRecord { + id + trackerId + status + score + displayScore + lastChapterRead + totalChapters + startDate + finishDate + private + } + } + } +`; + +export const UNBIND_TRACK = ` + mutation UnbindTrack($recordId: Int!) { + unbindTrack(input: { recordId: $recordId }) { + trackRecord { + id + } + } + } +`; + +export const FETCH_TRACK = ` + mutation FetchTrack($recordId: Int!) { + fetchTrack(input: { recordId: $recordId }) { + trackRecord { + id + trackerId + status + score + displayScore + lastChapterRead + totalChapters + startDate + finishDate + } + } + } +`; + +export const GET_ALL_TRACKER_RECORDS = ` + query GetAllTrackerRecords { + trackers { + nodes { + id + name + icon + isLoggedIn + scores + statuses { value name } + trackRecords { + nodes { + id + trackerId + title + status + displayScore + lastChapterRead + totalChapters + remoteUrl + private + manga { + id + title + thumbnailUrl + inLibrary + } + } + } + } + } + } +`; + +export const GET_TRACKER_RECORDS = ` + query GetTrackerRecords($trackerId: Int!) { + trackers(condition: { id: $trackerId }) { + nodes { + id + name + statuses { value name } + trackRecords { + nodes { + id + title + status + displayScore + lastChapterRead + totalChapters + remoteUrl + manga { + id + title + thumbnailUrl + } + } + } + } + } + } +`; + +export const LOGIN_TRACKER_OAUTH = ` + mutation LoginTrackerOAuth($trackerId: Int!, $callbackUrl: String!) { + loginTrackerOAuth(input: { trackerId: $trackerId, callbackUrl: $callbackUrl }) { + isLoggedIn + tracker { + id + name + isLoggedIn + authUrl + } + } + } +`; + +export const LOGIN_TRACKER_CREDENTIALS = ` + mutation LoginTrackerCredentials($trackerId: Int!, $username: String!, $password: String!) { + loginTrackerCredentials(input: { trackerId: $trackerId, username: $username, password: $password }) { + isLoggedIn + tracker { + id + name + isLoggedIn + authUrl + } + } + } +`; + +export const LOGOUT_TRACKER = ` + mutation LogoutTracker($trackerId: Int!) { + logoutTracker(input: { trackerId: $trackerId }) { + tracker { + id + name + isLoggedIn + authUrl + } + } + } +`; diff --git a/src/lib/types.ts b/src/lib/types.ts index f29ca1b..5fd3303 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -86,4 +86,50 @@ export interface DownloadStatus { export interface Connection { nodes: T[]; -} \ No newline at end of file +} + +export interface TrackerStatus { + value: number; + name: string; +} + +export interface Tracker { + id: number; + name: string; + icon: string; + isLoggedIn: boolean; + authUrl: string | null; + supportsPrivateTracking: boolean; + scores: string[]; + statuses: TrackerStatus[]; +} + +export interface TrackRecord { + id: number; + trackerId: number; + remoteId: string; + title: string; + status: number; + score: number; + displayScore: string; + lastChapterRead: number; + totalChapters: number; + remoteUrl: string | null; + startDate: string | null; + finishDate: string | null; + private: boolean; +} + +export interface TrackSearch { + id: number; + trackerId: number; + remoteId: string; + title: string; + coverUrl: string | null; + summary: string | null; + publishingStatus: string | null; + publishingType: string | null; + startDate: string | null; + totalChapters: number; + trackingUrl: string | null; +} diff --git a/src/store/state.svelte.ts b/src/store/state.svelte.ts index ee8fccb..c81fdf9 100644 --- a/src/store/state.svelte.ts +++ b/src/store/state.svelte.ts @@ -4,7 +4,7 @@ import { DEFAULT_KEYBINDS, type Keybinds } from "../lib/keybinds"; export type PageStyle = "single" | "double" | "longstrip"; export type FitMode = "width" | "height" | "screen" | "original"; export type LibraryFilter = "all" | "library" | "downloaded" | string; -export type NavPage = "home" | "library" | "sources" | "explore" | "downloads" | "extensions" | "history" | "search"; +export type NavPage = "home" | "library" | "sources" | "explore" | "downloads" | "extensions" | "history" | "search" | "tracking"; export type ReadingDirection = "ltr" | "rtl"; export type ChapterSortDir = "desc" | "asc"; export type Theme = "dark" | "high-contrast" | "light" | "light-contrast" | "midnight" | "warm";