chore: rework ShipFit to use SVGs for better pixel-perfect rendering (#41)

This commit is contained in:
Patric Stout
2023-12-10 12:42:46 +01:00
committed by GitHub
parent e60aa71230
commit aa76928b5f
14 changed files with 315 additions and 191 deletions

View File

@@ -50,7 +50,7 @@ export const FitLink = () => {
onClick: isRemoteViewer ? undefined : linkPropsClick,
};
return <div className={styles.fitlink}>
return <div className={styles.fitLink}>
<svg viewBox="0 0 730 730" xmlns="http://www.w3.org/2000/svg">
<path
id="fitlink"

View File

@@ -17,8 +17,6 @@ export const Hull = () => {
}
return <div className={styles.hull}>
<div className={styles.hullInner}>
<img src={`https://images.evetech.net/types/${hull}/render?size=1024`} />
</div>
<img src={`https://images.evetech.net/types/${hull}/render?size=1024`} />
</div>
}

View File

@@ -0,0 +1,48 @@
import React from "react";
import styles from "./ShipFit.module.css";
const highlightSettings = {
lowslot: {
width: 12,
height: 3,
x: 0,
y: 9,
},
medslot: {
width: 3,
height: 12,
x: 9,
y: 0,
},
hislot: {
width: 12,
height: 3,
x: 0,
y: 0,
},
};
export const RadialMenu = (props: { type: "lowslot" | "medslot" | "hislot" }) => {
const highlight = highlightSettings[props.type];
return <svg viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" className={styles.radialMenu}>
<defs>
<mask id={`radial-menu-${props.type}`}>
<rect style={{ fill: "#ffffff", fillOpacity: 0.5 }} width={12} height={12} x={0} y={0} />
<rect style={{ fill: "#ffffff" }} width={highlight.width} height={highlight.height} x={highlight.x} y={highlight.y} />
<circle style={{ fill: "#000000" }} cx={6} cy={6} r={5} />
<rect style={{ fill: "#000000" }} width={3} height={3} x={0} y={0} />
<rect style={{ fill: "#000000" }} width={4} height={3} x={9} y={0} />
<rect style={{ fill: "#000000" }} width={3} height={4} x={0} y={9} />
<rect style={{ fill: "#000000" }} width={4} height={4} x={9} y={9} />
</mask>
</defs>
<g>
<rect style={{ fill: "#ffffff" }} width={12} height={12} x={0} y={0} mask={`url(#radial-menu-${props.type})`} />
</g>
</svg>
}

23
src/ShipFit/RingInner.tsx Normal file
View File

@@ -0,0 +1,23 @@
import React from "react";
import styles from "./ShipFit.module.css";
export const RingInner = () => {
return <svg viewBox="24 24 464 464" xmlns="http://www.w3.org/2000/svg" className={styles.ringInner}>
<defs>
<mask id="slot-corners">
<rect style={{ fill: "#ffffff" }} width="512" height="512" x="0" y="0" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="133" y="126" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="366" y="129" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="366" y="366" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="132" y="369" />
<rect style={{ fill: "#000000" }} width="12" height="12" x="230" y="44" transform="rotate(56)" />
</mask>
</defs>
<g>
<circle style={{ fill: "none", stroke: "#000000", strokeWidth: 46, strokeOpacity: 0.6 }} cx="256" cy="256" r="195" mask="url(#slot-corners)" />
</g>
</svg>
}

17
src/ShipFit/RingOuter.tsx Normal file
View File

@@ -0,0 +1,17 @@
import React from "react";
import styles from "./ShipFit.module.css";
export const RingOuter = () => {
return <svg viewBox="24 24 464 464" xmlns="http://www.w3.org/2000/svg" className={styles.ringOuter}>
<g>
<circle style={{ fill: "none", stroke: "#000000", strokeWidth: 16 }} cx="256" cy="256" r="224" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="98" y="89" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="401" y="93" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="402" y="401" />
<rect style={{ fill: "#000000" }} width="17" height="17" x="94" y="402" />
<rect style={{ fill: "#000000" }} width="12" height="12" x="196" y="82" transform="rotate(56)" />
</g>
</svg>
}

19
src/ShipFit/RingTop.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from "react";
import styles from "./ShipFit.module.css";
export const RingTop = (props: { children: React.ReactNode }) => {
return <div className={styles.ringTop}>
{props.children}
</div>
}
export const RingTopItem = (props: { children: React.ReactNode, rotation: number }) => {
const rotationStyle = {
"--rotation": `${props.rotation}deg`,
} as React.CSSProperties;
return <div className={styles.ringTopItem} style={rotationStyle}>
{props.children}
</div>
}

View File

@@ -1,121 +1,24 @@
.fit {
border-radius: 50%;
border: 1px solid black;
height: calc(var(--radius) * 2);
height: 100%;
position: relative;
width: calc(var(--radius) * 2);
--radius-slots: calc(var(--radius) * 0.92);
width: 100%;
}
.outerBand {
border-radius: 50%;
border: calc((var(--radius) - var(--radius-slots)) * 0.8) solid black;
box-sizing: border-box;
height: calc(var(--radius) * 2);
.ringOuter {
filter: drop-shadow(0 0 6px #000000);
position: absolute;
width: calc(var(--radius) * 2);
width: 100%;
z-index: 2;
}
.innerBand {
border-radius: 50%;
border: calc(var(--radius-slots) / 6 + var(--radius) - var(--radius-slots)) solid black;
box-sizing: border-box;
height: calc(var(--radius) * 2);
opacity: 0.5;
.ringInner {
position: absolute;
width: calc(var(--radius) * 2);
z-index: 3;
width: 100%;
z-index: 1;
}
.hull {
height: 1px;
left: var(--radius);
position: absolute;
top: var(--radius);
width: 1px;
}
.hullInner {
margin-left: calc(var(--radius) * -1);
margin-top: calc(var(--radius) * -1);
}
.hullInner > img {
border-radius: 50%;
height: calc(var(--radius) * 2);
width: calc(var(--radius) * 2);
}
.slots {
margin-left: calc(var(--radius) - var(--radius-slots));
margin-top: calc(var(--radius) - var(--radius-slots));
position: relative;
}
.slot {
height: 1px;
left: var(--radius-slots);
position: absolute;
transform-origin: 0 var(--radius-slots);
transform: rotate(var(--rotation));
width: 1px;
z-index: 4;
}
.slotInner {
border-top-left-radius: 100% 4px;
border-top-right-radius: 100% 4px;
border: 1px solid black;
box-sizing: border-box;
height: calc(var(--radius-slots) / 7);
left: calc(var(--radius-slots) / 7 / 2 * -1);
position: absolute;
top: 2px;
transform: perspective(8px) rotateX(-1.5deg);
width: calc(var(--radius-slots) / 7);
}
.slotInnerInvalid {
border: 1px solid red;
}
.slotInner[data-state="Passive"] {
background-color: #0000002d;
border-color: #6c6c6c9f;
opacity: 0.3;
}
.slotInner[data-state="Online"] {
background-color: #9595952d;
border-color: #9595959f;
}
.slotInner[data-state="Active"] {
background-color: #87d3282d;
border-color: #87d3289f;
}
.slotInner[data-state="Overload"] {
background-color: #e8342b2d;
border-color: #e8342b9f;
}
.slotItem {
--reverse-rotation: calc(-1 * var(--rotation));
left: -32px;
position: absolute;
top: calc(-32px + var(--radius-slots) / 7 / 2);
transform: rotate(var(--reverse-rotation)) scale(calc(var(--scale) * 0.8));
}
.slotItemOffline {
opacity: 0.3;
}
.slotItem > img {
border-top-left-radius: 32px;
}
.fitlink {
.fitLink {
height: 100%;
left: 0;
position: absolute;
@@ -123,3 +26,99 @@
width: 100%;
z-index: 3;
}
.hull {
height: 100%;
position: absolute;
width: 100%;
}
.hull > img {
border-radius: 50%;
height: calc(100% - 2 * 3%);
left: 3%;
opacity: 0.8;
position: relative;
top: 3%;
width: calc(100% - 2 * 3%);
}
.ringTop {
height: 100%;
position: absolute;
width: 100%;
}
.ringTopItem {
height: 100%;
pointer-events: none;
position: absolute;
transform: rotate(var(--rotation));
width: 100%;
z-index: 4;
}
.ringTopItem > div, .ringTopItem > svg {
--reverse-rotation: calc(-1 * var(--rotation));
left: 50%;
pointer-events: all;
position: absolute;
top: 3.5%;
transform: rotate(var(--reverse-rotation));
}
.radialMenu {
filter: drop-shadow(0 0 2px #ffffff);
position: absolute;
margin-top: 3.5%;
width: 2.5%;
}
.slot {
height: 9.5%;
margin-left: -2.5%;
position: relative;
user-select: none;
width: 7%;
}
.slot > svg {
height: 100%;
position: absolute;
transform: rotate(var(--rotation));
width: 100%;
z-index: 4;
}
.slotImage {
height: 100%;
position: absolute;
margin-top: 8%;
margin-left: -10%;
width: 100%;
z-index: 5;
}
.slotImage > img {
border-top-left-radius: 50%;
width: 120%;
}
.slot > svg {
fill: #999999;
stroke: #999999;
}
.slot[data-state="Active"] > svg {
fill: #8ae04a;
stroke: #8ae04a;
}
.slot[data-state="Overload"] > svg {
fill: #fd2d2d;
stroke: #fd2d2d;
}
.slot[data-state="Offline"] {
opacity: 0.3;
}
.slot[data-state="Unavailable"] {
opacity: 0.1;
}

View File

@@ -17,12 +17,14 @@ const meta: Meta<typeof ShipFit> = {
export default meta;
type Story = StoryObj<typeof ShipFit>;
const withShipSnapshotProvider: Decorator<{ radius?: number }> = (Story, context) => {
const withShipSnapshotProvider: Decorator<Record<string, never>> = (Story, context) => {
return (
<EveDataProvider>
<DogmaEngineProvider>
<ShipSnapshotProvider {...context.parameters.snapshot}>
<Story />
<div style={{ width: context.args.width, height: context.args.width }}>
<Story />
</div>
</ShipSnapshotProvider>
</DogmaEngineProvider>
</EveDataProvider>
@@ -31,7 +33,7 @@ const withShipSnapshotProvider: Decorator<{ radius?: number }> = (Story, context
export const Default: Story = {
args: {
radius: 365,
width: 730,
},
decorators: [withShipSnapshotProvider],
parameters: {

View File

@@ -5,70 +5,69 @@ import { ShipSnapshotContext } from '../ShipSnapshotProvider';
import { FitLink } from './FitLink';
import { Hull } from './Hull';
import { Slot } from './Slot';
import { RadialMenu } from "./RadialMenu";
import { RingOuter } from "./RingOuter";
import { RingInner } from "./RingInner";
import { RingTop, RingTopItem } from "./RingTop";
import styles from "./ShipFit.module.css";
export interface ShipFitProps {
radius?: number;
}
/**
* Render a ship fit similar to how it is done in-game.
*/
export const ShipFit = (props: ShipFitProps) => {
const radius = props.radius ?? 365;
export const ShipFit = () => {
const shipSnapshot = React.useContext(ShipSnapshotContext);
const slots = shipSnapshot.slots;
const scaleStyle = {
"--radius": `${radius}px`,
"--scale": `${radius / 365}`
} as React.CSSProperties;
return <div className={styles.fit} style={scaleStyle}>
<div className={styles.outerBand} />
<div className={styles.innerBand} />
return <div className={styles.fit}>
<RingOuter />
<RingInner />
<Hull />
<FitLink />
<div className={styles.slots}>
<Slot type="subsystem" index={1} fittable={slots?.subsystem >= 1} rotation="-125deg" />
<Slot type="subsystem" index={2} fittable={slots?.subsystem >= 2} rotation="-114deg" />
<Slot type="subsystem" index={3} fittable={slots?.subsystem >= 3} rotation="-103deg" />
<Slot type="subsystem" index={4} fittable={slots?.subsystem >= 4} rotation="-92deg" />
<RingTop>
<RingTopItem rotation={-45}><RadialMenu type="hislot" /></RingTopItem>
<Slot type="rig" index={1} fittable={slots?.rig >= 1} rotation="-73deg" />
<Slot type="rig" index={2} fittable={slots?.rig >= 2} rotation="-63deg" />
<Slot type="rig" index={3} fittable={slots?.rig >= 3} rotation="-53deg" />
<RingTopItem rotation={-36.5 + 71 / 7 * 0}><Slot type="hislot" index={1} fittable={slots?.hislot >= 1} main /></RingTopItem>
<RingTopItem rotation={-36.5 + 71 / 7 * 1}><Slot type="hislot" index={2} fittable={slots?.hislot >= 2} /></RingTopItem>
<RingTopItem rotation={-36.5 + 71 / 7 * 2}><Slot type="hislot" index={3} fittable={slots?.hislot >= 3} /></RingTopItem>
<RingTopItem rotation={-36.5 + 71 / 7 * 3}><Slot type="hislot" index={4} fittable={slots?.hislot >= 4} /></RingTopItem>
<RingTopItem rotation={-36.5 + 71 / 7 * 4}><Slot type="hislot" index={5} fittable={slots?.hislot >= 5} /></RingTopItem>
<RingTopItem rotation={-36.5 + 71 / 7 * 5}><Slot type="hislot" index={6} fittable={slots?.hislot >= 6} /></RingTopItem>
<RingTopItem rotation={-36.5 + 71 / 7 * 6}><Slot type="hislot" index={7} fittable={slots?.hislot >= 7} /></RingTopItem>
<RingTopItem rotation={-36.5 + 71 / 7 * 7}><Slot type="hislot" index={8} fittable={slots?.hislot >= 8} /></RingTopItem>
<Slot type="hislot" index={1} fittable={slots?.hislot >= 1} rotation="-34deg" />
<Slot type="hislot" index={2} fittable={slots?.hislot >= 2} rotation="-24deg" />
<Slot type="hislot" index={3} fittable={slots?.hislot >= 3} rotation="-14deg" />
<Slot type="hislot" index={4} fittable={slots?.hislot >= 4} rotation="-4deg" />
<Slot type="hislot" index={5} fittable={slots?.hislot >= 5} rotation="6deg" />
<Slot type="hislot" index={6} fittable={slots?.hislot >= 6} rotation="16deg" />
<Slot type="hislot" index={7} fittable={slots?.hislot >= 7} rotation="26deg" />
<Slot type="hislot" index={8} fittable={slots?.hislot >= 8} rotation="36deg" />
<RingTopItem rotation={43}><RadialMenu type="medslot" /></RingTopItem>
<Slot type="medslot" index={1} fittable={slots?.medslot >= 1} rotation="55deg" />
<Slot type="medslot" index={2} fittable={slots?.medslot >= 2} rotation="65deg" />
<Slot type="medslot" index={3} fittable={slots?.medslot >= 3} rotation="75deg" />
<Slot type="medslot" index={4} fittable={slots?.medslot >= 4} rotation="85deg" />
<Slot type="medslot" index={5} fittable={slots?.medslot >= 5} rotation="95deg" />
<Slot type="medslot" index={6} fittable={slots?.medslot >= 6} rotation="105deg" />
<Slot type="medslot" index={7} fittable={slots?.medslot >= 7} rotation="115deg" />
<Slot type="medslot" index={8} fittable={slots?.medslot >= 8} rotation="125deg" />
<RingTopItem rotation={53 + 72 / 7 * 0}><Slot type="medslot" index={1} fittable={slots?.medslot >= 1} /></RingTopItem>
<RingTopItem rotation={53 + 72 / 7 * 1}><Slot type="medslot" index={2} fittable={slots?.medslot >= 2} /></RingTopItem>
<RingTopItem rotation={53 + 72 / 7 * 2}><Slot type="medslot" index={3} fittable={slots?.medslot >= 3} /></RingTopItem>
<RingTopItem rotation={53 + 72 / 7 * 3}><Slot type="medslot" index={4} fittable={slots?.medslot >= 4} /></RingTopItem>
<RingTopItem rotation={53 + 72 / 7 * 4}><Slot type="medslot" index={5} fittable={slots?.medslot >= 5} /></RingTopItem>
<RingTopItem rotation={53 + 72 / 7 * 5}><Slot type="medslot" index={6} fittable={slots?.medslot >= 6} /></RingTopItem>
<RingTopItem rotation={53 + 72 / 7 * 6}><Slot type="medslot" index={7} fittable={slots?.medslot >= 7} /></RingTopItem>
<RingTopItem rotation={53 + 72 / 7 * 7}><Slot type="medslot" index={8} fittable={slots?.medslot >= 8} /></RingTopItem>
<Slot type="lowslot" index={1} fittable={slots?.lowslot >= 1} rotation="144deg" />
<Slot type="lowslot" index={2} fittable={slots?.lowslot >= 2} rotation="154deg" />
<Slot type="lowslot" index={3} fittable={slots?.lowslot >= 3} rotation="164deg" />
<Slot type="lowslot" index={4} fittable={slots?.lowslot >= 4} rotation="174deg" />
<Slot type="lowslot" index={5} fittable={slots?.lowslot >= 5} rotation="184deg" />
<Slot type="lowslot" index={6} fittable={slots?.lowslot >= 6} rotation="194deg" />
<Slot type="lowslot" index={7} fittable={slots?.lowslot >= 7} rotation="204deg" />
<Slot type="lowslot" index={8} fittable={slots?.lowslot >= 8} rotation="214deg" />
</div>
<RingTopItem rotation={133}><RadialMenu type="lowslot" /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 0}><Slot type="lowslot" index={1} fittable={slots?.lowslot >= 1} /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 1}><Slot type="lowslot" index={2} fittable={slots?.lowslot >= 2} /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 2}><Slot type="lowslot" index={3} fittable={slots?.lowslot >= 3} /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 3}><Slot type="lowslot" index={4} fittable={slots?.lowslot >= 4} /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 4}><Slot type="lowslot" index={5} fittable={slots?.lowslot >= 5} /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 5}><Slot type="lowslot" index={6} fittable={slots?.lowslot >= 6} /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 6}><Slot type="lowslot" index={7} fittable={slots?.lowslot >= 7} /></RingTopItem>
<RingTopItem rotation={142 + 72 / 7 * 7}><Slot type="lowslot" index={8} fittable={slots?.lowslot >= 8} /></RingTopItem>
<RingTopItem rotation={-74 + 21 / 2 * 0}><Slot type="rig" index={1} fittable={slots?.rig >= 1} /></RingTopItem>
<RingTopItem rotation={-74 + 21 / 2 * 1}><Slot type="rig" index={2} fittable={slots?.rig >= 2} /></RingTopItem>
<RingTopItem rotation={-74 + 21 / 2 * 2}><Slot type="rig" index={3} fittable={slots?.rig >= 3} /></RingTopItem>
<RingTopItem rotation={-128 + 38 / 3 * 0}><Slot type="subsystem" index={1} fittable={slots?.subsystem >= 1} /></RingTopItem>
<RingTopItem rotation={-128 + 38 / 3 * 1}><Slot type="subsystem" index={2} fittable={slots?.subsystem >= 2} /></RingTopItem>
<RingTopItem rotation={-128 + 38 / 3 * 2}><Slot type="subsystem" index={3} fittable={slots?.subsystem >= 3} /></RingTopItem>
<RingTopItem rotation={-128 + 38 / 3 * 3}><Slot type="subsystem" index={4} fittable={slots?.subsystem >= 4} /></RingTopItem>
</RingTop>
</div>
};

View File

@@ -1,5 +1,4 @@
import React from "react";
import ctlx from "clsx";
import { EveDataContext } from '../EveDataProvider';
import { ShipSnapshotContext } from '../ShipSnapshotProvider';
@@ -31,26 +30,56 @@ const stateRotation: Record<string, string[]> = {
"Overload": ["Passive", "Online", "Active", "Overload"],
};
export const Slot = (props: {type: string, index: number, fittable: boolean, rotation: string}) => {
export const Slot = (props: { type: string, index: number, fittable: boolean, main?: boolean }) => {
const eveData = React.useContext(EveDataContext);
const shipSnapshot = React.useContext(ShipSnapshotContext);
const rotationStyle = { "--rotation": props.rotation } as React.CSSProperties;
const esiFlag = esiFlagMapping[props.type][props.index - 1];
const esiItem = shipSnapshot?.items?.find((item) => item.flag == esiFlag);
const active = esiItem?.max_state !== "Passive" && esiItem?.max_state !== "Online";
let item = <></>;
let svg = <></>;
if (props.main !== undefined) {
svg = <svg viewBox="235 40 52 50" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMin slice" style={{ display: "none" }}>
<g id="slot">
<path style={{ fillOpacity: 0.1, strokeWidth: 1, strokeOpacity: 0.5 }} d="M 243 46 A 210 210 0 0 1 279 46.7 L 276 84.7 A 172 172 0 0 0 246 84 L 243 46" />
</g>
<g id="slot-active">
<path style={{ fillOpacity: 0.6, strokeWidth: 1 }} d="M 250 84 L 254 79 L 268 79 L 272 84" />
</g>
<g id="slot-passive">
<path style={{ strokeWidth: 1 }} d="M 245 48 A 208 208 0 0 1 250 47.5 L 248 50 L 245 50" />
<path style={{ strokeWidth: 1 }} d="M 277.5 48.5 A 208 208 0 0 0 273 48 L 275 50.5 L 277.5 50.5" />
<path style={{ strokeWidth: 1 }} d="M 247 82 A 170 170 0 0 1 252 82 L 250 80 L 246.8 80" />
<path style={{ strokeWidth: 1 }} d="M 275 82.5 A 170 170 0 0 0 270 82 L 272 80 L 275.2 80" />
</g>
</svg>;
}
svg = <>
{svg}
<svg viewBox="235 40 52 50" xmlns="http://www.w3.org/2000/svg" className={styles.ringInner} preserveAspectRatio="xMidYMin slice">
<use href="#slot" />
{props.fittable && active && <use href="#slot-active" />}
{props.fittable && !active && <use href="#slot-passive" />}
</svg>
</>;
/* Not fittable and nothing fitted; no need to render the slot. */
if (esiItem === undefined && !props.fittable) {
return <></>
return <div className={styles.slot} data-state="Unavailable">
{svg}
</div>
}
if (esiItem !== undefined) {
item = <img src={`https://images.evetech.net/types/${esiItem.type_id}/icon?size=64`} title={eveData?.typeIDs?.[esiItem.type_id].name} />
}
const isOffline = esiItem?.state === "Passive" && esiItem?.max_state !== "Passive";
const state = (esiItem?.state === "Passive" && esiItem?.max_state !== "Passive") ? "Offline" : esiItem?.state;
function cycleState(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
if (!shipSnapshot?.loaded || !esiItem) return;
@@ -68,10 +97,9 @@ export const Slot = (props: {type: string, index: number, fittable: boolean, rot
shipSnapshot.setItemState(esiItem.flag, newState);
}
return <div className={styles.slot} style={rotationStyle} onClick={cycleState}>
<div className={ctlx(styles.slotInner, { [styles.slotInnerInvalid]: !props.fittable, })} data-state={esiItem?.state}>
</div>
<div className={ctlx(styles.slotItem, { [styles.slotItemOffline]: isOffline })}>
return <div className={styles.slot} onClick={cycleState} data-state={state}>
{svg}
<div className={styles.slotImage}>
{item}
</div>
</div>

View File

@@ -2,10 +2,9 @@
background-color: #111111;
color: #c5c5c5;
font-size: 15px;
padding-bottom: 60px;
padding-left: 50px;
height: 100%;
position: relative;
width: calc(var(--radius) * 2 + 2 * 50px);
width: 100%;
}
.cpuPg {

View File

@@ -17,12 +17,14 @@ const meta: Meta<typeof ShipFitExtended> = {
export default meta;
type Story = StoryObj<typeof ShipFitExtended>;
const withShipSnapshotProvider: Decorator<{ radius?: number }> = (Story, context) => {
const withShipSnapshotProvider: Decorator<Record<string, never>> = (Story, context) => {
return (
<EveDataProvider>
<DogmaEngineProvider>
<ShipSnapshotProvider {...context.parameters.snapshot}>
<Story />
<div style={{ width: context.args.width, height: context.args.width }}>
<Story />
</div>
</ShipSnapshotProvider>
</DogmaEngineProvider>
</EveDataProvider>
@@ -31,7 +33,7 @@ const withShipSnapshotProvider: Decorator<{ radius?: number }> = (Story, context
export const Default: Story = {
args: {
radius: 365,
width: 730,
},
decorators: [withShipSnapshotProvider],
parameters: {

View File

@@ -5,10 +5,6 @@ import { ShipAttribute } from "../ShipAttribute";
import styles from "./ShipFitExtended.module.css";
export interface ShipFitExtendedProps {
radius?: number;
}
const CargoHold = () => {
return <div>
<div className={styles.cargoIcon}>
@@ -61,15 +57,9 @@ const CpuPg = (props: { title: string, children: React.ReactNode }) => {
* also adds the cargo hold, drone bay, and CPU/PG usage at the
* bottom of the fit.
*/
export const ShipFitExtended = (props: ShipFitExtendedProps) => {
const radius = props.radius ?? 365;
const scaleStyle = {
"--radius": `${radius}px`,
} as React.CSSProperties;
return <div className={styles.fit} style={scaleStyle}>
<ShipFit radius={radius} />
export const ShipFitExtended = () => {
return <div className={styles.fit}>
<ShipFit />
<div className={styles.cargoHold}>
<CargoHold />

View File

@@ -21,8 +21,8 @@ export interface ShipSnapshotItem {
type_id: number,
quantity: number,
flag: number,
state: string,
max_state: string,
state: "Passive" | "Online" | "Active" | "Overload",
max_state: "Passive" | "Online" | "Active" | "Overload",
attributes: Map<number, ShipSnapshotItemAttribute>,
effects: number[],
}