feat(charge): allow adding/removing charges to fits (#66)
This commit is contained in:
@@ -111,32 +111,50 @@ export const fullFit = {
|
||||
{
|
||||
"flag": 27,
|
||||
"quantity": 1,
|
||||
"type_id": 25715
|
||||
"type_id": 25715,
|
||||
"charge": {
|
||||
"type_id": 20308
|
||||
}
|
||||
},
|
||||
{
|
||||
"flag": 28,
|
||||
"quantity": 1,
|
||||
"type_id": 25715
|
||||
"type_id": 25715,
|
||||
"charge": {
|
||||
"type_id": 20308
|
||||
}
|
||||
},
|
||||
{
|
||||
"flag": 29,
|
||||
"quantity": 1,
|
||||
"type_id": 25715
|
||||
"type_id": 25715,
|
||||
"charge": {
|
||||
"type_id": 20308
|
||||
}
|
||||
},
|
||||
{
|
||||
"flag": 30,
|
||||
"quantity": 1,
|
||||
"type_id": 25715
|
||||
"type_id": 25715,
|
||||
"charge": {
|
||||
"type_id": 20308
|
||||
}
|
||||
},
|
||||
{
|
||||
"flag": 31,
|
||||
"quantity": 1,
|
||||
"type_id": 25715
|
||||
"type_id": 25715,
|
||||
"charge": {
|
||||
"type_id": 20308
|
||||
}
|
||||
},
|
||||
{
|
||||
"flag": 32,
|
||||
"quantity": 1,
|
||||
"type_id": 25715
|
||||
"type_id": 25715,
|
||||
"charge": {
|
||||
"type_id": 20308
|
||||
}
|
||||
},
|
||||
{
|
||||
"flag": 33,
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"typescript-plugin-css-modules": "^5.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@eveshipfit/dogma-engine": "^2.5.1",
|
||||
"@eveshipfit/dogma-engine": "^2.6.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@@ -50,7 +50,14 @@ const ModuleGroup = (props: { level: number; group: ListingGroup; hideGroup?: bo
|
||||
.sort((a, b) => a.meta - b.meta || a.name.localeCompare(b.name))
|
||||
.map((item) => {
|
||||
if (item.slotType === "charge") {
|
||||
return <TreeLeaf key={item.typeId} level={2} content={item.name} onClick={() => {}} />;
|
||||
return (
|
||||
<TreeLeaf
|
||||
key={item.typeId}
|
||||
level={2}
|
||||
content={item.name}
|
||||
onClick={() => shipSnapShot.addCharge(item.typeId)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const slotType = item.slotType;
|
||||
return (
|
||||
@@ -152,7 +159,9 @@ export const HardwareListing = () => {
|
||||
|
||||
setModulesWithCharges(newModulesWithCharges);
|
||||
|
||||
/* Reset the filter, as the ship is changed. */
|
||||
/* If the moduleWithCharge filter was set, validate if it is still valid. */
|
||||
if (newModulesWithCharges.find((charge) => charge.typeId === filter.moduleWithCharge?.typeId) !== undefined) return;
|
||||
|
||||
setFilter({
|
||||
...filter,
|
||||
moduleWithCharge: undefined,
|
||||
|
||||
@@ -58,6 +58,11 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
</g>
|
||||
</svg>
|
||||
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" style={{ display: "none" }}>
|
||||
<g id="uncharge">
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 4 6 A 8 8 0 1 1 4 14" />
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 11 6 L 6 10 L 11 14" />
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 6 10 L 16 10" />
|
||||
</g>
|
||||
<g id="unfit">
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 4 6 A 8 8 0 1 1 4 14" />
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 11 6 L 6 10 L 11 14" />
|
||||
@@ -131,6 +136,16 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
[shipSnapshot, esiItem],
|
||||
);
|
||||
|
||||
const unfitCharge = React.useCallback(
|
||||
(e: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (!shipSnapshot?.loaded || !esiItem) return;
|
||||
|
||||
shipSnapshot.removeCharge(esiItem.flag);
|
||||
},
|
||||
[shipSnapshot, esiItem],
|
||||
);
|
||||
|
||||
/* Not fittable and nothing fitted; no need to render the slot. */
|
||||
if (esiItem === undefined && !props.fittable) {
|
||||
return (
|
||||
@@ -143,12 +158,21 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
}
|
||||
|
||||
if (esiItem !== undefined) {
|
||||
item = (
|
||||
<img
|
||||
src={`https://images.evetech.net/types/${esiItem.type_id}/icon?size=64`}
|
||||
title={eveData?.typeIDs?.[esiItem.type_id].name}
|
||||
/>
|
||||
);
|
||||
if (esiItem.charge !== undefined) {
|
||||
item = (
|
||||
<img
|
||||
src={`https://images.evetech.net/types/${esiItem.charge.type_id}/icon?size=64`}
|
||||
title={`${eveData?.typeIDs?.[esiItem.type_id].name}\n${eveData?.typeIDs?.[esiItem.charge.type_id].name}`}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
item = (
|
||||
<img
|
||||
src={`https://images.evetech.net/types/${esiItem.type_id}/icon?size=64`}
|
||||
title={eveData?.typeIDs?.[esiItem.type_id].name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const state = esiItem?.state === "Passive" && esiItem?.max_state !== "Passive" ? "Offline" : esiItem?.state;
|
||||
@@ -160,6 +184,11 @@ export const Slot = (props: { type: string; index: number; fittable: boolean; ma
|
||||
<div className={styles.slotImage}>{item}</div>
|
||||
</div>
|
||||
<div className={styles.slotOptions}>
|
||||
{esiItem?.charge !== undefined && (
|
||||
<svg viewBox="0 0 20 20" width={20} xmlns="http://www.w3.org/2000/svg" onClick={unfitCharge}>
|
||||
<use href="#uncharge" />
|
||||
</svg>
|
||||
)}
|
||||
<svg viewBox="0 0 20 20" width={20} xmlns="http://www.w3.org/2000/svg" onClick={unfitModule}>
|
||||
<use href="#unfit" />
|
||||
</svg>
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface ShipSnapshotItem {
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
flag: number;
|
||||
charge: ShipSnapshotItem | undefined;
|
||||
state: "Passive" | "Online" | "Active" | "Overload";
|
||||
max_state: "Passive" | "Online" | "Active" | "Overload";
|
||||
attributes: Map<number, ShipSnapshotItemAttribute>;
|
||||
@@ -32,9 +33,12 @@ export interface EsiFit {
|
||||
description: string;
|
||||
ship_type_id: number;
|
||||
items: {
|
||||
flag: number;
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
flag: number;
|
||||
charge?: {
|
||||
type_id: number;
|
||||
};
|
||||
state?: string;
|
||||
}[];
|
||||
}
|
||||
@@ -59,6 +63,8 @@ interface ShipSnapshot {
|
||||
|
||||
addModule: (typeId: number, slot: ShipSnapshotSlotsType | "dronebay") => void;
|
||||
removeModule: (flag: number) => void;
|
||||
addCharge: (chargeTypeId: number) => void;
|
||||
removeCharge: (flag: number) => void;
|
||||
changeHull: (typeId: number) => void;
|
||||
changeFit: (fit: EsiFit) => void;
|
||||
setItemState: (flag: number, state: string) => void;
|
||||
@@ -76,6 +82,8 @@ export const ShipSnapshotContext = React.createContext<ShipSnapshot>({
|
||||
},
|
||||
addModule: () => {},
|
||||
removeModule: () => {},
|
||||
addCharge: () => {},
|
||||
removeCharge: () => {},
|
||||
changeHull: () => {},
|
||||
changeFit: () => {},
|
||||
setItemState: () => {},
|
||||
@@ -115,6 +123,8 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
|
||||
},
|
||||
addModule: () => {},
|
||||
removeModule: () => {},
|
||||
addCharge: () => {},
|
||||
removeCharge: () => {},
|
||||
changeHull: () => {},
|
||||
changeFit: () => {},
|
||||
setItemState: () => {},
|
||||
@@ -203,6 +213,80 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const addCharge = React.useCallback(
|
||||
(chargeTypeId: number) => {
|
||||
const chargeSize =
|
||||
eveData.typeDogma?.[chargeTypeId]?.dogmaAttributes.find(
|
||||
(attr) => attr.attributeID === eveData.attributeMapping?.chargeSize,
|
||||
)?.value ?? -1;
|
||||
const groupID = eveData.typeIDs?.[chargeTypeId]?.groupID ?? -1;
|
||||
|
||||
setCurrentFit((oldFit: EsiFit | undefined) => {
|
||||
if (oldFit === undefined) return undefined;
|
||||
|
||||
const newItems = [];
|
||||
|
||||
for (let item of oldFit.items) {
|
||||
/* If the module has size restrictions, ensure the charge matches. */
|
||||
const moduleChargeSize = eveData.typeDogma?.[item.type_id]?.dogmaAttributes.find(
|
||||
(attr) => attr.attributeID === eveData.attributeMapping?.chargeSize,
|
||||
)?.value;
|
||||
if (moduleChargeSize !== undefined && moduleChargeSize !== chargeSize) {
|
||||
newItems.push(item);
|
||||
}
|
||||
|
||||
/* Check if the charge fits in this module; if so, assign it. */
|
||||
for (const attr of eveData.typeDogma?.[item.type_id]?.dogmaAttributes ?? []) {
|
||||
switch (attr.attributeID) {
|
||||
case eveData.attributeMapping?.chargeGroup1:
|
||||
case eveData.attributeMapping?.chargeGroup2:
|
||||
case eveData.attributeMapping?.chargeGroup3:
|
||||
case eveData.attributeMapping?.chargeGroup4:
|
||||
case eveData.attributeMapping?.chargeGroup5:
|
||||
if (attr.value === groupID) {
|
||||
item = {
|
||||
...item,
|
||||
charge: {
|
||||
type_id: chargeTypeId,
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newItems.push(item);
|
||||
}
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: newItems,
|
||||
};
|
||||
});
|
||||
},
|
||||
[eveData],
|
||||
);
|
||||
|
||||
const removeCharge = React.useCallback((flag: number) => {
|
||||
setCurrentFit((oldFit: EsiFit | undefined) => {
|
||||
if (oldFit === undefined) return undefined;
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: oldFit.items.map((item) => {
|
||||
if (item.flag === flag) {
|
||||
return {
|
||||
...item,
|
||||
charge: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}),
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const changeHull = React.useCallback(
|
||||
(typeId: number) => {
|
||||
const hullName = eveData?.typeIDs?.[typeId].name;
|
||||
@@ -222,12 +306,14 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
|
||||
...oldSnapshot,
|
||||
addModule,
|
||||
removeModule,
|
||||
addCharge,
|
||||
removeCharge,
|
||||
changeHull,
|
||||
changeFit: setCurrentFit,
|
||||
setItemState,
|
||||
setName,
|
||||
}));
|
||||
}, [addModule, removeModule, changeHull, setItemState, setName]);
|
||||
}, [addModule, removeModule, addCharge, removeCharge, changeHull, setItemState, setName]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!dogmaEngine.loaded) return;
|
||||
|
||||
Reference in New Issue
Block a user