Fix/install external extension does not update extension list (#580)

* Update extension list after installing external extension file

The cache was never updated after the installation

* Make it possible to install external extension while having an empty list

* Show toasts while having an empty extension list

* Use correct extension id

* Update extension action after manual file installation

When installing an external extension the action button did not get updated due to this being an internal state of the ExtensionCard.
By including this in the key, it gets ensured that the component will get correctly updated
This commit is contained in:
schroda
2024-01-29 20:59:27 +01:00
committed by GitHub
parent 506e0aa0e3
commit b074de26a2
2 changed files with 92 additions and 21 deletions

View File

@@ -1005,12 +1005,68 @@ export class RequestManager {
extensionFile: File,
options?: MutationOptions<InstallExternalExtensionMutation, InstallExternalExtensionMutationVariables>,
): AbortableApolloMutationResponse<InstallExternalExtensionMutation> {
return this.doRequest<InstallExternalExtensionMutation, InstallExternalExtensionMutationVariables>(
const result = this.doRequest<InstallExternalExtensionMutation, InstallExternalExtensionMutationVariables>(
GQLMethod.MUTATION,
INSTALL_EXTERNAL_EXTENSION,
{ file: extensionFile },
{ refetchQueries: [GET_EXTENSIONS], ...options },
);
result.response.then((response) => {
this.graphQLClient.client.cache.evict({ fieldName: 'sources' });
const cachedExtensions = this.cache.getResponseFor<MutationResult<GetExtensionsFetchMutation>>(
EXTENSION_LIST_CACHE_KEY,
undefined,
);
const installedExtension = response.data?.installExternalExtension.extension;
if (!cachedExtensions || !cachedExtensions.data) {
this.cache.cacheResponse(EXTENSION_LIST_CACHE_KEY, undefined, {
data: {
fetchExtensions: {
extensions: [installedExtension],
},
},
});
return;
}
const isExtensionCached = !!cachedExtensions.data.fetchExtensions.extensions.find(
(extension) => installedExtension?.pkgName === extension.pkgName,
);
const updatedCachedExtensions: MutationResult<GetExtensionsFetchMutation> = {
...cachedExtensions,
data: {
...cachedExtensions.data,
fetchExtensions: {
...cachedExtensions.data.fetchExtensions,
extensions: isExtensionCached
? cachedExtensions.data.fetchExtensions.extensions.map((extension) => {
const isUpdatedExtension = installedExtension?.pkgName === extension.pkgName;
if (!isUpdatedExtension) {
return extension;
}
return {
...extension,
...installedExtension,
hasUpdate: installedExtension?.versionCode < extension.versionCode,
};
})
: [
...cachedExtensions.data.fetchExtensions.extensions,
installedExtension as (typeof cachedExtensions.data.fetchExtensions.extensions)[number],
],
},
},
};
this.cache.cacheResponse(EXTENSION_LIST_CACHE_KEY, undefined, updatedCachedExtensions);
});
return result;
}
public updateExtension(

View File

@@ -160,6 +160,7 @@ export function Extensions() {
requestManager
.installExternalExtension(file)
.response.then(() => {
handleExtensionUpdate();
makeToast(t('extension.label.installed_successfully'), 'success');
})
.catch(() => makeToast(t('extension.label.installation_failed'), 'error'));
@@ -207,25 +208,8 @@ export function Extensions() {
};
}, []);
if (!allExtensions && (isLoading || !called)) {
return <LoadingPlaceholder />;
}
const showAddRepoInfo = !allExtensions?.length && !areReposDefined;
if (showAddRepoInfo) {
return (
<Stack sx={{ paddingTop: '20px' }} alignItems="center" justifyContent="center" rowGap="10px">
<Typography>{t('extension.label.add_repository_info')}</Typography>
<Button component={Link} variant="contained" to="/settings/extensionSettings">
{t('settings.title')}
</Button>
</Stack>
);
}
return (
<>
{toasts}
const FileInputComponent = useMemo(
() => (
<input
type="file"
style={{ display: 'none' }}
@@ -237,6 +221,34 @@ export function Extensions() {
}
}}
/>
),
[],
);
if (!allExtensions && (isLoading || !called)) {
return <LoadingPlaceholder />;
}
const showAddRepoInfo = !allExtensions?.length && !areReposDefined;
if (showAddRepoInfo) {
return (
<>
{toasts}
{FileInputComponent}
<Stack sx={{ paddingTop: '20px' }} alignItems="center" justifyContent="center" rowGap="10px">
<Typography>{t('extension.label.add_repository_info')}</Typography>
<Button component={Link} variant="contained" to="/settings/extensionSettings">
{t('settings.title')}
</Button>
</Stack>
</>
);
}
return (
<>
{toasts}
{FileInputComponent}
<StyledGroupedVirtuoso
style={{
// override Virtuoso default values and set them with class
@@ -274,7 +286,10 @@ export function Extensions() {
const item = visibleExtensions[index];
return (
<StyledGroupItemWrapper key={item.apkName} isLastItem={index === visibleExtensions.length - 1}>
<StyledGroupItemWrapper
key={`${item.pkgName}_${item.isInstalled}_${item.isObsolete}_${item.hasUpdate}`}
isLastItem={index === visibleExtensions.length - 1}
>
<ExtensionCard
extension={item}
handleUpdate={handleExtensionUpdate}