Mudanças entre as edições de "Módulo:Conquistas"

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
Linha 5: Linha 5:
   Módulo:AchievementDB (carregado via mw.loadData, cacheado pelo Scribunto).
   Módulo:AchievementDB (carregado via mw.loadData, cacheado pelo Scribunto).


   Usado com Widget:Conquistas via {{#invoke:Conquistas|renderAll}}
   Usado com Widget:Conquistas via {{#invoke:Conquistas|renderTab|tab=geral}}
   (normalmente através da predefinição {{Conquistas}}).
   no carregamento inicial; outras abas via renderTab sob demanda (JS).
  renderAll ainda existe (todas as abas de uma vez) para debug/legado.


   Rewards usam exatamente o mesmo visual do Módulo:Reward (item-wrapper
   Rewards usam exatamente o mesmo visual do Módulo:Reward (item-wrapper
Linha 302: Linha 303:
end
end


-- p.renderAll(frame): emite todos os cards em lista plana (data-tab em cada um).
-- Emite cards de uma aba (lista plana com data-tab em cada card).
-- Painéis/header ficam no Widget:Conquistas.
local function renderTabContent(tab, lang)
--
    tab = normalizeTab(tab)
-- O invoke precisa ficar FORA do widget na página: Smarty não reparsa wikitext;
    local list = AchievementDB.byTab(tab) or {}
-- links de arquivo (File:/Arquivo:) viram texto cru se o invoke for parâmetro do widget.
 
-- Na wiki use a predefinição {{Conquistas}} ou, manualmente:
    local source = mw.html.create("div"):addClass("gla-conquistas-source")
--  {{#widget:Conquistas}} + {{#invoke:Conquistas|renderAll}}
    for _, ach in ipairs(list) do
--
        source:wikitext(renderCard(ach, lang))
-- O JS do widget move .gla-item[data-tab] para o painel certo e remove .gla-conquistas-source.
    end
-- Parâmetros opcionais: |lang=pt|en , |only= id da aba (debug)
    return tostring(source)
end
 
-- Uma aba — usado no HTML inicial (geral) e no lazy-load via API (JS).
-- Parâmetros: |tab=geral|missao|... , |lang=pt-br|en
function p.renderTab(frame)
    local args = getArgs(frame)
    local tab = normalizeTab(args.tab or args[1])
    local lang = trim(args.lang)
    if lang == "" then lang = DEFAULT_LANG end
    return renderTabContent(tab, lang)
end
 
-- Todas as abas (legado/debug). |only= filtra uma aba só.
function p.renderAll(frame)
function p.renderAll(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
Linha 317: Linha 331:
     if lang == "" then lang = DEFAULT_LANG end
     if lang == "" then lang = DEFAULT_LANG end
     local onlyTab = trim(args.only):lower()
     local onlyTab = trim(args.only):lower()
    if onlyTab ~= "" then
        return renderTabContent(onlyTab, lang)
    end


     local list = AchievementDB.all() or {}
     local list = AchievementDB.all() or {}
     local source = mw.html.create("div"):addClass("gla-conquistas-source")
     local source = mw.html.create("div"):addClass("gla-conquistas-source")
     for _, ach in ipairs(list) do
     for _, ach in ipairs(list) do
         local tab = normalizeTab(ach.type)
         source:wikitext(renderCard(ach, lang))
        if onlyTab == "" or onlyTab == tab then
            source:wikitext(renderCard(ach, lang))
        end
     end
     end
     return tostring(source)
     return tostring(source)
end
end


return p
return p

Edição das 04h17min de 21 de maio de 2026

A documentação para este módulo pode ser criada em Módulo:Conquistas/doc

--[[
  Módulo:Conquistas

  Renderiza TODAS as conquistas em uma única passada, lendo o banco
  Módulo:AchievementDB (carregado via mw.loadData, cacheado pelo Scribunto).

  Usado com Widget:Conquistas via {{#invoke:Conquistas|renderTab|tab=geral}}
  no carregamento inicial; outras abas via renderTab sob demanda (JS).
  renderAll ainda existe (todas as abas de uma vez) para debug/legado.

  Rewards usam exatamente o mesmo visual do Módulo:Reward (item-wrapper
  com tooltip, ordenando berries por último).
]]

local p = {}

local Item = require("Módulo:Item")
-- require (não loadData) porque AchievementDB expõe funções (all/byTab/getById).
-- O Scribunto cacheia requires dentro do mesmo parser pass.
local AchievementDB = require("Módulo:AchievementDB")

local DEFAULT_LANG = "pt-br"
local DEFAULT_ICON = "conquistafoosha.png"
local RESOLVED_ITEM_CACHE = {}
local RENDER_ONE_CACHE = {}

local TABS = {
    { id = "geral",       label = "Geral" },
    { id = "personagens", label = "Personagens" },
    { id = "missao",      label = "Missão" },
    { id = "bau",         label = "Baú" },
    { id = "navegacao",   label = "Navegação" },
    { id = "pvp",         label = "PvP" },
    { id = "pve",         label = "PvE" },
    { id = "coliseu",     label = "Coliseu" },
    { id = "poneglyph",   label = "Poneglyph" },
    { id = "indicacao",   label = "Indicação" },
    { id = "celular",     label = "Celular" },
    { id = "bossrush",    label = "Boss Rush" },
}

local VALID_TABS = {}
for _, t in ipairs(TABS) do VALID_TABS[t.id] = true end

local function trim(s)
    return mw.text.trim(s or "")
end

local function normalizeTab(tag)
    local t = trim(tag):lower()
    if t == "" or not VALID_TABS[t] then return "geral" end
    return t
end

local function normalizeIcon(name)
    local clean = trim(name)
    if clean == "" then return DEFAULT_ICON end
    if clean:match("%.%w+$") then return clean end
    return clean .. ".png"
end

local function formatNumber(num)
    num = tonumber(num) or 0
    if num >= 1e9 then
        return string.format("%.0f", num / 1e6) .. "KKK"
    elseif num >= 1e6 then
        return string.format("%.0f", num / 1e6) .. "KK"
    elseif num >= 1e4 then
        return string.format("%.0f", num / 1e3) .. "K"
    else
        local s = tostring(num)
        return s:reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "")
    end
end

local function normalizeForMatch(s)
    if not s then return "" end
    return mw.ustring.lower(mw.text.trim(tostring(s)))
end

local function isBerriesItem(item)
    if not item then return false end

    local image = normalizeForMatch(item.image)
    if image ~= "" and image:find("berr", 1, true) then
        return true
    end

    local namePt = item.names and normalizeForMatch(item.names.pt) or ""
    local nameEn = item.names and normalizeForMatch(item.names.en) or ""
    if namePt:find("berr", 1, true) or nameEn:find("berr", 1, true) then
        return true
    end

    if item.aliases and type(item.aliases) == "table" then
        for _, alias in ipairs(item.aliases) do
            local norm = normalizeForMatch(alias)
            if norm:find("berr", 1, true) then
                return true
            end
        end
    end

    return item.category == "currency"
end

local function resolveRewardItem(ident)
    local key = trim(ident)
    if key == "" then
        return {
            id = 0,
            image = DEFAULT_ICON,
            names = { pt = "Item", en = "Item" },
            category = "unknown",
        }
    end

    local cached = RESOLVED_ITEM_CACHE[key]
    if cached then return cached end

    local item = Item.resolve(key)
    if item then
        RESOLVED_ITEM_CACHE[key] = item
        return item
    end

    local fallback = {
        id = 0,
        image = key:match("%.%w+$") and key or (key .. ".png"),
        names = { pt = key, en = key },
        category = "unknown",
    }
    RESOLVED_ITEM_CACHE[key] = fallback
    return fallback
end

--[[
  renderRewards: replica o output do Módulo:Reward (predefinição {{Reward}})
  para uma lista estruturada [{ ident, qty }, ...].

  - Mesmo wrapper .reward-wrapper
  - Mesmo .reward-items
  - Mesmo Item.renderOne com tooltip e count
  - Berries sempre por último
]]
local function renderRewards(rewards, lang)
    if not rewards or #rewards == 0 then return "" end

    local wrapper = mw.html.create("div"):addClass("reward-wrapper")
    local line = wrapper:tag("div"):addClass("reward-items")

    local normais = {}
    local berries = {}

    for _, r in ipairs(rewards) do
        local ident = trim(r.ident)
        local qty = tonumber(r.qty) or 0
        if ident ~= "" then
            local item = resolveRewardItem(ident)
            local isBerries = isBerriesItem(item)

            local block = {
                item = item,
                qty = formatNumber(qty),
            }

            if isBerries then
                table.insert(berries, block)
            else
                table.insert(normais, block)
            end
        end
    end

    local function append(b)
        local itemRef = tostring(b.item.id or b.item.image or b.item.names and b.item.names.pt or "")
        local cacheKey = itemRef .. "|" .. tostring(b.qty) .. "|" .. tostring(lang)
        local cachedHtml = RENDER_ONE_CACHE[cacheKey]
        if not cachedHtml then
            cachedHtml = Item.renderOne(b.item, b.qty, lang, {
                showTooltip = true,
                showCount = true,
            })
            RENDER_ONE_CACHE[cacheKey] = cachedHtml
        end
        line:wikitext(cachedHtml)
    end

    for _, b in ipairs(normais) do append(b) end
    for _, b in ipairs(berries) do append(b) end

    return tostring(wrapper)
end

--[[
  Constrói o card de uma conquista. Retorna string HTML.
  Usa dados puros (não recebe wikitext) — ideal para iteração em massa.
]]
local function renderCard(ach, lang)
    local name = ""
    if ach.names then
        name = trim(ach.names[lang]) ~= "" and ach.names[lang]
            or trim(ach.names.pt) ~= "" and ach.names.pt
            or trim(ach.names.en) ~= "" and ach.names.en
            or ""
    end
    if name == "" then name = "Titulo da Conquista" end

    local desc = ""
    if ach.desc then
        desc = trim(ach.desc[lang]) ~= "" and ach.desc[lang]
            or trim(ach.desc.pt) ~= "" and ach.desc.pt
            or trim(ach.desc.en) ~= "" and ach.desc.en
            or ""
    end
    if desc == "" then desc = "Descricao da conquista." end

    local icon = normalizeIcon(ach.icon)
    local tab = normalizeTab(ach.type)
    local hidden = ach.hidden == true

    local card = mw.html.create("div"):addClass("gla-item"):attr("data-tab", tab)
    -- data-id: chave estável pro JS do widget persistir reveals de
    -- conquistas secretas no localStorage. Usa ach.id (id numérico do DB)
    -- pra ser robusto a mudanças de nome/descrição.
    if ach.id ~= nil then card:attr("data-id", tostring(ach.id)) end
    if hidden then card:addClass("is-hidden"):attr("data-hidden", "true") end

    local subtype = ach.subtype and trim(ach.subtype):lower() or ""
    if subtype == "onemany" or subtype == "corrida" then
        card:attr("data-subtype", subtype)
    end

    local main = card:tag("div"):addClass("gla-item-main")
    local left = main:tag("div"):addClass("gla-item-left")

    -- Ícone sem tamanho explícito: respeita o tamanho do arquivo na wiki
    -- (recomendado: 52x52). O CSS no Widget:Conquistas só faz reset visual.
    left:tag("div")
        :addClass("gla-item-icon")
        :wikitext(string.format("[[File:%s|alt=%s|link=]]", icon, name))

    local textWrap = left:tag("div"):addClass("gla-item-text")
    textWrap:tag("div"):addClass("gla-item-title"):wikitext(name)
    textWrap:tag("div"):addClass("gla-item-desc"):wikitext(desc)

    -- Spoiler toggle vai dentro do wrapper do texto pra ficar alinhado com
    -- título/descrição independente do tamanho do ícone (ícones variam de
    -- 52 a 100+ px na wiki). Antes era absolute em left fixo e o ícone
    -- maior cobria o botão.
    local info = ach.info and trim(ach.info) or ""
    local mediaRaw = ach.media and trim(ach.media) or ""
    local mediaFile = mediaRaw ~= "" and normalizeIcon(mediaRaw) or ""
    local hasSpoilerContent = (info ~= "" or mediaFile ~= "")
    local spoilerEnabled = ach.spoiler == true or hasSpoilerContent
    if spoilerEnabled and hasSpoilerContent then
        card:addClass("has-spoiler")
        -- Usa <span role="button">. MediaWiki não permite <button> em wikitext
        -- (sanitizer escapa pra texto). Span com role/tabindex preserva
        -- semântica e o JS do widget já trata click + keydown.
        local toggle = textWrap:tag("span")
            :addClass("gla-item-spoiler-toggle")
            :attr("role", "button")
            :attr("tabindex", "0")
            :attr("aria-expanded", "false")
            :attr("title", "Abrir spoiler")
        toggle:tag("span"):addClass("gla-item-spoiler-text"):wikitext("Spoiler")
        toggle:tag("span"):addClass("gla-item-chevron"):wikitext("&#x203A;")
    end

    if ach.rewards and #ach.rewards > 0 then
        local rewardHtml = renderRewards(ach.rewards, lang)
        if rewardHtml ~= "" then
            local rewardDiv = main:tag("div"):addClass("gla-item-reward")
            local labelText = #ach.rewards == 1 and "Recompensa" or "Recompensas"
            rewardDiv:tag("div")
                :addClass("gla-item-reward-label")
                :wikitext(labelText)
            rewardDiv:wikitext(rewardHtml)
        end
    end

    if spoilerEnabled and hasSpoilerContent then
        local spoiler = card:tag("div"):addClass("gla-item-spoiler")
        if info ~= "" then
            spoiler:tag("div"):addClass("gla-item-spoiler-info"):wikitext(info)
        end
        if mediaFile ~= "" then
            spoiler:tag("div"):addClass("gla-item-spoiler-media")
                :wikitext(string.format("[[File:%s|link=]]", mediaFile))
        end
    end

    return tostring(card)
end

local function getArgs(frame)
    local args = frame.args or {}
    if not next(args) and frame:getParent() then
        args = frame:getParent().args or {}
    end
    return args
end

-- Emite cards de uma aba (lista plana com data-tab em cada card).
local function renderTabContent(tab, lang)
    tab = normalizeTab(tab)
    local list = AchievementDB.byTab(tab) or {}

    local source = mw.html.create("div"):addClass("gla-conquistas-source")
    for _, ach in ipairs(list) do
        source:wikitext(renderCard(ach, lang))
    end
    return tostring(source)
end

-- Uma aba — usado no HTML inicial (geral) e no lazy-load via API (JS).
-- Parâmetros: |tab=geral|missao|... , |lang=pt-br|en
function p.renderTab(frame)
    local args = getArgs(frame)
    local tab = normalizeTab(args.tab or args[1])
    local lang = trim(args.lang)
    if lang == "" then lang = DEFAULT_LANG end
    return renderTabContent(tab, lang)
end

-- Todas as abas (legado/debug). |only= filtra uma aba só.
function p.renderAll(frame)
    local args = getArgs(frame)
    local lang = trim(args.lang)
    if lang == "" then lang = DEFAULT_LANG end
    local onlyTab = trim(args.only):lower()

    if onlyTab ~= "" then
        return renderTabContent(onlyTab, lang)
    end

    local list = AchievementDB.all() or {}
    local source = mw.html.create("div"):addClass("gla-conquistas-source")
    for _, ach in ipairs(list) do
        source:wikitext(renderCard(ach, lang))
    end
    return tostring(source)
end

return p