From 59183e9124ba1cae98b25f3be03583f7acd9972f Mon Sep 17 00:00:00 2001
From: LynithDev <61880709+LynithDev@users.noreply.github.com>
Date: Sun, 16 Feb 2025 11:03:38 +0100
Subject: [PATCH] feat: Launch button debounce + now in cluster sidebar
---
apps/frontend/package.json | 1 +
.../src/ui/components/LaunchButton.tsx | 49 ++++++++++++++++++
.../src/ui/components/base/Button.tsx | 2 +-
apps/frontend/src/ui/pages/Home.tsx | 17 ++-----
.../src/ui/pages/cluster/ClusterOverview.tsx | 16 ++----
.../src/ui/pages/cluster/ClusterRoot.tsx | 50 +++++++++++--------
pnpm-lock.yaml | 14 ++++++
7 files changed, 101 insertions(+), 48 deletions(-)
create mode 100644 apps/frontend/src/ui/components/LaunchButton.tsx
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 217363d7..f8bac6b1 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -25,6 +25,7 @@
"@solid-primitives/i18n": "^2.1.1",
"@solid-primitives/map": "^0.4.13",
"@solid-primitives/refs": "^1.0.8",
+ "@solid-primitives/scheduled": "^1.5.0",
"@solidjs/router": "^0.14.7",
"@t3-oss/env-core": "^0.11.0",
"@tanstack/solid-query": "^5.56.2",
diff --git a/apps/frontend/src/ui/components/LaunchButton.tsx b/apps/frontend/src/ui/components/LaunchButton.tsx
new file mode 100644
index 00000000..f8c480b4
--- /dev/null
+++ b/apps/frontend/src/ui/components/LaunchButton.tsx
@@ -0,0 +1,49 @@
+import type { Cluster } from '@onelauncher/client/bindings';
+import type { ButtonProps } from './base/Button';
+import { debounce } from '@solid-primitives/scheduled';
+import { PlayIcon } from '@untitled-theme/icons-solid';
+import { useLaunchCluster } from '~ui/hooks/useCluster';
+import useProcessor from '~ui/hooks/useProcessor';
+import useSettings from '~ui/hooks/useSettings';
+import { Match, splitProps, Switch } from 'solid-js';
+import Button from './base/Button';
+
+function LaunchButton(props: ButtonProps & { cluster: Cluster; iconOnly?: boolean }) {
+ const [split, rest] = splitProps(props, ['cluster']);
+ const innerLaunch = useLaunchCluster(() => split.cluster?.uuid);
+ // eslint-disable-next-line solid/reactivity -- a
+ const { running: runningProcesses } = useProcessor(split.cluster);
+ const { settings } = useSettings();
+
+ const launch = (e: MouseEvent) => {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ debounce(innerLaunch, 300)();
+ };
+
+ return (
+
+
+ }
+ disabled={settings().allow_parallel_running_clusters !== true && (runningProcesses()?.length || 0) > 0}
+ onClick={launch}
+ {...rest}
+ />
+
+
+
+
+ );
+}
+
+export default LaunchButton;
diff --git a/apps/frontend/src/ui/components/base/Button.tsx b/apps/frontend/src/ui/components/base/Button.tsx
index 38624da8..e1db537f 100644
--- a/apps/frontend/src/ui/components/base/Button.tsx
+++ b/apps/frontend/src/ui/components/base/Button.tsx
@@ -2,7 +2,7 @@ import type { Accessor, JSX, ParentProps } from 'solid-js';
import { createEffect, createSignal, mergeProps, on, splitProps, untrack } from 'solid-js';
import styles from './Button.module.scss';
-type ButtonProps = JSX.ButtonHTMLAttributes & ParentProps & {
+export type ButtonProps = JSX.ButtonHTMLAttributes & ParentProps & {
iconLeft?: JSX.Element;
iconRight?: JSX.Element;
buttonStyle?: 'primary' | 'secondary' | 'danger' | 'icon' | 'iconPrimary' | 'iconSecondary' | 'iconDanger' | 'ghost';
diff --git a/apps/frontend/src/ui/pages/Home.tsx b/apps/frontend/src/ui/pages/Home.tsx
index b2213b61..46a375af 100644
--- a/apps/frontend/src/ui/pages/Home.tsx
+++ b/apps/frontend/src/ui/pages/Home.tsx
@@ -9,10 +9,11 @@ import {
import { bridge } from '~imports';
import ClusterCover from '~ui/components/game/ClusterCover';
import PlayerHead from '~ui/components/game/PlayerHead';
+import LaunchButton from '~ui/components/LaunchButton';
import { useClusterCreator } from '~ui/components/overlay/cluster/ClusterCreationModal';
import { useLaunchCluster, useRecentCluster } from '~ui/hooks/useCluster';
-import useCommand from '~ui/hooks/useCommand';
+import useCommand from '~ui/hooks/useCommand';
import useProcessor from '~ui/hooks/useProcessor';
import { formatAsDuration, upperFirst } from '~utils';
import { For, onMount, Show } from 'solid-js';
@@ -178,8 +179,6 @@ function ClusterCard(props: Cluster) {
navigate(`/clusters/?id=${props.uuid}`);
}
- const launch = useLaunchCluster(() => props.uuid);
-
return (
<>
-
+
+
>
diff --git a/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx b/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx
index 82a0a42e..f3585fcd 100644
--- a/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx
+++ b/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx
@@ -2,14 +2,15 @@ import type { Cluster } from '@onelauncher/client/bindings';
import { useNavigate } from '@solidjs/router';
import * as dialog from '@tauri-apps/plugin-dialog';
import { open } from '@tauri-apps/plugin-shell';
-import { Edit02Icon, FolderIcon, ImagePlusIcon, LinkExternal01Icon, PlayIcon, Save01Icon, Share07Icon, Tool02Icon, Trash01Icon } from '@untitled-theme/icons-solid';
+import { Edit02Icon, FolderIcon, ImagePlusIcon, LinkExternal01Icon, Save01Icon, Share07Icon, Tool02Icon, Trash01Icon } from '@untitled-theme/icons-solid';
import { bridge } from '~imports';
import TextField from '~ui/components/base/TextField';
+import LaunchButton from '~ui/components/LaunchButton';
import Modal, { createModal, type ModalProps } from '~ui/components/overlay/Modal';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import SettingsRow from '~ui/components/SettingsRow';
import Sidebar from '~ui/components/Sidebar';
-import useClusterContext, { useLaunchCluster } from '~ui/hooks/useCluster';
+import useClusterContext from '~ui/hooks/useCluster';
import usePreventLeave from '~ui/hooks/usePreventLeave';
import useSettings from '~ui/hooks/useSettings';
import { formatAsDuration, upperFirst } from '~utils';
@@ -264,8 +265,6 @@ interface BannerProps {
}
function Banner(props: BannerProps) {
- const launch = useLaunchCluster(() => props.cluster.uuid);
-
async function launchFilePicker() {
const selected = await dialog.open({
multiple: false,
@@ -361,14 +360,7 @@ function Banner(props: BannerProps) {
disabled={true}
/>
- }
- onClick={launch}
- />
+
diff --git a/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx b/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx
index 8f935208..fdf557dd 100644
--- a/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx
+++ b/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx
@@ -1,6 +1,7 @@
import { type Navigator, Route, useIsRouting, useSearchParams } from '@solidjs/router';
import { EyeIcon, File06Icon, Globe04Icon, Image03Icon, PackagePlusIcon, Settings04Icon } from '@untitled-theme/icons-solid';
import PlayerHead from '~ui/components/game/PlayerHead';
+import LaunchButton from '~ui/components/LaunchButton';
import useClusterContext, { ClusterProvider } from '~ui/hooks/useCluster';
import useProcessor from '~ui/hooks/useProcessor';
import { supportsMods } from '~utils';
@@ -70,7 +71,6 @@ ClusterRoot.open = function (navigate: Navigator, uuid: string) {
function ClusterSidebar() {
const [cluster, { refetch: refetchCluster }] = useClusterContext();
const { running: runningProcesses } = useProcessor(cluster()!);
-
const isRouting = useIsRouting();
createEffect(() => {
@@ -79,28 +79,34 @@ function ClusterSidebar() {
});
return (
- , 'Overview', '/'],
- (supportsMods(cluster?.()) ? [, 'Mods', '/mods'] : undefined),
- [, 'Screenshots', '/screenshots'],
- [, 'Worlds', '/worlds'],
- [, 'Logs', '/logs'],
- [, 'Game Settings', '/settings'],
- ],
- ...(runningProcesses() && runningProcesses()!.length > 0
- ? {
- Running: runningProcesses()!.map((details, index) => {
- const icon = ; ;
+
+
+ , 'Overview', '/'],
+ (supportsMods(cluster()) ? [, 'Mods', '/mods'] : undefined),
+ [, 'Screenshots', '/screenshots'],
+ [, 'Worlds', '/worlds'],
+ [, 'Logs', '/logs'],
+ [, 'Game Settings', '/settings'],
+ ],
+ ...(runningProcesses() && runningProcesses()!.length > 0
+ ? {
+ Running: runningProcesses()!.map((details, index) => {
+ const icon = ; ;
- return [icon, `Process #${index + 1}`, '/game', ClusterGame.buildUrl(cluster()!.uuid, details)];
- }),
- }
- : {}),
- }}
- />
+ return [icon, `Process #${index + 1}`, '/game', ClusterGame.buildUrl(cluster()!.uuid, details)];
+ }),
+ }
+ : {}),
+ }}
+ />
+
+
+
+
);
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a3ccc724..078a732e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -106,6 +106,9 @@ importers:
'@solid-primitives/refs':
specifier: ^1.0.8
version: 1.0.8(solid-js@1.9.3)
+ '@solid-primitives/scheduled':
+ specifier: ^1.5.0
+ version: 1.5.0(solid-js@1.9.3)
'@solidjs/router':
specifier: ^0.14.7
version: 0.14.10(solid-js@1.9.3)
@@ -876,6 +879,7 @@ packages:
'@flowr/eslint-config@4.4.1':
resolution: {integrity: sha512-orefWDMD5tGnShE+fS5b5XjfU7X0vrRORLUmouyBuRVmHbhgZEXMn4EJF4AW/Ej8p2+AV3UlybrTjU3hBVsxLg==}
engines: {node: '>=20'}
+ deprecated: use `@flowr/eslint` instead.
peerDependencies:
'@eslint-react/eslint-plugin': ^1.12.2
'@prettier/plugin-xml': ^3.4.1
@@ -940,6 +944,7 @@ packages:
'@flowr/utils@4.4.1':
resolution: {integrity: sha512-9tWJ5TmFy0RaFtxOmuLvTmuluNoi7H1FbBxuIjO3mQatwRMz7ZAv+9uWRcB2+UUCr3IwbCcH4vC9nARZb9CsIg==}
engines: {node: '>=20'}
+ deprecated: use @flowr/utilities instead.
'@fontsource/poppins@5.1.0':
resolution: {integrity: sha512-tpLXlnNi2fwQjiipvuj4uNFHCdoLA8izRsKdoexZuEzjx0r/g1aKLf4ta6lFgF7L+/+AFdmaXFlUwwvmDzYH+g==}
@@ -1374,6 +1379,11 @@ packages:
peerDependencies:
solid-js: ^1.6.12
+ '@solid-primitives/scheduled@1.5.0':
+ resolution: {integrity: sha512-RVw24IRNh1FQ4DCMb3OahB70tXIwc5vH8nhR4nNPsXwUPQeuOkLsDI5BlxaPk0vyZgqw9lDpufgI3HnPwplgDw==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
'@solid-primitives/transition-group@1.0.5':
resolution: {integrity: sha512-G3FuqvL13kQ55WzWPX2ewiXdZ/1iboiX53195sq7bbkDbXqP6TYKiadwEdsaDogW5rPnPYAym3+xnsNplQJRKQ==}
peerDependencies:
@@ -5311,6 +5321,10 @@ snapshots:
dependencies:
solid-js: 1.9.3
+ '@solid-primitives/scheduled@1.5.0(solid-js@1.9.3)':
+ dependencies:
+ solid-js: 1.9.3
+
'@solid-primitives/transition-group@1.0.5(solid-js@1.9.3)':
dependencies:
solid-js: 1.9.3