diff --git a/src/modules/global-search/screens/SearchAll.tsx b/src/modules/global-search/screens/SearchAll.tsx index 93f3ae49..b7c56dc8 100644 --- a/src/modules/global-search/screens/SearchAll.tsx +++ b/src/modules/global-search/screens/SearchAll.tsx @@ -24,7 +24,6 @@ import IconButton from '@mui/material/IconButton'; // ms import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { AppbarSearch } from '@/modules/core/components/AppbarSearch.tsx'; -import { LanguageSelect } from '@/modules/core/components/inputs/LanguageSelect.tsx'; import { useDebounce } from '@/modules/core/hooks/useDebounce.ts'; import { MangaCardProps } from '@/modules/manga/Manga.types.ts'; import { EmptyView } from '@/modules/core/components/feedback/EmptyView.tsx'; @@ -53,6 +52,7 @@ import { makeToast } from '@/modules/core/utils/Toast.ts'; import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx'; import { MUIUtil } from '@/lib/mui/MUI.util.ts'; import { MetadataBrowseSettings } from '@/modules/browse/Browse.types.ts'; +import { SourceLanguageSelect } from '@/modules/source/components/SourceLanguageSelect.tsx'; type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean; error: any }; type SourceToLoadingStateMap = Map; @@ -293,13 +293,14 @@ export const SearchAll: React.FC = () => { t(isMigrateMode ? 'migrate.search.title' : 'search.title.global_search', { title: state?.mangaTitle }), <> - , - [shownLangs, setShownLangs, sourceLanguages], + [shownLangs, setShownLangs, sourceLanguages, sources], ); if (loading) { diff --git a/src/modules/source/Source.types.ts b/src/modules/source/Source.types.ts index 14e0abbe..a6e9e78f 100644 --- a/src/modules/source/Source.types.ts +++ b/src/modules/source/Source.types.ts @@ -71,3 +71,4 @@ export type SourceNsfwInfo = Pick; export type SourceRepoInfo = { extension: Pick }; export type SourceMetaInfo = { meta: SourceMetaFieldsFragment[] }; export type SourceConfigurableInfo = Pick; +export type SourceIconInfo = Pick; diff --git a/src/modules/source/components/SourceLanguageSelect.tsx b/src/modules/source/components/SourceLanguageSelect.tsx new file mode 100644 index 00000000..716a464e --- /dev/null +++ b/src/modules/source/components/SourceLanguageSelect.tsx @@ -0,0 +1,186 @@ +/* + * 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 { useMemo, useState } from 'react'; +import Button from '@mui/material/Button'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import Dialog from '@mui/material/Dialog'; +import Switch from '@mui/material/Switch'; +import IconButton from '@mui/material/IconButton'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import ListItemText from '@mui/material/ListItemText'; +import ListItem from '@mui/material/ListItem'; +import { useTranslation } from 'react-i18next'; +import { Virtuoso } from 'react-virtuoso'; +import Box from '@mui/material/Box'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx'; +import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts'; +import { languageSortComparator, toUniqueLanguageCodes } from '@/modules/core/utils/Languages.ts'; +import { + SourceDisplayNameInfo, + SourceIconInfo, + SourceIdInfo, + SourceLanguageInfo, + SourceMetaInfo, + SourceNameInfo, +} from '@/modules/source/Source.types.ts'; +import { Sources } from '@/modules/source/services/Sources'; +import { getSourceMetadata, updateSourceMetadata } from '@/modules/source/services/SourceMetadata.ts'; +import { requestManager } from '@/lib/requests/RequestManager.ts'; +import { ListCardAvatar } from '@/modules/core/components/lists/cards/ListCardAvatar.tsx'; +import { makeToast } from '@/modules/core/utils/Toast.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; + +export const SourceLanguageSelect = ({ + selectedLanguages, + setSelectedLanguages, + languages, + sources, +}: { + selectedLanguages: string[]; + setSelectedLanguages: (languages: string[]) => void; + languages: string[]; + sources: (SourceIdInfo & + SourceLanguageInfo & + SourceNameInfo & + SourceDisplayNameInfo & + SourceIconInfo & + SourceMetaInfo)[]; +}) => { + const { t } = useTranslation(); + + const [tmpSourceIdToEnabledState, setTmpSourceIdToEnabledState] = useState>({}); + + const [tmpSelectedLanguages, setTmpSelectedLanguages] = useState(toUniqueLanguageCodes(selectedLanguages)); + const [open, setOpen] = useState(false); + + const sourcesByLanguage = useMemo(() => Sources.groupByLanguage(sources), [sources]); + + const languagesSortedBySelectState = useMemo( + () => + toUniqueLanguageCodes([ + ...tmpSelectedLanguages.toSorted(languageSortComparator), + ...languages.toSorted(languageSortComparator), + ]), + [languages, tmpSelectedLanguages], + ); + + const handleCancel = () => { + setOpen(false); + + setTmpSourceIdToEnabledState({}); + setTmpSelectedLanguages(toUniqueLanguageCodes(selectedLanguages)); + }; + + const handleOk = () => { + setOpen(false); + + Promise.all( + Object.entries(tmpSourceIdToEnabledState).map(([sourceId, enabled]) => + updateSourceMetadata(sources.find((source) => source.id === sourceId)!, 'isEnabled', enabled), + ), + ).catch((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e))); + setTmpSourceIdToEnabledState({}); + setSelectedLanguages(toUniqueLanguageCodes(tmpSelectedLanguages)); + }; + + const handleChange = (language: string, selected: boolean) => { + if (selected) { + setTmpSelectedLanguages([...tmpSelectedLanguages, language]); + } else { + setTmpSelectedLanguages(tmpSelectedLanguages.toSpliced(tmpSelectedLanguages.indexOf(language), 1)); + } + }; + + return ( + <> + + setOpen(true)} aria-label="display more actions" edge="end" color="inherit"> + + + + + {t('global.language.title.enabled_languages')} + + languagesSortedBySelectState[index]} + itemContent={(_index, language) => { + const sourcesOfLanguage = sourcesByLanguage[language]; + const isEnabled = tmpSelectedLanguages.includes(language); + + return ( + <> + + + handleChange(language, e.target.checked)} + /> + + {isEnabled && sourcesOfLanguage.length && ( + + {sourcesOfLanguage.map((source) => ( + + + + + + + setTmpSourceIdToEnabledState({ + ...tmpSourceIdToEnabledState, + [source.id]: e.target.checked, + }) + } + /> + + ))} + + )} + + ); + }} + /> + + + + + + + + ); +}; diff --git a/src/modules/source/screens/Sources.tsx b/src/modules/source/screens/Sources.tsx index a8d2f245..49cc9ff4 100644 --- a/src/modules/source/screens/Sources.tsx +++ b/src/modules/source/screens/Sources.tsx @@ -17,7 +17,6 @@ import { requestManager } from '@/lib/requests/RequestManager.ts'; import { DefaultLanguage } from '@/modules/core/utils/Languages.ts'; import { LoadingPlaceholder } from '@/modules/core/components/feedback/LoadingPlaceholder.tsx'; import { SourceCard } from '@/modules/source/components/SourceCard.tsx'; -import { LanguageSelect } from '@/modules/core/components/inputs/LanguageSelect.tsx'; import { EmptyViewAbsoluteCentered } from '@/modules/core/components/feedback/EmptyViewAbsoluteCentered.tsx'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { isPinnedOrLastUsedSource, translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts'; @@ -30,6 +29,7 @@ import { StyledGroupedVirtuoso } from '@/modules/core/components/virtuoso/Styled import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx'; import { StyledGroupHeader } from '@/modules/core/components/virtuoso/StyledGroupHeader.tsx'; import { StyledGroupItemWrapper } from '@/modules/core/components/virtuoso/StyledGroupItemWrapper.tsx'; +import { SourceLanguageSelect } from '@/modules/source/components/SourceLanguageSelect.tsx'; export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) { const { t } = useTranslation(); @@ -102,13 +102,14 @@ export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) { - , - [t, shownLangs, sourceLanguages], + [t, shownLangs, sourceLanguages, sources], ); if (isLoading) return ;