feat: component to show in detail how attributes were calculated (#31)
This includes which effects applied on them, where they came from, what their value was, etc.
This commit is contained in:
55
src/CalculationDetail/CalculationDetail.module.css
Normal file
55
src/CalculationDetail/CalculationDetail.module.css
Normal file
@@ -0,0 +1,55 @@
|
||||
.entry {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
.entry > span {
|
||||
flex: 1;
|
||||
}
|
||||
.entry > span:first-child {
|
||||
display: inline-block;
|
||||
flex: unset;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.entry > span:last-child {
|
||||
display: inline-block;
|
||||
flex: unset;
|
||||
width: 60px;
|
||||
}
|
||||
.entry:hover {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
||||
.header {
|
||||
cursor: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
.header:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.effects {
|
||||
background-color: #cccccc;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.effect {
|
||||
display: flex;
|
||||
}
|
||||
.effect > span:nth-child(3) {
|
||||
flex: 1;
|
||||
}
|
||||
.effect > span:nth-child(1) {
|
||||
width: 40px;
|
||||
}
|
||||
.effect > span:nth-child(2) {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.line:nth-child(odd) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
43
src/CalculationDetail/CalculationDetail.stories.tsx
Normal file
43
src/CalculationDetail/CalculationDetail.stories.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Decorator, Meta, StoryObj } from '@storybook/react';
|
||||
import React from "react";
|
||||
|
||||
import { fullFit } from '../../.storybook/fits';
|
||||
|
||||
import { DogmaEngineProvider } from '../DogmaEngineProvider';
|
||||
import { EveDataProvider } from '../EveDataProvider';
|
||||
import { ShipSnapshotProvider } from '../ShipSnapshotProvider';
|
||||
import { CalculationDetail } from './';
|
||||
|
||||
const meta: Meta<typeof CalculationDetail> = {
|
||||
component: CalculationDetail,
|
||||
tags: ['autodocs'],
|
||||
title: 'Component/CalculationDetail',
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CalculationDetail>;
|
||||
|
||||
const withShipSnapshotProvider: Decorator<{source: "Ship" | { Item: number }}> = (Story, context) => {
|
||||
return (
|
||||
<EveDataProvider>
|
||||
<DogmaEngineProvider>
|
||||
<ShipSnapshotProvider {...context.parameters.snapshot}>
|
||||
<Story {...context.args} />
|
||||
</ShipSnapshotProvider>
|
||||
</DogmaEngineProvider>
|
||||
</EveDataProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
source: "Ship",
|
||||
},
|
||||
decorators: [withShipSnapshotProvider],
|
||||
parameters: {
|
||||
snapshot: {
|
||||
fit: fullFit,
|
||||
skills: {},
|
||||
}
|
||||
},
|
||||
};
|
||||
117
src/CalculationDetail/CalculationDetail.tsx
Normal file
117
src/CalculationDetail/CalculationDetail.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import { EveDataContext } from "../EveDataProvider";
|
||||
import { ShipSnapshotContext, ShipSnapshotItemAttribute, ShipSnapshotItemAttributeEffect } from "../ShipSnapshotProvider";
|
||||
|
||||
import styles from "./CalculationDetail.module.css";
|
||||
import { Icon } from "../Icon";
|
||||
|
||||
const EffectOperatorOrder: Record<string, string> = {
|
||||
"PreAssign": "=",
|
||||
"PreMul": "*",
|
||||
"PreDiv": "/",
|
||||
"ModAdd": "+",
|
||||
"ModSub": "-",
|
||||
"PostMul": "*",
|
||||
"PostDiv": "/",
|
||||
"PostPercent": "%",
|
||||
"PostAssignment": "=",
|
||||
};
|
||||
|
||||
const Effect = (props: { effect: ShipSnapshotItemAttributeEffect }) => {
|
||||
const eveData = React.useContext(EveDataContext);
|
||||
const shipSnapshot = React.useContext(ShipSnapshotContext);
|
||||
|
||||
const eveAttribute = eveData.dogmaAttributes?.[props.effect.source_attribute_id];
|
||||
|
||||
let sourceName;
|
||||
let attribute;
|
||||
if (props.effect.source === "Ship") {
|
||||
sourceName = "Ship";
|
||||
attribute = shipSnapshot.hull?.attributes.get(props.effect.source_attribute_id);
|
||||
} else if (props.effect.source.Item !== undefined) {
|
||||
const item = shipSnapshot.items?.[props.effect.source.Item];
|
||||
if (item === undefined) {
|
||||
sourceName = "Unknown";
|
||||
} else {
|
||||
sourceName = eveData.typeIDs?.[item?.type_id]?.name;
|
||||
attribute = item?.attributes.get(props.effect.source_attribute_id);
|
||||
}
|
||||
}
|
||||
|
||||
return <div className={styles.effect}>
|
||||
<span>{EffectOperatorOrder[props.effect.operator]}</span>
|
||||
<span>{attribute?.value || eveAttribute?.defaultValue}{props.effect.penalty ? " (penalized)" : ""}</span>
|
||||
<span>{sourceName} - {eveAttribute?.name}</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const CalculationDetailMeta = (props: { attributeId: number, attribute: ShipSnapshotItemAttribute }) => {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const eveData = React.useContext(EveDataContext);
|
||||
|
||||
const eveAttribute = eveData.dogmaAttributes?.[props.attributeId];
|
||||
let index = 0;
|
||||
|
||||
const sortedEffects = props.attribute.effects.sort((a, b) => {
|
||||
const aIndex = Object.keys(EffectOperatorOrder).indexOf(a.operator);
|
||||
const bIndex = Object.keys(EffectOperatorOrder).indexOf(b.operator);
|
||||
if (aIndex === -1 || bIndex === -1) {
|
||||
return 0;
|
||||
}
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
return <div className={styles.line}>
|
||||
<div className={styles.entry} onClick={() => setExpanded(!expanded)}>
|
||||
<span>
|
||||
<Icon name={expanded ? "menu-expand" : "menu-collapse"} />
|
||||
</span>
|
||||
<span>{eveAttribute?.name}</span>
|
||||
<span>{props.attribute.value}</span>
|
||||
<span>{props.attribute.effects.length}</span>
|
||||
</div>
|
||||
<div className={clsx(styles.effects, { [styles.collapsed]: !expanded })}>
|
||||
<div className={styles.effect}>
|
||||
<span>=</span>
|
||||
<span>{props.attribute.base_value}</span>
|
||||
<span>base value {props.attributeId < 0 && <>(list of effects might be incomplete)</>}</span>
|
||||
</div>
|
||||
{sortedEffects.map((effect) => {
|
||||
index += 1;
|
||||
return <Effect key={index} effect={effect} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
/**
|
||||
* Show in detail for each attribute how the value came to be. This includes
|
||||
* the base value, all effects (and their source) and the final value.
|
||||
*/
|
||||
export const CalculationDetail = (props: {source: "Ship" | { Item: number }}) => {
|
||||
const shipSnapshot = React.useContext(ShipSnapshotContext);
|
||||
|
||||
let attributes;
|
||||
if (props.source === "Ship") {
|
||||
attributes = [...shipSnapshot.hull?.attributes.entries() || []];
|
||||
} else if (props.source.Item !== undefined) {
|
||||
const item = shipSnapshot.items?.[props.source.Item];
|
||||
if (item !== undefined) {
|
||||
attributes = [...item.attributes.entries()];
|
||||
}
|
||||
}
|
||||
|
||||
return <div>
|
||||
<div className={clsx(styles.entry, styles.header)}>
|
||||
<span></span>
|
||||
<span>Attribute</span>
|
||||
<span>Value</span>
|
||||
<span>Effects</span>
|
||||
</div>
|
||||
{attributes?.map(([attributeId, attribute]) => {
|
||||
return <CalculationDetailMeta key={attributeId} attributeId={attributeId} attribute={attribute} />
|
||||
})}
|
||||
</div>
|
||||
};
|
||||
1
src/CalculationDetail/index.ts
Normal file
1
src/CalculationDetail/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CalculationDetail } from "./CalculationDetail";
|
||||
@@ -2,12 +2,18 @@ import React from "react";
|
||||
|
||||
import { DogmaEngineContext } from '../DogmaEngineProvider';
|
||||
|
||||
export interface ShipSnapshotItemAttributeEffect {
|
||||
operator: string,
|
||||
penalty: boolean,
|
||||
source: "Ship" | { Item: number },
|
||||
source_category: string,
|
||||
source_attribute_id: number,
|
||||
};
|
||||
|
||||
export interface ShipSnapshotItemAttribute {
|
||||
base_value: number,
|
||||
value: number,
|
||||
effects: {
|
||||
penalty: boolean,
|
||||
}[],
|
||||
effects: ShipSnapshotItemAttributeEffect[],
|
||||
};
|
||||
|
||||
export interface ShipSnapshotItem {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { ShipSnapshotContext, ShipSnapshotProvider } from "./ShipSnapshotProvider";
|
||||
export type { EsiFit, ShipSnapshotItem, ShipSnapshotItemAttribute } from "./ShipSnapshotProvider";
|
||||
export type { EsiFit, ShipSnapshotItem, ShipSnapshotItemAttribute, ShipSnapshotItemAttributeEffect } from "./ShipSnapshotProvider";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './DogmaEngineProvider';
|
||||
export * from './CalculationDetail';
|
||||
export * from './EsiCharacterSelection';
|
||||
export * from './EsiProvider';
|
||||
export * from './EveDataProvider';
|
||||
|
||||
Reference in New Issue
Block a user