Merge pull request #54 from teaxyz/reviews

Package reviews
This commit is contained in:
Neil 2022-12-01 13:24:27 +08:00 committed by GitHub
commit 713c10db74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 246 additions and 42 deletions

View file

@ -43,6 +43,8 @@
"@tauri-apps/api": "^1.2.0",
"buffer": "^6.0.3",
"fuse.js": "^6.6.2",
"lodash": "^4.17.21",
"lorem-ipsum": "^2.0.8",
"svelte-watch-resize": "^1.0.3"
},
"pnpm": {

View file

@ -1,6 +1,47 @@
<script lang="ts">
import '$appcss';
import Placeholder from '$components/Placeholder/Placeholder.svelte';
import { afterUpdate } from 'svelte';
import ReviewCard from '@tea/ui/ReviewCard/ReviewCard.svelte';
import type { Review } from '@tea/ui/types';
export let reviews: Review[];
const getColReviews = (n: number) => {
return reviews.filter((_item, i) => (i - n) % 3 === 0);
};
let col1: Review[] = [];
let col2: Review[] = [];
let col3: Review[] = [];
afterUpdate(() => {
col1 = getColReviews(0);
col2 = getColReviews(1);
col3 = getColReviews(2);
});
// TODO: problem with reviews with differing heights
// ideally they should work like metro-ui to not have extreme height diff between columns
</script>
<Placeholder label="PackageReviews" />
<header class="text-primary bg-black border border-gray p-4">REVIEWS ({reviews.length})</header>
<section class="flex flex-wrap bg-black flex-row font-machina">
<div class="border-gray border-0 border-l-2 p-4 w-1/3">
{#each col1 as review}
<ReviewCard {review} />
<div class="mt-4" />
{/each}
</div>
<div class="border-gray border-0 border-l-2 p-4 w-1/3">
{#each col2 as review}
<ReviewCard {review} />
<div class="mt-4" />
{/each}
</div>
<div class="border-gray border-0 p-4 border-x-2 w-1/3">
{#each col3 as review}
<ReviewCard {review} />
<div class="mt-4" />
{/each}
</div>
</section>

View file

@ -19,6 +19,7 @@
const searchLimit = 5;
const setPackages = (pkgs: Package[]) => {
console.log('pkgs sub', pkgs);
packages = pkgs.sort((a, b) => {
if (sortBy === 'popularity') {
const aPop = +a.dl_count + a.installs;
@ -36,9 +37,12 @@
packagesStore.subscribe((v) => {
allPackages = v;
setPackages(allPackages);
if (!packagesIndex) {
// dont remove or this can get crazy
packagesIndex = new Fuse(allPackages, {
keys: ['name', 'full_name', 'desc']
});
}
});
onMount(async () => {

View file

@ -5,7 +5,10 @@
* TODO:
* * make cors work with api.tea.xyz/v1
*/
import type { Package } from '../types';
import type { Package, Review } from '@tea/ui/types';
import { loremIpsum } from 'lorem-ipsum';
import _ from 'lodash';
const packages: Package[] = [
{
slug: 'mesonbuild_com',
@ -156,3 +159,41 @@ export async function getPackages(): Promise<Package[]> {
export async function getFeaturedPackages(): Promise<Package[]> {
return packages.slice(0, 4);
}
export async function getPackageReviews(full_name: string): Promise<Review[]> {
console.log(`generating reviews for ${full_name}`);
const reviewCount = _.random(7, 21);
const reviews: Review[] = [];
for (let i = 0; i < reviewCount; i++) {
const title = loremIpsum({
count: _.random(2, 5),
format: 'plain',
paragraphLowerBound: 3,
paragraphUpperBound: 7,
random: Math.random,
sentenceLowerBound: 5,
sentenceUpperBound: 15,
units: 'words'
});
const comment = loremIpsum({
count: 2,
format: 'plain',
paragraphLowerBound: 3,
paragraphUpperBound: 7,
random: Math.random,
sentenceLowerBound: 5,
sentenceUpperBound: 15,
units: 'sentences'
});
const rating = _.random(0, 5);
reviews.push({
title,
comment,
rating
});
}
return reviews;
}

View file

@ -12,7 +12,7 @@
*/
import { getClient } from '@tauri-apps/api/http';
import { Buffer } from 'buffer';
import type { Package } from '../types';
import type { Package, Review } from '../types';
import * as mock from './mock';
const username = 'user';
@ -55,3 +55,10 @@ export async function getFeaturedPackages(): Promise<Package[]> {
const packages = await mock.getFeaturedPackages();
return packages;
}
export async function getPackageReviews(full_name: string): Promise<Review[]> {
console.log(`getting reviews for ${full_name}`);
const reviews: Review[] = await mock.getPackageReviews(full_name);
return reviews;
}

View file

@ -1,7 +1,9 @@
import { writable } from 'svelte/store';
import type { Package } from './types';
import type { Package, Review } from '@tea/ui/types';
import { getPackages, getFeaturedPackages } from '@api';
// TODO: figure out a better structure for managing states maybe turn them into models?
import { getPackages, getFeaturedPackages, getPackageReviews } from '@api';
export const backLink = writable<string>('/');
@ -10,11 +12,51 @@ export const packages = writable<Package[]>([]);
export const featuredPackages = writable<Package[]>([]);
export const initializePackages = async () => {
console.log('initialize packages');
const newPackages = await getPackages();
packages.set(newPackages);
};
export const initializeFeaturedPackages = async () => {
console.log('initialzie featured packages');
const packages = await getFeaturedPackages();
featuredPackages.set(packages);
};
interface PackagesReview {
[full_name: string]: Review[];
}
function initPackagesReviewStore() {
const { set, update, subscribe } = writable<PackagesReview>({});
let packagesReviews: PackagesReview = {};
subscribe((v) => (packagesReviews = v));
const getSetPackageReviews = async (full_name: string) => {
if (full_name && !packagesReviews[full_name]) {
console.log('getting reviews for', full_name);
const reviews = await getPackageReviews(full_name);
update((v) => {
return {
...v,
[full_name]: reviews
};
});
}
};
return {
subscribe: (full_name: string, reset: (reviews: Review[]) => void) => {
getSetPackageReviews(full_name);
return subscribe((value) => {
if (value[full_name]) {
reset(value[full_name]);
}
});
}
};
}
export const packagesReviewStore = initPackagesReviewStore();

View file

@ -9,17 +9,24 @@
/** @type {import('./$types').PageData} */
export let data;
import { packages, featuredPackages } from '$libs/stores';
import { packages, featuredPackages, packagesReviewStore } from '$libs/stores';
import type { Package } from '@tea/ui/types';
import type { Package, Review } from '@tea/ui/types';
let pkg: Package;
let reviews: Review[];
const setPkg = (pkgs: Package[]) => {
const foundPackage = pkgs.find(({ slug }) => slug === data?.slug) as Package;
if (!pkg && foundPackage) {
pkg = foundPackage;
}
if (!reviews && pkg) {
packagesReviewStore.subscribe(pkg.full_name, (updatedReviews) => {
reviews = updatedReviews;
});
}
};
packages.subscribe(setPkg);
@ -32,6 +39,6 @@
<PackageBanner {pkg} />
</section>
<section class="mt-8">
<PackageReviews />
<PackageReviews reviews={reviews || []} />
</section>
</div>

View file

@ -0,0 +1,30 @@
import ReviewCard from './ReviewCard.svelte';
// More on how to set up stories at: https://storybook.js.org/docs/7.0/svelte/writing-stories/introduction
export default {
title: 'Example/ReviewCard',
component: ReviewCard,
tags: ['docsPage'],
render: ({ review }) => ({
Component: ReviewCard,
props: { review }
}),
argTypes: {
review: {
name: 'review',
description: 'this is type Review'
}
}
};
// More on writing stories with args: https://storybook.js.org/docs/7.0/svelte/writing-stories/args
export const Example = {
args: {
review: {
title: 'installing tea',
comment:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptas, voluptatum molestiae esse quisquam earum debitis.',
rating: 2
}
}
};

View file

@ -0,0 +1,35 @@
<script type="ts">
import '../app.css';
import type { Review } from '../types';
export let review: Review;
const getStarType = () => {
let star = 'full';
if (review.rating < 3) {
star = 'empty';
} else if (review.rating < 4) {
star = 'half';
}
return `icon-star-${star}`;
};
const getStarLabel = () => {
let label = 'DELIGHTFUL';
if (review.rating < 3) {
label = 'NOT DELIGHTFUL';
} else if (review.rating < 4) {
label = 'DELIGHTFUL WITH NOTES';
}
return label;
};
</script>
<section class="border border-gray p-4">
<header class=" text-primary text-lg">{review.title}</header>
<div class="flex mt-2 text-xs">
<i class={`${getStarType()} text-primary`} />
<span class="text-gray pl-2">{getStarLabel()}</span>
</div>
<p class="mt-2 text-sm">{review.comment}</p>
</section>

View file

@ -1,3 +1,9 @@
export interface Review {
title: string;
comment: string;
rating: number;
created_at?: Date | string;
}
export interface Package {
slug: string;
version: string;
@ -11,4 +17,5 @@ export interface Package {
desc: string;
dl_count: number;
installs: number;
reviews?: Review[];
}

View file

@ -22,6 +22,8 @@ importers:
eslint-config-prettier: ^8.3.0
eslint-plugin-svelte3: ^4.0.0
fuse.js: ^6.6.2
lodash: ^4.17.21
lorem-ipsum: ^2.0.8
postcss: ^8.4.19
prettier: ^2.6.2
prettier-plugin-svelte: ^2.7.0
@ -38,12 +40,14 @@ importers:
'@tauri-apps/api': 1.2.0
buffer: 6.0.3
fuse.js: 6.6.2
lodash: 4.17.21
lorem-ipsum: 2.0.8
svelte-watch-resize: 1.0.3
devDependencies:
'@playwright/test': 1.25.0
'@sveltejs/adapter-auto': 1.0.0-next.90
'@sveltejs/adapter-static': 1.0.0-next.48
'@sveltejs/kit': 1.0.0-next.567_svelte@3.53.1+vite@3.2.4
'@sveltejs/kit': 1.0.0-next.570_svelte@3.53.1+vite@3.2.4
'@tauri-apps/cli': 1.2.0
'@tea/ui': link:../ui
'@typescript-eslint/eslint-plugin': 5.43.0_wze2rj5tow7zwqpgbdx2buoy3m
@ -3206,34 +3210,6 @@ packages:
resolution: {integrity: sha512-Z5Z+QZOav6D0KDeU3ReksGERJg/sX1k5OKWWXyQ11OwGErEEwSXHYRUyjaBmZEPeGzpVVGwwMUK8YWJlG/MKeA==}
dev: true
/@sveltejs/kit/1.0.0-next.567_svelte@3.53.1+vite@3.2.4:
resolution: {integrity: sha512-4Q74HPLZb9Rr60n7EJANXH2Ng3d7wwrz67TuXxVfYuxARigWO9YzjcLHxG2IVMwRZIeT+LlwOKkg9vYYdBiezQ==}
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.13.0
vite: 3.2.4
transitivePeerDependencies:
- diff-match-patch
- supports-color
dev: true
/@sveltejs/kit/1.0.0-next.570_svelte@3.53.1+vite@3.2.4:
resolution: {integrity: sha512-7CUoYidoWlKdTGxL/5RsPPkgGTb36TwFBnhSZmspHFeiIST5qQEXyzqicFk8+4M5qQhxvOZ0NuP8uy7Pc+xf/Q==}
engines: {node: '>=16.14'}
@ -4634,6 +4610,11 @@ packages:
engines: {node: '>= 6'}
dev: true
/commander/9.4.1:
resolution: {integrity: sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==}
engines: {node: ^12.20.0 || >=14}
dev: false
/commondir/1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
dev: true
@ -7341,7 +7322,6 @@ packages:
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/longest-streak/3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@ -7354,6 +7334,14 @@ packages:
js-tokens: 4.0.0
dev: true
/lorem-ipsum/2.0.8:
resolution: {integrity: sha512-5RIwHuCb979RASgCJH0VKERn9cQo/+NcAi2BMe9ddj+gp7hujl6BI+qdOG4nVsLDpwWEJwTVYXNKP6BGgbcoGA==}
engines: {node: '>= 8.x', npm: '>= 5.x'}
hasBin: true
dependencies:
commander: 9.4.1
dev: false
/lower-case/2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies: