feat(ui): Featured package
This commit is contained in:
parent
b4100a4a3c
commit
aa6c3f333b
7 changed files with 152 additions and 98 deletions
|
|
@ -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<Vec<onelauncher::package::content::FeaturedPackage>, String> {
|
||||
Ok(onelauncher::package::content::get_featured_packages().await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub fn get_program_info() -> Result<super::statics::ProgramInfo, String> {
|
||||
|
|
|
|||
|
|
@ -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<PackageType>;
|
||||
|
||||
popularPackages: Accessor<PopularPackages | undefined>;
|
||||
featuredPackage: Accessor<ManagedPackage | undefined>;
|
||||
featuredPackage: Resource<FeaturedPackage[]>;
|
||||
};
|
||||
|
||||
const BrowserContext = createContext() as Context<BrowserControllerType>;
|
||||
|
||||
export function BrowserProvider(props: ParentProps) {
|
||||
const [popularPackages, setPopularPackages] = createSignal<PopularPackages | undefined>();
|
||||
const [featuredPackage, setFeaturedPackage] = createSignal<ManagedPackage | undefined>();
|
||||
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<PackageType>('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(() => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<BrowserContent>
|
||||
<div class="flex flex-col gap-8">
|
||||
<Show when={browser.popularPackages() !== undefined && browser.featuredPackage() !== undefined}>
|
||||
<Featured package={browser.featuredPackage()!} />
|
||||
<Featured />
|
||||
|
||||
<For each={Object.entries(browser.popularPackages()!)}>
|
||||
{([provider, results]) => (
|
||||
<SearchResultsContainer
|
||||
collapsable
|
||||
header={provider}
|
||||
provider={provider as Providers}
|
||||
results={results}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
<Spinner.Suspense>
|
||||
<Show when={browser.popularPackages() !== undefined}>
|
||||
<For each={Object.entries(browser.popularPackages()!)}>
|
||||
{([provider, results]) => (
|
||||
<SearchResultsContainer
|
||||
collapsable
|
||||
header={provider}
|
||||
provider={provider as Providers}
|
||||
results={results}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</Spinner.Suspense>
|
||||
</div>
|
||||
</BrowserContent>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<h5 class="ml-2">Featured</h5>
|
||||
<div class="w-full flex flex-row overflow-hidden rounded-lg bg-page-elevated">
|
||||
<div class="w-full p-1">
|
||||
<img alt="" class="aspect-ratio-video w-full rounded-md object-cover object-center" src="" />
|
||||
</div>
|
||||
<div class="max-w-84 min-w-52 flex flex-col gap-y-1 p-4">
|
||||
<h2>{props.package.title}</h2>
|
||||
|
||||
<div class="w-fit flex flex-row items-center gap-x-1 rounded-lg bg-border/10 px-1.5 py-1 text-fg-primary transition hover:opacity-80">
|
||||
<OneConfigLogo class="h-3.5 w-3.5" />
|
||||
<span class="text-sm font-medium">OneConfig Integrated</span>
|
||||
<Show when={featured()}>
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<h5 class="ml-2">Featured</h5>
|
||||
<div class="w-full flex flex-row overflow-hidden rounded-lg bg-page-elevated">
|
||||
<div class="w-full p-1">
|
||||
<img alt={`${featured()?.title} thumbnail`} class="aspect-ratio-video h-full w-full rounded-md object-cover object-center" src={featured()?.thumbnail} />
|
||||
</div>
|
||||
<div class="max-w-84 min-w-52 flex flex-col gap-y-1 p-4">
|
||||
<h2>{featured()?.title}</h2>
|
||||
|
||||
<p class="mt-1 flex-1">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?</p>
|
||||
<Show when={featured()?.oneconfig}>
|
||||
<div class="w-fit flex flex-row items-center gap-x-1 rounded-lg bg-border/10 px-1.5 py-1 text-fg-primary transition hover:opacity-80">
|
||||
<OneConfigLogo class="h-3.5 w-3.5" />
|
||||
<span class="text-sm font-medium">OneConfig Integrated</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="flex flex-row justify-end">
|
||||
<Button
|
||||
buttonStyle="ghost"
|
||||
children="View Package"
|
||||
iconRight={<ChevronRightIcon />}
|
||||
onClick={open}
|
||||
/>
|
||||
<p class="mt-1 flex-1 leading-normal">{featured()?.description}</p>
|
||||
|
||||
<div class="flex flex-row justify-end">
|
||||
<Button
|
||||
buttonStyle="ghost"
|
||||
children="View Package"
|
||||
iconRight={<ChevronRightIcon />}
|
||||
onClick={open}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,85 +42,85 @@ export default defineConfig({
|
|||
},
|
||||
|
||||
colors: {
|
||||
white: 'rgba(var(--clr-white), <alpha-value>)',
|
||||
black: 'rgba(var(--clr-black), <alpha-value>)',
|
||||
white: 'rgba(var(--clr-white))',
|
||||
black: 'rgba(var(--clr-black))',
|
||||
|
||||
border: 'rgba(var(--clr-border), <alpha-value>)',
|
||||
border: 'rgba(var(--clr-border))',
|
||||
|
||||
fg: {
|
||||
primary: {
|
||||
DEFAULT: 'rgba(var(--clr-fg-primary), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-fg-primary-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-fg-primary-pressed), <alpha-value>)',
|
||||
disabled: 'rgba(var(--clr-fg-primary-disabled), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-fg-secondary-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-fg-secondary-pressed), <alpha-value>)',
|
||||
disabled: 'rgba(var(--clr-fg-secondary-disabled), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-brand-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-brand-pressed), <alpha-value>)',
|
||||
disabled: 'rgba(var(--clr-brand-disabled), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-onbrand-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-onbrand-pressed), <alpha-value>)',
|
||||
disabled: 'rgba(var(--clr-onbrand-disabled), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-danger-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-danger-pressed), <alpha-value>)',
|
||||
disabled: 'rgba(var(--clr-danger-disabled), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-success-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-success-pressed), <alpha-value>)',
|
||||
disabled: 'rgba(var(--clr-success-disabled), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-component-bg-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-component-bg-pressed), <alpha-value>)',
|
||||
disabled: 'rgba(var(--clr-component-bg-disabled), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
warn: 'rgba(var(--clr-code-warn), <alpha-value>)',
|
||||
error: 'rgba(var(--clr-code-error), <alpha-value>)',
|
||||
debug: 'rgba(var(--clr-code-debug), <alpha-value>)',
|
||||
trace: 'rgba(var(--clr-code-trace), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
hover: 'rgba(var(--clr-link-hover), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-link-pressed), <alpha-value>)',
|
||||
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), <alpha-value>)',
|
||||
elevated: 'rgba(var(--clr-page-elevated), <alpha-value>)',
|
||||
pressed: 'rgba(var(--clr-page-pressed), <alpha-value>)',
|
||||
DEFAULT: 'rgba(var(--clr-page))',
|
||||
elevated: 'rgba(var(--clr-page-elevated))',
|
||||
pressed: 'rgba(var(--clr-page-pressed))',
|
||||
},
|
||||
|
||||
secondary: 'rgba(var(--clr-secondary), <alpha-value>)',
|
||||
secondary: 'rgba(var(--clr-secondary))',
|
||||
},
|
||||
extend: {
|
||||
height: {
|
||||
|
|
|
|||
11
packages/client/src/bindings.ts
generated
11
packages/client/src/bindings.ts
generated
|
|
@ -524,6 +524,16 @@ export const commands = {
|
|||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getFeaturedPackages(): Promise<Result<FeaturedPackage[], string>> {
|
||||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<Vec<FeaturedPackage>> {
|
||||
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<Vec<String>>,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
/// <https://mclo.gs>/ API base url.
|
||||
pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue