feat: add Source filter to library filtering system (#1131)

* feat: add Source filter to library filtering system

Add a new Source filter section to the library options panel that allows
users to filter their library manga by source (e.g. MangaDex, MangaSee).

- Add hasSource to LibraryOptions type and category metadata
- Add source filtering logic in useGetVisibleLibraryMangas
- Add Source section with three-state checkboxes in filter UI
- Show loading spinner while source list loads
- Include source filter in active filter indicator

Closes #1057

* refactor: address review feedback for source filter

- Remove skip option, let query always run
- Use uniqBy from lodash/fp for deduplication
- Remove loading spinner (data available quickly)
- Wrap source section in length check

* docs: add source filter to changelog

---------

Co-authored-by: Copilot <noreply@github.com>
This commit is contained in:
Sean Smith
2026-07-20 15:09:06 -07:00
committed by GitHub
parent 98ee3d46c8
commit 8b8da30335
8 changed files with 58 additions and 3 deletions

View File

@@ -11,6 +11,8 @@ import FormLabel from '@mui/material/FormLabel';
import RadioGroup from '@mui/material/RadioGroup';
import { useLingui } from '@lingui/react/macro';
import { msg } from '@lingui/core/macro';
import { useMemo } from 'react';
import uniqBy from 'lodash/fp/uniqBy';
import { CheckboxInput } from '@/base/components/inputs/CheckboxInput.tsx';
import { RadioInput } from '@/base/components/inputs/RadioInput.tsx';
import { SortRadioInput } from '@/base/components/inputs/SortRadioInput.tsx';
@@ -65,6 +67,16 @@ export const LibraryOptionsPanel = ({
const trackerList = requestManager.useGetTrackerList<GetTrackersSettingsQuery>(GET_TRACKERS_SETTINGS);
const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? STABLE_EMPTY_ARRAY);
const migratableSourcesResult = requestManager.useGetMigratableSources();
const librarySources = useMemo(() => {
const sources = migratableSourcesResult.data?.mangas.nodes
.map(({ source }) => source)
.filter((source) => source != null);
const uniqueSources = uniqBy('id', sources);
return uniqueSources.toSorted((a, b) => a.displayName.localeCompare(b.displayName));
}, [migratableSourcesResult.data?.mangas.nodes]);
const categoryLibraryOptions = useGetCategoryMetadata(category);
const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, (e) =>
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
@@ -140,6 +152,24 @@ export const LibraryOptionsPanel = ({
}
/>
))}
{librarySources.length > 0 && (
<>
<FormLabel sx={{ mt: 2 }}>{t`Source`}</FormLabel>
{librarySources.map((source) => (
<ThreeStateCheckboxInput
key={source.id}
label={source.displayName}
checked={categoryLibraryOptions.hasSource[source.id]}
onChange={(checked) =>
updateCategoryLibraryOptions('hasSource', {
...categoryLibraryOptions.hasSource,
[source.id]: checked,
})
}
/>
))}
</>
)}
</>
);
}