Merge pull request #190 from teaxyz/package-page-metas

package page metas
This commit is contained in:
Neil 2023-02-10 09:39:40 +08:00 committed by GitHub
commit 73c9eb843e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 263 additions and 96 deletions

View file

@ -113,15 +113,12 @@ ipcMain.on('to-main', (event, count) => {
});
ipcMain.handle('get-installed-packages', async () => {
console.log('get installed pkgs: ipc');
const pkgs = await getInstalledPackages();
return pkgs;
});
ipcMain.handle('get-session', async () => {
console.log('get session');
const session = await readSessionData();
console.log('session:', session);
return session;
});

View file

@ -71,10 +71,12 @@
"dependencies": {
"@electron/asar": "^3.2.3",
"@types/bcryptjs": "^2.4.2",
"@types/js-yaml": "^4.0.5",
"@vitest/coverage-c8": "^0.27.1",
"axios": "^1.3.2",
"bcryptjs": "^2.4.3",
"buffer": "^6.0.3",
"dayjs": "^1.11.7",
"electron-context-menu": "^3.6.1",
"electron-log": "^4.4.8",
"electron-serve": "^1.1.0",
@ -87,7 +89,8 @@
"semver": "^7.3.8",
"svelte-markdown": "^0.2.3",
"svelte-watch-resize": "^1.0.3",
"upath": "^2.0.1"
"upath": "^2.0.1",
"yaml": "^2.2.1"
},
"pnpm": {
"onlyBuiltDependencies": [

View file

@ -95,8 +95,15 @@ export async function getDeviceAuth(deviceId: string): Promise<DeviceAuth> {
export async function getPackageBottles(packageName: string): Promise<Bottle[]> {
console.log('getting bottles for ', packageName);
const req = await axios.get(`https://app.tea.xyz/api/bottles/${packageName}`);
return req.data as Bottle[];
const pkg: Package = await apiGet<Package>(`packages/${packageName.replaceAll('/', ':')}`);
return pkg.bottles || [];
}
export async function getPackage(packageName: string): Promise<Partial<Package>> {
const pkg: Partial<Package> = await apiGet<Partial<Package>>(
`packages/${packageName.replaceAll('/', ':')}`
);
return pkg;
}
export async function registerDevice(): Promise<string> {

View file

@ -0,0 +1,48 @@
import axios from 'axios';
import type { Contributor, Package } from '@tea/ui/types';
const yaml = window.require('yaml');
export async function getPackageYaml(pkgYamlUrl: string) {
const url = pkgYamlUrl.replace('/github.com', '/raw.githubusercontent.com').replace('/blob', '');
const { data: rawYaml } = await axios.get(url);
const data = await yaml.parse(rawYaml);
return data;
}
export async function getReadme(owner: string, repo: string): Promise<string> {
let readme = '';
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}/readme`);
if (req.data?.download_url) {
const reqDl = await axios.get(req.data.download_url);
readme = reqDl.data;
}
return readme;
}
export async function getContributors(owner: string, repo: string): Promise<Contributor[]> {
// maintainer/repo
let contributors: Contributor[] = [];
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}/contributors`);
if (req.data) {
contributors = req.data.map((c: Contributor & { id: number }) => ({
login: c.login,
avatar_url: c.avatar_url,
name: c.name || '',
github_id: c.id,
contributions: c.contributions
}));
}
return contributors;
}
export async function getRepoAsPackage(owner: string, repo: string): Promise<Partial<Package>> {
const req = await axios.get(`https://api.github.com/repos/${owner}/${repo}`);
const pkg: Partial<Package> = {};
if (req.data) {
pkg.license = req.data?.license?.name || '';
}
return pkg;
}

View file

@ -2,10 +2,13 @@ import { writable } from 'svelte/store';
import type { GUIPackage } from '../types';
import { getPackages } from '@api';
import Fuse from 'fuse.js';
import { getPackage } from '@api';
import { getReadme, getContributors, getRepoAsPackage } from '$libs/github';
export default function initPackagesStore() {
let initialized = false;
const { subscribe, set } = writable<GUIPackage[]>([]);
const { subscribe, set, update } = writable<GUIPackage[]>([]);
const packages: GUIPackage[] = [];
let packagesIndex: Fuse<GUIPackage>;
@ -21,16 +24,67 @@ export default function initPackagesStore() {
subscribe((v) => packages.push(...v));
const updatePackageProp = (full_name: string, props: Partial<GUIPackage>) => {
update((pkgs) => {
const i = pkgs.findIndex((pkg) => pkg.full_name === full_name);
if (i >= 0) {
pkgs[i] = {
...pkgs[i],
...props
};
}
return pkgs;
});
};
const syncPackageData = async (guiPkg: Partial<GUIPackage>) => {
if (guiPkg.synced) return;
const pkg = await getPackage(guiPkg.full_name!); // ATM: pkg only bottles and github:string
const readmeMd = `# ${guiPkg.full_name} #
To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
`;
const updatedPackage: Partial<GUIPackage> = {
...pkg,
readme_md: readmeMd,
synced: true
};
if (pkg.github) {
const [owner, repo] = pkg.github.split('/');
const [readme, contributors, repoData] = await Promise.all([
getReadme(owner, repo),
getContributors(owner, repo),
getRepoAsPackage(owner, repo)
]);
if (readme) {
updatedPackage.readme_md = readme;
}
updatedPackage.contributors = contributors;
updatedPackage.license = repoData.license;
}
updatePackageProp(guiPkg.full_name!, updatedPackage);
};
return {
packages,
subscribe,
search: async (term: string, limit = 5): Promise<GUIPackage[]> => {
if (!term || !packagesIndex) return [];
// TODO: if online, use algolia else use Fuse
const res = packagesIndex.search(term, { limit });
const matchingPackages: GUIPackage[] = res.map((v) => v.item);
return matchingPackages;
},
subscribeToPackage: (slug: string, cb: (pkg: GUIPackage) => void) => {
subscribe((pkgs) => {
const foundPackage = pkgs.find((p) => p.slug === slug) as GUIPackage;
if (foundPackage) {
cb(foundPackage);
syncPackageData(foundPackage);
}
});
}
};
}

View file

@ -15,6 +15,7 @@ export enum PackageStates {
export type GUIPackage = Package & {
state: PackageStates;
installed_version?: string;
synced?: boolean;
};
export type Course = {

View file

@ -26,37 +26,17 @@
let reviews: Review[];
let bottles: Bottle[] = [];
let versions: string[] = [];
let readme: string;
let tabs: Tab[] = [];
const setPkg = (pkgs: Package[]) => {
const foundPackage = pkgs.find(({ slug }) => slug === data?.slug) as Package;
if (!pkg && foundPackage) {
pkg = foundPackage;
tabs.push({
label: 'details',
component: Markdown,
props: { pkg }
});
}
packagesStore.subscribeToPackage(data?.slug, (p) => {
pkg = p;
if (!reviews && pkg) {
packagesReviewStore.subscribe(pkg.full_name, (updatedReviews) => {
reviews = updatedReviews;
});
}
};
packagesStore.subscribe(setPkg);
featuredPackages.subscribe(setPkg);
onMount(async () => {
try {
const newBottles = await getPackageBottles(pkg.full_name);
const newVersion = newBottles.map((b) => b.version);
if (!bottles.length && pkg.bottles) {
const newVersion = pkg.bottles.map((b) => b.version);
versions = [...new Set(newVersion)];
bottles.push(...newBottles);
bottles.push(...pkg.bottles);
tabs = [
...tabs,
{
@ -67,10 +47,21 @@
}
}
];
} catch (err) {
console.error(err);
}
if (!readme && pkg.readme_md) {
readme = pkg.readme_md;
tabs = [
{
label: 'details',
component: Markdown,
props: { pkg, source: readme }
},
...tabs,
];
}
});
</script>
<div>
@ -84,7 +75,9 @@
<Tabs class="bg-black" {tabs} />
</div>
<div class="w-1/3">
<PackageMetas />
{#if pkg}
<PackageMetas {pkg} />
{/if}
</div>
</section>
<PageHeader class="mt-8" coverUrl="/images/headers/header_bg_1.png">SNIPPETS</PageHeader>

View file

@ -4,9 +4,6 @@
import './styles.css';
// TODO: rm sample
import { md } from './sample';
export let source: string;
const renderers = {
@ -15,5 +12,5 @@
</script>
<section class="markdown-body p-4">
<SvelteMarkdown source={source || md} {renderers} />
<SvelteMarkdown {source} {renderers} />
</section>

View file

@ -1,63 +1,101 @@
<script lang="ts">
import type { Package } from '../types';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export let pkg: Package;
const computeFileSize = (bytes: number): string => {
let n: number = bytes;
let unit = 'bytes';
let divisor = 1;
if (n > 1024 ** 3) {
unit = 'GB';
divisor = 1024 ** 3;
} else if (n > 1024 ** 2) {
unit = 'MB';
divisor = 1024 ** 2;
} else if (n > 1024) {
unit = 'KB';
divisor = 1024;
}
return `${(n / divisor).toFixed(2)} ${unit}`;
};
</script>
<section class="bg-black">
<h1 class="border border-gray p-4 text-primary">METADATA</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">4 months ago</span>
</li>
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">MIT or Apache-2.0</span>
</li>
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">22.7 kB</span>
</li>
{#if pkg?.bottles}
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">{dayjs().to(dayjs(pkg?.bottles[0].last_modified_at))}</span>
</li>
{/if}
{#if pkg?.license}
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">{pkg.license}</span>
</li>
{/if}
{#if pkg?.bottles}
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">{computeFileSize(pkg?.bottles[0].bytes)}</span>
</li>
{/if}
</ul>
<h1 class="border border-t-0 border-gray p-4 text-primary">HOMEPAGE</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">https://homepage.org</span>
</li>
</ul>
<h1 class="border border-t-0 border-gray p-4 text-primary">DOCUMENTATION</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">https://docs.org</span>
</li>
</ul>
<h1 class="border border-t-0 border-gray p-4 text-primary">GITHUB REPOSITORY</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">https://github.orm/owner/repo</span>
</li>
</ul>
<h1 class="border border-t-0 border-gray p-4 text-primary">OWNERS</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="flex items-center border border-gray p-4">
<figure class="h-5 w-5 bg-gray" />
<span class="ml-4">optimus_prime</span>
</li>
<li class="flex items-center border border-gray p-4">
<figure class="h-5 w-5 bg-gray" />
<span class="ml-4">batman_suparman</span>
</li>
<li class="flex items-center border border-gray p-4">
<figure class="h-5 w-5 bg-gray" />
<span class="ml-4">james_bond</span>
</li>
<li class="flex items-center border border-gray p-4">
<figure class="h-5 w-5 bg-gray" />
<span class="ml-4">pizza-rocks</span>
</li>
<li class="flex items-center border border-gray p-4">
<figure class="h-5 w-5 bg-gray" />
<span class="ml-4">cognito_inc</span>
<a target="_blank" rel="noreferrer" href={pkg.homepage}>
<span class="ml-4">{pkg.homepage}</span>
</a>
</li>
</ul>
{#if pkg.documentation_url}
<h1 class="border border-t-0 border-gray p-4 text-primary">DOCUMENTATION</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="border border-gray p-4">
<i class="icon-calendar" />
<span class="ml-4">{pkg.documentation_url}</span>
<a target="_blank" rel="noreferrer" href={pkg.documentation_url}>
<span class="ml-4">{pkg.documentation_url}</span>
</a>
</li>
</ul>
{/if}
{#if pkg.github}
<h1 class="border border-t-0 border-gray p-4 text-primary">GITHUB REPOSITORY</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="border border-gray p-4">
<i class="icon-calendar" />
<a target="_blank" rel="noreferrer" href={`https://github.com/${pkg.github}`}>
<span class="ml-4">{pkg.github}</span>
</a>
</li>
</ul>
{/if}
{#if pkg.contributors}
<h1 class="border border-t-0 border-gray p-4 text-primary">CONTRIBUTORS</h1>
<ul class="border border-t-0 border-gray p-4">
{#each pkg.contributors as contributor}
<a href={`https://github.com/${contributor.login}`} rel="noreferrer" target="_blank">
<li class="flex items-center border border-gray p-4">
<figure class="h-5 w-5 bg-gray">
<img src={contributor.avatar_url} alt={contributor.login} />
</figure>
<span class="ml-4">{contributor.login}</span>
</li>
</a>
{/each}
</ul>
{/if}
<h1 class="border border-t-0 border-gray p-4 text-primary">CATEGORIES</h1>
<ul class="border border-t-0 border-gray p-4">
<li class="border border-gray p-4">

View file

@ -1,6 +1,6 @@
<script lang="ts">
import type { Tab } from '../types';
import { onMount } from 'svelte';
import { afterUpdate } from 'svelte';
let clazz = '';
@ -12,8 +12,8 @@
let active: string;
onMount(() => {
if (tabs.length) {
afterUpdate(() => {
if (tabs.length && !active) {
active = tabs[0].label;
}
});

View file

@ -19,15 +19,17 @@ export interface Package {
dl_count: number;
installs: number;
reviews?: Review[];
package_yml_url?: string;
// metas
full_description?: string; // probably markdown
bottles?: Bottle[];
license?: string;
size_bytes?: number;
documentation_url?: string;
github_repository_url?: string;
owners?: Partial<Developer>[];
github?: string;
categories?: string[];
contributors?: Contributor[];
readme_md?: string;
}
export type AirtablePost = {
@ -58,6 +60,8 @@ export type Bottle = {
platform: string;
arch: string;
version: string;
bytes: number;
last_modified_at?: Date | string;
};
export type Tab = {
@ -94,3 +98,9 @@ export type Snippet = {
comment: string;
}[];
};
export type Contributor = Pick<Developer, 'avatar_url' | 'login' | 'name'> & {
github_id: number;
developer_id?: string;
contributions: number;
};

View file

@ -19,6 +19,7 @@ importers:
'@testing-library/jest-dom': ^5.16.5
'@testing-library/svelte': ^3.2.2
'@types/bcryptjs': ^2.4.2
'@types/js-yaml': ^4.0.5
'@types/testing-library__jest-dom': ^5.14.5
'@typescript-eslint/eslint-plugin': ^5.27.0
'@typescript-eslint/parser': ^5.27.0
@ -29,6 +30,7 @@ importers:
buffer: ^6.0.3
concurrently: ^7.6.0
cross-env: ^7.0.3
dayjs: ^1.11.7
electron: 22.1.0
electron-builder: ^23.6.0
electron-context-menu: ^3.6.1
@ -62,13 +64,16 @@ importers:
upath: ^2.0.1
vite: ^4.0.0
vitest: ^0.28.3
yaml: ^2.2.1
dependencies:
'@electron/asar': 3.2.3
'@types/bcryptjs': 2.4.2
'@types/js-yaml': 4.0.5
'@vitest/coverage-c8': 0.27.1_jsdom@21.0.0
axios: 1.3.2
bcryptjs: 2.4.3
buffer: 6.0.3
dayjs: 1.11.7
electron-context-menu: 3.6.1
electron-log: 4.4.8
electron-serve: 1.1.0
@ -82,6 +87,7 @@ importers:
svelte-markdown: 0.2.3_svelte@3.55.1
svelte-watch-resize: 1.0.3
upath: 2.0.1
yaml: 2.2.1
devDependencies:
'@electron/notarize': 1.2.3
'@playwright/experimental-ct-svelte': 1.29.2_svelte@3.55.1
@ -4624,6 +4630,10 @@ packages:
pretty-format: 29.3.1
dev: true
/@types/js-yaml/4.0.5:
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
dev: false
/@types/json-schema/7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
@ -6275,6 +6285,10 @@ packages:
time-zone: 1.0.0
dev: true
/dayjs/1.11.7:
resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
dev: false
/debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@ -13586,6 +13600,11 @@ packages:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
/yaml/2.2.1:
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
engines: {node: '>= 14'}
dev: false
/yargs-parser/20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}