add install button to search results and package page (#372)

* add install button to search results and packae page
This commit is contained in:
ABevier 2023-03-31 21:41:35 -04:00 committed by GitHub
parent 5a81d87839
commit c8cf0e8c1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 147 additions and 138 deletions

View file

@ -1,37 +1,44 @@
<script lang="ts">
import type { GUIPackage } from "$libs/types";
import Package from "$components/packages/package.svelte";
import { PackageStates, type GUIPackage } from "$libs/types";
import clickOutside from "@tea/ui/lib/clickOutside";
import PackageStateButton from "./package-state-button.svelte";
export let buttonSize: "small" | "large" = "small";
export let pkg: GUIPackage;
export let availableVersions: string[] = [];
export let onClickCTA = async (_version: string) => {
export let onClick = async (_version: string) => {
console.log("do nothing");
};
$: isOpened = false;
const toggleOpen = () => {
if ([PackageStates.INSTALLING, PackageStates.UPDATING].includes(pkg.state)) {
return;
}
isOpened = !isOpened;
};
const handleClick = (version: string) => {
isOpened = false;
onClickCTA(version);
onClick(version);
};
const handleClickOutside = () => (isOpened = false);
</script>
<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 class="dropdown" use:clickOutside on:click_outside={handleClickOutside}>
<PackageStateButton {buttonSize} {pkg} onClick={toggleOpen} />
<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>
@ -67,7 +74,7 @@
.dropdown {
position: relative;
display: inline-block;
width: 130px;
min-width: 130px;
}
.divider {

View file

@ -3,6 +3,8 @@
import Button from "@tea/ui/button/button.svelte";
import { t } from "$libs/translations";
export let buttonSize: "small" | "large" = "small";
export let pkg: GUIPackage;
export let onClick = () => {
console.log("do nothing");
@ -22,7 +24,7 @@
</script>
<Button
class="h-8 w-full border text-xs text-white "
class={`w-full border text-xs text-white ${buttonSize === "small" ? "h-8" : "h-10"}`}
type="plain"
color={getColor(pkg.state)}
{onClick}

View file

@ -1,12 +1,14 @@
<script lang="ts">
import '$appcss';
import '@tea/ui/icons/icons.css';
import Button from '@tea/ui/button/button.svelte';
import "$appcss";
import "@tea/ui/icons/icons.css";
import Button from "@tea/ui/button/button.svelte";
import { installPackage } from '@native';
import { PackageStates, type GUIPackage } from '$libs/types';
import { packagesStore } from '$libs/stores';
import { shellOpenExternal } from '@native';
import { installPackage } from "@native";
import { PackageStates, type GUIPackage } from "$libs/types";
import { packagesStore } from "$libs/stores";
import { shellOpenExternal } from "@native";
import InstallButton from "$components/install-button/install-button.svelte";
import { findAvailableVersions } from "$libs/packages/pkg-utils";
export let pkg: GUIPackage;
let installing = false;
@ -16,10 +18,9 @@
await installPackage(pkg);
installing = false;
packagesStore.updatePackage(pkg.full_name, {
state: PackageStates.INSTALLED,
state: PackageStates.INSTALLED
});
}
};
</script>
<section class="mt-4 bg-black">
@ -35,24 +36,22 @@
</a>
{/if}
<p class="mt-4 text-sm">{pkg.desc}</p>
<menu class="h-10 grid grid-cols-2 gap-4 mt-4 text-xs">
{#if pkg.state === PackageStates.INSTALLED}
<Button type="plain" color="primary">
Latest version installed v{pkg.version}
</Button>
{:else if pkg.state === PackageStates.AVAILABLE}
<Button type="plain" color="secondary" onClick={install} loading={installing}>
{installing ? "Installing" : "Install"} v{pkg.version}
</Button>
{:else if pkg.state === PackageStates.NEEDS_UPDATE}
<Button type="plain" color="secondary" onClick={install} loading={installing}>
{installing ? "Updating" : "Update"} to v{pkg.version}
</Button>
{/if}
<menu class="mt-4 grid h-10 grid-cols-2 gap-4 text-xs">
<InstallButton
buttonSize="large"
{pkg}
availableVersions={findAvailableVersions(pkg)}
onClick={install}
/>
{#if pkg.github}
<Button class="h-10" type="plain" color="black" onClick={() => {
shellOpenExternal(`https://github.com/${pkg.github}`)
}}>View on github</Button>
<Button
class="h-10"
type="plain"
color="black"
onClick={() => {
shellOpenExternal(`https://github.com/${pkg.github}`);
}}>View on github</Button
>
{/if}
</menu>
</article>

View file

@ -2,7 +2,7 @@
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 InstallButton from "../install-button/install-button.svelte";
import type { GUIPackage } from "$libs/types";
import VersionLabel from "./version-label.svelte";
@ -39,7 +39,9 @@
</a>
<footer class="absolute bottom-0 left-0 flex w-full items-stretch justify-between gap-2 p-2">
<VersionLabel {pkg} {availableVersions} />
<InstallButton {pkg} {availableVersions} {onClickCTA} />
<div class="flex flex-col justify-end">
<InstallButton {pkg} {availableVersions} onClick={onClickCTA} />
</div>
</footer>
{#if progessLoading > 0 && progessLoading < 100}
<div class="absolute top-0 left-0 h-full w-full bg-black bg-opacity-50">

View file

@ -4,16 +4,13 @@
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";
import { findAvailableVersions } from "$libs/packages/pkg-utils";
export let tab = "all";
export let pkg: GUIPackage;
export let onClick: (version: string) => void;
let fakeLoadingProgress = 0;
const onClickCTA = async (version: string) => {
await onClick(version);
notificationStore.add({
@ -21,24 +18,6 @@
});
};
const findAvailableVersions = (pkg: GUIPackage) => {
// default to just showing the latest if bottles haven't loaded yet
if (!pkg.bottles) {
return [pkg.version];
}
const versionSet = new Set<string>();
for (const b of pkg.bottles) {
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";
};
onMount(() => {
packagesStore.fetchPackageBottles(pkg.full_name);
});

View file

@ -1,20 +1,20 @@
<script lang="ts">
import '$appcss';
import "$appcss";
import { watchResize } from "svelte-watch-resize";
import InfiniteScroll from "svelte-infinite-scroll";
// import { t } from '$libs/translations';
import type { GUIPackage } from '$libs/types';
import type { GUIPackage } from "$libs/types";
import moment from "moment";
import { PackageStates, SideMenuOptions } from '$libs/types';
import Preloader from '@tea/ui/Preloader/Preloader.svelte';
import { PackageStates, SideMenuOptions } from "$libs/types";
import Preloader from "@tea/ui/Preloader/Preloader.svelte";
import Package from "./package.svelte";
import { packagesStore } from '$libs/stores';
import { packagesStore } from "$libs/stores";
const { packages: allPackages } = packagesStore;
export let packageFilter: SideMenuOptions = SideMenuOptions.all;
export let sortBy: "popularity" | "most recent" = 'popularity';
export let sortDirection: 'asc' | 'desc' = 'desc';
export let sortBy: "popularity" | "most recent" = "popularity";
export let sortDirection: "asc" | "desc" = "desc";
export let scrollY = 0;
@ -22,7 +22,7 @@
let limit = loadMore;
// TODO: figure out a better type strategy here so that this breaks if SideMenuOptions is updated
const pkgFilters: { [key:string]: (pkg: GUIPackage) => boolean } = {
const pkgFilters: { [key: string]: (pkg: GUIPackage) => boolean } = {
[SideMenuOptions.all]: (_pkg: GUIPackage) => true,
[SideMenuOptions.installed]: (pkg: GUIPackage) => {
return [
@ -41,32 +41,34 @@
[SideMenuOptions.new_packages]: (pkg: GUIPackage) => {
return moment(pkg.created).isAfter(moment().subtract(30, "days"));
},
[SideMenuOptions.popular]: (pkg: GUIPackage) => pkg.categories.includes(SideMenuOptions.popular),
[SideMenuOptions.featured]: (pkg: GUIPackage) => pkg.categories.includes(SideMenuOptions.featured),
[SideMenuOptions.essentials]: (pkg: GUIPackage) => pkg.categories.includes(SideMenuOptions.essentials),
[SideMenuOptions.star_struct]: (pkg: GUIPackage) => pkg.categories.includes(SideMenuOptions.star_struct),
[SideMenuOptions.made_by_tea]: (pkg: GUIPackage) => pkg.full_name.includes("tea.xyz"),
}
[SideMenuOptions.popular]: (pkg: GUIPackage) =>
pkg.categories.includes(SideMenuOptions.popular),
[SideMenuOptions.featured]: (pkg: GUIPackage) =>
pkg.categories.includes(SideMenuOptions.featured),
[SideMenuOptions.essentials]: (pkg: GUIPackage) =>
pkg.categories.includes(SideMenuOptions.essentials),
[SideMenuOptions.star_struct]: (pkg: GUIPackage) =>
pkg.categories.includes(SideMenuOptions.star_struct),
[SideMenuOptions.made_by_tea]: (pkg: GUIPackage) => pkg.full_name.includes("tea.xyz")
};
const onScroll = (e: Event) => {
const target = e.target as HTMLInputElement;
scrollY = target.scrollTop || 0;
}
};
$: packages = $allPackages
.filter(pkgFilters[packageFilter] || pkgFilters.all)
.sort((a, b) => {
if (sortBy === "popularity") {
const aPop = +a.dl_count + a.installs;
const bPop = +b.dl_count + b.installs;
return sortDirection === "asc" ? aPop - bPop : bPop - aPop;
} else {
// most recent
const aDate = new Date(a.last_modified);
const bDate = new Date(b.last_modified);
return sortDirection === "asc" ? +aDate - +bDate : +bDate - +aDate;
}
});
$: packages = $allPackages.filter(pkgFilters[packageFilter] || pkgFilters.all).sort((a, b) => {
if (sortBy === "popularity") {
const aPop = +a.dl_count + a.installs;
const bPop = +b.dl_count + b.installs;
return sortDirection === "asc" ? aPop - bPop : bPop - aPop;
} else {
// most recent
const aDate = new Date(a.last_modified);
const bDate = new Date(b.last_modified);
return sortDirection === "asc" ? +aDate - +bDate : +bDate - +aDate;
}
});
const onResize = (node: HTMLElement) => {
const assumedCardHeight = 250;
@ -76,18 +78,15 @@
const addLimit = 3 * (minCardRows - cardRows);
limit += addLimit;
}
}
};
</script>
<div>
<ul class="grid grid-cols-3 gap-2 bg-black"
use:watchResize={onResize}
on:scroll={onScroll}
>
<ul class="grid grid-cols-3 gap-2 bg-black" use:watchResize={onResize} on:scroll={onScroll}>
{#if packages.length > 0}
{#each packages as pkg, index}
{#if index < limit}
<div class={pkg.state === PackageStates.INSTALLING ? 'animate-pulse' : ''}>
<div class={pkg.state === PackageStates.INSTALLING ? "animate-pulse" : ""}>
<Package
tab={packageFilter}
{pkg}
@ -103,7 +102,7 @@
</section>
{/each}
{/if}
<InfiniteScroll threshold={100} on:loadMore={() => limit += loadMore} />
<InfiniteScroll threshold={100} on:loadMore={() => (limit += loadMore)} />
</ul>
</div>
@ -124,9 +123,9 @@
/* Track */
::-webkit-scrollbar-track {
background: #272626;
background: #272626;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #949494;
@ -135,6 +134,6 @@
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: white;
background: white;
}
</style>

View file

@ -1,34 +1,35 @@
<script lang="ts">
import type { GUIPackage } from '$libs/types';
import Button from "@tea/ui/button/button.svelte";
import ImgLoader from "@tea/ui/img-loader/img-loader.svelte";
export let pkg:GUIPackage;
export let ctaLabel = "INSTALL";
export let ctaType: "ghost" | "plain" = "plain";
export let ctaColor: "green" | "secondary" = "secondary";
export let onClick: () => void;
import InstallButton from "$components/install-button/install-button.svelte";
import type { GUIPackage } from "$libs/types";
import { packagesStore } from "$libs/stores";
import { onMount } from "svelte";
import ImgLoader from "@tea/ui/img-loader/img-loader.svelte";
import { findAvailableVersions } from "$libs/packages/pkg-utils";
export let pkg: GUIPackage;
export let onClick: (_version: string) => Promise<void>;
onMount(() => {
packagesStore.fetchPackageBottles(pkg.full_name);
});
const availableVersions = findAvailableVersions(pkg);
</script>
<figure class="flex flex-row p-2 gap-2 border border-gray">
<ImgLoader
class="pkg-image object-cover w-16 h-16"
src={!pkg.thumb_image_url.includes("https://tea.xyz")
? "https://tea.xyz/Images/package-thumb-nolabel4.jpg"
: pkg.thumb_image_url}
alt={pkg.name}
/>
<header class="flex-grow">
<h1>{pkg.full_name}</h1>
<p class="text-xs line-clamp-2">{pkg.desc}</p>
</header>
<aside>
<Button
class="h-8 w-1/2 border border-gray p-2 text-xs"
onClick={onClick}
type={ctaType}
color={ctaColor}
>{ctaLabel}
</Button>
<footer class="text-xs text-center mt-2">v{pkg.version}</footer>
</aside>
</figure>
<figure class="border-gray flex flex-row gap-2 border p-2">
<ImgLoader
class="pkg-image h-16 w-16 object-cover"
src={!pkg.thumb_image_url.includes("https://tea.xyz")
? "https://tea.xyz/Images/package-thumb-nolabel4.jpg"
: pkg.thumb_image_url}
alt={pkg.name}
/>
<header class="flex-grow">
<h1>{pkg.full_name}</h1>
<p class="line-clamp-2 text-xs">{pkg.desc}</p>
</header>
<aside>
<InstallButton {pkg} {availableVersions} {onClick} />
<footer class="mt-2 text-center text-xs">v{pkg.version}</footer>
</aside>
</figure>

View file

@ -0,0 +1,20 @@
import type { GUIPackage } from "$libs/types";
import { clean } from "semver";
import semverCompare from "semver/functions/compare";
// Find a list of available versions for a package based on the bottles
export const findAvailableVersions = (pkg: GUIPackage) => {
// default to just showing the latest if bottles haven't loaded yet
if (!pkg.bottles) {
return [pkg.version];
}
const versionSet = new Set<string>();
for (const b of pkg.bottles) {
versionSet.add(b.version);
}
return Array.from(versionSet).sort((a, b) => semverCompare(cleanVersion(b), cleanVersion(a)));
};
export const cleanVersion = (version: string) => clean(version) || "0.0.0";