Show UPDATED status (#658)

This commit is contained in:
ABevier 2023-06-08 20:11:05 -04:00 committed by GitHub
parent 543411433b
commit 2f133f4b77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 98 additions and 35 deletions

View file

@ -4,6 +4,7 @@
import { afterUpdate } from "svelte";
import { packagesStore } from "$libs/stores";
import Button from "@tea/ui/button/button.svelte";
import { packageHadError, packageWasInstalled } from "$libs/packages/pkg-utils";
let root: HTMLElement | undefined;
@ -28,7 +29,7 @@
const myConfetti = confetti.create(canvas, { resize: true });
await myConfetti({ particleCount: 500, spread: 360, startVelocity: 20, gravity: 0.5 });
root.removeChild(canvas);
root?.removeChild(canvas);
isAnimating = false;
}
};
@ -39,14 +40,14 @@
};
afterUpdate(() => {
if (pkg.displayState?.kind === "INSTALLED_SUCCESSFULLY") {
if (packageWasInstalled(pkg)) {
playConfetti();
}
});
</script>
<div bind:this={root} class="z-60 pointer-events-none absolute left-0 top-0 h-full w-full">
{#if pkg.displayState?.kind === "INSTALLATION_ERROR"}
{#if packageHadError(pkg)}
<div
class="pointer-events-auto flex h-full w-full flex-col items-center justify-center bg-black/90"
>

View file

@ -2,7 +2,7 @@
import "../../app.css";
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
import { PackageStates, type GUIPackage } from "$libs/types";
import { findRecentInstalledVersion } from "$libs/packages/pkg-utils";
import { findRecentInstalledVersion, packageWasUpdated } from "$libs/packages/pkg-utils";
import BgImage from "./bg-image.svelte";
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
import PackageInstalledBadge from "$components/package-install-button/package-installed-badge.svelte";
@ -28,7 +28,11 @@
</script>
<div class="relative">
<section class="package-card relative h-auto border border-gray {layout}" class:active={isActive}>
<section
class="package-card relative h-auto border border-gray {layout}"
class:active={isActive}
class:updated={packageWasUpdated(pkg)}
>
<BgImage class="absolute left-0 top-0 h-full w-full" {layout} {pkg} />
<a href={link} on:mousedown={activate} on:mouseup={deactivate} on:mouseleave={deactivate}>
@ -43,16 +47,26 @@
</div>
</div>
<div class="content-container absolute bottom-0 w-full {layout}">
<article class="card-thumb-label relative">
<article class="card-thumb-label">
{#if layout === "bottom"}
<div class="flex items-center">
<h3 class="text-bold font-mona text-2xl font-bold text-white line-clamp-1">
{fixPackageName(pkg.name)}
</h3>
{#if pkg.state === PackageStates.INSTALLED}
<i class="icon-check-circle-o mb-1 ml-2 flex text-2xl text-[#00ffd0]" />
{/if}
</div>
<p class="h-[32px] text-xs font-thin lowercase line-clamp-2">{pkg.desc ?? ""}</p>
{:else}
<h3 class="text-bold mb-4 font-mona text-3xl font-bold text-white line-clamp-1">
<div class="mb-4 flex items-center">
<h3 class="text-bold font-mona text-3xl font-bold text-white line-clamp-1">
{fixPackageName(pkg.name)}
</h3>
{#if pkg.state === PackageStates.INSTALLED}
<i class="icon-check-circle-o mb-1 ml-2 flex text-3xl text-[#00ffd0]" />
{/if}
</div>
<p class="line-clamp-[8] h-[160px] text-[14px] font-thin lowercase leading-[20px]">
{pkg.desc ?? ""}
</p>
@ -125,6 +139,15 @@
box-shadow: 0px 0px 0px 2px rgba(128, 0, 255, 0.5);
}
section.package-card.updated {
border-color: #00ffd0;
}
section.package-card.updated:active {
border-color: #00ffd0;
box-shadow: 0px 0px 0px 2px rgba(0, 255, 208, 0.5);
}
.content-container {
height: 50%;
background: linear-gradient(180deg, rgba(26, 26, 26, 0.3) 0%, rgba(26, 26, 26, 0.75) 72.92%);

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { PackageStates, type GUIPackage } from "$libs/types";
import Button from "@tea/ui/button/button.svelte";
import { t } from "$libs/translations";
import { getPackageBadgeText } from "$libs/packages/pkg-utils";
export let buttonSize: "small" | "large" = "small";
@ -44,7 +44,7 @@
const hasVersionSelectorDropdown = !!$$slots.selector;
$: ctaLabel = $t(`package.cta-${pkg.state}`);
$: ctaLabel = getPackageBadgeText(pkg);
</script>
<Button

View file

@ -1,13 +1,13 @@
<script lang="ts">
import { getPackageBadgeText } from "$libs/packages/pkg-utils";
import type { GUIPackage } from "$libs/types";
export let pkg: GUIPackage | null = null;
export let pkg: GUIPackage;
</script>
<div class="container relative h-full" data-testid={`install-badge-${pkg?.slug}`}>
<div class="content flex items-center justify-center gap-2 p-2">
<i class="icon-check-circle-o flex text-sm text-[#00ffd0]" />
<div class="text-xs">INSTALLED</div>
<div class="text-xs">{getPackageBadgeText(pkg)}</div>
<div class="rounded-sm bg-white px-1 text-[10px] leading-[12px] text-black">
v{pkg?.installed_versions?.[0]}
</div>

View file

@ -12,6 +12,7 @@
import NoUpdates from "./no-updates.svelte";
import { packagesStore, scrollStore } from "$libs/stores";
import { afterUpdate, beforeUpdate } from "svelte";
import { packageWasUpdated } from "$libs/packages/pkg-utils";
const { packageList: allPackages } = packagesStore;
export let packageFilter: SideMenuOptions = SideMenuOptions.all;
@ -38,7 +39,10 @@
].includes(pkg.state);
},
[SideMenuOptions.installed_updates_available]: (pkg: GUIPackage) => {
return [PackageStates.UPDATING, PackageStates.NEEDS_UPDATE].includes(pkg.state);
return (
[PackageStates.UPDATING, PackageStates.NEEDS_UPDATE].includes(pkg.state) ||
packageWasUpdated(pkg)
);
},
[SideMenuOptions.recently_updated]: (pkg: GUIPackage) => {
return moment(pkg.last_modified).isAfter(moment().subtract(30, "days"));

View file

@ -1,6 +1,7 @@
import log from "$libs/logger";
import type { GUIPackage } from "$libs/types";
import SemVer from "@teaxyz/lib/semver";
import { t } from "$libs/translations";
// Find a list of available versions for a package based on the bottles
export const findAvailableVersions = (pkg: Pick<GUIPackage, "bottles" | "version">) => {
@ -67,3 +68,21 @@ export const isPackageUpToDate = (pkg: GUIPackage) => {
// if the installed version is equal or newer than the latest version, it's up to date
return semverCompare(pkg.installed_versions[0], pkg.version) >= 0;
};
export const packageWasInstalled = (pkg: GUIPackage) => {
return pkg.displayState?.state === "INSTALLED";
};
export const packageWasUpdated = (pkg: GUIPackage) => {
return pkg.displayState?.state === "UPDATED";
};
export const packageHadError = (pkg: GUIPackage) => {
return pkg.displayState?.state === "ERROR";
};
export const getPackageBadgeText = (pkg: GUIPackage) => {
// UPDATED is a "pseudo-state" that overrides other states
const state = packageWasUpdated(pkg) ? "UPDATED" : pkg.state;
return t.get(`package.cta-${state}`);
};

View file

@ -21,7 +21,7 @@ import {
import { getReadme, getContributors, getRepoAsPackage } from "$libs/github";
import { NotificationType } from "@tea/ui/types";
import { trackInstall, trackInstallFailed } from "$libs/analytics";
import { addInstalledVersion, isInstalling } from "$libs/packages/pkg-utils";
import { addInstalledVersion, isInstalling, packageWasUpdated } from "$libs/packages/pkg-utils";
import withDebounce from "$libs/utils/debounce";
import { trimGithubSlug } from "$libs/github";
import { notificationStore } from "$libs/stores";
@ -230,19 +230,18 @@ const installPkg = async (pkg: GUIPackage, version?: string) => {
await installPackage(pkg, versionToInstall);
trackInstall(pkg.full_name);
// If the package was AVAILABLE previously then it was just installed, otherwise it was updated
const state = pkg.state === PackageStates.AVAILABLE ? "INSTALLED" : "UPDATED";
updatePackage(pkg.full_name, { displayState: { state, version: versionToInstall } });
await refreshSinglePackage(pkg.full_name);
if (pkg.state === PackageStates.AVAILABLE) {
updatePackage(pkg.full_name, {
displayState: { kind: "INSTALLED_SUCCESSFULLY", version: versionToInstall }
});
}
} catch (error) {
log.error(error);
let message = "Unknown Error";
if (error instanceof Error) message = error.message;
trackInstallFailed(pkg.full_name, message || "unknown");
updatePackage(pkg.full_name, {
displayState: { kind: "INSTALLATION_ERROR", errorMessage: message, version: versionToInstall }
displayState: { state: "ERROR", errorMessage: message, version: versionToInstall }
});
} finally {
updatePackage(pkg.full_name, { install_progress_percentage: 100 });
@ -364,6 +363,19 @@ const resetPackageDisplayState = (pkg: GUIPackage) => {
});
};
const resetAllPackagesUpdatedState = () => {
packageMap.update((pkgs) => {
Object.values(pkgs.packages).forEach((pkg) => {
// only reset the display state if the package was updated,
// installed and error display states should not be reset here
if (packageWasUpdated(pkg)) {
pkg.displayState = null;
}
});
return pkgs;
});
};
export default {
packageList,
search: searchPackages,
@ -374,5 +386,6 @@ export default {
deletePkg,
destroy,
cachePkgImage,
resetPackageDisplayState
resetPackageDisplayState,
resetAllPackagesUpdatedState
};

View file

@ -12,6 +12,7 @@
"cta-UNINSTALL": "UNINSTALL",
"cta-NEEDS_UPDATE": "UPDATE",
"cta-UPDATING": "UPDATING",
"cta-UPDATED": "UPDATED",
"cta-PRUNE": "PRUNE",
"cta-PRUNING": "PRUNING"
},

View file

@ -13,12 +13,13 @@ export enum PackageStates {
UPDATING = "UPDATING"
}
// PackageDisplayState is for showing temporary UI elements when a package is installed or updated. It is not persisted.
// PackageDisplayState is a TEMPORARY state for showing temporary UI elements when a package is
// installed or updated. It is not persisted.
export interface PackageDisplayState {
// INSTALLED_SUCCESSFULLY -> The pacakge was installed successfully show some confetti!
// UPDATED_SUCCESSFULLY -> The package was updated successfully change the badge temporarily
// INSTALLATION_ERROR -> The package failed to install show an error overlay temporarily
kind: "INSTALLED_SUCCESSFULLY" | "UPDATED_SUCCESSFULLY" | "INSTALLATION_ERROR";
// INSTALLED -> The package was installed successfully. Let's show some confetti!
// UPDATED -> The package was updated successfully so change the badge temporarily
// ERROR -> The package failed to install so show an error overlay temporarily
state: "INSTALLED" | "UPDATED" | "ERROR";
errorMessage?: string;
version: string;
}

View file

@ -27,6 +27,9 @@
const fromPath = from?.url.href.replace(from.url.origin, "");
setNewPath(nextPath, fromPath || "/");
}
// the updated state should be reset any time the user navigates
packagesStore.resetAllPackagesUpdatedState();
});
const syncPath = async () => {

View file

@ -41,7 +41,6 @@
}
}
updating = false;
sideMenuOption = SideMenuOptions.all;
}
$: needsUpdateCount = pkgsToUpdate.length;

View file

@ -83,7 +83,6 @@ describe("basic smoke test", () => {
await expect(updateBtn).toExist();
updateBtn.click();
// FIXME: This should test for the button saying "UPDATED", but that feature is not done yet so uncomment this when it is
//await utils.verifyInstalledBadge(slug, "UPDATED");
await utils.verifyInstalledBadge(slug, "UPDATED");
});
});

View file

@ -91,7 +91,7 @@ export function setupUtils(browser: WebdriverIO.Browser) {
const verifyInstalledBadge = async (
slug: string,
state: "INSTALLED" | "UPDATE" = "INSTALLED"
state: "INSTALLED" | "UPDATE" | "UPDATED" = "INSTALLED"
) => {
// wait 30 seconds for the badge to show up
for (let i = 0; i < 30; i++) {