diff --git a/apps/onelauncher/frontend/src/components/content/PackageItem.tsx b/apps/onelauncher/frontend/src/components/content/PackageItem.tsx new file mode 100644 index 00000000..63e9830f --- /dev/null +++ b/apps/onelauncher/frontend/src/components/content/PackageItem.tsx @@ -0,0 +1,75 @@ +import type { Provider, SearchResult } from '@/bindings.gen'; +import { abbreviateNumber } from '@/utils'; +import { Show } from '@onelauncher/common/components'; +import { useNavigate } from '@tanstack/react-router'; +import { Download01Icon } from '@untitled-theme/icons-react'; + +export function PackageGrid({ items, provider }: { items: Array; provider: Provider }) { + return ( +
+ {items.map(item => ( + + ))} +
+ ); +} + +export function PackageItem({ provider, ...item }: SearchResult & { provider: Provider }) { + const navigate = useNavigate() + function redirect() { + navigate({to: "/app/browser/package/$provider/$slug", params: {provider, slug: item.slug}}) + } + + return ( +
+
+ + )} + when={item.icon_url} + > + {`Icon + {`Icon + +
+
+
+

{item.title}

+

+ By + {' '} + {item.author} + {' '} + on + {' '} + {provider} +

+
+ +

{item.description}

+ +
+ +
+ + {abbreviateNumber(item.downloads)} +
+
+
+
+
+ ); +} diff --git a/apps/onelauncher/frontend/src/hooks/useBrowser.tsx b/apps/onelauncher/frontend/src/hooks/useBrowser.tsx index 51be9ff1..522af87b 100644 --- a/apps/onelauncher/frontend/src/hooks/useBrowser.tsx +++ b/apps/onelauncher/frontend/src/hooks/useBrowser.tsx @@ -1,8 +1,10 @@ -import type { ClusterModel, Provider, SearchQuery } from '@/bindings.gen'; +import type { ClusterModel, GameLoader, ManagedPackage, ManagedVersion, Paginated, Provider, SearchQuery, SearchResult } from '@/bindings.gen'; +import type { UndefinedInitialDataOptions } from '@tanstack/react-query'; import { bindings } from '@/main'; import { PROVIDERS } from '@/utils'; import { useCommand } from '@onelauncher/common'; -import { createContext, useContext, useMemo, useState } from 'react'; +import { createContext, useContext, useEffect, useMemo, useState } from 'react'; +import { useNavigate } from '@tanstack/react-router'; export interface BrowserControllerType { cluster: ClusterModel | undefined; @@ -27,6 +29,7 @@ export function BrowserProvider(props: any) { const { children } = props; const [cluster, setCluster] = useState(undefined); const [provider, setProvider] = useState(PROVIDERS[0]); + const navigate = useNavigate() const [query, setQuery] = useState({ filters: null, query: null, @@ -35,6 +38,11 @@ export function BrowserProvider(props: any) { sort: null, }); + useEffect(()=>{ + if(query.filters?.categories || query.query) + navigate({to: "/app/browser/search"}) + },[query]) + const context = useMemo(() => ({ cluster, setCluster, @@ -52,6 +60,15 @@ export function BrowserProvider(props: any) { ); } -export function useBrowserSearch(provider: Provider, query: SearchQuery) { - return useCommand('searchPackages', () => bindings.core.searchPackages(provider, query)); +export function useBrowserSearch(provider: Provider, query: SearchQuery, options?: Omit>, 'queryKey' | 'queryFn'> | undefined) { + const validFilters = useMemo(() => Object.values(query.filters ?? {}).filter(a => a).length > 0, [query.filters]); + return useCommand('searchPackages', () => bindings.core.searchPackages(provider, validFilters ? query : { ...query, filters: null }), options); +} + +export function usePackageData(provider: Provider, slug: string, options?: Omit, "queryKey" | "queryFn"> | undefined){ + return useCommand("getPackage", () => bindings.core.getPackage(provider, slug), options) +} + +export function usePackageVersions(provider: Provider, slug: string, {mc_versions, loaders, offset, limit, ...options}:{mc_versions?:Array|null, loaders?:Array|null, offset?:number, limit: number} & Omit>, "queryKey" | "queryFn">){ + return useCommand("getPackageVersions", ()=>bindings.core.getPackageVersions(provider, slug, mc_versions??null, loaders??null, (offset??0) as unknown as bigint, limit as unknown as bigint), options) } diff --git a/apps/onelauncher/frontend/src/hooks/usePagination.tsx b/apps/onelauncher/frontend/src/hooks/usePagination.tsx new file mode 100644 index 00000000..33d5937e --- /dev/null +++ b/apps/onelauncher/frontend/src/hooks/usePagination.tsx @@ -0,0 +1,118 @@ +import { Button, Show } from '@onelauncher/common/components'; +import { ChevronRightIcon, ChevronLeftIcon } from '@untitled-theme/icons-react'; +import { useState } from 'react'; + +export interface PaginationOptions { + itemsCount: number; + itemsPerPage: number; + initialPage?: number; +}; + +function usePagination(options: PaginationOptions) { + // const [options, setOptions] = useState(initialOptions); + + const [page, setPage] = useState(Math.max(1, options.initialPage || 1)); + + const next = () => setPage(page => page + 1); + const prev = () => setPage(page => page - 1); + + const totalPages = Math.ceil(options.itemsCount / options.itemsPerPage); + + const hasNext = page < totalPages; + const hasPrev = page > 1; + + const offset = (page - 1) * options.itemsPerPage; + + // Exported + function reset(/*options: PaginationOptions*/) { + setPage(1); + } + + function getVisiblePageNumbers(page: number) { + const pages: number[] = []; + + const left = page - 1 > 1 ? page - 1 : null; + const right = page + 1 < totalPages ? page + 1 : null; + + if (left !== null) + pages.push(left); + + if (page !== 1 && page !== totalPages) + pages.push(page); + + if (right !== null) + pages.push(right); + + return pages; + } + + function PaginationBtn(props: { page: number }) { + return ( + + ); + } + + const Navigation = () => ( +
+ +
+ ); + + return { + page, + next, + prev, + hasNext, + hasPrev, + totalPages, + offset, + reset, + options, + Navigation, + }; +} + +export default usePagination; diff --git a/apps/onelauncher/frontend/src/routes/app/browser/index.tsx b/apps/onelauncher/frontend/src/routes/app/browser/index.tsx index 43cc0b81..9ca0ce77 100644 --- a/apps/onelauncher/frontend/src/routes/app/browser/index.tsx +++ b/apps/onelauncher/frontend/src/routes/app/browser/index.tsx @@ -1,12 +1,17 @@ +import type { Provider } from '@/bindings.gen'; import OneConfigLogo from '@/assets/logos/oneconfig.svg'; +import { PackageGrid } from '@/components/content/PackageItem'; import { useBrowserContext, useBrowserSearch } from '@/hooks/useBrowser'; +import { PROVIDERS } from '@/utils'; import { Button, Show } from '@onelauncher/common/components'; -import { createFileRoute, useSearch } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; import { ChevronRightIcon } from '@untitled-theme/icons-react'; +import { useState } from 'react'; import { BrowserLayout } from './route'; export const Route = createFileRoute('/app/browser/')({ - component: RouteComponent, + component: RouteComponent + }); function RouteComponent() { @@ -14,6 +19,7 @@ function RouteComponent() {
+
); @@ -21,9 +27,8 @@ function RouteComponent() { function Featured() { const context = useBrowserContext(); - const search = useBrowserSearch(context.provider, context.query); return ( - + 0)}>
Featured
@@ -50,13 +55,43 @@ function Featured() {
- {search.status} - {search.isSuccess && ( - <> - {search.data.items.map(item =>

{item.name}

)} - - )}
); } + +function Lists() { + return ( +
+ {PROVIDERS.map(provider => ( + + ))} +
+ ); +} + +function List({ provider }: { provider: Provider }) { + const search = useBrowserSearch(provider, { + filters: null, + limit: 18 as unknown as bigint, + offset: null, + query: null, + sort: null, + }); + const [expanded, setExpanded] = useState(false); + return ( +
+

{provider}

+
+ {search.isSuccess + ? + :

Loading...

} + +
+ +
+
+
+
+ ); +} diff --git a/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx b/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx new file mode 100644 index 00000000..e3d3f84b --- /dev/null +++ b/apps/onelauncher/frontend/src/routes/app/browser/package.$provider.$slug.tsx @@ -0,0 +1,287 @@ +import type { ClusterModel, ManagedPackage, ManagedUser, ManagedVersion, Paginated, Provider } from '@/bindings.gen' +import { useBrowserContext, usePackageData, usePackageVersions } from '@/hooks/useBrowser' +import { useClusters } from '@/hooks/useCluster' +import { bindings } from '@/main' +import { abbreviateNumber, formatAsRelative, PROVIDERS } from '@/utils' +import { useCommand } from '@onelauncher/common' +import { Show, Tooltip, Button, Dropdown } from '@onelauncher/common/components' +import { createFileRoute, Link } from '@tanstack/react-router' +import { openUrl } from '@tauri-apps/plugin-opener' +import { Download01Icon, LinkExternal01Icon, File02Icon, CalendarIcon, ClockRewindIcon, ChevronDownIcon, ChevronUpIcon } from '@untitled-theme/icons-react' +import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' +import { Collection, ListBox, ListBoxItem, Popover, Select } from 'react-aria-components' + + +export const Route = createFileRoute('/app/browser/package/$provider/$slug')({ + component: RouteComponent, +}) + +function includes(list: { includes: (arg0: C) => boolean }, element: T): element is C { + return list.includes(element as unknown as C) +} + +type PackageContextType = { + pkg: ManagedPackage | undefined + versions: Paginated | undefined +} + +const PackageContext = createContext({ + pkg: undefined, + versions: undefined +}) + +function RouteComponent() { + const { provider, slug } = Route.useParams() + if (!includes(PROVIDERS, provider)) throw new Error("Invalid provider"); + const packageData = usePackageData(provider, slug) + const browserContext = useBrowserContext() + const { data: versions } = usePackageVersions(provider, slug, { + mc_versions: browserContext.cluster ? [browserContext.cluster.mc_version] : null, + loaders: browserContext.cluster ? [browserContext.cluster.mc_loader] : null, + limit: 50 + }) + + return ( + + +
+ + +
+
+ About + Versions + + +
+ +
+ +
+
+
+
+
+ ) +} + + +function BrowserSidebar({ package: pkg }: { package: ManagedPackage }) { + const { provider } = Route.useParams() + + const createdAt = useMemo(() => pkg.created ? new Date(pkg.created) : null, []); + const updatedAt = useMemo(() => pkg.updated ? new Date(pkg.updated) : null, []); + + const authors = useCommand("getUsersFromAuthor", () => bindings.core.getUsersFromAuthor(provider as Provider, pkg.author)) + + return ( +
+
+
+ {`Icon + {`Icon +
+
+
+

{pkg.name}

+

+ {pkg.package_type} + {' '} + on + {' '} + {pkg.provider} +

+
+ +

{pkg.short_desc}

+ +
+ +
+ + {abbreviateNumber(pkg.downloads)} +
+
+
+
+
+ + + + {/*
+

Links

+ + {contentPackage.provider} + {' '} + Page + +
*/} + +
+

Authors

+ { + authors.isSuccess + ? authors.data.map(author => ) + :

Loading...

+ } + +
+ +
+

Details

+ +
+ + License + + {pkg.license?.name || pkg.license?.id || 'Unknown'} + +
+
+ + + +
+ + Created + + {formatAsRelative(createdAt!.getTime(), 'en', 'long')} + +
+
+
+ + + +
+ + Last Updated + + {formatAsRelative(updatedAt!.getTime(), 'en', 'long')} + +
+
+
+
+ +
+ ); +} + + +function colorForType(type: string) { + switch (type.toLowerCase()) { + case 'release': + return 'bg-code-trace'; + case 'snapshot': + return 'bg-code-debug'; + case 'beta': + return 'bg-code-warn'; + case 'alpha': + return 'bg-code-error'; + default: + return 'bg-border/05'; + } +} + +function InstallButton({ ...pkg }: ManagedPackage) { + const triggerRef = useRef(null) + const { provider, slug } = Route.useParams() + const [open, setOpen] = useState(false) + const clusters = useClusters() + const browserContext = useBrowserContext() + const { versions } = useContext(PackageContext) + const version = useMemo(() => { + if (!versions || !browserContext.cluster) return undefined; + return versions.items.findLast(version => + version.mc_versions.includes(browserContext.cluster!.mc_version) + && version.loaders.includes(browserContext.cluster!.mc_loader) + ) + }, [browserContext.cluster, versions]) + + function download() { + if (!version || !browserContext.cluster || !includes(PROVIDERS, provider)) return false; + downloadPackage(browserContext.cluster, provider, version) + } + + + return ( + +
+ + + + +
+ + + ) +} + + +function Author({ author }: { author: ManagedUser }) { + + //TODO: onclick and fallback avatar url + return ( + { if (author.url) openUrl(author.url) }} + > + {`${author.username}'s +
+ {author.username} + + + Organization + + + + {author.role} + +
+ +
+ + ) +} + +function downloadPackage(cluster: ClusterModel, provider: Provider, version: ManagedVersion, skipCompatibility = false) { + return bindings.core.downloadPackage(provider, version.project_id, version.version_id, cluster.id, skipCompatibility) +} diff --git a/apps/onelauncher/frontend/src/routes/app/browser/route.tsx b/apps/onelauncher/frontend/src/routes/app/browser/route.tsx index b15a71d6..ab0ae053 100644 --- a/apps/onelauncher/frontend/src/routes/app/browser/route.tsx +++ b/apps/onelauncher/frontend/src/routes/app/browser/route.tsx @@ -4,9 +4,10 @@ import { BrowserProvider, useBrowserContext } from '@/hooks/useBrowser'; import { useClusters } from '@/hooks/useCluster'; import { PROVIDERS } from '@/utils'; import { browserCategories } from '@/utils/browser'; -import { Dropdown, Show, TextField } from '@onelauncher/common/components'; +import { AnimatedOutlet, Dropdown, Show, TextField } from '@onelauncher/common/components'; import { createFileRoute, Outlet } from '@tanstack/react-router'; import { SearchMdIcon } from '@untitled-theme/icons-react'; +import { useEffect, useState } from 'react'; export const Route = createFileRoute('/app/browser')({ component: RouteComponent, @@ -16,7 +17,11 @@ function RouteComponent() { return (
- +
); @@ -51,11 +56,16 @@ export function BrowserLayout(props: any) { function BrowserSidebar() { const context = useBrowserContext(); const clusters = useClusters(); + useEffect(() => { + const game_versions = context.cluster?.mc_version ? [context.cluster.mc_version] : null; + const loaders = context.cluster?.mc_loader ? [context.cluster.mc_loader] : null; + context.setQuery({ ...context.query, filters: { ...defaultFilters, ...context.query.filters, game_versions, loaders } }); + }, [context.cluster]); return (
-
Active Cluster
+
Active Cluster
{ const cluster = clusters?.find(cluster => cluster.id.toString() === id); @@ -71,7 +81,7 @@ function BrowserSidebar() {
-
Provider
+
Provider
context.setProvider(id as typeof context.provider)} selectedKey={context.provider}> {PROVIDERS.map(provider => ( @@ -88,22 +98,22 @@ function BrowserSidebar() { ); } -const defaultFilters:Filters = { +const defaultFilters: Filters = { categories: null, game_versions: null, loaders: null, - package_types: null -} + package_types: null, +}; function BrowserCategories() { const context = useBrowserContext(); const categories = browserCategories.byPackageType('mod', context.provider); - function switchCategory(category:string){ - if(context.query.filters?.categories?.includes(category)) - context.setQuery({...context.query, filters: {...defaultFilters, ...context.query.filters, categories: (context.query.filters?.categories ?? []).filter(cat=>cat!==category)}}) - else - context.setQuery({...context.query, filters: {...defaultFilters, ...context.query.filters, categories: [...(context.query.filters?.categories ?? []), category]}}) + function switchCategory(category: string) { + const newCategories = context.query.filters?.categories?.includes(category) + ? context.query.filters.categories.filter(cat => cat !== category) + : [...(context.query.filters?.categories ?? []), category]; + context.setQuery({ ...context.query, filters: { ...defaultFilters, ...context.query.filters, categories: newCategories.length > 0 ? newCategories : null } }); } return ( @@ -112,13 +122,13 @@ function BrowserCategories() {
-
Categories
+
Categories
{categories.map(category => (

switchCategory(category.id)} + onClick={() => switchCategory(category.id)} > {category.display}

@@ -131,13 +141,23 @@ function BrowserCategories() { } function BrowserToolbar() { + const context = useBrowserContext(); + const [query, setQuery] = useState(context.query.query ?? "") return (
} + onChange={e=>setQuery(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key !== 'Enter') + return; + e.preventDefault(); + context.setQuery({ ...context.query, query: e.currentTarget.value }); + }} placeholder="Search for content" />
diff --git a/apps/onelauncher/frontend/src/routes/app/browser/search.tsx b/apps/onelauncher/frontend/src/routes/app/browser/search.tsx new file mode 100644 index 00000000..e31ffb6a --- /dev/null +++ b/apps/onelauncher/frontend/src/routes/app/browser/search.tsx @@ -0,0 +1,72 @@ +import { PackageGrid } from '@/components/content/PackageItem'; +import { useBrowserContext, useBrowserSearch } from '@/hooks/useBrowser'; +import { Show } from '@onelauncher/common/components'; +import { createFileRoute } from '@tanstack/react-router'; +import { useEffect, useMemo, useState } from 'react'; +import { BrowserLayout } from './route'; +import type { Paginated, SearchResult } from '@/bindings.gen'; +import type { PaginationOptions } from '@/hooks/usePagination'; +import usePagination from '@/hooks/usePagination'; + +export const Route = createFileRoute('/app/browser/search')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( + +
+ +
+
+ ); +} + +function Search() { + const context = useBrowserContext(); + const search = useBrowserSearch(context.provider, context.query, { + }); + useEffect(() => { + search.refetch(); + }, [context.provider, context.query, context.cluster]); + + return ( + <> + { + search.data + ? + :

Loading

+ } + + ); +} + +function Results({results}:{results:Paginated}){ + const context = useBrowserContext(); + const [oldTotal, setOldTotal] = useState(results.total) + const pagination = usePagination({ + itemsCount: results.total as unknown as number, + itemsPerPage: context.query.limit as unknown as number + }) + useEffect(()=>{ + context.setQuery({...context.query, offset: pagination.offset as unknown as bigint}) + },[pagination.page]) + useEffect(()=>{ + if(oldTotal === results.total) return; + pagination.reset() + console.log("pagination reset", results.total) + setOldTotal(results.total) + },[results.total]) + return ( +
+
+ +
+ {results.items.length == 0 + ?

No results

+ : + } + +
+ ) +} diff --git a/apps/onelauncher/frontend/src/styles/global.css b/apps/onelauncher/frontend/src/styles/global.css index e215a8b0..04e19e8e 100644 --- a/apps/onelauncher/frontend/src/styles/global.css +++ b/apps/onelauncher/frontend/src/styles/global.css @@ -41,3 +41,7 @@ code { background: linear-gradient(270deg, transparent, rgba(0, 0, 0, 0.85) 80%); } + +@utility text-trim{ + text-box: trim-both cap alphabetic +} diff --git a/apps/onelauncher/frontend/src/utils/browser.ts b/apps/onelauncher/frontend/src/utils/browser.ts index 375db1d3..b32f8c6f 100644 --- a/apps/onelauncher/frontend/src/utils/browser.ts +++ b/apps/onelauncher/frontend/src/utils/browser.ts @@ -6,7 +6,7 @@ export interface CategoryItem { } export const browserCategories = { - byPackageType(packageType: 'mod' | string, provider: string): Array { + byPackageType(packageType: 'mod' | string & {}, provider: string): Array { switch (packageType) { case 'mod': return this.mod(provider); diff --git a/apps/onelauncher/frontend/tsconfig.json b/apps/onelauncher/frontend/tsconfig.json index 6325fbce..d92bf525 100644 --- a/apps/onelauncher/frontend/tsconfig.json +++ b/apps/onelauncher/frontend/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "target": "ES2022", "jsx": "react-jsx", - "lib": ["ES2022", "DOM", "DOM.Iterable"], + "lib": ["ES2023", "DOM", "DOM.Iterable"], "baseUrl": ".", "module": "ESNext", diff --git a/packages/core/src/api/packages/data.rs b/packages/core/src/api/packages/data.rs index d046ea63..a26c5639 100644 --- a/packages/core/src/api/packages/data.rs +++ b/packages/core/src/api/packages/data.rs @@ -201,14 +201,14 @@ pub struct PackageLinks { #[onelauncher_macro::specta] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PackageDonationUrl { - #[serde(alias = "id")] - pub platform: PackageDonationPlatform, + pub id: PackageDonationPlatform, pub url: String, } /// https://api.modrinth.com/v2/tag/donation_platform #[onelauncher_macro::specta] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum PackageDonationPlatform { Patreon, #[serde(alias = "bmac")] diff --git a/packages/core/src/api/packages/provider/modrinth.rs b/packages/core/src/api/packages/provider/modrinth.rs index 2712b28c..a08dd67f 100644 --- a/packages/core/src/api/packages/provider/modrinth.rs +++ b/packages/core/src/api/packages/provider/modrinth.rs @@ -70,11 +70,11 @@ impl ProviderExt for ModrinthProviderImpl { if let Some(loaders) = &filters.loaders { for loader in loaders { - builder.and(Facet("loaders".to_string(), FacetOperation::Eq, loader.to_string())); + builder.and(Facet("categories".to_string(), FacetOperation::Eq, loader.to_string())); } } - params.append_pair("facets", &builder.build()); + params.append_pair("facets", &format!("[{}]", &builder.build())); } if let Some(sort) = &query.sort { @@ -510,8 +510,8 @@ impl From for ManagedUser { struct ModrinthUser { pub id: String, pub username: String, - pub avatar_url: String, - pub bio: String, + pub avatar_url: Option, + pub bio: Option, } impl From for ManagedUser { @@ -520,16 +520,12 @@ impl From for ManagedUser { id: value.id.clone(), username: value.username, url: Some(format!( - "{}/user/{}", + "{}user/{}", Provider::Modrinth.website(), value.id )), - avatar_url: Some(value.avatar_url), - bio: if value.bio.is_empty() || value.bio.eq_ignore_ascii_case("a modrinth creator.") { - None - } else { - Some(value.bio) - }, + avatar_url: value.avatar_url, + bio: value.bio, is_organization_user: false, role: None, } diff --git a/packages/core/src/api/tauri/commands.rs b/packages/core/src/api/tauri/commands.rs index 2ac6b8a4..4fa6576e 100644 --- a/packages/core/src/api/tauri/commands.rs +++ b/packages/core/src/api/tauri/commands.rs @@ -1,7 +1,7 @@ use interpulse::api::minecraft::Version; use onelauncher_entity::{icon::Icon, loader::GameLoader, package::Provider, packages, resolution::Resolution}; use sea_orm::ActiveValue::Set; -use crate::{api::packages::{data::{SearchResult, ManagedPackage, ManagedUser, ManagedVersion, SearchQuery}, provider::ProviderExt}, store::{Settings, State}, utils::pagination::Paginated}; +use crate::{api::packages::{data::{ManagedPackage, ManagedUser, ManagedVersion, PackageAuthor, SearchQuery, SearchResult}, provider::ProviderExt}, store::{Settings, State}, utils::pagination::Paginated}; use tauri::{AppHandle, Runtime}; use crate::{api::{self, cluster::dao::ClusterId}, error::{LauncherError, LauncherResult}, store::{credentials::MinecraftCredentials, Core}}; @@ -99,6 +99,9 @@ pub trait TauriLauncherApi { #[taurpc(alias = "downloadPackage")] async fn download_package(provider: Provider, package_id: String, version_id: String, cluster_id: ClusterId, skip_compatibility: Option) -> LauncherResult; + + #[taurpc(alias = "getUsersFromAuthor")] + async fn get_users_from_author(provider: Provider, author: PackageAuthor) -> LauncherResult>; } @@ -425,4 +428,8 @@ impl TauriLauncherApi for TauriLauncherApiImpl { Ok(model) } + + async fn get_users_from_author(self, provider: Provider, author: PackageAuthor) -> LauncherResult> { + Ok(provider.get_users_from_author(author).await?) + } } diff --git a/packages/scripts/setup.ps1 b/packages/scripts/setup.ps1 index fcf91cc6..75503a94 100644 --- a/packages/scripts/setup.ps1 +++ b/packages/scripts/setup.ps1 @@ -198,8 +198,7 @@ https://learn.microsoft.com/windows/package-manager/winget/ Write-Host Write-Host 'Installing Rust tools...' -ForegroundColor Yellow - cargo install cargo-watch - cargo install sqlx-cli + if (Get-Command cargo-binstall -ea 0) { cargo binstall cargo-watch sqlx-cli } else { cargo install cargo-watch sqlx-cli } if ($LASTEXITCODE -ne 0) { Exit-WithError 'Failed to install Rust tools' } diff --git a/packages/scripts/setup.sh b/packages/scripts/setup.sh index 8c35af75..5ef07cda 100755 --- a/packages/scripts/setup.sh +++ b/packages/scripts/setup.sh @@ -184,7 +184,7 @@ esac if [ "${CI:-}" != "true" ]; then echo "installing rust tools..." _tools="cargo-watch sqlx-cli" - echo "$_tools" | xargs cargo install + echo "$_tools" | xargs cargo $(has cargo-binstall && echo binstall || echo install) fi echo 'your machine has been setup for onelauncher development!'