mirror of
https://github.com/ivabus/gui
synced 2025-06-08 00:00:27 +03:00
#55 install state and install on click
This commit is contained in:
parent
21e12427ad
commit
b267706f5c
10 changed files with 212 additions and 67 deletions
22
packages/gui/src-tauri/Cargo.lock
generated
22
packages/gui/src-tauri/Cargo.lock
generated
|
@ -1622,6 +1622,16 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6a252f1f8c11e84b3ab59d7a488e48e4478a93937e027076638c49536204639"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
|
@ -2339,6 +2349,16 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
|
@ -2552,6 +2572,7 @@ dependencies = [
|
|||
"objc",
|
||||
"once_cell",
|
||||
"open",
|
||||
"os_pipe",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
|
@ -2561,6 +2582,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_repr",
|
||||
"serialize-to-javascript",
|
||||
"shared_child",
|
||||
"state",
|
||||
"tar",
|
||||
"tauri-macros",
|
||||
|
|
|
@ -17,7 +17,7 @@ tauri-build = { version = "1.2.0", features = [] }
|
|||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2.0", features = ["http-all", "shell-open", "window-all"] }
|
||||
tauri = { version = "1.2.0", features = ["fs-read-dir", "http-all", "shell-all", "window-all"] }
|
||||
uuid = "1.2.1"
|
||||
futures = "0.3"
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#[tauri::command]
|
||||
pub fn install_package(package: String) {
|
||||
println!("installing: {}", package);
|
||||
|
||||
}
|
|
@ -18,10 +18,26 @@
|
|||
"scope": ["https://api.tea.xyz/v1/*", "https://github.com/*"]
|
||||
},
|
||||
"shell": {
|
||||
"all": false,
|
||||
"execute": false,
|
||||
"all": true,
|
||||
"execute": true,
|
||||
"open": true,
|
||||
"scope": [],
|
||||
"scope": [
|
||||
{
|
||||
"name": "tea-install",
|
||||
"cmd": "tea",
|
||||
"args": [{ "validator": "\\S+" }, "true"]
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"cmd": "node",
|
||||
"args": ["--version"]
|
||||
},
|
||||
{
|
||||
"name": "list-packages",
|
||||
"cmd": "ls",
|
||||
"args": ["-R ~/.tea/tea.xyz/var/www | grep 'xz\\|gz'"]
|
||||
}
|
||||
],
|
||||
"sidecar": false
|
||||
},
|
||||
"window": {
|
||||
|
@ -55,6 +71,13 @@
|
|||
"startDragging": true,
|
||||
"unmaximize": true,
|
||||
"unminimize": true
|
||||
},
|
||||
"fs": {
|
||||
"readDir": true,
|
||||
"scope": [
|
||||
"$HOME/.tea/*",
|
||||
"$APPDATA/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
|
|
|
@ -3,15 +3,18 @@
|
|||
import Fuse from 'fuse.js';
|
||||
import { packages as packagesStore, initializePackages } from '$libs/stores';
|
||||
import SortingButtons from './SortingButtons.svelte';
|
||||
import type { Package } from '@tea/ui/types';
|
||||
import type { GUIPackage } from '$libs/types';
|
||||
import { PackageStates } from '$libs/types';
|
||||
import PackageCard from '@tea/ui/PackageCard/PackageCard.svelte';
|
||||
import SearchInput from '@tea/ui/SearchInput/SearchInput.svelte';
|
||||
import Preloader from '@tea/ui/Preloader/Preloader.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let allPackages: Package[] = [];
|
||||
let packagesIndex: Fuse<Package>;
|
||||
let packages: Package[] = [];
|
||||
import { installPackage } from '@api';
|
||||
|
||||
let allPackages: GUIPackage[] = [];
|
||||
let packagesIndex: Fuse<GUIPackage>;
|
||||
let packages: GUIPackage[] = [];
|
||||
let initialized = false;
|
||||
|
||||
let sortBy = 'popularity';
|
||||
|
@ -19,7 +22,7 @@
|
|||
|
||||
const searchLimit = 5;
|
||||
|
||||
const setPackages = (pkgs: Package[]) => {
|
||||
const setPackages = (pkgs: GUIPackage[]) => {
|
||||
console.log('pkgs sub', pkgs);
|
||||
packages = pkgs.sort((a, b) => {
|
||||
if (sortBy === 'popularity') {
|
||||
|
@ -54,7 +57,7 @@
|
|||
});
|
||||
|
||||
const onSearch = (term: string) => {
|
||||
if (term !== '' && term.length > 3) {
|
||||
if (term !== '' && term.length > 1) {
|
||||
const res = packagesIndex.search(term);
|
||||
const matchingPackages = [];
|
||||
for (let i = 0; i < searchLimit; i++) {
|
||||
|
@ -73,6 +76,15 @@
|
|||
sortDirection = dir;
|
||||
setPackages(packages);
|
||||
};
|
||||
|
||||
const getCTALabel = (state: PackageStates): string => {
|
||||
return {
|
||||
[PackageStates.AVAILABLE]: 'INSTALL',
|
||||
[PackageStates.INSTALLED]: 'INSTALLED',
|
||||
[PackageStates.INSTALLING]: 'INSTALLING',
|
||||
[PackageStates.UNINSTALLED]: 'RE-INSTALL'
|
||||
}[state];
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="border border-gray bg-black">
|
||||
|
@ -87,9 +99,24 @@
|
|||
</div>
|
||||
</section>
|
||||
<ul class="grid grid-cols-3">
|
||||
{#if packages.length}
|
||||
{#if packages.length > 0}
|
||||
{#each packages as pkg}
|
||||
<PackageCard {pkg} link={`/packages/${pkg.slug}`} />
|
||||
<div class={pkg.state === PackageStates.INSTALLING ? 'animate-pulse' : ''}>
|
||||
<PackageCard
|
||||
{pkg}
|
||||
link={`/packages/${pkg.slug}`}
|
||||
ctaLabel={getCTALabel(pkg.state)}
|
||||
onClickCTA={async () => {
|
||||
try {
|
||||
pkg.state = PackageStates.INSTALLING;
|
||||
await installPackage(pkg.full_name);
|
||||
pkg.state = PackageStates.INSTALLED;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each Array(12) as _}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* * make cors work with api.tea.xyz/v1
|
||||
*/
|
||||
import type { Package, Review } from '@tea/ui/types';
|
||||
import type { GUIPackage } from '../types';
|
||||
import { PackageStates } from '../types';
|
||||
import { loremIpsum } from 'lorem-ipsum';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
@ -152,9 +154,14 @@ const packages: Package[] = [
|
|||
}
|
||||
];
|
||||
|
||||
export async function getPackages(): Promise<Package[]> {
|
||||
export async function getPackages(): Promise<GUIPackage[]> {
|
||||
await delay(2000);
|
||||
return packages;
|
||||
return packages.map((pkg) => {
|
||||
return {
|
||||
...pkg,
|
||||
state: PackageStates.AVAILABLE
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFeaturedPackages(): Promise<Package[]> {
|
||||
|
@ -201,6 +208,11 @@ export async function getPackageReviews(full_name: string): Promise<Review[]> {
|
|||
return reviews;
|
||||
}
|
||||
|
||||
export async function installPackage(full_name: string) {
|
||||
console.log('installing: ', full_name);
|
||||
await delay(10000);
|
||||
}
|
||||
|
||||
function delay(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
|
|
@ -11,9 +11,14 @@
|
|||
* - connect to a local platform api and returns a data
|
||||
*/
|
||||
import { getClient } from '@tauri-apps/api/http';
|
||||
// import { invoke } from '@tauri-apps/api';
|
||||
import { Command } from '@tauri-apps/api/shell';
|
||||
import { readDir, BaseDirectory } from '@tauri-apps/api/fs';
|
||||
import { Buffer } from 'buffer';
|
||||
import type { Package, Review } from '../types';
|
||||
import type { Package, Review } from '@tea/ui/types';
|
||||
import type { GUIPackage } from '../types';
|
||||
import * as mock from './mock';
|
||||
import { PackageStates } from '../types';
|
||||
|
||||
const username = 'user';
|
||||
const password = 'password';
|
||||
|
@ -46,9 +51,20 @@ const join = function (...paths: string[]) {
|
|||
.join('/');
|
||||
};
|
||||
|
||||
export async function getPackages(): Promise<Package[]> {
|
||||
const packages = await get<Package[]>('packages');
|
||||
return packages;
|
||||
export async function getPackages(): Promise<GUIPackage[]> {
|
||||
const [packages, installedPackages] = await Promise.all([
|
||||
get<Package[]>('packages'),
|
||||
getInstalledPackages()
|
||||
]);
|
||||
|
||||
return packages.map((pkg) => {
|
||||
const found = installedPackages.find((p) => p.full_name === pkg.full_name);
|
||||
return {
|
||||
...pkg,
|
||||
state: found ? PackageStates.INSTALLED : PackageStates.AVAILABLE,
|
||||
installed_version: found ? found.version : ''
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFeaturedPackages(): Promise<Package[]> {
|
||||
|
@ -62,3 +78,50 @@ export async function getPackageReviews(full_name: string): Promise<Review[]> {
|
|||
|
||||
return reviews;
|
||||
}
|
||||
|
||||
export async function installPackage(full_name: string) {
|
||||
try {
|
||||
await installPackageCommand(full_name);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function installPackageCommand(full_name: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const teaInstallCommand = new Command('tea-install', [`+${full_name}`, 'true']);
|
||||
teaInstallCommand.on('error', reject);
|
||||
|
||||
const handleLineOutput = async (line: string) => {
|
||||
const c = await child;
|
||||
if (line.includes('installed:')) {
|
||||
c.kill();
|
||||
resolve(c.pid);
|
||||
}
|
||||
};
|
||||
|
||||
teaInstallCommand.stdout.on('data', handleLineOutput);
|
||||
teaInstallCommand.stderr.on('data', handleLineOutput);
|
||||
|
||||
const child = teaInstallCommand.spawn();
|
||||
});
|
||||
}
|
||||
|
||||
async function getInstalledPackages() {
|
||||
const entries = await readDir('.tea/tea.xyz/var/www', {
|
||||
dir: BaseDirectory.Home,
|
||||
recursive: false
|
||||
});
|
||||
const packages = entries
|
||||
.filter((o) => o.path.match('^(.*).(g|x)z$'))
|
||||
.map((o) => {
|
||||
const [pkg_version] = (o?.name || '').split('+');
|
||||
const version = pkg_version.split('-').pop();
|
||||
const full_name = pkg_version.replace(`-${version}`, '');
|
||||
return {
|
||||
full_name,
|
||||
version
|
||||
};
|
||||
});
|
||||
return packages;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Package, Review } from '@tea/ui/types';
|
||||
|
||||
import type { GUIPackage } from '$libs/types';
|
||||
// TODO: figure out a better structure for managing states maybe turn them into models?
|
||||
|
||||
import { getPackages, getFeaturedPackages, getPackageReviews } from '@api';
|
||||
|
||||
export const backLink = writable<string>('/');
|
||||
|
||||
export const packages = writable<Package[]>([]);
|
||||
export const packages = writable<GUIPackage[]>([]);
|
||||
|
||||
export const featuredPackages = writable<Package[]>([]);
|
||||
|
||||
|
|
|
@ -2,3 +2,17 @@
|
|||
// else
|
||||
// please use the package @tea/ui/src/types.ts
|
||||
// things that go there are shared types/shapes like ie: Package
|
||||
|
||||
import type { Package } from '@tea/ui/types';
|
||||
|
||||
export enum PackageStates {
|
||||
AVAILABLE,
|
||||
INSTALLED,
|
||||
INSTALLING,
|
||||
UNINSTALLED
|
||||
}
|
||||
|
||||
export type GUIPackage = Package & {
|
||||
state: PackageStates;
|
||||
installed_version?: string;
|
||||
};
|
||||
|
|
|
@ -2,29 +2,37 @@
|
|||
import '../app.css';
|
||||
import type { Package } from '../types';
|
||||
import ImgLoader from '../ImgLoader/ImgLoader.svelte';
|
||||
|
||||
export let pkg: Package;
|
||||
export let link: string;
|
||||
export let ctaLabel: string;
|
||||
|
||||
export let onClickCTA = () => {
|
||||
console.log('do nothing');
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="package-card border border-gray p-4">
|
||||
<figure>
|
||||
<ImgLoader
|
||||
class="pkg-image"
|
||||
src={!pkg.thumb_image_url.includes('https://tea.xyz')
|
||||
? 'https://tea.xyz/Images/package-thumb-nolabel4.jpg'
|
||||
: pkg.thumb_image_url}
|
||||
alt={pkg.name}
|
||||
/>
|
||||
<article class="card-thumb-label">
|
||||
<i class="icon-tea-logo-iconasset-1">
|
||||
<!-- TODO: replace with icon.svg -->
|
||||
</i>
|
||||
<h3>{pkg.name}</h3>
|
||||
{#if pkg.maintainer}
|
||||
<h4>• {pkg.maintainer}</h4>
|
||||
{/if}
|
||||
</article>
|
||||
</figure>
|
||||
<a href={link}>
|
||||
<figure>
|
||||
<ImgLoader
|
||||
class="pkg-image"
|
||||
src={!pkg.thumb_image_url.includes('https://tea.xyz')
|
||||
? 'https://tea.xyz/Images/package-thumb-nolabel4.jpg'
|
||||
: pkg.thumb_image_url}
|
||||
alt={pkg.name}
|
||||
/>
|
||||
<article class="card-thumb-label">
|
||||
<i class="icon-tea-logo-iconasset-1">
|
||||
<!-- TODO: replace with icon.svg -->
|
||||
</i>
|
||||
<h3>{pkg.name}</h3>
|
||||
{#if pkg.maintainer}
|
||||
<h4>• {pkg.maintainer}</h4>
|
||||
{/if}
|
||||
</article>
|
||||
</figure>
|
||||
</a>
|
||||
<footer class="mt-4 flex items-center justify-between">
|
||||
<div>
|
||||
<p>
|
||||
|
@ -35,10 +43,7 @@
|
|||
<span class="package-install-no">>{{- .installs -}} installs</span> -->
|
||||
</p>
|
||||
</div>
|
||||
<!-- TODO: move this button into its own reusable component -->
|
||||
<a href={link}>
|
||||
<button class="detail-btn"><i class="icon-enter-arrow" />details</button>
|
||||
</a>
|
||||
<button class="p-2 font-machina" on:click={onClickCTA}>{ctaLabel}</button>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
|
@ -91,40 +96,18 @@
|
|||
padding: 0px;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
position: relative;
|
||||
float: right;
|
||||
right: 0;
|
||||
display: inline-block;
|
||||
font-family: 'pp-neue-machina', sans-serif;
|
||||
button {
|
||||
background-color: #1a1a1a;
|
||||
border: 0.5px solid #ffffff;
|
||||
color: #fff;
|
||||
padding-top: 0.279vw;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
width: 120px;
|
||||
height: 2.232vw;
|
||||
min-height: 34px;
|
||||
min-width: 120px;
|
||||
transition: 0.1s linear;
|
||||
}
|
||||
|
||||
.detail-btn:hover {
|
||||
button:hover {
|
||||
background-color: #8000ff;
|
||||
box-shadow: inset 0vw 0vw 0vw 0.223vw #1a1a1a !important;
|
||||
}
|
||||
|
||||
/* Icon Styling */
|
||||
|
||||
.detail-btn .icon-enter-arrow {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin-right: 0.558vw;
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.detail-btn:hover .icon-enter-arrow {
|
||||
display: inline-block;
|
||||
transform: rotate(-45deg) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue