mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: drop
This commit is contained in:
1 parent
0db4be4d20
commit
d851a05f53
10 files changed
+204
-39
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
export const tierListDropOffs = new Set<HTMLDivElement>();
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.drop-off {
|
||||||
|
width: 0.125rem;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: var(--radius-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-off[data-dragging='true'] {
|
||||||
|
background-color: var(--accent-indicator);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { fetchTankDefinitions } from '@blitzkit/core';
|
||||||
|
import { Flex, Heading, Table } from '@radix-ui/themes';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import { Var } from '../../core/radix/var';
|
||||||
|
import { TierList } from '../../stores/tierList';
|
||||||
|
import { DropOff } from './DropOff';
|
||||||
|
import { tierListRows } from './Table/constants';
|
||||||
|
import { TierListTile } from './Tile';
|
||||||
|
|
||||||
|
interface TierListRowProps {
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tankDefinitions = await fetchTankDefinitions();
|
||||||
|
|
||||||
|
export function TierListRow({ index }: TierListRowProps) {
|
||||||
|
const row = tierListRows[index];
|
||||||
|
const tanks = TierList.use((state) => state.tanks[index]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table.Row key={row.name}>
|
||||||
|
<Table.RowHeaderCell
|
||||||
|
width="0"
|
||||||
|
style={{ backgroundColor: Var(row.color) }}
|
||||||
|
>
|
||||||
|
<Heading>{row.name}</Heading>
|
||||||
|
</Table.RowHeaderCell>
|
||||||
|
|
||||||
|
<Table.Cell>
|
||||||
|
<Flex wrap="wrap" height="100%" gap="2">
|
||||||
|
<DropOff row={index} index={0} />
|
||||||
|
|
||||||
|
{tanks.map((id, tankIndex) => {
|
||||||
|
const tank = tankDefinitions.tanks[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment key={id}>
|
||||||
|
<TierListTile tank={tank} />
|
||||||
|
<DropOff row={index} index={tankIndex + 1} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Heading, Table } from '@radix-ui/themes';
|
|
||||||
import { Var, type VarName } from '../../core/radix/var';
|
|
||||||
|
|
||||||
const rows: {
|
|
||||||
name: string;
|
|
||||||
color: VarName;
|
|
||||||
}[] = [
|
|
||||||
{ name: 'S', color: 'tomato-9' },
|
|
||||||
{ name: 'A', color: 'orange-9' },
|
|
||||||
{ name: 'B', color: 'amber-9' },
|
|
||||||
{ name: 'C', color: 'green-9' },
|
|
||||||
{ name: 'D', color: 'blue-9' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function TierListTable() {
|
|
||||||
return (
|
|
||||||
<Table.Root variant="surface">
|
|
||||||
<Table.Body>
|
|
||||||
{rows.map((row) => (
|
|
||||||
<Table.Row key={row.name}>
|
|
||||||
<Table.RowHeaderCell
|
|
||||||
width="0"
|
|
||||||
style={{ backgroundColor: Var(row.color) }}
|
|
||||||
>
|
|
||||||
<Heading>{row.name}</Heading>
|
|
||||||
</Table.RowHeaderCell>
|
|
||||||
|
|
||||||
<Table.Cell></Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
))}
|
|
||||||
</Table.Body>
|
|
||||||
</Table.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { VarName } from '../../../core/radix/var';
|
||||||
|
|
||||||
|
export const tierListRows: {
|
||||||
|
name: string;
|
||||||
|
color: VarName;
|
||||||
|
}[] = [
|
||||||
|
{ name: 'S', color: 'tomato-9' },
|
||||||
|
{ name: 'A', color: 'orange-9' },
|
||||||
|
{ name: 'B', color: 'amber-9' },
|
||||||
|
{ name: 'C', color: 'green-9' },
|
||||||
|
{ name: 'D', color: 'blue-9' },
|
||||||
|
];
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Table } from '@radix-ui/themes';
|
||||||
|
import { TierListRow } from '../Row';
|
||||||
|
import { tierListRows } from './constants';
|
||||||
|
|
||||||
|
export function TierListTable() {
|
||||||
|
return (
|
||||||
|
<Table.Root variant="surface">
|
||||||
|
<Table.Body>
|
||||||
|
{tierListRows.map((_, index) => (
|
||||||
|
<TierListRow index={index} key={index} />
|
||||||
|
))}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
type PointerEvent as ReactPointerEvent,
|
type PointerEvent as ReactPointerEvent,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { TierList } from '../../stores/tierList';
|
||||||
import { TankCard } from '../TankCard';
|
import { TankCard } from '../TankCard';
|
||||||
|
import { tierListDropOffs } from './DropOff/constants';
|
||||||
|
|
||||||
interface TierListTileProps {
|
interface TierListTileProps {
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
@@ -12,11 +14,14 @@ interface TierListTileProps {
|
|||||||
|
|
||||||
export function TierListTile({ tank }: TierListTileProps) {
|
export function TierListTile({ tank }: TierListTileProps) {
|
||||||
const card = useRef<HTMLDivElement>(null);
|
const card = useRef<HTMLDivElement>(null);
|
||||||
|
const mutateTierList = TierList.useMutation();
|
||||||
const lastPosition = useRef({ x: 0, y: 0 });
|
const lastPosition = useRef({ x: 0, y: 0 });
|
||||||
const handlePointerDown = useCallback(
|
const handlePointerDown = useCallback(
|
||||||
(event: ReactPointerEvent<HTMLSpanElement>) => {
|
(event: ReactPointerEvent<HTMLSpanElement>) => {
|
||||||
if (!card.current) return;
|
if (!card.current) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
const rect = card.current.getBoundingClientRect();
|
const rect = card.current.getBoundingClientRect();
|
||||||
|
|
||||||
card.current.style.position = 'fixed';
|
card.current.style.position = 'fixed';
|
||||||
@@ -29,12 +34,18 @@ export function TierListTile({ tank }: TierListTileProps) {
|
|||||||
|
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
|
|
||||||
|
mutateTierList((draft) => {
|
||||||
|
draft.dragging = true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const handlePointerMove = useCallback((event: PointerEvent) => {
|
const handlePointerMove = useCallback((event: PointerEvent) => {
|
||||||
if (!card.current) return;
|
if (!card.current) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
const rect = card.current.getBoundingClientRect();
|
const rect = card.current.getBoundingClientRect();
|
||||||
const dx = event.clientX - lastPosition.current.x;
|
const dx = event.clientX - lastPosition.current.x;
|
||||||
const dy = event.clientY - lastPosition.current.y;
|
const dy = event.clientY - lastPosition.current.y;
|
||||||
@@ -44,12 +55,53 @@ export function TierListTile({ tank }: TierListTileProps) {
|
|||||||
|
|
||||||
lastPosition.current = { x: event.clientX, y: event.clientY };
|
lastPosition.current = { x: event.clientX, y: event.clientY };
|
||||||
}, []);
|
}, []);
|
||||||
const handlePointerUp = useCallback(() => {
|
const handlePointerUp = useCallback((event: PointerEvent) => {
|
||||||
if (!card.current) return;
|
if (!card.current) return;
|
||||||
|
|
||||||
|
const rect = card.current.getBoundingClientRect();
|
||||||
|
let dropOff: { row: number; index: number; distance: number };
|
||||||
|
|
||||||
|
tierListDropOffs.forEach((element) => {
|
||||||
|
const thisRect = element.getBoundingClientRect();
|
||||||
|
const thisX = (thisRect.left + thisRect.right) / 2;
|
||||||
|
const thisY = (thisRect.top + thisRect.bottom) / 2;
|
||||||
|
let distance = 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(thisX < rect.left || thisX > rect.right) &&
|
||||||
|
(thisY < rect.top || thisY > rect.bottom)
|
||||||
|
) {
|
||||||
|
const rectX = (rect.left + rect.right) / 2;
|
||||||
|
const rectY = (rect.top + rect.bottom) / 2;
|
||||||
|
distance = Math.sqrt((thisX - rectX) ** 2 + (thisY - rectY) ** 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropOff === undefined || dropOff.distance > distance) {
|
||||||
|
dropOff = {
|
||||||
|
row: Number(element.dataset.row),
|
||||||
|
index: Number(element.dataset.index),
|
||||||
|
distance,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mutateTierList((draft) => {
|
||||||
|
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';
|
||||||
card.current.style.cursor = 'grab';
|
card.current.style.cursor = 'grab';
|
||||||
|
|
||||||
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { PageWrapper } from '../../../components/PageWrapper';
|
import { PageWrapper } from '../../../components/PageWrapper';
|
||||||
import { TierListTiles } from '../../../components/TierList/Tiles';
|
|
||||||
import { TierListTable } from '../../../components/TierList/Table';
|
import { TierListTable } from '../../../components/TierList/Table';
|
||||||
|
import { TierListTiles } from '../../../components/TierList/Tiles';
|
||||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||||
|
import { TierList } from '../../../stores/tierList';
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
return (
|
return (
|
||||||
<TankopediaPersistent.Provider>
|
<TierList.Provider>
|
||||||
<Content />
|
<TankopediaPersistent.Provider>
|
||||||
</TankopediaPersistent.Provider>
|
<Content />
|
||||||
|
</TankopediaPersistent.Provider>
|
||||||
|
</TierList.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { subscribeWithSelector } from 'zustand/middleware';
|
||||||
|
import { tierListRows } from '../components/TierList/Table/constants';
|
||||||
|
import { createContextualStore } from '../core/zustand/createContextualStore';
|
||||||
|
|
||||||
|
export interface TierList {
|
||||||
|
dragging: boolean;
|
||||||
|
tanks: number[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TierList = createContextualStore(() =>
|
||||||
|
create<TierList>()(
|
||||||
|
subscribeWithSelector<TierList>(() => ({
|
||||||
|
dragging: false,
|
||||||
|
tanks: tierListRows.map(() => []),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
Reference in new issue
Block a user