chore: fix eslint warnings

This commit is contained in:
LynithDev 2025-07-22 12:45:22 +02:00
parent 5ac215a5c2
commit cd40d951f0
33 changed files with 272 additions and 210 deletions

View file

@ -20,7 +20,7 @@ interface TargetConfig {
}
interface BuildTarget {
updater: false | Updater;
standalone: TargetConfig[];
standalone: Array<TargetConfig>;
}
const osTargets = {
@ -67,7 +67,7 @@ const frontendBundle = 'apps/frontend/dist.tar.xz';
const updaterName = `OneLauncher-Updater-${os}-${arch}`;
const frontendName = `OneLauncher-Frontend-${os}-${arch}`;
const globFiles = async (pattern: string): Promise<string[]> => await (await glob.create(pattern)).glob();
const globFiles = async (pattern: string): Promise<Array<string>> => await (await glob.create(pattern)).glob();
async function uploadFrontend() {
if (!(await exists(frontendBundle))) {

View file

@ -8,8 +8,7 @@
"build": "vite build && tsc",
"serve": "vite preview",
"lint": "eslint",
"format": "prettier",
"check": "prettier --write . && eslint --fix"
"check": "eslint --fix"
},
"dependencies": {
"@fontsource/poppins": "catalog:",

View file

@ -1,10 +0,0 @@
// @ts-check
/** @type {import('prettier').Config} */
const config = {
semi: true,
singleQuote: true,
trailingComma: 'all',
};
export default config;

View file

@ -1,10 +1,11 @@
import { useEffect, type PropsWithChildren } from 'react';
import type { PropsWithChildren } from 'react';
import { LoaderSuspense, Navbar } from '@/components';
import { GameBackground } from '@/components/GameBackground';
import useAppShellStore from '@/stores/appShellStore';
import { AnimatedOutlet } from '@onelauncher/common/components';
import { createFileRoute, useRouter } from '@tanstack/react-router';
import { AnimatePresence } from 'motion/react';
import { useEffect } from 'react';
export const Route = createFileRoute('/app')({
component: RouteComponent,
@ -40,12 +41,12 @@ function AppShell({
const setPrevLocation = useAppShellStore(state => state.setPrevLocation);
useEffect(() => {
const unsub = router.subscribe("onBeforeNavigate", (e) => {
const unsub = router.subscribe('onBeforeNavigate', (e) => {
setPrevLocation(e.fromLocation ?? null);
});
return () => unsub();
}, [router]);
}, [router, setPrevLocation]);
return (
<div className="flex flex-col h-full w-full">

View file

@ -25,5 +25,5 @@
"skipLibCheck": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*.ts", "**/*.tsx", "prettier.config.js", "vite.config.js"]
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts"]
}

View file

@ -1,7 +1,7 @@
import type { Provider, SearchResult } from '@/bindings.gen';
import { abbreviateNumber, LOADERS, upperFirst } from '@/utils';
import { Show, Tooltip } from '@onelauncher/common/components';
import { Link, useNavigate } from '@tanstack/react-router';
import { Link } from '@tanstack/react-router';
import { Download01Icon } from '@untitled-theme/icons-react';
import { useMemo } from 'react';
import { Focusable } from 'react-aria-components';
@ -22,7 +22,7 @@ export function PackageGrid({ items, provider }: { items: Array<SearchResult>; p
}
export function PackageItem({ provider, ...item }: SearchResult & { provider: Provider }) {
const loaders = useMemo(() => item.categories.filter(cat => includes(LOADERS, cat)), []);
const loaders = useMemo(() => item.categories.filter(cat => includes(LOADERS, cat)), [item.categories]);
return (
<Link

View file

@ -36,7 +36,7 @@ export function BrowserProvider(props: any) {
categories: null,
game_versions: null,
loaders: null,
package_types: ["mod"]
package_types: ['mod'],
},
query: null,
limit: 18 as unknown as bigint,
@ -47,7 +47,7 @@ export function BrowserProvider(props: any) {
useEffect(() => {
if (query.filters?.categories || query.query)
navigate({ to: '/app/browser/search' });
}, [query]);
}, [navigate, query]);
const context = useMemo<BrowserControllerType>(() => ({
cluster,

View file

@ -1,10 +1,10 @@
import type { ClusterModel } from '@/bindings.gen';
import type { ModalProps } from '@/components/overlay/Modal';
import Modal from '@/components/overlay/Modal';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import { useEffect, useState } from 'react';
import Modal from '@/components/overlay/Modal';
import { Button, Dropdown } from '@onelauncher/common/components';
import { useEffect, useState } from 'react';
import { Heading } from 'react-aria-components';
export function useRecentCluster() {
@ -58,26 +58,29 @@ export function useClusters() {
}
type ChooseClusterModalProps = ModalProps & {
clusters?: ClusterModel[];
clusters?: Array<ClusterModel>;
confirmText?: string;
selected?: (clusters: ClusterModel[]) => number;
selected?: (clusters: Array<ClusterModel>) => number;
onSelected?: (cluster: ClusterModel) => void;
};
export function ChooseClusterModal({clusters: clusterList, selected: defaultSelected, onSelected, confirmText, ...props}: ChooseClusterModalProps) {
const clusters = useClusters()
export function ChooseClusterModal({ clusters: clusterList, selected: defaultSelected, onSelected, confirmText, ...props }: ChooseClusterModalProps) {
const clusters = useClusters();
const [selected, setSelected] = useState(0);
useEffect(() => {
if (!clusters) return;
const newSelected = defaultSelected?.(clusters)
if (newSelected) setSelected(newSelected)
}, [clusters])
if (!clusters)
return;
const newSelected = defaultSelected?.(clusters);
if (newSelected)
setSelected(newSelected);
}, [clusters, defaultSelected]);
function chooseCluster() {
if (clusters !== undefined && selected !== undefined) {
const cluster = clusters[selected];
if (clusters !== undefined) {
const cluster = clusters[selected] as ClusterModel | undefined;
if (cluster !== undefined)
onSelected?.(cluster);
}
@ -87,35 +90,35 @@ export function ChooseClusterModal({clusters: clusterList, selected: defaultSele
<Modal {...props}>
<div className="min-w-sm flex flex-col rounded-lg bg-page text-center p-4 gap-2">
<Heading slot='title' className='text-xl font-semibold'>Select a Cluster</Heading>
<Dropdown
onSelectionChange={(index) => {
setSelected(index as number)
}}
selectedKey={selected}
>
{clusters?.map((cluster, index) => (
<Dropdown.Item id={index} key={cluster.id}>
{cluster.name}
</Dropdown.Item>
))}
</Dropdown>
<Heading className="text-xl font-semibold" slot="title">Select a Cluster</Heading>
<Dropdown
onSelectionChange={(index) => {
setSelected(index as number);
}}
selectedKey={selected}
>
{clusters?.map((cluster, index) => (
<Dropdown.Item id={index} key={cluster.id}>
{cluster.name}
</Dropdown.Item>
))}
</Dropdown>
<div className='flex gap-2'>
<Button
color="secondary"
children="Close"
slot="close"
/>
<Button
children={confirmText ?? "Save"}
slot="close"
onClick={chooseCluster}
/>
</div>
<div className="flex gap-2">
<Button
children="Close"
color="secondary"
slot="close"
/>
<Button
children={confirmText ?? 'Save'}
onClick={chooseCluster}
slot="close"
/>
</div>
</div>
</Modal>
)
);
// return (
// <Modal

View file

@ -46,20 +46,20 @@ function usePagination(options: PaginationOptions) {
return pages;
}
function PaginationBtn(props: { page: number }) {
function PaginationBtn({ page: pageIndex }: { page: number }) {
return (
<Button
className="p-2 text-lg min-w-8"
color={props.page === page ? 'primary' : 'secondary'}
color={pageIndex === page ? 'primary' : 'secondary'}
onClick={() => {
if (props.page === page)
if (pageIndex === page)
return;
setPage(props.page);
setPage(pageIndex);
}}
>
<span className="text-trim">{props.page}</span>
<span className="text-trim">{pageIndex}</span>
</Button>
);
}

View file

@ -3,7 +3,7 @@
import type { Settings } from '@/bindings.gen';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
interface SettingsControllerType {
settings: Settings | undefined;
@ -12,7 +12,7 @@ interface SettingsControllerType {
setSettingsToSave: (settings: Partial<Settings> | null) => void;
settingsChanged: boolean;
setSettingsChanged: (changed: boolean) => void;
createSetting: <K extends keyof Settings, V = Settings[K]>(name: K, value: V) => [V, (value: V) => void];
createSetting: <K extends keyof Settings, V = Settings[K]>(name: K, value: V) => [V, (v: V) => void];
}
const SettingsContext = createContext<SettingsControllerType | null>(null);
@ -51,14 +51,14 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
}, []);
const saveChangedSettings = () => {
const saveChangedSettings = useCallback(() => {
if (!settings)
return;
writeQuery.refetch();
setSettingsChanged(false);
};
}, [settings, writeQuery]);
const handleSetSettingsToSave = useCallback((newSettings: Partial<Settings> | null) => {
setSettingsToSave(newSettings);
@ -86,7 +86,7 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
return [value, setValue];
}, [settings]);
const controller: SettingsControllerType = {
const controller: SettingsControllerType = useMemo(() => ({
settings,
saveChangedSettings,
settingsToSave,
@ -94,7 +94,7 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
settingsChanged,
setSettingsChanged,
createSetting,
};
}), [createSetting, handleSetSettingsToSave, saveChangedSettings, settings, settingsChanged, settingsToSave]);
if (settings === undefined)
return null;

View file

@ -32,6 +32,7 @@ function Featured() {
const navigate = useNavigate();
const featuredImageIndex = useMemo(() => featuredPackage.data?.gallery.findIndex(image => image.featured), [featuredPackage.data]);
const [selectedImage, setSelectedImage] = useState(featuredImageIndex ?? 0);
return (
<Show when={featuredPackage.isSuccess}>
<div className="flex flex-col gap-y-1">

View file

@ -3,6 +3,7 @@ import type { HTMLProps } from 'react';
import Modal from '@/components/overlay/Modal';
import { useBrowserContext, usePackageData, usePackageVersions } from '@/hooks/useBrowser';
import { ChooseClusterModal, useClusters } from '@/hooks/useCluster';
import usePagination from '@/hooks/usePagination';
import { bindings } from '@/main';
import { abbreviateNumber, formatAsRelative, PROVIDERS, upperFirst } from '@/utils';
import { useCommand } from '@onelauncher/common';
@ -16,7 +17,6 @@ import Markdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import { twMerge } from 'tailwind-merge';
import usePagination from '@/hooks/usePagination';
export const Route = createFileRoute('/app/browser/package/$provider/$slug')({
component: RouteComponent,
@ -26,10 +26,10 @@ function includes<T, TArray extends T>(list: { includes: (arg0: TArray) => boole
return list.includes(element as unknown as TArray);
}
function CustomA({ href, children, includeIcon, className, ...rest }: { href: string; children: any, includeIcon?: boolean } & HTMLProps<HTMLAnchorElement>) {
function CustomA({ href, children, includeIcon, className, ...rest }: { href: string; children: any; includeIcon?: boolean } & HTMLProps<HTMLAnchorElement>) {
return (
<a
className={twMerge("text-fg-primary underline", className)}
className={twMerge('text-fg-primary underline', className)}
href={href}
onClick={(e) => {
e.preventDefault();
@ -38,7 +38,7 @@ function CustomA({ href, children, includeIcon, className, ...rest }: { href: st
{...rest}
>
{children}
{includeIcon && <LinkExternal01Icon className='inline w-4 ml-1' />}
{includeIcon && <LinkExternal01Icon className="inline w-4 ml-1" />}
</a>
);
}
@ -56,10 +56,14 @@ function RouteComponent() {
if (!includes(PROVIDERS, provider))
throw new Error('Invalid provider');
const packageData = usePackageData(provider, slug, {});
const browserContext = useBrowserContext();
// const browserContext = useBrowserContext();
const packageContextValue = useMemo(() => ({
pkg: packageData.data,
}), [packageData.data]);
return (
<PackageContext value={{ pkg: packageData.data }}>
<PackageContext value={packageContextValue}>
<Show when={packageData.isSuccess && !packageData.isFetching}>
<div className="h-full flex flex-1 flex-row items-start gap-x-4">
<BrowserSidebar package={packageData.data!} />
@ -84,14 +88,14 @@ function RouteComponent() {
</div>
</TabPanel>
<TabPanel id="versions">
<Versions/>
<Versions />
</TabPanel>
<TabPanel className="flex flex-wrap gap-2 justify-center" id="gallery">
{packageData.data?.gallery.map(item => (
<Modal.Trigger key={item.url}>
<Pressable>
<div className="rounded-md overflow-hidden bg-component-bg-pressed outline-0 h-64 flex flex-col relative" aria-label='Loaders'>
<div aria-label="Loaders" className="rounded-md overflow-hidden bg-component-bg-pressed outline-0 h-64 flex flex-col relative">
<img className="h-full" src={item.thumbnail_url} />
{item.title && <div className="absolute w-full bottom-0 bg-component-bg-disabled/80 p-2">{item.title}</div>}
</div>
@ -113,17 +117,17 @@ function RouteComponent() {
function getPackageUrl(pkg: ManagedPackage): string {
switch (pkg.provider) {
case 'Modrinth': return `https://modrinth.com/project/${pkg.slug}`
case 'Curseforge': return `https://www.curseforge.com/minecraft/${pkg.package_type}s/${pkg.slug}`
case 'SkyClient': return ``
case 'Modrinth': return `https://modrinth.com/project/${pkg.slug}`;
case 'Curseforge': return `https://www.curseforge.com/minecraft/${pkg.package_type}s/${pkg.slug}`;
case 'SkyClient': return ``;
}
}
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 createdAt = useMemo(() => pkg.created ? new Date(pkg.created) : null, [pkg.created]);
const updatedAt = useMemo(() => pkg.updated ? new Date(pkg.updated) : null, [pkg.updated]);
const authors = useCommand('getUsersFromAuthor', () => bindings.core.getUsersFromAuthor(provider as Provider, pkg.author));
@ -163,18 +167,32 @@ function BrowserSidebar({ package: pkg }: { package: ManagedPackage }) {
<div className="flex flex-col rounded-lg bg-component-bg p-3">
<h4 className="text-fg-primary font-bold">Links</h4>
<div className='flex flex-col'>
<CustomA href={getPackageUrl(pkg)} includeIcon className='text-link hover:text-link-hover'>
<div className="flex flex-col">
<CustomA className="text-link hover:text-link-hover" href={getPackageUrl(pkg)} includeIcon>
{provider}
{' '}
Page
</CustomA>
{(Object.entries(pkg.links) as [keyof typeof pkg.links, typeof pkg.links[keyof typeof pkg.links]][]).filter(a => a[1]).map(link =>
typeof link[1] == "string"
? <CustomA href={link[1]} children={upperFirst(link[0])} className='text-link hover:text-link-hover' includeIcon />
: (link[1] as PackageDonationUrl[]).map(donationLink => <CustomA href={donationLink.url} className='text-link hover:text-link-hover' children={upperFirst(donationLink.id)} includeIcon />)
)}
{(Object.entries(pkg.links) as Array<[keyof typeof pkg.links, typeof pkg.links[keyof typeof pkg.links]]>).filter(a => a[1]).map(link =>
typeof link[1] == 'string'
? (
<CustomA
children={upperFirst(link[0])}
className="text-link hover:text-link-hover"
href={link[1]}
includeIcon
key={link[1]}
/>
)
: (link[1] as Array<PackageDonationUrl>).map(donationLink => (
<CustomA
children={upperFirst(donationLink.id)}
className="text-link hover:text-link-hover"
href={donationLink.url}
includeIcon
key={donationLink.url}
/>
)))}
</div>
</div>
@ -232,19 +250,20 @@ function BrowserSidebar({ package: pkg }: { package: ManagedPackage }) {
function InstallButton() {
const triggerRef = useRef<HTMLDivElement>(null);
const { provider, slug } = Route.useParams();
if(!includes(PROVIDERS, provider)) throw new Error("invalid provider")
if (!includes(PROVIDERS, provider))
throw new Error('invalid provider');
const [open, setOpen] = useState(false);
const clusters = useClusters();
const browserContext = useBrowserContext();
const { data: versions } = usePackageVersions(provider, slug, {
mc_versions: browserContext.cluster ? [browserContext.cluster.mc_version] : [],
loaders: browserContext.cluster ? [browserContext.cluster.mc_loader] : [],
limit: 1
limit: 1,
});
const version = useMemo(() => {
if (!versions || !browserContext.cluster)
return undefined;
return versions.items[0]
return versions.items[0];
}, [browserContext.cluster, versions]);
function download() {
@ -268,12 +287,12 @@ function InstallButton() {
browserContext.cluster
? version
? (
<span>
Download latest to
<br />
<span className="text-md font-semibold">{browserContext.cluster.name}</span>
</span>
)
<span>
Download latest to
<br />
<span className="text-md font-semibold">{browserContext.cluster.name}</span>
</span>
)
: `No matching version found`
: 'Select a Cluster'
}
@ -289,7 +308,7 @@ function InstallButton() {
aria-label="cluster" isOpen={open}
onOpenChange={setOpen}
onSelectionChange={(e) => {
const cluster = clusters?.find(cluster => cluster.id as unknown as number === e);
const cluster = clusters?.find(item => item.id as unknown as number === e);
if (cluster)
browserContext.setCluster(cluster);
}}
@ -359,50 +378,60 @@ function colorForType(type: string) {
}
}
function Versions(){
const {provider, slug} = Route.useParams()
if(!includes(PROVIDERS, provider)) throw new Error("invalid provider")
const [offset, setOffset] = useState(0)
function Versions() {
const { provider, slug } = Route.useParams();
if (!includes(PROVIDERS, provider))
throw new Error('invalid provider');
const [offset, setOffset] = useState(0);
const { data: versions } = usePackageVersions(provider, slug, {
limit: 20,
offset
offset,
});
const pagination = usePagination({
itemsCount: versions?.total as unknown as number,
itemsPerPage: versions?.limit as unknown as number
})
useEffect(()=>{
setOffset(pagination.offset)
},[pagination.offset])
useEffect(()=>{
pagination.reset()
},[versions?.total])
return <div className='flex flex-col'>
<div className='flex justify-between'>
<pagination.Navigation/>
itemsPerPage: versions?.limit as unknown as number,
});
useEffect(() => {
setOffset(pagination.offset);
}, [pagination.offset]);
useEffect(() => {
pagination.reset();
}, [pagination, versions?.total]);
return (
<div className="flex flex-col">
<div className="flex justify-between">
<pagination.Navigation />
</div>
<Table className="border-separate border-spacing-x-none border-spacing-y-1">
<TableHeader className="text-left">
<Column className="pr-2" />
<Column className="pr-4" isRowHeader>Name</Column>
<Column className="pr-4">Game Versions</Column>
<Column className="pr-4">Loaders</Column>
<Column className="pr-4">Created</Column>
<Column className="pr-4">Downloads</Column>
<Column className="pr-4" />
</TableHeader>
<TableBody className="">
{versions?.items.map(item =>
<VersionRow key={item.version_id} version={item} />)}
</TableBody>
</Table>
</div>
<Table className="border-separate border-spacing-x-none border-spacing-y-1">
<TableHeader className="text-left">
<Column className="pr-2"/>
<Column className="pr-4" isRowHeader>Name</Column>
<Column className="pr-4">Game Versions</Column>
<Column className="pr-4">Loaders</Column>
<Column className="pr-4">Created</Column>
<Column className="pr-4">Downloads</Column>
<Column className="pr-4"/>
</TableHeader>
<TableBody className="">
{versions?.items.map(item=>
<VersionRow version={item}/>
)}
</TableBody>
</Table>
</div>
);
}
function VersionRow({version}:{version:ManagedVersion}){
const {provider} = Route.useParams()
if(!includes(PROVIDERS, provider)) throw new Error("invalid provider")
function VersionRow({ version }: { version: ManagedVersion }) {
const { provider } = Route.useParams();
if (!includes(PROVIDERS, provider))
throw new Error('invalid provider');
return (
<Row className="my-2 bg-page-elevated px-4 [&>td]:py-4">
<Cell className="p-4 my-2 bg-component-bg rounded-l-xl">
@ -413,22 +442,22 @@ function VersionRow({version}:{version:ManagedVersion}){
</Tooltip>
</Cell>
<Cell className="p-4 px-2 bg-component-bg">{version.display_name}</Cell>
<Cell className="p-4 pl-0 bg-component-bg">{version.mc_versions.join(", ")}</Cell>
<Cell className="p-4 pl-0 bg-component-bg">{version.loaders.map(upperFirst).join(", ")}</Cell>
<Cell className="p-4 pl-0 bg-component-bg">{version.mc_versions.join(', ')}</Cell>
<Cell className="p-4 pl-0 bg-component-bg">{version.loaders.map(upperFirst).join(', ')}</Cell>
<Cell className="p-4 pl-0 bg-component-bg">{formatAsRelative(Date.parse(version.published))}</Cell>
<Cell className="p-4 pl-0 bg-component-bg">{version.downloads}</Cell>
<Cell className="p-4 pl-0 bg-component-bg rounded-r-xl">
<Modal.Trigger>
<Button
size='icon'
color='secondary'
<Button
children={<Download01Icon />}
color="secondary"
size="icon"
/>
<ChooseClusterModal confirmText='Download' onSelected={cluster=>downloadPackage(cluster, provider, version, true)}/>
<ChooseClusterModal confirmText="Download" onSelected={cluster => downloadPackage(cluster, provider, version, true)} />
</Modal.Trigger>
</Cell>
</Row>
)
);
}
function downloadPackage(cluster: ClusterModel, provider: Provider, version: ManagedVersion, skipCompatibility = false) {

View file

@ -1,11 +1,12 @@
import type { Filters } from '@/bindings.gen';
import type { PropsWithChildren } from 'react';
import ProviderIcon from '@/components/content/ProviderIcon';
import { BrowserProvider, useBrowserContext } from '@/hooks/useBrowser';
import { useClusters } from '@/hooks/useCluster';
import { PROVIDERS } from '@/utils';
import { browserCategories } from '@/utils/browser';
import { AnimatedOutlet, Dropdown, Show, TextField } from '@onelauncher/common/components';
import { createFileRoute, Outlet } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { SearchMdIcon } from '@untitled-theme/icons-react';
import { useEffect, useState } from 'react';
@ -27,7 +28,7 @@ function RouteComponent() {
);
}
export function BrowserLayout(props: any) {
export function BrowserLayout({ children }: PropsWithChildren) {
return (
<div className="relative h-full flex flex-1 flex-col items-center gap-2">
<div className="h-full w-full max-w-screen-2xl flex flex-1 flex-col items-center gap-y-2">
@ -42,7 +43,7 @@ export function BrowserLayout(props: any) {
<div className="h-full flex flex-col gap-y-4">
<div className="h-full flex-1">
{props.children}
{children}
</div>
</div>
@ -56,11 +57,14 @@ 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 } });
// eslint-disable-next-line react-hooks/exhaustive-deps -- loop
}, [context.cluster]);
return (
<div className="flex flex-col gap-y-4">
<div className="flex flex-col gap-y-4">
@ -68,7 +72,7 @@ function BrowserSidebar() {
<h6 className="my-1 uppercase opacity-60">Active Cluster</h6>
<Dropdown
onSelectionChange={(id) => {
const cluster = clusters?.find(cluster => cluster.id.toString() === id);
const cluster = clusters?.find(item => item.id.toString() === id);
context.setCluster(cluster);
}}
selectedKey={context.cluster?.id.toString()}
@ -107,7 +111,7 @@ const defaultFilters: Filters = {
function BrowserCategories() {
const context = useBrowserContext();
const categories = browserCategories.byPackageType((context.query.filters?.package_types ?? ["mod"])[0], context.provider);
const categories = browserCategories.byPackageType((context.query.filters?.package_types ?? ['mod'])[0], context.provider);
function switchCategory(category: string) {
const newCategories = context.query.filters?.categories?.includes(category)

View file

@ -1,11 +1,9 @@
import type { Paginated, SearchResult } from '@/bindings.gen';
import type { PaginationOptions } from '@/hooks/usePagination';
import { PackageGrid } from '@/components/content/PackageItem';
import { useBrowserContext, useBrowserSearch } from '@/hooks/useBrowser';
import usePagination from '@/hooks/usePagination';
import { Show } from '@onelauncher/common/components';
import { createFileRoute } from '@tanstack/react-router';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { BrowserLayout } from './route';
export const Route = createFileRoute('/app/browser/search')({
@ -25,10 +23,12 @@ function RouteComponent() {
function Search() {
const context = useBrowserContext();
const search = useBrowserSearch(context.provider, context.query, {
});
useEffect(() => {
search.refetch();
}, [context.provider, context.query, context.cluster]);
}, [context.provider, context.query, context.cluster, search]);
return (
<>
@ -44,19 +44,23 @@ function Search() {
function Results({ results }: { results: Paginated<SearchResult> }) {
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]);
}, [context, pagination.offset, pagination.page]);
useEffect(() => {
if (oldTotal === results.total)
return;
pagination.reset();
setOldTotal(results.total);
}, [results.total]);
}, [oldTotal, pagination, results.total]);
return (
<div>
<div className="w-full flex justify-end my-2">

View file

@ -48,9 +48,7 @@ function RouteComponent() {
})();
}, [cluster.data?.folder_name]);
const openClusterDir = async () => {
openPath(launcherDir);
};
const openClusterDir = () => openPath(launcherDir);
async function handleEditMode() {
if (edit)

View file

@ -5,7 +5,7 @@ import { NewClusterCreate } from '@/components/launcher/cluster/ClusterCreation'
import { useRecentCluster } from '@/hooks/useCluster';
import { bindings } from '@/main';
import { formatAsDuration, upperFirst } from '@/utils';
import { useCommand, useCommandMut, useCommandSuspense } from '@onelauncher/common';
import { useCommand, useCommandMut } from '@onelauncher/common';
import { Button, Show } from '@onelauncher/common/components';
import { createFileRoute, Link } from '@tanstack/react-router';
import { convertFileSrc } from '@tauri-apps/api/core';

View file

@ -23,9 +23,7 @@ function RouteComponent() {
})();
}, []);
const openLauncherDir = async () => {
openPath(launcherDir);
};
const openLauncherDir = () => openPath(launcherDir);
const [discordRpc, setDiscordRpc] = createSetting('discord_enabled', settings?.discord_enabled);

View file

@ -86,27 +86,28 @@ function Sidebar(props: SidebarProps) {
return (
<div className="w-56 flex flex-col pr-2">
{Object.keys(links).map((section, i) => (
<div className="flex flex-col gap-y-2" key={i}>
{Object.keys(links).map(section => (
<div className="flex flex-col gap-y-2" key={section}>
<div>
<p className="m-1.5 mt-5 text-xs text-fg-secondary font-medium">{section.toUpperCase()}</p>
<div className="flex flex-col gap-y-1 fill-fg-primary text-fg-primary font-medium">
{links[section].map((link, i) => {
// eslint yaps too much tbh
if (!link)
{links[section].map((entry) => {
if (!entry)
return '';
const [Component, label, link, params] = entry;
return (
<a
className={
`px-3 py-1 rounded-md text-md border hover:bg-component-bg-hover active:bg-component-bg-pressed ${isActive(link[2], link[3]) ? 'bg-component-bg border-component-bg-pressed' : 'border-transparent'}`
`px-3 py-1 rounded-md text-md border hover:bg-component-bg-hover active:bg-component-bg-pressed ${isActive(link, params) ? 'bg-component-bg border-component-bg-pressed' : 'border-transparent'}`
}
key={i}
onClick={() => goto(link[2], link[3])}
key={link}
onClick={() => goto(link, params)}
>
<span className="flex flex-row items-center gap-x-3 *:w-5">
{link[0]}
{link[1]}
{Component}
{label}
</span>
</a>
);

View file

@ -104,7 +104,7 @@ function RouteComponent() {
setIsForwardButtonEnabled(true);
}, [currentStepIndex, tasksStage, tasksCompleted]);
const next = useCallback(async () => {
const next = useCallback(() => {
const isLast = currentStepIndex === steps.length - 1;
if (isLast) {
@ -140,14 +140,17 @@ function RouteComponent() {
setTasksMessage('Starting setup...');
const tasksToRun = [
async () => {
() => new Promise<void>((resolve) => {
setTasksMessage(`Setting language to ${language?.lang}...`);
console.log('Language task completed for:', language?.lang);
},
async () => {
resolve();
}),
() => new Promise<void>((resolve) => {
if (importInstancesMap.size === 0) {
console.log('No instances selected for import.');
resolve();
return;
}
@ -165,7 +168,9 @@ function RouteComponent() {
throw e;
}
}
},
resolve();
}),
];
const results = await Promise.allSettled(tasksToRun.map(task => task()));

View file

@ -1,4 +1,4 @@
import type { PackageType, Provider } from "@/bindings.gen";
import type { PackageType, Provider } from '@/bindings.gen';
export const BROWSER_VIEWS: Array<string> = ['grid', 'list'] as const;

View file

@ -41,12 +41,12 @@ export function int32ToBigInt([high, low]: [number, number]): bigint {
}
export function randomString(len: number = 6, charSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') {
let randomString = '';
let str = '';
for (let i = 0; i < len; i++) {
const randomPos = Math.floor(Math.random() * charSet.length);
randomString += charSet.substring(randomPos, randomPos + 1);
str += charSet.substring(randomPos, randomPos + 1);
}
return randomString;
return str;
}
export function abbreviateNumber(n: number | bigint, locale: string = 'en-US'): string {

View file

@ -27,5 +27,5 @@
"skipLibCheck": true,
"noUncheckedSideEffectImports": true
},
"include": ["**/*.ts", "**/*.tsx", "vite.config.js"]
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts"]
}

View file

@ -23,6 +23,22 @@ export default defineConfig(
return config;
}),
{
ignores: [
'apps/onelauncher/old_frontend',
'apps/*/desktop/capabilities',
'apps/*/desktop/gen',
'**/*.gen.*',
],
// overrides: [
// {
// files: ['vite.config.js'],
// parserOptions: {
// project: null, // disables type-aware linting for this file
// },
// },
// ],
},
{
rules: {
'sort-imports': 'off', // Replaced by perfectionist/sort-named-imports',
@ -31,7 +47,14 @@ export default defineConfig(
'ts/no-use-before-define': 'off',
'style/function-paren-newline': ['error', 'consistent'],
'react/no-context-provider': 'off',
"prefer-const": "off" // disabled due to maximum call stack size
'prefer-const': 'off', // disabled due to maximum call stack size
'eslint-comments/no-unlimited-disable': 'off',
'eslint-comments/require-description': ['error', {
ignore: [
'eslint-disable',
],
}],
'no-shadow': 'off',
},
},
);

View file

@ -7,7 +7,7 @@ interface MappedCategoryItem {
id: string;
}
type MappingList = Record<string, MappedCategoryItem[]>;
type MappingList = Record<string, Array<MappedCategoryItem>>;
const packageTypes = ['mod', 'modpack', 'shader', 'resourcepack'] as const;
type PackageType = typeof packageTypes[number];
@ -43,7 +43,7 @@ async function fetchCategoriesCurseforge(packageType: PackageType): Promise<Mapp
parentCategoryId?: number;
};
const data = (await response.json() as { data: CFCategory[] }).data;
const data = (await response.json() as { data: Array<CFCategory> }).data;
// TODO: Add more calssIds
const classIds: Record<PackageType, number> = {
@ -79,7 +79,7 @@ async function fetchCategoriesModrinth(packageType: PackageType): Promise<Mappin
header: string;
}
const data = await response.json() as ModrinthCategory[];
const data = await response.json() as Array<ModrinthCategory>;
const mapping: MappingList = {};

View file

@ -29,7 +29,7 @@ if (cargoConfig.env && typeof cargoConfig.env === 'object')
function targetFilter(filter: 'b' | 't') {
const filters = filter === 'b' ? ['-b', '--bundles'] : ['-t', '--target'];
return (_: string, idx: number, args: string[]) => {
return (_: string, idx: number, args: Array<string>) => {
if (idx === 0)
return false;
const prev = args[idx - 1] ?? '';

View file

@ -45,9 +45,9 @@ export async function which(cmd: TemplateStringsArray): Promise<boolean> {
return type() === 'Windows_NT'
? where(cmd[0]!)
: Promise.any(
Array.from(new Set(process.env.PATH?.split(':')))
.map(p => fs.access(join(p, cmd[0]!), fs.constants.X_OK)),
).then(_ => true, _ => false);
Array.from(new Set(process.env.PATH?.split(':')))
.map(p => fs.access(join(p, cmd[0]!), fs.constants.X_OK)),
).then(_ => true, _ => false);
}
export async function awaitLock(file: string): Promise<void> {

View file

@ -47,7 +47,7 @@ export async function tauriUpdateKey(env: CheckedEnvironment): Promise<string |
return keys.publicKey;
}
export async function patchTauri(env: CheckedEnvironment, targets: string[], args: string[]): Promise<string[]> {
export async function patchTauri(env: CheckedEnvironment, targets: Array<string>, args: Array<string>): Promise<Array<string>> {
consola.start('patching tauri configuration...');
if (args.findIndex(a => ['--config', '-c'].includes(a)) !== -1)
throw new Error('custom tauri build configuration is not supported!');
@ -55,7 +55,7 @@ export async function patchTauri(env: CheckedEnvironment, targets: string[], arg
const osType = type();
const tauriPatch: {
build: {
features: string[];
features: Array<string>;
};
bundle: {
macOS: { minimumSystemVersion: string };
@ -107,10 +107,10 @@ export async function patchTauri(env: CheckedEnvironment, targets: string[], arg
if (
(targets.includes('aarch64-apple-darwin')
|| (targets.length === 0 && process.arch === 'arm64'))
&& (macOSStore.minimumVersion == null || semver.lt(
semver.coerce(macOSStore.minimumVersion)!,
semver.coerce(macOSStore.defaultArm64)!,
))
&& (macOSStore.minimumVersion == null || semver.lt(
semver.coerce(macOSStore.minimumVersion)!,
semver.coerce(macOSStore.defaultArm64)!,
))
) {
macOSStore.minimumVersion = macOSStore.defaultArm64;
consola.debug(`setting minimum macOS version to ${macOSStore.minimumVersion}...`);

View file

@ -1,13 +1,20 @@
[package]
name = "serde_json_path_to_error"
version = "0.2.0"
authors = [ "Polyfrost", "Ethan Brierley <ethanboxx@gmail.com>" ]
authors = [
"Polyfrost",
"Ethan Brierley <ethanboxx@gmail.com>"
]
license = "MIT/Apache-2.0"
readme = "README.md"
description = """
A drop in replacement for serde_json where detailed errors are the default
"""
keywords = [ "serde", "json", "serialization" ]
keywords = [
"serde",
"json",
"serialization"
]
documentation = "https://docs.rs/serde_json_path_to_error/"
repository = "https://github.com/eopb/serde_json_path_to_error"
edition = "2024"

View file

@ -1,4 +1,3 @@
/* eslint-disable eslint-comments/require-description -- i trust that one github discussion user on this */
/* eslint-disable react/no-unstable-default-props */
// Taken from https://github.com/TanStack/router/discussions/823#discussioncomment-12765059

View file

@ -1,5 +1,5 @@
import type { JSX, ReactNode } from 'react';
import React, { createContext, useContext } from 'react';
import type { JSX } from 'react';
import React, { createContext, useCallback, useContext, useMemo } from 'react';
interface SelectListContextType {
onSelect: (value: string) => void;
@ -30,7 +30,7 @@ export const SelectList: React.FC<SelectListProps> & { Item: React.FC<SelectList
multiple = false,
children,
}) => {
const handleSelect = (itemValue: string): void => {
const handleSelect = useCallback((itemValue: string): void => {
if (multiple) {
const currentValues = Array.isArray(value) ? value : [];
const newValues = currentValues.includes(itemValue)
@ -41,20 +41,20 @@ export const SelectList: React.FC<SelectListProps> & { Item: React.FC<SelectList
else {
onChange(itemValue);
}
};
}, [multiple, onChange, value]);
const isSelected = (itemValue: string): boolean => {
const isSelected = useCallback((itemValue: string): boolean => {
if (multiple)
return Array.isArray(value) && value.includes(itemValue);
return value === itemValue;
};
}, [multiple, value]);
const contextValue: SelectListContextType = {
const contextValue: SelectListContextType = useMemo(() => ({
onSelect: handleSelect,
isSelected,
multiple,
};
}), [handleSelect, isSelected, multiple]);
return (
<SelectListContext.Provider value={contextValue}>

View file

@ -36,7 +36,7 @@ catalog:
# ---
# dependencies
# ---
# react + libraries
react: ^19.0.0
react-aria-components: ^1.8.0