feat: ability to remove modules from a fit (via hover on module) (#42)
This commit is contained in:
@@ -60,11 +60,11 @@
|
||||
|
||||
.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 {
|
||||
@@ -74,18 +74,23 @@
|
||||
width: 2.5%;
|
||||
}
|
||||
|
||||
.slot {
|
||||
height: 9.5%;
|
||||
.slotOuter {
|
||||
height: 18%;
|
||||
margin-left: -2.5%;
|
||||
position: absolute;
|
||||
width: 7%;
|
||||
}
|
||||
|
||||
.slot {
|
||||
height: 50%;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
width: 7%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slot > svg {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transform: rotate(var(--rotation));
|
||||
width: 100%;
|
||||
z-index: 4;
|
||||
}
|
||||
@@ -93,14 +98,15 @@
|
||||
.slotImage {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
margin-top: 8%;
|
||||
margin-left: -10%;
|
||||
transform: rotate(var(--reverse-rotation));
|
||||
width: 100%;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.slotImage > img {
|
||||
border-top-left-radius: 50%;
|
||||
margin-left: -10%;
|
||||
margin-top: 5%;
|
||||
width: 120%;
|
||||
}
|
||||
|
||||
@@ -116,9 +122,31 @@
|
||||
fill: #fd2d2d;
|
||||
stroke: #fd2d2d;
|
||||
}
|
||||
.slot[data-state="Offline"] {
|
||||
.slot[data-state="Offline"] > svg, .slot[data-state="Offline"] > .slotImage {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.slot[data-state="Unavailable"] {
|
||||
.slot[data-state="Unavailable"] > svg, .slot[data-state="Unavailable"] > .slotImage {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.slotOuter .slotOptions {
|
||||
display: none;
|
||||
}
|
||||
.slotOuter[data-hasitem=true]:hover .slotOptions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slotOptions {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
.slotOptions > svg {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
filter: drop-shadow(0px 2px 1px #222222);
|
||||
margin: 6px auto;
|
||||
transform: rotate(var(--reverse-rotation));
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
@@ -43,45 +43,56 @@ export const Slot = (props: { type: string, index: number, fittable: boolean, ma
|
||||
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 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 viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" style={{ display: "none" }}>
|
||||
<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" />
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 6 10 L 16 10" />
|
||||
</g>
|
||||
<g id="offline">
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 12 4 A 8 8 0 1 1 6 4" />
|
||||
<path style={{ fill: "none", strokeWidth: 1 }} d="M 9 2 L 9 12" />
|
||||
</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" />}
|
||||
{props.fittable && esiItem && active && <use href="#slot-active" />}
|
||||
{props.fittable && esiItem && !active && <use href="#slot-passive" />}
|
||||
</svg>
|
||||
</>;
|
||||
|
||||
/* Not fittable and nothing fitted; no need to render the slot. */
|
||||
if (esiItem === undefined && !props.fittable) {
|
||||
return <div className={styles.slot} data-state="Unavailable">
|
||||
{svg}
|
||||
</div>
|
||||
}
|
||||
const offlineState = React.useCallback((e: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (!shipSnapshot?.loaded || !esiItem) return;
|
||||
|
||||
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.state === "Passive") {
|
||||
shipSnapshot.setItemState(esiItem.flag, "Online");
|
||||
} else {
|
||||
shipSnapshot.setItemState(esiItem.flag, "Passive");
|
||||
}
|
||||
}, [shipSnapshot, esiItem]);
|
||||
|
||||
const state = (esiItem?.state === "Passive" && esiItem?.max_state !== "Passive") ? "Offline" : esiItem?.state;
|
||||
|
||||
function cycleState(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
const cycleState = React.useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
if (!shipSnapshot?.loaded || !esiItem) return;
|
||||
|
||||
const states = stateRotation[esiItem.max_state];
|
||||
@@ -95,12 +106,46 @@ export const Slot = (props: { type: string, index: number, fittable: boolean, ma
|
||||
}
|
||||
|
||||
shipSnapshot.setItemState(esiItem.flag, newState);
|
||||
}, [shipSnapshot, esiItem]);
|
||||
|
||||
const unfitModule = React.useCallback((e: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (!shipSnapshot?.loaded || !esiItem) return;
|
||||
|
||||
shipSnapshot.removeModule(esiItem.flag);
|
||||
}, [shipSnapshot, esiItem]);
|
||||
|
||||
/* Not fittable and nothing fitted; no need to render the slot. */
|
||||
if (esiItem === undefined && !props.fittable) {
|
||||
return <div className={styles.slotOuter} data-hasitem={false}>
|
||||
<div className={styles.slot} data-state="Unavailable">
|
||||
{svg}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
return <div className={styles.slot} onClick={cycleState} data-state={state}>
|
||||
{svg}
|
||||
<div className={styles.slotImage}>
|
||||
{item}
|
||||
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 state = (esiItem?.state === "Passive" && esiItem?.max_state !== "Passive") ? "Offline" : esiItem?.state;
|
||||
|
||||
return <div className={styles.slotOuter} data-hasitem={esiItem !== undefined}>
|
||||
<div className={styles.slot} onClick={cycleState} data-state={state}>
|
||||
{svg}
|
||||
<div className={styles.slotImage}>
|
||||
{item}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.slotOptions}>
|
||||
<svg viewBox="0 0 20 20" width={20} xmlns="http://www.w3.org/2000/svg" onClick={unfitModule}>
|
||||
<use href="#unfit" />
|
||||
</svg>
|
||||
{esiItem?.max_state !== "Passive" &&
|
||||
<svg viewBox="0 0 20 20" width={20} xmlns="http://www.w3.org/2000/svg" onClick={offlineState}>
|
||||
<use href="#offline" />
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ interface ShipSnapshot {
|
||||
fit?: EsiFit;
|
||||
|
||||
addModule: (typeId: number, slot: ShipSnapshotSlotsType | "dronebay") => void;
|
||||
removeModule: (flag: number) => void;
|
||||
changeHull: (typeId: number) => void;
|
||||
changeFit: (fit: EsiFit) => void;
|
||||
setItemState: (flag: number, state: string) => void;
|
||||
@@ -73,6 +74,7 @@ export const ShipSnapshotContext = React.createContext<ShipSnapshot>({
|
||||
"rig": 0,
|
||||
},
|
||||
addModule: () => {},
|
||||
removeModule: () => {},
|
||||
changeHull: () => {},
|
||||
changeFit: () => {},
|
||||
setItemState: () => {},
|
||||
@@ -110,6 +112,7 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
|
||||
"rig": 0,
|
||||
},
|
||||
addModule: () => {},
|
||||
removeModule: () => {},
|
||||
changeHull: () => {},
|
||||
changeFit: () => {},
|
||||
setItemState: () => {},
|
||||
@@ -172,6 +175,17 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
|
||||
})
|
||||
}, [shipSnapshot.slots]);
|
||||
|
||||
const removeModule = React.useCallback((flag: number) => {
|
||||
setCurrentFit((oldFit: EsiFit | undefined) => {
|
||||
if (oldFit === undefined) return undefined;
|
||||
|
||||
return {
|
||||
...oldFit,
|
||||
items: oldFit.items.filter((item) => item.flag !== flag),
|
||||
};
|
||||
})
|
||||
}, []);
|
||||
|
||||
const changeHull = React.useCallback((typeId: number) => {
|
||||
setCurrentFit({
|
||||
"name": "New Ship",
|
||||
@@ -185,11 +199,12 @@ export const ShipSnapshotProvider = (props: ShipSnapshotProps) => {
|
||||
setShipSnapshot((oldSnapshot) => ({
|
||||
...oldSnapshot,
|
||||
addModule,
|
||||
removeModule,
|
||||
changeHull,
|
||||
changeFit: setCurrentFit,
|
||||
setItemState,
|
||||
}));
|
||||
}, [addModule, changeHull, setItemState]);
|
||||
}, [addModule, removeModule, changeHull, setItemState]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!dogmaEngine.loaded) return;
|
||||
|
||||
Reference in New Issue
Block a user