- You have a local fitting with the name "{currentFit.fit?.name}"; do you want to update it?
+ You have a local fitting with the name "{currentFit.currentFit?.name}"; do you want to update it?
saveBrowser(true)}>
diff --git a/src/components/FitHistory/FitHistory.stories.tsx b/src/components/FitHistory/FitHistory.stories.tsx
index b0e5c43..3ce8de4 100644
--- a/src/components/FitHistory/FitHistory.stories.tsx
+++ b/src/components/FitHistory/FitHistory.stories.tsx
@@ -21,7 +21,7 @@ type Story = StoryObj;
const TestStory = () => {
const currentFit = useCurrentFit();
- return Current Fit: {currentFit.fit?.name}
;
+ return Current Fit: {currentFit.currentFit?.name}
;
};
export const Default: Story = {
diff --git a/src/components/FitHistory/FitHistory.tsx b/src/components/FitHistory/FitHistory.tsx
index b0021ec..a1be1dc 100644
--- a/src/components/FitHistory/FitHistory.tsx
+++ b/src/components/FitHistory/FitHistory.tsx
@@ -27,12 +27,14 @@ export const FitHistory = (props: FitHistoryProps) => {
historySizeRef.current = props.historySize;
React.useEffect(() => {
- const fit = currentFit.fit;
- if (fit === null) return;
+ const fit = currentFit.currentFit;
+ if (fit === null || currentFit.isPreview) return;
/* Store the fit as a JSON string, to ensure that any modifications
* to the current doesn't impact the history. */
const fitString = JSON.stringify(fit);
+ /* Do not store fits in the history that have no changes. */
if (currentIndexRef.current !== -1 && historyRef.current[currentIndexRef.current] === fitString) return;
+ if (historyRef.current.length > 0 && historyRef.current[historyRef.current.length - 1] === fitString) return;
setHistory((prev) => {
if (prev.length >= historySizeRef.current) {
@@ -42,7 +44,7 @@ export const FitHistory = (props: FitHistoryProps) => {
return [...prev, fitString];
});
setCurrentIndex(-1);
- }, [currentFit.fit]);
+ }, [currentFit.currentFit, currentFit.isPreview]);
React.useEffect(() => {
if (currentIndex === -1) return;
diff --git a/src/components/HardwareListing/HardwareListing.tsx b/src/components/HardwareListing/HardwareListing.tsx
index 742198d..7afec2a 100644
--- a/src/components/HardwareListing/HardwareListing.tsx
+++ b/src/components/HardwareListing/HardwareListing.tsx
@@ -56,16 +56,21 @@ const OnItemDragStart = (
};
};
-const PreloadImage = (typeId: number): ((e: React.MouseEvent) => void) => {
- return () => {
- const img = new Image();
- img.src = `https://images.evetech.net/types/${typeId}/icon?size=64`;
- };
-};
-
const ModuleGroup = (props: { level: number; group: ListingGroup; hideGroup?: boolean }) => {
const fitManager = useFitManager();
+ const PreviewStart = React.useCallback(
+ (typeId: number, slotType: CalculationSlotType): void => {
+ if (slotType === "DroneBay") return;
+ fitManager.addItem(typeId, slotType, true);
+ },
+ [fitManager],
+ );
+
+ const PreviewEnd = React.useCallback((): void => {
+ fitManager.removePreview();
+ }, [fitManager]);
+
const getChildren = React.useCallback(() => {
return (
<>
@@ -78,9 +83,13 @@ const ModuleGroup = (props: { level: number; group: ListingGroup; hideGroup?: bo
key={item.typeId}
level={2}
content={item.name}
- onDoubleClick={() => fitManager.addItem(item.typeId, slotType)}
+ onDoubleClick={() => {
+ PreviewEnd();
+ fitManager.addItem(item.typeId, slotType);
+ }}
onDragStart={OnItemDragStart(item.typeId, slotType)}
- onMouseEnter={PreloadImage(item.typeId)}
+ onMouseEnter={() => PreviewStart(item.typeId, slotType)}
+ onMouseLeave={() => PreviewEnd()}
/>
);
})}
@@ -95,7 +104,7 @@ const ModuleGroup = (props: { level: number; group: ListingGroup; hideGroup?: bo
})}
>
);
- }, [fitManager, props.group, props.level]);
+ }, [fitManager, props.group, props.level, PreviewStart, PreviewEnd]);
if (props.hideGroup) {
return ;
diff --git a/src/components/HullListing/HullListing.tsx b/src/components/HullListing/HullListing.tsx
index 1470a4a..96c393d 100644
--- a/src/components/HullListing/HullListing.tsx
+++ b/src/components/HullListing/HullListing.tsx
@@ -213,7 +213,7 @@ export const HullListing = () => {
if (hull.marketGroupID === undefined) continue;
if (!hull.published) continue;
- if (filter.currentHull && currentFit.fit?.shipTypeId !== parseInt(typeId)) continue;
+ if (filter.currentHull && currentFit.currentFit?.shipTypeId !== parseInt(typeId)) continue;
const fits: ListingFit[] = [];
if (anyFilter) {
@@ -221,7 +221,7 @@ export const HullListing = () => {
if (filter.characterFits && Object.keys(characterFitsGrouped).includes(typeId))
fits.push(...characterFitsGrouped[typeId]);
if (fits.length == 0) {
- if (!filter.currentHull || currentFit.fit?.shipTypeId !== parseInt(typeId)) continue;
+ if (!filter.currentHull || currentFit.currentFit?.shipTypeId !== parseInt(typeId)) continue;
}
} else {
if (Object.keys(localFitsGrouped).includes(typeId)) fits.push(...localFitsGrouped[typeId]);
diff --git a/src/components/ShipAttribute/ShipAttribute.module.css b/src/components/ShipAttribute/ShipAttribute.module.css
new file mode 100644
index 0000000..12c81f1
--- /dev/null
+++ b/src/components/ShipAttribute/ShipAttribute.module.css
@@ -0,0 +1,7 @@
+.increase {
+ color: #8dc169;
+}
+
+.decrease {
+ color: #ff454b;
+}
diff --git a/src/components/ShipAttribute/ShipAttribute.tsx b/src/components/ShipAttribute/ShipAttribute.tsx
index 9305dda..3f19258 100644
--- a/src/components/ShipAttribute/ShipAttribute.tsx
+++ b/src/components/ShipAttribute/ShipAttribute.tsx
@@ -1,11 +1,16 @@
+import clsx from "clsx";
import React from "react";
import { useEveData } from "@/providers/EveDataProvider";
-import { useStatistics } from "@/providers/StatisticsProvider";
+import { useCurrentStatistics, useStatistics } from "@/providers/StatisticsProvider";
+
+import styles from "./ShipAttribute.module.css";
export interface AttributeProps {
/** Name of the attribute. */
name: string;
+ /** The unit of the attribute, used as postfix. */
+ unit?: string;
/** How many decimals to render. */
fixed: number;
/** Whether this is a resistance attribute. */
@@ -16,28 +21,55 @@ export interface AttributeProps {
roundDown?: boolean;
}
+export enum AttributeChange {
+ Increase = "Increase",
+ Decrease = "Decrease",
+ Unchanged = "Unchanged",
+ Unknown = "Unknown",
+}
+
/**
* Return the value of a ship's attribute.
*/
-export function useAttribute(type: "Ship" | "Char", props: AttributeProps) {
+export function useAttribute(type: "Ship" | "Char", props: AttributeProps): { value: string; change: AttributeChange } {
const eveData = useEveData();
const statistics = useStatistics();
+ const currentStatistics = useCurrentStatistics();
+
+ const attributeId = eveData?.attributeMapping[props.name] ?? 0;
let value;
+ let currentValue;
if (eveData === null || statistics === null) {
value = 0;
+ currentValue = 0;
} else {
- const attributeId = eveData.attributeMapping[props.name] ?? 0;
-
if (type === "Ship") {
value = statistics.hull.attributes.get(attributeId)?.value;
+ currentValue = currentStatistics?.hull.attributes.get(attributeId)?.value;
} else {
value = statistics.char.attributes.get(attributeId)?.value;
+ currentValue = currentStatistics?.char.attributes.get(attributeId)?.value;
}
}
if (value === undefined) {
- return "?";
+ return {
+ value: "?",
+ change: AttributeChange.Unknown,
+ };
+ }
+
+ let change = AttributeChange.Unchanged;
+ if (currentValue !== undefined && currentValue !== value) {
+ const highIsGood =
+ props.isResistance || props.name === "mass" ? false : eveData?.dogmaAttributes[attributeId]?.highIsGood;
+
+ if (currentValue < value) {
+ change = highIsGood ? AttributeChange.Increase : AttributeChange.Decrease;
+ } else {
+ change = highIsGood ? AttributeChange.Decrease : AttributeChange.Increase;
+ }
}
if (props.isResistance) {
@@ -65,26 +97,61 @@ export function useAttribute(type: "Ship" | "Char", props: AttributeProps) {
value = 0;
}
- return value.toLocaleString("en", {
- minimumFractionDigits: props.fixed,
- maximumFractionDigits: props.fixed,
- });
+ return {
+ value: value.toLocaleString("en", {
+ minimumFractionDigits: props.fixed,
+ maximumFractionDigits: props.fixed,
+ }),
+ change,
+ };
}
/**
* Render a single ship attribute of a ship's snapshot.
*/
export const ShipAttribute = (props: AttributeProps) => {
- const stringValue = useAttribute("Ship", props);
+ const { value, change } = useAttribute("Ship", props);
+ const prefix =
+ props.unit === undefined
+ ? ""
+ : props.unit === "s" || props.unit === "x" || props.unit === "%"
+ ? props.unit
+ : ` ${props.unit}`;
- return {stringValue};
+ return (
+
+ {value}
+ {prefix}
+
+ );
};
/**
* Render a single character attribute of a ship's snapshot.
*/
export const CharAttribute = (props: AttributeProps) => {
- const stringValue = useAttribute("Char", props);
+ const { value, change } = useAttribute("Char", props);
+ const prefix =
+ props.unit === undefined
+ ? ""
+ : props.unit === "s" || props.unit === "x" || props.unit === "%"
+ ? props.unit
+ : ` ${props.unit}`;
- return {stringValue};
+ return (
+
+ {value}
+ {prefix}
+
+ );
};
diff --git a/src/components/ShipFit/Hull.tsx b/src/components/ShipFit/Hull.tsx
index 8b792b1..a512280 100644
--- a/src/components/ShipFit/Hull.tsx
+++ b/src/components/ShipFit/Hull.tsx
@@ -10,11 +10,11 @@ export interface ShipFitProps {
export const Hull = () => {
const currentFit = useCurrentFit();
- if (currentFit.fit === null) {
+ if (currentFit.currentFit === null) {
return <>>;
}
- const shipTypeId = currentFit.fit.shipTypeId;
+ const shipTypeId = currentFit.currentFit.shipTypeId;
if (shipTypeId === undefined) {
return <>>;
}
diff --git a/src/components/ShipFit/ShipFit.module.css b/src/components/ShipFit/ShipFit.module.css
index 2f71bbc..7d03e95 100644
--- a/src/components/ShipFit/ShipFit.module.css
+++ b/src/components/ShipFit/ShipFit.module.css
@@ -209,3 +209,7 @@
transform: rotate(var(--reverse-rotation));
stroke: #ffffff;
}
+
+.preview {
+ filter: sepia(100%) hue-rotate(190deg) saturate(200%);
+}
diff --git a/src/components/ShipFit/Slot.tsx b/src/components/ShipFit/Slot.tsx
index 739c38b..04e651d 100644
--- a/src/components/ShipFit/Slot.tsx
+++ b/src/components/ShipFit/Slot.tsx
@@ -1,3 +1,4 @@
+import clsx from "clsx";
import React from "react";
import { Icon, IconName } from "@/components/Icon";
@@ -5,7 +6,7 @@ import { useEveData } from "@/providers/EveDataProvider";
import { useStatistics } from "@/providers/StatisticsProvider";
import { useFitManager } from "@/providers/FitManagerProvider";
import { CalculationSlot } from "@/providers/DogmaEngineProvider";
-import { EsfSlot, EsfSlotType, EsfState } from "@/providers/CurrentFitProvider";
+import { EsfSlot, EsfSlotType, EsfState, useCurrentFit } from "@/providers/CurrentFitProvider";
import styles from "./ShipFit.module.css";
@@ -20,8 +21,12 @@ export const Slot = (props: { type: EsfSlotType; index: number; fittable: boolea
const eveData = useEveData();
const statistics = useStatistics();
const fitManager = useFitManager();
+ const currentFit = useCurrentFit();
const module = statistics?.items.find((item) => item.slot.type === props.type && item.slot.index === props.index);
+ const fitModule = currentFit?.fit?.modules.find(
+ (item) => item.slot.type === props.type && item.slot.index === props.index,
+ );
const active = module?.max_state !== "Passive" && module?.max_state !== "Online";
const offlineState = React.useCallback(
@@ -199,8 +204,12 @@ export const Slot = (props: { type: EsfSlotType; index: number; fittable: boolea
preserveAspectRatio="xMidYMin slice"
>
- {props.fittable && module !== undefined && active && }
- {props.fittable && module !== undefined && !active && }
+ {props.fittable && fitModule?.state !== "Preview" && module !== undefined && active && (
+
+ )}
+ {props.fittable && fitModule?.state !== "Preview" && module !== undefined && !active && (
+
+ )}
>
);
@@ -233,6 +242,7 @@ export const Slot = (props: { type: EsfSlotType; index: number; fittable: boolea
title={eveData.typeIDs[module.type_id].name}
draggable={true}
onDragStart={onDragStart}
+ className={clsx({ [styles.preview]: fitModule?.state === "Preview" })}
/>
);
}
diff --git a/src/components/ShipFitExtended/ShipFitExtended.stories.tsx b/src/components/ShipFitExtended/ShipFitExtended.stories.tsx
index 5bb60dc..1e5a1e8 100644
--- a/src/components/ShipFitExtended/ShipFitExtended.stories.tsx
+++ b/src/components/ShipFitExtended/ShipFitExtended.stories.tsx
@@ -6,6 +6,7 @@ import { useFitSelection, withDecoratorFull } from "../../../.storybook/helpers"
import { HardwareListing } from "@/components/HardwareListing";
import { HullListing } from "@/components/HullListing";
+import { ShipStatistics } from "@/components/ShipStatistics";
import { EsfFit } from "@/providers/CurrentFitProvider";
import { ShipFitExtended } from "./";
@@ -52,7 +53,7 @@ export const WithHardwareListing: Story = {
useFitSelection(fit);
return (
-
+
@@ -60,6 +61,10 @@ export const WithHardwareListing: Story = {
+
+
+
+
);
},
@@ -77,7 +82,7 @@ export const WithHullListing: Story = {
useFitSelection(fit);
return (
-
+
@@ -85,6 +90,10 @@ export const WithHullListing: Story = {
+
+
+
+
);
},
diff --git a/src/components/ShipFitExtended/ShipFitExtended.tsx b/src/components/ShipFitExtended/ShipFitExtended.tsx
index 7a2e4c2..140a6a1 100644
--- a/src/components/ShipFitExtended/ShipFitExtended.tsx
+++ b/src/components/ShipFitExtended/ShipFitExtended.tsx
@@ -36,7 +36,7 @@ const ShipDroneBay = () => {
if (eveData === null) return <>>;
- const isStructure = eveData.typeIDs[currentFit.fit?.shipTypeId ?? 0]?.categoryID === 65;
+ const isStructure = eveData.typeIDs[currentFit.currentFit?.shipTypeId ?? 0]?.categoryID === 65;
return (
<>
@@ -77,7 +77,7 @@ const FitName = () => {
return (
<>
Name
-
{currentFit.fit?.name}
+
{currentFit.currentFit?.name}
>
);
};
@@ -118,7 +118,7 @@ export const ShipFitExtended = () => {
- {currentFit.fit === null &&
To start, select a hull on the left.
}
+ {currentFit.currentFit === null &&
To start, select a hull on the left.
}
);
};
diff --git a/src/components/ShipStatistics/RechargeRate.tsx b/src/components/ShipStatistics/RechargeRate.tsx
index 4bada50..13230ca 100644
--- a/src/components/ShipStatistics/RechargeRate.tsx
+++ b/src/components/ShipStatistics/RechargeRate.tsx
@@ -2,17 +2,17 @@ import clsx from "clsx";
import React from "react";
import { IconName, Icon } from "@/components/Icon";
-import { useAttribute } from "@/components/ShipAttribute";
+import { ShipAttribute, useAttribute } from "@/components/ShipAttribute";
import styles from "./ShipStatistics.module.css";
export const RechargeRateItem = (props: { name: string; icon: IconName }) => {
- const stringValue = useAttribute("Ship", {
+ const { value } = useAttribute("Ship", {
name: props.name,
fixed: 1,
});
- if (stringValue == "0.0") {
+ if (value == "0.0") {
return (
@@ -28,7 +28,7 @@ export const RechargeRateItem = (props: { name: string; icon: IconName }) => {
- {stringValue} hp/s
+
);
};
diff --git a/src/components/ShipStatistics/Resistance.tsx b/src/components/ShipStatistics/Resistance.tsx
index d7a2ddb..5e0aa84 100644
--- a/src/components/ShipStatistics/Resistance.tsx
+++ b/src/components/ShipStatistics/Resistance.tsx
@@ -1,11 +1,11 @@
import React from "react";
-import { useAttribute } from "@/components/ShipAttribute";
+import { ShipAttribute, useAttribute } from "@/components/ShipAttribute";
import styles from "./ShipStatistics.module.css";
export const Resistance = (props: { name: string }) => {
- const stringValue = useAttribute("Ship", {
+ const { value } = useAttribute("Ship", {
name: props.name,
fixed: 0,
isResistance: true,
@@ -25,8 +25,8 @@ export const Resistance = (props: { name: string }) => {
return (
-
- {stringValue} %
+
+
);
};
diff --git a/src/components/ShipStatistics/ShipStatistics.tsx b/src/components/ShipStatistics/ShipStatistics.tsx
index 0ab4ae6..c29913b 100644
--- a/src/components/ShipStatistics/ShipStatistics.tsx
+++ b/src/components/ShipStatistics/ShipStatistics.tsx
@@ -22,7 +22,7 @@ export const ShipStatistics = () => {
const statistics = useStatistics();
let capacitorState = "Stable";
- const isStructure = eveData?.typeIDs[currentFit.fit?.shipTypeId ?? 0]?.categoryID === 65;
+ const isStructure = eveData?.typeIDs[currentFit.currentFit?.shipTypeId ?? 0]?.categoryID === 65;
const attributeId = eveData?.attributeMapping.capacitorDepletesIn ?? 0;
const capacitorDepletesIn = statistics?.hull.attributes.get(attributeId)?.value;
@@ -55,15 +55,14 @@ export const ShipStatistics = () => {
>
- GJ /{" "}
- s
+ /{" "}
+
- Δ GJ/s (
-
- %)
+ Δ (
+ )
@@ -72,7 +71,7 @@ export const ShipStatistics = () => {
headerLabel="Offense"
headerContent={
- dps
+
}
>
@@ -82,8 +81,8 @@ export const ShipStatistics = () => {
- dps (
- dps)
+ (
+ )
@@ -91,7 +90,7 @@ export const ShipStatistics = () => {
- HP
+
@@ -101,7 +100,7 @@ export const ShipStatistics = () => {
headerLabel="Defense"
headerContent={
- ehp
+
}
>
@@ -128,9 +127,10 @@ export const ShipStatistics = () => {
- hp
+
+
+
- s
@@ -146,7 +146,7 @@ export const ShipStatistics = () => {
- hp
+
@@ -162,7 +162,7 @@ export const ShipStatistics = () => {
- hp
+
@@ -178,7 +178,7 @@ export const ShipStatistics = () => {
headerLabel="Targeting"
headerContent={
- km
+
}
>
@@ -188,7 +188,7 @@ export const ShipStatistics = () => {
- points
+
@@ -196,7 +196,7 @@ export const ShipStatistics = () => {
- mm
+
@@ -206,7 +206,7 @@ export const ShipStatistics = () => {
- m
+
@@ -214,7 +214,7 @@ export const ShipStatistics = () => {
- x
+
@@ -225,7 +225,7 @@ export const ShipStatistics = () => {
headerLabel="Navigation"
headerContent={
- m/s
+
}
>
@@ -235,7 +235,7 @@ export const ShipStatistics = () => {
- t
+
@@ -243,7 +243,7 @@ export const ShipStatistics = () => {
- x
+
@@ -253,7 +253,7 @@ export const ShipStatistics = () => {
- AU/s
+
@@ -261,7 +261,7 @@ export const ShipStatistics = () => {
- s
+
@@ -273,7 +273,7 @@ export const ShipStatistics = () => {
headerLabel="Drones"
headerContent={
- dps
+
}
>
@@ -292,7 +292,7 @@ export const ShipStatistics = () => {
- km
+
diff --git a/src/components/TreeListing/TreeListing.tsx b/src/components/TreeListing/TreeListing.tsx
index fd25494..556c9d5 100644
--- a/src/components/TreeListing/TreeListing.tsx
+++ b/src/components/TreeListing/TreeListing.tsx
@@ -56,6 +56,7 @@ export const TreeLeaf = (props: {
onDoubleClick?: (e: React.MouseEvent
) => void;
onDragStart?: (e: React.DragEvent) => void;
onMouseEnter?: (e: React.MouseEvent) => void;
+ onMouseLeave?: (e: React.MouseEvent) => void;
}) => {
const stylesHeader = styles[`header${props.level}`];
@@ -76,6 +77,7 @@ export const TreeLeaf = (props: {
draggable={!!props.onDragStart}
onDragStart={props.onDragStart}
onMouseEnter={props.onMouseEnter}
+ onMouseLeave={props.onMouseLeave}
>
{props.icon !== undefined && (
diff --git a/src/hooks/ExportEft/ExportEft.tsx b/src/hooks/ExportEft/ExportEft.tsx
index cbd01a1..4b24669 100644
--- a/src/hooks/ExportEft/ExportEft.tsx
+++ b/src/hooks/ExportEft/ExportEft.tsx
@@ -22,7 +22,7 @@ export function useExportEft() {
const statistics = useStatistics();
return (): string | null => {
- const fit = currentFit.fit;
+ const fit = currentFit.currentFit;
if (eveData === null || fit === null || statistics === null) return null;
diff --git a/src/hooks/ExportEveShipFitHash/ExportEveShipFitHash.tsx b/src/hooks/ExportEveShipFitHash/ExportEveShipFitHash.tsx
index 0abb028..e8138d3 100644
--- a/src/hooks/ExportEveShipFitHash/ExportEveShipFitHash.tsx
+++ b/src/hooks/ExportEveShipFitHash/ExportEveShipFitHash.tsx
@@ -55,8 +55,8 @@ export function useExportEveShipFitHash(hashOnly?: boolean) {
setFitHash((hashOnly ? "" : "https://eveship.fit/") + `#fit:${newFitHash}`);
}
- createHash(currentFit.fit);
- }, [currentFit.fit, hashOnly]);
+ createHash(currentFit.currentFit);
+ }, [currentFit.currentFit, hashOnly]);
return fitHash;
}
diff --git a/src/providers/CurrentFitProvider/CurrentFitProvider.stories.tsx b/src/providers/CurrentFitProvider/CurrentFitProvider.stories.tsx
index 469d723..19004cb 100644
--- a/src/providers/CurrentFitProvider/CurrentFitProvider.stories.tsx
+++ b/src/providers/CurrentFitProvider/CurrentFitProvider.stories.tsx
@@ -24,7 +24,7 @@ const TestStory = ({ fit }: { fit: EsfFit | null }) => {
currentFit.setFit(fit ?? null);
});
- return {JSON.stringify(currentFit.fit, null, 2)};
+ return {JSON.stringify(currentFit.currentFit, null, 2)};
};
export const Default: Story = {
diff --git a/src/providers/CurrentFitProvider/CurrentFitProvider.tsx b/src/providers/CurrentFitProvider/CurrentFitProvider.tsx
index 961b3c7..383d65a 100644
--- a/src/providers/CurrentFitProvider/CurrentFitProvider.tsx
+++ b/src/providers/CurrentFitProvider/CurrentFitProvider.tsx
@@ -15,7 +15,7 @@ export interface EsfSlot {
export interface EsfModule {
typeId: number;
slot: EsfSlot;
- state: EsfState;
+ state: EsfState | "Preview";
charge?: EsfCharge;
}
@@ -42,13 +42,24 @@ export interface EsfFit {
}
interface CurrentFit {
+ /** The current fit to render. */
fit: EsfFit | null;
+ /** The current fit, regardless of preview state. */
+ currentFit: EsfFit | null;
+ /** Whether the fit is a preview. */
+ isPreview: boolean;
+ /** Set the current fit. */
setFit: React.Dispatch>;
+ /** Set a (temporary) preview fit, for the over-over effect on modules. */
+ setPreview: React.Dispatch>;
}
const CurrentFitContext = React.createContext({
fit: null,
+ currentFit: null,
+ isPreview: false,
setFit: () => {},
+ setPreview: () => {},
});
export const useCurrentFit = () => {
@@ -73,13 +84,17 @@ interface CurrentFitProps {
*/
export const CurrentFitProvider = (props: CurrentFitProps) => {
const [currentFit, setCurrentFit] = React.useState(props.initialFit ?? null);
+ const [previewFit, setPreviewFit] = React.useState(null);
const contextValue = React.useMemo(() => {
return {
- fit: currentFit,
+ fit: previewFit ?? currentFit,
+ currentFit: currentFit,
+ isPreview: previewFit !== null,
setFit: setCurrentFit,
+ setPreview: setPreviewFit,
};
- }, [currentFit, setCurrentFit]);
+ }, [previewFit, currentFit]);
return {props.children};
};
diff --git a/src/providers/DogmaEngineProvider/DogmaEngineProvider.tsx b/src/providers/DogmaEngineProvider/DogmaEngineProvider.tsx
index d8349e7..ce21b38 100644
--- a/src/providers/DogmaEngineProvider/DogmaEngineProvider.tsx
+++ b/src/providers/DogmaEngineProvider/DogmaEngineProvider.tsx
@@ -103,7 +103,7 @@ export const DogmaEngineProvider = (props: DogmaEngineProps) => {
modules: fit.modules.map((module) => ({
type_id: module.typeId,
slot: module.slot,
- state: module.state,
+ state: module.state === "Preview" ? "Active" : module.state,
charge:
module.charge === undefined
? undefined
diff --git a/src/providers/FitManagerProvider/FitManagerProvider.tsx b/src/providers/FitManagerProvider/FitManagerProvider.tsx
index 265244e..fd2c2ce 100644
--- a/src/providers/FitManagerProvider/FitManagerProvider.tsx
+++ b/src/providers/FitManagerProvider/FitManagerProvider.tsx
@@ -7,13 +7,15 @@ import { useEveData } from "@/providers/EveDataProvider";
interface FitManager {
/** Set the current fit. */
setFit: (fit: EsfFit) => void;
+ /** Remove the preview fit, reverting back to the actual fit. */
+ removePreview: () => void;
/** Create a new fit of the given ship type. */
createNewFit: (typeId: number) => void;
/** Set the name of the current fit. */
setName: (name: string) => void;
/** Add an item (module, charge, drone) to the fit. */
- addItem: (typeId: number, slot: EsfSlotType | "DroneBay" | "Charge") => void;
+ addItem: (typeId: number, slot: EsfSlotType | "DroneBay" | "Charge", preview?: boolean) => void;
/** Set a module in a slot. */
setModule: (slot: EsfSlot, typeId: number) => void;
@@ -37,6 +39,7 @@ interface FitManager {
const FitManagerContext = React.createContext({
setFit: () => {},
+ removePreview: () => {},
createNewFit: () => {},
setName: () => {},
@@ -71,11 +74,16 @@ export const FitManagerProvider = (props: FitManagerProps) => {
const currentFit = useCurrentFit();
const statistics = useStatistics();
const setFit = currentFit.setFit;
+ const setPreview = currentFit.setPreview;
+
+ const currentFitRef = React.useRef(currentFit.currentFit);
+ currentFitRef.current = currentFit.currentFit;
const contextValue = React.useMemo(() => {
if (eveData === null) {
return {
setFit: () => {},
+ removePreview: () => {},
createNewFit: () => {},
setName: () => {},
@@ -98,6 +106,9 @@ export const FitManagerProvider = (props: FitManagerProps) => {
setFit: (fit: EsfFit) => {
setFit(fit);
},
+ removePreview: () => {
+ setPreview(null);
+ },
createNewFit: (typeId: number) => {
setFit({
name: "Unnamed Fit",
@@ -119,8 +130,11 @@ export const FitManagerProvider = (props: FitManagerProps) => {
});
},
- addItem: (typeId: number, slot: EsfSlotType | "DroneBay" | "Charge") => {
- setFit((oldFit: EsfFit | null): EsfFit | null => {
+ addItem: (typeId: number, slot: EsfSlotType | "DroneBay" | "Charge", preview?: boolean) => {
+ const setFitOrPreview = preview ? setPreview : setFit;
+ setFitOrPreview((oldFit: EsfFit | null): EsfFit | null => {
+ /* Previews are always based on the current fit. */
+ if (preview) oldFit = currentFitRef.current;
if (oldFit === null) return null;
if (slot === "Charge") {
@@ -217,7 +231,7 @@ export const FitManagerProvider = (props: FitManagerProps) => {
},
typeId: typeId,
charge: undefined,
- state: "Active",
+ state: preview ? "Preview" : "Active",
},
],
};
@@ -414,7 +428,7 @@ export const FitManagerProvider = (props: FitManagerProps) => {
});
},
};
- }, [eveData, statistics?.slots, setFit]);
+ }, [eveData, statistics?.slots, setFit, setPreview]);
return {props.children};
};
diff --git a/src/providers/StatisticsProvider/StatisticsProvider.stories.tsx b/src/providers/StatisticsProvider/StatisticsProvider.stories.tsx
index fa0ad52..87da62a 100644
--- a/src/providers/StatisticsProvider/StatisticsProvider.stories.tsx
+++ b/src/providers/StatisticsProvider/StatisticsProvider.stories.tsx
@@ -25,11 +25,11 @@ const TestStory = ({ fit }: { fit: EsfFit | null }) => {
const currentFit = useCurrentFit();
const statistics = useStatistics();
- if (fit != currentFit.fit) {
+ if (fit != currentFit.currentFit) {
currentFit.setFit(fit);
}
- if (currentFit.fit === null) {
+ if (currentFit.currentFit === null) {
return No fit selected
;
}
if (statistics === null) {
diff --git a/src/providers/StatisticsProvider/StatisticsProvider.tsx b/src/providers/StatisticsProvider/StatisticsProvider.tsx
index 0dbaf05..4c0812a 100644
--- a/src/providers/StatisticsProvider/StatisticsProvider.tsx
+++ b/src/providers/StatisticsProvider/StatisticsProvider.tsx
@@ -26,10 +26,18 @@ interface Statistics extends Calculation {
slots: StatisticsSlots;
}
-const StatisticsContext = React.createContext(null);
+const StatisticsContext = React.createContext<{ statistics: Statistics | null; current: Statistics | null } | null>(
+ null,
+);
export const useStatistics = () => {
- return React.useContext(StatisticsContext);
+ const statistics = React.useContext(StatisticsContext);
+ return statistics === null ? null : statistics.statistics;
+};
+
+export const useCurrentStatistics = () => {
+ const statistics = React.useContext(StatisticsContext);
+ return statistics === null ? null : statistics.current ?? statistics.statistics;
};
interface StatisticsProps {
@@ -78,6 +86,10 @@ export const StatisticsProvider = (props: StatisticsProps) => {
const currentCharacter = useCurrentCharacter();
const dogmaEngine = useDogmaEngine();
+ const [lastStatistics, setLastStatistics] = React.useState(null);
+ const lastStatisticsRef = React.useRef(lastStatistics);
+ lastStatisticsRef.current = lastStatistics;
+
const contextValue = React.useMemo(() => {
const fit = currentFit.fit;
const skills = currentCharacter.character?.skills;
@@ -101,8 +113,15 @@ export const StatisticsProvider = (props: StatisticsProps) => {
CalculateSlots(eveData, statistics);
- return statistics;
- }, [eveData, dogmaEngine, currentFit.fit, currentCharacter.character?.skills]);
+ if (!currentFit.isPreview) {
+ setLastStatistics(statistics);
+ }
+
+ return {
+ statistics: statistics,
+ current: currentFit.isPreview ? lastStatisticsRef.current : statistics,
+ };
+ }, [eveData, dogmaEngine, currentFit.fit, currentFit.isPreview, currentCharacter.character?.skills]);
return {props.children};
};
diff --git a/src/providers/StatisticsProvider/index.ts b/src/providers/StatisticsProvider/index.ts
index 6ecb754..a6fea0c 100644
--- a/src/providers/StatisticsProvider/index.ts
+++ b/src/providers/StatisticsProvider/index.ts
@@ -1,2 +1,2 @@
-export { StatisticsProvider, useStatistics } from "./StatisticsProvider";
+export { StatisticsProvider, useStatistics, useCurrentStatistics } from "./StatisticsProvider";
export type { StatisticsSlots, StatisticsSlotType } from "./StatisticsProvider";