feat: ui additions and testing valtio
This commit is contained in:
parent
ad45798ea2
commit
6fdd5e678f
11 changed files with 276 additions and 97 deletions
|
|
@ -35,6 +35,9 @@
|
|||
{
|
||||
"identifier": "opener:allow-open-path",
|
||||
"allow": [
|
||||
{
|
||||
"path": "$DATA/OneLauncher/**"
|
||||
},
|
||||
{
|
||||
"path": "$DATA/OneLauncher"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@
|
|||
"tailwindcss": "^4.0.6",
|
||||
"tailwindcss-animated": "^2.0.0",
|
||||
"tailwindcss-react-aria-components": "^2.0.0",
|
||||
"taurpc": "^1.8.1"
|
||||
"taurpc": "^1.8.1",
|
||||
"valtio": "^2.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/eslint-config": "^0.1.0",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ interface NotificationToastProps {
|
|||
|
||||
const fractionEnded = (fraction: number | undefined) => fraction !== undefined && fraction >= 0.97;
|
||||
|
||||
function NotificationToast({ notification, onRemove }: NotificationToastProps) {
|
||||
export function NotificationToast({ notification, onRemove }: NotificationToastProps) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isRemoving, setIsRemoving] = useState(false);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { NotificationData } from '@/hooks/useNotification';
|
|||
import useNotifications from '@/hooks/useNotification';
|
||||
import { Button, Popup } from '@onelauncher/common/components';
|
||||
import { XIcon } from '@untitled-theme/icons-react';
|
||||
import { NotificationToast } from '../NotificationToasts';
|
||||
|
||||
interface NotificationItemProps {
|
||||
id: string;
|
||||
|
|
@ -9,6 +10,7 @@ interface NotificationItemProps {
|
|||
onRemove: (id: string) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars -- deprecated
|
||||
function NotificationItem({ id, data, onRemove }: NotificationItemProps) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-3 bg-component-bg border border-component-border rounded-lg">
|
||||
|
|
@ -77,11 +79,7 @@ function NotificationPopup() {
|
|||
{notificationEntries.map(([id, data]) => (
|
||||
<div key={id}>
|
||||
<div className="p-2">
|
||||
<NotificationItem
|
||||
data={data}
|
||||
id={id}
|
||||
onRemove={removeNotification}
|
||||
/>
|
||||
<NotificationToast key={id} notification={{ ...data, timestamp: Date.now(), id }} onRemove={removeNotification} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ import ScrollableContainer from '@/components/ScrollableContainer';
|
|||
import SettingsRow from '@/components/SettingsRow';
|
||||
import useNotifications from '@/hooks/useNotification';
|
||||
import { bindings } from '@/main';
|
||||
import { formatAsDuration } from '@/utils';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { Button, Show, TextField } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { dataDir, join } from '@tauri-apps/api/path';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { openPath } from '@tauri-apps/plugin-opener';
|
||||
import { Edit02Icon, FolderIcon, ImagePlusIcon, Share07Icon, Tool02Icon, Trash01Icon } from '@untitled-theme/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import Sidebar from '../settings/route';
|
||||
|
||||
|
|
@ -37,6 +40,18 @@ function RouteComponent() {
|
|||
subscribed: false,
|
||||
});
|
||||
|
||||
const [launcherDir, setLauncherDir] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setLauncherDir(await join(await dataDir(), 'OneLauncher', 'clusters', cluster.data?.folder_name as string));
|
||||
})();
|
||||
}, [cluster.data?.folder_name]);
|
||||
|
||||
const openClusterDir = async () => {
|
||||
openPath(launcherDir);
|
||||
};
|
||||
|
||||
async function handleEditMode() {
|
||||
if (edit)
|
||||
await editing.refetch();
|
||||
|
|
@ -94,9 +109,10 @@ function RouteComponent() {
|
|||
children="Open"
|
||||
color="primary"
|
||||
isDisabled={false}
|
||||
onClick={openClusterDir}
|
||||
/>
|
||||
)}
|
||||
description="asdsad"
|
||||
description={launcherDir}
|
||||
disabled={false}
|
||||
icon={<FolderIcon />}
|
||||
title="Cluster Folder"
|
||||
|
|
@ -207,10 +223,8 @@ function Banner({
|
|||
|
||||
const url = cluster?.icon_url;
|
||||
|
||||
if (!url) {
|
||||
console.warn('No icon URL, using default');
|
||||
if (!url)
|
||||
return DefaultInstancePhoto;
|
||||
}
|
||||
|
||||
return convertFileSrc(url);
|
||||
};
|
||||
|
|
@ -272,7 +286,7 @@ function Banner({
|
|||
>
|
||||
Played for
|
||||
{' '}
|
||||
<b>{cluster?.overall_played || 0}</b>
|
||||
<b>{formatAsDuration(cluster?.overall_played || 0)}</b>
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,57 +1,52 @@
|
|||
import type { ProcessPayload } from '@/bindings.gen';
|
||||
import ScrollableContainer from '@/components/ScrollableContainer';
|
||||
import { bindings } from '@/main';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { Button, Show } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { dataDir, join } from '@tauri-apps/api/path';
|
||||
import { openPath } from '@tauri-apps/plugin-opener';
|
||||
import { LinkExternal01Icon, Upload01Icon } from '@untitled-theme/icons-react';
|
||||
import { useCallback, useEffect, useReducer } from 'react';
|
||||
import {
|
||||
ListBox,
|
||||
ListBoxItem,
|
||||
ListLayout,
|
||||
Virtualizer,
|
||||
} from 'react-aria-components';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import { useEffect } from 'react';
|
||||
import { proxy, useSnapshot } from 'valtio';
|
||||
import Sidebar from '../settings/route';
|
||||
|
||||
export const Route = createFileRoute('/app/cluster/logs')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
enum LoggerActionType {
|
||||
ADD,
|
||||
}
|
||||
|
||||
interface LoggerAction {
|
||||
type: LoggerActionType;
|
||||
payload: ProcessPayload;
|
||||
}
|
||||
|
||||
function reducer(state: Array<ProcessPayload>, action: LoggerAction) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
// eslint-disable-next-line ts/no-unnecessary-condition -- oh please shut the fuck up i dont want to listen your yapping anymore
|
||||
case LoggerActionType.ADD:
|
||||
return [...state, payload];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
const store = proxy({
|
||||
logs: [] as Array<ProcessPayload>,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { id: _id } = Route.useSearch();
|
||||
const [logs, dispatch] = useReducer(reducer, []);
|
||||
const cluster = useCommand('getClusterById', () => bindings.core.getClusterById(Number(_id.toString()) as unknown as bigint));
|
||||
const state = useSnapshot(store);
|
||||
|
||||
const addLog = useCallback((newLog: ProcessPayload) => {
|
||||
dispatch({ type: LoggerActionType.ADD, payload: newLog });
|
||||
}, []);
|
||||
const addLog = (newLog: ProcessPayload) => {
|
||||
store.logs.push(newLog);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
bindings.events.process.on(addLog);
|
||||
}, [addLog]);
|
||||
let cleanup: (() => void) | undefined;
|
||||
|
||||
bindings.events.process.on(addLog).then((e) => {
|
||||
cleanup = e;
|
||||
}).catch(console.error);
|
||||
|
||||
return () => {
|
||||
cleanup?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const openLogsFolder = async () => {
|
||||
openPath(await join(await dataDir(), 'OneLauncher', 'clusters', cluster.data?.folder_name as string, 'logs'));
|
||||
};
|
||||
|
||||
return (
|
||||
<Sidebar.Page>
|
||||
<h1>Overview</h1>
|
||||
<ScrollableContainer>
|
||||
<div className="h-full">
|
||||
<div className="flex flex-1 flex-col">
|
||||
|
|
@ -61,8 +56,8 @@ function RouteComponent() {
|
|||
<div className="flex flex-row gap-x-2">
|
||||
<Button
|
||||
color="secondary"
|
||||
// isDisabled={missingLogs()}
|
||||
// onClick={uploadAndOpenLog}
|
||||
// isDisabled={missingLogs()}
|
||||
// onClick={uploadAndOpenLog}
|
||||
>
|
||||
<Upload01Icon />
|
||||
{' '}
|
||||
|
|
@ -90,7 +85,7 @@ function RouteComponent() {
|
|||
|
||||
<Button
|
||||
color="primary"
|
||||
// onClick={openFolder}
|
||||
onClick={openLogsFolder}
|
||||
>
|
||||
<LinkExternal01Icon />
|
||||
{' '}
|
||||
|
|
@ -101,9 +96,10 @@ function RouteComponent() {
|
|||
|
||||
<Show
|
||||
fallback={<span>No logs were found.</span>}
|
||||
when={logs.length > 0}
|
||||
when={state.logs.length > 0}
|
||||
>
|
||||
<FormattedLog log={logs} />
|
||||
<FormattedLog log={state.logs} />
|
||||
{/* {JSON.stringify(state.logs)} */}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -114,37 +110,114 @@ function RouteComponent() {
|
|||
}
|
||||
|
||||
interface FormattedLogProps {
|
||||
log: Array<ProcessPayload>;
|
||||
log: ReadonlyArray<ProcessPayload>;
|
||||
}
|
||||
|
||||
function FormattedLog(props: FormattedLogProps) {
|
||||
const { log } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
layoutOptions={{
|
||||
rowHeight: 32,
|
||||
padding: 4,
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<ListBox
|
||||
aria-label="Virtualized ListBox"
|
||||
items={log}
|
||||
selectionMode="none"
|
||||
<div className="relative h-full flex flex-1 flex-col overflow-hidden rounded-lg bg-component-bg border border-component-border shadow-sm">
|
||||
|
||||
<div className="h-px bg-gradient-to-r from-transparent via-component-border to-transparent" />
|
||||
|
||||
<div className="h-full flex flex-1 overflow-auto font-medium font-mono text-sm">
|
||||
<OverlayScrollbarsComponent
|
||||
className="relative h-full flex-1"
|
||||
>
|
||||
{(item) => {
|
||||
if (item.type === 'Output')
|
||||
return (
|
||||
<ListBoxItem key={item.output}>
|
||||
{item.output}
|
||||
</ListBoxItem>
|
||||
);
|
||||
}}
|
||||
</ListBox>
|
||||
</Virtualizer>
|
||||
</>
|
||||
<code className="log-container whitespace-pre flex flex-col text-sm p-2 gap-0.5">
|
||||
{log.map((logLine, index) => {
|
||||
if (index === log.length - 1)
|
||||
return null;
|
||||
|
||||
// trickery fuckery
|
||||
const lineKey = `${logLine.type}-${index}-${(logLine.type === 'Output' ? logLine.output : '').slice(0, 50)}`;
|
||||
return <Line key={lineKey} line={logLine} />;
|
||||
})}
|
||||
</code>
|
||||
</OverlayScrollbarsComponent>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const REGEX_PATTERN = /\[(\d+:\d+:\d+)\] \[(.[^(\n\r/\u2028\u2029]*)\/(\w+)\]:? (?:\[(CHAT)\])?/;
|
||||
|
||||
export function Line(props: { line: ProcessPayload }) {
|
||||
const { line } = props;
|
||||
|
||||
const format = (logText: string) => {
|
||||
let processedLine = logText.replace(/§./g, '');
|
||||
const prefix = processedLine.match(REGEX_PATTERN);
|
||||
|
||||
const isEmpty = processedLine.trim() === '';
|
||||
|
||||
if (isEmpty)
|
||||
processedLine = '\n';
|
||||
|
||||
if (prefix === null)
|
||||
return (
|
||||
<span
|
||||
className={`
|
||||
block py-0.5 rounded-sm hover:bg-component-bg-hover transition-colors duration-150 select-text
|
||||
text-fg-secondary leading-relaxed
|
||||
${isEmpty ? 'h-5 hover:bg-transparent' : ''}
|
||||
`}
|
||||
>
|
||||
{processedLine}
|
||||
</span>
|
||||
);
|
||||
|
||||
const isChatMsg = prefix[4] === 'CHAT';
|
||||
const logLevel = prefix[3].toUpperCase();
|
||||
|
||||
const getLevelColor = (level: string) => {
|
||||
switch (level) {
|
||||
case 'INFO':
|
||||
return 'text-code-info';
|
||||
case 'DEBUG':
|
||||
return 'text-code-debug';
|
||||
case 'TRACE':
|
||||
return 'text-code-trace';
|
||||
case 'WARN':
|
||||
return 'text-code-warn';
|
||||
case 'ERROR':
|
||||
return 'text-code-error';
|
||||
default:
|
||||
return 'text-fg-primary';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`
|
||||
block py-0.5 rounded-sm hover:bg-component-bg-hover transition-colors duration-150 select-text
|
||||
${getLevelColor(logLevel)} leading-relaxed
|
||||
${isEmpty ? 'h-5 hover:bg-transparent' : ''}
|
||||
`}
|
||||
data-level={logLevel}
|
||||
{...(isChatMsg ? { 'data-chat': 'true' } : {})}
|
||||
{...(isEmpty ? { 'data-empty': 'true' } : {})}
|
||||
>
|
||||
<span className="text-fg-secondary opacity-60 font-medium text-xs mr-2 inline-block min-w-[4.5rem] text-right">
|
||||
{prefix[1]}
|
||||
</span>
|
||||
<span className="font-semibold opacity-90 text-xs mr-2">
|
||||
{`[${prefix[2]}/${prefix[3]}]`}
|
||||
</span>
|
||||
{isChatMsg && (
|
||||
<span className="bg-brand text-white px-1.5 py-0.5 rounded text-xs font-medium mr-2">
|
||||
CHAT
|
||||
</span>
|
||||
)}
|
||||
<span className={`${isChatMsg ? 'text-white font-medium' : ''} break-words`}>
|
||||
{processedLine.slice(prefix[0].length)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>{format(line.type === 'Output' ? line.output : '')}</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import DefaultInstancePhoto from '@/assets/images/default_instance_cover.jpg';
|
|||
import { NewClusterCreate } from '@/components/launcher/cluster/ClusterCreation';
|
||||
import useRecentCluster from '@/hooks/useCluster';
|
||||
import { bindings } from '@/main';
|
||||
import { upperFirst } from '@/utils';
|
||||
import { formatAsDuration, upperFirst } from '@/utils';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { Button, Show } from '@onelauncher/common/components';
|
||||
import { createFileRoute, Link } from '@tanstack/react-router';
|
||||
|
|
@ -56,35 +56,46 @@ function Banner() {
|
|||
// const launch = useLaunchCluster(() => cluster()?.uuid);
|
||||
// const navigate = useNavigate();
|
||||
|
||||
const image = () => {
|
||||
const url = cluster?.icon_url;
|
||||
|
||||
if (!url)
|
||||
return DefaultBanner;
|
||||
|
||||
return convertFileSrc(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative h-52 min-h-52 w-full overflow-hidden rounded-xl border border-component-border">
|
||||
<div className="absolute h-52 min-h-52 w-full">
|
||||
<img
|
||||
alt="Default Banner"
|
||||
className="top-0 left-0 h-full rounded-xl w-full object-cover"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src = DefaultBanner;
|
||||
}}
|
||||
src={cluster?.icon_url || DefaultBanner}
|
||||
/>
|
||||
<div className="linearBlur after:right-1/3">
|
||||
<img
|
||||
alt="Default Banner"
|
||||
className="top-0 left-0 h-full rounded-xl w-full object-cover"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src = DefaultBanner;
|
||||
}}
|
||||
src={image()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 h-full flex flex-col items-start justify-between px-8 py-6">
|
||||
<div className="theme-OneLauncher-Dark flex flex-col gap-y-2 text-fg-primary">
|
||||
<h1 className="text-2xl font-medium text-shadow-black text-shadow-2xs">Create a cluster</h1>
|
||||
<h1 className="text-2xl font-medium text-shadow-black text-shadow-2xs">{cluster?.name || 'Create a cluster'}</h1>
|
||||
<Show when={cluster !== undefined}>
|
||||
<p>
|
||||
You've played
|
||||
{' '}
|
||||
<strong>
|
||||
{cluster?.name}
|
||||
{cluster?.mc_version}
|
||||
{' '}
|
||||
{upperFirst(cluster?.mc_loader || 'Unknown')}
|
||||
</strong>
|
||||
{' '}
|
||||
for
|
||||
{' '}
|
||||
<strong>{cluster?.overall_played || 0}</strong>
|
||||
<strong>{formatAsDuration(cluster?.overall_played || 0)}</strong>
|
||||
.
|
||||
</p>
|
||||
</Show>
|
||||
|
|
@ -177,9 +188,6 @@ function ClusterCard({
|
|||
return convertFileSrc(url);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-console -- ok
|
||||
console.log(image());
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ import SettingsRow from '@/components/SettingsRow';
|
|||
import useSettings from '@/hooks/useSettings';
|
||||
import { Button, Switch } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { dataDir } from '@tauri-apps/api/path';
|
||||
import { openPath as open } from '@tauri-apps/plugin-opener';
|
||||
import { dataDir, join } from '@tauri-apps/api/path';
|
||||
import { openPath } from '@tauri-apps/plugin-opener';
|
||||
import { FolderIcon, LinkExternal01Icon } from '@untitled-theme/icons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Sidebar from './route';
|
||||
|
||||
export const Route = createFileRoute('/app/settings/')({
|
||||
|
|
@ -14,11 +15,16 @@ export const Route = createFileRoute('/app/settings/')({
|
|||
|
||||
function RouteComponent() {
|
||||
const { settings, createSetting } = useSettings();
|
||||
const [launcherDir, setLauncherDir] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setLauncherDir(await join(await dataDir(), 'OneLauncher'));
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const openLauncherDir = async () => {
|
||||
const dir = await dataDir();
|
||||
|
||||
open(`${dir}/OneLauncher`);
|
||||
openPath(launcherDir);
|
||||
};
|
||||
|
||||
const [discordRpc, setDiscordRpc] = createSetting('discord_enabled', settings?.discord_enabled);
|
||||
|
|
@ -60,7 +66,7 @@ function RouteComponent() {
|
|||
|
||||
<SettingsRow.Header>Folders and Files</SettingsRow.Header>
|
||||
<SettingsRow
|
||||
description="Unknown for now"
|
||||
description={launcherDir}
|
||||
icon={<FolderIcon />}
|
||||
title="Launcher Folder"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -13,3 +13,20 @@
|
|||
--radius-3xl: 1.5rem;
|
||||
--radius-4xl: 2rem;
|
||||
}
|
||||
|
||||
/* i dont know where to put this */
|
||||
.linearBlur::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
-webkit-backdrop-filter: blur(30);
|
||||
backdrop-filter: blur(30);
|
||||
|
||||
-webkit-mask: linear-gradient(270deg, transparent, black 50%);
|
||||
mask: linear-gradient(270deg, transparent, black 50%);
|
||||
|
||||
background: linear-gradient(270deg, transparent, rgba(0, 0, 0, 0.85) 80%);
|
||||
}
|
||||
|
|
@ -86,3 +86,35 @@ export function formatAsRelative(
|
|||
|
||||
return 'now';
|
||||
}
|
||||
|
||||
function convertSeconds(secondsInput: number): string {
|
||||
let remaining = secondsInput;
|
||||
|
||||
const hours = Math.floor(remaining / 3600);
|
||||
remaining %= 3600;
|
||||
|
||||
const minutes = Math.floor(remaining / 60);
|
||||
remaining %= 60;
|
||||
|
||||
const seconds = remaining;
|
||||
|
||||
const parts: Array<string> = [];
|
||||
|
||||
if (hours)
|
||||
parts.push(`${hours} hours`);
|
||||
if (minutes)
|
||||
parts.push(`${minutes} minutes`);
|
||||
if (seconds)
|
||||
parts.push(`${seconds} seconds`);
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
export function formatAsDuration(seconds: number | bigint | Date): string {
|
||||
if (seconds instanceof Date)
|
||||
seconds = seconds.getTime() / 1000;
|
||||
else
|
||||
seconds = Number(seconds);
|
||||
|
||||
return convertSeconds(seconds);
|
||||
}
|
||||
|
|
|
|||
27
pnpm-lock.yaml
generated
27
pnpm-lock.yaml
generated
|
|
@ -291,6 +291,9 @@ importers:
|
|||
taurpc:
|
||||
specifier: ^1.8.1
|
||||
version: 1.8.1
|
||||
valtio:
|
||||
specifier: ^2.1.5
|
||||
version: 2.1.5(@types/react@19.1.2)(react@19.1.0)
|
||||
devDependencies:
|
||||
'@tanstack/eslint-config':
|
||||
specifier: ^0.1.0
|
||||
|
|
@ -4902,6 +4905,9 @@ packages:
|
|||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
|
||||
proxy-compare@3.0.1:
|
||||
resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -5481,6 +5487,18 @@ packages:
|
|||
validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
|
||||
valtio@2.1.5:
|
||||
resolution: {integrity: sha512-vsh1Ixu5mT0pJFZm+Jspvhga5GzHUTYv0/+Th203pLfh3/wbHwxhu/Z2OkZDXIgHfjnjBns7SN9HNcbDvPmaGw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=18.0.0'
|
||||
react: '>=18.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
|
||||
vite-node@2.1.3:
|
||||
resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
|
@ -10944,6 +10962,8 @@ snapshots:
|
|||
|
||||
process@0.11.10: {}
|
||||
|
||||
proxy-compare@3.0.1: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
quansync@0.2.10: {}
|
||||
|
|
@ -11668,6 +11688,13 @@ snapshots:
|
|||
spdx-correct: 3.2.0
|
||||
spdx-expression-parse: 3.0.1
|
||||
|
||||
valtio@2.1.5(@types/react@19.1.2)(react@19.1.0):
|
||||
dependencies:
|
||||
proxy-compare: 3.0.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.2
|
||||
react: 19.1.0
|
||||
|
||||
vite-node@2.1.3(@types/node@22.7.9)(lightningcss@1.30.1)(sass@1.80.3):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue