Reorganizes equipment list into by-slot so you can tag BiS items
This commit is contained in:
1
package-lock.json
generated
1
package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.7",
|
"@headlessui/react": "^2.2.7",
|
||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1"
|
"react-dom": "^19.1.1"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.7",
|
"@headlessui/react": "^2.2.7",
|
||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1"
|
"react-dom": "^19.1.1"
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
.equipedItems {
|
.equipedItems {
|
||||||
display: grid;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
grid-template-columns: 10em 1fr 1fr 5em;
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemList {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 10em 5em;
|
||||||
|
}
|
||||||
|
|
||||||
.gridItem {
|
.gridItem {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border: 1px solid #666;
|
border: 1px solid #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slot {
|
.slot {
|
||||||
grid-column: 1;
|
font-size: 120%;
|
||||||
|
background-color: #666;
|
||||||
|
padding: 4px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
grid-column: 2;
|
grid-column: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quality {
|
.quality {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bis {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { ItemsById, itemsPerSlot } from "../lib/items";
|
import { ItemsById, itemsForSlot, itemsPerSlot } from "../lib/items";
|
||||||
import type { State } from "../lib/state";
|
import type { State } from "../lib/state";
|
||||||
import { Slots, type Item, type Slot } from "../lib/types";
|
import { Slots, type Item, type Slot } from "../lib/types";
|
||||||
import { ItemLink } from "./ItemLink";
|
import { ItemLink } from "./ItemLink";
|
||||||
import { ItemTypeahead } from "./ItemTypeahead";
|
import { ItemTypeahead } from "./ItemTypeahead";
|
||||||
import styles from "./Equipment.module.css";
|
import styles from "./Equipment.module.css";
|
||||||
import { QualitySelector } from "./QualitySelector";
|
import { QualitySelector } from "./QualitySelector";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useAppState } from "../lib/context/StateContext";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
state: State;
|
state: State;
|
||||||
@@ -19,14 +21,7 @@ export const Equipment = ({ state, onEquip, onUnequip }: Props) => {
|
|||||||
<div className={styles.equipedItems}>
|
<div className={styles.equipedItems}>
|
||||||
{Slots.map((slot) => {
|
{Slots.map((slot) => {
|
||||||
if (itemsPerSlot(slot, state.weaponConfig) > 0) {
|
if (itemsPerSlot(slot, state.weaponConfig) > 0) {
|
||||||
return (
|
return <Slot slot={slot} onUnequip={onUnequip} key={slot} />;
|
||||||
<Slot
|
|
||||||
state={state}
|
|
||||||
slot={slot}
|
|
||||||
onUnequip={onUnequip}
|
|
||||||
key={slot}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})}
|
})}
|
||||||
@@ -36,42 +31,64 @@ export const Equipment = ({ state, onEquip, onUnequip }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type SlotProps = {
|
type SlotProps = {
|
||||||
state: State;
|
|
||||||
slot: Slot;
|
slot: Slot;
|
||||||
onUnequip: (item: Item) => void;
|
onUnequip: (item: Item) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Slot = ({ state, slot, onUnequip }: SlotProps) => {
|
const Slot = ({ slot, onUnequip }: SlotProps) => {
|
||||||
const items = state.equipedItems
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { state, dispatch } = useAppState();
|
||||||
|
|
||||||
|
const equipedItems = state.equipedItems
|
||||||
.map((item) => ({ ...item, item: ItemsById[item.id] }))
|
.map((item) => ({ ...item, item: ItemsById[item.id] }))
|
||||||
.filter((item) => item && item.item.slot === slot);
|
.filter((item) => item && item.item.slot === slot);
|
||||||
|
|
||||||
|
const allItems = itemsForSlot(slot);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${styles.slot} ${styles.gridItem}`}>{slot}</div>
|
<div className={`${styles.slot}`} onClick={() => setIsOpen(!isOpen)}>
|
||||||
{
|
{slot}
|
||||||
// Show placeholder if there are no items
|
</div>
|
||||||
items.length === 0 && (
|
{isOpen && (
|
||||||
<>
|
<div className={`${styles.itemList}`}>
|
||||||
<div className={`${styles.gridItem} ${styles.item}`}></div>
|
{allItems.map((item) => {
|
||||||
<div className={`${styles.gridItem} ${styles.quality}`}></div>
|
const equiped = equipedItems.find((e) => e.item.id === item.id);
|
||||||
<div className={`${styles.gridItem} ${styles.actions}`}></div>
|
const isBis = state.bisList.includes(item.id);
|
||||||
</>
|
return (
|
||||||
)
|
<>
|
||||||
}
|
<div className={`${styles.gridItem} ${styles.item}`}>
|
||||||
{items.map((item) => (
|
<ItemLink item={item} />
|
||||||
<>
|
</div>
|
||||||
<div className={`${styles.gridItem} ${styles.item}`}>
|
<div className={`${styles.gridItem} ${styles.quality}`}>
|
||||||
<ItemLink item={item.item} />
|
<QualitySelector
|
||||||
</div>
|
itemId={item.id}
|
||||||
<div className={`${styles.gridItem} ${styles.quality}`}>
|
quality={equiped?.quality}
|
||||||
<QualitySelector item={item} />
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles.gridItem} ${styles.actions}`}>
|
<div className={`${styles.gridItem} ${styles.bis}`}>
|
||||||
<button onClick={() => onUnequip(item.item)}>X</button>
|
<input
|
||||||
</div>
|
type="checkbox"
|
||||||
</>
|
checked={isBis}
|
||||||
))}
|
onClick={() =>
|
||||||
|
dispatch({
|
||||||
|
action: "setBis",
|
||||||
|
itemId: item.id,
|
||||||
|
isBis: !isBis,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.gridItem} ${styles.actions}`}>
|
||||||
|
{equiped && (
|
||||||
|
<button onClick={() => onUnequip(item)}>X</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
.item {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.itemLink {
|
.itemLink {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
import type { Item } from "../lib/types";
|
import type { Item } from "../lib/types";
|
||||||
import * as styles from "./ItemLink.module.css";
|
import styles from "./ItemLink.module.css";
|
||||||
|
import { TagList } from "./TagList";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
item: Item;
|
item: Item;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ItemLink = ({ item }: Props) => (
|
export const ItemLink = ({ item }: Props) => (
|
||||||
<a
|
<span className={styles.item}>
|
||||||
href={`https://www.wowhead.com/item=${item.id}`}
|
<a
|
||||||
target="_blank"
|
href={`https://www.wowhead.com/item=${item.id}`}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
className={styles.itemLink}
|
rel="noopener noreferrer"
|
||||||
>
|
className={styles.itemLink}
|
||||||
{item.icon && (
|
>
|
||||||
<img
|
{item.icon && (
|
||||||
src={`https://wow.zamimg.com/images/wow/icons/medium/${item.icon}.jpg`}
|
<img
|
||||||
/>
|
src={`https://wow.zamimg.com/images/wow/icons/medium/${item.icon}.jpg`}
|
||||||
)}
|
/>
|
||||||
{item.name}
|
)}
|
||||||
</a>
|
{item.name}
|
||||||
|
</a>
|
||||||
|
{item.tags && <TagList tags={item.tags} />}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
import { useAppState } from "../lib/context/StateContext";
|
import { useAppState } from "../lib/context/StateContext";
|
||||||
import { Quality, type EquipedItem } from "../lib/types";
|
import { Quality, type ItemId } from "../lib/types";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
item: EquipedItem;
|
itemId: ItemId;
|
||||||
|
quality?: Quality;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QualitySelector = ({ item }: Props) => {
|
export const QualitySelector = ({ itemId, quality }: Props) => {
|
||||||
const { dispatch } = useAppState();
|
const { dispatch } = useAppState();
|
||||||
|
|
||||||
const handleOnChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleOnChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
action: "changeQuality",
|
action: "changeQuality",
|
||||||
itemId: item.id,
|
itemId: itemId,
|
||||||
quality: event.target.value as Quality,
|
quality: event.target.value as Quality,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<select onChange={handleOnChange} value={item.quality}>
|
<select onChange={handleOnChange} value={quality}>
|
||||||
|
<option value=""></option>
|
||||||
{Object.values(Quality).map((quality) => (
|
{Object.values(Quality).map((quality) => (
|
||||||
<option value={quality}>
|
<option value={quality}>
|
||||||
{quality.charAt(0).toUpperCase() + quality.slice(1)}
|
{quality.charAt(0).toUpperCase() + quality.slice(1)}
|
||||||
|
|||||||
@@ -7,3 +7,7 @@
|
|||||||
.itemList {
|
.itemList {
|
||||||
grid-column: 1 / span 4;
|
grid-column: 1 / span 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visibleQuality {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ import {
|
|||||||
} from "../lib/types";
|
} from "../lib/types";
|
||||||
import { ItemLink } from "./ItemLink";
|
import { ItemLink } from "./ItemLink";
|
||||||
import styles from "./SourceList.module.css";
|
import styles from "./SourceList.module.css";
|
||||||
|
import { clsx } from "clsx";
|
||||||
|
import { TagList } from "./TagList";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
state: State;
|
state: State;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SourceList = ({ state }: Props) => {
|
export const SourceList = ({ state }: Props) => {
|
||||||
const upgrades = getUpgrades(state.equipedItems, state.weaponConfig);
|
const upgrades = getUpgrades(state);
|
||||||
|
|
||||||
const upgradesBySource = _.groupBy(
|
const upgradesBySource = _.groupBy(
|
||||||
upgrades,
|
upgrades,
|
||||||
@@ -43,61 +45,77 @@ type SourceInfoProps = {
|
|||||||
items: Upgrade[];
|
items: Upgrade[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const QualitiesInGrid = [
|
||||||
|
UpgradeType.Champion,
|
||||||
|
UpgradeType.Hero,
|
||||||
|
UpgradeType.Myth,
|
||||||
|
] as UpgradeType[];
|
||||||
|
|
||||||
const SourceInfo = ({ source, items }: SourceInfoProps) => {
|
const SourceInfo = ({ source, items }: SourceInfoProps) => {
|
||||||
const [upgradeTypeShown, setUpgradeTypeShown] = useState<UpgradeType | null>(
|
const [upgradeTypeShown, setUpgradeTypeShown] = useState<UpgradeType | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const upgrades = {
|
||||||
|
[UpgradeType.Champion]: items.filter(hasUpgradeType(UpgradeType.Champion)),
|
||||||
|
[UpgradeType.Hero]: items.filter(hasUpgradeType(UpgradeType.Hero)),
|
||||||
|
[UpgradeType.Myth]: items.filter(hasUpgradeType(UpgradeType.Myth)),
|
||||||
|
};
|
||||||
|
|
||||||
const championUpgrades = items.filter(hasUpgradeType(UpgradeType.Champion));
|
const numBiS = {
|
||||||
const heroUpgrades = items.filter(hasUpgradeType(UpgradeType.Hero));
|
[UpgradeType.Champion]: upgrades[UpgradeType.Champion].filter(
|
||||||
const mythUpgrades = items.filter(hasUpgradeType(UpgradeType.Myth));
|
hasUpgradeType(UpgradeType.BiS),
|
||||||
|
).length,
|
||||||
|
[UpgradeType.Hero]: upgrades[UpgradeType.Hero].filter(
|
||||||
|
hasUpgradeType(UpgradeType.BiS),
|
||||||
|
).length,
|
||||||
|
[UpgradeType.Myth]: upgrades[UpgradeType.Myth].filter(
|
||||||
|
hasUpgradeType(UpgradeType.BiS),
|
||||||
|
).length,
|
||||||
|
};
|
||||||
|
|
||||||
const toggleIsOpen = (upgradeType: UpgradeType) => {
|
const toggleIsOpen = (upgradeType: UpgradeType) => {
|
||||||
if (upgradeTypeShown === upgradeType) {
|
if (upgradeTypeShown === upgradeType) {
|
||||||
setUpgradeTypeShown(null);
|
setUpgradeTypeShown(null);
|
||||||
|
} else {
|
||||||
|
setUpgradeTypeShown(upgradeType);
|
||||||
}
|
}
|
||||||
setUpgradeTypeShown(upgradeType);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>{source}</div>
|
<div>{source}</div>
|
||||||
<div onClick={() => toggleIsOpen(UpgradeType.Champion)}>
|
{QualitiesInGrid.map((quality) => (
|
||||||
{championUpgrades.length}
|
<div
|
||||||
</div>
|
key={quality}
|
||||||
<div onClick={() => toggleIsOpen(UpgradeType.Hero)}>
|
className={clsx({
|
||||||
{heroUpgrades.length}
|
[styles.visibleQuality]: upgradeTypeShown === quality,
|
||||||
</div>
|
})}
|
||||||
<div onClick={() => toggleIsOpen(UpgradeType.Myth)}>
|
onClick={() => toggleIsOpen(quality)}
|
||||||
{mythUpgrades.length}
|
>
|
||||||
</div>
|
{upgrades[quality].length}{" "}
|
||||||
{upgradeTypeShown === UpgradeType.Champion && (
|
{numBiS[quality] > 0 && `(${numBiS[quality]})`}
|
||||||
<div className={styles.itemList}>
|
|
||||||
{championUpgrades.map((upgrade) => (
|
|
||||||
<div>
|
|
||||||
<ItemLink item={upgrade.item} /> ({upgrade.item.slot})
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
{upgradeTypeShown === UpgradeType.Hero && (
|
{QualitiesInGrid.map((quality) => {
|
||||||
<div className={styles.itemList}>
|
if (upgradeTypeShown !== quality) {
|
||||||
{heroUpgrades.map((upgrade) => (
|
return null;
|
||||||
<div>
|
}
|
||||||
<ItemLink item={upgrade.item} />
|
return (
|
||||||
</div>
|
<div className={styles.itemList} key={quality}>
|
||||||
))}
|
{upgrades[quality].map((upgrade) => (
|
||||||
</div>
|
<div>
|
||||||
)}
|
<ItemLink item={upgrade.item} />{" "}
|
||||||
{upgradeTypeShown === UpgradeType.Myth && (
|
<TagList
|
||||||
<div className={styles.itemList}>
|
tags={[
|
||||||
{mythUpgrades.map((upgrade) => (
|
upgrade.item.slot,
|
||||||
<div>
|
upgrade.upgradeTypes.includes(UpgradeType.BiS) && "BiS",
|
||||||
<ItemLink item={upgrade.item} />
|
]}
|
||||||
</div>
|
/>
|
||||||
))}
|
</div>
|
||||||
</div>
|
))}
|
||||||
)}
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
12
src/components/TagList.module.css
Normal file
12
src/components/TagList.module.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.tagList {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #666;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
18
src/components/TagList.tsx
Normal file
18
src/components/TagList.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import styles from "./TagList.module.css";
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
tags: (string | undefined | null | false)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TagList = ({ tags }: Props) => {
|
||||||
|
const validTags = tags.filter((tag): tag is string => Boolean(tag));
|
||||||
|
return (
|
||||||
|
validTags.length > 0 && (
|
||||||
|
<span className={styles.tagList}>
|
||||||
|
{validTags.map((tag) => (
|
||||||
|
<span className={styles.tag}>{tag}</span>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ItemId } from "../types";
|
import { Tag, type ItemId } from "../types";
|
||||||
|
|
||||||
export const ManaforgeOmega = [
|
export const ManaforgeOmega = [
|
||||||
{
|
{
|
||||||
@@ -6,36 +6,42 @@ export const ManaforgeOmega = [
|
|||||||
name: "Half-Mask of Fallen Storms",
|
name: "Half-Mask of Fallen Storms",
|
||||||
slot: "head",
|
slot: "head",
|
||||||
source: "manaforge omega",
|
source: "manaforge omega",
|
||||||
|
tags: [Tag.Tier],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 237674 as ItemId,
|
id: 237674 as ItemId,
|
||||||
name: "Grasp of Fallen Storms",
|
name: "Grasp of Fallen Storms",
|
||||||
slot: "hands",
|
slot: "hands",
|
||||||
source: "manaforge omega",
|
source: "manaforge omega",
|
||||||
|
tags: [Tag.Tier],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 237676 as ItemId,
|
id: 237676 as ItemId,
|
||||||
name: "Gi of Fallen Storms",
|
name: "Gi of Fallen Storms",
|
||||||
slot: "chest",
|
slot: "chest",
|
||||||
source: "manaforge omega",
|
source: "manaforge omega",
|
||||||
|
tags: [Tag.Tier],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 237672 as ItemId,
|
id: 237672 as ItemId,
|
||||||
name: "Legwraps of Fallen Storms",
|
name: "Legwraps of Fallen Storms",
|
||||||
slot: "legs",
|
slot: "legs",
|
||||||
source: "manaforge omega",
|
source: "manaforge omega",
|
||||||
|
tags: [Tag.Tier],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 237671 as ItemId,
|
id: 237671 as ItemId,
|
||||||
name: "Glyphs of Fallen Storms",
|
name: "Glyphs of Fallen Storms",
|
||||||
slot: "shoulders",
|
slot: "shoulders",
|
||||||
source: "manaforge omega",
|
source: "manaforge omega",
|
||||||
|
tags: [Tag.Tier],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 235799 as ItemId,
|
id: 235799 as ItemId,
|
||||||
name: "Reshii Wraps",
|
name: "Reshii Wraps",
|
||||||
slot: "back",
|
slot: "back",
|
||||||
source: "manaforge omega",
|
source: "manaforge omega",
|
||||||
|
tags: [Tag.Quest],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 242395,
|
id: 242395,
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { AllItems } from "./drops";
|
||||||
|
import type { State } from "./state";
|
||||||
import {
|
import {
|
||||||
UpgradeType,
|
UpgradeType,
|
||||||
WeaponConfig,
|
WeaponConfig,
|
||||||
type EquipedItem,
|
|
||||||
type Item,
|
type Item,
|
||||||
type ItemId,
|
type ItemId,
|
||||||
type Quality,
|
type Quality,
|
||||||
type Slot,
|
type Slot,
|
||||||
type Upgrade,
|
type Upgrade,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { AllItems } from "./drops";
|
|
||||||
|
|
||||||
export const ItemsById: Record<string, Item> = AllItems.reduce(
|
export const ItemsById: Record<string, Item> = AllItems.reduce(
|
||||||
(db, item) => {
|
(db, item) => {
|
||||||
@@ -66,25 +66,23 @@ export const upgradesForItem = ({ quality }: { quality: Quality }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUpgrades = (
|
export const getUpgrades = ({
|
||||||
equipedItemIds: EquipedItem[],
|
equipedItems,
|
||||||
weaponConfig: WeaponConfig,
|
bisList,
|
||||||
): Upgrade[] => {
|
weaponConfig,
|
||||||
|
}: State): Upgrade[] => {
|
||||||
return Object.entries(ItemsBySlot).flatMap(([slot, items]) => {
|
return Object.entries(ItemsBySlot).flatMap(([slot, items]) => {
|
||||||
const equipedItems = equipedItemIds.flatMap(({ id, quality }) => ({
|
const equipedItemsHydrated = equipedItems.flatMap(({ id, quality }) => ({
|
||||||
item: ItemsById[id],
|
item: ItemsById[id],
|
||||||
quality,
|
quality,
|
||||||
}));
|
}));
|
||||||
const equipedInSlot = equipedItems.filter(
|
const equipedInSlot = equipedItemsHydrated.filter(
|
||||||
(item) => item.item.slot === slot,
|
(item) => item.item.slot === slot,
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemsForSlot = itemsPerSlot(slot as Slot, weaponConfig);
|
const itemsForSlot = itemsPerSlot(slot as Slot, weaponConfig);
|
||||||
|
|
||||||
if (itemsForSlot === 0) {
|
if (itemsForSlot === 0) {
|
||||||
console.log(
|
|
||||||
`Skipping ${slot} because it has no items with ${weaponConfig}`,
|
|
||||||
);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +95,20 @@ export const getUpgrades = (
|
|||||||
numMyth >= itemsForSlot ? [] : [UpgradeType.Myth],
|
numMyth >= itemsForSlot ? [] : [UpgradeType.Myth],
|
||||||
].flat();
|
].flat();
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
|
const isBis = bisList.includes(item.id);
|
||||||
const equiped = equipedInSlot.find((i) => i.item.id === item.id);
|
const equiped = equipedInSlot.find((i) => i.item.id === item.id);
|
||||||
|
|
||||||
|
// Bis items are always desired at any better quality
|
||||||
|
if (isBis) {
|
||||||
|
return {
|
||||||
|
item,
|
||||||
|
upgradeTypes: equiped
|
||||||
|
? [...upgradesForItem(equiped), UpgradeType.BiS]
|
||||||
|
: Object.values(UpgradeType),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's equiped, only show higher upgrades
|
||||||
if (equiped) {
|
if (equiped) {
|
||||||
const upgradesForThisItem = upgradesForItem(equiped);
|
const upgradesForThisItem = upgradesForItem(equiped);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ export type Action =
|
|||||||
itemId: ItemId;
|
itemId: ItemId;
|
||||||
quality: Quality;
|
quality: Quality;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
action: "setBis";
|
||||||
|
itemId: ItemId;
|
||||||
|
isBis: boolean;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
action: "changeWeaponConfig";
|
action: "changeWeaponConfig";
|
||||||
weaponConfig: WeaponConfig;
|
weaponConfig: WeaponConfig;
|
||||||
@@ -65,6 +70,14 @@ export const reducer = (state: State, action: Action): State => {
|
|||||||
return item;
|
return item;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
case "setBis":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
bisList: [
|
||||||
|
...state.bisList.filter((id) => id !== action.itemId),
|
||||||
|
...(action.isBis ? [action.itemId] : []),
|
||||||
|
],
|
||||||
|
};
|
||||||
case "changeWeaponConfig":
|
case "changeWeaponConfig":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export type Item = {
|
|||||||
name: string;
|
name: string;
|
||||||
source: Source;
|
source: Source;
|
||||||
icon?: string; // Wowhead icon name (not ID)
|
icon?: string; // Wowhead icon name (not ID)
|
||||||
|
tags?: Tag[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EquipedItem = {
|
export type EquipedItem = {
|
||||||
@@ -74,3 +75,10 @@ export const WeaponConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type WeaponConfig = (typeof WeaponConfig)[keyof typeof WeaponConfig];
|
export type WeaponConfig = (typeof WeaponConfig)[keyof typeof WeaponConfig];
|
||||||
|
|
||||||
|
export const Tag = {
|
||||||
|
Tier: "tier",
|
||||||
|
Quest: "quest",
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Tag = (typeof Tag)[keyof typeof Tag];
|
||||||
|
|||||||
Reference in New Issue
Block a user