From aa6c3f333b42a94b4cf12e4aedb2e803269e356a Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:02:30 +0100 Subject: [PATCH] feat(ui): Featured package --- apps/desktop/src/api/commands/mod.rs | 9 +- apps/frontend/src/ui/hooks/useBrowser.tsx | 23 ++--- .../src/ui/pages/browser/BrowserMain.tsx | 92 ++++++++++--------- apps/frontend/uno.config.ts | 86 ++++++++--------- packages/client/src/bindings.ts | 11 +++ packages/core/src/api/package/content/mod.rs | 27 +++++- packages/core/src/constants.rs | 2 + 7 files changed, 152 insertions(+), 98 deletions(-) diff --git a/apps/desktop/src/api/commands/mod.rs b/apps/desktop/src/api/commands/mod.rs index e569501..761a54a 100644 --- a/apps/desktop/src/api/commands/mod.rs +++ b/apps/desktop/src/api/commands/mod.rs @@ -81,11 +81,18 @@ macro_rules! collect_commands { install_update, // Other set_window_style, - get_program_info + get_program_info, + get_featured_packages, ] }}; } +#[specta::specta] +#[tauri::command] +pub async fn get_featured_packages() -> Result, String> { + Ok(onelauncher::package::content::get_featured_packages().await?) +} + #[specta::specta] #[tauri::command] pub fn get_program_info() -> Result { diff --git a/apps/frontend/src/ui/hooks/useBrowser.tsx b/apps/frontend/src/ui/hooks/useBrowser.tsx index 98fa585..813a2c6 100644 --- a/apps/frontend/src/ui/hooks/useBrowser.tsx +++ b/apps/frontend/src/ui/hooks/useBrowser.tsx @@ -1,10 +1,10 @@ -import type { Cluster, ManagedPackage, PackageType, Providers, ProviderSearchQuery, SearchResult } from '@onelauncher/client/bindings'; +import type { Cluster, FeaturedPackage, PackageType, Providers, ProviderSearchQuery, SearchResult } from '@onelauncher/client/bindings'; import { useNavigate } from '@solidjs/router'; import { bridge } from '~imports'; import { createModal } from '~ui/components/overlay/Modal'; import BrowserPackage from '~ui/pages/browser/BrowserPackage'; import { PROVIDERS } from '~utils'; -import { type Accessor, type Context, createContext, createEffect, createSignal, on, onMount, type ParentProps, type Setter, useContext } from 'solid-js'; +import { type Accessor, type Context, createContext, createEffect, createResource, createSignal, on, onMount, type ParentProps, type Resource, type Setter, useContext } from 'solid-js'; import { ChooseClusterModal, useRecentCluster } from './useCluster'; import { tryResult } from './useCommand'; @@ -27,14 +27,22 @@ interface BrowserControllerType { setPackageType: Setter; popularPackages: Accessor; - featuredPackage: Accessor; + featuredPackage: Resource; }; const BrowserContext = createContext() as Context; export function BrowserProvider(props: ParentProps) { const [popularPackages, setPopularPackages] = createSignal(); - const [featuredPackage, setFeaturedPackage] = createSignal(); + const [featuredPackage] = createResource(async () => { + try { + return await tryResult(bridge.commands.getFeaturedPackages); + } + catch (e) { + console.error('Failed to fetch featured packages', e); + return []; + } + }); // Used for the current "Browser Mode". It'll only show packages of the selected type const [packageType, setPackageType] = createSignal('mod'); @@ -139,13 +147,6 @@ export function BrowserProvider(props: ParentProps) { }, {} as PopularPackages); setPopularPackages(popularPackages); - - // TODO: Better algorithm for selecting a featured package - const firstPackage = popularPackages.Modrinth[0] || popularPackages.Curseforge[0]; - if (firstPackage !== undefined) { - const featuredPackage = await tryResult(() => bridge.commands.getProviderPackage('Modrinth', firstPackage.project_id)); - setFeaturedPackage(featuredPackage); - } }); createEffect(() => { diff --git a/apps/frontend/src/ui/pages/browser/BrowserMain.tsx b/apps/frontend/src/ui/pages/browser/BrowserMain.tsx index 59710e6..84b2872 100644 --- a/apps/frontend/src/ui/pages/browser/BrowserMain.tsx +++ b/apps/frontend/src/ui/pages/browser/BrowserMain.tsx @@ -1,10 +1,11 @@ -import type { ManagedPackage, Providers } from '@onelauncher/client/bindings'; +import type { Providers } from '@onelauncher/client/bindings'; import { ChevronRightIcon } from '@untitled-theme/icons-solid'; import OneConfigLogo from '~assets/logos/oneconfig.svg?component-solid'; import Button from '~ui/components/base/Button'; import SearchResultsContainer from '~ui/components/content/SearchResults'; +import Spinner from '~ui/components/Spinner'; import useBrowser from '~ui/hooks/useBrowser'; -import { For, onMount, Show } from 'solid-js'; +import { createMemo, For, onMount, Show } from 'solid-js'; import { BrowserContent } from './BrowserRoot'; function BrowserMain() { @@ -20,64 +21,71 @@ function BrowserMain() { return (
- - + - - {([provider, results]) => ( - - )} - - + + + + {([provider, results]) => ( + + )} + + +
); } -interface FeaturedProps { - package: ManagedPackage; -} - -function Featured(props: FeaturedProps) { +function Featured() { const browser = useBrowser(); + const featured = createMemo(() => browser.featuredPackage()?.[0]); + const open = () => { - browser.displayPackage(props.package.id, props.package.provider); + const pkg = featured(); + if (!pkg) + return; + browser.displayPackage(pkg.id, pkg.provider); }; return ( -
-
Featured
-
-
- -
-
-

{props.package.title}

- -
- - OneConfig Integrated + +
+
Featured
+
+
+ {`${featured()?.title}
+
+

{featured()?.title}

-

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Aliquid, veniam odio. Animi quia corporis id fugiat, libero commodi. Repudiandae repellat placeat sed tempora molestias id consequuntur, corrupti ullam mollitia amet?

+ +
+ + OneConfig Integrated +
+
-
-
-
+ ); } diff --git a/apps/frontend/uno.config.ts b/apps/frontend/uno.config.ts index caf13e5..a2626a9 100644 --- a/apps/frontend/uno.config.ts +++ b/apps/frontend/uno.config.ts @@ -42,85 +42,85 @@ export default defineConfig({ }, colors: { - white: 'rgba(var(--clr-white), )', - black: 'rgba(var(--clr-black), )', + white: 'rgba(var(--clr-white))', + black: 'rgba(var(--clr-black))', - border: 'rgba(var(--clr-border), )', + border: 'rgba(var(--clr-border))', fg: { primary: { - DEFAULT: 'rgba(var(--clr-fg-primary), )', - hover: 'rgba(var(--clr-fg-primary-hover), )', - pressed: 'rgba(var(--clr-fg-primary-pressed), )', - disabled: 'rgba(var(--clr-fg-primary-disabled), )', + DEFAULT: 'rgba(var(--clr-fg-primary))', + hover: 'rgba(var(--clr-fg-primary-hover))', + pressed: 'rgba(var(--clr-fg-primary-pressed))', + disabled: 'rgba(var(--clr-fg-primary-disabled))', }, secondary: { - DEFAULT: 'rgba(var(--clr-fg-secondary), )', - hover: 'rgba(var(--clr-fg-secondary-hover), )', - pressed: 'rgba(var(--clr-fg-secondary-pressed), )', - disabled: 'rgba(var(--clr-fg-secondary-disabled), )', + DEFAULT: 'rgba(var(--clr-fg-secondary))', + hover: 'rgba(var(--clr-fg-secondary-hover))', + pressed: 'rgba(var(--clr-fg-secondary-pressed))', + disabled: 'rgba(var(--clr-fg-secondary-disabled))', }, }, brand: { - DEFAULT: 'rgba(var(--clr-brand), )', - hover: 'rgba(var(--clr-brand-hover), )', - pressed: 'rgba(var(--clr-brand-pressed), )', - disabled: 'rgba(var(--clr-brand-disabled), )', + DEFAULT: 'rgba(var(--clr-brand))', + hover: 'rgba(var(--clr-brand-hover))', + pressed: 'rgba(var(--clr-brand-pressed))', + disabled: 'rgba(var(--clr-brand-disabled))', }, onbrand: { - DEFAULT: 'rgba(var(--clr-onbrand), )', - hover: 'rgba(var(--clr-onbrand-hover), )', - pressed: 'rgba(var(--clr-onbrand-pressed), )', - disabled: 'rgba(var(--clr-onbrand-disabled), )', + DEFAULT: 'rgba(var(--clr-onbrand))', + hover: 'rgba(var(--clr-onbrand-hover))', + pressed: 'rgba(var(--clr-onbrand-pressed))', + disabled: 'rgba(var(--clr-onbrand-disabled))', }, danger: { - DEFAULT: 'rgba(var(--clr-danger), )', - hover: 'rgba(var(--clr-danger-hover), )', - pressed: 'rgba(var(--clr-danger-pressed), )', - disabled: 'rgba(var(--clr-danger-disabled), )', + DEFAULT: 'rgba(var(--clr-danger))', + hover: 'rgba(var(--clr-danger-hover))', + pressed: 'rgba(var(--clr-danger-pressed))', + disabled: 'rgba(var(--clr-danger-disabled))', }, success: { - DEFAULT: 'rgba(var(--clr-success), )', - hover: 'rgba(var(--clr-success-hover), )', - pressed: 'rgba(var(--clr-success-pressed), )', - disabled: 'rgba(var(--clr-success-disabled), )', + DEFAULT: 'rgba(var(--clr-success))', + hover: 'rgba(var(--clr-success-hover))', + pressed: 'rgba(var(--clr-success-pressed))', + disabled: 'rgba(var(--clr-success-disabled))', }, component: { bg: { - DEFAULT: 'rgba(var(--clr-component-bg), )', - hover: 'rgba(var(--clr-component-bg-hover), )', - pressed: 'rgba(var(--clr-component-bg-pressed), )', - disabled: 'rgba(var(--clr-component-bg-disabled), )', + DEFAULT: 'rgba(var(--clr-component-bg))', + hover: 'rgba(var(--clr-component-bg-hover))', + pressed: 'rgba(var(--clr-component-bg-pressed))', + disabled: 'rgba(var(--clr-component-bg-disabled))', }, }, code: { - info: 'rgba(var(--clr-code-info), )', - warn: 'rgba(var(--clr-code-warn), )', - error: 'rgba(var(--clr-code-error), )', - debug: 'rgba(var(--clr-code-debug), )', - trace: 'rgba(var(--clr-code-trace), )', + info: 'rgba(var(--clr-code-info))', + warn: 'rgba(var(--clr-code-warn))', + error: 'rgba(var(--clr-code-error))', + debug: 'rgba(var(--clr-code-debug))', + trace: 'rgba(var(--clr-code-trace))', }, link: { - DEFAULT: 'rgba(var(--clr-link), )', - hover: 'rgba(var(--clr-link-hover), )', - pressed: 'rgba(var(--clr-link-pressed), )', + DEFAULT: 'rgba(var(--clr-link))', + hover: 'rgba(var(--clr-link-hover))', + pressed: 'rgba(var(--clr-link-pressed))', disabled: 'rgba(var(--clr-link-disabled))', }, page: { - DEFAULT: 'rgba(var(--clr-page), )', - elevated: 'rgba(var(--clr-page-elevated), )', - pressed: 'rgba(var(--clr-page-pressed), )', + DEFAULT: 'rgba(var(--clr-page))', + elevated: 'rgba(var(--clr-page-elevated))', + pressed: 'rgba(var(--clr-page-pressed))', }, - secondary: 'rgba(var(--clr-secondary), )', + secondary: 'rgba(var(--clr-secondary))', }, extend: { height: { diff --git a/packages/client/src/bindings.ts b/packages/client/src/bindings.ts index 62b1806..026058e 100644 --- a/packages/client/src/bindings.ts +++ b/packages/client/src/bindings.ts @@ -524,6 +524,16 @@ export const commands = { else return { status: 'error', error: e as any }; } }, + async getFeaturedPackages(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_featured_packages') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, }; /** user-defined events */ @@ -678,6 +688,7 @@ export type ClusterStage = 'not_installed'; export interface CreateCluster { name: string; mc_version: string; mod_loader: Loader; loader_version: string | null; icon: string | null; icon_url: string | null; package_data: PackageData | null; skip: boolean | null; skip_watch: boolean | null }; export interface DetailedProcess { uuid: string; user: string | null; started_at: string; pid: number }; +export interface FeaturedPackage { package_type: PackageType; provider: Providers; id: string; title: string; description: string; thumbnail: string; oneconfig: boolean }; /** * List of launcher types we support importing from. */ diff --git a/packages/core/src/api/package/content/mod.rs b/packages/core/src/api/package/content/mod.rs index 85685ca..7f8ef5d 100644 --- a/packages/core/src/api/package/content/mod.rs +++ b/packages/core/src/api/package/content/mod.rs @@ -5,13 +5,15 @@ use std::collections::HashMap; use modrinth::{Facet, FacetOperation}; +use reqwest::Method; use serde::{Deserialize, Serialize}; use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}; use crate::package::content::modrinth::FacetBuilder; use crate::store::{Author, PackageBody, ProviderSearchResults}; +use crate::utils::http::fetch_json; use crate::utils::pagination::Pagination; -use crate::Result; +use crate::{Result, State}; mod curseforge; mod modrinth; @@ -218,6 +220,29 @@ impl Providers { } } +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[derive(Debug, Serialize, Deserialize)] +pub struct FeaturedPackage { + pub package_type: PackageType, + pub provider: Providers, + pub id: String, + pub title: String, + pub description: String, + pub thumbnail: String, + pub oneconfig: bool, +} + +pub async fn get_featured_packages() -> Result> { + let state = State::get().await?; + fetch_json( + Method::GET, + crate::constants::FEATURED_PACKAGES_URL, + None, + None, + &state.fetch_semaphore + ).await +} + fn build_facets( builder: &mut FacetBuilder, categories: Option>, diff --git a/packages/core/src/constants.rs b/packages/core/src/constants.rs index 89cf592..69dde14 100644 --- a/packages/core/src/constants.rs +++ b/packages/core/src/constants.rs @@ -44,6 +44,8 @@ pub const CURSEFORGE_API_KEY: &str = "$2a$10$6utA1UNSmFPrE/Lh7b7ndeeGmiOkjKNY8kp pub const CURSEFORGE_GAME_ID: u32 = 432; /// Our metadata API base url. pub const METADATA_API_URL: &str = "https://meta.polyfrost.org"; +/// Featured packages API url. +pub const FEATURED_PACKAGES_URL: &str = "https://polyfrost.org/meta/onelauncher/featured.json"; /// / API base url. pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1";