From 5dcc24e682fd4bfe9a5a85a7fb62389f317042e9 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:48:20 +0200 Subject: [PATCH] chore: Start work on drag n drop --- apps/desktop/src/api/commands/cluster.rs | 9 +- apps/desktop/src/api/commands/mod.rs | 1 + apps/frontend/src/ui/pages/Home.tsx | 125 ++++++++++++++++++++--- packages/core/src/api/cluster/mod.rs | 19 ++++ pnpm-lock.yaml | 24 ++--- 5 files changed, 151 insertions(+), 27 deletions(-) diff --git a/apps/desktop/src/api/commands/cluster.rs b/apps/desktop/src/api/commands/cluster.rs index 4a3a2215..6e47ec6e 100644 --- a/apps/desktop/src/api/commands/cluster.rs +++ b/apps/desktop/src/api/commands/cluster.rs @@ -1,5 +1,4 @@ -use std::path::PathBuf; -use std::str::FromStr; +use std::{collections::HashMap, path::PathBuf, str::FromStr}; use onelauncher::cluster::content::logger; use onelauncher::cluster::{self}; @@ -25,6 +24,12 @@ pub async fn get_clusters() -> Result, String> { Ok(onelauncher::cluster::list(None).await?) } +#[specta::specta] +#[tauri::command] +pub async fn get_clusters_grouped() -> Result>, String> { + Ok(onelauncher::cluster::list_grouped(None).await?) +} + #[derive(Serialize, Deserialize, Type)] pub struct CreateCluster { name: String, diff --git a/apps/desktop/src/api/commands/mod.rs b/apps/desktop/src/api/commands/mod.rs index 57d74881..6c22866d 100644 --- a/apps/desktop/src/api/commands/mod.rs +++ b/apps/desktop/src/api/commands/mod.rs @@ -33,6 +33,7 @@ macro_rules! collect_commands { remove_cluster, get_cluster, get_clusters, + get_clusters_grouped, run_cluster, get_cluster_logs, get_cluster_log, diff --git a/apps/frontend/src/ui/pages/Home.tsx b/apps/frontend/src/ui/pages/Home.tsx index caeb0b94..891ff3e3 100644 --- a/apps/frontend/src/ui/pages/Home.tsx +++ b/apps/frontend/src/ui/pages/Home.tsx @@ -7,8 +7,21 @@ import { SearchMdIcon, } from '@untitled-theme/icons-solid'; import type { Accessor, Setter } from 'solid-js'; -import { For, Match, Switch, createSignal } from 'solid-js'; +import { For, Show, batch, createMemo, createSignal, untrack } from 'solid-js'; import { useNavigate } from '@solidjs/router'; +import { + DragDropProvider, + DragDropSensors, + type DragEventHandler, + DragOverlay, + type Draggable, + type Droppable, + SortableProvider, + closestCenter, + createDroppable, + createSortable, +} from '@thisbeyond/solid-dnd'; +import { mergeRefs } from '@solid-primitives/refs'; import image from '../../assets/images/header.png'; import ContextMenu from '../components/overlay/ContextMenu'; import Button from '../components/base/Button'; @@ -16,15 +29,42 @@ import Tag from '../components/base/Tag'; import TextField from '../components/base/TextField'; import ClusterCover from '~ui/components/game/ClusterCover'; import type { Cluster } from '~bindings'; -import useCommand from '~ui/hooks/useCommand'; +import useCommand, { tryResult } from '~ui/hooks/useCommand'; import { bridge } from '~imports'; import { upperFirst } from '~utils/primitives'; import { useClusterModalController } from '~ui/components/overlay/cluster/ClusterCreationModal'; function HomePage() { - const [clusters, { refetch }] = useCommand(bridge.commands.getClusters); + const [clusters, { refetch, mutate }] = useCommand(bridge.commands.getClustersGrouped); const controller = useClusterModalController(); + function setClusters(group: string, clusters: Cluster[]) { + mutate((prev) => { + if (prev === undefined) + prev = {}; + + prev[group] = clusters; + return prev; + }); + } + + const containerIds = () => Object.keys(clusters); + + const isContainer = (id: string) => containerIds().includes(id); + + function getContainer(id: string) { + const list = untrack(() => clusters()); + + if (list === undefined) + return undefined; + + for (const [key, items] of Object.entries(list)) + if (items.find(cluster => cluster.uuid === id)) + return key; + + return undefined; + }; + async function newCluster() { try { await controller.start(); @@ -36,6 +76,44 @@ function HomePage() { refetch(); } + const move = (draggable: Draggable, droppable: Droppable, onlyWhenChangingContainer: boolean) => { + // console.log('Draggable', draggable, 'Droppable', droppable); + const draggableContainer = getContainer(draggable.id); + const droppableContainer = isContainer(droppable.id) + ? droppable.id + : getContainer(droppable.id); + + if ( + draggableContainer != droppableContainer + || !onlyWhenChangingContainer + ) { + const containerItemIds = clusters()![droppableContainer]; + let index = containerItemIds.indexOf(droppable.id); + if (index === -1) + index = containerItemIds.length; + + batch(() => { + setClusters(draggableContainer, items => + items.filter(item => item !== draggable.id)); + setClusters(droppableContainer, items => [ + ...items.slice(0, index), + draggable.id, + ...items.slice(index), + ]); + }); + } + }; + + const onDragEnd: DragEventHandler = (event) => { + if (event.draggable && event.droppable) + move(event.draggable, event.droppable, false); + }; + + const onDragOver: DragEventHandler = (event) => { + if (event.draggable && event.droppable) + move(event.draggable, event.droppable, true); + }; + return (
@@ -54,17 +132,32 @@ function HomePage() {
- - + + + + {([group, clusters]) => ( + + )} + + )} + >
No clusters were found. Create one now with the New Cluster button.
-
- - - -
+ + + {draggable => ( + draggable?.node.cloneNode(true) + )} + + ); } @@ -170,6 +263,7 @@ function ClusterCard(props: Cluster) { const navigate = useNavigate(); const [pos, setPos] = createSignal({ x: 0, y: 0 }); const [contextMenuVisible, setContextMenuVisible] = createSignal(false); + const sortable = createSortable(props.uuid); let ref!: HTMLDivElement; function openClusterPage(_e: MouseEvent) { @@ -192,7 +286,7 @@ function ClusterCard(props: Cluster) { onClick={e => openClusterPage(e)} onMouseDown={e => onMouseDown(e)} onContextMenu={e => openContextMenu(e)} - ref={ref} + ref={mergeRefs(ref, sortable)} class="relative h-[152px] group flex flex-col rounded-xl bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed border border-gray-05" >
@@ -239,11 +333,16 @@ interface ClusterGroupProps { } function ClusterGroup(props: ClusterGroupProps) { + const droppable = createDroppable(props.title); + const ids = createMemo(() => props.clusters.map(cluster => cluster.uuid)); + return ( -
+

{props.title}

- {item => } + + {item => } +
); diff --git a/packages/core/src/api/cluster/mod.rs b/packages/core/src/api/cluster/mod.rs index 4640a9d1..9c4af75f 100644 --- a/packages/core/src/api/cluster/mod.rs +++ b/packages/core/src/api/cluster/mod.rs @@ -60,6 +60,25 @@ pub async fn list(clear: Option) -> crate::Result> { .collect()) } +/// get a Map of [`Cluster`] group names containing lists of [`Cluster`]s +#[tracing::instrument] +pub async fn list_grouped(clear: Option) -> crate::Result>> { + // TODO: This can 100% be made better + let list = list(clear).await?; + let mut map = HashMap::>::new(); + + for cluster in list { + let group = cluster.meta.group.clone().unwrap_or(String::from("Ungrouped")); + if let Some(items) = map.get_mut(&group) { + items.push(cluster); + } else { + map.insert(group, vec![cluster]); + } + } + + Ok(map) +} + /// run a Minecraft [`Cluster`] using the default credentials. #[tracing::instrument] pub async fn run(path: &ClusterPath) -> crate::Result>> { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c94e3d4f..bc52920c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,13 +95,13 @@ importers: version: 2.0.0-beta.16 '@tauri-apps/plugin-clipboard-manager': specifier: github:tauri-apps/tauri-plugin-clipboard-manager#v2 - version: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53 + version: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0 '@tauri-apps/plugin-dialog': specifier: 2.0.0-beta.8 version: 2.0.0-beta.8 '@tauri-apps/plugin-shell': specifier: github:tauri-apps/tauri-plugin-shell#v2 - version: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96 + version: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311 '@thisbeyond/solid-dnd': specifier: ^0.7.5 version: 0.7.5(solid-js@1.8.19) @@ -1268,16 +1268,16 @@ packages: engines: {node: '>= 10'} hasBin: true - '@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53': - resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53} - version: 2.1.0-beta.5 + '@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0': + resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0} + version: 2.1.0-beta.6 '@tauri-apps/plugin-dialog@2.0.0-beta.8': resolution: {integrity: sha512-6iVhEBceWYLUgX6ygBc4/yJ1o/D4YwgoAGt7ebFUTjEyIhp79PeFtE8OTjdyJbtVOc6bAcDeAn4+pbWtSw9/4w==} - '@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96': - resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96} - version: 2.0.0-beta.8 + '@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311': + resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311} + version: 2.0.0-beta.9 '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} @@ -4565,17 +4565,17 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.23 '@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.23 - '@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53': + '@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0': dependencies: - '@tauri-apps/api': 2.0.0-beta.15 + '@tauri-apps/api': 2.0.0-beta.16 '@tauri-apps/plugin-dialog@2.0.0-beta.8': dependencies: '@tauri-apps/api': 2.0.0-beta.16 - '@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96': + '@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311': dependencies: - '@tauri-apps/api': 2.0.0-beta.15 + '@tauri-apps/api': 2.0.0-beta.16 '@testing-library/dom@10.4.0': dependencies: