Module:Icon: Difference between revisions

LVL (talk | contribs)
Testing fix for many icons on one page
LVL (talk | contribs)
mNo edit summary
 
(29 intermediate revisions by 6 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


-- Load language codes
local iconDataRaw = mw.loadJsonData("Data:ResourceLookup.json")
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local iconData2 = mw.loadJsonData("Data:ResourceLookup2.json")
local dictionary = mw.loadJsonData("Data:Dictionary")


-- Cache for compiled icon data, split by type (persists during page render)
local iconData = {}
local heroCacheBuilt = false
local abilityCacheBuilt = false
local itemCacheBuilt = false
local iconDataCache = {}


local cachedLangCode, cachedLangData
for k, v in pairs(iconDataRaw) do
    iconData[k] = v
end


-- Cache for expensive existence checks (persists during page render)
for k, v in pairs(iconData2) do
local existenceCache = {}
    iconData[k] = v
end


-- Heroes/Items that don't have images uploaded yet
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local missingImageOverrides = {
local lang_module = require("Module:Lang")
    -- Unreleased Heroes
local table_data = require("Module:Icon/data")
    ["boho"] = "Module-Icon missing.png",
    ["druid"] = "Module-Icon missing.png",
    ["fortuna"] = "Module-Icon missing.png",
    ["graf"] = "Module-Icon missing.png",
    ["gunslinger"] = "Module-Icon missing.png",
    ["skyrunner"] = "Module-Icon missing.png",
    ["swan"] = "Module-Icon missing.png",
    ["thumper"] = "Module-Icon missing.png",
    -- Add any others that show red links
}


-- Build lookup table from bot-uploaded JSONs, only loading the relevant JSON per type
local cachedLangCode
-- to reduce CPU cost on pages with many icon calls
local function buildIconData(iconType)
    -- Only load hero data if not already built
    if iconType == "hero" and not heroCacheBuilt then
        local heroData = mw.loadJsonData("Data:HeroData.json")


        -- Process Heroes
local linkOverrides = table_data.linkOverrides
        for heroKey, hero in pairs(heroData) do
local legacyItems = table_data.legacyItems
            if type(hero) == "table" and hero.Name then
local missingImageOverrides = table_data.missingImageOverrides
                local name = hero.Name
local aliases = table_data.aliases
                local lowerName = name:lower()
local renames = table_data.renames


                -- Check for missing image override
local function getLangCode()
                local imageName = missingImageOverrides[lowerName] or (name .. ".png")
    if cachedLangCode then
        return cachedLangCode
    end


                iconDataCache[lowerName] = {
    local title = mw.title.getCurrentTitle().fullText
                    name = name,
    local subpageLang = title:match("/([^/]+)$")
                    key = heroKey,
    local langCode = (subpageLang and lang_codes[subpageLang]) and subpageLang or "en"
                    link = name,
                    image = imageName,
                    type = "hero"
                }
            end
        end


        heroCacheBuilt = true
    cachedLangCode = langCode
    return langCode
end


    -- Only load ability data (and hero data for parent linking) if not already built
-- Infer image path from name, with override support for missing images
    elseif iconType == "ability" and not abilityCacheBuilt then
local function getImage(iconInfo, args)
        local heroData = mw.loadJsonData("Data:HeroData.json")
    local lowerName = iconInfo.name:lower()
        local abilityData = mw.loadJsonData("Data:AbilityData.json")


         -- Track which hero owns which ability (for linking)
    if args.image then
        local abilityToHero = {}
         return args.image
    end


        for heroKey, hero in pairs(heroData) do
    return missingImageOverrides[lowerName] or (iconInfo.name .. ".png")
            if type(hero) == "table" and hero.BoundAbilities then
end
                for slot, ability in pairs(hero.BoundAbilities) do
                    if ability.Key then
                        abilityToHero[ability.Key] = {
                            heroName = hero.Name,
                            abilityName = ability.Name
                        }
                    end
                end
            end
        end


        -- Process Abilities
-- Get the translated name
        for abilityKey, ability in pairs(abilityData) do
local function getTranslatedName(iconInfo, langCode)
            if type(ability) == "table" and ability.Name then
    if langCode == "en" then
                local name = ability.Name
        return iconInfo.name
                local lowerName = name:lower()
    end
                local parentInfo = abilityToHero[abilityKey]


                -- If we already have this ability name, prefer the one linked to a hero
    -- 1. Direct key
                if iconDataCache[lowerName] and iconDataCache[lowerName].type == "ability" then
    if iconInfo.key and iconInfo.key ~= "" then
                    local existingHasParent = iconDataCache[lowerName].link ~= iconDataCache[lowerName].name
        local translated = lang_module.get_string(iconInfo.key, langCode, iconInfo.name
                    local newHasParent = parentInfo ~= nil
        )


                    -- Skip if existing is linked to hero but this one isn't (prevents orphans from overwriting real ones)
        if type(translated) == "string" then
                    if existingHasParent and not newHasParent then
            return (translated:gsub("<[^>]+>", ""))
                        -- Skip this orphan ability
                    else
                        -- Overwrite: new has parent and existing doesn't, or both/neither have parents
                        local link = parentInfo and parentInfo.heroName or name
                        iconDataCache[lowerName] = {
                            name = name,
                            key = abilityKey,
                            link = link,
                            image = name .. ".png",
                            type = "ability",
                            class = "theme"
                        }
                    end
                else
                    -- No existing entry, add normally
                    local link = parentInfo and parentInfo.heroName or name
                    iconDataCache[lowerName] = {
                        name = name,
                        key = abilityKey,
                        link = link,
                        image = name .. ".png",
                        type = "ability",
                        class = "theme"
                    }
                end
            end
         end
         end


         abilityCacheBuilt = true
         return iconInfo.name
    end


     -- Only load item data if not already built
     -- 2. Dictionary lookup
     elseif iconType == "item" and not itemCacheBuilt then
     if iconInfo.key_dictionary and iconInfo.key_dictionary ~= "" then
         local itemData = mw.loadJsonData("Data:ItemData.json")
         local entry = dictionary[iconInfo.key_dictionary]


         -- Process Items
         if type(entry) == "table" then
        for itemKey, item in pairs(itemData) do
            return entry[langCode]
            if type(item) == "table" and item.Name then
                 or entry.en
                 local name = item.Name
                 or iconInfo.name
                 local lowerName = name:lower()
        end
    end


                iconDataCache[lowerName] = {
    -- No valid key > fallback
                    name = name,
    return iconInfo.name
                    key = itemKey,
end
                    link = name,
                    image = name .. ".png",
                    type = "item"
                }
            end
        end


         itemCacheBuilt = true
-- Infer link from type and available fields
local function getLink(iconInfo, langCode)
    if iconInfo.type == "ability" then
        local hero = iconInfo.hero_name or iconInfo.name
       
        -- Get the translated name for the specific ability
        local abilityName = getTranslatedName(iconInfo, langCode)
       
        -- Format for URL: replace spaces with underscores
         local anchor = abilityName:gsub(" ", "_")
       
        return hero .. "#" .. anchor
     end
     end
    return linkOverrides[iconInfo.name] or iconInfo.name
end
end


-- Get icon data by name and type (with alias support)
-- Returns the canonical display name for a given user input
local function getIconData(iconName, iconType)
-- Used by templates to store the correct casing in data attributes
     buildIconData(iconType)
-- Normalizes casing so exact string matches work reliably in Lua modules
     local lowerName = iconName:lower()
function p.getCanonicalName(frame)
     local name = frame.args[1] or ""
     local lowerName = name:lower()


     -- Check main lookup
     if renames[lowerName] then
     if iconDataCache[lowerName] then
        lowerName = renames[lowerName]
         return iconDataCache[lowerName], nil
     elseif aliases[lowerName] then
         lowerName = aliases[lowerName]
     end
     end


    -- Check aliases (for edge cases like "doorman" -> "the doorman")
     local iconInfo = iconData[lowerName] or legacyItems[lowerName]
     local aliases = {
        ["doorman"] = "the doorman",
        ["mo and krill"] = "mo & krill",
        ["mo"] = "mo & krill",
        ["krill"] = "mo & krill",
        ["curse"] = "cursed relic",
        ["debuff remover"] = "dispel magic"
    }


     if aliases[lowerName] then
    -- If unknown, return original input
         return iconDataCache[aliases[lowerName]], nil
     if not iconInfo then
         return name
     end
     end


    return nil, string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", iconName)
-- Quick hack: ability names are used by MediaWiki:Gadget-infobox-tooltip.js and expected to be localized
end
    local langCode = getLangCode()


-- Language handling
     if iconInfo.type == "ability" then
local function getLangData()
         return getTranslatedName(iconInfo, langCode)
     if cachedLangData then
         return cachedLangCode, cachedLangData
     end
     end


     local title = mw.title.getCurrentTitle().fullText
     return iconInfo.name
     local subpageLang = title:match("/([^/]+)$")
end
     local langCode = (subpageLang and lang_codes[subpageLang]) and subpageLang or "en"
 
local function render(name, args)
     local originalName = name
    local lowerName = name:lower()
     args = args or {}


    local langData = {}
     if renames[lowerName] then
     if langCode ~= "en" then
         lowerName = renames[lowerName]
         local success, data = pcall(function()
    elseif aliases[lowerName] then
            return mw.loadJsonData("Data:Lang " .. langCode .. ".json")
         lowerName = aliases[lowerName]
        end)
         if success and type(data) == "table" then
            langData = data
        end
     end
     end


     cachedLangCode = langCode
     -- Lookup live data > legacy fallback
     cachedLangData = langData
     local iconInfo = iconData[lowerName] or legacyItems[lowerName]


     return langCode, langData
     -- Throw error if not found
end
    if not iconInfo then
        return string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", name)
    end


-- Main render function
     local customText = args.l1 ~= "" and args.l1 or nil
function p.render(frame)
     local customLink = args.link ~= "" and args.link or nil
     local args = frame:getParent().args
    local name = args[1] or ""
    local customText = args.l1 or ""
     local iconType = args.type or "item"
 
    -- Set Defaults
     local size = args.size or "20px"
     local size = args.size or "20px"
     local noLink = args["no-link"] == "true"
     local noLink = args["no-link"] == "true"
    local noFile = args["no-file"] == "true"
     local iconOnly = args["icon-only"] == "true"
     local iconOnly = args["icon-only"] == "true"


    -- Scan unnamed parameters
     for k, v in pairs(args) do
     for k, v in pairs(args) do
         if type(k) == "number" and k > 1 then
         if type(k) == "number" and k > 1 then
             local val = mw.text.trim(v)
             local val = mw.text.trim(v)
             if val == "icon-only" then
             if val == "icon-only" then
                 iconOnly = true
                 iconOnly = true
Line 226: Line 170:
     end
     end


    -- Get icon data automatically from JSON, using type to limit which JSON is loaded
     local langCode = getLangCode()
     local iconData, err = getIconData(name, iconType)
 
     if not iconData then
    local displayName
         return "Error: " .. err
     if customText then
         displayName = customText
    elseif renames[name:lower()] then
        -- Preserve original if renamed
        displayName = originalName
    else
        displayName = getTranslatedName(iconInfo, langCode)
     end
     end


     local langCode, langData = getLangData()
     local baseLink = customLink or getLink(iconInfo, langCode)
    local fragment = ""


     -- Determine display name
     -- Split the fragment (#Abilities) from the base link
    local displayName = ""
     if baseLink:find("#") then
     if customText ~= "" then
         local parts = mw.text.split(baseLink, "#", true)
         displayName = customText
         baseLink = parts[1]
    elseif iconData.key and langData[iconData.key] then
         fragment = "#" .. (parts[2] or "")
         displayName = langData[iconData.key]
    else
         displayName = iconData.name
     end
     end


    -- Localize link if on translated subpage
    local baseLink = iconData.link or ""
     local link = baseLink
     local link = baseLink


     if langCode ~= "en" and baseLink ~= "" then
     if not customLink and langCode ~= "en" and baseLink ~= "" then
         link = baseLink .. "/" .. langCode
         link = baseLink .. "/" .. langCode
     end
     end


     -- Style logic (only abilities get theme class by default)
     -- Reattach fragment at the end
     local class = iconData.class or ""
     link = link .. fragment
 
    -- Add CSS class for abilities
     local style
     local style
     if class:find("theme", 1, true) then
     if iconInfo.type == "ability" then
         style = 'class="module-icon-ability" style="position:relative; bottom:2px;"'
         style = 'class="module-icon-ability" style="position:relative; bottom:2px;"'
     else
     else
         style = 'style="position:relative; bottom:2px;"'
         style = 'class="c-icon" style="position:relative; bottom:2px;"'
     end
     end


    -- Generate icon HTML
     local imageLink = noLink and "" or link
     local imageLink = noLink and "" or link
     local iconHtml = string.format('<span %s>[[File:%s|%s|link=%s]]</span>', style, iconData.image, size, imageLink)
     local iconHtml = ""
 
    if not noFile then
        local image = getImage(iconInfo, args)
        iconHtml = string.format('<span %s>[[File:%s|%s|link=%s]]</span>', style, image, size, imageLink)
    end


     if iconOnly or displayName == "" then
     if iconOnly or displayName == "" then
Line 270: Line 222:


     local textHtml = (noLink or link == "") and displayName or string.format('[[%s|%s]]', link, displayName)
     local textHtml = (noLink or link == "") and displayName or string.format('[[%s|%s]]', link, displayName)
     return '<span style="white-space:nowrap;">' .. iconHtml .. " " .. textHtml .. '</span>'
 
     if iconHtml ~= "" then
        return '<span style="white-space:nowrap;">' .. iconHtml .. " " .. textHtml .. '</span>'
    else
        return textHtml
    end
end
 
-- Template entrypoint
function p.render(frame)
    local args = frame:getParent().args
    local name = args[1] or ""
 
    return render(name, args)
end
 
-- Module entrypoint
function p._render(name, args)
    return render(name, args)
end
end


return p
return p