Add option to disable sources of an extension
This commit is contained in:
@@ -17,6 +17,8 @@ import Card from '@mui/material/Card';
|
|||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
|
import Switch from '@mui/material/Switch';
|
||||||
|
import CardActionArea from '@mui/material/CardActionArea';
|
||||||
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
|
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
|
||||||
import { Metadata } from '@/modules/core/components/texts/Metadata.tsx';
|
import { Metadata } from '@/modules/core/components/texts/Metadata.tsx';
|
||||||
import { useAppTitle } from '@/modules/navigation-bar/hooks/useAppTitle.ts';
|
import { useAppTitle } from '@/modules/navigation-bar/hooks/useAppTitle.ts';
|
||||||
@@ -41,7 +43,8 @@ import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
|||||||
import { SourceConfigurableInfo, SourceIdInfo, SourceLanguageInfo } from '@/modules/source/Source.types.ts';
|
import { SourceConfigurableInfo, SourceIdInfo, SourceLanguageInfo } from '@/modules/source/Source.types.ts';
|
||||||
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
import { useBackButton } from '@/modules/core/hooks/useBackButton.ts';
|
import { useBackButton } from '@/modules/core/hooks/useBackButton.ts';
|
||||||
import { OptionalCardActionAreaLink } from '@/modules/core/components/lists/cards/OptionalCardActionAreaLink.tsx';
|
import { createUpdateSourceMetadata, useGetSourceMetadata } from '@/modules/source/services/SourceMetadata.ts';
|
||||||
|
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||||
|
|
||||||
const Header = ({ name, pkgName, iconUrl, repo }: TExtension) => (
|
const Header = ({ name, pkgName, iconUrl, repo }: TExtension) => (
|
||||||
<Stack sx={{ alignItems: 'center' }}>
|
<Stack sx={{ alignItems: 'center' }}>
|
||||||
@@ -140,43 +143,53 @@ const ActionButton = ({ pkgName, isInstalled, isObsolete, hasUpdate }: TExtensio
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SourceList = ({ sources }: { sources: (SourceIdInfo & SourceLanguageInfo & SourceConfigurableInfo)[] }) => {
|
const SourceCard = (source: SourceIdInfo & SourceLanguageInfo & SourceConfigurableInfo) => {
|
||||||
|
const { id, isConfigurable } = source;
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { isEnabled } = useGetSourceMetadata(source);
|
||||||
|
|
||||||
|
const updateSetting = createUpdateSourceMetadata(source, (e) =>
|
||||||
|
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ px: 1 }}>
|
<StyledGroupItemWrapper key={id} sx={{ px: 0 }}>
|
||||||
{sources?.map((source) => (
|
<Card>
|
||||||
<StyledGroupItemWrapper key={source.id} sx={{ px: 0 }}>
|
<CardActionArea onClick={() => updateSetting('isEnabled', !isEnabled)}>
|
||||||
<Card>
|
<ListCardContent>
|
||||||
<OptionalCardActionAreaLink
|
<Typography variant="h6" component="h3" sx={{ flexGrow: 1 }}>
|
||||||
disabled={!source.isConfigurable}
|
{translateExtensionLanguage(Sources.getLanguage(source))}
|
||||||
to={AppRoutes.sources.childRoutes.configure.path(source.id)}
|
</Typography>
|
||||||
>
|
{isConfigurable && (
|
||||||
<ListCardContent>
|
<CustomTooltip title={t('settings.title')}>
|
||||||
<Typography variant="h6" component="h3" sx={{ flexGrow: 1 }}>
|
<IconButton
|
||||||
{translateExtensionLanguage(Sources.getLanguage(source))}
|
component={Link}
|
||||||
</Typography>
|
to={AppRoutes.sources.childRoutes.configure.path(id)}
|
||||||
{source.isConfigurable && (
|
color="inherit"
|
||||||
<CustomTooltip title={t('settings.title')}>
|
onClick={(e) => e.stopPropagation()}
|
||||||
<IconButton
|
{...MUIUtil.preventRippleProp()}
|
||||||
component={Link}
|
>
|
||||||
to={AppRoutes.sources.childRoutes.configure.path(source.id)}
|
<SettingsIcon />
|
||||||
color="inherit"
|
</IconButton>
|
||||||
{...MUIUtil.preventRippleProp()}
|
</CustomTooltip>
|
||||||
>
|
)}
|
||||||
<SettingsIcon />
|
<Switch checked={isEnabled} onChange={(_, checked) => updateSetting('isEnabled', checked)} />
|
||||||
</IconButton>
|
</ListCardContent>
|
||||||
</CustomTooltip>
|
</CardActionArea>
|
||||||
)}
|
</Card>
|
||||||
</ListCardContent>
|
</StyledGroupItemWrapper>
|
||||||
</OptionalCardActionAreaLink>
|
|
||||||
</Card>
|
|
||||||
</StyledGroupItemWrapper>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SourceList = ({ sources }: { sources: (SourceIdInfo & SourceLanguageInfo & SourceConfigurableInfo)[] }) => (
|
||||||
|
<Box sx={{ px: 1 }}>
|
||||||
|
{sources.map((source) => (
|
||||||
|
<SourceCard key={source.id} {...source} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
export const ExtensionInfo = () => {
|
export const ExtensionInfo = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { pkgName } = useParams<{ pkgName: string }>();
|
const { pkgName } = useParams<{ pkgName: string }>();
|
||||||
@@ -195,7 +208,7 @@ export const ExtensionInfo = () => {
|
|||||||
return sourcesResponse.data.sources.nodes
|
return sourcesResponse.data.sources.nodes
|
||||||
.filter((source) => source.extension.pkgName === extension?.pkgName)
|
.filter((source) => source.extension.pkgName === extension?.pkgName)
|
||||||
.sort((a, b) => languageSortComparator(Sources.getLanguage(a), Sources.getLanguage(b)));
|
.sort((a, b) => languageSortComparator(Sources.getLanguage(a), Sources.getLanguage(b)));
|
||||||
}, [extension?.pkgName]);
|
}, [extension?.pkgName, sourcesResponse.data]);
|
||||||
|
|
||||||
const isLoading = extensionResponse.loading || sourcesResponse.loading;
|
const isLoading = extensionResponse.loading || sourcesResponse.loading;
|
||||||
const error = extensionResponse.error || sourcesResponse.error;
|
const error = extensionResponse.error || sourcesResponse.error;
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ export const SearchAll: React.FC = () => {
|
|||||||
languages: shownLangs,
|
languages: shownLangs,
|
||||||
keepLocalSource: true,
|
keepLocalSource: true,
|
||||||
pinned: shouldShowOnlyPinnedSources,
|
pinned: shouldShowOnlyPinnedSources,
|
||||||
|
enabled: true,
|
||||||
}),
|
}),
|
||||||
[sources, shownLangs, shouldShowOnlyPinnedSources],
|
[sources, shownLangs, shouldShowOnlyPinnedSources],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export const APP_METADATA_KEY_TO_TYPE = {
|
|||||||
shouldUsePureBlackMode: 'boolean',
|
shouldUsePureBlackMode: 'boolean',
|
||||||
mangaGridItemWidth: 'number',
|
mangaGridItemWidth: 'number',
|
||||||
isPinned: 'boolean',
|
isPinned: 'boolean',
|
||||||
|
isEnabled: 'boolean',
|
||||||
lastUsedSourceId: 'string',
|
lastUsedSourceId: 'string',
|
||||||
shouldShowOnlySourcesWithResults: 'boolean',
|
shouldShowOnlySourcesWithResults: 'boolean',
|
||||||
excludedScanlators: 'string', // string[]
|
excludedScanlators: 'string', // string[]
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export type SavedSourceSearch = { query?: string; filters?: IPos[] };
|
|||||||
export interface ISourceMetadata {
|
export interface ISourceMetadata {
|
||||||
savedSearches?: Record<string, SavedSourceSearch>;
|
savedSearches?: Record<string, SavedSourceSearch>;
|
||||||
isPinned: boolean;
|
isPinned: boolean;
|
||||||
|
isEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SourceFilters = GetSourceBrowseQuery['source']['filters'][number];
|
export type SourceFilters = GetSourceBrowseQuery['source']['filters'][number];
|
||||||
|
|||||||
@@ -48,7 +48,13 @@ export function Sources({ tabsMenuHeight }: { tabsMenuHeight: number }) {
|
|||||||
} = requestManager.useGetSourceList({ notifyOnNetworkStatusChange: true });
|
} = requestManager.useGetSourceList({ notifyOnNetworkStatusChange: true });
|
||||||
const sources = data?.sources.nodes;
|
const sources = data?.sources.nodes;
|
||||||
const filteredSources = useMemo(
|
const filteredSources = useMemo(
|
||||||
() => SourceService.filter(sources ?? [], { showNsfw, languages: shownLangs, keepLocalSource: true }),
|
() =>
|
||||||
|
SourceService.filter(sources ?? [], {
|
||||||
|
showNsfw,
|
||||||
|
languages: shownLangs,
|
||||||
|
keepLocalSource: true,
|
||||||
|
enabled: true,
|
||||||
|
}),
|
||||||
[sources, shownLangs],
|
[sources, shownLangs],
|
||||||
);
|
);
|
||||||
const sourcesByLanguage = useMemo(() => {
|
const sourcesByLanguage = useMemo(() => {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
const DEFAULT_SOURCE_METADATA: ISourceMetadata = {
|
const DEFAULT_SOURCE_METADATA: ISourceMetadata = {
|
||||||
savedSearches: undefined,
|
savedSearches: undefined,
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
|
isEnabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertAppMetadataToGqlMetadata = (
|
const convertAppMetadataToGqlMetadata = (
|
||||||
|
|||||||
@@ -81,7 +81,14 @@ export class Sources {
|
|||||||
languages,
|
languages,
|
||||||
keepLocalSource,
|
keepLocalSource,
|
||||||
pinned,
|
pinned,
|
||||||
}: { showNsfw?: boolean; languages?: string[]; keepLocalSource?: boolean; pinned?: boolean } = {},
|
enabled,
|
||||||
|
}: {
|
||||||
|
showNsfw?: boolean;
|
||||||
|
languages?: string[];
|
||||||
|
keepLocalSource?: boolean;
|
||||||
|
pinned?: boolean;
|
||||||
|
enabled?: boolean;
|
||||||
|
} = {},
|
||||||
): Source[] {
|
): Source[] {
|
||||||
const normalizedLanguages = toComparableLanguages(toUniqueLanguageCodes(languages ?? []));
|
const normalizedLanguages = toComparableLanguages(toUniqueLanguageCodes(languages ?? []));
|
||||||
|
|
||||||
@@ -105,6 +112,13 @@ export class Sources {
|
|||||||
!pinned ||
|
!pinned ||
|
||||||
getSourceMetadata(source).isPinned ||
|
getSourceMetadata(source).isPinned ||
|
||||||
(keepLocalSource && Sources.isLocalSource(source)),
|
(keepLocalSource && Sources.isLocalSource(source)),
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(source) =>
|
||||||
|
enabled === undefined ||
|
||||||
|
!enabled ||
|
||||||
|
getSourceMetadata(source).isEnabled ||
|
||||||
|
(keepLocalSource && Sources.isLocalSource(source)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user