From c07ba95c6f3f18ea61f6bd1fcd5d3ecd832e387b Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 17 May 2024 20:58:30 +0200 Subject: [PATCH] feat: ability to drag and drop to the center, to fit module/charge (#125) --- .../HardwareListing/HardwareListing.tsx | 4 +- src/components/ShipFit/HullDraggable.tsx | 48 +++++++++++++++++++ src/components/ShipFit/RingTop.tsx | 5 +- src/components/ShipFit/ShipFit.module.css | 15 ++++++ src/components/ShipFit/ShipFit.tsx | 23 +++++---- src/components/TreeListing/TreeListing.tsx | 6 ++- .../ShipSnapshotProvider.tsx | 8 +++- 7 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 src/components/ShipFit/HullDraggable.tsx diff --git a/src/components/HardwareListing/HardwareListing.tsx b/src/components/HardwareListing/HardwareListing.tsx index 9fc7a3a..d612aa6 100644 --- a/src/components/HardwareListing/HardwareListing.tsx +++ b/src/components/HardwareListing/HardwareListing.tsx @@ -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)} /> ); diff --git a/src/components/ShipFit/HullDraggable.tsx b/src/components/ShipFit/HullDraggable.tsx new file mode 100644 index 0000000..964d364 --- /dev/null +++ b/src/components/ShipFit/HullDraggable.tsx @@ -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) => { + e.preventDefault(); + }, []); + + const onDragEnd = React.useCallback( + (e: React.DragEvent) => { + 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
; +}; diff --git a/src/components/ShipFit/RingTop.tsx b/src/components/ShipFit/RingTop.tsx index 631d8a2..aa1f5b9 100644 --- a/src/components/ShipFit/RingTop.tsx +++ b/src/components/ShipFit/RingTop.tsx @@ -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
{props.children}
; }; -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 ( -
+
{props.children}
); diff --git a/src/components/ShipFit/ShipFit.module.css b/src/components/ShipFit/ShipFit.module.css index 7747144..2f71bbc 100644 --- a/src/components/ShipFit/ShipFit.module.css +++ b/src/components/ShipFit/ShipFit.module.css @@ -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%; diff --git a/src/components/ShipFit/ShipFit.tsx b/src/components/ShipFit/ShipFit.tsx index d56cd8a..d0147fc 100644 --- a/src/components/ShipFit/ShipFit.tsx +++ b/src/components/ShipFit/ShipFit.tsx @@ -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 }) => { {props.withStats && ( <> - +
@@ -56,7 +57,7 @@ export const ShipFit = (props: { withStats?: boolean }) => { {Array.from({ length: slots?.turret }, (_, i) => { turretSlotsUsed--; return ( - +
= 0, @@ -68,7 +69,7 @@ export const ShipFit = (props: { withStats?: boolean }) => { ); })} - +
@@ -76,7 +77,7 @@ export const ShipFit = (props: { withStats?: boolean }) => { {Array.from({ length: slots?.launcher }, (_, i) => { launcherSlotsUsed--; return ( - +
= 0, @@ -88,17 +89,17 @@ export const ShipFit = (props: { withStats?: boolean }) => { ); })} - +
- +
- +
@@ -106,7 +107,7 @@ export const ShipFit = (props: { withStats?: boolean }) => { )} - + @@ -135,7 +136,7 @@ export const ShipFit = (props: { withStats?: boolean }) => { = 8} /> - + @@ -164,7 +165,7 @@ export const ShipFit = (props: { withStats?: boolean }) => { = 8} /> - + @@ -216,6 +217,8 @@ export const ShipFit = (props: { withStats?: boolean }) => { = 4} /> + +
); }; diff --git a/src/components/TreeListing/TreeListing.tsx b/src/components/TreeListing/TreeListing.tsx index ea0a265..19a5825 100644 --- a/src/components/TreeListing/TreeListing.tsx +++ b/src/components/TreeListing/TreeListing.tsx @@ -53,6 +53,7 @@ export const TreeLeaf = (props: { iconTitle?: string; content: string; onClick?: (e: React.MouseEvent) => void; + onDoubleClick?: (e: React.MouseEvent) => void; onDragStart?: (e: React.DragEvent) => void; }) => { const stylesHeader = styles[`header${props.level}`]; @@ -66,10 +67,11 @@ export const TreeLeaf = (props: {
diff --git a/src/providers/ShipSnapshotProvider/ShipSnapshotProvider.tsx b/src/providers/ShipSnapshotProvider/ShipSnapshotProvider.tsx index 1dbc8f5..f0c0599 100644 --- a/src/providers/ShipSnapshotProvider/ShipSnapshotProvider.tsx +++ b/src/providers/ShipSnapshotProvider/ShipSnapshotProvider.tsx @@ -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 ?? []) {