master @ 113 LINES
[ HISTORY ] [ UP ]
┌─ ASTRO ────────────────────────────────────────────────────────────────────┐
│ --- │
│ import "./GearSlotCard.scss"; │
│ import type { GearSlotBucket } from "../../../lib/types"; │
│ import ItemTooltip from "../../ItemTooltip/ItemTooltip.astro"; │
│ │
│ interface Props { │
│ slot: string; │
│ label: string; │
│ bucket: GearSlotBucket | undefined; │
│ // cap items per slot to keep long-tail noise out │
│ limit?: number; │
│ // hide items below this share % of players_with_slot │
│ minSharePct?: number; │
│ // Blizzard down-scales every CM piece to ilvl 463, so the tooltip should │
│ // show that level (not the player's actual upgraded ilvl) │
│ tooltipIlvl?: number; │
│ } │
│ │
│ const { │
│ slot, │
│ label, │
│ bucket, │
│ limit = 8, │
│ minSharePct = 0, │
│ tooltipIlvl = 463, │
│ } = Astro.props; │
│ │
│ const playersWithSlot = bucket?.players_with_slot ?? 0; │
│ const allItems = bucket?.items ?? []; │
│ const items = allItems │
│ .filter((item) => { │
│ if (playersWithSlot === 0) return false; │
│ return (item.count / playersWithSlot) * 100 >= minSharePct; │
│ }) │
│ .slice(0, limit); │
│ │
│ function pct(count: number): number { │
│ if (!playersWithSlot) return 0; │
│ return (count / playersWithSlot) * 100; │
│ } │
│ │
│ function iconUrl(icon: string | undefined): string | null { │
│ if (!icon) return null; │
│ return `https://wow.zamimg.com/images/wow/icons/medium/${icon}.jpg`; │
│ } │
│ │
│ function qualityClass(q: number): string { │
│ switch (q) { │
│ case 5: │
│ return "quality-legendary"; │
│ case 4: │
│ return "quality-epic"; │
│ case 3: │
│ return "quality-rare"; │
│ case 2: │
│ return "quality-uncommon"; │
│ case 7: │
│ return "quality-heirloom"; │
│ default: │
│ return "quality-common"; │
│ } │
│ } │
│ --- │
│ │
│ <article class="gear-slot-card"> │
│ <header class="gear-slot-card__header"> │
│ <span class="gear-slot-card__slot">{label}</span> │
│ {bucket && <span class="gear-slot-card__pool">{playersWithSlot} runs</span>} │
│ </header> │
│ │
│ { │
│ items.length > 0 ? ( │
│ <ul class="gear-slot-card__bars"> │
│ {items.map((item) => { │
│ const sharePct = pct(item.count); │
│ return ( │
│ <li class="gear-slot-card__row"> │
│ <ItemTooltip │
│ itemId={item.item_id} │
│ ilvl={tooltipIlvl} │
│ class={`gear-slot-card__icon-link ${qualityClass(item.quality)}`} │
│ ariaLabel={item.name} │
│ > │
│ {iconUrl(item.icon) ? ( │
│ <img │
│ class="gear-slot-card__icon" │
│ src={iconUrl(item.icon)} │
│ alt={item.name} │
│ loading="lazy" │
│ /> │
│ ) : ( │
│ <span class="gear-slot-card__icon-fallback" /> │
│ )} │
│ </ItemTooltip> │
│ <div class="gear-slot-card__bar-track" aria-hidden="true"> │
│ <div │
│ class="gear-slot-card__bar-fill" │
│ style={`width: ${sharePct.toFixed(1)}%`} │
│ /> │
│ <span class="gear-slot-card__bar-label"> │
│ {sharePct.toFixed(0)}% │
│ </span> │
│ </div> │
│ </li> │
│ ); │
│ })} │
│ </ul> │
│ ) : ( │
│ <p class="gear-slot-card__empty">No data</p> │
│ ) │
│ } │
│ </article> │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ASTRO ──────────────────────────────┐
│ --- │
│ import "./GearSlotCard.scss"; │
│ import type { GearSlotBucket } from "../../. │
│ ./lib/types"; │
│ import ItemTooltip from "../../ItemTooltip/I │
│ temTooltip.astro"; │
│ │
│ interface Props { │
│ slot: string; │
│ label: string; │
│ bucket: GearSlotBucket | undefined; │
│ // cap items per slot to keep long-tail no │
│ ise out │
│ limit?: number; │
│ // hide items below this share % of player │
│ s_with_slot │
│ minSharePct?: number; │
│ // Blizzard down-scales every CM piece to │
│ ilvl 463, so the tooltip should │
│ // show that level (not the player's actua │
│ l upgraded ilvl) │
│ tooltipIlvl?: number; │
│ } │
│ │
│ const { │
│ slot, │
│ label, │
│ bucket, │
│ limit = 8, │
│ minSharePct = 0, │
│ tooltipIlvl = 463, │
│ } = Astro.props; │
│ │
│ const playersWithSlot = bucket?.players_with │
│ _slot ?? 0; │
│ const allItems = bucket?.items ?? []; │
│ const items = allItems │
│ .filter((item) => { │
│ if (playersWithSlot === 0) return false; │
│ return (item.count / playersWithSlot) * │
│ 100 >= minSharePct; │
│ }) │
│ .slice(0, limit); │
│ │
│ function pct(count: number): number { │
│ if (!playersWithSlot) return 0; │
│ return (count / playersWithSlot) * 100; │
│ } │
│ │
│ function iconUrl(icon: string | undefined): │
│ string | null { │
│ if (!icon) return null; │
│ return `https://wow.zamimg.com/images/wow/ │
│ icons/medium/${icon}.jpg`; │
│ } │
│ │
│ function qualityClass(q: number): string { │
│ switch (q) { │
│ case 5: │
│ return "quality-legendary"; │
│ case 4: │
│ return "quality-epic"; │
│ case 3: │
│ return "quality-rare"; │
│ case 2: │
│ return "quality-uncommon"; │
│ case 7: │
│ return "quality-heirloom"; │
│ default: │
│ return "quality-common"; │
│ } │
│ } │
│ --- │
│ │
│ <article class="gear-slot-card"> │
│ <header class="gear-slot-card__header"> │
│ <span class="gear-slot-card__slot">{labe │
│ l}</span> │
│ {bucket && <span class="gear-slot-card__ │
│ pool">{playersWithSlot} runs</span>} │
│ </header> │
│ │
│ { │
│ items.length > 0 ? ( │
│ <ul class="gear-slot-card__bars"> │
│ {items.map((item) => { │
│ const sharePct = pct(item.count); │
│ return ( │
│ <li class="gear-slot-card__row"> │
│ <ItemTooltip │
│ itemId={item.item_id} │
│ ilvl={tooltipIlvl} │
│ class={`gear-slot-card__icon │
│ -link ${qualityClass(item.quality)}`} │
│ ariaLabel={item.name} │
│ > │
│ {iconUrl(item.icon) ? ( │
│ <img │
│ class="gear-slot-card__i │
│ con" │
│ src={iconUrl(item.icon)} │
│ alt={item.name} │
│ loading="lazy" │
│ /> │
│ ) : ( │
│ <span class="gear-slot-car │
│ d__icon-fallback" /> │
│ )} │
│ </ItemTooltip> │
│ <div class="gear-slot-card__ba │
│ r-track" aria-hidden="true"> │
│ <div │
│ class="gear-slot-card__bar │
│ -fill" │
│ style={`width: ${sharePct. │
│ toFixed(1)}%`} │
│ /> │
│ <span class="gear-slot-card_ │
│ _bar-label"> │
│ {sharePct.toFixed(0)}% │
│ </span> │
│ </div> │
│ </li> │
│ ); │
│ })} │
│ </ul> │
│ ) : ( │
│ <p class="gear-slot-card__empty">No da │
│ ta</p> │
│ ) │
│ } │
│ </article> │
└──────────────────────────────────────────────┘
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET