From db678b9a9d64e0e439ff7960b8808649c4e09ac6 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:26:55 +0200 Subject: [PATCH] feat: Cluster editing, took too long --- Cargo.toml | 5 +- apps/desktop/Cargo.toml | 2 + apps/desktop/build.rs | 1 + apps/desktop/capabilities/default.json | 3 +- apps/desktop/package.json | 3 +- .../permissions/onelauncher/default.toml | 1 + apps/desktop/src/api/commands.rs | 31 ++- apps/desktop/src/lib.rs | 1 + apps/desktop/tauri.conf.json | 8 +- apps/frontend/package.json | 1 + apps/frontend/src-isolation/index.js | 1 + apps/frontend/src/ui/components/Sidebar.tsx | 5 - .../src/ui/components/base/Button.module.scss | 10 +- .../src/ui/components/base/Button.tsx | 12 +- .../ui/components/base/TextField.module.scss | 6 +- .../src/ui/components/base/TextField.tsx | 21 +- .../src/ui/components/game/ClusterCover.tsx | 30 ++- apps/frontend/src/ui/hooks/useCluster.tsx | 12 +- .../src/ui/pages/cluster/ClusterOverview.tsx | 217 +++++++++++++++--- .../src/ui/pages/cluster/ClusterRoot.tsx | 5 +- packages/core/src/api/cluster/create.rs | 2 +- packages/core/src/api/cluster/mod.rs | 2 +- packages/core/src/store/clusters.rs | 16 +- pnpm-lock.yaml | 10 + 24 files changed, 324 insertions(+), 81 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abb54614..9ecfe366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ tauri = { version = "=2.0.0-beta.23", features = [ "rustls-tls", "tracing", "specta", - "uuid" + "uuid", + "protocol-asset" ] } tauri-utils = { version = "=2.0.0-beta.18" } tauri-build = { version = "=2.0.0-beta.18" } @@ -37,12 +38,14 @@ tauri-codegen = { version = "=2.0.0-beta.18" } tauri-macros = { version = "=2.0.0-beta.18" } tauri-plugin = { version = "=2.0.0-beta.18" } tauri-runtime = { version = "=2.0.0-beta.19" } + tauri-plugin-authenticator = { version = "2.0.0-beta" } tauri-plugin-shell = { version = "2.0.0-beta" } tauri-plugin-window-state = { version = "2.0.0-beta" } tauri-plugin-single-instance = { version = "2.0.0-beta" } tauri-plugin-clipboard-manager = { version = "2.1.0-beta" } tauri-plugin-updater = { version = "2.0.0-beta", features = [ "rustls-tls" ] } +tauri-plugin-dialog = { version = "2.0.0-beta" } # rspc is updated on the v0.x branch rspc = { version = "=0.2.0", features = [ "specta2" ] } diff --git a/apps/desktop/Cargo.toml b/apps/desktop/Cargo.toml index 90b8970e..829a380a 100644 --- a/apps/desktop/Cargo.toml +++ b/apps/desktop/Cargo.toml @@ -40,12 +40,14 @@ tauri-codegen = { workspace = true } tauri-macros = { workspace = true } tauri-plugin = { workspace = true } tauri-runtime = { workspace = true } + tauri-plugin-shell = { workspace = true } tauri-plugin-window-state = { workspace = true } tauri-plugin-single-instance = { workspace = true } tauri-plugin-updater = { workspace = true } tauri-plugin-authenticator = { workspace = true } tauri-plugin-clipboard-manager = { workspace = true } +tauri-plugin-dialog = { workspace = true } tauri-specta = { workspace = true } specta = { workspace = true } diff --git a/apps/desktop/build.rs b/apps/desktop/build.rs index 301bd282..b24a0d7a 100644 --- a/apps/desktop/build.rs +++ b/apps/desktop/build.rs @@ -14,6 +14,7 @@ fn main() { "set_default_user", // Cluster "create_cluster", + "edit_cluster", "remove_cluster", "get_cluster", "get_clusters", diff --git a/apps/desktop/capabilities/default.json b/apps/desktop/capabilities/default.json index b7275760..6ee2fb29 100644 --- a/apps/desktop/capabilities/default.json +++ b/apps/desktop/capabilities/default.json @@ -17,6 +17,7 @@ "window:allow-internal-toggle-maximize", "event:default", "shell:allow-open", - "clipboard-manager:allow-write-text" + "clipboard-manager:allow-write-text", + "dialog:allow-open" ] } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b6fc337c..47cdeba6 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -12,7 +12,8 @@ }, "scripts": { "build": "tauri build", - "dev": "tauri dev" + "dev": "tauri dev", + "tauri": "tauri" }, "dependencies": { "@onelauncher/core": "workspace:*", diff --git a/apps/desktop/permissions/onelauncher/default.toml b/apps/desktop/permissions/onelauncher/default.toml index 4955c8e6..7906611c 100644 --- a/apps/desktop/permissions/onelauncher/default.toml +++ b/apps/desktop/permissions/onelauncher/default.toml @@ -11,6 +11,7 @@ permissions = [ # Cluster "allow-create-cluster", + "allow-edit-cluster", "allow-remove-cluster", "allow-get-cluster", "allow-get-clusters", diff --git a/apps/desktop/src/api/commands.rs b/apps/desktop/src/api/commands.rs index 542c5753..22bb3d4d 100644 --- a/apps/desktop/src/api/commands.rs +++ b/apps/desktop/src/api/commands.rs @@ -1,10 +1,11 @@ use std::path::PathBuf; +use std::str::FromStr; use interpulse::api::minecraft::Version; use onelauncher::data::{Loader, ManagedPackage, MinecraftCredentials, PackageData, Settings}; use onelauncher::package::content; use onelauncher::store::{Cluster, ClusterPath}; -use onelauncher::{cluster, minecraft, processor, settings}; +use onelauncher::{cluster, minecraft, processor, settings, State}; use serde::{Deserialize, Serialize}; use specta::Type; use tauri::{AppHandle, Manager}; @@ -24,6 +25,7 @@ macro_rules! collect_commands { remove_user, // Cluster create_cluster, + edit_cluster, remove_cluster, get_cluster, get_clusters, @@ -81,6 +83,33 @@ pub async fn create_cluster(props: CreateCluster) -> Result { } } +#[specta::specta] +#[tauri::command] +pub async fn edit_cluster( + uuid: Uuid, + name: Option, + icon_path: Option, +) -> Result<(), String> { + let cluster = cluster::get_by_uuid(uuid, None).await?.ok_or("cluster does not exist")?; + + cluster::edit(&cluster.cluster_path(), move |cluster| { + if let Some(name) = name.clone() { + cluster.meta.name = name; + } + + async move { + Ok(()) + } + }).await?; + + let icon_path = icon_path.and_then(|x| PathBuf::from_str(x.as_str()).ok()); + cluster::edit_icon(&cluster.cluster_path(), icon_path.as_deref()).await?; + + State::sync().await?; + + Ok(()) +} + #[specta::specta] #[tauri::command] pub async fn remove_cluster(uuid: Uuid) -> Result<(), String> { diff --git a/apps/desktop/src/lib.rs b/apps/desktop/src/lib.rs index b28f2a50..065bfade 100644 --- a/apps/desktop/src/lib.rs +++ b/apps/desktop/src/lib.rs @@ -65,6 +65,7 @@ pub async fn run_app(setup: F) { .plugin(tauri_plugin_clipboard_manager::init()) .plugin(ext::updater::plugin()) .manage(ext::updater::State::default()) + .plugin(tauri_plugin_dialog::init()) // .plugin(tauri_plugin_window_state::Builder::default().build()) .plugin(api::init()) .menu(tauri::menu::Menu::new) diff --git a/apps/desktop/tauri.conf.json b/apps/desktop/tauri.conf.json index ecbe241f..496add4b 100644 --- a/apps/desktop/tauri.conf.json +++ b/apps/desktop/tauri.conf.json @@ -24,13 +24,11 @@ "enable": true, "scope": { "allow": [ - "$APPDATA/caches/icons/*", - "$APPCONFIG/caches/icons/*", - "$CONFIG/caches/icons/*", - "$APPDATA/db/**", + "$DATA/OneLauncher/metadata/caches/icons/*", + "$DATA/OneLauncher/db/**", "$RESOURCE/**" ], - "deny": ["$APPDATA/db/*.stronghold"] + "deny": ["$DATA/OneLauncher/db/*.stronghold"] } }, "freezePrototype": true, diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 3efe4d0c..e54c207c 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -27,6 +27,7 @@ "@tanstack/solid-query": "^5.51.15", "@tauri-apps/api": "^2.0.0-beta.13", "@tauri-apps/plugin-clipboard-manager": "github:tauri-apps/tauri-plugin-clipboard-manager#v2", + "@tauri-apps/plugin-dialog": "2.0.0-beta.7", "@tauri-apps/plugin-shell": "github:tauri-apps/tauri-plugin-shell#v2", "@thisbeyond/solid-dnd": "^0.7.5", "@thisbeyond/solid-select": "^0.15.0", diff --git a/apps/frontend/src-isolation/index.js b/apps/frontend/src-isolation/index.js index 1eb85f22..84a4327b 100644 --- a/apps/frontend/src-isolation/index.js +++ b/apps/frontend/src-isolation/index.js @@ -1,3 +1,4 @@ +// @ts-nocheck window.__TAURI_ISOLATION_HOOK__ = (payload) => { return payload; }; diff --git a/apps/frontend/src/ui/components/Sidebar.tsx b/apps/frontend/src/ui/components/Sidebar.tsx index 372e92fa..49a34e93 100644 --- a/apps/frontend/src/ui/components/Sidebar.tsx +++ b/apps/frontend/src/ui/components/Sidebar.tsx @@ -3,7 +3,6 @@ import type { JSX, ParentProps } from 'solid-js'; import { For, createEffect, splitProps } from 'solid-js'; type SidebarProps = ParentProps & { - state: Record; base: string; links: Record; }; @@ -16,10 +15,6 @@ function Sidebar(props: SidebarProps) { createEffect(() => { if (props.base.endsWith('/')) throw new Error('Base should not end with a slash'); - - Object.keys(props.state).forEach((key) => { - searchParams.set(key, props.state[key]); - }); }); function goto(href: string) { diff --git a/apps/frontend/src/ui/components/base/Button.module.scss b/apps/frontend/src/ui/components/base/Button.module.scss index 50b291db..164c42de 100644 --- a/apps/frontend/src/ui/components/base/Button.module.scss +++ b/apps/frontend/src/ui/components/base/Button.module.scss @@ -1,6 +1,14 @@ .button { @apply py-1.5 px-3 rounded-lg flex flex-row justify-center items-center gap-x-2.5; + &:disabled { + @apply bg-opacity-10! text-op-10! pointer-events-none; + + svg { + @apply fill-op-10! stroke-op-10!; + } + } + &__primary { @apply text-onbrand hover:text-onbrand-hover active:text-onbrand-pressed disabled:text-onbrand-disabled; @apply bg-brand hover:bg-brand-hover active:bg-brand-pressed disabled:bg-brand-disabled; @@ -17,7 +25,7 @@ } &__iconDanger { - @apply bg-op-10; + @apply bg-op-50; } &__ghost { diff --git a/apps/frontend/src/ui/components/base/Button.tsx b/apps/frontend/src/ui/components/base/Button.tsx index abeb5c4c..ff3be616 100644 --- a/apps/frontend/src/ui/components/base/Button.tsx +++ b/apps/frontend/src/ui/components/base/Button.tsx @@ -1,5 +1,5 @@ -import type { JSX, ParentProps } from 'solid-js'; -import { createSignal, mergeProps, splitProps } from 'solid-js'; +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 & { @@ -23,13 +23,15 @@ function Button(props: ButtonProps) { } type ButtonToggleProps = ButtonProps & { - defaultChecked?: boolean; + checked?: Accessor; onChecked?: (checked: boolean) => any; }; Button.Toggle = (props: ButtonToggleProps) => { - const [checked, setChecked] = createSignal(props.defaultChecked || false); - const [split, rest] = splitProps(props, ['defaultChecked', 'onChecked', 'onClick', 'class']); + const [split, rest] = splitProps(props, ['checked', 'onChecked', 'onClick', 'class']); + const [checked, setChecked] = createSignal(untrack(() => split.checked?.()) || false); + + createEffect(on(() => split.checked?.(), newValue => setChecked(newValue || false))); function toggle() { const newValue = !checked(); diff --git a/apps/frontend/src/ui/components/base/TextField.module.scss b/apps/frontend/src/ui/components/base/TextField.module.scss index 61e12298..f9b5848c 100644 --- a/apps/frontend/src/ui/components/base/TextField.module.scss +++ b/apps/frontend/src/ui/components/base/TextField.module.scss @@ -1,19 +1,19 @@ .textfield { @apply bg-component-bg [&:not(:focus-within)]:hover:bg-component-bg-hover focus-within:bg-component-bg-pressed disabled:bg-component-bg-disabled; @apply border border-gray-05 text-fg-primary fill-fg-primary; - @apply rounded-lg px-2.5 py-1.5 max-h-8; + @apply rounded-lg px-2.5 py-1.5 min-h-8; @apply flex flex-row items-center justify-start gap-x-1.5; @apply text-sm; &.invalid { @apply border-danger; } - + & input { &::selection { @apply bg-brand/30; } - + @apply bg-transparent outline-none appearance-none p-0 m-0 box-border w-full; } diff --git a/apps/frontend/src/ui/components/base/TextField.tsx b/apps/frontend/src/ui/components/base/TextField.tsx index daa743f9..0d1c657f 100644 --- a/apps/frontend/src/ui/components/base/TextField.tsx +++ b/apps/frontend/src/ui/components/base/TextField.tsx @@ -8,33 +8,34 @@ type TextFieldProps = { inputFilter?: (value: string) => boolean; onValidInput?: (value: string) => any; onValidSubmit?: (value: string) => any; + labelClass?: string; } & JSX.InputHTMLAttributes; function TextField(props: TextFieldProps) { - const [fieldProps, rest] = splitProps(props, ['iconLeft', 'iconRight', 'inputFilter', 'onValidInput', 'onValidSubmit']); + const [split, rest] = splitProps(props, ['iconLeft', 'iconRight', 'inputFilter', 'onValidInput', 'onValidSubmit', 'labelClass']); const [isValid, setIsValid] = createSignal(true); const id = createUniqueId(); function validate(e: Event & { currentTarget: HTMLInputElement }) { - if (!fieldProps.inputFilter) + if (!split.inputFilter) return; const value = e.currentTarget.value; - const valid = fieldProps.inputFilter(value); + const valid = split.inputFilter(value); setIsValid(valid); - if (valid && fieldProps.onValidInput) - fieldProps.onValidInput(value); + if (valid && split.onValidInput) + split.onValidInput(value); } function onSubmit(e: Event & { currentTarget: HTMLInputElement }) { - if (isValid() && fieldProps.onValidSubmit) - fieldProps.onValidSubmit(e.currentTarget.value); + if (isValid() && split.onValidSubmit) + split.onValidSubmit(e.currentTarget.value); } return ( -