From e45810892383481ac902d991ccda2065c23df482 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 22 Sep 2024 19:45:36 +0200 Subject: [PATCH] feat: Add fetching package body --- apps/desktop/src/api/commands/mod.rs | 1 + apps/desktop/src/api/commands/package.rs | 17 +++- .../ui/components/content/SearchResults.tsx | 10 ++- .../src/ui/pages/browser/BrowserPackage.tsx | 55 ++++++++----- apps/frontend/src/utils/index.ts | 22 +++-- .../core/src/api/cluster/content/package.rs | 2 + .../src/api/package/content/curseforge.rs | 80 +++++++++++-------- packages/core/src/api/package/content/mod.rs | 59 ++++++++++---- .../core/src/api/package/content/modrinth.rs | 4 +- packages/core/src/store/package.rs | 11 ++- packages/core/src/utils/mod.rs | 1 + packages/core/src/utils/pagination.rs | 10 +++ 12 files changed, 184 insertions(+), 88 deletions(-) create mode 100644 packages/core/src/utils/pagination.rs diff --git a/apps/desktop/src/api/commands/mod.rs b/apps/desktop/src/api/commands/mod.rs index c96fbe48..8fa041a5 100644 --- a/apps/desktop/src/api/commands/mod.rs +++ b/apps/desktop/src/api/commands/mod.rs @@ -67,6 +67,7 @@ macro_rules! collect_commands { get_provider_package_version, search_provider_packages, get_provider_authors, + get_package_body, download_provider_package, // Cluster Packages get_cluster_package, diff --git a/apps/desktop/src/api/commands/package.rs b/apps/desktop/src/api/commands/package.rs index 54697604..a646f0a5 100644 --- a/apps/desktop/src/api/commands/package.rs +++ b/apps/desktop/src/api/commands/package.rs @@ -4,7 +4,7 @@ use onelauncher::cluster::content::package; use onelauncher::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}; use onelauncher::package::content::Providers; use onelauncher::package::import::{default_launcher_path, ImportType}; -use onelauncher::store::{Author, ClusterPath, Package, PackagePath, ProviderSearchResults}; +use onelauncher::store::{Author, ClusterPath, Package, PackageBody, PackagePath, ProviderSearchResults}; use uuid::Uuid; #[specta::specta] @@ -51,7 +51,7 @@ pub async fn get_all_provider_package_versions( loaders: Option>, ) -> Result, String> { Ok(provider - .get_all_versions(&project_id, game_versions, loaders) + .get_all_versions(&project_id, game_versions, loaders, None, None) // TODO .await?) } @@ -61,7 +61,7 @@ pub async fn get_provider_package_versions( provider: Providers, versions: Vec, ) -> Result, String> { - Ok(provider.get_versions(versions).await?) + Ok(provider.get_versions("", versions).await?) // TODO } #[specta::specta] @@ -70,7 +70,7 @@ pub async fn get_provider_package_version( provider: Providers, version: String, ) -> Result { - Ok(provider.get_version(&version).await?) + Ok(provider.get_version("", &version).await?) // TODO } #[derive(specta::Type, serde::Deserialize, serde::Serialize)] @@ -114,6 +114,15 @@ pub async fn get_provider_authors( Ok(provider.get_authors(&author).await?) } +#[specta::specta] +#[tauri::command] +pub async fn get_package_body( + provider: Providers, + body: PackageBody +) -> Result { + Ok(provider.get_package_body(&body).await?) +} + #[specta::specta] #[tauri::command] pub async fn download_provider_package( diff --git a/apps/frontend/src/ui/components/content/SearchResults.tsx b/apps/frontend/src/ui/components/content/SearchResults.tsx index 17192ac5..ef0126ee 100644 --- a/apps/frontend/src/ui/components/content/SearchResults.tsx +++ b/apps/frontend/src/ui/components/content/SearchResults.tsx @@ -165,10 +165,12 @@ function PackageItem(props: SearchResult & { provider: Providers; row?: boolean {abbreviateNumber(props.downloads)} -
- - {abbreviateNumber(props.follows)} -
+ 0}> +
+ + {abbreviateNumber(props.follows)} +
+
diff --git a/apps/frontend/src/ui/pages/browser/BrowserPackage.tsx b/apps/frontend/src/ui/pages/browser/BrowserPackage.tsx index 163d7f69..17f95fde 100644 --- a/apps/frontend/src/ui/pages/browser/BrowserPackage.tsx +++ b/apps/frontend/src/ui/pages/browser/BrowserPackage.tsx @@ -1,4 +1,4 @@ -import type { Cluster, ManagedPackage, ManagedUser, ManagedVersion, Providers } from '@onelauncher/client/bindings'; +import type { Cluster, ManagedPackage, ManagedVersion, Providers } from '@onelauncher/client/bindings'; import { A, type Params, Route, useSearchParams } from '@solidjs/router'; import { CalendarIcon, ChevronDownIcon, ClockRewindIcon, Download01Icon, File02Icon, HeartIcon, LinkExternal01Icon } from '@untitled-theme/icons-solid'; import SteveHead from '~assets/images/steve.png'; @@ -15,7 +15,7 @@ import useCommand from '~ui/hooks/useCommand'; import usePagination from '~ui/hooks/usePagination'; import usePromptOpener from '~ui/hooks/usePromptOpener'; import { abbreviateNumber, formatAsRelative } from '~utils'; -import { createContext, createEffect, createSignal, For, Match, on, type ParentProps, Show, Switch, useContext } from 'solid-js'; +import { type Accessor, createContext, createEffect, createSignal, For, Match, on, type ParentProps, type Resource, Show, Switch, useContext } from 'solid-js'; import { getLicenseUrl, getPackageUrl, upperFirst } from '../../../utils'; interface BrowserModParams extends Params { @@ -23,14 +23,25 @@ interface BrowserModParams extends Params { provider: Providers; } -const BrowserPackageContext = createContext(null); +interface BrowserPackageContextType { + pkg: Accessor; + body: Resource; +}; + +const BrowserPackageContext = createContext(null); const basePath = '/browser/package'; function BrowserPackageProvider(props: ParentProps & { pkg: ManagedPackage }) { + const [body] = useCommand(() => bridge.commands.getPackageBody(props.pkg.provider, props.pkg.body)); + + const ctx: BrowserPackageContextType = { + pkg: () => props.pkg, + body, + }; + return ( - // eslint-disable-next-line solid/reactivity -- Should be fine - + {props.children} ); @@ -67,7 +78,6 @@ function NavLink(props: ParentProps & { href: string }) { function BrowserPackage(props: ParentProps) { const [params] = useSearchParams(); const [pkg] = useCommand(() => bridge.commands.getProviderPackage(params.provider!, params.id!)); - const [authors] = useCommand(pkg, () => bridge.commands.getProviderAuthors(pkg()!.provider, pkg()!.author)); const links = [ ['About', '/'], @@ -81,7 +91,7 @@ function BrowserPackage(props: ParentProps) { - +
@@ -108,7 +118,7 @@ function BrowserPackage(props: ParentProps) {
)} - when={pkg() !== undefined && authors() !== undefined} + when={pkg() !== undefined} />
@@ -123,7 +133,8 @@ BrowserPackage.Routes = BrowserPackageRoutes; export default BrowserPackage; -function BrowserSidebar(props: { package: ManagedPackage; authors: ManagedUser[] }) { +function BrowserSidebar(props: { package: ManagedPackage }) { + const [authors] = useCommand(() => props.package, () => bridge.commands.getProviderAuthors(props.package.provider, props.package.author)); const createdAt = () => new Date(props.package.created); const updatedAt = () => new Date(props.package.updated); const promptOpen = usePromptOpener(); @@ -158,10 +169,12 @@ function BrowserSidebar(props: { package: ManagedPackage; authors: ManagedUser[] {abbreviateNumber(props.package.downloads)} -
- - {abbreviateNumber(props.package.followers)} -
+ 0}> +
+ + {abbreviateNumber(props.package.followers)} +
+
@@ -170,7 +183,7 @@ function BrowserSidebar(props: { package: ManagedPackage; authors: ManagedUser[]

Links

- + {props.package.provider} {' '} Page @@ -179,14 +192,14 @@ function BrowserSidebar(props: { package: ManagedPackage; authors: ManagedUser[]

Authors

- + {author => ( <>
promptOpen(author.url)} > - {`${author.username}'s + {`${author.username}'s
{author.username} @@ -200,7 +213,7 @@ function BrowserSidebar(props: { package: ManagedPackage; authors: ManagedUser[]
- 1}> + 1)}>
@@ -346,7 +359,7 @@ function BrowserPackageBody() { return (
- +
); } @@ -371,7 +384,7 @@ function BrowserPackageVersions() { const MAX_ITEMS_PER_PAGE = 20; const { page, Navigation } = usePagination({ - itemsCount: () => context?.versions.length || 0, + itemsCount: () => context?.pkg().versions.length || 0, itemsPerPage: () => MAX_ITEMS_PER_PAGE, }); @@ -379,7 +392,7 @@ function BrowserPackageVersions() { if (context === null) return []; - const newestToOldest = context.versions.toReversed(); + const newestToOldest = context.pkg().versions.toReversed(); const start = (page - 1) * MAX_ITEMS_PER_PAGE; const end = start + MAX_ITEMS_PER_PAGE; @@ -392,7 +405,7 @@ function BrowserPackageVersions() { const list = getVersionsForPage(page()); - return await bridge.commands.getProviderPackageVersions(context.provider, list); + return await bridge.commands.getProviderPackageVersions(context.pkg().provider, list); }); let container!: HTMLDivElement; diff --git a/apps/frontend/src/utils/index.ts b/apps/frontend/src/utils/index.ts index b70ba771..aee66ca2 100644 --- a/apps/frontend/src/utils/index.ts +++ b/apps/frontend/src/utils/index.ts @@ -1,6 +1,6 @@ +import type { Cluster, ImportType, License, Loader, ManagedPackage, PackageType, Providers, VersionType } from '@onelauncher/client/bindings'; import { DurationFormat } from '@formatjs/intl-durationformat'; import { open } from '@tauri-apps/plugin-shell'; -import type { Cluster, ImportType, License, Loader, PackageType, Providers, VersionType } from '@onelauncher/client/bindings'; export function setAsyncTimeout(ms: number): Promise; export function setAsyncTimeout(callback: () => void, ms: number): Promise; @@ -160,13 +160,23 @@ export function getLicenseUrl(licenseId: string | License | null | undefined): s return `https://spdx.org/licenses/${id}.html`; } -export function getPackageUrl(provider: Providers, id: string, package_type: PackageType = 'mod'): string { - const mapping: Record = { - Modrinth: `https://modrinth.com/${package_type}/${id}`, - Curseforge: `https://www.curseforge.com/minecraft/${package_type}/${id}`, +export function getPackageUrl(pkg: ManagedPackage): string { + const mapping: Record string> = { + Modrinth: () => `https://modrinth.com/${pkg.package_type}/${pkg.id}`, + Curseforge: () => { + const packageTypeMapping: Record = { + mod: 'mc-mods', + shaderpack: 'shaders', + resourcepack: 'texture-packs', + datapack: 'data-packs', + modpack: 'modpacks', + }; + + return `https://www.curseforge.com/minecraft/${packageTypeMapping[pkg.package_type]}/${pkg.main}`; + }, }; - return mapping[provider]; + return mapping[pkg.provider](); } export const LOADERS: Loader[] = ['vanilla', 'fabric', 'forge', 'neoforge', 'quilt'] as const; diff --git a/packages/core/src/api/cluster/content/package.rs b/packages/core/src/api/cluster/content/package.rs index ee5457c8..eb8ac777 100644 --- a/packages/core/src/api/cluster/content/package.rs +++ b/packages/core/src/api/cluster/content/package.rs @@ -28,6 +28,8 @@ pub async fn find_managed_version( &package.id, game_version.as_ref().map(|v| vec![v.to_owned()]).clone(), loader.map(|l| vec![l]).clone(), + None, + None, ) .await?; diff --git a/packages/core/src/api/package/content/curseforge.rs b/packages/core/src/api/package/content/curseforge.rs index d953fa20..0d3b9dd3 100644 --- a/packages/core/src/api/package/content/curseforge.rs +++ b/packages/core/src/api/package/content/curseforge.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use url::Url; -use crate::{data::{Loader, ManagedPackage, PackageType}, store::{ProviderSearchResults, SearchResult}, utils::http, Result, State}; +use crate::{data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}, store::{ProviderSearchResults, SearchResult}, utils::http, Result, State}; /// Mapping of Curseforge "classes" to their respective category ids #[derive(Debug, Default, Serialize_repr, Deserialize_repr)] @@ -121,23 +121,33 @@ impl From for ManagedPackage { provider: super::Providers::Curseforge, package_type: package.package_type.into(), description: package.summary, - body: "".to_string(), // TODO: Description is a new HTTP request - main: todo!(), - versions: todo!(), - game_versions: todo!(), - loaders: todo!(), - icon_url: todo!(), - created: todo!(), - updated: todo!(), - client: todo!(), - server: todo!(), - downloads: todo!(), - followers: todo!(), - categories: todo!(), - optional_categories: todo!(), - license: todo!(), - author: todo!(), - is_archived: todo!(), + body: crate::store::PackageBody::Url(format!("/v1/mods/{}/description", package.id)), + main: package.slug, + versions: vec![], + game_versions: vec![], + loaders: vec![], + icon_url: Some(package.logo.url), + created: package.date_created, + updated: package.date_modified, + client: crate::store::PackageSide::Unknown, + server: crate::store::PackageSide::Unknown, + downloads: package.download_count, + followers: package.rating.unwrap_or(0.0) as u32, + categories: vec![], // TODO + optional_categories: None, + license: None, + author: crate::store::Author::Users( + package.authors.into_iter().map(|a| ManagedUser { + id: a.id.to_string(), + username: a.name, + url: Some(a.url), + is_organization_user: false, + avatar_url: None, + bio: None, + role: None, + }).collect() + ), + is_archived: false, } } } @@ -220,7 +230,7 @@ pub struct Screenshot { #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LatestFile { +pub struct ModFile { pub id: u32, pub game_id: u32, pub mod_id: u32, @@ -282,19 +292,7 @@ pub struct Module { #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LatestFilesIndex { - pub game_version: String, - pub file_id: u32, - pub filename: String, - pub release_type: u32, - pub game_version_type_id: u32, - #[serde(default)] - pub mod_loader: CurseforgeLoader, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatestEarlyAccessFilesIndex { +pub struct ModFilesIndex { pub game_version: String, pub file_id: u32, pub filename: String, @@ -423,4 +421,20 @@ pub async fn get(id: u32) -> Result { &fetch(format!("/v1/mods/{}", id).as_str()) .await?, )?.data) -} \ No newline at end of file +} + +pub async fn get_package_body(url: String) -> Result { + Ok(serde_json::from_slice::>( + &fetch(&url).await?, + )?.data) +} + +// pub async fn get_all_versions( +// project_id: &str, +// game_versions: Option>, +// loaders: Option>, +// page: Option, +// page_size: Option, +// ) -> Result> { + +// } \ No newline at end of file diff --git a/packages/core/src/api/package/content/mod.rs b/packages/core/src/api/package/content/mod.rs index fa748f3a..74df8030 100644 --- a/packages/core/src/api/package/content/mod.rs +++ b/packages/core/src/api/package/content/mod.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}; use crate::package::content::modrinth::FacetBuilder; -use crate::store::{Author, ProviderSearchResults}; +use crate::store::{Author, PackageBody, ProviderSearchResults}; use crate::Result; mod modrinth; @@ -107,18 +107,29 @@ impl Providers { project_id: &str, game_versions: Option>, loaders: Option>, + page: Option, + page_size: Option, ) -> Result> { - Ok(match self { - Self::Modrinth => modrinth::get_all_versions(project_id, game_versions, loaders), - Self::Curseforge => todo!(), - } - .await? - .into_iter() - .map(Into::into) - .collect()) + Ok( + match self { + Self::Modrinth => modrinth::get_all_versions(project_id, game_versions, loaders), + // Self::Curseforge => curseforge::get_all_versions( + // project_id, + // game_versions.map(|list| list.first()), + // loaders.map(|list| list.first()), + // page, + // page_size + // ), + Self::Curseforge => todo!(), + } + .await? + .into_iter() + .map(Into::into) + .collect() + ) } - pub async fn get_versions(&self, versions: Vec) -> Result> { + pub async fn get_versions(&self, pkg_id: &str, versions: Vec) -> Result> { Ok(match self { Self::Modrinth => modrinth::get_versions(versions), Self::Curseforge => todo!(), @@ -129,7 +140,7 @@ impl Providers { .collect()) } - pub async fn get_version(&self, version_id: &str) -> Result { + pub async fn get_version(&self, pkg_id: &str, version_id: &str) -> Result { Ok(match self { Self::Modrinth => modrinth::get_version(version_id), Self::Curseforge => todo!(), @@ -139,11 +150,27 @@ impl Providers { } pub async fn get_authors(&self, author: &Author) -> Result> { - match self { - Self::Modrinth => modrinth::get_authors(author), - Self::Curseforge => todo!(), - } - .await + Ok(match author { + Author::Team { .. } => { + match self { + Self::Modrinth => modrinth::get_authors(author).await?, + _ => return Err(anyhow::anyhow!("{} does not support teams", self).into()), + } + }, + Author::Users(users) => users.to_owned() + }) + } + + pub async fn get_package_body(&self, body: &PackageBody) -> Result { + Ok(match body { + PackageBody::Url(url) => { + match self { + Self::Curseforge => curseforge::get_package_body(url.to_owned()).await?, + _ => return Err(anyhow::anyhow!("{} does not support direct URLs", self).into()), + } + }, + PackageBody::Markdown(markdown) => markdown.to_owned() + }) } } diff --git a/packages/core/src/api/package/content/modrinth.rs b/packages/core/src/api/package/content/modrinth.rs index 296dc59b..549d6987 100644 --- a/packages/core/src/api/package/content/modrinth.rs +++ b/packages/core/src/api/package/content/modrinth.rs @@ -68,7 +68,7 @@ impl From for ManagedPackage { id: value.id, title: value.title, description: value.description, - body: value.body, + body: crate::store::PackageBody::Markdown(value.body), main: value.slug, versions: value.versions, game_versions: value.game_versions, @@ -78,7 +78,7 @@ impl From for ManagedPackage { updated: value.updated, client: value.client_side, server: value.server_side, - downloads: value.downloads, + downloads: value.downloads as u64, followers: value.followers, categories: value.categories, optional_categories: Some(value.additional_categories), diff --git a/packages/core/src/store/package.rs b/packages/core/src/store/package.rs index 89b117a8..5b151af5 100644 --- a/packages/core/src/store/package.rs +++ b/packages/core/src/store/package.rs @@ -603,6 +603,13 @@ pub enum Author { Users(Vec), } +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum PackageBody { + Url(String), + Markdown(String), +} + /// Universal metadata for any managed package from a Mod distribution platform. #[cfg_attr(feature = "specta", derive(specta::Type))] #[derive(Serialize, Deserialize, Clone, Debug)] @@ -613,7 +620,7 @@ pub struct ManagedPackage { pub package_type: PackageType, pub title: String, pub description: String, - pub body: String, + pub body: PackageBody, pub main: String, pub versions: Vec, pub game_versions: Vec, @@ -624,7 +631,7 @@ pub struct ManagedPackage { pub updated: DateTime, pub client: PackageSide, pub server: PackageSide, - pub downloads: u32, + pub downloads: u64, pub followers: u32, pub categories: Vec, pub optional_categories: Option>, diff --git a/packages/core/src/utils/mod.rs b/packages/core/src/utils/mod.rs index 293d0d95..8b95dcf1 100644 --- a/packages/core/src/utils/mod.rs +++ b/packages/core/src/utils/mod.rs @@ -8,6 +8,7 @@ pub mod http; pub mod java; pub mod watcher; +pub mod pagination; #[cfg(feature = "tauri")] pub mod window; diff --git a/packages/core/src/utils/pagination.rs b/packages/core/src/utils/pagination.rs new file mode 100644 index 00000000..d5cd4f85 --- /dev/null +++ b/packages/core/src/utils/pagination.rs @@ -0,0 +1,10 @@ +pub fn get_page_offset(page: Option, page_size: Option) -> (u32, u16) { + let page = page.unwrap_or(1); + let page_size = page_size.unwrap_or(10); + let offset = (page - 1) * page_size as u32; + (offset, page_size) +} + +pub fn get_page_count(total: u32, page_size: u16) -> u32 { + (total as f64 / page_size as f64).ceil() as u32 +} \ No newline at end of file