Jump to content

Module:ElementPortraitCarousel

Revision as of 22:52, 6 May 2025 by MarkWD (talk | contribs) (// via Wikitext Extension for VSCode)

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

--[[ 
 * Module:ElementPortraitCarousel
 * Renders a portrait carousel for person templates with 3 display modes:
 * 1. Single image - Simple centered display
 * 2. Dual images - Orbital display with animation
 * 3. Multi-image - Full carousel with navigation arrows
 * 
 * Integrates with Blueprint template system.
 ]]

local p = {}

p.elementName = "portraitCarousel"

-- Load required modules
local ErrorHandling = require('Module:ErrorHandling')

-- Default configuration
p.defaultConfig = {
    maxWidth = "220px",
    carouselClass = "person-portrait-carousel"
}

-- Helper function to create a properly formatted image URL
local function formatImageUrl(imageName)
    -- Remove any File: or Image: prefix if present
    imageName = imageName:gsub('^[Ff]ile:', '')
    imageName = imageName:gsub('^[Ii]mage:', '')
    
    -- Format the URL using MediaWiki's makeImageLink function
    local imageTitle = mw.title.new('File:' .. imageName)
    if not imageTitle then
        return '' -- Invalid title
    end
    
    -- Use thumb with a large size to get the full image
    local imageObj = mw.image.new(imageTitle.text)
    if not imageObj then
        return '' -- Image doesn't exist
    end
    
    return imageObj:url()
end

-- Split portrait field by semicolons
local function splitPortraitImages(portrait)
    if not portrait or portrait == "" then
        return {}
    end
    
    local images = {}
    for image in portrait:gmatch("[^;]+") do
        local imageName = mw.text.trim(image)
        local imageUrl = formatImageUrl(imageName)
        if imageUrl and imageUrl ~= "" then
            table.insert(images, {
                name = imageName,
                url = imageUrl
            })
        end
    end
    return images
end

-- Create single image HTML
local function createSingleImage(imageData, maxWidth)
    return string.format(
        '<div class="person-portrait-carousel">' ..
        '<img src="%s" style="max-width:%s;" alt="Portrait" />' ..
        '</div>',
        imageData.url, maxWidth
    )
end

-- Create dual-image orbital display HTML
local function createDualImages(images, maxWidth)
    local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person")
    
    return string.format(
        '<div class="person-portrait-carousel">' ..
        '<div class="carousel-container">' ..
        '<div class="carousel-images" id="carousel-%s">' ..
        '<div class="carousel-item carousel-orbital-1" data-index="1"><img src="%s" alt="Portrait 1" /></div>' ..
        '<div class="carousel-item carousel-orbital-2" data-index="2"><img src="%s" alt="Portrait 2" /></div>' ..
        '</div></div></div>',
        navId,
        images[1].url, images[2].url
    )
end

-- Create multi-image carousel HTML
local function createCarousel(images, maxWidth)
    local itemsHtml = {}
    local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person")
    
    -- Generate carousel items
    for i, imageData in ipairs(images) do
        local class = i == 1 and "carousel-visible" or "carousel-hidden"
        -- Add positioning classes for 3+ images
        if i == 2 then
            class = class .. " carousel-right"
        elseif i == #images then
            class = class .. " carousel-left"
        end
        
        table.insert(itemsHtml, string.format(
            '<div class="carousel-item %s" data-index="%d"><img src="%s" alt="Portrait %d" /></div>',
            class, i, imageData.url, i
        ))
    end
    
    -- Build complete carousel HTML
    return string.format(
        '<div class="person-portrait-carousel">' ..
        '<div class="carousel-container">' ..
        '<div class="carousel-nav carousel-prev">&#9664;</div>' ..
        '<div class="carousel-images" id="carousel-%s">%s</div>' ..
        '<div class="carousel-nav carousel-next">&#9654;</div>' ..
        '</div></div>',
        navId, table.concat(itemsHtml)
    )
end

-- Create block function
function p.createBlock()
    return function(template, args)
        -- Protected execution wrapper
        local function execute()
            local portrait = args.portrait or ""
            local images = splitPortraitImages(portrait)
            local maxWidth = template.config.portraitCarousel 
                and template.config.portraitCarousel.maxWidth
                or p.defaultConfig.maxWidth
            
            -- Determine display type based on number of images
            if #images == 0 then
                return "" -- No images
            elseif #images == 1 then
                return createSingleImage(images[1], maxWidth)
            elseif #images == 2 then
                return createDualImages(images, maxWidth)
            else
                return createCarousel(images, maxWidth)
            end
        end
        
        -- Wrap with error handling
        if template._errorContext then
            return ErrorHandling.protect(
                template._errorContext,
                "ElementBlock_portraitCarousel",
                execute,
                ""
            )
        else
            local ok, result = pcall(execute)
            return ok and result or ""
        end
    end
end

return p