Adds source list.
This commit is contained in:
37
src/App.css
37
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;
|
||||
}
|
||||
|
||||
@@ -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 })}
|
||||
/>
|
||||
<SourceList state={state} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
17
src/components/Equipment.module.css
Normal file
17
src/components/Equipment.module.css
Normal file
@@ -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 {
|
||||
}
|
||||
@@ -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) => (
|
||||
<div>
|
||||
<ItemTypeahead value={null} onSelect={onEquip} />
|
||||
{Slots.map((slot) => (
|
||||
<Slot state={state} slot={slot} onUnequip={onUnequip} key={slot} />
|
||||
))}
|
||||
<div className={styles.equipedItems}>
|
||||
{Slots.map((slot) => (
|
||||
<Slot state={state} slot={slot} onUnequip={onUnequip} key={slot} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -26,16 +29,18 @@ type SlotProps = {
|
||||
};
|
||||
|
||||
const Slot = ({ state, slot, onUnequip }: SlotProps) => (
|
||||
<div>
|
||||
{slot}:{" "}
|
||||
{state.equipedItems
|
||||
.map((item) => ({ ...item, item: ItemsById[item.id] }))
|
||||
.filter((item) => item && item.item.slot === slot)
|
||||
.map((item) => (
|
||||
<span>
|
||||
<ItemLink item={item.item} />({item.quality})
|
||||
<button onClick={() => onUnequip(item.item)}>X</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div className={`${styles.slot} ${styles.gridItem}`}>{slot}</div>
|
||||
<div className={styles.gridItem}>
|
||||
{state.equipedItems
|
||||
.map((item) => ({ ...item, item: ItemsById[item.id] }))
|
||||
.filter((item) => item && item.item.slot === slot)
|
||||
.map((item) => (
|
||||
<div>
|
||||
<ItemLink item={item.item} />({item.quality})
|
||||
<button onClick={() => onUnequip(item.item)}>X</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
5
src/components/SourceList.module.css
Normal file
5
src/components/SourceList.module.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.sourceList {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: 1fr 6em 6em 6em;
|
||||
}
|
||||
40
src/components/SourceList.tsx
Normal file
40
src/components/SourceList.tsx
Normal file
@@ -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 (
|
||||
<div className={styles.sourceList}>
|
||||
<div>Source</div>
|
||||
<div>Champion</div>
|
||||
<div>Hero</div>
|
||||
<div>Myth</div>
|
||||
{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 (
|
||||
<>
|
||||
<div>{source}</div>
|
||||
<div>{champion}</div>
|
||||
<div>{hero}</div>
|
||||
<div>{myth}</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,7 @@ a {
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
@@ -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<string, Item> = AllItems.reduce(
|
||||
|
||||
export const ItemsBySlot: Record<Slot, Item[]> = _.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 [];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user