Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Bracket: Difference between revisions

From eSportsAmaze
No edit summary
No edit summary
 
(3 intermediate revisions by the same user not shown)
Line 82: Line 82:
-- ── NEW MODERN MATCH CARD ──
-- ── NEW MODERN MATCH CARD ──
local function renderMatchCard(d, matchID, defaultLabel, winTo, loseTo, target, isDrop, extraClass, game)
local function renderMatchCard(d, matchID, defaultLabel, winTo, loseTo, target, isDrop, extraClass, game)
    -- REMOVED .bk-match-clickable from the main box to prevent miss-clicks
     local box = html.create('div'):addClass('bk-match')
     local box = html.create('div'):addClass('bk-match')
         :attr('id', 'match-' .. matchID)
         :attr('id', 'match-' .. matchID)
Line 95: Line 94:
     local win2 = (d.winner == "2" or d.winner == d.team2) and d.team2 ~= "TBD"
     local win2 = (d.winner == "2" or d.winner == d.team2) and d.team2 ~= "TBD"


     -- Top Bar (Label & Meta & Info Icon)
     -- Top Bar (Entire bar is now the clickable trigger)
     local top = box:tag('div'):addClass('bk-match-top')
     local top = box:tag('div'):addClass('bk-match-top')
    top:tag('span'):addClass('bk-match-label'):wikitext(d.label or defaultLabel or matchID)
      :attr('data-matchid', matchID) -- Ready for your future Match Page logic
   
    local metaStr = ""
    if d.bo and d.bo ~= "" then metaStr = "Bo" .. d.bo end
    local fDate = formatDate(d.date)
    if fDate ~= "" then
        if metaStr ~= "" then metaStr = metaStr .. " • " .. fDate else metaStr = fDate end
    end
    top:tag('span'):addClass('bk-match-meta'):wikitext(metaStr)
   
    -- The Info Icon (Triggers Popup)
    -- Passes all the data attributes exclusively to this button
    top:tag('span'):addClass('bk-match-details-btn')
       :attr('data-team1', d.team1):attr('data-team2', d.team2)
       :attr('data-team1', d.team1):attr('data-team2', d.team2)
       :attr('data-score1', d.score1):attr('data-score2', d.score2)
       :attr('data-score1', d.score1):attr('data-score2', d.score2)
       :attr('data-bo', d.bo):attr('data-date', d.date)
       :attr('data-bo', d.bo):attr('data-date', d.date)
       :attr('data-casters', d.casters):attr('data-vod', d.vod):attr('data-notes', d.notes)
       :attr('data-casters', d.casters):attr('data-vod', d.vod):attr('data-notes', d.notes)
       :attr('title', 'Match Details')
       :attr('title', 'Click for Match Details')
       :wikitext('ⓘ') -- Unicode Info Circle ⓘ
   
    -- The Label
    top:tag('span'):addClass('bk-match-label')
       :wikitext(d.label or defaultLabel or matchID)
   
    -- Meta Info (ONLY Date)
    local metaStr = formatDate(d.date)
    top:tag('span'):addClass('bk-match-meta'):wikitext(metaStr)


     -- Teams Wrapper
     -- Teams Wrapper
     local teamsWrap = box:tag('div'):addClass('bk-teams-wrap')
     local teamsWrap = box:tag('div'):addClass('bk-teams-wrap')


    -- Game Prefix for Links
     local linkPrefix = ""
     local linkPrefix = ""
     if game and game ~= "" and game ~= "BGMI" then
     if game and game ~= "" and game ~= "BGMI" then linkPrefix = game .. "/Teams/" end
        linkPrefix = game .. "/Teams/"
    end


     -- Team 1
     -- Team 1

Latest revision as of 03:39, 4 April 2026

Documentation for this module may be created at Module:Bracket/doc

-- ================================================================
-- Module:Bracket v3.0 (Smart Nodes, Game Links, Info Popups)
-- ================================================================
local p = {}
local html = mw.html
local cargo = mw.ext.cargo

local function esc(s) return s and s:gsub("\\", "\\\\"):gsub("'", "\\'") or "" end
local function clean(s)
    if not s then return nil end
    local c = s:gsub("[\r\n]",""):gsub("^%s*(.-)%s*$","%1")
    return c ~= "" and c or nil
end

-- Smarter Date Formatter (Handles YYYY-MM-DD and YYYY-MM-DD HH:MM)
local function formatDate(d)
    if not d or d == "" then return "" end
    local year, month, day, hour, min = string.match(d, "(%d%d%d%d)%-(%d%d)%-(%d%d)[T%s]*(%d*)%:*(%d*)")
    if year then
        local months = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}
        local dateStr = tonumber(day) .. "-" .. months[tonumber(month)]
        if hour and hour ~= "" then
            dateStr = dateStr .. " " .. hour .. ":" .. (min ~= "" and min or "00")
        end
        return dateStr
    end
    return d 
end

local teamLogoCache = {}
local function getTeamLogo(teamName)
    if not teamName or teamName == "" or teamName == "TBD" then return "" end
    if teamLogoCache[teamName] then return teamLogoCache[teamName] end
    
    local rows = cargo.query("Teams","image,image_dark",{where="name='"..esc(teamName).."'", limit=1})
    local t = (rows and #rows > 0) and rows[1] or false
    if not t then 
        teamLogoCache[teamName] = ""
        return "" 
    end
    
    local light = t.image or ""
    local dark = (t.image_dark and t.image_dark ~= "") and t.image_dark or light
    if light == "" then 
        teamLogoCache[teamName] = ""
        return "" 
    end
    
    local htmlStr = "[[File:"..light.."|18px|link=|class=logo-lightmode bk-team-logo]]"
        .. "[[File:"..dark .."|18px|link=|class=logo-darkmode bk-team-logo]]"
    teamLogoCache[teamName] = htmlStr
    return htmlStr
end

local function fetchMatch(event, matchID)
    if not event or event == "" then return {} end
    local rows = cargo.query("BracketMatch",
        "team1,score1,team2,score2,winner,bo,match_date,casters,vod,notes,short1,short2",
        { where = "event='"..esc(event).."' AND match_id='"..esc(matchID).."'", limit = 1 })
    if rows and #rows > 0 then return rows[1] end
    return {}
end

local function mergeMatchData(cargoData, args, prefix)
    local d = {}
    d.label   = clean(args[prefix.."label"])
    d.team1   = clean(args[prefix.."team1"])   or clean(cargoData.team1)   or "TBD"
    d.team2   = clean(args[prefix.."team2"])   or clean(cargoData.team2)   or "TBD"
    d.short1  = clean(args[prefix.."short1"])  or clean(cargoData.short1)  or d.team1
    d.short2  = clean(args[prefix.."short2"])  or clean(cargoData.short2)  or d.team2
    d.score1  = clean(args[prefix.."score1"])  or clean(cargoData.score1)  or ""
    d.score2  = clean(args[prefix.."score2"])  or clean(cargoData.score2)  or ""
    d.winner  = clean(args[prefix.."win"])     or clean(cargoData.winner)  or ""
    d.bo      = clean(args[prefix.."bo"])      or clean(cargoData.bo)      or ""
    d.date    = clean(args[prefix.."date"])    or clean(cargoData.match_date) or ""
    d.casters = clean(args[prefix.."casters"]) or clean(cargoData.casters) or ""
    d.vod     = clean(args[prefix.."vod"])     or clean(cargoData.vod)     or ""
    d.notes   = clean(args[prefix.."notes"])   or clean(cargoData.notes)   or ""
    return d
end

-- ── NEW MODERN MATCH CARD ──
local function renderMatchCard(d, matchID, defaultLabel, winTo, loseTo, target, isDrop, extraClass, game)
    local box = html.create('div'):addClass('bk-match')
        :attr('id', 'match-' .. matchID)
    
    if winTo and winTo ~= "" then box:attr('data-target-win', 'match-' .. winTo) end
    if loseTo and loseTo ~= "" then box:attr('data-target-lose', 'match-' .. loseTo) end
    if target and target ~= "" then box:attr('data-target', 'match-' .. target) end
    if isDrop then box:attr('data-drop', 'true') end
    if extraClass then box:addClass(extraClass) end

    local win1 = (d.winner == "1" or d.winner == d.team1) and d.team1 ~= "TBD"
    local win2 = (d.winner == "2" or d.winner == d.team2) and d.team2 ~= "TBD"

    -- Top Bar (Entire bar is now the clickable trigger)
    local top = box:tag('div'):addClass('bk-match-top')
       :attr('data-matchid', matchID) -- Ready for your future Match Page logic
       :attr('data-team1', d.team1):attr('data-team2', d.team2)
       :attr('data-score1', d.score1):attr('data-score2', d.score2)
       :attr('data-bo', d.bo):attr('data-date', d.date)
       :attr('data-casters', d.casters):attr('data-vod', d.vod):attr('data-notes', d.notes)
       :attr('title', 'Click for Match Details')
    
    -- The Label
    top:tag('span'):addClass('bk-match-label')
       :wikitext(d.label or defaultLabel or matchID)
    
    -- Meta Info (ONLY Date)
    local metaStr = formatDate(d.date)
    top:tag('span'):addClass('bk-match-meta'):wikitext(metaStr)

    -- Teams Wrapper
    local teamsWrap = box:tag('div'):addClass('bk-teams-wrap')

    local linkPrefix = ""
    if game and game ~= "" and game ~= "BGMI" then linkPrefix = game .. "/Teams/" end

    -- Team 1
    local row1 = teamsWrap:tag('div'):addClass('bk-team')
    if win1 then row1:addClass('bk-win') elseif win2 then row1:addClass('bk-lose') end
    row1:wikitext(getTeamLogo(d.team1))
    local t1Link = d.team1 ~= "TBD" and "[[" .. linkPrefix .. d.team1 .. "|" .. d.short1 .. "]]" or "TBD"
    row1:tag('span'):addClass('bk-team-name'):wikitext(t1Link)
    row1:tag('span'):addClass('bk-score'):wikitext(d.score1 ~= "" and d.score1 or "-")

    -- Team 2
    local row2 = teamsWrap:tag('div'):addClass('bk-team')
    if win2 then row2:addClass('bk-win') elseif win1 then row2:addClass('bk-lose') end
    row2:wikitext(getTeamLogo(d.team2))
    local t2Link = d.team2 ~= "TBD" and "[[" .. linkPrefix .. d.team2 .. "|" .. d.short2 .. "]]" or "TBD"
    row2:tag('span'):addClass('bk-team-name'):wikitext(t2Link)
    row2:tag('span'):addClass('bk-score'):wikitext(d.score2 ~= "" and d.score2 or "-")

    return box
end

-- ================================================================
-- FORMAT: SINGLE ELIMINATION
-- ================================================================
local function renderSingleElim(args, event, teamCount, game)
    local rounds = math.floor(math.log(teamCount) / math.log(2))
    local suffixNames = { "Grand Final", "Semifinals", "Quarterfinals", "Round of 16", "Round of 32" }
    
    local scroll = html.create('div'):addClass('bk-scroll')
    local wrapper = scroll:tag('div'):addClass('bk-wrapper')

    for r = 1, rounds do
        local col = wrapper:tag('div'):addClass('bk-col')
        local pos = rounds - r + 1
        col:tag('div'):addClass('bk-col-header'):wikitext(args["r"..r.."name"] or suffixNames[pos] or ("Round "..r))

        local matchCount = teamCount / (2^r)
        for m = 1, matchCount do
            local matchID = "R"..r.."M"..m
            local cd = fetchMatch(event, matchID)
            local d  = mergeMatchData(cd, args, matchID)
            
            local target = ""
            local isFinal = (r == rounds)
            if not isFinal then
                local nextMatchNum = math.ceil(m / 2)
                target = "R"..(r+1).."M"..nextMatchNum
            end

            local card = renderMatchCard(d, matchID, "Match "..m, nil, nil, target, false, isFinal and "bk-final-match" or "", game)
            col:node(card)
        end
    end
    return tostring(scroll)
end

-- ================================================================
-- FORMAT: CUSTOM MANUAL BRACKET
-- ================================================================
local function renderCustom(args, event, game)
    local scroll = html.create('div'):addClass('bk-scroll')
    local wrapper = scroll:tag('div'):addClass('bk-wrapper')
    
    local cols = tonumber(args.columns) or 2
    for c = 1, cols do
        local col = wrapper:tag('div'):addClass('bk-col')
        
        local colName = clean(args["col"..c.."name"])
        if colName and colName:lower() ~= "none" then
            col:tag('div'):addClass('bk-col-header'):wikitext(colName)
        end
        
        local matchesStr = args["col"..c.."_matches"]
        if matchesStr then
            for mID in string.gmatch(matchesStr, '([^,]+)') do
                mID = clean(mID)
                local cd = fetchMatch(event, mID)
                local d  = mergeMatchData(cd, args, mID)
                local winTo = args[mID.."_win_to"]
                local loseTo = args[mID.."_lose_to"]
                local target = args[mID.."_target"] 
                local isDrop = args[mID.."_drop"] == "true" 
                
                col:node(renderMatchCard(d, mID, args[mID.."_label"], winTo, loseTo, target, isDrop, "", game))
            end
        end
    end
    return tostring(scroll)
end

-- ================================================================
-- MAIN ENTRY
-- ================================================================
function p.main(frame)
    local args = (frame:getParent() and frame:getParent().args) or frame.args
    local format = (clean(args.format) or "single"):lower()
    local event = clean(args.event) or ""
    local game = clean(args.game) or "" -- Pass the game prefix (e.g. "Honor of Kings")
    local teamCount = tonumber(args.teams) or 8

    local root = html.create('div'):addClass('bk-root')
    local bracketHTML

    if format == "single" then
        bracketHTML = renderSingleElim(args, event, teamCount, game)
    elseif format == "custom" then
        bracketHTML = renderCustom(args, event, game)
    else
        bracketHTML = "<div style='color:red; padding:20px'>Format '"..format.."' is currently being upgraded to v3.0. Use 'single' or 'custom' for now.</div>"
    end

    root:wikitext(bracketHTML)

    -- MODAL POPUP
    local overlay = root:tag('div'):addClass('bk-modal-overlay')
    local modal = overlay:tag('div'):addClass('bk-modal')
    modal:tag('span'):addClass('bk-modal-close'):attr('role','button'):wikitext('&#10005;')
    local mheader = modal:tag('div'):addClass('bk-modal-header')
    local mteams = mheader:tag('div'):addClass('bk-modal-teams')
    mteams:tag('span'):addClass('bk-modal-t1')
    mteams:tag('span'):addClass('bk-modal-vs'):wikitext('vs')
    mteams:tag('span'):addClass('bk-modal-t2')
    local mscore = mheader:tag('div'):addClass('bk-modal-score')
    mscore:tag('span'):addClass('bk-modal-s1')
    mscore:tag('span'):addClass('bk-modal-dash'):wikitext('&#8211;')
    mscore:tag('span'):addClass('bk-modal-s2')
    local mmeta = modal:tag('div'):addClass('bk-modal-meta')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-bo')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-date')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-casters')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-notes')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-vod')

    return tostring(root)
end

return p