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 []; + }); + }); +};