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 { afterUpdate } from "svelte";
import { packagesStore } from "$libs/stores"; import { packagesStore } from "$libs/stores";
import Button from "@tea/ui/button/button.svelte"; import Button from "@tea/ui/button/button.svelte";
import { packageHadError, packageWasInstalled } from "$libs/packages/pkg-utils";
let root: HTMLElement | undefined; let root: HTMLElement | undefined;
@ -28,7 +29,7 @@
const myConfetti = confetti.create(canvas, { resize: true }); const myConfetti = confetti.create(canvas, { resize: true });
await myConfetti({ particleCount: 500, spread: 360, startVelocity: 20, gravity: 0.5 }); await myConfetti({ particleCount: 500, spread: 360, startVelocity: 20, gravity: 0.5 });
root.removeChild(canvas); root?.removeChild(canvas);
isAnimating = false; isAnimating = false;
} }
}; };
@ -39,14 +40,14 @@
}; };
afterUpdate(() => { afterUpdate(() => {
if (pkg.displayState?.kind === "INSTALLED_SUCCESSFULLY") { if (packageWasInstalled(pkg)) {
playConfetti(); playConfetti();
} }
}); });
</script> </script>
<div bind:this={root} class="z-60 pointer-events-none absolute left-0 top-0 h-full w-full"> <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 <div
class="pointer-events-auto flex h-full w-full flex-col items-center justify-center bg-black/90" 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 "../../app.css";
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte"; import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
import { PackageStates, type GUIPackage } from "$libs/types"; 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 BgImage from "./bg-image.svelte";
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte"; import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
import PackageInstalledBadge from "$components/package-install-button/package-installed-badge.svelte"; import PackageInstalledBadge from "$components/package-install-button/package-installed-badge.svelte";
@ -28,7 +28,11 @@
</script> </script>
<div class="relative"> <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} /> <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}> <a href={link} on:mousedown={activate} on:mouseup={deactivate} on:mouseleave={deactivate}>
@ -43,16 +47,26 @@
</div> </div>
</div> </div>
<div class="content-container absolute bottom-0 w-full {layout}"> <div class="content-container absolute bottom-0 w-full {layout}">
<article class="card-thumb-label relative"> <article class="card-thumb-label">
{#if layout === "bottom"} {#if layout === "bottom"}
<div class="flex items-center">
<h3 class="text-bold font-mona text-2xl font-bold text-white line-clamp-1"> <h3 class="text-bold font-mona text-2xl font-bold text-white line-clamp-1">
{fixPackageName(pkg.name)} {fixPackageName(pkg.name)}
</h3> </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> <p class="h-[32px] text-xs font-thin lowercase line-clamp-2">{pkg.desc ?? ""}</p>
{:else} {: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)} {fixPackageName(pkg.name)}
</h3> </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]"> <p class="line-clamp-[8] h-[160px] text-[14px] font-thin lowercase leading-[20px]">
{pkg.desc ?? ""} {pkg.desc ?? ""}
</p> </p>
@ -125,6 +139,15 @@
box-shadow: 0px 0px 0px 2px rgba(128, 0, 255, 0.5); 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 { .content-container {
height: 50%; height: 50%;
background: linear-gradient(180deg, rgba(26, 26, 26, 0.3) 0%, rgba(26, 26, 26, 0.75) 72.92%); 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"> <script lang="ts">
import { PackageStates, type GUIPackage } from "$libs/types"; import { PackageStates, type GUIPackage } from "$libs/types";
import Button from "@tea/ui/button/button.svelte"; 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"; export let buttonSize: "small" | "large" = "small";
@ -44,7 +44,7 @@
const hasVersionSelectorDropdown = !!$$slots.selector; const hasVersionSelectorDropdown = !!$$slots.selector;
$: ctaLabel = $t(`package.cta-${pkg.state}`); $: ctaLabel = getPackageBadgeText(pkg);
</script> </script>
<Button <Button

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import log from "$libs/logger"; import log from "$libs/logger";
import type { GUIPackage } from "$libs/types"; import type { GUIPackage } from "$libs/types";
import SemVer from "@teaxyz/lib/semver"; import SemVer from "@teaxyz/lib/semver";
import { t } from "$libs/translations";
// Find a list of available versions for a package based on the bottles // Find a list of available versions for a package based on the bottles
export const findAvailableVersions = (pkg: Pick<GUIPackage, "bottles" | "version">) => { 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 // 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; 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 { getReadme, getContributors, getRepoAsPackage } from "$libs/github";
import { NotificationType } from "@tea/ui/types"; import { NotificationType } from "@tea/ui/types";
import { trackInstall, trackInstallFailed } from "$libs/analytics"; 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 withDebounce from "$libs/utils/debounce";
import { trimGithubSlug } from "$libs/github"; import { trimGithubSlug } from "$libs/github";
import { notificationStore } from "$libs/stores"; import { notificationStore } from "$libs/stores";
@ -230,19 +230,18 @@ const installPkg = async (pkg: GUIPackage, version?: string) => {
await installPackage(pkg, versionToInstall); await installPackage(pkg, versionToInstall);
trackInstall(pkg.full_name); 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); await refreshSinglePackage(pkg.full_name);
if (pkg.state === PackageStates.AVAILABLE) {
updatePackage(pkg.full_name, {
displayState: { kind: "INSTALLED_SUCCESSFULLY", version: versionToInstall }
});
}
} catch (error) { } catch (error) {
log.error(error); log.error(error);
let message = "Unknown Error"; let message = "Unknown Error";
if (error instanceof Error) message = error.message; if (error instanceof Error) message = error.message;
trackInstallFailed(pkg.full_name, message || "unknown"); trackInstallFailed(pkg.full_name, message || "unknown");
updatePackage(pkg.full_name, { updatePackage(pkg.full_name, {
displayState: { kind: "INSTALLATION_ERROR", errorMessage: message, version: versionToInstall } displayState: { state: "ERROR", errorMessage: message, version: versionToInstall }
}); });
} finally { } finally {
updatePackage(pkg.full_name, { install_progress_percentage: 100 }); 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 { export default {
packageList, packageList,
search: searchPackages, search: searchPackages,
@ -374,5 +386,6 @@ export default {
deletePkg, deletePkg,
destroy, destroy,
cachePkgImage, cachePkgImage,
resetPackageDisplayState resetPackageDisplayState,
resetAllPackagesUpdatedState
}; };

View file

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

View file

@ -13,12 +13,13 @@ export enum PackageStates {
UPDATING = "UPDATING" 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 { export interface PackageDisplayState {
// INSTALLED_SUCCESSFULLY -> The pacakge was installed successfully show some confetti! // INSTALLED -> The package was installed successfully. Let's show some confetti!
// UPDATED_SUCCESSFULLY -> The package was updated successfully change the badge temporarily // UPDATED -> The package was updated successfully so change the badge temporarily
// INSTALLATION_ERROR -> The package failed to install show an error overlay temporarily // ERROR -> The package failed to install so show an error overlay temporarily
kind: "INSTALLED_SUCCESSFULLY" | "UPDATED_SUCCESSFULLY" | "INSTALLATION_ERROR"; state: "INSTALLED" | "UPDATED" | "ERROR";
errorMessage?: string; errorMessage?: string;
version: string; version: string;
} }

View file

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

View file

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

View file

@ -83,7 +83,6 @@ describe("basic smoke test", () => {
await expect(updateBtn).toExist(); await expect(updateBtn).toExist();
updateBtn.click(); 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 ( const verifyInstalledBadge = async (
slug: string, slug: string,
state: "INSTALLED" | "UPDATE" = "INSTALLED" state: "INSTALLED" | "UPDATE" | "UPDATED" = "INSTALLED"
) => { ) => {
// wait 30 seconds for the badge to show up // wait 30 seconds for the badge to show up
for (let i = 0; i < 30; i++) { for (let i = 0; i < 30; i++) {