mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: better placing
This commit is contained in:
1 parent
a08107d0b2
commit
5e912e5b51
7 files changed
+109
-104
No files matched your search
@@ -1 +0,0 @@
|
|||||||
export const tierListDropOffs = new Set<HTMLDivElement>();
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
.drop-off {
|
|
||||||
width: 0.125rem;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: var(--radius-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.drop-off[data-dragging='true'] {
|
|
||||||
background-color: var(--accent-indicator);
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { Box } from '@radix-ui/themes';
|
|
||||||
import { useEffect, useRef } from 'react';
|
|
||||||
import { TierList } from '../../../stores/tierList';
|
|
||||||
import { tierListDropOffs } from './constants';
|
|
||||||
import './index.css';
|
|
||||||
|
|
||||||
interface DropOffProps {
|
|
||||||
row: number;
|
|
||||||
index: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DropOff({ row, index }: DropOffProps) {
|
|
||||||
const tierListStore = TierList.useStore();
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unsubscribe = tierListStore.subscribe(
|
|
||||||
(state) => state.dragging,
|
|
||||||
(dragging) => {
|
|
||||||
if (!ref.current) return;
|
|
||||||
ref.current.dataset.dragging = `${dragging}`;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
tierListDropOffs.add(ref.current!);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribe();
|
|
||||||
tierListDropOffs.delete(ref.current!);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
className="drop-off"
|
|
||||||
data-dragging="false"
|
|
||||||
data-row={row}
|
|
||||||
data-index={index}
|
|
||||||
ref={ref}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import { fetchTankDefinitions } from '@blitzkit/core';
|
import { fetchTankDefinitions } from '@blitzkit/core';
|
||||||
import { Flex, Heading, Table } from '@radix-ui/themes';
|
import { Flex, Heading, Table } from '@radix-ui/themes';
|
||||||
import { Fragment } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { Var } from '../../core/radix/var';
|
import { Var } from '../../core/radix/var';
|
||||||
import { TierList } from '../../stores/tierList';
|
import { TierList } from '../../stores/tierList';
|
||||||
import { DropOff } from './DropOff';
|
import { tierListRowElements, tierListRows } from './Table/constants';
|
||||||
import { tierListRows } from './Table/constants';
|
|
||||||
import { TierListTile } from './Tile';
|
import { TierListTile } from './Tile';
|
||||||
|
|
||||||
interface TierListRowProps {
|
interface TierListRowProps {
|
||||||
@@ -14,30 +13,43 @@ interface TierListRowProps {
|
|||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await fetchTankDefinitions();
|
||||||
|
|
||||||
export function TierListRow({ index }: TierListRowProps) {
|
export function TierListRow({ index }: TierListRowProps) {
|
||||||
const row = tierListRows[index];
|
const rowStyle = tierListRows[index];
|
||||||
const tanks = TierList.use((state) => state.tanks[index]);
|
const tanks = TierList.use((state) => state.tanks[index]);
|
||||||
|
const row = useRef<HTMLTableRowElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!row.current) return;
|
||||||
|
|
||||||
|
tierListRowElements.add(row.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!row.current) return;
|
||||||
|
|
||||||
|
tierListRowElements.delete(row.current);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Row key={row.name}>
|
<Table.Row key={rowStyle.name} ref={row} data-index={index}>
|
||||||
<Table.RowHeaderCell
|
<Table.RowHeaderCell
|
||||||
width="0"
|
width="0"
|
||||||
style={{ backgroundColor: Var(row.color) }}
|
style={{ backgroundColor: Var(rowStyle.color) }}
|
||||||
>
|
>
|
||||||
<Heading>{row.name}</Heading>
|
<Heading>{rowStyle.name}</Heading>
|
||||||
</Table.RowHeaderCell>
|
</Table.RowHeaderCell>
|
||||||
|
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Flex wrap="wrap" height="100%" gap="2">
|
<Flex wrap="wrap" height="100%" gap="2">
|
||||||
<DropOff row={index} index={0} />
|
|
||||||
|
|
||||||
{tanks.map((id, tankIndex) => {
|
{tanks.map((id, tankIndex) => {
|
||||||
const tank = tankDefinitions.tanks[id];
|
const tank = tankDefinitions.tanks[id];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={id}>
|
<TierListTile
|
||||||
<TierListTile tank={tank} />
|
isPlaced
|
||||||
<DropOff row={index} index={tankIndex + 1} />
|
key={id}
|
||||||
</Fragment>
|
tank={tank}
|
||||||
|
rowIndex={index}
|
||||||
|
tileIndex={tankIndex}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -10,3 +10,8 @@ export const tierListRows: {
|
|||||||
{ name: 'C', color: 'green-9' },
|
{ name: 'C', color: 'green-9' },
|
||||||
{ name: 'D', color: 'blue-9' },
|
{ name: 'D', color: 'blue-9' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const tierListRowElements = new Set<HTMLTableRowElement>();
|
||||||
|
export const tierListCardElements = tierListRows.map(
|
||||||
|
() => new Set<HTMLDivElement>(),
|
||||||
|
);
|
||||||
@@ -1,18 +1,28 @@
|
|||||||
import type { TankDefinition } from '@blitzkit/core';
|
import type { TankDefinition } from '@blitzkit/core';
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
type PointerEvent as ReactPointerEvent,
|
type PointerEvent as ReactPointerEvent,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { TierList } from '../../stores/tierList';
|
import { TierList } from '../../stores/tierList';
|
||||||
import { TankCard } from '../TankCard';
|
import { TankCard } from '../TankCard';
|
||||||
import { tierListDropOffs } from './DropOff/constants';
|
import { tierListCardElements, tierListRowElements } from './Table/constants';
|
||||||
|
|
||||||
interface TierListTileProps {
|
type TierListTileProps = {
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
}
|
} & (
|
||||||
|
| {
|
||||||
|
isPlaced?: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isPlaced: true;
|
||||||
|
rowIndex: number;
|
||||||
|
tileIndex: number;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export function TierListTile({ tank }: TierListTileProps) {
|
export function TierListTile(props: TierListTileProps) {
|
||||||
const card = useRef<HTMLDivElement>(null);
|
const card = useRef<HTMLDivElement>(null);
|
||||||
const mutateTierList = TierList.useMutation();
|
const mutateTierList = TierList.useMutation();
|
||||||
const lastPosition = useRef({ x: 0, y: 0 });
|
const lastPosition = useRef({ x: 0, y: 0 });
|
||||||
@@ -58,43 +68,59 @@ export function TierListTile({ tank }: TierListTileProps) {
|
|||||||
const handlePointerUp = useCallback((event: PointerEvent) => {
|
const handlePointerUp = useCallback((event: PointerEvent) => {
|
||||||
if (!card.current) return;
|
if (!card.current) return;
|
||||||
|
|
||||||
const rect = card.current.getBoundingClientRect();
|
const cardRect = card.current.getBoundingClientRect();
|
||||||
let dropOff: { row: number; index: number; distance: number };
|
const cardX = (cardRect.left + cardRect.right) / 2;
|
||||||
|
const cardY = (cardRect.top + cardRect.bottom) / 2;
|
||||||
|
|
||||||
tierListDropOffs.forEach((element) => {
|
rowLoop: for (const row of tierListRowElements) {
|
||||||
const thisRect = element.getBoundingClientRect();
|
const rowRect = row.getBoundingClientRect();
|
||||||
const thisX = (thisRect.left + thisRect.right) / 2;
|
const isWithinRow =
|
||||||
const thisY = (thisRect.top + thisRect.bottom) / 2;
|
cardX >= rowRect.left &&
|
||||||
let distance = 0;
|
cardX <= rowRect.right &&
|
||||||
|
cardY >= rowRect.top &&
|
||||||
|
cardY <= rowRect.bottom;
|
||||||
|
|
||||||
if (
|
if (!isWithinRow) continue;
|
||||||
(thisX < rect.left || thisX > rect.right) &&
|
|
||||||
(thisY < rect.top || thisY > rect.bottom)
|
const rowIndex = Number(row.dataset.index);
|
||||||
) {
|
|
||||||
const rectX = (rect.left + rect.right) / 2;
|
for (const tile of tierListCardElements[rowIndex]) {
|
||||||
const rectY = (rect.top + rect.bottom) / 2;
|
const tileId = Number(tile.dataset.tileId);
|
||||||
distance = Math.sqrt((thisX - rectX) ** 2 + (thisY - rectY) ** 2);
|
|
||||||
|
if (tileId === props.tank.id) continue;
|
||||||
|
|
||||||
|
const tileRect = tile.getBoundingClientRect();
|
||||||
|
const isWithinTile =
|
||||||
|
cardX >= tileRect.left &&
|
||||||
|
cardX <= tileRect.right &&
|
||||||
|
cardY >= tileRect.top &&
|
||||||
|
cardY <= tileRect.bottom;
|
||||||
|
|
||||||
|
if (!isWithinTile) continue;
|
||||||
|
|
||||||
|
const tileIndex = Number(tile.dataset.tileIndex);
|
||||||
|
|
||||||
|
mutateTierList((draft) => {
|
||||||
|
draft.dragging = false;
|
||||||
|
draft.tanks = draft.tanks.map((row) =>
|
||||||
|
row.filter((tankId) => tankId !== props.tank.id),
|
||||||
|
);
|
||||||
|
draft.tanks[rowIndex].splice(tileIndex, 0, props.tank.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
break rowLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dropOff === undefined || dropOff.distance > distance) {
|
mutateTierList((draft) => {
|
||||||
dropOff = {
|
draft.dragging = false;
|
||||||
row: Number(element.dataset.row),
|
draft.tanks = draft.tanks.map((row) =>
|
||||||
index: Number(element.dataset.index),
|
row.filter((tankId) => tankId !== props.tank.id),
|
||||||
distance,
|
);
|
||||||
};
|
draft.tanks[rowIndex].push(props.tank.id);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
mutateTierList((draft) => {
|
break;
|
||||||
draft.dragging = false;
|
}
|
||||||
draft.tanks = draft.tanks.map((tanks) =>
|
|
||||||
tanks.filter((id) => id !== tank.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (dropOff) {
|
|
||||||
draft.tanks[dropOff.row].splice(dropOff.index, 0, tank.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
card.current.style.position = 'static';
|
card.current.style.position = 'static';
|
||||||
card.current.style.zIndex = 'unset';
|
card.current.style.zIndex = 'unset';
|
||||||
@@ -104,11 +130,25 @@ export function TierListTile({ tank }: TierListTileProps) {
|
|||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!props.isPlaced || !card.current) return;
|
||||||
|
|
||||||
|
tierListCardElements[props.rowIndex].add(card.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!card.current) return;
|
||||||
|
|
||||||
|
tierListCardElements[props.rowIndex].delete(card.current);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TankCard
|
<TankCard
|
||||||
|
data-tile-index={props.isPlaced ? props.tileIndex : undefined}
|
||||||
|
data-tile-id={props.tank.id}
|
||||||
ref={card}
|
ref={card}
|
||||||
noLink
|
noLink
|
||||||
tank={tank}
|
tank={props.tank}
|
||||||
style={{ cursor: 'grab' }}
|
style={{ cursor: 'grab' }}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function TierListTiles() {
|
|||||||
<TankCardWrapper>
|
<TankCardWrapper>
|
||||||
{tanks
|
{tanks
|
||||||
.filter((tank) => filterTank(filters, tank))
|
.filter((tank) => filterTank(filters, tank))
|
||||||
.map((tank) => (
|
.map((tank, tileIndex) => (
|
||||||
<TierListTile key={tank.id} tank={tank} />
|
<TierListTile key={tank.id} tank={tank} />
|
||||||
))}
|
))}
|
||||||
</TankCardWrapper>
|
</TankCardWrapper>
|
||||||
|
|||||||
Reference in new issue
Block a user