feat: ability to drag and drop to the center, to fit module/charge (#125)

This commit is contained in:
Patric Stout
2024-05-17 20:58:30 +02:00
committed by GitHub
parent 1008128bcc
commit c07ba95c6f
7 changed files with 91 additions and 18 deletions

View File

@@ -71,7 +71,7 @@ const ModuleGroup = (props: { level: number; group: ListingGroup; hideGroup?: bo
key={item.typeId}
level={2}
content={item.name}
onClick={() => shipSnapShot.addCharge(item.typeId)}
onDoubleClick={() => shipSnapShot.addCharge(item.typeId)}
onDragStart={onItemDragStart(item.typeId, "charge")}
/>
);
@@ -82,7 +82,7 @@ const ModuleGroup = (props: { level: number; group: ListingGroup; hideGroup?: bo
key={item.typeId}
level={2}
content={item.name}
onClick={() => shipSnapShot.addModule(item.typeId, slotType)}
onDoubleClick={() => shipSnapShot.addModule(item.typeId, slotType)}
onDragStart={onItemDragStart(item.typeId, slotType)}
/>
);

View File

@@ -0,0 +1,48 @@
import React from "react";
import { ShipSnapshotContext, ShipSnapshotSlotsType } from "@/providers";
import styles from "./ShipFit.module.css";
export const HullDraggable = () => {
const shipSnapshot = React.useContext(ShipSnapshotContext);
const onDragOver = React.useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
}, []);
const onDragEnd = React.useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
const parseNumber = (maybeNumber: string): number | undefined => {
const num = parseInt(maybeNumber);
return Number.isInteger(num) ? num : undefined;
};
const draggedTypeId: number | undefined = parseNumber(e.dataTransfer.getData("application/type_id"));
const draggedSlotId: number | undefined = parseNumber(e.dataTransfer.getData("application/slot_id"));
const draggedSlotType: ShipSnapshotSlotsType | "charge" = e.dataTransfer.getData("application/slot_type") as
| ShipSnapshotSlotsType
| "charge";
if (draggedTypeId === undefined) {
return;
}
/* Dragging a fitted module to the middle has no effect. */
if (draggedSlotId !== undefined) {
return;
}
if (draggedSlotType === "charge") {
shipSnapshot.addCharge(draggedTypeId);
return;
}
shipSnapshot.addModule(draggedTypeId, draggedSlotType);
},
[shipSnapshot],
);
return <div className={styles.hullDraggable} onDragOver={onDragOver} onDrop={onDragEnd} />;
};

View File

@@ -1,3 +1,4 @@
import clsx from "clsx";
import React from "react";
import styles from "./ShipFit.module.css";
@@ -6,13 +7,13 @@ export const RingTop = (props: { children: React.ReactNode }) => {
return <div className={styles.ringTop}>{props.children}</div>;
};
export const RingTopItem = (props: { children: React.ReactNode; rotation: number }) => {
export const RingTopItem = (props: { children: React.ReactNode; rotation: number; background?: boolean }) => {
const rotationStyle = {
"--rotation": `${props.rotation}deg`,
} as React.CSSProperties;
return (
<div className={styles.ringTopItem} style={rotationStyle}>
<div className={clsx(styles.ringTopItem, { [styles.background]: props.background })} style={rotationStyle}>
{props.children}
</div>
);

View File

@@ -43,6 +43,16 @@
width: calc(100% - 2 * 3%);
}
.hullDraggable {
border-radius: 50%;
height: 76%;
left: 12%;
position: absolute;
top: 12%;
width: 76%;
z-index: 3;
}
.ringTop {
height: 100%;
position: absolute;
@@ -68,6 +78,11 @@
top: 3.5%;
}
.ringTopItem.background > div,
.ringTopItem.background > svg {
pointer-events: none;
}
.turretLauncherIcon {
height: 2.5%;
margin-top: -2%;

View File

@@ -15,6 +15,7 @@ import { Slot } from "./Slot";
import { Usage } from "./Usage";
import styles from "./ShipFit.module.css";
import { HullDraggable } from "./HullDraggable";
/**
* Render a ship fit similar to how it is done in-game.
@@ -48,7 +49,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
<RingTop>
{props.withStats && (
<>
<RingTopItem rotation={-45}>
<RingTopItem rotation={-45} background>
<div className={styles.turretLauncherIcon}>
<Icon name="hardpoint-turret" size={16} />
</div>
@@ -56,7 +57,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
{Array.from({ length: slots?.turret }, (_, i) => {
turretSlotsUsed--;
return (
<RingTopItem key={i} rotation={-40 + i * 3}>
<RingTopItem key={i} rotation={-40 + i * 3} background>
<div
className={clsx(styles.turretLauncherItem, {
[styles.turretLauncherItemUsed]: turretSlotsUsed >= 0,
@@ -68,7 +69,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
);
})}
<RingTopItem rotation={43}>
<RingTopItem rotation={43} background>
<div className={styles.turretLauncherIcon}>
<Icon name="hardpoint-launcher" size={16} />
</div>
@@ -76,7 +77,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
{Array.from({ length: slots?.launcher }, (_, i) => {
launcherSlotsUsed--;
return (
<RingTopItem key={i} rotation={39 - i * 3}>
<RingTopItem key={i} rotation={39 - i * 3} background>
<div
className={clsx(styles.turretLauncherItem, {
[styles.turretLauncherItemUsed]: launcherSlotsUsed >= 0,
@@ -88,17 +89,17 @@ export const ShipFit = (props: { withStats?: boolean }) => {
);
})}
<RingTopItem rotation={-47}>
<RingTopItem rotation={-47} background>
<div className={styles.usage}>
<Usage type="rig" angle={-30} intervals={30} markers={2} color="#3d4547" />
</div>
</RingTopItem>
<RingTopItem rotation={134.5}>
<RingTopItem rotation={134.5} background>
<div className={styles.usage}>
<Usage type="cpu" angle={-44} intervals={40} markers={5} color="#2a504f" />
</div>
</RingTopItem>
<RingTopItem rotation={135}>
<RingTopItem rotation={135} background>
<div className={styles.usage}>
<Usage type="pg" angle={44} intervals={40} markers={5} color="#541208" />
</div>
@@ -106,7 +107,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
</>
)}
<RingTopItem rotation={-45}>
<RingTopItem rotation={-45} background>
<RadialMenu type="hislot" />
</RingTopItem>
@@ -135,7 +136,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
<Slot type="hislot" index={8} fittable={slots?.hislot >= 8} />
</RingTopItem>
<RingTopItem rotation={43}>
<RingTopItem rotation={43} background>
<RadialMenu type="medslot" />
</RingTopItem>
@@ -164,7 +165,7 @@ export const ShipFit = (props: { withStats?: boolean }) => {
<Slot type="medslot" index={8} fittable={slots?.medslot >= 8} />
</RingTopItem>
<RingTopItem rotation={133}>
<RingTopItem rotation={133} background>
<RadialMenu type="lowslot" />
</RingTopItem>
@@ -216,6 +217,8 @@ export const ShipFit = (props: { withStats?: boolean }) => {
<Slot type="subsystem" index={4} fittable={slots?.subsystem >= 4} />
</RingTopItem>
</RingTop>
<HullDraggable />
</div>
);
};

View File

@@ -53,6 +53,7 @@ export const TreeLeaf = (props: {
iconTitle?: string;
content: string;
onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
onDoubleClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;
}) => {
const stylesHeader = styles[`header${props.level}`];
@@ -66,10 +67,11 @@ export const TreeLeaf = (props: {
<div
style={style}
className={clsx(styles.header, stylesHeader, {
[styles.headerHover]: props.onClick !== undefined,
[styles.leaf]: props.onClick !== undefined,
[styles.headerHover]: props.onClick !== undefined || props.onDoubleClick !== undefined,
[styles.leaf]: props.onClick !== undefined || props.onDoubleClick !== undefined,
})}
onClick={props.onClick}
onDoubleClick={props.onDoubleClick}
draggable={!!props.onDragStart}
onDragStart={props.onDragStart}
>

View File

@@ -74,7 +74,7 @@ interface ShipSnapshot {
setModule: (typeId: number, flag: number) => void;
addModule: (typeId: number, slot: ShipSnapshotSlotsType) => void;
removeModule: (flag: number) => void;
addCharge: (chargeTypeId: number) => void;
addCharge: (chargeTypeId: number, flag?: number) => void;
removeCharge: (flag: number) => void;
toggleDrones: (typeId: number, active: number) => void;
removeDrones: (typeId: number) => void;
@@ -286,7 +286,7 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
}, []);
const addCharge = React.useCallback(
(chargeTypeId: number) => {
(chargeTypeId: number, flag?: number) => {
const chargeSize =
eveData.typeDogma?.[chargeTypeId]?.dogmaAttributes.find(
(attr) => attr.attributeID === eveData.attributeMapping?.chargeSize,
@@ -307,6 +307,10 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
newItems.push(item);
continue;
}
if (flag !== undefined && item.flag !== flag) {
newItems.push(item);
continue;
}
/* Check if the charge fits in this module; if so, assign it. */
for (const attr of eveData.typeDogma?.[item.type_id]?.dogmaAttributes ?? []) {