master
  1import { Badge, Evidence, ID, List, Presenter } from './badges.js'
  2import { parseMask } from './utils.js'
  3import { Data } from './data.js'
  4
  5export const presentBadges = <P extends Presenter<List>>(
  6  presenters: P[],
  7  data: Data,
  8  userBadges: Badge[],
  9  pickBadges: string[],
 10  omitBadges: string[],
 11  compact: boolean,
 12): Badge[] => {
 13  const newlyAddedBadges = new Set<ID>()
 14
 15  for (const presenter of presenters) {
 16    const newBadges: Badge[] = []
 17    const grant = badgeCollection(newBadges)
 18
 19    // @ts-ignore As `id: string` is not assignable to `id: ID`.
 20    presenter.present(data, grant)
 21
 22    if (newBadges.length === 0) {
 23      continue
 24    }
 25
 26    // Add new badges to the list of badges.
 27    for (const b of newBadges) {
 28      newlyAddedBadges.add(b.id)
 29    }
 30
 31    // Enhance badges with image URLs.
 32    for (const b of newBadges) {
 33      b.image = `https://my-badges.github.io/my-badges/${b.id}.png`
 34    }
 35
 36    const badgeFromPresenter = (x: Badge) => presenter.badges.includes(x.id)
 37
 38    // Merge existing userBadges with newBadges.
 39    if (compact && presenter.tiers) {
 40      const newHighestTierBadge = newBadges.reduce((prev, curr) => {
 41        return prev.tier > curr.tier ? prev : curr
 42      })
 43
 44      const existingBadgeIndex = userBadges.findIndex(badgeFromPresenter)
 45      if (existingBadgeIndex === -1) {
 46        userBadges.push(newHighestTierBadge)
 47      } else if (
 48        newHighestTierBadge.tier >= userBadges[existingBadgeIndex].tier
 49      ) {
 50        userBadges[existingBadgeIndex] = newHighestTierBadge
 51
 52        // Drop all other badges from the same presenter.
 53        userBadges = userBadges.filter(
 54          (x, i) => i === existingBadgeIndex || !badgeFromPresenter(x),
 55        )
 56      }
 57    } else {
 58      for (const badge of newBadges) {
 59        const index = userBadges.findIndex((x) => x.id === badge.id)
 60        if (index === -1) {
 61          userBadges.push(badge)
 62        } else {
 63          userBadges[index] = badge
 64        }
 65      }
 66    }
 67  }
 68
 69  if (pickBadges.length > 0) {
 70    userBadges = userBadges.filter((x) =>
 71      pickBadges.map(parseMask).some((r) => r.test(x.id)),
 72    )
 73  }
 74
 75  if (omitBadges.length > 0) {
 76    userBadges = userBadges.filter((x) =>
 77      omitBadges.map(parseMask).every((r) => !r.test(x.id)),
 78    )
 79  }
 80
 81  // Filter out old badges, keep only granted badges.
 82  userBadges = userBadges.filter((b) => newlyAddedBadges.has(b.id))
 83
 84  return userBadges
 85}
 86
 87export function badgeCollection(newBadges: Badge[]) {
 88  return function grant(id: ID, desc: string) {
 89    const badge: Badge = {
 90      id,
 91      tier: 0,
 92      desc,
 93      body: '',
 94      image: '',
 95    }
 96    if (!newBadges.some((x) => x.id === id)) {
 97      newBadges.push(badge)
 98    }
 99    return new Evidence(badge)
100  }
101}