Skip to content

Commit

Permalink
feat(ui): Featured package
Browse files Browse the repository at this point in the history
  • Loading branch information
LynithDev committed Dec 1, 2024
1 parent b4100a4 commit aa6c3f3
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 98 deletions.
9 changes: 8 additions & 1 deletion apps/desktop/src/api/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
23 changes: 12 additions & 11 deletions apps/frontend/src/ui/hooks/useBrowser.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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');
Expand Down Expand Up @@ -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(() => {
Expand Down
92 changes: 50 additions & 42 deletions apps/frontend/src/ui/pages/browser/BrowserMain.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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>
);
}

Expand Down
86 changes: 43 additions & 43 deletions apps/frontend/uno.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
11 changes: 11 additions & 0 deletions packages/client/src/bindings.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion packages/core/src/api/package/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>>,
Expand Down
Loading

0 comments on commit aa6c3f3

Please sign in to comment.