feat: drag and drop

This commit is contained in:
TrèsAbhi committed 2024-04-10 09:40:52 -05:00
1 parent 8c9dcfa551
commit 59772afb04
1 file changed
+144 -3
+144 -3
View File
@@ -21,7 +21,8 @@ import {
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { times } from 'lodash'; import { times } from 'lodash';
import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { use, useCallback, useEffect, useMemo, useState } from 'react'; import { use, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Vector2 } from 'three';
import { ConsumablesManager } from '../../../components/ConsumablesManager'; import { ConsumablesManager } from '../../../components/ConsumablesManager';
import { CrewSkillManager } from '../../../components/CrewSkillManager'; import { CrewSkillManager } from '../../../components/CrewSkillManager';
import { EquipmentManager } from '../../../components/EquipmentManager'; import { EquipmentManager } from '../../../components/EquipmentManager';
@@ -58,6 +59,8 @@ import { permanentSkills } from '../tankopedia/[id]/components/Characteristics/c
import { TankSearch } from '../tankopedia/components/TankSearch'; import { TankSearch } from '../tankopedia/components/TankSearch';
import { TankControl } from './components/TankControl'; import { TankControl } from './components/TankControl';
const insertionMarkers: { element: HTMLDivElement; index: number }[] = [];
export default function Page() { export default function Page() {
const pathname = usePathname(); const pathname = usePathname();
const router = useRouter(); const router = useRouter();
@@ -328,8 +331,94 @@ export default function Page() {
} }
function TankCard({ index, tank }: { index: number; tank: TankDefinition }) { function TankCard({ index, tank }: { index: number; tank: TankDefinition }) {
const image = useRef<HTMLImageElement>(null);
useEffect(() => {
const initial = new Vector2();
const current = new Vector2();
const delta = new Vector2();
const marker = new Vector2();
let initialRect = image.current!.getBoundingClientRect();
let dropIndex = -1;
function handlePointerDown(event: PointerEvent) {
event.preventDefault();
initialRect = image.current!.getBoundingClientRect();
delta.set(0, 0);
current.copy(initial.set(event.clientX, event.clientY));
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
event.preventDefault();
current.set(event.clientX, event.clientY);
delta.copy(current).sub(initial);
insertionMarkers.forEach(({ element }) => {
element.style.opacity = '0';
});
const distances = insertionMarkers.map((insertionMarker) => {
const markerBounds = insertionMarker.element.getBoundingClientRect();
marker.set(markerBounds.left, markerBounds.top);
return {
...insertionMarker,
distance: marker.distanceTo(current),
};
});
const closest = distances.reduce((a, b) =>
a.distance < b.distance ? a : b,
);
dropIndex = closest.index;
closest.element.style.opacity = '1';
image.current!.style.position = 'fixed';
image.current!.style.zIndex = '1';
image.current!.style.left = `${initialRect.left + delta.x}px`;
image.current!.style.top = `${initialRect.top + delta.y}px`;
}
function handlePointerUp(event: PointerEvent) {
event.preventDefault();
image.current!.style.position = 'static';
image.current!.style.zIndex = 'unset';
insertionMarkers.forEach(({ element }) => {
element.style.opacity = '0';
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
if (dropIndex === -1) return;
mutateCompareTemporary((draft) => {
const fromIndex = index;
const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex;
const member = draft.members[fromIndex];
draft.members.splice(fromIndex, 1);
draft.members.splice(toIndex, 0, member);
});
dropIndex = -1;
}
image.current?.addEventListener('pointerdown', handlePointerDown);
return () => {
image.current?.removeEventListener('pointerdown', handlePointerDown);
};
}, []);
return ( return (
<Table.ColumnHeaderCell width="0" style={{ left: 0 }}> <Table.ColumnHeaderCell
width="0"
style={{ left: 0, position: 'relative' }}
>
<Flex <Flex
direction="column" direction="column"
align="center" align="center"
@@ -344,10 +433,15 @@ export default function Page() {
members={members} members={members}
/> />
<img <img
ref={image}
alt={tank.name}
src={tankIcon(tank.id)} src={tankIcon(tank.id)}
width={64} width={64}
height={64} height={64}
draggable={false}
style={{ style={{
userSelect: 'none',
cursor: 'grab',
objectFit: 'contain', objectFit: 'contain',
}} }}
/> />
@@ -363,10 +457,51 @@ export default function Page() {
{tank.name} {tank.name}
</Text> </Text>
</Flex> </Flex>
<InsertionMarker index={index + 1} />
</Table.ColumnHeaderCell> </Table.ColumnHeaderCell>
); );
} }
function InsertionMarker({ index }: { index: number }) {
const marker = useRef<HTMLDivElement>(null);
useEffect(() => {
insertionMarkers.push({
element: marker.current!,
index,
});
console.log(insertionMarkers);
return () => {
insertionMarkers.splice(
insertionMarkers.findIndex(
(insertionMarker) => insertionMarker.element === marker.current!,
),
1,
);
};
});
return (
<div
ref={marker}
style={{
height: '75%',
width: 2,
borderRadius: 1,
backgroundColor: theme.colors.textLowContrast_crimson,
position: 'absolute',
right: 0,
top: '50%',
transform: 'translateY(-50%)',
opacity: 0,
}}
/>
);
}
return ( return (
<PageWrapper color="crimson" size="100%"> <PageWrapper color="crimson" size="100%">
<Flex justify="center" gap="2" align="center" mt="4" direction="column"> <Flex justify="center" gap="2" align="center" mt="4" direction="column">
@@ -462,7 +597,12 @@ export default function Page() {
<Table.Root variant="surface" style={{ maxWidth: '100%' }}> <Table.Root variant="surface" style={{ maxWidth: '100%' }}>
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.ColumnHeaderCell width="0"> <Table.ColumnHeaderCell
width="0"
style={{
position: 'relative',
}}
>
<Flex <Flex
style={{ style={{
width: '100%', width: '100%',
@@ -472,6 +612,7 @@ export default function Page() {
justify="center" justify="center"
> >
<BlitzkriegButtonWatermark width={64} height={64} /> <BlitzkriegButtonWatermark width={64} height={64} />
<InsertionMarker index={0} />
</Flex> </Flex>
</Table.ColumnHeaderCell> </Table.ColumnHeaderCell>