feat: Instance card dragging

This commit is contained in:
LynithDev 2024-03-17 15:12:50 +01:00
parent 0f5b035e17
commit 71c98255a1
5 changed files with 167 additions and 21 deletions

View file

@ -18,6 +18,7 @@
"@solidjs/router": "^0.13.0", "@solidjs/router": "^0.13.0",
"@tauri-apps/api": "2.0.0-beta.5", "@tauri-apps/api": "2.0.0-beta.5",
"@tauri-apps/plugin-shell": "2.0.0-beta.2", "@tauri-apps/plugin-shell": "2.0.0-beta.2",
"@thisbeyond/solid-dnd": "^0.7.5",
"@untitled-theme/icons-solid": "^0.6.0", "@untitled-theme/icons-solid": "^0.6.0",
"overlayscrollbars": "^2.6.1", "overlayscrollbars": "^2.6.1",
"overlayscrollbars-solid": "^0.5.3", "overlayscrollbars-solid": "^0.5.3",

12
pnpm-lock.yaml generated
View file

@ -17,6 +17,9 @@ dependencies:
'@tauri-apps/plugin-shell': '@tauri-apps/plugin-shell':
specifier: 2.0.0-beta.2 specifier: 2.0.0-beta.2
version: 2.0.0-beta.2 version: 2.0.0-beta.2
'@thisbeyond/solid-dnd':
specifier: ^0.7.5
version: 0.7.5(solid-js@1.8.15)
'@untitled-theme/icons-solid': '@untitled-theme/icons-solid':
specifier: ^0.6.0 specifier: ^0.6.0
version: 0.6.0(solid-js@1.8.15) version: 0.6.0(solid-js@1.8.15)
@ -1113,6 +1116,15 @@ packages:
'@tauri-apps/api': 2.0.0-beta.4 '@tauri-apps/api': 2.0.0-beta.4
dev: false dev: false
/@thisbeyond/solid-dnd@0.7.5(solid-js@1.8.15):
resolution: {integrity: sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw==}
engines: {node: '>=18.0.0', pnpm: '>=8.6.0'}
peerDependencies:
solid-js: ^1.5
dependencies:
solid-js: 1.8.15
dev: false
/@types/babel__core@7.20.5: /@types/babel__core@7.20.5:
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
dependencies: dependencies:

View file

@ -1,6 +1,6 @@
import { A } from '@solidjs/router'; import { A } from '@solidjs/router';
import { createSignal } from 'solid-js'; import { createSignal } from 'solid-js';
import { Bell01Icon, Cloud01Icon, Settings01Icon, TerminalBrowserIcon, TerminalIcon } from '@untitled-theme/icons-solid'; import { Bell01Icon, Cloud01Icon, Settings01Icon, TerminalBrowserIcon } from '@untitled-theme/icons-solid';
import useAccount from '../hooks/useAccount'; import useAccount from '../hooks/useAccount';
import PolyfrostFull from './logos/PolyfrostFull'; import PolyfrostFull from './logos/PolyfrostFull';
import AccountPopup from './overlay/AccountPopup'; import AccountPopup from './overlay/AccountPopup';

View file

@ -1,8 +1,88 @@
import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd';
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter, createSortable, useDragDropContext } from '@thisbeyond/solid-dnd';
import type { JSX } from 'solid-js';
import { For, createSignal } from 'solid-js';
declare module 'solid-js' {
// eslint-disable-next-line ts/no-namespace
namespace JSX {
interface Directives {
sortable: any;
}
}
}
interface ItemProps {
item: boolean | Id | Node | JSX.ArrayElement | null | undefined;
};
function Item(props: ItemProps) {
const id = () => props.item as Id;
// eslint-disable-next-line solid/reactivity
const sortable = createSortable(id());
// @ts-expect-error - `state` is not defined in the type definition
const [state] = useDragDropContext();
return (
<div
use:sortable
class="sortable"
classList={{
'opacity-25': sortable.isActiveDraggable,
'transition-transform': !!state.active.draggable,
}}
>
{props.item}
</div>
);
}
function List() {
const [items, setItems] = createSignal([1, 2, 3]);
const [activeItem, setActiveItem] = createSignal<number | null>(null);
const ids = () => items();
const onDragStart: DragEventHandler = ({ draggable }) => setActiveItem(draggable.id as number);
const onDragEnd: DragEventHandler = ({ draggable, droppable }) => {
if (draggable && droppable) {
const currentItems = ids();
const fromIndex = currentItems.indexOf(draggable.id as number);
const toIndex = currentItems.indexOf(droppable.id as number);
if (fromIndex !== toIndex) {
const updatedItems = currentItems.slice();
updatedItems.splice(toIndex, 0, ...updatedItems.splice(fromIndex, 1));
setItems(updatedItems);
}
}
};
return (
<DragDropProvider
onDragStart={onDragStart}
onDragEnd={onDragEnd}
collisionDetector={closestCenter}
>
<DragDropSensors />
<div class="column self-stretch">
<SortableProvider ids={ids()}>
<For each={items()}>{item => <Item item={item} />}</For>
</SortableProvider>
</div>
<DragOverlay>
<div class="sortable">{activeItem()}</div>
</DragOverlay>
</DragDropProvider>
);
}
function BrowserPage() { function BrowserPage() {
return ( return (
<h1> <div class="flex flex-col gap-y-4">
Browser <h1>Browser</h1>
</h1> <List />
</div>
); );
} }

View file

@ -6,7 +6,9 @@ import {
PlusIcon, PlusIcon,
SearchMdIcon, SearchMdIcon,
} from '@untitled-theme/icons-solid'; } from '@untitled-theme/icons-solid';
import { For, createEffect, createSignal } from 'solid-js'; import { For, createSignal } from 'solid-js';
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter, createSortable, useDragDropContext } from '@thisbeyond/solid-dnd';
import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd';
import image from '../../assets/images/header.png'; import image from '../../assets/images/header.png';
import Button from '../components/base/Button'; import Button from '../components/base/Button';
import Tag from '../components/base/Tag'; import Tag from '../components/base/Tag';
@ -61,14 +63,28 @@ interface InstanceCardProps {
name: string; name: string;
version: string; version: string;
client: game.Client; client: game.Client;
id: number;
mods?: number; mods?: number;
cover?: string; cover?: string;
lastPlayed?: number; lastPlayed?: number;
} }
declare module 'solid-js' {
// eslint-disable-next-line ts/no-namespace
namespace JSX {
interface Directives {
sortable: any;
}
}
}
function InstanceCard(props: InstanceCardProps) { function InstanceCard(props: InstanceCardProps) {
const id = () => props.id as Id;
// eslint-disable-next-line solid/reactivity
const sortable = createSortable(id());
return ( return (
<div class="h-[152px] group flex flex-col rounded-xl bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed border border-gray-0.05 overflow-hidden"> <div use:sortable={sortable} class="h-[152px] group flex flex-col rounded-xl bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed border border-gray-0.05 overflow-hidden">
<div class="flex-1 relative overflow-hidden"> <div class="flex-1 relative overflow-hidden">
<div <div
class="absolute h-full w-full group-hover:!scale-110 transition-transform" class="absolute h-full w-full group-hover:!scale-110 transition-transform"
@ -105,27 +121,44 @@ interface InstanceGroupProps {
} }
function InstanceGroup(props: InstanceGroupProps) { function InstanceGroup(props: InstanceGroupProps) {
const [sorted, setSorted] = createSignal<InstanceCardProps[]>([]); // eslint-disable-next-line solid/reactivity
const [instances, setInstances] = createSignal(props.instances);
const ids = () => instances().map(instance => instance.id);
createEffect(() => { const onDragStart: DragEventHandler = ({ draggable }) => {
setSorted(props.instances.sort((a, b) => { draggable.node.style.zIndex = '1000';
if (a.lastPlayed && b.lastPlayed) };
return b.lastPlayed - a.lastPlayed;
return 0; const onDragEnd: DragEventHandler = ({ draggable, droppable }) => {
})); draggable.node.style.zIndex = '';
}); if (draggable && droppable) {
const currentItems = ids();
const fromIndex = currentItems.indexOf(draggable.id as number);
const toIndex = currentItems.indexOf(droppable.id as number);
if (fromIndex !== toIndex) {
const updatedItems = instances().slice();
updatedItems.splice(toIndex, 0, ...updatedItems.splice(fromIndex, 1));
setInstances(updatedItems);
}
}
};
return ( return (
<div class="flex flex-col gap-y-4"> <div class="flex flex-col gap-y-4">
<h4>{props.title}</h4> <h4>{props.title}</h4>
<div class="grid grid-cols-4 gap-4"> <DragDropProvider
<For each={sorted()}> onDragStart={onDragStart}
{instance => ( onDragEnd={onDragEnd}
<InstanceCard {...instance} /> collisionDetector={closestCenter}
)} >
</For> <DragDropSensors />
</div> <div class="grid grid-cols-4 gap-4">
<SortableProvider ids={ids()}>
<For each={instances()}>{item => <InstanceCard {...item} />}</For>
</SortableProvider>
</div>
</DragDropProvider>
</div> </div>
); );
} }
@ -136,41 +169,61 @@ function HomePage() {
name: 'Hypixel', name: 'Hypixel',
client: 'vanilla', client: 'vanilla',
version: '1.8.9', version: '1.8.9',
id: 1,
}, },
{ {
name: 'OneConfig ??', name: 'OneConfig ??',
client: 'forge', client: 'forge',
version: '1.8.9', version: '1.8.9',
mods: 12, mods: 12,
id: 2,
}, },
{ {
name: 'Fabulously Optimised', name: 'Fabulously Optimised',
client: 'fabric', client: 'fabric',
version: '1.20.4', version: '1.20.4',
mods: 48, mods: 48,
id: 3,
}, },
{ {
name: 'sus client', name: 'sus client',
client: 'other', client: 'other',
version: '1.8.9', version: '1.8.9',
id: 4,
},
{
name: 'Hypixel',
client: 'vanilla',
version: '1.8.9',
id: 5,
}, },
{ {
name: 'OneConfig ??', name: 'OneConfig ??',
client: 'forge', client: 'forge',
version: '1.8.9', version: '1.8.9',
mods: 12, mods: 12,
id: 6,
}, },
{ {
name: 'Fabulously Optimised', name: 'Fabulously Optimised',
client: 'fabric', client: 'fabric',
version: '1.20.4', version: '1.20.4',
mods: 48, mods: 48,
id: 7,
}, },
{ {
name: 'sus client', name: 'sus client',
client: 'other', client: 'other',
version: '1.8.9', version: '1.8.9',
id: 8,
}, },
// {
// name: 'OneConfig ??',
// client: 'forge',
// version: '1.8.9',
// mods: 12,
// id: 5,
// },
]; ];
return ( return (