OneLauncher-Cracked/apps/frontend/src/ui/hooks/useBrowser.tsx
2024-10-08 18:06:43 +02:00

184 lines
5.1 KiB
TypeScript

import type { Cluster, ManagedPackage, 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 { ChooseClusterModal, useRecentCluster } from './useCluster';
import { tryResult } from './useCommand';
export type ProviderSearchOptions = ProviderSearchQuery & { provider: Providers };
export type PopularPackages = Record<Providers, SearchResult[]>;
interface BrowserControllerType {
cluster: Accessor<Cluster | undefined>;
setCluster: Setter<Cluster | undefined>;
displayBrowser: (cluster?: Cluster | undefined) => void;
displayPackage: (id: string, provider: Providers) => void;
displayClusterSelector: () => void;
search: () => void;
searchQuery: Accessor<ProviderSearchOptions>;
setSearchQuery: Setter<ProviderSearchOptions>;
packageType: Accessor<PackageType>;
setPackageType: Setter<PackageType>;
popularPackages: Accessor<PopularPackages | undefined>;
featuredPackage: Accessor<ManagedPackage | undefined>;
};
const BrowserContext = createContext() as Context<BrowserControllerType>;
export function BrowserProvider(props: ParentProps) {
const [popularPackages, setPopularPackages] = createSignal<PopularPackages | undefined>();
const [featuredPackage, setFeaturedPackage] = createSignal<ManagedPackage | undefined>();
// Used for the current "Browser Mode". It'll only show packages of the selected type
const [packageType, setPackageType] = createSignal<PackageType>('mod');
// Active cluster that results should be "focused" on
const [cluster, setCluster] = createSignal<Cluster>();
const [searchOptions, setSearchOptions] = createSignal<ProviderSearchOptions>({
provider: 'Modrinth',
query: '',
limit: 18, // Even multiples of 3 recommended for grid view (normally 3 columns, 2 columns on very small windows)
offset: 0,
categories: null,
game_versions: null,
loaders: null,
package_types: ['mod'],
open_source: null,
});
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}
/>
));
const controller: BrowserControllerType = {
cluster,
setCluster,
displayBrowser(cluster?: Cluster) {
setCluster(cluster);
},
displayPackage(id: string, provider: Providers) {
navigate(BrowserPackage.buildUrl({ id, provider }));
},
displayClusterSelector() {
modal.show();
},
search: () => navigate('/browser/search'),
searchQuery: searchOptions,
setSearchQuery: setSearchOptions,
packageType,
setPackageType,
popularPackages,
featuredPackage,
};
onMount(async () => {
const getOpts = (provider: Providers): ProviderSearchOptions => ({
provider,
query: '',
limit: 10,
offset: 0,
categories: null,
game_versions: null,
loaders: null,
package_types: ['mod'],
open_source: null,
});
const response = await Promise.allSettled(
PROVIDERS.map(provider => tryResult(() => bridge.commands.searchProviderPackages(provider, getOpts(provider)))),
);
const popularPackages = response.reduce((acc, res, i) => {
const provider = PROVIDERS[i] as Providers;
if (res.status === 'fulfilled')
acc[provider] = res.value.results;
return acc;
}, {} 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(() => {
setCluster(recentCluster());
});
createEffect(on(cluster, (cluster) => {
if (cluster !== undefined)
setSearchOptions((prev) => {
const opts: ProviderSearchOptions = {
...prev,
game_versions: [cluster.meta.mc_version],
};
if (cluster.meta.loader !== undefined)
opts.loaders = [cluster.meta.loader];
return opts;
});
}));
return (
<BrowserContext.Provider value={controller}>
{props.children}
</BrowserContext.Provider>
);
}
export default function useBrowser() {
const controller = useContext(BrowserContext);
if (!controller)
throw new Error('useBrowserController should be called inside its BrowserProvider');
return controller;
}