Cleanup "Extensions"

This commit is contained in:
schroda
2025-04-21 02:07:23 +02:00
parent e0567fac5d
commit 9dfd9ed5e3

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { fromEvent } from 'file-selector'; import { fromEvent } from 'file-selector';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
@@ -116,51 +116,36 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
const { pathname, search, state } = useLocation<{ selectedExtensionPkg?: TExtension['pkgName'] }>(); const { pathname, search, state } = useLocation<{ selectedExtensionPkg?: TExtension['pkgName'] }>();
const selectedExtensionPkg = state?.selectedExtensionPkg; const selectedExtensionPkg = state?.selectedExtensionPkg;
const setSelectedExtensionPkg = (newPkg: TExtension['pkgName'] | undefined) => {
navigate(pathname + search, {
replace: true,
state: {
selectedExtensionPkg: newPkg,
},
});
};
const { const {
data: serverSettingsData, data: serverSettingsData,
loading: areServerSettingsLoading, loading: areServerSettingsLoading,
error: serverSettingsError, error: serverSettingsError,
refetch: refetchServerSettings, refetch: refetchServerSettings,
} = requestManager.useGetServerSettings({ notifyOnNetworkStatusChange: true }); } = requestManager.useGetServerSettings({ notifyOnNetworkStatusChange: true });
const areReposDefined = !!serverSettingsData?.settings.extensionRepos.length; const [fetchExtensions, { data, loading: areExtensionsLoading, error: extensionsError }] =
const areMultipleReposInUse = (serverSettingsData?.settings.extensionRepos.length ?? 0) > 1; requestManager.useExtensionListFetch();
const inputRef = useRef<HTMLInputElement>(null);
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs()); const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true); const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
const [query] = useQueryParam('query', StringParam); const [query] = useQueryParam('query', StringParam);
const [refetchExtensions, setRefetchExtensions] = useState({});
const [fetchExtensions, { data, loading: areExtensionsLoading, error: extensionsError }] =
requestManager.useExtensionListFetch();
const allExtensions = data?.fetchExtensions?.extensions;
const [updatingExtensionIds, setUpdatingExtensionIds] = useState<string[]>([]); const [updatingExtensionIds, setUpdatingExtensionIds] = useState<string[]>([]);
const [refetchExtensions, setRefetchExtensions] = useState({});
const handleExtensionUpdate = useCallback(() => setRefetchExtensions({}), []); const isLoading = areServerSettingsLoading || areExtensionsLoading;
const error = serverSettingsError ?? extensionsError;
useEffect(() => { const areReposDefined = !!serverSettingsData?.settings.extensionRepos.length;
fetchExtensions(); const areMultipleReposInUse = (serverSettingsData?.settings.extensionRepos.length ?? 0) > 1;
}, [refetchExtensions]);
const allExtensions = data?.fetchExtensions?.extensions;
const allLangs = useMemo(() => getLanguagesFromExtensions(allExtensions ?? []), [allExtensions]); const allLangs = useMemo(() => getLanguagesFromExtensions(allExtensions ?? []), [allExtensions]);
const filteredExtensions = useMemo( const filteredExtensions = useMemo(
() => filterExtensions(allExtensions ?? [], shownLangs, showNsfw, query), () => filterExtensions(allExtensions ?? [], shownLangs, showNsfw, query),
[allExtensions, shownLangs, shownLangs, query], [allExtensions, shownLangs, shownLangs, query],
); );
const groupedExtensions = useMemo(() => groupExtensionsByLanguage(filteredExtensions), [filteredExtensions]); const groupedExtensions = useMemo(() => groupExtensionsByLanguage(filteredExtensions), [filteredExtensions]);
const groupCounts = useMemo( const groupCounts = useMemo(
() => groupedExtensions.map((extensionGroup) => extensionGroup[EXTENSIONS].length), () => groupedExtensions.map((extensionGroup) => extensionGroup[EXTENSIONS].length),
[groupedExtensions], [groupedExtensions],
@@ -176,10 +161,21 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
useCallback((index) => visibleExtensions[index].pkgName, [visibleExtensions]), useCallback((index) => visibleExtensions[index].pkgName, [visibleExtensions]),
); );
const handleExtensionUpdate = useCallback(() => setRefetchExtensions({}), []);
const setSelectedExtensionPkg = (newPkg: TExtension['pkgName'] | undefined) => {
navigate(pathname + search, {
replace: true,
state: {
selectedExtensionPkg: newPkg,
},
});
};
const submitExternalExtension = (file: File) => { const submitExternalExtension = (file: File) => {
if (file.name.toLowerCase().endsWith('apk')) { if (!file.name.toLowerCase().endsWith('apk')) {
if (inputRef.current) { makeToast(t('global.error.label.invalid_file_type'), 'error');
inputRef.current.value = ''; return;
} }
makeToast(t('extension.label.installing_file'), 'info'); makeToast(t('extension.label.installing_file'), 'info');
@@ -190,17 +186,35 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
makeToast(t('extension.label.installed_successfully'), 'success'); makeToast(t('extension.label.installed_successfully'), 'success');
}) })
.catch((e) => makeToast(t('extension.label.installation_failed'), 'error', getErrorMessage(e))); .catch((e) => makeToast(t('extension.label.installation_failed'), 'error', getErrorMessage(e)));
} else {
makeToast(t('global.error.label.invalid_file_type'), 'error');
}
}; };
useEffect(() => {
fetchExtensions();
}, [refetchExtensions]);
useLayoutEffect(() => { useLayoutEffect(() => {
setAction( setAction(
<> <>
<AppbarSearch /> <AppbarSearch />
<CustomTooltip title={t('extension.action.label.install_external')}> <CustomTooltip title={t('extension.action.label.install_external')}>
<IconButton onClick={() => inputRef.current?.click()} color="inherit"> <IconButton
onClick={() => {
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
input.onchange = () => {
const file = input.files?.[0];
if (file) {
submitExternalExtension(file);
}
};
document.documentElement.appendChild(input);
input.click();
document.documentElement.removeChild(input);
}}
color="inherit"
>
<AddIcon /> <AddIcon />
</IconButton> </IconButton>
</CustomTooltip> </CustomTooltip>
@@ -234,26 +248,6 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
}; };
}, []); }, []);
const FileInputComponent = useMemo(
() => (
<input
type="file"
style={{ display: 'none' }}
ref={inputRef}
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
submitExternalExtension(file);
}
}}
/>
),
[],
);
const isLoading = areServerSettingsLoading || areExtensionsLoading;
const error = serverSettingsError ?? extensionsError;
if (isLoading) { if (isLoading) {
return <LoadingPlaceholder />; return <LoadingPlaceholder />;
} }
@@ -279,8 +273,6 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
const showAddRepoInfo = !allExtensions?.length && !areReposDefined; const showAddRepoInfo = !allExtensions?.length && !areReposDefined;
if (showAddRepoInfo) { if (showAddRepoInfo) {
return ( return (
<>
{FileInputComponent}
<Stack <Stack
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@@ -294,13 +286,11 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
{t('settings.title')} {t('settings.title')}
</Button> </Button>
</Stack> </Stack>
</>
); );
} }
return ( return (
<> <>
{FileInputComponent}
<StyledGroupedVirtuoso <StyledGroupedVirtuoso
heightToSubtract={tabsMenuHeight} heightToSubtract={tabsMenuHeight}
overscan={window.innerHeight * 0.5} overscan={window.innerHeight * 0.5}