Fix: Library Filtering + GQL Cleanup P.1

This commit is contained in:
Youwes09
2026-06-07 00:18:45 -05:00
parent ed4c11ca7e
commit 79e5548879
9 changed files with 160 additions and 245 deletions
+57 -22
View File
@@ -2,9 +2,9 @@
import { getAdapter } from '$lib/request-manager' import { getAdapter } from '$lib/request-manager'
import { libraryState } from '$lib/state/library.svelte' import { libraryState } from '$lib/state/library.svelte'
import type { LibrarySortOption, LibraryContentFilter, LibraryStatusFilter } from '$lib/state/library.svelte' import type { LibrarySortOption, LibraryContentFilter, LibraryStatusFilter } from '$lib/state/library.svelte'
import { startLibraryUpdate } from '$lib/components/library/lib/libraryUpdater'
import { addToast } from '$lib/state/notifications.svelte' import { addToast } from '$lib/state/notifications.svelte'
import { updateSettings, settingsState } from '$lib/state/settings.svelte' import { updateSettings, settingsState } from '$lib/state/settings.svelte'
import { readerState } from '$lib/state/reader.svelte'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
import LibraryToolbar from '$lib/components/library/LibraryToolbar.svelte' import LibraryToolbar from '$lib/components/library/LibraryToolbar.svelte'
import LibraryGrid from '$lib/components/library/LibraryGrid.svelte' import LibraryGrid from '$lib/components/library/LibraryGrid.svelte'
@@ -23,13 +23,17 @@
const DT_TAB = 'application/x-moku-tab' const DT_TAB = 'application/x-moku-tab'
const COMPLETED_NAME = 'Completed' const COMPLETED_NAME = 'Completed'
let cancelUpdate: (() => void) | null = null let statusPollTimer: ReturnType<typeof setTimeout> | null = null
let refreshDoneTimer: ReturnType<typeof setTimeout> | null = null let refreshDoneTimer: ReturnType<typeof setTimeout> | null = null
const UPDATE_STATUS_POLL_MS = 2_000
let ctx: { x: number; y: number; manga: Manga } | null = $state(null) let ctx: { x: number; y: number; manga: Manga } | null = $state(null)
let emptyCtx: { x: number; y: number } | null = $state(null) let emptyCtx: { x: number; y: number } | null = $state(null)
let bulkWorking: boolean = $state(false) let bulkWorking: boolean = $state(false)
let sortPanelOpen: boolean = $state(false)
let filterPanelOpen: boolean = $state(false)
let activeDragKind: 'tab' | null = $state(null) let activeDragKind: 'tab' | null = $state(null)
let dragInsertIdx = $state(-1) let dragInsertIdx = $state(-1)
let dragTabId: string|null = $state(null) let dragTabId: string|null = $state(null)
@@ -42,6 +46,9 @@
$effect(() => { libraryState.syncFromSettings(settingsState.settings) }) $effect(() => { libraryState.syncFromSettings(settingsState.settings) })
$effect(() => { libraryState.tab; libraryState.exitSelect() }) $effect(() => { libraryState.tab; libraryState.exitSelect() })
$effect(() => { libraryState.guardTab() }) $effect(() => { libraryState.guardTab() })
$effect(() => {
if (readerState.activeManga === null) loadLibrary()
})
async function loadLibrary() { async function loadLibrary() {
libraryState.loading = true libraryState.loading = true
@@ -197,33 +204,57 @@
} finally { bulkWorking = false } } finally { bulkWorking = false }
} }
function stopStatusPolling() {
if (!statusPollTimer) return
clearTimeout(statusPollTimer)
statusPollTimer = null
}
async function startRefresh() { async function startRefresh() {
if (libraryState.refreshing) return if (libraryState.refreshing) return
libraryState.refreshing = true libraryState.refreshing = true
libraryState.refreshProgress = { finished: 0, total: 0 } libraryState.refreshProgress = { finished: 0, total: 0 }
cancelUpdate = startLibraryUpdate({ try {
onProgress(p) { libraryState.refreshProgress = p }, await getAdapter().checkForUpdates()
async onDone({ newChapters, totalUpdated }) { } catch (e) {
cancelUpdate = null libraryState.refreshing = false
await loadLibrary() addToast({ kind: 'error', title: 'Update failed', body: String(e) })
libraryState.refreshing = false return
libraryState.refreshDone = true }
if (refreshDoneTimer) clearTimeout(refreshDoneTimer)
refreshDoneTimer = setTimeout(() => { libraryState.refreshDone = false }, 2500) const tick = async () => {
if (newChapters > 0) { statusPollTimer = null
addToast({ kind: 'success', title: 'Library updated', body: `${newChapters} new chapter${newChapters !== 1 ? 's' : ''} across ${totalUpdated} series` }) try {
} else { const statusRes = await getAdapter().getLibraryUpdateStatus()
addToast({ kind: 'info', title: 'Already up to date' }) const wasRunning = libraryState.refreshing
libraryState.refreshProgress = {
finished: statusRes.finishedJobs ?? 0,
total: statusRes.totalJobs ?? 0,
} }
},
onError() { libraryState.refreshing = false; cancelUpdate = null }, if (statusRes.isRunning) {
}) statusPollTimer = setTimeout(tick, UPDATE_STATUS_POLL_MS)
} else if (wasRunning) {
libraryState.refreshing = false
libraryState.refreshDone = true
if (refreshDoneTimer) clearTimeout(refreshDoneTimer)
refreshDoneTimer = setTimeout(() => { libraryState.refreshDone = false }, 2500)
await loadLibrary()
addToast({ kind: 'info', title: 'Library updated' })
}
} catch {
if (libraryState.refreshing) statusPollTimer = setTimeout(tick, UPDATE_STATUS_POLL_MS)
}
}
statusPollTimer = setTimeout(tick, UPDATE_STATUS_POLL_MS)
} }
async function cancelRefresh() { async function cancelRefresh() {
if (!libraryState.refreshing) return if (!libraryState.refreshing) return
cancelUpdate?.(); cancelUpdate = null stopStatusPolling()
try { await getAdapter().stopLibraryUpdate() } catch {} try { await getAdapter().stopLibraryUpdate() } catch {}
libraryState.refreshing = false libraryState.refreshing = false
libraryState.refreshProgress = { finished: 0, total: 0 } libraryState.refreshProgress = { finished: 0, total: 0 }
@@ -370,7 +401,7 @@
visibleCategories={libraryState.visibleCategories} visibleCategories={libraryState.visibleCategories}
visibleTabIds={libraryState.visibleTabIds} visibleTabIds={libraryState.visibleTabIds}
counts={libraryState.counts} counts={libraryState.counts}
query={libraryState.filter.query} search={libraryState.filter.query}
refreshing={libraryState.refreshing} refreshing={libraryState.refreshing}
refreshProgress={libraryState.refreshProgress} refreshProgress={libraryState.refreshProgress}
refreshDone={libraryState.refreshDone} refreshDone={libraryState.refreshDone}
@@ -379,13 +410,17 @@
{dragInsertIdx} {dragInsertIdx}
{dragTabId} {dragTabId}
{dragOverTabId} {dragOverTabId}
{sortPanelOpen}
{filterPanelOpen}
onTabChange={(t) => libraryState.tab = t} onTabChange={(t) => libraryState.tab = t}
onQuery={(q) => libraryState.filter.query = q} onSearchChange={(q) => libraryState.filter.query = q}
onSortChange={(mode) => libraryState.setTabSort(libraryState.tab, mode)} onSortChange={(mode) => libraryState.setTabSort(libraryState.tab, mode)}
onSortDirToggle={() => libraryState.toggleTabSortDir(libraryState.tab)} onSortDirToggle={() => libraryState.toggleTabSortDir(libraryState.tab)}
onSortPanelToggle={() => sortPanelOpen = !sortPanelOpen}
onStatusChange={(s) => libraryState.setTabStatus(libraryState.tab, s)} onStatusChange={(s) => libraryState.setTabStatus(libraryState.tab, s)}
onFilterToggle={(f) => libraryState.toggleTabFilter(libraryState.tab, f)} onFilterToggle={(f) => libraryState.toggleTabFilter(libraryState.tab, f)}
onFiltersClear={() => libraryState.clearTabFilters(libraryState.tab)} onFiltersClear={() => libraryState.clearTabFilters(libraryState.tab)}
onFilterPanelToggle={() => filterPanelOpen = !filterPanelOpen}
onRefresh={startRefresh} onRefresh={startRefresh}
onCancelRefresh={cancelRefresh} onCancelRefresh={cancelRefresh}
onRefreshCategory={refreshCategory} onRefreshCategory={refreshCategory}
@@ -212,14 +212,14 @@
</div> </div>
<LibraryFilters <LibraryFilters
{tabStatus} status={tabStatus}
{tabFilters} filters={tabFilters}
{hasActiveFilters} hasActive={hasActiveFilters}
{filterPanelOpen} open={filterPanelOpen}
onToggle={onFilterPanelToggle}
{onStatusChange} {onStatusChange}
{onFilterToggle} {onFilterToggle}
{onFiltersClear} onClear={onFiltersClear}
{onFilterPanelToggle}
/> />
</div> </div>
</div> </div>
@@ -1,125 +0,0 @@
import { getAdapter } from '$lib/request-manager'
const POLL_INTERVAL_MS = 2000
const POLL_INITIAL_MS = 500
export interface UpdateProgress {
finished: number
total: number
skippedManga: number
skippedCategories: number
}
export interface UpdateResult {
entries: UpdateEntry[]
totalUpdated: number
newChapters: number
}
export interface UpdateEntry {
mangaId: number
mangaTitle: string
thumbnailUrl: string
newChapters: number
checkedAt: number
}
export interface LibraryUpdaterCallbacks {
onProgress: (p: UpdateProgress) => void
onDone: (r: UpdateResult) => void
onError: (e?: unknown) => void
}
function buildEntries(
mangaUpdates: { status: string; manga: { id: number; title: string; thumbnailUrl: string; unreadCount: number } }[]
): UpdateEntry[] {
const byManga = new Map<number, UpdateEntry>()
for (const u of mangaUpdates) {
if (u.status !== 'UPDATED') continue
const existing = byManga.get(u.manga.id)
if (existing) {
existing.newChapters++
} else {
byManga.set(u.manga.id, {
mangaId: u.manga.id,
mangaTitle: u.manga.title,
thumbnailUrl: u.manga.thumbnailUrl,
newChapters: 1,
checkedAt: Date.now(),
})
}
}
return [...byManga.values()]
}
export function startLibraryUpdate(callbacks: LibraryUpdaterCallbacks): () => void {
let timer: ReturnType<typeof setTimeout> | null = null
let cancelled = false
function cancel() {
cancelled = true
if (timer) { clearTimeout(timer); timer = null }
}
async function run() {
let jobsStarted = false
try {
const status = await getAdapter().checkForUpdates()
if (cancelled) return
const { jobsInfo } = status
jobsStarted = jobsInfo.totalJobs > 0
callbacks.onProgress({
finished: jobsInfo.finishedJobs,
total: jobsInfo.totalJobs,
skippedManga: jobsInfo.skippedMangasCount,
skippedCategories: jobsInfo.skippedCategoriesCount,
})
if (!jobsStarted || !jobsInfo.isRunning) {
callbacks.onDone({ entries: [], totalUpdated: 0, newChapters: 0 })
return
}
} catch (e) {
console.error('[libraryUpdater] failed to start update', e)
if (!cancelled) callbacks.onError(e)
return
}
function poll() {
getAdapter().getLibraryUpdateStatus()
.then(d => {
if (cancelled) return
const { jobsInfo, mangaUpdates } = d
if (jobsInfo.totalJobs > 0) jobsStarted = true
callbacks.onProgress({
finished: jobsInfo.finishedJobs,
total: jobsInfo.totalJobs,
skippedManga: jobsInfo.skippedMangasCount,
skippedCategories: jobsInfo.skippedCategoriesCount,
})
if (!jobsInfo.isRunning && jobsStarted) {
const entries = buildEntries(mangaUpdates)
const newChapters = entries.reduce((s, e) => s + e.newChapters, 0)
callbacks.onDone({ entries, totalUpdated: entries.length, newChapters })
return
}
timer = setTimeout(poll, POLL_INTERVAL_MS)
})
.catch(e => {
console.error('[libraryUpdater] poll error', e)
if (!cancelled) callbacks.onError(e)
})
}
timer = setTimeout(poll, POLL_INITIAL_MS)
}
run()
return cancel
}
+2 -13
View File
@@ -12,6 +12,7 @@
import { clampZoom, captureZoomAnchor, restoreZoomAnchor } from "$lib/components/reader/lib/zoomHelpers"; import { clampZoom, captureZoomAnchor, restoreZoomAnchor } from "$lib/components/reader/lib/zoomHelpers";
import { loadChapter, scheduleResumeDismiss } from "$lib/components/reader/lib/chapterLoader"; import { loadChapter, scheduleResumeDismiss } from "$lib/components/reader/lib/chapterLoader";
import { historyState } from "$lib/state/history.svelte"; import { historyState } from "$lib/state/history.svelte";
import { getAdapter } from "$lib/request-manager";
import type { ReaderSettings } from "$lib/state/reader.svelte"; import type { ReaderSettings } from "$lib/state/reader.svelte";
import ReaderControls from "$lib/components/reader/ReaderControls.svelte"; import ReaderControls from "$lib/components/reader/ReaderControls.svelte";
import PageView from "$lib/components/reader/PageView.svelte"; import PageView from "$lib/components/reader/PageView.svelte";
@@ -380,19 +381,7 @@
const toQueue = list.slice(idx + 1, idx + 1 + prefs.downloadAhead) const toQueue = list.slice(idx + 1, idx + 1 + prefs.downloadAhead)
.filter(c => !c.downloaded && !c.read) .filter(c => !c.downloaded && !c.read)
.map(c => c.id); .map(c => c.id);
if (toQueue.length) { if (toQueue.length) getAdapter().enqueueDownloads(toQueue.map(String)).catch(console.error);
const DL = `mutation EnqueueDl($ids: [Int!]!) { enqueueChaptersDownloads(input: { ids: $ids }) { downloadStatus { queue { chapter { id } } } } }`;
const base = settingsState.settings.serverUrl ?? "http://localhost:4567";
const headers: Record<string, string> = { "Content-Type": "application/json" };
const mode = settingsState.settings.serverAuthMode ?? "NONE";
if (mode === "BASIC_AUTH") {
const u = settingsState.settings.serverAuthUser?.trim() ?? "";
const p = settingsState.settings.serverAuthPass?.trim() ?? "";
if (u && p) headers["Authorization"] = `Basic ${btoa(`${u}:${p}`)}`;
}
fetch(`${base}/api/graphql`, { method: "POST", headers, body: JSON.stringify({ query: DL, variables: { ids: toQueue } }) })
.catch(console.error);
}
} }
} }
}); });
+29 -25
View File
@@ -3,10 +3,11 @@
X, CaretLeft, CaretRight, CaretUp, CaretDown, X, CaretLeft, CaretRight, CaretUp, CaretDown,
MagnifyingGlassMinus, MagnifyingGlassPlus, MagnifyingGlassMinus, MagnifyingGlassPlus,
Bookmark, MapPin, Download, Check, GearSix, Sliders, Bookmark, MapPin, Download, Check, GearSix, Sliders,
ArrowsOut, ArrowsIn, ArrowsOut, ArrowsIn, Minus,
} from "phosphor-svelte"; } from "phosphor-svelte";
import { readerState, MARKER_COLORS, MARKER_COLOR_HEX, ZOOM_STEP, ZOOM_MIN, ZOOM_MAX } from "$lib/state/reader.svelte"; import { readerState, MARKER_COLORS, MARKER_COLOR_HEX, ZOOM_STEP, ZOOM_MIN, ZOOM_MAX } from "$lib/state/reader.svelte";
import { settingsState } from "$lib/state/settings.svelte"; import { getAdapter } from "$lib/request-manager";
import { platformService } from "$lib/platform-service";
import { fly } from "svelte/transition"; import { fly } from "svelte/transition";
import { cubicOut, cubicIn } from "svelte/easing"; import { cubicOut, cubicIn } from "svelte/easing";
import type { Chapter } from "$lib/types"; import type { Chapter } from "$lib/types";
@@ -52,24 +53,6 @@
const queueable = $derived(adjacent.remaining.filter(c => !c.downloaded)); const queueable = $derived(adjacent.remaining.filter(c => !c.downloaded));
async function gqlMutation(query: string, variables: Record<string, unknown>): Promise<void> {
const base = settingsState.settings.serverUrl ?? "http://localhost:4567";
const headers: Record<string, string> = { "Content-Type": "application/json" };
const mode = settingsState.settings.serverAuthMode ?? "NONE";
if (mode === "BASIC_AUTH") {
const u = settingsState.settings.serverAuthUser?.trim() ?? "";
const p = settingsState.settings.serverAuthPass?.trim() ?? "";
if (u && p) headers["Authorization"] = `Basic ${btoa(`${u}:${p}`)}`;
}
const res = await fetch(`${base}/api/graphql`, { method: "POST", headers, body: JSON.stringify({ query, variables }) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
if (json.errors?.length) throw new Error(json.errors[0].message);
}
const ENQUEUE_ONE = `mutation EnqueueOne($id: Int!) { fetchChapterPages(input: { chapterId: $id }) { chapter { id } } }`;
const ENQUEUE_MANY = `mutation EnqueueMany($ids: [Int!]!) { enqueueChaptersDownloads(input: { ids: $ids }) { downloadStatus { queue { chapter { id } } } } }`;
async function runDl(fn: () => Promise<void>) { async function runDl(fn: () => Promise<void>) {
readerState.dlBusy = true; readerState.dlBusy = true;
try { await fn(); } catch (e) { console.error(e); } try { await fn(); } catch (e) { console.error(e); }
@@ -77,6 +60,14 @@
readerState.dlOpen = false; readerState.dlOpen = false;
} }
function enqueueOne(chapterId: number) {
return getAdapter().enqueueDownload(String(chapterId));
}
function enqueueMany(chapterIds: number[]) {
return getAdapter().enqueueDownloads(chapterIds.map(String));
}
const isVertical = $derived(barPosition === "left" || barPosition === "right"); const isVertical = $derived(barPosition === "left" || barPosition === "right");
const popoverSide = $derived( const popoverSide = $derived(
barPosition === "left" ? "right" : barPosition === "left" ? "right" :
@@ -96,9 +87,10 @@
onRestoreZoomAnchor(); onRestoreZoomAnchor();
} }
const isTauri = platformService.platform === "tauri";
async function toggleFullscreen() { async function toggleFullscreen() {
if (!document.fullscreenElement) await document.documentElement.requestFullscreen(); await platformService.toggleFullscreen();
else await document.exitFullscreen();
} }
function closeAllPopovers() { function closeAllPopovers() {
@@ -337,6 +329,16 @@
<span>Fullscreen</span> <span>Fullscreen</span>
{/if} {/if}
</button> </button>
{#if isTauri}
<button class="action-row" onclick={() => { readerState.actionsOpen = false; platformService.minimize(); }}>
<Minus size={13} weight="regular" />
<span>Minimize</span>
</button>
<button class="action-row action-row-danger" onclick={() => { readerState.actionsOpen = false; platformService.close(); }}>
<X size={13} weight="regular" />
<span>Close window</span>
</button>
{/if}
</div> </div>
{/if} {/if}
@@ -345,13 +347,13 @@
<div class="popover dl-popover popover-{popoverSide}" role="presentation" onclick={(e) => e.stopPropagation()}> <div class="popover dl-popover popover-{popoverSide}" role="presentation" onclick={(e) => e.stopPropagation()}>
<p class="dl-title">Download</p> <p class="dl-title">Download</p>
<button class="dl-option" disabled={readerState.dlBusy || !!chapter.downloaded} <button class="dl-option" disabled={readerState.dlBusy || !!chapter.downloaded}
onclick={() => runDl(() => gqlMutation(ENQUEUE_ONE, { id: chapter.id }))}> onclick={() => runDl(() => enqueueOne(chapter.id))}>
This chapter This chapter
<span class="dl-sub">{chapter.downloaded ? "Already downloaded" : chapter.name}</span> <span class="dl-sub">{chapter.downloaded ? "Already downloaded" : chapter.name}</span>
</button> </button>
<div class="dl-row"> <div class="dl-row">
<button class="dl-option" disabled={readerState.dlBusy || queueable.length === 0} <button class="dl-option" disabled={readerState.dlBusy || queueable.length === 0}
onclick={() => runDl(() => gqlMutation(ENQUEUE_MANY, { ids: queueable.slice(0, readerState.nextN).map(c => c.id) }))}> onclick={() => runDl(() => enqueueMany(queueable.slice(0, readerState.nextN).map(c => c.id)))}>
Next chapters Next chapters
<span class="dl-sub">{Math.min(readerState.nextN, queueable.length)} not yet downloaded</span> <span class="dl-sub">{Math.min(readerState.nextN, queueable.length)} not yet downloaded</span>
</button> </button>
@@ -362,7 +364,7 @@
</div> </div>
</div> </div>
<button class="dl-option" disabled={readerState.dlBusy || queueable.length === 0} <button class="dl-option" disabled={readerState.dlBusy || queueable.length === 0}
onclick={() => runDl(() => gqlMutation(ENQUEUE_MANY, { ids: queueable.map(c => c.id) }))}> onclick={() => runDl(() => enqueueMany(queueable.map(c => c.id)))}>
All remaining All remaining
<span class="dl-sub">{queueable.length} not yet downloaded</span> <span class="dl-sub">{queueable.length} not yet downloaded</span>
</button> </button>
@@ -661,8 +663,10 @@
transition: background var(--t-fast), color var(--t-fast); transition: background var(--t-fast), color var(--t-fast);
} }
.action-row:hover { background: var(--bg-overlay); color: var(--text-primary); } .action-row:hover { background: var(--bg-overlay); color: var(--text-primary); }
.action-row.action-row-danger:hover { background: color-mix(in srgb, #c0392b 15%, transparent); color: var(--color-error, #e57373); }
.action-row svg, .action-row :global(svg) { flex-shrink: 0; color: var(--text-faint); } .action-row svg, .action-row :global(svg) { flex-shrink: 0; color: var(--text-faint); }
.action-row:hover svg, .action-row:hover :global(svg) { color: var(--text-muted); } .action-row:hover svg, .action-row:hover :global(svg) { color: var(--text-muted); }
.action-row-danger:hover svg, .action-row-danger:hover :global(svg) { color: var(--color-error, #e57373); }
.action-divider { height: 1px; background: var(--border-dim); margin: var(--sp-1) 0; } .action-divider { height: 1px; background: var(--border-dim); margin: var(--sp-1) 0; }
@@ -79,15 +79,6 @@ export const CLEAR_DOWNLOADER = `
} }
` `
export const FETCH_SOURCE_MANGA = `
mutation FetchSourceManga($source: LongString!, $type: FetchSourceMangaType!, $page: Int!, $query: String, $filters: [FilterChangeInput!]) {
fetchSourceManga(input: { source: $source, type: $type, page: $page, query: $query, filters: $filters }) {
mangas { id title thumbnailUrl inLibrary }
hasNextPage
}
}
`
export const SET_DOWNLOADS_PATH = ` export const SET_DOWNLOADS_PATH = `
mutation SetDownloadsPath($path: String!) { mutation SetDownloadsPath($path: String!) {
setSettings(input: { settings: { downloadsPath: $path } }) { setSettings(input: { settings: { downloadsPath: $path } }) {
+21 -44
View File
@@ -23,12 +23,14 @@ import {
GET_LIBRARY, GET_LIBRARY,
GET_MANGA, GET_MANGA,
GET_CATEGORIES, GET_CATEGORIES,
GET_DOWNLOADS_PATH,
FETCH_MANGA, FETCH_MANGA,
UPDATE_MANGA, UPDATE_MANGA,
UPDATE_MANGAS, UPDATE_MANGAS,
UPDATE_MANGA_CATEGORIES, UPDATE_MANGA_CATEGORIES,
UPDATE_MANGAS_CATEGORIES, UPDATE_MANGAS_CATEGORIES,
CREATE_CATEGORY, CREATE_CATEGORY,
UPDATE_CATEGORY,
DELETE_CATEGORY, DELETE_CATEGORY,
UPDATE_CATEGORY_ORDER, UPDATE_CATEGORY_ORDER,
UPDATE_CATEGORY_MANGA, UPDATE_CATEGORY_MANGA,
@@ -37,6 +39,8 @@ import {
UPDATE_STOP, UPDATE_STOP,
SET_MANGA_META, SET_MANGA_META,
DELETE_MANGA_META, DELETE_MANGA_META,
CREATE_BACKUP,
RESTORE_BACKUP,
FETCH_SOURCE_MANGA, FETCH_SOURCE_MANGA,
LIBRARY_UPDATE_STATUS, LIBRARY_UPDATE_STATUS,
MANGAS_BY_GENRE, MANGAS_BY_GENRE,
@@ -64,16 +68,26 @@ import {
START_DOWNLOADER, START_DOWNLOADER,
STOP_DOWNLOADER, STOP_DOWNLOADER,
CLEAR_DOWNLOADER, CLEAR_DOWNLOADER,
SET_DOWNLOADS_PATH,
SET_LOCAL_SOURCE_PATH,
} from './downloads' } from './downloads'
import { import {
GET_EXTENSIONS, GET_EXTENSIONS,
GET_SOURCES, GET_SOURCES,
GET_SOURCE_SETTINGS,
GET_SETTINGS,
GET_SERVER_SECURITY, GET_SERVER_SECURITY,
FETCH_EXTENSIONS, FETCH_EXTENSIONS,
UPDATE_EXTENSION, UPDATE_EXTENSION,
UPDATE_EXTENSIONS, UPDATE_EXTENSIONS,
INSTALL_EXTERNAL_EXTENSION, INSTALL_EXTERNAL_EXTENSION,
UPDATE_SOURCE_PREFERENCE,
SET_SOURCE_META,
DELETE_SOURCE_META,
SET_EXTENSION_REPOS,
SET_SERVER_AUTH, SET_SERVER_AUTH,
CLEAR_CACHED_IMAGES,
RESET_SETTINGS,
} from './extensions' } from './extensions'
import { import {
GET_TRACKERS, GET_TRACKERS,
@@ -85,10 +99,17 @@ import {
UNLINK_TRACK, UNLINK_TRACK,
TRACK_PROGRESS, TRACK_PROGRESS,
UPDATE_TRACK, UPDATE_TRACK,
LOGIN_TRACKER_CREDENTIALS,
LOGOUT_TRACKER,
} from './tracking' } from './tracking'
import { import {
GET_ABOUT_SERVER, GET_ABOUT_SERVER,
GET_ABOUT_WEBUI, GET_ABOUT_WEBUI,
CHECK_FOR_SERVER_UPDATES,
GET_META,
GET_METAS,
SET_SOCKS_PROXY,
SET_FLARE_SOLVERR,
} from './meta' } from './meta'
import { import {
type GQLResponse, type GQLResponse,
@@ -100,50 +121,6 @@ import {
} from './types' } from './types'
import { initPageCache, clearPageCache as _clearPageCache } from './pageCache' import { initPageCache, clearPageCache as _clearPageCache } from './pageCache'
const SET_SOCKS_PROXY = `
mutation SetSocksProxy(
$socksProxyEnabled: Boolean!
$socksProxyHost: String!
$socksProxyPort: String!
$socksProxyVersion: Int!
$socksProxyUsername: String!
$socksProxyPassword: String!
) {
setSettings(input: { settings: {
socksProxyEnabled: $socksProxyEnabled
socksProxyHost: $socksProxyHost
socksProxyPort: $socksProxyPort
socksProxyVersion: $socksProxyVersion
socksProxyUsername: $socksProxyUsername
socksProxyPassword: $socksProxyPassword
}}) {
settings { socksProxyEnabled socksProxyHost socksProxyPort }
}
}
`
const SET_FLARE_SOLVERR = `
mutation SetFlareSolverr(
$flareSolverrEnabled: Boolean!
$flareSolverrUrl: String!
$flareSolverrTimeout: Int!
$flareSolverrSessionName: String!
$flareSolverrSessionTtl: Int!
$flareSolverrAsResponseFallback: Boolean!
) {
setSettings(input: { settings: {
flareSolverrEnabled: $flareSolverrEnabled
flareSolverrUrl: $flareSolverrUrl
flareSolverrTimeout: $flareSolverrTimeout
flareSolverrSessionName: $flareSolverrSessionName
flareSolverrSessionTtl: $flareSolverrSessionTtl
flareSolverrAsResponseFallback: $flareSolverrAsResponseFallback
}}) {
settings { flareSolverrEnabled flareSolverrUrl }
}
}
`
type RawQueueItem = Record<string, unknown> type RawQueueItem = Record<string, unknown>
function mapDownloadStatus(raw: { state: string; queue: RawQueueItem[] }): DownloadStatus { function mapDownloadStatus(raw: { state: string; queue: RawQueueItem[] }): DownloadStatus {
+44
View File
@@ -36,4 +36,48 @@ export const GET_METAS = `
nodes { key value } nodes { key value }
} }
} }
`
export const SET_SOCKS_PROXY = `
mutation SetSocksProxy(
$socksProxyEnabled: Boolean!
$socksProxyHost: String!
$socksProxyPort: String!
$socksProxyVersion: Int!
$socksProxyUsername: String!
$socksProxyPassword: String!
) {
setSettings(input: { settings: {
socksProxyEnabled: $socksProxyEnabled
socksProxyHost: $socksProxyHost
socksProxyPort: $socksProxyPort
socksProxyVersion: $socksProxyVersion
socksProxyUsername: $socksProxyUsername
socksProxyPassword: $socksProxyPassword
}}) {
settings { socksProxyEnabled socksProxyHost socksProxyPort }
}
}
`
export const SET_FLARE_SOLVERR = `
mutation SetFlareSolverr(
$flareSolverrEnabled: Boolean!
$flareSolverrUrl: String!
$flareSolverrTimeout: Int!
$flareSolverrSessionName: String!
$flareSolverrSessionTtl: Int!
$flareSolverrAsResponseFallback: Boolean!
) {
setSettings(input: { settings: {
flareSolverrEnabled: $flareSolverrEnabled
flareSolverrUrl: $flareSolverrUrl
flareSolverrTimeout: $flareSolverrTimeout
flareSolverrSessionName: $flareSolverrSessionName
flareSolverrSessionTtl: $flareSolverrSessionTtl
flareSolverrAsResponseFallback: $flareSolverrAsResponseFallback
}}) {
settings { flareSolverrEnabled flareSolverrUrl }
}
}
` `
+1 -1
View File
@@ -149,7 +149,7 @@ class LibraryState {
const f = this.tabFilters[tab] ?? {}; const f = this.tabFilters[tab] ?? {};
if (f.unread) items = items.filter(m => (m.unreadCount ?? 0) > 0); if (f.unread) items = items.filter(m => (m.unreadCount ?? 0) > 0);
if (f.started) items = items.filter(m => (m.unreadCount ?? 0) > 0 && (m.chapters?.totalCount ?? 0) > (m.unreadCount ?? 0)); if (f.started) items = items.filter(m => (m.unreadCount ?? 0) > 0 && (m.totalChapters ?? 0) > (m.unreadCount ?? 0));
if (f.downloaded) items = items.filter(m => (m.downloadCount ?? 0) > 0); if (f.downloaded) items = items.filter(m => (m.downloadCount ?? 0) > 0);
if (f.bookmarked) items = items.filter(m => (m.bookmarkCount ?? 0) > 0); if (f.bookmarked) items = items.filter(m => (m.bookmarkCount ?? 0) > 0);