mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Show upgradable packages only when checking for updates (#4599)
* Sort upgradable packages to top * Show upgradable packages only by default * Fix loading state when refetching
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
mutateUninstallPluginPackages,
|
mutateUninstallPluginPackages,
|
||||||
mutateUpdatePluginPackages,
|
mutateUpdatePluginPackages,
|
||||||
pluginMutationImpactedQueries,
|
pluginMutationImpactedQueries,
|
||||||
|
isLoading,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { useMonitorJob } from "src/utils/job";
|
import { useMonitorJob } from "src/utils/job";
|
||||||
import {
|
import {
|
||||||
@@ -25,9 +26,11 @@ export const InstalledPluginPackages: React.FC = () => {
|
|||||||
const [jobID, setJobID] = useState<string>();
|
const [jobID, setJobID] = useState<string>();
|
||||||
const { job } = useMonitorJob(jobID, () => onPackageChanges());
|
const { job } = useMonitorJob(jobID, () => onPackageChanges());
|
||||||
|
|
||||||
const { data, previousData, refetch, loading, error } =
|
const { data, previousData, refetch, networkStatus, error } =
|
||||||
useInstalledPluginPackages(loadUpgrades);
|
useInstalledPluginPackages(loadUpgrades);
|
||||||
|
|
||||||
|
const loading = isLoading(networkStatus);
|
||||||
|
|
||||||
async function onUpdatePackages(packages: GQL.PackageSpecInput[]) {
|
async function onUpdatePackages(packages: GQL.PackageSpecInput[]) {
|
||||||
const r = await mutateUpdatePluginPackages(packages);
|
const r = await mutateUpdatePluginPackages(packages);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
mutateUninstallScraperPackages,
|
mutateUninstallScraperPackages,
|
||||||
mutateInstallScraperPackages,
|
mutateInstallScraperPackages,
|
||||||
scraperMutationImpactedQueries,
|
scraperMutationImpactedQueries,
|
||||||
|
isLoading,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { useMonitorJob } from "src/utils/job";
|
import { useMonitorJob } from "src/utils/job";
|
||||||
import {
|
import {
|
||||||
@@ -25,9 +26,11 @@ export const InstalledScraperPackages: React.FC = () => {
|
|||||||
const [jobID, setJobID] = useState<string>();
|
const [jobID, setJobID] = useState<string>();
|
||||||
const { job } = useMonitorJob(jobID, () => onPackageChanges());
|
const { job } = useMonitorJob(jobID, () => onPackageChanges());
|
||||||
|
|
||||||
const { data, previousData, refetch, loading, error } =
|
const { data, previousData, refetch, networkStatus, error } =
|
||||||
useInstalledScraperPackages(loadUpgrades);
|
useInstalledScraperPackages(loadUpgrades);
|
||||||
|
|
||||||
|
const loading = isLoading(networkStatus);
|
||||||
|
|
||||||
async function onUpdatePackages(packages: GQL.PackageSpecInput[]) {
|
async function onUpdatePackages(packages: GQL.PackageSpecInput[]) {
|
||||||
const r = await mutateUpdateScraperPackages(packages);
|
const r = await mutateUpdateScraperPackages(packages);
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ function filterPackages<T extends IPackage>(packages: T[], filter: string) {
|
|||||||
|
|
||||||
export type InstalledPackage = Omit<GQL.Package, "requires">;
|
export type InstalledPackage = Omit<GQL.Package, "requires">;
|
||||||
|
|
||||||
|
function hasUpgrade(pkg: InstalledPackage) {
|
||||||
|
if (!pkg.date || !pkg.source_package?.date) return false;
|
||||||
|
|
||||||
|
const pkgDate = new Date(pkg.date);
|
||||||
|
const upgradeDate = new Date(pkg.source_package.date);
|
||||||
|
return upgradeDate > pkgDate;
|
||||||
|
}
|
||||||
|
|
||||||
const InstalledPackageRow: React.FC<{
|
const InstalledPackageRow: React.FC<{
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
pkg: InstalledPackage;
|
pkg: InstalledPackage;
|
||||||
@@ -75,11 +83,7 @@ const InstalledPackageRow: React.FC<{
|
|||||||
|
|
||||||
const updateAvailable = useMemo(() => {
|
const updateAvailable = useMemo(() => {
|
||||||
if (!updatesLoaded) return false;
|
if (!updatesLoaded) return false;
|
||||||
if (!pkg.date || !pkg.source_package?.date) return false;
|
return hasUpgrade(pkg);
|
||||||
|
|
||||||
const pkgDate = new Date(pkg.date);
|
|
||||||
const upgradeDate = new Date(pkg.source_package.date);
|
|
||||||
return upgradeDate > pkgDate;
|
|
||||||
}, [updatesLoaded, pkg]);
|
}, [updatesLoaded, pkg]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -124,6 +128,7 @@ const InstalledPackagesList: React.FC<{
|
|||||||
packages: InstalledPackage[];
|
packages: InstalledPackage[];
|
||||||
checkedPackages: InstalledPackage[];
|
checkedPackages: InstalledPackage[];
|
||||||
setCheckedPackages: React.Dispatch<React.SetStateAction<InstalledPackage[]>>;
|
setCheckedPackages: React.Dispatch<React.SetStateAction<InstalledPackage[]>>;
|
||||||
|
upgradableOnly: boolean;
|
||||||
}> = ({
|
}> = ({
|
||||||
filter,
|
filter,
|
||||||
packages,
|
packages,
|
||||||
@@ -132,6 +137,7 @@ const InstalledPackagesList: React.FC<{
|
|||||||
updatesLoaded,
|
updatesLoaded,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
|
upgradableOnly,
|
||||||
}) => {
|
}) => {
|
||||||
const checkedMap = useMemo(() => {
|
const checkedMap = useMemo(() => {
|
||||||
const map: Record<string, boolean> = {};
|
const map: Record<string, boolean> = {};
|
||||||
@@ -146,8 +152,10 @@ const InstalledPackagesList: React.FC<{
|
|||||||
}, [checkedPackages, packages]);
|
}, [checkedPackages, packages]);
|
||||||
|
|
||||||
const filteredPackages = useMemo(() => {
|
const filteredPackages = useMemo(() => {
|
||||||
return filterPackages(packages, filter);
|
return filterPackages(packages, filter).filter((pkg) => {
|
||||||
}, [filter, packages]);
|
return !updatesLoaded || !upgradableOnly || hasUpgrade(pkg);
|
||||||
|
});
|
||||||
|
}, [packages, filter, updatesLoaded, upgradableOnly]);
|
||||||
|
|
||||||
function toggleAllChecked() {
|
function toggleAllChecked() {
|
||||||
setCheckedPackages(allChecked ? [] : packages.slice());
|
setCheckedPackages(allChecked ? [] : packages.slice());
|
||||||
@@ -179,10 +187,13 @@ const InstalledPackagesList: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filteredPackages.length === 0) {
|
if (filteredPackages.length === 0) {
|
||||||
|
const id = upgradableOnly
|
||||||
|
? "package_manager.no_upgradable"
|
||||||
|
: "package_manager.no_packages";
|
||||||
return (
|
return (
|
||||||
<tr className="package-manager-no-results">
|
<tr className="package-manager-no-results">
|
||||||
<td colSpan={1000}>
|
<td colSpan={1000}>
|
||||||
<FormattedMessage id="package_manager.no_packages" />
|
<FormattedMessage id={id} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@@ -242,6 +253,9 @@ const InstalledPackagesToolbar: React.FC<{
|
|||||||
onCheckForUpdates: () => void;
|
onCheckForUpdates: () => void;
|
||||||
onUpdatePackages: () => void;
|
onUpdatePackages: () => void;
|
||||||
onUninstallPackages: () => void;
|
onUninstallPackages: () => void;
|
||||||
|
|
||||||
|
upgradableOnly: boolean;
|
||||||
|
setUpgradableOnly: (v: boolean) => void;
|
||||||
}> = ({
|
}> = ({
|
||||||
loading,
|
loading,
|
||||||
checkedPackages,
|
checkedPackages,
|
||||||
@@ -250,8 +264,11 @@ const InstalledPackagesToolbar: React.FC<{
|
|||||||
onUninstallPackages,
|
onUninstallPackages,
|
||||||
filter,
|
filter,
|
||||||
setFilter,
|
setFilter,
|
||||||
|
upgradableOnly,
|
||||||
|
setUpgradableOnly,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="package-manager-toolbar">
|
<div className="package-manager-toolbar">
|
||||||
<ClearableInput
|
<ClearableInput
|
||||||
@@ -259,6 +276,15 @@ const InstalledPackagesToolbar: React.FC<{
|
|||||||
value={filter}
|
value={filter}
|
||||||
setValue={(v) => setFilter(v)}
|
setValue={(v) => setFilter(v)}
|
||||||
/>
|
/>
|
||||||
|
{upgradableOnly && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => setUpgradableOnly(!upgradableOnly)}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="package_manager.show_all" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<div className="flex-grow-1" />
|
<div className="flex-grow-1" />
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -306,11 +332,28 @@ export const InstalledPackages: React.FC<{
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const [filter, setFilter] = useState("");
|
const [filter, setFilter] = useState("");
|
||||||
|
const [upgradableOnly, setUpgradableOnly] = useState(true);
|
||||||
const [uninstalling, setUninstalling] = useState(false);
|
const [uninstalling, setUninstalling] = useState(false);
|
||||||
|
|
||||||
|
// sort packages so that those with updates are at the top
|
||||||
|
const sortedPackages = useMemo(() => {
|
||||||
|
return packages.slice().sort((a, b) => {
|
||||||
|
const aHasUpdate = hasUpgrade(a);
|
||||||
|
const bHasUpdate = hasUpgrade(b);
|
||||||
|
|
||||||
|
if (aHasUpdate && !bHasUpdate) return -1;
|
||||||
|
if (!aHasUpdate && bHasUpdate) return 1;
|
||||||
|
|
||||||
|
// sort by name
|
||||||
|
return a.package_id.localeCompare(b.package_id);
|
||||||
|
});
|
||||||
|
}, [packages]);
|
||||||
|
|
||||||
const filteredPackages = useMemo(() => {
|
const filteredPackages = useMemo(() => {
|
||||||
return filterPackages(checkedPackages, filter);
|
return filterPackages(checkedPackages, filter).filter((pkg) => {
|
||||||
}, [checkedPackages, filter]);
|
return !updatesLoaded || !upgradableOnly || hasUpgrade(pkg);
|
||||||
|
});
|
||||||
|
}, [checkedPackages, filter, updatesLoaded, upgradableOnly]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCheckedPackages((prev) => {
|
setCheckedPackages((prev) => {
|
||||||
@@ -330,6 +373,12 @@ export const InstalledPackages: React.FC<{
|
|||||||
setUninstalling(false);
|
setUninstalling(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkForUpdates() {
|
||||||
|
// reset to only show upgradable packages
|
||||||
|
setUpgradableOnly(true);
|
||||||
|
onCheckForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AlertModal
|
<AlertModal
|
||||||
@@ -349,19 +398,22 @@ export const InstalledPackages: React.FC<{
|
|||||||
setFilter={(f) => setFilter(f)}
|
setFilter={(f) => setFilter(f)}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
checkedPackages={filteredPackages}
|
checkedPackages={filteredPackages}
|
||||||
onCheckForUpdates={onCheckForUpdates}
|
onCheckForUpdates={() => checkForUpdates()}
|
||||||
onUpdatePackages={() => onUpdatePackages(filteredPackages)}
|
onUpdatePackages={() => onUpdatePackages(filteredPackages)}
|
||||||
onUninstallPackages={() => setUninstalling(true)}
|
onUninstallPackages={() => setUninstalling(true)}
|
||||||
|
upgradableOnly={updatesLoaded && upgradableOnly}
|
||||||
|
setUpgradableOnly={(v) => setUpgradableOnly(v)}
|
||||||
/>
|
/>
|
||||||
<InstalledPackagesList
|
<InstalledPackagesList
|
||||||
filter={filter}
|
filter={filter}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
packages={packages}
|
packages={sortedPackages}
|
||||||
// use original checked packages so that check boxes are not affected by filter
|
// use original checked packages so that check boxes are not affected by filter
|
||||||
checkedPackages={checkedPackages}
|
checkedPackages={checkedPackages}
|
||||||
setCheckedPackages={setCheckedPackages}
|
setCheckedPackages={setCheckedPackages}
|
||||||
updatesLoaded={updatesLoaded}
|
updatesLoaded={updatesLoaded}
|
||||||
|
upgradableOnly={upgradableOnly}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
ApolloCache,
|
ApolloCache,
|
||||||
DocumentNode,
|
DocumentNode,
|
||||||
FetchResult,
|
FetchResult,
|
||||||
|
NetworkStatus,
|
||||||
useQuery,
|
useQuery,
|
||||||
} from "@apollo/client";
|
} from "@apollo/client";
|
||||||
import { Modifiers } from "@apollo/client/cache";
|
import { Modifiers } from "@apollo/client/cache";
|
||||||
@@ -93,6 +94,16 @@ function deleteObject(
|
|||||||
cache.evict({ id: cache.identify(obj) });
|
cache.evict({ id: cache.identify(obj) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isLoading(networkStatus: NetworkStatus) {
|
||||||
|
// useQuery hook loading field only returns true when initially loading the query
|
||||||
|
// and not during subsequent fetches
|
||||||
|
return (
|
||||||
|
networkStatus === NetworkStatus.loading ||
|
||||||
|
networkStatus === NetworkStatus.fetchMore ||
|
||||||
|
networkStatus === NetworkStatus.refetch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Object queries
|
/// Object queries
|
||||||
|
|
||||||
export const useFindScene = (id: string) => {
|
export const useFindScene = (id: string) => {
|
||||||
|
|||||||
@@ -1095,6 +1095,7 @@
|
|||||||
"latest_version": "Latest Version",
|
"latest_version": "Latest Version",
|
||||||
"no_packages": "No packages found",
|
"no_packages": "No packages found",
|
||||||
"no_sources": "No sources configured",
|
"no_sources": "No sources configured",
|
||||||
|
"no_upgradable": "No upgradable packages found",
|
||||||
"package": "Package",
|
"package": "Package",
|
||||||
"required_by": "Required by {packages}",
|
"required_by": "Required by {packages}",
|
||||||
"selected_only": "Selected only",
|
"selected_only": "Selected only",
|
||||||
|
|||||||
Reference in New Issue
Block a user