chore: Start work on drag n drop

This commit is contained in:
LynithDev 2024-08-01 17:48:20 +02:00
parent 2ec1a04c8b
commit 5dcc24e682
5 changed files with 151 additions and 27 deletions

View file

@ -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<Vec<Cluster>, String> {
Ok(onelauncher::cluster::list(None).await?)
}
#[specta::specta]
#[tauri::command]
pub async fn get_clusters_grouped() -> Result<HashMap<String, Vec<Cluster>>, String> {
Ok(onelauncher::cluster::list_grouped(None).await?)
}
#[derive(Serialize, Deserialize, Type)]
pub struct CreateCluster {
name: String,

View file

@ -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,

View file

@ -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 (
<div class="flex flex-col h-full gap-y-4 text-fg-primary">
<Banner />
@ -54,17 +132,32 @@ function HomePage() {
</div>
</div>
<Switch>
<Match when={clusters()?.length === 0}>
<DragDropProvider
onDragOver={onDragOver}
onDragEnd={onDragEnd}
>
<DragDropSensors />
<Show
when={Object.keys(clusters() ?? {}).length === 0}
fallback={(
<For each={Object.entries(clusters() ?? {})}>
{([group, clusters]) => (
<ClusterGroup title={group} clusters={clusters} />
)}
</For>
)}
>
<div class="flex flex-col flex-1 gap-y-4 max-h-64 justify-center items-center">
<span class="text-lg font-bold uppercase text-fg-secondary">No clusters were found.</span>
<span class="text-xl font-bold">Create one now with the New Cluster button.</span>
</div>
</Match>
<Match when={clusters() !== undefined}>
<ClusterGroup title="Clusters" clusters={clusters()!} />
</Match>
</Switch>
</Show>
<DragOverlay>
{draggable => (
draggable?.node.cloneNode(true)
)}
</DragOverlay>
</DragDropProvider>
</div>
);
}
@ -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"
>
<div class="flex-1 relative overflow-hidden rounded-t-xl">
@ -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 (
<div class="flex flex-col gap-y-4">
<div ref={droppable} class="flex flex-col gap-y-4">
<h4>{props.title}</h4>
<div class="grid grid-cols-4 2xl:grid-cols-6 gap-4">
<For each={props.clusters}>{item => <ClusterCard {...item} />}</For>
<SortableProvider ids={ids()}>
<For each={props.clusters}>{item => <ClusterCard {...item} />}</For>
</SortableProvider>
</div>
</div>
);

View file

@ -60,6 +60,25 @@ pub async fn list(clear: Option<bool>) -> crate::Result<Vec<Cluster>> {
.collect())
}
/// get a Map of [`Cluster`] group names containing lists of [`Cluster`]s
#[tracing::instrument]
pub async fn list_grouped(clear: Option<bool>) -> crate::Result<HashMap<String, Vec<Cluster>>> {
// TODO: This can 100% be made better
let list = list(clear).await?;
let mut map = HashMap::<String, Vec<Cluster>>::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<Arc<RwLock<ProcessorChild>>> {

24
pnpm-lock.yaml generated
View file

@ -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: