mirror of
https://github.com/ivabus/gui
synced 2025-04-23 14:07:14 +03:00
Merge branch 'main' of github.com:teaxyz/gui
This commit is contained in:
commit
10dd8c69ef
12 changed files with 93 additions and 84 deletions
4
.github/workflows/build-sign-notarize.yml
vendored
4
.github/workflows/build-sign-notarize.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
|||
steps:
|
||||
- uses: teaxyz/setup@v0
|
||||
with:
|
||||
version: 0.25.0
|
||||
version: 0.26.2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: get gui version
|
||||
|
@ -282,4 +282,4 @@ jobs:
|
|||
run: |
|
||||
export S3_INSTALLERS_KEY=s3://preview.gui.tea.xyz/$prefix/dist.tgz
|
||||
aws s3 cp dist.tgz $S3_INSTALLERS_KEY
|
||||
echo s3-key=$S3_INSTALLERS_KEY >> $GITHUB_OUTPUT
|
||||
echo s3-key=$S3_INSTALLERS_KEY >> $GITHUB_OUTPUT
|
||||
|
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
|||
steps:
|
||||
- uses: teaxyz/setup@v0
|
||||
with:
|
||||
version: 0.25.0
|
||||
version: 0.26.2
|
||||
- uses: actions/checkout@v3
|
||||
- name: install app dependencies
|
||||
run: tea -E xc setup
|
||||
|
@ -51,7 +51,7 @@ jobs:
|
|||
steps:
|
||||
- uses: teaxyz/setup@v0
|
||||
with:
|
||||
version: 0.25.0
|
||||
version: 0.26.2
|
||||
- uses: actions/checkout@v3
|
||||
- name: cache node_modules build
|
||||
# TODO: cache issue in our self-hosted macos runner ESPIPE: invalid seek, read
|
||||
|
@ -257,4 +257,4 @@ jobs:
|
|||
```bash
|
||||
http://preview.gui.tea.xyz.s3-website-us-east-1.amazonaws.com/${{ needs.changes.outputs.preview_folder }}/${{ steps.app_files.outputs.dmg_x86 }}
|
||||
```
|
||||
copy-paste into a browser to download
|
||||
copy-paste into a browser to download
|
||||
|
|
|
@ -1,26 +1,15 @@
|
|||
<script lang="ts">
|
||||
import '$appcss';
|
||||
import '@tea/ui/icons/icons.css';
|
||||
import type { Bottle } from '@tea/ui/types';
|
||||
import Button from '@tea/ui/button/button.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { getPackageBottles } from '@native';
|
||||
|
||||
import { installPackage } from '@native';
|
||||
import { PackageStates, type GUIPackage } from '$libs/types';
|
||||
import { packagesStore } from '$libs/stores';
|
||||
import { shellOpenExternal } from '@native';
|
||||
|
||||
export let pkg: GUIPackage;
|
||||
let bottles: Bottle[] = [];
|
||||
|
||||
let installing = false;
|
||||
onMount(async () => {
|
||||
try {
|
||||
bottles = await getPackageBottles(pkg.full_name);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
const install = async () => {
|
||||
installing = true;
|
||||
|
@ -30,6 +19,7 @@
|
|||
state: PackageStates.INSTALLED,
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<section class="mt-4 bg-black">
|
||||
|
@ -60,9 +50,9 @@
|
|||
</Button>
|
||||
{/if}
|
||||
{#if pkg.github}
|
||||
<a href={`https://github.com/${pkg.github}`} target="_blank" rel="noreferrer">
|
||||
<Button class="border border-gray h-10">View on github</Button>
|
||||
</a>
|
||||
<Button class="border border-gray h-10" onClick={() => {
|
||||
shellOpenExternal(`https://github.com/${pkg.github}`)
|
||||
}}>View on github</Button>
|
||||
{/if}
|
||||
</menu>
|
||||
</article>
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
ul {
|
||||
margin-top: 0px;
|
||||
padding-top: 60px;
|
||||
height: calc(100vh - 95px);
|
||||
height: calc(100vh - 76px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<button on:click class={`text-xs w-full flex transition-all rounded-sm hover:bg-opacity-25 border-gray hover:border p-2 gap-2 items-center align-middle text-left hover:bg-gray box-border ${active && 'active'}`}>
|
||||
<i class={`icon-${icon} mt-1`}/>
|
||||
<div class="font-thin">{label}</div>
|
||||
<div class="font-thin text-sm">{label}</div>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
* - connect to a local platform api and returns a data
|
||||
*/
|
||||
|
||||
import semverCompare from "semver/functions/compare";
|
||||
import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types";
|
||||
import { type GUIPackage, type DeviceAuth, type Session, AuthStatus } from "./types";
|
||||
|
||||
|
@ -21,26 +20,24 @@ import { installPackageCommand } from "./native/cli";
|
|||
|
||||
import { get as apiGet } from "$libs/v1-client";
|
||||
import axios from "axios";
|
||||
import withRetry from "./utils/retry";
|
||||
|
||||
const log = window.require("electron-log");
|
||||
const { ipcRenderer, shell } = window.require("electron");
|
||||
|
||||
let retryLimit = 0;
|
||||
export async function getDistPackages(): Promise<Package[]> {
|
||||
let packages: Package[] = [];
|
||||
try {
|
||||
const req = await axios.get<Package[]>(
|
||||
"https://s3.amazonaws.com/preview.gui.tea.xyz/packages.json"
|
||||
);
|
||||
log.info("packages received:", req.data.length);
|
||||
packages = req.data;
|
||||
return withRetry(async () => {
|
||||
const req = await axios.get<Package[]>(
|
||||
"https://s3.amazonaws.com/preview.gui.tea.xyz/packages.json"
|
||||
);
|
||||
log.info("packages received:", req.data.length);
|
||||
return req.data;
|
||||
});
|
||||
} catch (error) {
|
||||
retryLimit++;
|
||||
log.error("getDistPackagesList:", error);
|
||||
if (retryLimit < 3) packages = await getDistPackages();
|
||||
return [];
|
||||
}
|
||||
retryLimit = 0;
|
||||
return packages;
|
||||
}
|
||||
|
||||
export async function getInstalledPackages(): Promise<InstalledPackage[]> {
|
||||
|
@ -135,36 +132,31 @@ export async function getDeviceAuth(deviceId: string): Promise<DeviceAuth> {
|
|||
|
||||
export async function getPackageBottles(packageName: string): Promise<Bottle[]> {
|
||||
try {
|
||||
const pkg = await apiGet<Package>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
log.info(`got ${pkg?.bottles?.length || 0} bottles for ${packageName}`);
|
||||
return (pkg && pkg.bottles) || [];
|
||||
return withRetry(async () => {
|
||||
const pkg = await apiGet<Package>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
log.info(`got ${pkg?.bottles?.length || 0} bottles for ${packageName}`);
|
||||
return (pkg && pkg.bottles) || [];
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
log.error("getPackageBottles:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const retryGetPackage: { [key: string]: number } = {};
|
||||
export async function getPackage(packageName: string): Promise<Partial<Package>> {
|
||||
let pkg: Partial<Package> = {};
|
||||
try {
|
||||
const data = await apiGet<Partial<Package>>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
if (data) {
|
||||
pkg = data;
|
||||
} else {
|
||||
throw new Error(`package:${packageName} not found`);
|
||||
}
|
||||
return await withRetry(async () => {
|
||||
const data = await apiGet<Partial<Package>>(`packages/${packageName.replaceAll("/", ":")}`);
|
||||
if (data) {
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(`package:${packageName} not found`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
retryGetPackage[packageName] = (retryGetPackage[packageName] || 0) + 1;
|
||||
if (retryGetPackage[packageName] < 3) {
|
||||
pkg = await getPackage(packageName);
|
||||
} else {
|
||||
log.info(`failed to get package:${packageName} after 3 tries`);
|
||||
}
|
||||
log.error("getPackage:", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
export const getSession = async (): Promise<Session | null> => {
|
||||
|
|
34
modules/desktop/src/libs/utils/retry.ts
Normal file
34
modules/desktop/src/libs/utils/retry.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
const log = window.require("electron-log");
|
||||
|
||||
export type RetryOptions = {
|
||||
// Number of times to retry. default 10
|
||||
maxRetries?: number;
|
||||
// Initial delay in ms. default 100
|
||||
initialDelayMs?: number;
|
||||
// Maximum delay in ms. default 5000
|
||||
maxDelayMs?: number;
|
||||
};
|
||||
|
||||
// Retry a function up to maxRetries times, with exponential backoff
|
||||
// With defaults retry cadence will look like this:
|
||||
// 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms, 5000ms, 5000ms, 5000ms, 5000ms
|
||||
export default async function withRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
{ maxRetries = 10, initialDelayMs = 100, maxDelayMs = 5000 }: RetryOptions = {}
|
||||
) {
|
||||
let retries = 0;
|
||||
let currentDelay = initialDelayMs;
|
||||
while (retries <= maxRetries) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
retries++;
|
||||
await wait(currentDelay);
|
||||
currentDelay = Math.min(currentDelay * 2, maxDelayMs);
|
||||
}
|
||||
}
|
||||
throw new Error(`Failed after ${maxRetries} retries`);
|
||||
}
|
||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@ -9,33 +9,25 @@ export async function get<T>(
|
|||
urlPath: string,
|
||||
params?: { [key: string]: string }
|
||||
): Promise<T | null> {
|
||||
try {
|
||||
console.log(`GET /v1/${urlPath}`);
|
||||
console.log(`GET /v1/${urlPath}`);
|
||||
|
||||
const [session] = await Promise.all([getSession()]);
|
||||
const [session] = await Promise.all([getSession()]);
|
||||
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: { Authorization: "public " };
|
||||
const headers =
|
||||
session?.device_id && session?.user
|
||||
? await getHeaders(`GET/${urlPath}`, session)
|
||||
: { Authorization: "public " };
|
||||
|
||||
const req = await axios.request({
|
||||
method: "GET",
|
||||
baseURL: "https://api.tea.xyz",
|
||||
url: ["v1", ...urlPath.split("/")].filter((p) => p).join("/"),
|
||||
headers,
|
||||
params
|
||||
});
|
||||
const req = await axios.request({
|
||||
method: "GET",
|
||||
baseURL: "https://api.tea.xyz",
|
||||
url: ["v1", ...urlPath.split("/")].filter((p) => p).join("/"),
|
||||
headers,
|
||||
params,
|
||||
validateStatus: (status) => status >= 200 && status < 300
|
||||
});
|
||||
|
||||
if (req.status == 200) {
|
||||
return req.data as T;
|
||||
} else {
|
||||
return await get<T>(urlPath, params || {});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("ERROR", error);
|
||||
return null;
|
||||
}
|
||||
return req.data as T;
|
||||
}
|
||||
|
||||
async function getHeaders(path: string, session: Session) {
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<div id="main-layout" class={`${$sideNavOpen ? "w-3/4" : "w-full"} transition-all font-inter border border-gray rounded-xl`}>
|
||||
<TopBar />
|
||||
<section class="relative" bind:this={view}>
|
||||
<div class="content p-2">
|
||||
<div class="content px-2">
|
||||
<slot/>
|
||||
</div>
|
||||
<SearchPopupResults />
|
||||
|
|
|
@ -74,14 +74,14 @@
|
|||
<span class="text-white">{pkg.full_name}</span>
|
||||
</header>
|
||||
{#if pkg}
|
||||
<div class="bg-black px-16">
|
||||
<div class="px-16">
|
||||
<section>
|
||||
<PackageBanner {pkg} />
|
||||
</section>
|
||||
|
||||
<section class="mt-8 flex gap-8">
|
||||
<div class="w-2/3">
|
||||
<Tabs class="bg-black" {tabs} />
|
||||
<Tabs {tabs} />
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
{#if pkg}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
-webkit-text-size-adjust: 100%;
|
||||
margin: 0;
|
||||
color: #c9d1d9;
|
||||
background-color: #0d1117;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji";
|
||||
font-size: 16px;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div class="icon pl-4">
|
||||
<i class="icon-search-icon" />
|
||||
</div>
|
||||
<input type="search" class="flex-grow pb-2" {placeholder} on:keyup={onChange} />
|
||||
<input type="search" class="flex-grow pb-2 text-sm" {placeholder} on:keyup={onChange} />
|
||||
</section>
|
||||
|
||||
<!-- <input type="search" class="w-full bg-black h-12 p-4 border border-x-0 border-gray"/> -->
|
||||
|
@ -49,7 +49,7 @@
|
|||
|
||||
section input {
|
||||
color: #00ffd0;
|
||||
margin-bottom: -5px;
|
||||
margin-bottom: -2px;
|
||||
min-width: 60%;
|
||||
padding: 0px;
|
||||
background-color: #1a1a1a !important;
|
||||
|
@ -57,6 +57,7 @@
|
|||
color: #00ffd0;
|
||||
outline: none;
|
||||
border-radius: 0px;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
section.medium input {
|
||||
|
@ -65,6 +66,7 @@
|
|||
section.large input {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
section input::placeholder {
|
||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: #949494;
|
||||
|
|
Loading…
Reference in a new issue