mirror of
https://github.com/ivabus/gui
synced 2025-04-23 14:07:14 +03:00
Show UPDATED status (#658)
This commit is contained in:
parent
543411433b
commit
2f133f4b77
13 changed files with 98 additions and 35 deletions
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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"}
|
||||||
<h3 class="text-bold font-mona text-2xl font-bold text-white line-clamp-1">
|
<div class="flex items-center">
|
||||||
{fixPackageName(pkg.name)}
|
<h3 class="text-bold font-mona text-2xl font-bold text-white line-clamp-1">
|
||||||
</h3>
|
{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>
|
<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">
|
||||||
{fixPackageName(pkg.name)}
|
<h3 class="text-bold font-mona text-3xl font-bold text-white line-clamp-1">
|
||||||
</h3>
|
{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]">
|
<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%);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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}`);
|
||||||
|
};
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updating = false;
|
updating = false;
|
||||||
sideMenuOption = SideMenuOptions.all;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: needsUpdateCount = pkgsToUpdate.length;
|
$: needsUpdateCount = pkgsToUpdate.length;
|
||||||
|
|
|
@ -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");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
Loading…
Reference in a new issue