chore: fix eslint warnings
This commit is contained in:
parent
5ac215a5c2
commit
cd40d951f0
33 changed files with 272 additions and 210 deletions
|
|
@ -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))) {
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
/** @type {import('prettier').Config} */
|
||||
const config = {
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -25,5 +25,5 @@
|
|||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "prettier.config.js", "vite.config.js"]
|
||||
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -48,9 +48,7 @@ function RouteComponent() {
|
|||
})();
|
||||
}, [cluster.data?.folder_name]);
|
||||
|
||||
const openClusterDir = async () => {
|
||||
openPath(launcherDir);
|
||||
};
|
||||
const openClusterDir = () => openPath(launcherDir);
|
||||
|
||||
async function handleEditMode() {
|
||||
if (edit)
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ function RouteComponent() {
|
|||
})();
|
||||
}, []);
|
||||
|
||||
const openLauncherDir = async () => {
|
||||
openPath(launcherDir);
|
||||
};
|
||||
const openLauncherDir = () => openPath(launcherDir);
|
||||
|
||||
const [discordRpc, setDiscordRpc] = createSetting('discord_enabled', settings?.discord_enabled);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -27,5 +27,5 @@
|
|||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "vite.config.js"]
|
||||
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -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] ?? '';
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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}...`);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ catalog:
|
|||
# ---
|
||||
# dependencies
|
||||
# ---
|
||||
|
||||
|
||||
# react + libraries
|
||||
react: ^19.0.0
|
||||
react-aria-components: ^1.8.0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue