From 1a099f6c3b67993904972a0da34e74f955908907 Mon Sep 17 00:00:00 2001 From: emirsassan Date: Thu, 19 Jun 2025 21:46:19 +0300 Subject: [PATCH] feat: cluster logs and ui tweaks --- Cargo.lock | 60 +++++++ Cargo.toml | 1 + apps/onelauncher/desktop/Cargo.toml | 1 + .../desktop/capabilities/default.json | 16 +- apps/onelauncher/desktop/src/lib.rs | 1 + apps/onelauncher/frontend/package.json | 1 + .../launcher/cluster/ClusterCreation.tsx | 49 ++---- .../frontend/src/routes/app/cluster/index.tsx | 59 ++++--- .../frontend/src/routes/app/cluster/logs.tsx | 150 ++++++++++++++++++ .../frontend/src/routes/app/index.tsx | 24 ++- .../src/routes/app/settings/index.tsx | 10 +- apps/onelauncher/frontend/src/utils/index.ts | 9 ++ packages/core/src/api/tauri/commands.rs | 9 ++ .../web_common/src/components/Dropdown.tsx | 1 + pnpm-lock.yaml | 10 ++ 15 files changed, 340 insertions(+), 61 deletions(-) create mode 100644 apps/onelauncher/frontend/src/routes/app/cluster/logs.tsx diff --git a/Cargo.lock b/Cargo.lock index 34bbd097..cb7859f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2512,6 +2512,25 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -3482,6 +3501,7 @@ dependencies = [ "tauri-plugin-clipboard-manager", "tauri-plugin-deep-link", "tauri-plugin-dialog", + "tauri-plugin-opener", "tauri-plugin-single-instance", "tauri-plugin-updater", "tauri-plugin-window-state", @@ -3512,6 +3532,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -3673,6 +3705,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -6010,6 +6048,28 @@ dependencies = [ "uuid 1.16.0", ] +[[package]] +name = "tauri-plugin-opener" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8983f50326d34437142a6d560b5c3426e91324297519b6eeb32ed0a1d1e0f2" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "open", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "url", + "windows 0.61.1", + "zbus", +] + [[package]] name = "tauri-plugin-single-instance" version = "2.2.3" diff --git a/Cargo.toml b/Cargo.toml index 70845882..90fed91f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ tauri-plugin-dialog = { version = "=2.2.1" } tauri-plugin-single-instance = { version = "=2.2.3" } tauri-plugin-updater = { version = "=2.7.1" } tauri-plugin-window-state = { version = "=2.2.2" } +tauri-plugin-opener = { version = "=2.3.0" } # code gen strum = { version = "=0.27.1", features = [ "derive" ] } diff --git a/apps/onelauncher/desktop/Cargo.toml b/apps/onelauncher/desktop/Cargo.toml index 1d613caa..188155e3 100644 --- a/apps/onelauncher/desktop/Cargo.toml +++ b/apps/onelauncher/desktop/Cargo.toml @@ -37,6 +37,7 @@ tauri-plugin-updater = { workspace = true } tauri-plugin-clipboard-manager = { workspace = true } tauri-plugin-dialog = { workspace = true } tauri-plugin-deep-link = { workspace = true } +tauri-plugin-opener = { workspace = true } # code gen taurpc = { workspace = true } diff --git a/apps/onelauncher/desktop/capabilities/default.json b/apps/onelauncher/desktop/capabilities/default.json index 40d2165e..f7557ce6 100644 --- a/apps/onelauncher/desktop/capabilities/default.json +++ b/apps/onelauncher/desktop/capabilities/default.json @@ -2,7 +2,9 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "capabilities for the main window", - "windows": ["*"], + "windows": [ + "*" + ], "permissions": [ "core:app:default", "core:webview:default", @@ -12,7 +14,6 @@ "core:resources:default", "core:tray:default", "core:window:default", - "core:window:allow-close", "core:window:allow-create", "core:window:allow-destroy", @@ -24,13 +25,20 @@ "core:window:allow-set-decorations", "core:window:allow-set-title-bar-style", "core:webview:allow-internal-toggle-devtools", - "core:event:default", "clipboard-manager:allow-write-text", "dialog:allow-open", "dialog:allow-save", "dialog:allow-confirm", "deep-link:default", - "updater:default" + "updater:default", + { + "identifier": "opener:allow-open-path", + "allow": [ + { + "path": "$DATA/OneLauncher" + } + ] + } ] } diff --git a/apps/onelauncher/desktop/src/lib.rs b/apps/onelauncher/desktop/src/lib.rs index 7bc4dad5..e6955510 100644 --- a/apps/onelauncher/desktop/src/lib.rs +++ b/apps/onelauncher/desktop/src/lib.rs @@ -70,6 +70,7 @@ async fn initialize_tauri(builder: tauri::Builder) -> LauncherResult .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_deep_link::init()) + .plugin(tauri_plugin_opener::init()) .menu(tauri::menu::Menu::new) .invoke_handler(router.into_handler()) .setup(move |app| { diff --git a/apps/onelauncher/frontend/package.json b/apps/onelauncher/frontend/package.json index 9469aff8..19fd82f7 100644 --- a/apps/onelauncher/frontend/package.json +++ b/apps/onelauncher/frontend/package.json @@ -24,6 +24,7 @@ "@tauri-apps/api": "^2.5.0", "@tauri-apps/plugin-clipboard-manager": "^2.2.2", "@tauri-apps/plugin-dialog": "^2.2.1", + "@tauri-apps/plugin-opener": "^2.3.0", "@tauri-apps/plugin-shell": "^2.2.1", "@untitled-theme/icons-react": "^0.14.0", "motion": "^12.10.5", diff --git a/apps/onelauncher/frontend/src/components/launcher/cluster/ClusterCreation.tsx b/apps/onelauncher/frontend/src/components/launcher/cluster/ClusterCreation.tsx index 67b33ae1..6d6cfa78 100644 --- a/apps/onelauncher/frontend/src/components/launcher/cluster/ClusterCreation.tsx +++ b/apps/onelauncher/frontend/src/components/launcher/cluster/ClusterCreation.tsx @@ -1,4 +1,4 @@ -import type { IngressPayload, Version, VersionType } from '@/bindings.gen'; +import type { GameLoader, IngressPayload, Version, VersionType } from '@/bindings.gen'; import type { JSX, RefAttributes } from 'react'; import DefaultBanner from '@/assets/images/default_banner.png'; import DefaultClusterBanner from '@/assets/images/default_instance_cover.jpg'; @@ -6,7 +6,7 @@ import LauncherIcon from '@/components/content/LauncherIcon'; import Modal from '@/components/overlay/Modal'; import ScrollableContainer from '@/components/ScrollableContainer'; import { bindings } from '@/main'; -import { LAUNCHER_IMPORT_TYPES } from '@/utils'; +import { LAUNCHER_IMPORT_TYPES, LOADERS, upperFirst } from '@/utils'; import { useCommand } from '@onelauncher/common'; import { Button, Dropdown, SelectList, TextField } from '@onelauncher/common/components'; import { useQueryClient } from '@tanstack/react-query'; @@ -36,7 +36,7 @@ export function NewClusterCreate() { const create = useCommand('createCluster', () => bindings.core.createCluster({ icon: DefaultClusterBanner, - mc_loader: 'vanilla', + mc_loader: formData.loader as GameLoader, mc_loader_version: null, mc_version: formData.clusterVersion, name: formData.clusterName, @@ -91,6 +91,7 @@ export function NewClusterCreate() { setFormData(prev => ({ ...prev, loader }))} onClusterNameChange={name => setFormData(prev => ({ ...prev, clusterName: name }))} onClusterVersionChange={version => setFormData(prev => ({ ...prev, clusterVersion: version }))} @@ -231,6 +232,7 @@ interface ClusterInformationProps { onClusterNameChange: (name: string) => void; clusterVersion: string; onClusterVersionChange: (version: string) => void; + onClusterLoaderChange: (loader: GameLoader) => void; } function ClusterInformation({ @@ -238,6 +240,7 @@ function ClusterInformation({ onClusterNameChange, clusterVersion, onClusterVersionChange, + onClusterLoaderChange, }: ClusterInformationProps) { const versions = useCommand('getGameVersions', bindings.core.getGameVersions); @@ -258,35 +261,17 @@ function ClusterInformation({ diff --git a/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx b/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx index 394b1559..141260f3 100644 --- a/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx +++ b/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx @@ -8,7 +8,9 @@ import { bindings } from '@/main'; 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 } from '@tauri-apps/api/path'; +import { open } from '@tauri-apps/plugin-dialog'; import { Edit02Icon, FolderIcon, ImagePlusIcon, Share07Icon, Tool02Icon, Trash01Icon } from '@untitled-theme/icons-react'; import { useState } from 'react'; import { twMerge } from 'tailwind-merge'; @@ -20,12 +22,12 @@ export const Route = createFileRoute('/app/cluster/')({ function RouteComponent() { const { id } = Route.useSearch(); - const [newCover, setNewCover] = useState(''); - const [newName, setNewName] = useState(''); - const [edit, setEdit] = useState(false); + const cluster = useCommand('getClusterById', () => bindings.core.getClusterById(Number(id.toString()) as unknown as bigint)); // dumbass fix ik - const cluster = useCommand('getClusterById', () => bindings.core.getClusterById(Number(id.toString()) as unknown as bigint)); + const [newCover, setNewCover] = useState(cluster.data?.icon_url as string); + const [newName, setNewName] = useState(cluster.data?.name as string); + const [edit, setEdit] = useState(false); const editing = useCommand('updateClusterById', () => bindings.core.updateClusterById(Number(id.toString()) as unknown as bigint, { icon_url: newCover, @@ -35,6 +37,11 @@ function RouteComponent() { subscribed: false, }); + bindings.events.process.on((e) => { + // eslint-disable-next-line no-console -- ok + console.log(e); + }); + function handleEditMode() { if (edit) editing.refetch(); @@ -126,22 +133,24 @@ function Banner({ cluster, editMode, setNewName, + setNewCover, }: BannerProps) { - // async function launchFilePicker() { - // const selected = await dialog.open({ - // multiple: false, - // directory: false, - // filters: [{ - // name: 'Image', - // extensions: ['png', 'jpg', 'jpeg', 'webp'], - // }], - // }); + async function launchFilePicker() { + const selected = await open({ + multiple: false, + directory: false, + filters: [{ + name: 'Image', + extensions: ['png', 'jpg', 'jpeg', 'webp'], + }], + }); - // if (selected === null) - // return; + if (selected === null) + return; + + setNewCover(selected); + } - // props.setNewCover(selected); - // } const launch = useCommand('launchCluster', () => bindings.core.launchCluster(cluster?.id as bigint, null), { enabled: false, subscribed: false, @@ -161,20 +170,29 @@ function Banner({ setNewName(name); } + const image = () => { + const url = cluster?.icon_url; + + if (url === undefined || url === null) + return DefaultInstancePhoto; + + return convertFileSrc(url); + }; + return (
{/* */} - +
@@ -222,8 +240,7 @@ function Banner({
diff --git a/apps/onelauncher/frontend/src/routes/app/cluster/logs.tsx b/apps/onelauncher/frontend/src/routes/app/cluster/logs.tsx new file mode 100644 index 00000000..63952f10 --- /dev/null +++ b/apps/onelauncher/frontend/src/routes/app/cluster/logs.tsx @@ -0,0 +1,150 @@ +import type { ProcessPayload } from '@/bindings.gen'; +import ScrollableContainer from '@/components/ScrollableContainer'; +import { bindings } from '@/main'; +import { Button, Show } from '@onelauncher/common/components'; +import { createFileRoute } from '@tanstack/react-router'; +import { LinkExternal01Icon, Upload01Icon } from '@untitled-theme/icons-react'; +import { useCallback, useEffect, useReducer } from 'react'; +import { + ListBox, + ListBoxItem, + ListLayout, + Virtualizer, +} from 'react-aria-components'; +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, 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; + } +} + +function RouteComponent() { + const { id: _id } = Route.useSearch(); + const [logs, dispatch] = useReducer(reducer, []); + + const addLog = useCallback((newLog: ProcessPayload) => { + dispatch({ type: LoggerActionType.ADD, payload: newLog }); + }, []); + + useEffect(() => { + bindings.events.process.on(addLog); + }, [addLog]); + + return ( + +

Overview

+ +
+
+
+
+

Logs

+
+ + + {/* + + {(log) => { + const pretty = log.split('/').pop(); + return ( + +
+ {pretty} +
+
+ ); + }} +
+
*/} + + +
+
+ + No logs were found.} + when={logs.length > 0} + > + + +
+
+
+
+
+ ); +} + +interface FormattedLogProps { + log: Array; +} + +function FormattedLog(props: FormattedLogProps) { + const { log } = props; + + return ( + <> + + + {(item) => { + if (item.type === 'Output') + return ( + + {item.output} + + ); + }} + + + + ); +} diff --git a/apps/onelauncher/frontend/src/routes/app/index.tsx b/apps/onelauncher/frontend/src/routes/app/index.tsx index 367c0391..45bcd1f8 100644 --- a/apps/onelauncher/frontend/src/routes/app/index.tsx +++ b/apps/onelauncher/frontend/src/routes/app/index.tsx @@ -1,4 +1,4 @@ -import type { ClusterModel } from '@/bindings.gen'; +import type { ClusterModel, IngressType } from '@/bindings.gen'; import DefaultBanner from '@/assets/images/default_banner.png'; import DefaultInstancePhoto from '@/assets/images/default_instance_cover.jpg'; import { NewClusterCreate } from '@/components/launcher/cluster/ClusterCreation'; @@ -7,7 +7,8 @@ import { bindings } from '@/main'; import { upperFirst } from '@/utils'; import { useCommand } from '@onelauncher/common'; import { Button, Show, TextField } from '@onelauncher/common/components'; -import { createFileRoute, Link } from '@tanstack/react-router'; +import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'; +import { convertFileSrc } from '@tauri-apps/api/core'; import { PlayIcon, Server01Icon } from '@untitled-theme/icons-react'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; @@ -26,6 +27,13 @@ hey future sassan here i guess the issue is solved idk function RouteComponent() { const result = useCommand('getClusters', bindings.core.getClusters); + bindings.events.ingress.on((e) => { + if (typeof e.ingress_type === 'object') + if ('PrepareCluster' in e.ingress_type) + // eslint-disable-next-line no-console -- ok + console.log(`Preparing cluster: ${e.ingress_type.PrepareCluster.cluster_name} %${e.percent}`); + }); + return (
@@ -149,6 +157,7 @@ function ClusterCard({ name, mc_loader, mc_version, + icon_url, }: ClusterModel) { const launch = useCommand('launchCluster', () => bindings.core.launchCluster(id, null), { enabled: false, @@ -162,6 +171,15 @@ function ClusterCard({ console.error(launch.error.message); }; + const image = () => { + const url = icon_url; + + if (url === null) + return DefaultInstancePhoto; + + return convertFileSrc(url); + }; + return ( <>
diff --git a/apps/onelauncher/frontend/src/routes/app/settings/index.tsx b/apps/onelauncher/frontend/src/routes/app/settings/index.tsx index 4d720b45..6b754f2c 100644 --- a/apps/onelauncher/frontend/src/routes/app/settings/index.tsx +++ b/apps/onelauncher/frontend/src/routes/app/settings/index.tsx @@ -4,6 +4,8 @@ import { bindings } from '@/main'; import { useCommand } from '@onelauncher/common'; 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 { FolderIcon, LinkExternal01Icon, XIcon } from '@untitled-theme/icons-react'; import Sidebar from './route'; @@ -14,6 +16,12 @@ export const Route = createFileRoute('/app/settings/')({ function RouteComponent() { const _result = useCommand('getGlobalProfile', bindings.core.getGlobalProfile); + const openLauncherDir = async () => { + const dir = await dataDir(); + + open(`${dir}/OneLauncher`); + }; + return (
@@ -52,7 +60,7 @@ function RouteComponent() { icon={} title="Launcher Folder" > -