From b267706f5cd961eeaa3372fc45afc9770b6e6257 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 2 Dec 2022 15:01:41 +0800 Subject: [PATCH] #55 install state and install on click --- packages/gui/src-tauri/Cargo.lock | 22 ++++++ packages/gui/src-tauri/Cargo.toml | 2 +- .../gui/src-tauri/src/handlers/packages.rs | 1 + packages/gui/src-tauri/tauri.conf.json | 29 ++++++- .../SearchPackages/SearchPackages.svelte | 43 +++++++++-- packages/gui/src/libs/api/mock.ts | 16 +++- packages/gui/src/libs/api/tauri.ts | 71 ++++++++++++++++- packages/gui/src/libs/stores.ts | 4 +- packages/gui/src/libs/types.ts | 14 ++++ .../ui/src/PackageCard/PackageCard.svelte | 77 ++++++++----------- 10 files changed, 212 insertions(+), 67 deletions(-) diff --git a/packages/gui/src-tauri/Cargo.lock b/packages/gui/src-tauri/Cargo.lock index 084781c..f276b59 100644 --- a/packages/gui/src-tauri/Cargo.lock +++ b/packages/gui/src-tauri/Cargo.lock @@ -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", diff --git a/packages/gui/src-tauri/Cargo.toml b/packages/gui/src-tauri/Cargo.toml index 8e3c1b4..6576123 100644 --- a/packages/gui/src-tauri/Cargo.toml +++ b/packages/gui/src-tauri/Cargo.toml @@ -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" diff --git a/packages/gui/src-tauri/src/handlers/packages.rs b/packages/gui/src-tauri/src/handlers/packages.rs index 6df1455..4e77c9a 100644 --- a/packages/gui/src-tauri/src/handlers/packages.rs +++ b/packages/gui/src-tauri/src/handlers/packages.rs @@ -1,4 +1,5 @@ #[tauri::command] pub fn install_package(package: String) { println!("installing: {}", package); + } \ No newline at end of file diff --git a/packages/gui/src-tauri/tauri.conf.json b/packages/gui/src-tauri/tauri.conf.json index 3fd334d..9b417d5 100644 --- a/packages/gui/src-tauri/tauri.conf.json +++ b/packages/gui/src-tauri/tauri.conf.json @@ -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": { diff --git a/packages/gui/src/components/SearchPackages/SearchPackages.svelte b/packages/gui/src/components/SearchPackages/SearchPackages.svelte index 1307062..648a093 100644 --- a/packages/gui/src/components/SearchPackages/SearchPackages.svelte +++ b/packages/gui/src/components/SearchPackages/SearchPackages.svelte @@ -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; - let packages: Package[] = []; + import { installPackage } from '@api'; + + let allPackages: GUIPackage[] = []; + let packagesIndex: Fuse; + 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]; + };
@@ -87,9 +99,24 @@
    - {#if packages.length} + {#if packages.length > 0} {#each packages as pkg} - +
    + { + try { + pkg.state = PackageStates.INSTALLING; + await installPackage(pkg.full_name); + pkg.state = PackageStates.INSTALLED; + } catch (error) { + console.error(error); + } + }} + /> +
    {/each} {:else} {#each Array(12) as _} diff --git a/packages/gui/src/libs/api/mock.ts b/packages/gui/src/libs/api/mock.ts index bdccc2a..aaf6d9d 100644 --- a/packages/gui/src/libs/api/mock.ts +++ b/packages/gui/src/libs/api/mock.ts @@ -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 { +export async function getPackages(): Promise { await delay(2000); - return packages; + return packages.map((pkg) => { + return { + ...pkg, + state: PackageStates.AVAILABLE + }; + }); } export async function getFeaturedPackages(): Promise { @@ -201,6 +208,11 @@ export async function getPackageReviews(full_name: string): Promise { 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)); } diff --git a/packages/gui/src/libs/api/tauri.ts b/packages/gui/src/libs/api/tauri.ts index 60bc07d..dc25694 100644 --- a/packages/gui/src/libs/api/tauri.ts +++ b/packages/gui/src/libs/api/tauri.ts @@ -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 { - const packages = await get('packages'); - return packages; +export async function getPackages(): Promise { + const [packages, installedPackages] = await Promise.all([ + get('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 { @@ -62,3 +78,50 @@ export async function getPackageReviews(full_name: string): Promise { 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; +} diff --git a/packages/gui/src/libs/stores.ts b/packages/gui/src/libs/stores.ts index 93fdc79..60dbe7b 100644 --- a/packages/gui/src/libs/stores.ts +++ b/packages/gui/src/libs/stores.ts @@ -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('/'); -export const packages = writable([]); +export const packages = writable([]); export const featuredPackages = writable([]); diff --git a/packages/gui/src/libs/types.ts b/packages/gui/src/libs/types.ts index ed18b7d..9924df9 100644 --- a/packages/gui/src/libs/types.ts +++ b/packages/gui/src/libs/types.ts @@ -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; +}; diff --git a/packages/ui/src/PackageCard/PackageCard.svelte b/packages/ui/src/PackageCard/PackageCard.svelte index 871d5f0..cf63f1d 100644 --- a/packages/ui/src/PackageCard/PackageCard.svelte +++ b/packages/ui/src/PackageCard/PackageCard.svelte @@ -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'); + };
    -
    - -
    - - - -

    {pkg.name}

    - {#if pkg.maintainer} -

    • {pkg.maintainer}

    - {/if} -
    -
    + +
    + +
    + + + +

    {pkg.name}

    + {#if pkg.maintainer} +

    • {pkg.maintainer}

    + {/if} +
    +
    +

    @@ -35,10 +43,7 @@ >{{- .installs -}} installs -->

    - - - - +
    @@ -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; - }