feat: Finished Curseforge implementation?
This commit is contained in:
parent
518c7ec8b9
commit
a1f8df7f49
8 changed files with 298 additions and 170 deletions
|
|
@ -149,6 +149,7 @@ pub async fn download_provider_package(
|
|||
package_version,
|
||||
)
|
||||
.await?;
|
||||
|
||||
package::add_package(
|
||||
&cluster.cluster_path(),
|
||||
pkg_path,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import type { Cluster, ManagedPackage, PackageType, Providers, ProviderSearchQuery, SearchResult } from '@onelauncher/client/bindings';
|
||||
import type { ModalProps } from '~ui/components/overlay/Modal';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { bridge } from '~imports';
|
||||
import Button from '~ui/components/base/Button';
|
||||
import Dropdown from '~ui/components/base/Dropdown';
|
||||
import Modal, { createModal } from '~ui/components/overlay/Modal';
|
||||
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, For, on, onMount, type ParentProps, type Setter, Show, untrack, useContext } from 'solid-js';
|
||||
import { useRecentCluster } from './useCluster';
|
||||
import useCommand, { tryResult } from './useCommand';
|
||||
import { type Accessor, type Context, createContext, createEffect, createSignal, on, onMount, type ParentProps, type Setter, useContext } from 'solid-js';
|
||||
import { ChooseClusterModal, useRecentCluster } from './useCluster';
|
||||
import { tryResult } from './useCommand';
|
||||
|
||||
export type ProviderSearchOptions = ProviderSearchQuery & { provider: Providers };
|
||||
export type PopularPackages = Record<Providers, SearchResult[]>;
|
||||
|
|
@ -60,8 +57,30 @@ export function BrowserProvider(props: ParentProps) {
|
|||
const navigate = useNavigate();
|
||||
const recentCluster = useRecentCluster();
|
||||
|
||||
const currentlySelectedCluster = (list: Cluster[]) => {
|
||||
const cluster = controller.cluster();
|
||||
|
||||
if (cluster === undefined)
|
||||
return 0;
|
||||
|
||||
if (list === undefined)
|
||||
return 0;
|
||||
|
||||
const index = list.findIndex(c => c.uuid === cluster.uuid) || 0;
|
||||
if (index === -1)
|
||||
return 0;
|
||||
|
||||
return index;
|
||||
};
|
||||
|
||||
const chooseCluster = (cluster: Cluster) => {
|
||||
controller.setCluster(cluster);
|
||||
};
|
||||
|
||||
const modal = createModal(props => (
|
||||
<ChooseClusterModal
|
||||
onSelected={chooseCluster}
|
||||
selected={list => currentlySelectedCluster(list) || 0}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
|
@ -163,73 +182,3 @@ export default function useBrowser() {
|
|||
|
||||
return controller;
|
||||
}
|
||||
|
||||
function ChooseClusterModal(props: ModalProps) {
|
||||
const [selected, setSelected] = createSignal<number>(0);
|
||||
const [clusters] = useCommand(() => bridge.commands.getClusters());
|
||||
const controller = useBrowser();
|
||||
|
||||
const currentlySelectedCluster = (list: Cluster[]) => {
|
||||
const cluster = controller.cluster();
|
||||
|
||||
if (cluster === undefined)
|
||||
return 0;
|
||||
|
||||
if (list === undefined)
|
||||
return 0;
|
||||
|
||||
const index = list.findIndex(c => c.uuid === cluster.uuid) || 0;
|
||||
if (index === -1)
|
||||
return 0;
|
||||
|
||||
return index;
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
setSelected(currentlySelectedCluster(clusters() || []));
|
||||
});
|
||||
|
||||
function chooseCluster() {
|
||||
const index = untrack(selected);
|
||||
const clusterz = untrack(clusters);
|
||||
if (clusterz !== undefined && index !== undefined)
|
||||
controller.setCluster(clusterz[index]);
|
||||
|
||||
props.hide();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal.Simple
|
||||
{...props}
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Close"
|
||||
onClick={props.hide}
|
||||
/>,
|
||||
<Button
|
||||
children="Save"
|
||||
onClick={chooseCluster}
|
||||
/>,
|
||||
]}
|
||||
children={(
|
||||
<Show
|
||||
fallback={<div>Loading...</div>}
|
||||
when={clusters !== undefined}
|
||||
>
|
||||
<Dropdown
|
||||
onChange={setSelected}
|
||||
selected={selected}
|
||||
>
|
||||
<For each={clusters()!}>
|
||||
{cluster => (
|
||||
<Dropdown.Row>{cluster.meta.name}</Dropdown.Row>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
</Show>
|
||||
)}
|
||||
title="Choose a cluster"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import type { Cluster } from '@onelauncher/client/bindings';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import { bridge } from '~imports';
|
||||
import Modal, { createModal } from '~ui/components/overlay/Modal';
|
||||
import Button from '~ui/components/base/Button';
|
||||
import Dropdown from '~ui/components/base/Dropdown';
|
||||
import Modal, { createModal, type ModalProps } from '~ui/components/overlay/Modal';
|
||||
import ClusterGame from '~ui/pages/cluster/ClusterGame';
|
||||
import { type Accessor, type Context, createContext, createEffect, createSignal, type ParentProps, type ResourceReturn, Show, useContext } from 'solid-js';
|
||||
import { type Accessor, type Context, createContext, createEffect, createResource, createSignal, For, Match, onMount, type ParentProps, type ResourceReturn, Show, Switch, untrack, useContext } from 'solid-js';
|
||||
import useCommand, { tryResult } from './useCommand';
|
||||
|
||||
const ClusterContext = createContext() as Context<ResourceReturn<Cluster>>;
|
||||
|
|
@ -104,3 +106,82 @@ export function useRecentCluster() {
|
|||
}
|
||||
|
||||
export default useClusterContext;
|
||||
|
||||
type ChooseClusterModalProps = ModalProps & {
|
||||
clusters?: () => Cluster[];
|
||||
selected?: (clusters: Cluster[]) => number;
|
||||
onSelected?: (cluster: Cluster) => void;
|
||||
};
|
||||
|
||||
export function ChooseClusterModal(props: ChooseClusterModalProps) {
|
||||
const [clusters] = createResource(async () => {
|
||||
if (props.clusters !== undefined)
|
||||
return await props.clusters();
|
||||
|
||||
return await tryResult(bridge.commands.getClusters);
|
||||
});
|
||||
const [selected, setSelected] = createSignal<number>(0);
|
||||
|
||||
onMount(() => {
|
||||
const newSelected = props.selected?.(clusters() || []);
|
||||
if (newSelected !== undefined)
|
||||
setSelected(newSelected);
|
||||
});
|
||||
|
||||
function chooseCluster() {
|
||||
const index = untrack(selected);
|
||||
const clusterz = untrack(clusters);
|
||||
if (clusterz !== undefined && index !== undefined) {
|
||||
const cluster = clusterz[index];
|
||||
if (cluster !== undefined)
|
||||
props.onSelected?.(cluster);
|
||||
}
|
||||
|
||||
props.hide();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal.Simple
|
||||
{...props}
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Close"
|
||||
onClick={props.hide}
|
||||
/>,
|
||||
<Button
|
||||
children="Save"
|
||||
onClick={chooseCluster}
|
||||
/>,
|
||||
]}
|
||||
children={(
|
||||
<Switch>
|
||||
<Match when={clusters() !== undefined}>
|
||||
<Dropdown
|
||||
disabled={clusters()?.length === 0}
|
||||
onChange={setSelected}
|
||||
selected={selected}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={(clusters()?.length || 0) > 0}>
|
||||
<For each={clusters()!}>
|
||||
{cluster => (
|
||||
<Dropdown.Row>{cluster.meta.name}</Dropdown.Row>
|
||||
)}
|
||||
</For>
|
||||
</Match>
|
||||
<Match when>
|
||||
<Dropdown.Row>No clusters found</Dropdown.Row>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Dropdown>
|
||||
</Match>
|
||||
<Match when>
|
||||
<div>Loading...</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
)}
|
||||
title="Choose a cluster"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,15 @@ import Dropdown from '~ui/components/base/Dropdown';
|
|||
import Link from '~ui/components/base/Link';
|
||||
import Tooltip from '~ui/components/base/Tooltip';
|
||||
import Markdown from '~ui/components/content/Markdown';
|
||||
import { createModal } from '~ui/components/overlay/Modal';
|
||||
import Spinner from '~ui/components/Spinner';
|
||||
import useBrowser from '~ui/hooks/useBrowser';
|
||||
import { ChooseClusterModal } from '~ui/hooks/useCluster';
|
||||
import useCommand, { tryResult } from '~ui/hooks/useCommand';
|
||||
import usePagination from '~ui/hooks/usePagination';
|
||||
import usePromptOpener from '~ui/hooks/usePromptOpener';
|
||||
import { abbreviateNumber, formatAsRelative } from '~utils';
|
||||
import { type Accessor, createContext, createEffect, createResource, createSignal, For, Match, on, type ParentProps, type Resource, Show, Switch, useContext } from 'solid-js';
|
||||
import { type Accessor, createContext, createEffect, createMemo, createResource, createSignal, For, Match, on, type ParentProps, type Resource, Show, Switch, useContext } from 'solid-js';
|
||||
import { getLicenseUrl, getPackageUrl, upperFirst } from '../../../utils';
|
||||
|
||||
interface BrowserModParams extends Params {
|
||||
|
|
@ -270,7 +272,7 @@ function InstallButton(props: ManagedPackage) {
|
|||
return game_version && loader;
|
||||
};
|
||||
|
||||
const filtered = () => clusters()?.filter(meetsRequirements);
|
||||
const filtered = createMemo(() => clusters()?.filter(meetsRequirements));
|
||||
|
||||
const getSelectedCluster = () => filtered()?.[selected()];
|
||||
|
||||
|
|
@ -312,44 +314,44 @@ function InstallButton(props: ManagedPackage) {
|
|||
});
|
||||
|
||||
return (
|
||||
<div class="h-12 flex flex-row">
|
||||
<Show when={(filtered()?.length || 0) > 0}>
|
||||
<div class="h-12 flex flex-row">
|
||||
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
children={(
|
||||
<div class="flex flex-1 flex-col items-center justify-center">
|
||||
<p class="text-xs">Download latest to</p>
|
||||
<span class="mt-0.5 h-3.5 max-w-38 overflow-x-hidden text-sm font-bold">{filtered?.()?.[selected?.()]?.meta.name || 'Unknown'}</span>
|
||||
</div>
|
||||
)}
|
||||
class="max-w-full flex-1 rounded-r-none!"
|
||||
disabled={filtered()?.length === 0}
|
||||
iconLeft={<Download01Icon />}
|
||||
onClick={download}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
class="w-8"
|
||||
component={props => (
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
class="h-full w-full border-l border-white/5 rounded-l-none! px-0!"
|
||||
iconLeft={<ChevronDownIcon />}
|
||||
onClick={() => props.setVisible(true)}
|
||||
/>
|
||||
)}
|
||||
disabled={filtered()?.length === 0}
|
||||
dropdownClass="w-58! right-0"
|
||||
onChange={setSelected}
|
||||
selected={selected}
|
||||
>
|
||||
<For each={filtered()}>
|
||||
{cluster => (
|
||||
<Dropdown.Row>{cluster.meta.name}</Dropdown.Row>
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
children={(
|
||||
<div class="flex flex-1 flex-col items-center justify-center">
|
||||
<p class="text-xs">Download latest to</p>
|
||||
<span class="mt-0.5 h-3.5 max-w-38 overflow-x-hidden text-sm font-bold">{filtered?.()?.[selected?.()]?.meta.name || 'Unknown'}</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
</div>
|
||||
class="max-w-full flex-1 rounded-r-none!"
|
||||
iconLeft={<Download01Icon />}
|
||||
onClick={download}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
class="w-8"
|
||||
component={props => (
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
class="h-full w-full border-l border-white/5 rounded-l-none! px-0!"
|
||||
iconLeft={<ChevronDownIcon />}
|
||||
onClick={() => props.setVisible(true)}
|
||||
/>
|
||||
)}
|
||||
dropdownClass="w-58! right-0"
|
||||
onChange={setSelected}
|
||||
selected={selected}
|
||||
>
|
||||
<For each={filtered()}>
|
||||
{cluster => (
|
||||
<Dropdown.Row>{cluster.meta.name}</Dropdown.Row>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -417,7 +419,7 @@ function BrowserPackageVersions() {
|
|||
|
||||
const list = getVersionsForPage(page());
|
||||
|
||||
return await tryResult(() => bridge.commands.getProviderPackageVersions(context.pkg().provider, list));
|
||||
return (await tryResult(() => bridge.commands.getProviderPackageVersions(context.pkg().provider, list))).toReversed();
|
||||
});
|
||||
|
||||
let container!: HTMLDivElement;
|
||||
|
|
@ -455,7 +457,7 @@ function BrowserPackageVersions() {
|
|||
</thead>
|
||||
|
||||
<tbody>
|
||||
<For each={versions()?.reverse()}>
|
||||
<For each={versions() || []}>
|
||||
{version => <VersionRow {...version} />}
|
||||
</For>
|
||||
</tbody>
|
||||
|
|
@ -469,7 +471,7 @@ function BrowserPackageVersions() {
|
|||
}
|
||||
|
||||
function colorForType(type: string) {
|
||||
switch (type) {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'release':
|
||||
return 'bg-code-trace';
|
||||
case 'snapshot':
|
||||
|
|
@ -484,6 +486,59 @@ function colorForType(type: string) {
|
|||
}
|
||||
|
||||
function VersionRow(props: ManagedVersion) {
|
||||
const [clusters, setClusters] = createSignal<Cluster[]>([]);
|
||||
const context = useContext(BrowserPackageContext);
|
||||
|
||||
async function getClusters() {
|
||||
const clusters = await tryResult(bridge.commands.getClusters);
|
||||
|
||||
return clusters.filter((cluster) => {
|
||||
const game_version = props.game_versions.includes(cluster.meta.mc_version);
|
||||
const loader = props.loaders.includes(cluster.meta.loader || 'vanilla');
|
||||
|
||||
return game_version && loader;
|
||||
});
|
||||
}
|
||||
|
||||
const modal = createModal((props) => {
|
||||
return (
|
||||
<ChooseClusterModal
|
||||
clusters={clusters}
|
||||
onSelected={downloadPackage}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
async function downloadPrompted() {
|
||||
const clusterz = await getClusters();
|
||||
setClusters(clusterz);
|
||||
|
||||
modal.show();
|
||||
}
|
||||
|
||||
async function downloadPackage(cluster: Cluster) {
|
||||
if (cluster === undefined)
|
||||
return;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line solid/reactivity -- lint issue
|
||||
await tryResult(() => bridge.commands.downloadProviderPackage(
|
||||
context!.pkg()!.provider,
|
||||
context!.pkg()!.id,
|
||||
cluster.uuid,
|
||||
cluster.meta.mc_version,
|
||||
cluster.meta.loader || null,
|
||||
props.id,
|
||||
));
|
||||
|
||||
await bridge.commands.syncClusterPackages(cluster.path || '');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<tr class="my-2 bg-page-elevated px-4 [&>td]:py-4">
|
||||
<td class="rounded-l-lg px-4">
|
||||
|
|
@ -533,6 +588,7 @@ function VersionRow(props: ManagedVersion) {
|
|||
<Button
|
||||
buttonStyle="iconSecondary"
|
||||
children={<Download01Icon />}
|
||||
onClick={downloadPrompted}
|
||||
/>
|
||||
</td>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import Button from '~ui/components/base/Button';
|
|||
import TextField from '~ui/components/base/TextField';
|
||||
import ScrollableContainer from '~ui/components/ScrollableContainer';
|
||||
import Sidebar from '~ui/components/Sidebar';
|
||||
import Spinner from '~ui/components/Spinner';
|
||||
import useBrowser from '~ui/hooks/useBrowser';
|
||||
import useClusterContext from '~ui/hooks/useCluster';
|
||||
import useCommand, { tryResult } from '~ui/hooks/useCommand';
|
||||
|
|
@ -39,19 +38,10 @@ function ClusterMods() {
|
|||
}
|
||||
|
||||
return formatted;
|
||||
}, {
|
||||
initialValue: new Map<Providers, ManagedPackage[]>(),
|
||||
});
|
||||
|
||||
function getManagedData(mod: Package): ManagedPackage | undefined {
|
||||
const meta = mod.meta;
|
||||
if (meta.type === 'managed') {
|
||||
const provider = managedMods()?.get(meta.provider);
|
||||
if (provider && provider.length > 0)
|
||||
return provider.find(pkg => pkg.id === meta.package_id);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Mods to display, should be a clone of the mods array but sorted
|
||||
const [displayedMods, setDisplayedMods] = createSignal<Package[]>([]);
|
||||
|
||||
|
|
@ -125,24 +115,19 @@ function ClusterMods() {
|
|||
</div>
|
||||
|
||||
<ScrollableContainer>
|
||||
<Spinner.Suspense>
|
||||
<Show when={managedMods.loading === false}>
|
||||
<Switch>
|
||||
<Match when={displayedMods().length > 0}>
|
||||
<For each={displayedMods()}>
|
||||
{(mod) => {
|
||||
const managed = getManagedData(mod);
|
||||
return <ModEntry managed={managed} pkg={mod} refetch={refetch} />;
|
||||
}}
|
||||
</For>
|
||||
</Match>
|
||||
<Switch>
|
||||
<Match when={displayedMods().length > 0}>
|
||||
<For each={displayedMods()}>
|
||||
{mod => (
|
||||
<ModEntry managedMods={managedMods()} pkg={mod} refetch={refetch} />
|
||||
)}
|
||||
</For>
|
||||
</Match>
|
||||
|
||||
<Match when={displayedMods().length === 0}>
|
||||
<p class="my-4 text-center text-2lg">No mods were found</p>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
</Spinner.Suspense>
|
||||
<Match when={displayedMods().length === 0}>
|
||||
<p class="my-4 text-center text-2lg">No mods were found</p>
|
||||
</Match>
|
||||
</Switch>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
);
|
||||
|
|
@ -153,13 +138,23 @@ export default ClusterMods;
|
|||
interface ModEntryProps {
|
||||
pkg: Package;
|
||||
refetch: () => void;
|
||||
managed?: ManagedPackage | undefined;
|
||||
managedMods?: Map<Providers, ManagedPackage[]>;
|
||||
};
|
||||
|
||||
function ModEntry(props: ModEntryProps) {
|
||||
const [cluster] = useClusterContext();
|
||||
const browser = useBrowser();
|
||||
|
||||
const managed = () => {
|
||||
const pkg = props.pkg;
|
||||
if (props.managedMods && pkg.meta.type === 'managed') {
|
||||
const package_id = pkg.meta.package_id;
|
||||
return props.managedMods.get(pkg.meta.provider)?.find(p => p.id === package_id);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const name = () => {
|
||||
if (props.pkg.meta.type === 'unknown')
|
||||
return props.pkg.file_name;
|
||||
|
|
@ -191,17 +186,19 @@ function ModEntry(props: ModEntryProps) {
|
|||
|
||||
const icon = () => {
|
||||
if (props.pkg.meta.type === 'managed')
|
||||
return props.managed?.icon_url;
|
||||
return managed()?.icon_url;
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
function onClick(e: MouseEvent) {
|
||||
if (props.managed) {
|
||||
const mngd = managed();
|
||||
|
||||
if (mngd) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
browser.displayPackage(props.managed.id, props.managed.provider);
|
||||
browser.displayPackage(mngd.id, mngd.provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ pub async fn find_managed_version(
|
|||
Ok(versions.0
|
||||
.iter()
|
||||
.find(|v| {
|
||||
if package_version.is_some() {
|
||||
return v.id == *package_version.as_ref().unwrap();
|
||||
}
|
||||
|
||||
let check_game_version = game_version
|
||||
.as_ref()
|
||||
.is_some_and(|gv| v.game_versions.iter().any(|gv2| *gv2 == *gv));
|
||||
|
|
@ -44,11 +48,7 @@ pub async fn find_managed_version(
|
|||
.as_ref()
|
||||
.is_some_and(|loader| v.loaders.iter().any(|l| *l == *loader));
|
||||
|
||||
let check_package_version = package_version
|
||||
.as_ref()
|
||||
.map_or(true, |pv| pv == &v.version_display);
|
||||
|
||||
check_game_version && check_loader && check_package_version
|
||||
check_game_version && check_loader
|
||||
})
|
||||
.ok_or_else(|| anyhow::anyhow!("no matching version found"))
|
||||
.cloned()?)
|
||||
|
|
|
|||
|
|
@ -109,11 +109,12 @@ pub struct CurseforgePackage {
|
|||
pub summary: String,
|
||||
// pub status: u32,
|
||||
pub download_count: u64,
|
||||
#[serde(default)]
|
||||
pub is_featured: bool,
|
||||
// pub primary_category_id: u32,
|
||||
pub categories: Vec<Category>,
|
||||
pub authors: Vec<Author>,
|
||||
pub logo: Logo,
|
||||
pub logo: Option<Logo>,
|
||||
// pub screenshots: Vec<Screenshot>,
|
||||
// pub main_file_id: u32,
|
||||
// pub latest_files: Vec<LatestFile>,
|
||||
|
|
@ -122,8 +123,8 @@ pub struct CurseforgePackage {
|
|||
pub date_created: DateTime<Utc>,
|
||||
pub date_modified: DateTime<Utc>,
|
||||
pub date_released: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
pub allow_mod_distribution: bool,
|
||||
// #[serde(default)]
|
||||
// pub allow_mod_distribution: bool,
|
||||
// pub game_popularity_rank: u32,
|
||||
// pub is_available: bool,
|
||||
pub thumbs_up_count: u32,
|
||||
|
|
@ -143,7 +144,7 @@ impl From<CurseforgePackage> for ManagedPackage {
|
|||
versions: vec![],
|
||||
game_versions: vec![],
|
||||
loaders: vec![],
|
||||
icon_url: Some(package.logo.url),
|
||||
icon_url: package.logo.and_then(|l| Some(l.url)),
|
||||
created: package.date_created,
|
||||
updated: package.date_modified,
|
||||
client: crate::store::PackageSide::Unknown,
|
||||
|
|
@ -189,7 +190,7 @@ impl Into<SearchResult> for CurseforgePackage {
|
|||
server_side: crate::store::PackageSide::Unknown,
|
||||
project_type: self.package_type.into(),
|
||||
downloads: self.download_count,
|
||||
icon_url: self.logo.url,
|
||||
icon_url: self.logo.map_or(String::new(), |l| l.url),
|
||||
categories: vec![], // TODO
|
||||
display_categories: vec![],
|
||||
versions: vec![],
|
||||
|
|
@ -278,6 +279,7 @@ pub struct ModFile {
|
|||
pub id: u32,
|
||||
pub game_id: u32,
|
||||
pub mod_id: u32,
|
||||
#[serde(default)]
|
||||
pub is_available: bool,
|
||||
pub display_name: String,
|
||||
pub file_name: String,
|
||||
|
|
@ -591,6 +593,25 @@ pub async fn get(id: u32) -> Result<CurseforgePackage> {
|
|||
)
|
||||
}
|
||||
|
||||
pub async fn get_multiple(ids: &[u32]) -> Result<Vec<CurseforgePackage>> {
|
||||
let mut map = serde_json::Map::new();
|
||||
map.insert("modIds".to_string(), ids.to_vec().into());
|
||||
|
||||
let json_body = serde_json::Value::Object(map);
|
||||
|
||||
Ok(
|
||||
serde_json::from_slice::<CFData<_>>(
|
||||
&fetch_advanced(
|
||||
Method::POST,
|
||||
"/v1/mods",
|
||||
None::<fn(&mut Url)>,
|
||||
None,
|
||||
Some(json_body),
|
||||
).await?)?
|
||||
.data,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_package_body(url: String) -> Result<String> {
|
||||
Ok(serde_json::from_slice::<CFData<_>>(&fetch(&url).await?)?.data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,13 +108,36 @@ impl Providers {
|
|||
|
||||
pub async fn get_multiple(&self, slug_or_ids: &[String]) -> Result<Vec<ManagedPackage>> {
|
||||
Ok(match self {
|
||||
Self::Modrinth => modrinth::get_multiple(slug_or_ids),
|
||||
Self::Curseforge => todo!(),
|
||||
}
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
Self::Modrinth => {
|
||||
if slug_or_ids.len() <= 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
modrinth::get_multiple(slug_or_ids)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
},
|
||||
Self::Curseforge => {
|
||||
let parsed_ids = slug_or_ids
|
||||
.iter()
|
||||
.filter_map(|id| id.parse::<u32>().ok())
|
||||
.collect::<Vec<u32>>();
|
||||
|
||||
if parsed_ids.len() <= 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let parsed_ids = parsed_ids.as_slice();
|
||||
|
||||
curseforge::get_multiple(parsed_ids)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_all_versions(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue