mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: drag and drop
This commit is contained in:
1 parent
8c9dcfa551
commit
59772afb04
1 file changed
+144
-3
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user