feat: ability to drag and drop to the center, to fit module/charge (#125)
This commit is contained in:
@@ -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)}
|
||||
/>
|
||||
);
|
||||
|
||||
48
src/components/ShipFit/HullDraggable.tsx
Normal file
48
src/components/ShipFit/HullDraggable.tsx
Normal 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} />;
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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 ?? []) {
|
||||
|
||||
Reference in New Issue
Block a user