OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 226 LINES
 
[ HISTORY ]  [ UP ]
 

import { formatRace as utilFormatRace } from "../../lib/utils";
import { renderEquipmentIconsCompact } from "../../lib/equipment-utils";

function wzIconSmall(slug?: string | null) {
  return slug
    ? `https://wow.zamimg.com/images/wow/icons/small/${slug}.jpg`
    : "";
}

function wzIconLarge(slug?: string | null) {
  return slug
    ? `https://wow.zamimg.com/images/wow/icons/large/${slug}.jpg`
    : "";
}

export function formatTalents(talents: any) {
  if (!talents || !Array.isArray(talents.talents)) return [] as any[];
  const list = talents.talents
    .map((t: any) => {
      const icon = t.icon
        ? `<img src="${wzIconSmall(t.icon)}" alt="${t.name}" class="talent-icon-in
line" loading="lazy" />`
        : "";
      const link = t.spellId
        ? `<a href="https://www.wowhead.com/mop-classic/spell=${t.spellId}" target
="_blank" class="talent-link">${t.name}</a>`
        : `<span class="talent-name">${t.name}</span>`;
      return `<div class="talent-line">${icon}${link}</div>`;
    })
    .join("");
  return [
    {
      label: "Talents",
      value: `<div class="talents-list">${list}</div>`,
      isTalentList: true,
    },
  ];
}

export function formatGlyphs(glyphs: any) {
  const mk = (slots: string[], label: string) => {
    const items: string[] = [];
    slots.forEach((slot) => {
      if (!glyphs[slot]) return;
      const name = glyphs[`${slot}Name`] || `Glyph ${glyphs[slot]}`;
      const icon = glyphs[`${slot}Icon`];
      const spellId = glyphs[`${slot}SpellId`];
      const url = spellId
        ? `https://www.wowhead.com/mop-classic/spell=${spellId}`
        : `https://www.wowhead.com/mop-classic/item=${glyphs[slot]}`;
      const iconHtml = icon
        ? `<img src="${wzIconSmall(icon)}" alt="${name}" class="glyph-icon-inline"
 loading="lazy" />`
        : "";
      const nameHtml = url
        ? `<a href="${url}" target="_blank" class="glyph-link">${name}</a>`
        : `<span class="glyph-name">${name}</span>`;
      items.push(`<div class="glyph-line">${iconHtml}${nameHtml}</div>`);
    });
    return items.length
      ? {
          label,
          value: `<div class=\"glyphs-list\">${items.join("")}</div>`,
          isGlyphList: true,
        }
      : null;
  };
  return [
    mk(["major1", "major2", "major3"], "Major Glyphs"),
    mk(["minor1", "minor2", "minor3"], "Minor Glyphs"),
  ].filter(Boolean) as any[];
}

export function formatConsumables(c: any) {
  const mk = (
    label: string,
    idKey: string,
    nameKey: string,
    iconKey: string,
    qualityKey: string,
  ) => {
    if (!c[idKey]) return null;
    const url = `https://www.wowhead.com/mop-classic/item=${c[idKey]}`;
    const iconUrl = c[iconKey] ? wzIconLarge(c[iconKey]) : null;
    return {
      label,
      value: c[nameKey] || `Item ${c[idKey]}`,
      wowheadUrl: url,
      iconUrl,
      quality: c[qualityKey],
      isItem: true,
    };
  };
  const items: any[] = [];
  const f1 = mk("Flask", "flaskId", "flaskName", "flaskIcon", "flaskQuality");
  if (f1) items.push(f1);
  const f2 = mk("Food", "foodId", "foodName", "foodIcon", "foodQuality");
  if (f2) items.push(f2);
  const f3 = mk("Potion", "potId", "potName", "potIcon", "potQuality");
  if (f3) items.push(f3);
  const f4 = mk(
    "Pre-Potion",
    "prepotId",
    "prepotName",
    "prepotIcon",
    "prepotQuality",
  );
  if (f4) items.push(f4);
  return items;
}

export function formatLoadout(loadout: any) {
  if (!loadout) return [] as any[];
  const sections: any[] = [];
  if (loadout.race || loadout.profession1 || loadout.profession2) {
    const items: any[] = [];
    if (loadout.race)
      items.push({ label: "Race", value: utilFormatRace(loadout.race) });
    if (loadout.profession1)
      items.push({ label: "Profession 1", value: loadout.profession1 });
    if (loadout.profession2)
      items.push({ label: "Profession 2", value: loadout.profession2 });
    sections.push({ title: "Character", items });
  }
  if (loadout.talents || loadout.glyphs) {
    const items: any[] = [];
    if (loadout.talents) items.push(...formatTalents(loadout.talents));
    if (loadout.glyphs) items.push(...formatGlyphs(loadout.glyphs));
    sections.push({ title: "Talents & Glyphs", items });
  }
  if (loadout.consumables) {
    const items = formatConsumables(loadout.consumables);
    if (items.length > 0) sections.push({ title: "Consumables", items });
  }
  if (
    loadout.equipment &&
    (loadout.equipment.items || Array.isArray(loadout.equipment))
  ) {
    const itemsArr = loadout.equipment.items || loadout.equipment;
    if (Array.isArray(itemsArr) && itemsArr.length > 0) {
      sections.push({ title: "Equipment", items: itemsArr, isEquipment: true });
    }
  }
  return sections;
}

export function generateLoadoutDropdown(loadout: any): string {
  const sections = formatLoadout(loadout);
  if (!sections || sections.length === 0) return "";
  const toQualityClass = (q: any) => {
    const map: Record<string, string> = {
      "0": "quality-poor",
      "1": "quality-common",
      "2": "quality-uncommon",
      "3": "quality-rare",
      "4": "quality-epic",
      "5": "quality-legendary",
      "6": "quality-artifact",
      "7": "quality-heirloom",
    };
    if (typeof q === "number") return map[String(q)] || "quality-common";
    if (typeof q === "string") {
      const s = q.toLowerCase();
      if (s.startsWith("quality-")) return s;
      // Accept both enum (EPIC) and numeric-like strings
      return map[s] || `quality-${s}`;
    }
    return "quality-common";
  };

  // compact equipment icon grid is now centralized in equipment-utils

  const sectionsHtml = sections
    .map((section: any) => {
      if (section.isEquipment) {
        const compact = renderEquipmentIconsCompact(
          loadout?.equipment?.items || section.items || [],
        );
        return `
        <div class="loadout-section">
          <h4 class="loadout-title">${section.title}</h4>
          <div class="loadout-item">${compact}</div>
        </div>`;
      } else {
        const itemsHtml = section.items
          .map((item: any) => {
            const labelHtml = item.label
              ? `<span class="loadout-label">${item.label}</span>`
              : "";
            // Consumables and linked items
            if (
              (item.isItem || item.isGlyph || item.isTalent) &&
              item.wowheadUrl
            ) {
              const iconHtml = item.iconUrl
                ? `<img src="${item.iconUrl}" alt="${item.value}" class="consumabl
e-icon" loading="lazy" />`
                : "";
              const qualityClass = item.quality
                ? `quality-${item.quality}`
                : "";
              const valueHtml = `<div class="consumable-item-header">${iconHtml}<a
 href="${item.wowheadUrl}" target="_blank" class="equipment-item-link ${qualityCla
ss}">${item.value}</a></div>`;
              return `<div class="loadout-item">${labelHtml}${valueHtml}</div>`;
            }
            // Talents & Glyphs lists come as prebuilt HTML
            if (item.isTalentList || item.isGlyphList) {
              return `<div class="loadout-item">${labelHtml}${item.value}</div>`;
            }
            // Plain values
            return `<div class="loadout-item">${labelHtml}<span class="loadout-val
ue">${item.value}</span></div>`;
          })
          .join("");
        return `
        <div class="loadout-section">
          <h4 class="loadout-title">${section.title}</h4>
          <div class="loadout-grid">${itemsHtml}</div>
        </div>`;
      }
    })
    .join("");

  const wowSimButton = loadout?.simLink
    ? `<a href="${loadout.simLink}" target="_blank" class="loadout-button wowsim-b
utton">Open in WoWSims</a>`
    : "";

  // Return just the content; caller wraps with chart-dropdown.
  return `${wowSimButton}
      ${sectionsHtml}`;
}

import { formatRace as utilFormatRace } from
 "../../lib/utils";
import { renderEquipmentIconsCompact } from 
"../../lib/equipment-utils";

function wzIconSmall(slug?: string | null) {
  return slug
    ? `https://wow.zamimg.com/images/wow/ico
ns/small/${slug}.jpg`
    : "";
}

function wzIconLarge(slug?: string | null) {
  return slug
    ? `https://wow.zamimg.com/images/wow/ico
ns/large/${slug}.jpg`
    : "";
}

export function formatTalents(talents: any) 
{
  if (!talents || !Array.isArray(talents.tal
ents)) return [] as any[];
  const list = talents.talents
    .map((t: any) => {
      const icon = t.icon
        ? `<img src="${wzIconSmall(t.icon)}"
 alt="${t.name}" class="talent-icon-inline" 
loading="lazy" />`
        : "";
      const link = t.spellId
        ? `<a href="https://www.wowhead.com/
mop-classic/spell=${t.spellId}" target="_bla
nk" class="talent-link">${t.name}</a>`
        : `<span class="talent-name">${t.nam
e}</span>`;
      return `<div class="talent-line">${ico
n}${link}</div>`;
    })
    .join("");
  return [
    {
      label: "Talents",
      value: `<div class="talents-list">${li
st}</div>`,
      isTalentList: true,
    },
  ];
}

export function formatGlyphs(glyphs: any) {
  const mk = (slots: string[], label: string
) => {
    const items: string[] = [];
    slots.forEach((slot) => {
      if (!glyphs[slot]) return;
      const name = glyphs[`${slot}Name`] || 
`Glyph ${glyphs[slot]}`;
      const icon = glyphs[`${slot}Icon`];
      const spellId = glyphs[`${slot}SpellId
`];
      const url = spellId
        ? `https://www.wowhead.com/mop-class
ic/spell=${spellId}`
        : `https://www.wowhead.com/mop-class
ic/item=${glyphs[slot]}`;
      const iconHtml = icon
        ? `<img src="${wzIconSmall(icon)}" a
lt="${name}" class="glyph-icon-inline" loadi
ng="lazy" />`
        : "";
      const nameHtml = url
        ? `<a href="${url}" target="_blank" 
class="glyph-link">${name}</a>`
        : `<span class="glyph-name">${name}<
/span>`;
      items.push(`<div class="glyph-line">${
iconHtml}${nameHtml}</div>`);
    });
    return items.length
      ? {
          label,
          value: `<div class=\"glyphs-list\"
>${items.join("")}</div>`,
          isGlyphList: true,
        }
      : null;
  };
  return [
    mk(["major1", "major2", "major3"], "Majo
r Glyphs"),
    mk(["minor1", "minor2", "minor3"], "Mino
r Glyphs"),
  ].filter(Boolean) as any[];
}

export function formatConsumables(c: any) {
  const mk = (
    label: string,
    idKey: string,
    nameKey: string,
    iconKey: string,
    qualityKey: string,
  ) => {
    if (!c[idKey]) return null;
    const url = `https://www.wowhead.com/mop
-classic/item=${c[idKey]}`;
    const iconUrl = c[iconKey] ? wzIconLarge
(c[iconKey]) : null;
    return {
      label,
      value: c[nameKey] || `Item ${c[idKey]}
`,
      wowheadUrl: url,
      iconUrl,
      quality: c[qualityKey],
      isItem: true,
    };
  };
  const items: any[] = [];
  const f1 = mk("Flask", "flaskId", "flaskNa
me", "flaskIcon", "flaskQuality");
  if (f1) items.push(f1);
  const f2 = mk("Food", "foodId", "foodName"
, "foodIcon", "foodQuality");
  if (f2) items.push(f2);
  const f3 = mk("Potion", "potId", "potName"
, "potIcon", "potQuality");
  if (f3) items.push(f3);
  const f4 = mk(
    "Pre-Potion",
    "prepotId",
    "prepotName",
    "prepotIcon",
    "prepotQuality",
  );
  if (f4) items.push(f4);
  return items;
}

export function formatLoadout(loadout: any) 
{
  if (!loadout) return [] as any[];
  const sections: any[] = [];
  if (loadout.race || loadout.profession1 ||
 loadout.profession2) {
    const items: any[] = [];
    if (loadout.race)
      items.push({ label: "Race", value: uti
lFormatRace(loadout.race) });
    if (loadout.profession1)
      items.push({ label: "Profession 1", va
lue: loadout.profession1 });
    if (loadout.profession2)
      items.push({ label: "Profession 2", va
lue: loadout.profession2 });
    sections.push({ title: "Character", item
s });
  }
  if (loadout.talents || loadout.glyphs) {
    const items: any[] = [];
    if (loadout.talents) items.push(...forma
tTalents(loadout.talents));
    if (loadout.glyphs) items.push(...format
Glyphs(loadout.glyphs));
    sections.push({ title: "Talents & Glyphs
", items });
  }
  if (loadout.consumables) {
    const items = formatConsumables(loadout.
consumables);
    if (items.length > 0) sections.push({ ti
tle: "Consumables", items });
  }
  if (
    loadout.equipment &&
    (loadout.equipment.items || Array.isArra
y(loadout.equipment))
  ) {
    const itemsArr = loadout.equipment.items
 || loadout.equipment;
    if (Array.isArray(itemsArr) && itemsArr.
length > 0) {
      sections.push({ title: "Equipment", it
ems: itemsArr, isEquipment: true });
    }
  }
  return sections;
}

export function generateLoadoutDropdown(load
out: any): string {
  const sections = formatLoadout(loadout);
  if (!sections || sections.length === 0) re
turn "";
  const toQualityClass = (q: any) => {
    const map: Record<string, string> = {
      "0": "quality-poor",
      "1": "quality-common",
      "2": "quality-uncommon",
      "3": "quality-rare",
      "4": "quality-epic",
      "5": "quality-legendary",
      "6": "quality-artifact",
      "7": "quality-heirloom",
    };
    if (typeof q === "number") return map[St
ring(q)] || "quality-common";
    if (typeof q === "string") {
      const s = q.toLowerCase();
      if (s.startsWith("quality-")) return s
;
      // Accept both enum (EPIC) and numeric
-like strings
      return map[s] || `quality-${s}`;
    }
    return "quality-common";
  };

  // compact equipment icon grid is now cent
ralized in equipment-utils

  const sectionsHtml = sections
    .map((section: any) => {
      if (section.isEquipment) {
        const compact = renderEquipmentIcons
Compact(
          loadout?.equipment?.items || secti
on.items || [],
        );
        return `
        <div class="loadout-section">
          <h4 class="loadout-title">${sectio
n.title}</h4>
          <div class="loadout-item">${compac
t}</div>
        </div>`;
      } else {
        const itemsHtml = section.items
          .map((item: any) => {
            const labelHtml = item.label
              ? `<span class="loadout-label"
>${item.label}</span>`
              : "";
            // Consumables and linked items
            if (
              (item.isItem || item.isGlyph |
| item.isTalent) &&
              item.wowheadUrl
            ) {
              const iconHtml = item.iconUrl
                ? `<img src="${item.iconUrl}
" alt="${item.value}" class="consumable-icon
" loading="lazy" />`
                : "";
              const qualityClass = item.qual
ity
                ? `quality-${item.quality}`
                : "";
              const valueHtml = `<div class=
"consumable-item-header">${iconHtml}<a href=
"${item.wowheadUrl}" target="_blank" class="
equipment-item-link ${qualityClass}">${item.
value}</a></div>`;
              return `<div class="loadout-it
em">${labelHtml}${valueHtml}</div>`;
            }
            // Talents & Glyphs lists come a
s prebuilt HTML
            if (item.isTalentList || item.is
GlyphList) {
              return `<div class="loadout-it
em">${labelHtml}${item.value}</div>`;
            }
            // Plain values
            return `<div class="loadout-item
">${labelHtml}<span class="loadout-value">${
item.value}</span></div>`;
          })
          .join("");
        return `
        <div class="loadout-section">
          <h4 class="loadout-title">${sectio
n.title}</h4>
          <div class="loadout-grid">${itemsH
tml}</div>
        </div>`;
      }
    })
    .join("");

  const wowSimButton = loadout?.simLink
    ? `<a href="${loadout.simLink}" target="
_blank" class="loadout-button wowsim-button"
>Open in WoWSims</a>`
    : "";

  // Return just the content; caller wraps w
ith chart-dropdown.
  return `${wowSimButton}
      ${sectionsHtml}`;
}
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET