install button styling (#362)

* install button styling

* fix progress loader

* accomodate pkg.state = UPDATING in packages filter

* fix package filter

---------

Co-authored-by: neil molina <neil@neils-MacBook-Pro.local>
This commit is contained in:
ABevier 2023-03-31 01:39:49 -04:00 committed by GitHub
parent f9db7dda5e
commit 5d2d5ff9cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 162 additions and 131 deletions

View file

@ -1,17 +1,15 @@
<script lang="ts">
import clickOutside from "../lib/clickOutside";
import Button from "../button/button.svelte";
import type { GUIPackage } from "$libs/types";
import clickOutside from "@tea/ui/lib/clickOutside";
import PackageStateButton from "./package-state-button.svelte";
export let ctaLabel: string;
export let ctaType: "ghost" | "plain" = "ghost";
export let ctaColor: "green" | "secondary" = "secondary";
export let pkg: GUIPackage;
export let availableVersions: string[] = [];
export let onClickCTA = async (_version: string) => {
console.log("do nothing");
};
export let availableVersions: string[] = [];
$: isOpened = false;
const handleClick = (version: string) => {
@ -22,23 +20,18 @@
const handleClickOutside = () => (isOpened = false);
</script>
<div class="dropdown" use:clickOutside on:click_outside={handleClickOutside}>
<Button
class="h-8 border border-gray p-2 text-xs"
type={ctaType}
color={ctaColor}
onClick={() => (isOpened = !isOpened)}
>
{ctaLabel}
</Button>
<div class="version-list" class:visible={isOpened}>
{#each availableVersions as version, idx}
{#if idx !== 0}<hr class="divider" />{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="version-item text-xs" on:click={() => handleClick(version)}>
v{version + (idx === 0 ? " (latest)" : "")}
</div>
{/each}
<div class="flex flex-col justify-end">
<div class="dropdown" use:clickOutside on:click_outside={handleClickOutside}>
<PackageStateButton {pkg} onClick={() => (isOpened = !isOpened)} />
<div class="version-list" class:visible={isOpened}>
{#each availableVersions as version, idx}
{#if idx !== 0}<hr class="divider" />{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="version-item text-xs" on:click={() => handleClick(version)}>
v{version + (idx === 0 ? " (latest)" : "")}
</div>
{/each}
</div>
</div>
</div>
@ -46,7 +39,7 @@
.version-list {
display: none;
position: absolute;
margin-top: 4px;
margin-top: 6px;
width: 100%;
z-index: 1;
background-color: #1a1a1a;

View file

@ -1,24 +1,22 @@
<script lang="ts">
import "../app.css";
import type { Package } from "../types";
import ImgLoader from "../img-loader/img-loader.svelte";
import ProgressCircle from "../progress-circle/progress-circle.svelte";
import "../../app.css";
import ImgLoader from "@tea/ui/img-loader/img-loader.svelte";
import ProgressCircle from "@tea/ui/progress-circle/progress-circle.svelte";
import InstallButton from "./install-button.svelte";
import type { GUIPackage } from "$libs/types";
import VersionLabel from "./version-label.svelte";
export let pkg: Package;
export let pkg: GUIPackage;
export let availableVersions: string[];
export let link: string;
export let ctaLabel: string;
export let progessLoading = 0;
export let ctaType: "ghost" | "plain" = "ghost";
export let ctaColor: "green" | "secondary" = "secondary";
export let onClickCTA = async (_version: string) => {
console.log("do nothing");
};
</script>
<section class="package-card relative h-auto border border-gray">
<section class="package-card border-gray relative h-auto border">
<a href={link}>
<figure class="relative">
<ImgLoader
@ -35,21 +33,13 @@
<h4 class="text-sm font-light">&#x2022;&nbsp;{pkg.maintainer}</h4>
{/if}
{#if pkg.desc}
<p class="text-xs font-thin line-clamp-2">{pkg.desc}</p>
<p class="line-clamp-2 text-xs font-thin">{pkg.desc}</p>
{/if}
</article>
</a>
<footer class="absolute bottom-0 left-0 flex w-full items-stretch justify-between gap-2 p-2">
<div>
<p>
<span class="pk-version text-xs font-extralight">v{pkg.version}</span>
<!--
TODO: uncomment once install counts improve
<br>
<span class="package-install-no">>{{- .installs -}}&nbsp;installs</span> -->
</p>
</div>
<InstallButton {ctaLabel} {ctaColor} {ctaType} {onClickCTA} {availableVersions} />
<VersionLabel {pkg} {availableVersions} />
<InstallButton {pkg} {availableVersions} {onClickCTA} />
</footer>
{#if progessLoading > 0 && progessLoading < 100}
<div class="absolute top-0 left-0 h-full w-full bg-black bg-opacity-50">

View file

@ -0,0 +1,54 @@
<script lang="ts">
import { PackageStates, type GUIPackage } from "$libs/types";
import Button from "@tea/ui/button/button.svelte";
import { t } from "$libs/translations";
export let pkg: GUIPackage;
export let onClick = () => {
console.log("do nothing");
};
const getLabel = (state: PackageStates): string => {
return {
[PackageStates.AVAILABLE]: $t("package.install-label").toUpperCase(),
[PackageStates.INSTALLED]: $t("package.installed-label").toUpperCase(),
[PackageStates.INSTALLING]: $t("package.installing-label").toUpperCase(),
[PackageStates.UNINSTALLED]: $t("package.reinstall-label").toUpperCase(),
[PackageStates.NEEDS_UPDATE]: $t("package.needs-update-label").toUpperCase(),
[PackageStates.UPDATING]: $t("package.needs-update-label").toUpperCase()
}[state];
};
const getColor = (state: PackageStates): "primary" | "secondary" | "black" => {
if (state === PackageStates.INSTALLED) {
return "black";
}
if (state === PackageStates.AVAILABLE || state === PackageStates.INSTALLING) {
return "secondary";
}
return "primary";
};
</script>
<Button
class="h-8 w-full border text-xs text-white "
type="plain"
color={getColor(pkg.state)}
{onClick}
>
{#if pkg.state === PackageStates.INSTALLED}
<div class="flex items-center justify-center">
<i class="icon-check-circle mr-2 flex" />
<span>{getLabel(pkg.state)}</span>
</div>
{:else if pkg.state === PackageStates.AVAILABLE || pkg.state === PackageStates.INSTALLING}
<div class="flex items-center justify-between">
<span class="ml-1">{getLabel(pkg.state)}</span>
<i class="icon-downward-arrow mr-1 flex" />
</div>
{:else}
<div class="flex items-center justify-center">
{getLabel(pkg.state)}
</div>
{/if}
</Button>

View file

@ -0,0 +1,24 @@
<script lang="ts">
import { PackageStates, type GUIPackage } from "$libs/types";
export let pkg: GUIPackage;
export let availableVersions: string[];
</script>
<div class="flex flex-col justify-center">
{#if pkg.state === PackageStates.AVAILABLE || pkg.state === PackageStates.INSTALLING}
<div class="pk-version text-xs font-extralight">latest version: v{pkg.version}</div>
<div class="pk-version text-xs font-extralight">
versions available: {availableVersions.length}
</div>
{:else if pkg.state === PackageStates.NEEDS_UPDATE || pkg.state === PackageStates.UPDATING}
<div class="pk-version text-xs font-extralight">
your latest version: v{pkg.installed_versions?.[0]}
</div>
<div class="pk-version text-xs font-extralight">update available: v{pkg.version}</div>
{:else if pkg.state === PackageStates.INSTALLED}
<div class="pk-version text-xs font-extralight">
version installed: v{pkg.installed_versions?.[0]}
</div>
{/if}
</div>

View file

@ -1,70 +1,53 @@
<script lang="ts">
import '$appcss';
import PackageCard from '@tea/ui/package-card/package-card.svelte';
import "$appcss";
import { t } from '$libs/translations';
import type { GUIPackage } from '$libs/types';
import { PackageStates } from '$libs/types';
import { getPackage } from '@native';
import { packagesStore, notificationStore } from '$libs/stores';
import { onMount } from 'svelte';
import type { GUIPackage } from "$libs/types";
import { packagesStore, notificationStore } from "$libs/stores";
import { onMount } from "svelte";
import semverCompare from "semver/functions/compare";
import { clean } from "semver";
import PackageCard from "$components/package-card/package-card.svelte";
export let tab = "all";
export let pkg: GUIPackage;
export let onClick: (version: string) => void;
export let onClick: (version: string) => void;
let fakeLoadingProgress = 0;
const getCTALabel = (state: PackageStates): string => {
return {
[PackageStates.AVAILABLE]: $t("package.install-label").toUpperCase(),
[PackageStates.INSTALLED]: $t("package.installed-label").toUpperCase(),
[PackageStates.INSTALLING]: $t("package.installing-label").toUpperCase(),
[PackageStates.UNINSTALLED]: $t("package.reinstall-label").toUpperCase(),
[PackageStates.NEEDS_UPDATE]: $t("package.needs-update-label").toUpperCase(),
}[state];
};
const onClickCTA = async (version: string) => {
await onClick(version);
notificationStore.add({
message: `Package ${pkg.full_name} v${pkg.version} has been installed.`,
message: `Package ${pkg.full_name} v${pkg.version} has been installed.`
});
}
};
const findAvailableVersions = (pkg: GUIPackage) => {
// default to just showing the latest if bottles haven't loaded yet
if (!pkg.bottles) {
return [pkg.version]
return [pkg.version];
}
const versionSet = new Set<string>()
const versionSet = new Set<string>();
for (const b of pkg.bottles) {
versionSet.add(b.version)
versionSet.add(b.version);
}
return Array.from(versionSet).sort((a, b) => semverCompare(cleanVersion(b), cleanVersion(a)));
}
};
const cleanVersion = (version: string) => {
return clean(version) || '0.0.0';
}
return clean(version) || "0.0.0";
};
onMount(() => {
packagesStore.fetchPackageBottles(pkg.full_name)
packagesStore.fetchPackageBottles(pkg.full_name);
});
</script>
<PackageCard
{pkg}
{pkg}
availableVersions={findAvailableVersions(pkg)}
link={`/packages/${pkg.slug}?tab=${tab}`}
ctaLabel={getCTALabel(pkg.state)}
link={`/packages/${pkg.slug}?tab=${tab}`}
progessLoading={pkg.install_progress_percentage}
ctaType="plain"
ctaColor={PackageStates.INSTALLED === pkg.state ? "green" : "secondary"}
{onClickCTA}
{onClickCTA}
/>

View file

@ -23,9 +23,16 @@
// TODO: figure out a better type strategy here so that this breaks if SideMenuOptions is updated
const pkgFilters: { [key:string]: (pkg: GUIPackage) => boolean } = {
[SideMenuOptions.all]: (_pkg: GUIPackage) => true,
[SideMenuOptions.installed]: (pkg: GUIPackage) => pkg.state === PackageStates.INSTALLED,
[SideMenuOptions.installed]: (pkg: GUIPackage) => {
return [
PackageStates.INSTALLED,
PackageStates.INSTALLING,
PackageStates.NEEDS_UPDATE,
PackageStates.UPDATING
].includes(pkg.state);
},
[SideMenuOptions.installed_updates_available]: (pkg: GUIPackage) => {
return [PackageStates.INSTALLING, PackageStates.NEEDS_UPDATE].includes(pkg.state);
return [PackageStates.UPDATING, PackageStates.NEEDS_UPDATE].includes(pkg.state);
},
[SideMenuOptions.recently_updated]: (pkg: GUIPackage) => {
return moment(pkg.last_modified).isAfter(moment().subtract(30, "days"));

View file

@ -148,7 +148,12 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
const installPkg = async (pkg: GUIPackage, version?: string) => {
let fakeTimer: NodeJS.Timer | null = null;
try {
updatePackage(pkg.full_name, { state: PackageStates.INSTALLING });
const state: PackageStates =
pkg.state === PackageStates.NEEDS_UPDATE
? PackageStates.UPDATING
: PackageStates.INSTALLING;
updatePackage(pkg.full_name, { state });
fakeTimer = withFakeLoader(pkg, (progress) => {
updatePackage(pkg.full_name, { install_progress_percentage: progress });

View file

@ -10,7 +10,8 @@ export enum PackageStates {
INSTALLED = "INSTALLED",
INSTALLING = "INSTALLING",
UNINSTALLED = "UNINSTALLED",
NEEDS_UPDATE = "NEEDS_UPDATE"
NEEDS_UPDATE = "NEEDS_UPDATE",
UPDATING = "UPDATING"
}
export type GUIPackage = Package & {

View file

@ -7,8 +7,8 @@
export let onClick: undefined | (() => void) = undefined;
export let active = false;
export let type: "ghost" | "plain" = "ghost";
export let color: "primary" | "secondary" | "green" = "primary";
export let type: "outline" | "ghost" | "plain" = "ghost";
export let color: "primary" | "secondary" | "green" | "black" = "primary";
export let loading = false;
</script>
@ -44,9 +44,16 @@
background-color: #00a517;
color: white;
}
button.plain.black {
background-color: #1a1a1a;
color: white;
}
button.active {
color: black;
}
button:hover {
box-shadow: inset 0px 0px 0px 5px #1a1a1a;
background-color: #00ffd0;
@ -61,4 +68,8 @@
button.green:hover {
background-color: #00a517;
}
button.black:hover {
box-shadow: inset 0px 0px 0px 5px #949494;
background-color: #1a1a1a;
}
</style>

View file

@ -47,4 +47,6 @@
<glyph glyph-name="lightbulb" unicode="&#76;" d="M411 415l0 0 0 0c3 3 3 8 0 11l-10 10c-3 3-8 3-11 0l0 0-45-45c-3-3-3-7 0-10l11-11 0 0c2-3 7-3 10 0z m-162-2l0 0 14 0 0 0c4 0 8 3 8 7l0 0 0 64c0 5-3 8-8 8l0 0-14 0 0 0c-5 0-8-3-8-8l0-64 0 0c0-4 4-7 8-7z m220-106c0 4-3 7-7 8l0 0-64 0c-4 0-8-4-8-8l0-15c0-4 4-7 8-7l64 0 0 0c4 0 7 3 7 7z m-213 81c-60 0-109-49-109-109 0-23 8-45 20-63l0 0c17-25 27-59 27-96 2-2 4-4 7-4 0 0 0 0 1 0l0 0 108 0 0 0c0 0 1 0 1 0 3 0 5 2 7 5 0 38 12 73 29 99 11 17 18 37 18 59 0 60-49 109-109 109z m55-336c0 0-1 0-1 0l0 0-108 0 0 0c-1 0-1 0-1 0-4 0-8-3-8-7l0-17c0-5 4-8 8-8 0 0 0 0 1 0l0 0 108 0 0 0c0 0 1 0 1 0 4 0 7 3 7 8l0 17c0 4-3 7-7 7z m0 49c0 0-1 0-1 0l0 0-108 0 0 0c-1 0-1 0-1 0-4 0-7-4-8-8 0 0 0 0 0 0l0-17c0-4 4-8 8-8 0 0 0 0 1 1l0-1 108 0 0 1c0-1 1-1 1-1 4 0 7 4 7 8l0 17c0 4-3 8-7 8z m-197 208l0 0-64 0c-4 0-7-3-7-8l0-14c0-5 3-8 7-8l64 0 0 0c4 0 8 4 8 8l0 14c0 4-4 8-8 8z m-11 109l46-46 0 0c3-2 7-2 10 1l0 0 11 10c2 3 2 8 0 10l0 0-46 46c-2 3-7 3-10 0l-11-11c-2-3-2-7 0-10z"/>
<glyph glyph-name="square" unicode="&#77;" d="M238 219l0-109c0-10-4-19-11-26-7-7-16-11-26-11l-146 0c-10 0-19 4-26 11-7 7-11 16-11 26l0 109c0 10 4 19 11 26 7 7 16 11 26 11l146 0c10 0 19-4 26-11 7-7 11-16 11-26z m0 220l0-110c0-10-4-18-11-26-7-7-16-10-26-10l-146 0c-10 0-19 3-26 10-7 8-11 16-11 26l0 110c0 10 4 18 11 26 7 7 16 10 26 10l146 0c10 0 19-3 26-10 7-8 11-16 11-26z m256-220l0-109c0-10-4-19-11-26-7-7-16-11-26-11l-146 0c-10 0-19 4-26 11-7 7-11 16-11 26l0 109c0 10 4 19 11 26 7 7 16 11 26 11l146 0c10 0 19-4 26-11 7-7 11-16 11-26z m0 220l0-110c0-10-4-18-11-26-7-7-16-10-26-10l-146 0c-10 0-19 3-26 10-7 8-11 16-11 26l0 110c0 10 4 18 11 26 7 7 16 10 26 10l146 0c10 0 19-3 26-10 7-8 11-16 11-26z"/>
<glyph glyph-name="birthday-cake" unicode="&#78;" d="M512 110l0-110-512 0 0 110c9 0 17 1 24 4 8 2 14 5 17 8 4 2 8 6 14 10 5 5 10 9 14 11 4 2 10 3 16 3 7 0 12-1 16-3 4-2 9-6 15-11 6-4 10-8 14-11 3-2 9-5 16-7 8-3 16-4 25-4 8 0 17 1 24 4 8 3 13 5 17 8 3 2 8 6 13 10 4 4 8 6 10 8 2 2 5 3 9 4 3 2 7 2 12 2 7 0 12-1 16-3 4-2 9-6 15-11 5-4 10-8 13-10 4-3 10-6 17-8 8-3 16-4 24-4 9 0 17 1 25 4 7 2 13 5 17 8 3 2 8 6 13 10 6 5 11 9 15 11 4 2 9 3 16 3 6 0 12-1 16-3 4-2 9-6 14-11 6-4 10-8 14-10 3-3 9-6 17-8 7-3 15-4 24-4z m0 91l0-55c-7 0-12 1-16 3-4 3-9 6-15 11-5 5-10 9-13 11-4 3-9 5-17 8-7 3-16 4-24 4-9 0-17-1-25-4-7-3-13-5-16-8-4-2-8-6-14-11-4-3-7-6-9-7-2-2-5-3-9-5-4-1-8-2-13-2-6 0-12 1-16 3-4 3-9 6-14 11-6 5-10 9-14 11-3 3-9 5-16 8-8 3-16 4-25 4-9 0-17-2-24-4-8-3-13-6-17-8-3-2-8-6-14-11-4-3-7-6-9-7-2-2-5-3-9-5-3-1-8-2-12-2-7 0-12 1-16 3-4 3-9 6-15 11-5 5-10 8-13 11-4 3-10 5-17 8-8 3-16 4-25 4-8 0-17-1-24-4-8-3-13-5-17-8-3-2-8-6-13-11-6-5-11-8-15-11-4-2-9-3-16-3l0 55c0 15 5 28 16 39 11 11 24 16 39 16l18 0 0 128 73 0 0-128 73 0 0 128 74 0 0-128 73 0 0 128 73 0 0-128 18 0c15 0 28-5 39-16 11-11 16-24 16-39z m-366 247c0-15-3-26-10-34-7-8-16-12-26-12-10 0-19 4-26 11-7 7-11 16-11 26 0 5 1 10 3 14 2 5 4 8 7 10 2 2 5 5 8 8 4 3 7 6 9 9 3 3 5 7 7 13 2 5 3 12 3 19 7 0 15-7 23-21 9-14 13-29 13-43z m147 0c0-15-4-26-11-34-7-8-15-12-26-12-10 0-19 4-26 11-7 7-11 16-11 26 0 5 1 10 3 14 2 5 4 8 7 10 3 2 5 5 9 8 3 3 6 6 9 9 2 3 4 7 6 13 2 5 3 12 3 19 7 0 15-7 24-21 8-14 13-29 13-43z m146 0c0-15-4-26-10-34-7-8-16-12-27-12-10 0-18 4-26 11-7 7-10 16-10 26 0 5 1 10 2 14 2 5 4 8 7 10 3 2 6 5 9 8 3 3 6 6 9 9 3 3 5 7 7 13 1 5 2 12 2 19 8 0 15-7 24-21 9-14 13-29 13-43z"/>
<glyph glyph-name="down-open-mini" unicode="&#79;" d="M344 317c8 9 16 9 25 0 9-7 9-15 0-24 0 0-101-98-101-98-7-8-15-8-24 0 0 0-101 98-101 98-9 9-9 17 0 24 9 9 17 9 26 0 0 0 87-79 87-79 0 0 88 79 88 79"/>
<glyph glyph-name="downward-arrow" unicode="&#80;" d="M60 416l196-198 196 198 60-61-256-259-256 259z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -157,3 +157,9 @@
.icon-birthday-cake:before {
content: "\4e";
}
.icon-down-open-mini:before {
content: "\4f";
}
.icon-downward-arrow:before {
content: "\50";
}

View file

@ -1,45 +0,0 @@
import PackageCard from "./PackageCard.svelte";
import type { Package } from "../types";
const SamplePkg: Package = {
slug: "mesonbuild_com",
homepage: "https://mesonbuild.com",
name: "mesonbuild.com",
version: "0.63.3",
last_modified: "2022-10-06T15:45:08.000Z",
full_name: "mesonbuild.com",
dl_count: 270745,
thumb_image_name: "mesonbuild_com_option 1.jpg ",
maintainer: "",
desc: "Fast and user friendly build system",
thumb_image_url: "https://tea.xyz/Images/packages/mesonbuild_com.jpg",
installs: 0
};
// More on how to set up stories at: https://storybook.js.org/docs/7.0/svelte/writing-stories/introduction
export default {
title: "Example/PackageCard",
component: PackageCard,
tags: ["docsPage"],
render: ({ pkg, link }: { pkg: Package; link: string }) => ({
Component: PackageCard,
props: { pkg, link }
}),
argTypes: {
pkg: {
name: "pkg",
description: "type Package"
},
link: {
name: "link"
}
}
};
// More on writing stories with args: https://storybook.js.org/docs/7.0/svelte/writing-stories/args
export const Example = {
args: {
pkg: SamplePkg,
link: "#"
}
};