Merge pull request #42 from teaxyz/search-packages-component

Search Packages Page
This commit is contained in:
Neil 2022-11-29 09:13:43 +08:00 committed by GitHub
commit 6cf00b00c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 504 additions and 217 deletions

View file

@ -41,7 +41,8 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
"buffer": "^6.0.3" "buffer": "^6.0.3",
"fuse.js": "^6.6.2"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [

View file

@ -102,7 +102,7 @@
"height": 600, "height": 600,
"resizable": true, "resizable": true,
"title": "gui", "title": "gui",
"width": 800, "width": 1024,
"decorations": false "decorations": false
} }
] ]

View file

@ -4,3 +4,4 @@
</script> </script>
<Placeholder label="FeaturedCourses" /> <Placeholder label="FeaturedCourses" />
<h1>test</h1>

View file

@ -2,6 +2,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { open } from '@tauri-apps/api/shell'; import { open } from '@tauri-apps/api/shell';
import { appWindow } from '@tauri-apps/api/window'; import { appWindow } from '@tauri-apps/api/window';
import SearchInput from '@tea/ui/SearchInput/SearchInput.svelte';
import { beforeUpdate } from 'svelte'; import { beforeUpdate } from 'svelte';
@ -53,6 +54,10 @@
routes[0].active = false; routes[0].active = false;
} }
}); });
const onSearch = (term: string) => {
console.log('navbar search:', term);
};
</script> </script>
<ul id="NavBar"> <ul id="NavBar">
@ -73,11 +78,7 @@
</a> </a>
</nav> </nav>
<input <SearchInput size="small" {onSearch} />
class="w-full bg-black h-12 p-4 border border-x-0 border-gray"
type="search"
placeholder="search"
/>
{#each routes as route} {#each routes as route}
<li class={route.active ? 'nav_button active' : 'nav_button'}> <li class={route.active ? 'nav_button active' : 'nav_button'}>

View file

@ -2,7 +2,7 @@
export let label = ''; export let label = '';
</script> </script>
<section> <section class="p-8 bg-gray">
<header>{label}</header> <header>{label}</header>
<slot /> <slot />
</section> </section>
@ -14,7 +14,7 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
min-width: 100%; min-width: 100%;
background-color: #ccc; /* background-color: #ccc; */
display: flex; display: flex;
} }
header { header {

View file

@ -1,15 +1,25 @@
<script type="ts"> <script type="ts">
import '$appcss'; import '$appcss';
import Placeholder from '$components/Placeholder/Placeholder.svelte'; import Fuse from 'fuse.js';
import { packages as packagesStore, initializePackages } from '$libs/stores'; import { packages as packagesStore, initializePackages } from '$libs/stores';
import type { Package } from '$libs/types';
import type { Package } from '@tea/ui/types';
import PackageCard from '@tea/ui/PackageCard/PackageCard.svelte';
import SearchInput from '@tea/ui/SearchInput/SearchInput.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let allPackages: Package[] = [];
let packagesIndex: Fuse<Package>;
let packages: Package[] = []; let packages: Package[] = [];
let initialized = false; let initialized = false;
const searchLimit = 5;
packagesStore.subscribe((v) => { packagesStore.subscribe((v) => {
packages = v; allPackages = v;
packages = allPackages;
packagesIndex = new Fuse(allPackages, {
keys: ['name', 'full_name', 'desc']
});
}); });
onMount(async () => { onMount(async () => {
@ -18,18 +28,34 @@
initializePackages(); initializePackages();
} }
}); });
const onSearch = (term: string) => {
if (term !== '' && term.length > 3) {
const res = packagesIndex.search(term);
packages = [];
for (let i = 0; i < searchLimit; i++) {
if (res[i]) {
packages.push(res[i].item);
}
}
} else {
packages = allPackages;
}
};
</script> </script>
<div class="bg-black border border-gray"> <div class="bg-black border border-gray">
<section class="flex"> <section class="flex justify-between items-center">
<h2>Filter Packages</h2> <div>
<input type="search" class="text-white bg-black border border-gray" /> <SearchInput size="medium" {onSearch} />
</div>
<div class="pr-4">
<section class="h-12 w-48 border border-gray" />
</div>
</section> </section>
<ul class="grid grid-cols-3 gap-8 mt-8"> <ul class="grid grid-cols-3">
{#each packages as pkg} {#each packages as pkg}
<li> <PackageCard {pkg} link={`/packages/${pkg.full_name}`} />
<a href={`/packages/${pkg.slug}`}><Placeholder label={pkg.name} /></a>
</li>
{/each} {/each}
</ul> </ul>
</div> </div>

View file

@ -6,8 +6,6 @@
* * make cors work with api.tea.xyz/v1 * * make cors work with api.tea.xyz/v1
*/ */
import type { Package } from '../types'; import type { Package } from '../types';
export async function getPackages(): Promise<Package[]> {
const packages: Package[] = [ const packages: Package[] = [
{ {
slug: 'mesonbuild_com', slug: 'mesonbuild_com',
@ -150,5 +148,7 @@ export async function getPackages(): Promise<Package[]> {
installs: 0 installs: 0
} }
]; ];
export async function getPackages(): Promise<Package[]> {
return packages; return packages;
} }

View file

@ -1,14 +1,4 @@
export interface Package { // as much possible add types here that are unique to @tea/gui use only
slug: string; // else
version: string; // please use the package @tea/ui/src/types.ts
full_name: string; // things that go there are shared types/shapes like ie: Package
name: string;
maintainer: string;
homepage: string;
last_modified: Date | string;
thumb_image_url: string;
thumb_image_name: string;
desc: string;
dl_count: number;
installs: number;
}

View file

@ -23,6 +23,7 @@
{/if} {/if}
<figure /> <figure />
<div> <div>
<!-- all pages get inserted in this slot -->
<slot /> <slot />
</div> </div>
</section> </section>

View file

@ -14,7 +14,7 @@
"$libs/*": ["src/libs/*"], "$libs/*": ["src/libs/*"],
"@api": ["src/lib/api/tauri.ts"], "@api": ["src/lib/api/tauri.ts"],
"$components/*": ["src/components/*"], "$components/*": ["src/components/*"],
"@tea/ui": ["../ui/src/*"] "@tea/ui/*": ["../ui/src/*"]
} }
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias

View file

@ -1,8 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {
"@api": ["src/lib/api_cli.ts"],
}
},
}

View file

@ -1,5 +1,8 @@
module.exports = { module.exports = {
root: true, root: true,
globals: {
NodeJS: true
},
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',

View file

@ -0,0 +1 @@
@import '../app.css';

View file

@ -0,0 +1,45 @@
import PackageCard from './PackageCard.svelte';
import type { Package } from '../types';
const SamplePkg: Package = {
slug: 'mesonbuild_com',
homepage: 'https://mesonbuild.com',
name: 'mesonbuild.com',
version: '0.63.3',
last_modified: '2022-10-06T15:45:08.000Z',
full_name: 'mesonbuild.com',
dl_count: 270745,
thumb_image_name: 'mesonbuild_com_option 1.jpg ',
maintainer: '',
desc: 'Fast and user friendly build system',
thumb_image_url: 'https://tea.xyz/Images/packages/mesonbuild_com.jpg',
installs: 0
};
// More on how to set up stories at: https://storybook.js.org/docs/7.0/svelte/writing-stories/introduction
export default {
title: 'Example/PackageCard',
component: PackageCard,
tags: ['docsPage'],
render: ({ pkg, link }: { pkg: Package; link: string }) => ({
Component: PackageCard,
props: { pkg }
}),
argTypes: {
pkg: {
name: 'pkg',
description: 'type Package'
},
link: {
name: 'link'
}
}
};
// More on writing stories with args: https://storybook.js.org/docs/7.0/svelte/writing-stories/args
export const Example = {
args: {
pkg: SamplePkg,
link: '#'
}
};

View file

@ -0,0 +1,129 @@
<script type="ts">
import './PackageCard.css';
import type { Package } from '../types';
export let pkg: Package;
export let link: string;
</script>
<section class="p-4 border border-gray">
<figure>
<img
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>&#x2022;&nbsp;{pkg.maintainer}</h4>
{/if}
</article>
</figure>
<footer class="flex mt-4 justify-between items-center">
<div>
<p>
<span>V&NonBreakingSpace;{pkg.version}</span>
<!--
TODO: uncomment once install counts improve
<br>
<span class="package-install-no">>{{- .installs -}}&nbsp;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>
</footer>
</section>
<style>
section {
background-color: #1a1a1a;
transition: all 0.3s;
width: 100%;
}
figure {
position: relative;
}
.detail-btn {
position: relative;
float: right !important;
right: 0;
}
img {
box-shadow: 0px 0px 12px #0c0c0c !important;
width: 100%;
height: 100%;
}
.card-thumb-label i {
font-size: 1.5vw;
}
.card-thumb-label h3 {
color: black;
font-size: 1.8vw;
line-height: 1.8vw;
margin: 0px 0px 0.5vw 0vw;
padding: 0px;
}
.card-thumb-label {
position: absolute;
background: rgba(255, 255, 255, 0.9);
left: 0;
bottom: 0vw;
padding: 1.116vw;
text-align: left;
width: 90%;
height: 40%;
}
.card-thumb-label h4 {
color: black;
font-size: 1.5vw;
line-height: 1.5vw;
margin: 0px;
padding: 0px;
}
.detail-btn {
display: inline-block;
font-family: 'pp-neue-machina', sans-serif;
background-color: #1a1a1a;
border: 0.5px solid #ffffff;
color: #fff;
padding-top: 0.279vw;
text-decoration: none;
text-transform: uppercase;
width: 100px;
height: 2.232vw;
min-height: 34px;
transition: 0.1s linear;
}
.detail-btn: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>

View file

@ -0,0 +1 @@
@import '../app.css';

View file

@ -0,0 +1,31 @@
import SearchInput from './SearchInput.svelte';
export default {
title: 'Example/SearchInput',
component: SearchInput,
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/7.0/svelte/writing-docs/docs-page
tags: [],
render: (args) => ({
Component: SearchInput,
props: args
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/7.0/svelte/configure/story-layout
// layout: 'fullscreen'
},
argTypes: {
// onLogin: { action: 'onLogin' },
// onLogout: { action: 'onLogout' },
// onCreateAccount: { action: 'onCreateAccount' }
}
};
export const Small = {
args: {
user: {
name: 'Jane Doe'
}
}
};
export const Big = {};

View file

@ -0,0 +1,72 @@
<script type="ts">
import './SearchInput.css';
export let size: 'small' | 'medium' | 'large' = 'small';
export let onSearch: (text: string) => void;
let timer: NodeJS.Timeout;
const onChange = (e: KeyboardEvent) => {
const t = e.target as HTMLInputElement;
clearTimeout(timer);
timer = setTimeout(() => {
onSearch && onSearch(t.value);
}, 300);
};
</script>
<section class={`flex items-center ${size}`}>
<div class="icon">
<i class="icon-search-icon" />
</div>
<input type="search" placeholder="search_" on:keyup={onChange} />
</section>
<!-- <input type="search" class="w-full bg-black h-12 p-4 border border-x-0 border-gray"/> -->
<style>
.icon-search-icon {
font-size: 30px;
color: #949494;
margin-right: 20px;
position: relative;
top: 2px;
}
section {
padding: 0px;
display: flex;
justify-content: space-between;
align-items: center;
}
section.medium {
height: 75px;
}
section.large {
height: 150px;
}
section input {
font-family: 'pp-neue-machina', sans-serif;
color: #00ffd0;
text-transform: uppercase;
margin-bottom: -5px;
min-width: 60%;
padding: 0px;
background-color: #1a1a1a !important;
border: none;
color: #00ffd0;
outline: none;
border-radius: 0px;
}
section.medium input {
font-size: 24px;
}
section.large input {
font-size: 32px;
}
section input::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: #949494;
opacity: 1; /* Firefox */
}
</style>

14
packages/ui/src/types.ts Normal file
View file

@ -0,0 +1,14 @@
export interface Package {
slug: string;
version: string;
full_name: string;
name: string;
maintainer: string;
homepage: string;
last_modified: Date | string;
thumb_image_url: string;
thumb_image_name: string;
desc: string;
dl_count: number;
installs: number;
}

View file

@ -21,6 +21,7 @@ importers:
eslint: ^8.16.0 eslint: ^8.16.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-svelte3: ^4.0.0 eslint-plugin-svelte3: ^4.0.0
fuse.js: ^6.6.2
postcss: ^8.4.19 postcss: ^8.4.19
prettier: ^2.6.2 prettier: ^2.6.2
prettier-plugin-svelte: ^2.7.0 prettier-plugin-svelte: ^2.7.0
@ -35,11 +36,12 @@ importers:
dependencies: dependencies:
'@tauri-apps/api': 1.2.0 '@tauri-apps/api': 1.2.0
buffer: 6.0.3 buffer: 6.0.3
fuse.js: 6.6.2
devDependencies: devDependencies:
'@playwright/test': 1.25.0 '@playwright/test': 1.25.0
'@sveltejs/adapter-auto': 1.0.0-next.89 '@sveltejs/adapter-auto': 1.0.0-next.89
'@sveltejs/adapter-static': 1.0.0-next.48 '@sveltejs/adapter-static': 1.0.0-next.48
'@sveltejs/kit': 1.0.0-next.562_svelte@3.53.1+vite@3.2.4 '@sveltejs/kit': 1.0.0-next.563_svelte@3.53.1+vite@3.2.4
'@tauri-apps/cli': 1.2.0 '@tauri-apps/cli': 1.2.0
'@tea/ui': link:../ui '@tea/ui': link:../ui
'@typescript-eslint/eslint-plugin': 5.43.0_wze2rj5tow7zwqpgbdx2buoy3m '@typescript-eslint/eslint-plugin': 5.43.0_wze2rj5tow7zwqpgbdx2buoy3m
@ -99,7 +101,7 @@ importers:
'@storybook/svelte-vite': 7.0.0-alpha.51_xlscsvjyync2sh57nhuoanpbpq '@storybook/svelte-vite': 7.0.0-alpha.51_xlscsvjyync2sh57nhuoanpbpq
'@storybook/testing-library': 0.0.13_wcqkhtmu7mswc6yz4uyexck3ty '@storybook/testing-library': 0.0.13_wcqkhtmu7mswc6yz4uyexck3ty
'@sveltejs/adapter-auto': 1.0.0-next.89 '@sveltejs/adapter-auto': 1.0.0-next.89
'@sveltejs/kit': 1.0.0-next.561_svelte@3.53.1+vite@3.2.4 '@sveltejs/kit': 1.0.0-next.563_svelte@3.53.1+vite@3.2.4
'@sveltejs/package': 1.0.0-next.1_7dvewpees4iyn2tkw2qzal77a4 '@sveltejs/package': 1.0.0-next.1_7dvewpees4iyn2tkw2qzal77a4
'@typescript-eslint/eslint-plugin': 5.43.0_wze2rj5tow7zwqpgbdx2buoy3m '@typescript-eslint/eslint-plugin': 5.43.0_wze2rj5tow7zwqpgbdx2buoy3m
'@typescript-eslint/parser': 5.43.0_e3uo4sehh4zr4i6m57mkkxxv7y '@typescript-eslint/parser': 5.43.0_e3uo4sehh4zr4i6m57mkkxxv7y
@ -3002,36 +3004,8 @@ packages:
resolution: {integrity: sha512-Z5Z+QZOav6D0KDeU3ReksGERJg/sX1k5OKWWXyQ11OwGErEEwSXHYRUyjaBmZEPeGzpVVGwwMUK8YWJlG/MKeA==} resolution: {integrity: sha512-Z5Z+QZOav6D0KDeU3ReksGERJg/sX1k5OKWWXyQ11OwGErEEwSXHYRUyjaBmZEPeGzpVVGwwMUK8YWJlG/MKeA==}
dev: true dev: true
/@sveltejs/kit/1.0.0-next.561_svelte@3.53.1+vite@3.2.4: /@sveltejs/kit/1.0.0-next.563_svelte@3.53.1+vite@3.2.4:
resolution: {integrity: sha512-N8HQvS6gcm7R78ADfM4xjhuFS3Ir+Ezce3De8WOnISXQ1tS2npc5LMH9LRHHi14nfosAfJ7vUlcLwLE6N/I7+Q==} resolution: {integrity: sha512-RvQSE6dOuH4vE2hM5K/DezJlm9RjC5EMQK8X46mBXIggp8unaDH+YJyF+KRvAZE5sV93Hk5xaq0WZa8jtU42Jw==}
engines: {node: '>=16.14'}
hasBin: true
requiresBuild: true
peerDependencies:
svelte: ^3.44.0
vite: ^3.2.0
dependencies:
'@sveltejs/vite-plugin-svelte': 1.3.1_svelte@3.53.1+vite@3.2.4
'@types/cookie': 0.5.1
cookie: 0.5.0
devalue: 4.2.0
kleur: 4.1.5
magic-string: 0.26.7
mime: 3.0.0
sade: 1.8.1
set-cookie-parser: 2.5.1
sirv: 2.0.2
svelte: 3.53.1
tiny-glob: 0.2.9
undici: 5.12.0
vite: 3.2.4
transitivePeerDependencies:
- diff-match-patch
- supports-color
dev: true
/@sveltejs/kit/1.0.0-next.562_svelte@3.53.1+vite@3.2.4:
resolution: {integrity: sha512-VgJzjtfjVLW/4A/vDtURc10PrS3bb/N62LHzqLZcUNb5+eN4a0k5cayC7Hz2tmtvrb2Qsg+piEAogvqjKBxrOg==}
engines: {node: '>=16.14'} engines: {node: '>=16.14'}
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true
@ -6043,6 +6017,11 @@ packages:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
dev: true dev: true
/fuse.js/6.6.2:
resolution: {integrity: sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==}
engines: {node: '>=10'}
dev: false
/gauge/3.0.2: /gauge/3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'} engines: {node: '>=10'}