diff --git a/src/App.css b/src/App.css
index b9d355d..592d97d 100644
--- a/src/App.css
+++ b/src/App.css
@@ -2,41 +2,4 @@
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
}
diff --git a/src/App.tsx b/src/App.tsx
index a7389dd..b72279c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@ import "./App.css";
import { Equipment } from "./components/Equipment";
import { reducer, withLoadingAction } from "./lib/state";
import type { ItemId } from "./lib/types";
+import { SourceList } from "./components/SourceList";
function App() {
const [state, dispatch] = useReducer(withLoadingAction(reducer), "loading");
@@ -44,6 +45,7 @@ function App() {
onEquip={(item) => dispatch({ action: "equipItem", item })}
onUnequip={(item) => dispatch({ action: "unequipItem", item })}
/>
+
>
);
}
diff --git a/src/components/Equipment.module.css b/src/components/Equipment.module.css
new file mode 100644
index 0000000..b89f4aa
--- /dev/null
+++ b/src/components/Equipment.module.css
@@ -0,0 +1,17 @@
+.equipedItems {
+ display: grid;
+ gap: 8px;
+ grid-template-columns: 200px 1fr;
+ align-items: stretch;
+}
+
+.gridItem {
+ padding: 4px 8px;
+ border: 1px solid #666;
+}
+
+.slot {
+}
+
+.items {
+}
diff --git a/src/components/Equipment.tsx b/src/components/Equipment.tsx
index baa2e7f..c9032ca 100644
--- a/src/components/Equipment.tsx
+++ b/src/components/Equipment.tsx
@@ -3,6 +3,7 @@ import type { State } from "../lib/state";
import { Slots, type Item, type Slot } from "../lib/types";
import { ItemLink } from "./ItemLink";
import { ItemTypeahead } from "./ItemTypeahead";
+import styles from "./Equipment.module.css";
export type Props = {
state: State;
@@ -13,9 +14,11 @@ export type Props = {
export const Equipment = ({ state, onEquip, onUnequip }: Props) => (
- {Slots.map((slot) => (
-
- ))}
+
+ {Slots.map((slot) => (
+
+ ))}
+
);
@@ -26,16 +29,18 @@ type SlotProps = {
};
const Slot = ({ state, slot, onUnequip }: SlotProps) => (
-
- {slot}:{" "}
- {state.equipedItems
- .map((item) => ({ ...item, item: ItemsById[item.id] }))
- .filter((item) => item && item.item.slot === slot)
- .map((item) => (
-
- ({item.quality})
-
-
- ))}
-
+ <>
+ {slot}
+
+ {state.equipedItems
+ .map((item) => ({ ...item, item: ItemsById[item.id] }))
+ .filter((item) => item && item.item.slot === slot)
+ .map((item) => (
+
+ ({item.quality})
+
+
+ ))}
+
+ >
);
diff --git a/src/components/SourceList.module.css b/src/components/SourceList.module.css
new file mode 100644
index 0000000..5d0d646
--- /dev/null
+++ b/src/components/SourceList.module.css
@@ -0,0 +1,5 @@
+.sourceList {
+ display: grid;
+ gap: 8px;
+ grid-template-columns: 1fr 6em 6em 6em;
+}
diff --git a/src/components/SourceList.tsx b/src/components/SourceList.tsx
new file mode 100644
index 0000000..0886a18
--- /dev/null
+++ b/src/components/SourceList.tsx
@@ -0,0 +1,40 @@
+import * as _ from "lodash";
+import { getUpgrades, qualityAtMost } from "../lib/items";
+import type { State } from "../lib/state";
+import type { Item } from "../lib/types";
+import styles from "./SourceList.module.css";
+
+export type Props = {
+ state: State;
+};
+
+export const SourceList = ({ state }: Props) => {
+ const upgrades = getUpgrades(state.equipedItems);
+
+ const upgradesBySource = _.groupBy(
+ upgrades,
+ (upgrade: { item: Item }) => upgrade.item.source,
+ );
+
+ return (
+
+
Source
+
Champion
+
Hero
+
Myth
+ {Object.entries(upgradesBySource).map(([source, items]) => {
+ const champion = items.filter(qualityAtMost("champion")).length;
+ const hero = items.filter(qualityAtMost("hero")).length;
+ const myth = items.filter(qualityAtMost("myth")).length;
+ return (
+ <>
+
{source}
+
{champion}
+
{hero}
+
{myth}
+ >
+ );
+ })}
+
+ );
+};
diff --git a/src/index.css b/src/index.css
index 08a3ac9..2043a1c 100644
--- a/src/index.css
+++ b/src/index.css
@@ -18,6 +18,7 @@ a {
color: #646cff;
text-decoration: inherit;
}
+
a:hover {
color: #535bf2;
}
diff --git a/src/lib/items.ts b/src/lib/items.ts
index 4dd7ccb..03f8bba 100644
--- a/src/lib/items.ts
+++ b/src/lib/items.ts
@@ -1,5 +1,11 @@
import _ from "lodash";
-import { type Item, type ItemId, type Slot } from "./types";
+import {
+ type EquipedItem,
+ type Item,
+ type ItemId,
+ type Quality,
+ type Slot,
+} from "./types";
export const PrioryOfTheSacredFlame = [
{ id: 219309 as ItemId, slot: "trinket", name: "Tome of Light's Devotion" },
@@ -26,7 +32,73 @@ export const ItemsById: Record = AllItems.reduce(
export const ItemsBySlot: Record = _.groupBy(
AllItems,
- (item) => item.slot,
+ (item: Item) => item.slot,
);
export const itemsForSlot = (slot: Slot): Item[] => ItemsBySlot[slot] ?? [];
+
+function qualityToNumber(quality: Quality): number {
+ switch (quality) {
+ case "champion":
+ return 1;
+ case "hero":
+ return 2;
+ case "myth":
+ return 3;
+ default:
+ return 0;
+ }
+}
+
+function numberToQuality(quality: number): Quality | null {
+ switch (quality) {
+ case 1:
+ return "champion";
+ case 2:
+ return "hero";
+ case 3:
+ return "myth";
+ default:
+ return null;
+ }
+}
+
+export const qualityAtLeast =
+ (desiredQuality: Quality) =>
+ ({ quality: actualQuality }: { quality: Quality }): boolean =>
+ qualityToNumber(actualQuality) >= qualityToNumber(desiredQuality);
+
+export const qualityAtMost =
+ (desiredQuality: Quality) =>
+ ({ quality: actualQuality }: { quality: Quality }): boolean =>
+ qualityToNumber(actualQuality) <= qualityToNumber(desiredQuality);
+
+export const getUpgrades = (
+ equipedItemIds: EquipedItem[],
+): { item: Item; quality: Quality }[] => {
+ return Object.entries(ItemsBySlot).flatMap(([slot, items]) => {
+ const equipedItems = equipedItemIds.flatMap(({ id, quality }) => ({
+ item: ItemsById[id],
+ quality,
+ }));
+ const equipedInSlot = equipedItems.filter(
+ (item) => item.item.slot === slot,
+ );
+ return items.flatMap((item) => {
+ if (equipedInSlot.length === 0) {
+ return [{ item, quality: "champion" as Quality }];
+ }
+ // Change this to some sort of quality-plus-one logic
+ if (equipedInSlot.every(qualityAtLeast("myth"))) {
+ return [];
+ }
+ if (equipedInSlot.every(qualityAtLeast("hero"))) {
+ return [{ item, quality: "myth" as Quality }];
+ }
+ if (equipedInSlot.every(qualityAtLeast("champion"))) {
+ return [{ item, quality: "hero" as Quality }];
+ }
+ return [];
+ });
+ });
+};