Merge pull request #63 from teaxyz/featured-course

#35 FeaturedCourses
This commit is contained in:
Neil 2022-12-06 13:29:32 +08:00 committed by GitHub
commit b7ae21c851
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 217 additions and 91 deletions

View file

@ -1,7 +1,26 @@
<script lang="ts"> <script lang="ts">
import '$appcss'; import '$appcss';
import Placeholder from '$components/Placeholder/Placeholder.svelte'; import { onMount } from 'svelte';
import type { Course } from '$libs/types';
import Gallery from '@tea/ui/Gallery/Gallery.svelte';
import { getFeaturedCourses } from '@api';
let courses: Course[] = [];
onMount(async () => {
if (!courses.length) {
courses = await getFeaturedCourses();
}
});
</script> </script>
<Placeholder label="FeaturedCourses" /> <Gallery
<h1>test</h1> title="FEATURED COURSES"
items={courses.map((course) => ({
title: course.title,
subTitle: course.sub_title,
imageUrl: course.banner_image_url,
link: course.link
}))}
/>

View file

@ -1,10 +1,9 @@
<script lang="ts"> <script lang="ts">
import '$appcss'; import '$appcss';
import { onDestroy, onMount } from 'svelte'; import { onMount } from 'svelte';
import { watchResize } from 'svelte-watch-resize';
import type { Package } from '@tea/ui/types'; import type { Package } from '@tea/ui/types';
import Preloader from '@tea/ui/Preloader/Preloader.svelte';
import FeaturedPackage from './FeaturedPackage.svelte'; import Gallery from '@tea/ui/Gallery/Gallery.svelte';
import { import {
featuredPackages as featuredPackagesStore, featuredPackages as featuredPackagesStore,
initializeFeaturedPackages initializeFeaturedPackages
@ -12,82 +11,23 @@
let featuredPackages: Package[] = []; let featuredPackages: Package[] = [];
let pkgFocus = 0;
let width = 0;
let styleFeaturedPackages: string;
function resetFeaturedStyle() {
const position = pkgFocus * width;
styleFeaturedPackages = `
width: ${featuredPackages.length * width}px;
left: -${position}px;
transition: left 0.6s ease-in;
`;
}
function handleContainerResize(node: HTMLElement) {
width = node.clientWidth;
resetFeaturedStyle();
}
let loop: NodeJS.Timer;
function resetLoop() {
if (loop) clearInterval(loop);
loop = setInterval(() => {
pkgFocus++;
if (pkgFocus === featuredPackages.length) {
pkgFocus = 0;
}
resetFeaturedStyle();
}, 3000);
resetFeaturedStyle();
}
featuredPackagesStore.subscribe((v) => { featuredPackagesStore.subscribe((v) => {
featuredPackages = v; featuredPackages = v;
}); });
onDestroy(() => clearInterval(loop));
onMount(() => { onMount(() => {
if (!featuredPackages.length) { if (!featuredPackages.length) {
initializeFeaturedPackages(); initializeFeaturedPackages();
} }
resetLoop();
}); });
</script> </script>
<section class="h-96 w-full bg-black" use:watchResize={handleContainerResize}> <Gallery
<!-- <Placeholder label="FeaturedPackages" /> --> title="FEATURED PACKAGES"
<header class="flex h-12 items-center justify-between bg-accent px-2"> items={featuredPackages.map((pkg) => ({
<p>FEATURED PACKAGES</p> title: pkg.full_name,
<ul class="flex gap-2"> subTitle: pkg.maintainer || '',
{#each featuredPackages as pkg, i} imageUrl: pkg.thumb_image_url,
<button link: `/packages/${pkg.slug}`
on:click={() => { }))}
pkgFocus = i; />
resetLoop();
}}
class={`bg-purple h-4 w-4 rounded-lg border-2 border-white transition-colors ${
i === pkgFocus ? 'bg-purple-900' : ''
}`}
/>
{/each}
</ul>
</header>
<figure class="absolute bottom-0 top-12 left-0 right-0 overflow-hidden">
{#if featuredPackages.length}
<section class="absolute top-0 flex h-full" style={styleFeaturedPackages}>
{#each featuredPackages as pkg}
<div class="h-full" style={`width:${width}px`}>
<a href={`/packages/${pkg.slug}`}>
<FeaturedPackage {pkg} {width} />
</a>
</div>
{/each}
</section>
{:else}
<Preloader />
{/if}
</figure>
</section>

View file

@ -6,7 +6,7 @@
* * make cors work with api.tea.xyz/v1 * * make cors work with api.tea.xyz/v1
*/ */
import type { Package, Review } from '@tea/ui/types'; import type { Package, Review } from '@tea/ui/types';
import type { GUIPackage } from '../types'; import type { GUIPackage, Course } from '../types';
import { PackageStates } from '../types'; import { PackageStates } from '../types';
import { loremIpsum } from 'lorem-ipsum'; import { loremIpsum } from 'lorem-ipsum';
import _ from 'lodash'; import _ from 'lodash';
@ -216,3 +216,28 @@ export async function installPackage(full_name: string) {
function delay(ms: number) { function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
export async function getFeaturedCourses(): Promise<Course[]> {
const mockCourses: Course[] = [
{
title: 'Developing With Tea',
sub_title: 'by Mxcl',
link: '#',
banner_image_url: 'https://tea.xyz/Images/packages/mesonbuild_com.jpg'
},
{
title: 'Brewing Tea',
sub_title: 'by Mxcl',
link: '#',
banner_image_url: 'https://tea.xyz/Images/packages/tea_xyz_gx_cc.jpg'
},
{
title: 'Harvesting Tea',
sub_title: 'by Mxcl',
link: '#',
banner_image_url: 'https://tea.xyz/Images/packages/ipfs_tech.jpg'
}
];
return mockCourses;
}

View file

@ -16,7 +16,7 @@ import { Command } from '@tauri-apps/api/shell';
import { readDir, BaseDirectory } from '@tauri-apps/api/fs'; import { readDir, BaseDirectory } from '@tauri-apps/api/fs';
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import type { Package, Review } from '@tea/ui/types'; import type { Package, Review } from '@tea/ui/types';
import type { GUIPackage } from '../types'; import type { GUIPackage, Course } from '../types';
import * as mock from './mock'; import * as mock from './mock';
import { PackageStates } from '../types'; import { PackageStates } from '../types';
@ -137,3 +137,8 @@ async function getInstalledPackages() {
}); });
return packages; return packages;
} }
export async function getFeaturedCourses(): Promise<Course[]> {
const courses = await mock.getFeaturedCourses();
return courses;
}

View file

@ -16,3 +16,10 @@ export type GUIPackage = Package & {
state: PackageStates; state: PackageStates;
installed_version?: string; installed_version?: string;
}; };
export type Course = {
title: string;
sub_title: string;
banner_image_url: string;
link: string;
};

View file

@ -52,6 +52,7 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@tailwindcss/line-clamp": "^0.4.2" "@tailwindcss/line-clamp": "^0.4.2",
"svelte-watch-resize": "^1.0.3"
} }
} }

View file

@ -0,0 +1,90 @@
<script lang="ts">
import '$appcss';
import { onDestroy, onMount } from 'svelte';
import { watchResize } from 'svelte-watch-resize';
import Preloader from '../Preloader/Preloader.svelte';
import GalleryItem from './GalleryItem.svelte';
export let title = '';
interface GalleryItemShape {
imageUrl: string;
title: string;
subTitle: string;
link: string;
}
export let items: GalleryItemShape[] = [];
let focus = 0;
let width = 0;
let styleFeaturedPackages: string;
function resetFeaturedStyle() {
const position = focus * width;
styleFeaturedPackages = `
width: ${items.length * width}px;
left: -${position}px;
transition: left 0.6s ease-in;
`;
}
function handleContainerResize(node: HTMLElement) {
width = node.clientWidth;
resetFeaturedStyle();
}
let loop: NodeJS.Timer;
function resetLoop() {
if (loop) clearInterval(loop);
loop = setInterval(() => {
focus++;
if (focus === items.length) {
focus = 0;
}
resetFeaturedStyle();
}, 3000);
resetFeaturedStyle();
}
onDestroy(() => clearInterval(loop));
onMount(() => {
resetLoop();
});
</script>
<section class="h-96 w-full bg-black" use:watchResize={handleContainerResize}>
<!-- <Placeholder label="FeaturedPackages" /> -->
<header class="flex h-12 items-center justify-between bg-accent px-2">
<p>{title}</p>
<ul class="flex gap-2">
{#each items as _item, i}
<button
on:click={() => {
focus = i;
resetLoop();
}}
class={`bg-purple h-4 w-4 rounded-lg border-2 border-white transition-colors ${
i === focus ? 'bg-purple-900' : ''
}`}
/>
{/each}
</ul>
</header>
<figure class="absolute bottom-0 top-12 left-0 right-0 overflow-hidden">
{#if items.length}
<section class="absolute top-0 flex h-full" style={styleFeaturedPackages}>
{#each items as item}
<div class="h-full" style={`width:${width}px`}>
<a href={item.link}>
<GalleryItem {...item} {width} />
</a>
</div>
{/each}
</section>
{:else}
<Preloader />
{/if}
</figure>
</section>

View file

@ -1,33 +1,35 @@
<script lang="ts"> <script lang="ts">
import '$appcss'; import '$appcss';
import type { Package } from '@tea/ui/types'; import ImgLoader from '../ImgLoader/ImgLoader.svelte';
import ImgLoader from '@tea/ui/ImgLoader/ImgLoader.svelte';
export let pkg: Package; export let width = 0;
export let width: number; export let imageUrl = '';
export let title = '';
export let subTitle = '';
</script> </script>
<figure class="featured-pkg relative h-full w-full" style={`width:${width}px`}> <figure class="gallery-item relative h-full w-full" style={`width:${width}px`}>
<ImgLoader <ImgLoader
class="featured-img" class="featured-img"
src={!pkg.thumb_image_url.includes('https://tea.xyz') src={!imageUrl.includes('https://tea.xyz')
? 'https://tea.xyz/Images/package-thumb-nolabel4.jpg' ? 'https://tea.xyz/Images/package-thumb-nolabel4.jpg'
: pkg.thumb_image_url} : imageUrl}
alt={pkg.name} alt={title}
/> />
<article class="card-thumb-label"> <article class="card-thumb-label">
<i class="icon-tea-logo-iconasset-1"> <i class="icon-tea-logo-iconasset-1">
<!-- TODO: replace with icon.svg --> <!-- TODO: replace with icon.svg -->
</i> </i>
<h3 class="text-3xl">{pkg.name}</h3> <h3 class="text-3xl">{title}</h3>
{#if pkg.maintainer} {#if subTitle}
<h4 class="mt-2 text-lg">&#x2022;&nbsp;{pkg.maintainer}</h4> <h4 class="mt-2 text-lg">&#x2022;&nbsp;{subTitle}</h4>
{/if} {/if}
</article> </article>
</figure> </figure>
<style> <style>
.featured-pkg :global(.featured-img) { .gallery-item :global(.featured-img) {
box-shadow: 0px 0px 12px #0c0c0c !important; box-shadow: 0px 0px 12px #0c0c0c !important;
width: 100%; width: 100%;
height: 100%; height: 100%;

View file

@ -98,12 +98,14 @@ importers:
svelte: ^3.44.0 svelte: ^3.44.0
svelte-check: ^2.7.1 svelte-check: ^2.7.1
svelte-preprocess: ^4.10.7 svelte-preprocess: ^4.10.7
svelte-watch-resize: ^1.0.3
tailwindcss: ^3.2.4 tailwindcss: ^3.2.4
tslib: ^2.3.1 tslib: ^2.3.1
typescript: ^4.7.4 typescript: ^4.7.4
vite: ^3.1.0 vite: ^3.1.0
dependencies: dependencies:
'@tailwindcss/line-clamp': 0.4.2_tailwindcss@3.2.4 '@tailwindcss/line-clamp': 0.4.2_tailwindcss@3.2.4
svelte-watch-resize: 1.0.3
devDependencies: devDependencies:
'@playwright/test': 1.25.0 '@playwright/test': 1.25.0
'@storybook/addon-essentials': 7.0.0-alpha.51_typescript@4.9.3 '@storybook/addon-essentials': 7.0.0-alpha.51_typescript@4.9.3
@ -113,7 +115,7 @@ importers:
'@storybook/svelte-vite': 7.0.0-alpha.51_typescript@4.9.3 '@storybook/svelte-vite': 7.0.0-alpha.51_typescript@4.9.3
'@storybook/testing-library': 0.0.13 '@storybook/testing-library': 0.0.13
'@sveltejs/adapter-auto': 1.0.0-next.90 '@sveltejs/adapter-auto': 1.0.0-next.90
'@sveltejs/kit': 1.0.0-next.570_svelte@3.53.1+vite@3.2.4 '@sveltejs/kit': 1.0.0-next.572_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
@ -3242,6 +3244,34 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sveltejs/kit/1.0.0-next.572_svelte@3.53.1+vite@3.2.4:
resolution: {integrity: sha512-PiKEr55L/uJyMKvDPdyoa5MlAYQwdgs8HLMbr28YcCBmhw/v6V7gutKOKdqeXc3YwKEFVS3z7TvW6c7eDokJdQ==}
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.27.0
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.13.0
vite: 3.2.4
transitivePeerDependencies:
- diff-match-patch
- supports-color
dev: true
/@sveltejs/package/1.0.0-next.1_7dvewpees4iyn2tkw2qzal77a4: /@sveltejs/package/1.0.0-next.1_7dvewpees4iyn2tkw2qzal77a4:
resolution: {integrity: sha512-U8XBk6Cfy8MjKG41Uyo+fqBpdhu7xUSnhiCNoODRaAtWV02RZoLh+McXrsxEvqi/ycgymctlhJhssqDnD+E+FA==} resolution: {integrity: sha512-U8XBk6Cfy8MjKG41Uyo+fqBpdhu7xUSnhiCNoODRaAtWV02RZoLh+McXrsxEvqi/ycgymctlhJhssqDnD+E+FA==}
engines: {node: '>=16.9'} engines: {node: '>=16.9'}
@ -7382,6 +7412,13 @@ packages:
sourcemap-codec: 1.4.8 sourcemap-codec: 1.4.8
dev: true dev: true
/magic-string/0.27.0:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/make-dir/2.1.0: /make-dir/2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'} engines: {node: '>=6'}