Module:MasonryLayout: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
--[[
--[[
* MasonryLayout.lua
* Name: MasonryLayout
* Intelligent masonry layout system for content distribution
* Author: Mark W. Datysgeld
*
* Description: Intelligent masonry layout system for content distribution with card organization across columns for optimal visual balance
* This module provides smart card distribution across columns for optimal visual balance.
* Notes: Content-aware size estimation; intelligent card distribution algorithm; responsive column management; aggressive caching for performance; Blueprint integration; error handling integration; integrates with ErrorHandling for protected operations; uses TemplateHelpers caching mechanisms and utility functions; integrates with TemplateStructure for block-based rendering
* Integrates with the Blueprint architecture and follows ICANNWiki performance patterns.
*
* Key Features:
* - Content-aware size estimation
* - Intelligent card distribution algorithm
* - Responsive column management
* - Aggressive caching for performance
* - Blueprint integration
* - Error handling integration
*
* Integration with other modules:
* - ErrorHandling: All operations are protected with centralized error handling
* - TemplateHelpers: Uses caching mechanisms and utility functions
* - TemplateStructure: Integrates with block-based rendering
]]
]]


Line 25: Line 11:
local EMPTY_STRING = ''
local EMPTY_STRING = ''
local DEFAULT_COLUMNS = 3
local DEFAULT_COLUMNS = 3
local MOBILE_BREAKPOINT = 480
local MOBILE_BREAKPOINT = 656  -- 41rem to match CSS breakpoint
local TABLET_BREAKPOINT = 768


-- Size estimation constants (based on typical MediaWiki table rendering)
-- Size estimation constants (based on typical MediaWiki table rendering)
Line 41: Line 26:
-- Column distribution weights for different screen sizes
-- Column distribution weights for different screen sizes
local RESPONSIVE_CONFIG = {
local RESPONSIVE_CONFIG = {
     desktop = { columns = 3, minWidth = TABLET_BREAKPOINT + 1 },
     desktop = { columns = 3, minWidth = MOBILE_BREAKPOINT + 1 },
    tablet = { columns = 2, minWidth = MOBILE_BREAKPOINT + 1, maxWidth = TABLET_BREAKPOINT },
     mobile = { columns = 1, maxWidth = MOBILE_BREAKPOINT }
     mobile = { columns = 1, maxWidth = MOBILE_BREAKPOINT }
}
}
Line 132: Line 116:
      
      
     return TemplateHelpers.withCache(cacheKey, function()
     return TemplateHelpers.withCache(cacheKey, function()
        -- Card padding constant (15px top + 15px bottom from CSS)
        local CARD_PADDING = 30
       
         -- Special handling for intro cards (text content, not tables)
         -- Special handling for intro cards (text content, not tables)
         if cardData.contentType == 'intro' then
         if cardData.contentType == 'intro' then
             -- Intro is typically 2-3 lines of welcome text
             -- Intro is typically 2-3 lines of welcome text + card padding
             return 120 -- Fixed height for consistency
             return 120 + CARD_PADDING -- Fixed height for consistency
         end
         end
          
          
         -- Special handling for infoBox
         -- Special handling for infoBox
         if cardData.contentType == 'infoBox' then
         if cardData.contentType == 'infoBox' then
             -- InfoBox has fixed structure: header + 4-5 rows
             -- InfoBox has fixed structure: header + 4-5 rows + card padding
             return 200 -- Fixed height based on actual structure
             return 200 + CARD_PADDING -- Fixed height based on actual structure
         end
         end
          
          
Line 152: Line 139:
          
          
         -- Calculate exact height based on row count
         -- Calculate exact height based on row count
         local contentHeight = TABLE_HEADER + TABLE_PADDING
         local contentHeight = TABLE_HEADER + TABLE_PADDING + CARD_PADDING
          
          
         if effectiveRowCount > 0 then
         if effectiveRowCount > 0 then
Line 637: Line 624:
         -- Render cards in this column
         -- Render cards in this column
         for j, card in ipairs(columnCards) do
         for j, card in ipairs(columnCards) do
             -- Check if content already has the card wrapper
             -- ALL cards should be wrapped in the card class for consistent styling
            -- (This handles cases where content might be pre-wrapped)
             cardHtml[j] = string.format(
            local content = card.content or EMPTY_STRING
                '<div class="%s" data-card-id="%s">%s</div>',
            local needsWrapper = true
                cardClass,
           
                card.id or 'unknown',
            -- Simple check: if content already has our card class, don't double-wrap
                 card.content or EMPTY_STRING
            if content:find('class="[^"]*' .. cardClass .. '[^"]*"') then
             )
                needsWrapper = false
            end
           
             if needsWrapper then
                cardHtml[j] = string.format(
                    '<div class="%s" data-card-id="%s">%s</div>',
                    cardClass,
                    card.id or 'unknown',
                    content
                 )
            else
                -- Content is already wrapped, just use it as-is
                cardHtml[j] = content
             end
         end
         end
          
          
Line 680: Line 653:
     if screenWidth <= MOBILE_BREAKPOINT then
     if screenWidth <= MOBILE_BREAKPOINT then
         return 1
         return 1
    elseif screenWidth <= TABLET_BREAKPOINT then
        return 2
     else
     else
         return 3
         return 3
Line 745: Line 716:
     local blockRenderers = config.blockRenderers or {}
     local blockRenderers = config.blockRenderers or {}
      
      
     local columnCount = options.columns or DEFAULT_COLUMNS
    -- Determine render mode (mobile vs desktop)
    -- Default to desktop mode, but can be overridden by options
    local isMobileMode = options.mobileMode or false
     local columnCount = isMobileMode and 1 or (options.columns or DEFAULT_COLUMNS)
      
      
     -- Build cards with render-time content generation
     -- Build cards with render-time content generation
Line 783: Line 757:
     end
     end
      
      
     -- Distribute cards across columns
     -- Branch based on render mode
     local distribution = p.distributeCards(cards, columnCount)
     if isMobileMode or columnCount == 1 then
   
        -- MOBILE MODE: Single column with explicit ordering
    -- Render the complete masonry layout
        local orderedCards = {}
    local containerClass = options.containerClass or 'country-hub-masonry-container'
        local introCard = nil
    local masonryHtml = string.format('<div class="%s">', containerClass)
        local infoBoxCard = nil
   
        local otherCards = {}
    -- Add debug information as HTML comments
       
    if distribution then
        -- Separate special cards from regular cards
        for _, card in ipairs(cards) do
            if card.id == 'intro' then
                introCard = card
            elseif card.id == 'infoBox' then
                infoBoxCard = card
            else
                table.insert(otherCards, card)
            end
        end
       
        -- Build final ordered list: intro → infoBox → others
        if introCard then
            table.insert(orderedCards, introCard)
        end
        if infoBoxCard then
            table.insert(orderedCards, infoBoxCard)
        end
        for _, card in ipairs(otherCards) do
            table.insert(orderedCards, card)
        end
       
        -- Render as single column layout
        local containerClass = options.containerClass or 'country-hub-masonry-container'
        local cardClass = options.cardClass or 'country-hub-masonry-card'
       
        local masonryHtml = string.format('<div class="%s country-hub-mobile-mode">', containerClass)
       
        -- Add debug information
         masonryHtml = masonryHtml .. string.format(
         masonryHtml = masonryHtml .. string.format(
             '<!-- Masonry Debug: Total cards: %d, Columns: %d, Heights: [%s], Balance: %.2f, Extreme cards: %d -->',
             '<!-- Masonry Debug: Total cards: %d, Mobile single column layout -->',
             #cards,
             #orderedCards
            #(distribution.columns or {}),
            table.concat(distribution.heights or {}, ', '),
            distribution.balance or 0,
            distribution.extremeCards or 0
         )
         )
          
          
         -- Add per-card debug info
         -- Render each card in order
        for i, column in ipairs(distribution.columns or {}) do
        for _, card in ipairs(orderedCards) do
            masonryHtml = masonryHtml .. string.format('\n<!-- Column %d: ', i)
            masonryHtml = masonryHtml .. string.format(
            for _, card in ipairs(column) do
                '\n<div class="%s" data-card-id="%s">%s</div>',
                masonryHtml = masonryHtml .. string.format('%s(%dpx) ', card.id or 'unknown', card.estimatedSize or 0)
                cardClass,
                card.id or 'unknown',
                card.content or EMPTY_STRING
            )
        end
       
        masonryHtml = masonryHtml .. '\n</div>'
       
        return masonryHtml
    else
        -- DESKTOP MODE: Multi-column with intelligent distribution
        local distribution = p.distributeCards(cards, columnCount)
       
        -- Render the complete masonry layout
        local containerClass = options.containerClass or 'country-hub-masonry-container'
        local masonryHtml = string.format('<div class="%s">', containerClass)
       
        -- Add debug information as HTML comments
        if distribution then
            masonryHtml = masonryHtml .. string.format(
                '<!-- Masonry Debug: Total cards: %d, Columns: %d, Heights: [%s], Balance: %.2f, Extreme cards: %d -->',
                #cards,
                #(distribution.columns or {}),
                table.concat(distribution.heights or {}, ', '),
                distribution.balance or 0,
                distribution.extremeCards or 0
            )
           
            -- Add per-card debug info
            for i, column in ipairs(distribution.columns or {}) do
                masonryHtml = masonryHtml .. string.format('\n<!-- Column %d: ', i)
                for _, card in ipairs(column) do
                    masonryHtml = masonryHtml .. string.format('%s(%dpx) ', card.id or 'unknown', card.estimatedSize or 0)
                end
                masonryHtml = masonryHtml .. '-->'
             end
             end
            masonryHtml = masonryHtml .. '-->'
         end
         end
       
        masonryHtml = masonryHtml .. '\n' .. p.renderDistributedLayout(distribution, options)
        masonryHtml = masonryHtml .. '</div>'
       
        return masonryHtml
     end
     end
   
    masonryHtml = masonryHtml .. '\n' .. p.renderDistributedLayout(distribution, options)
    masonryHtml = masonryHtml .. '</div>'
   
    return masonryHtml
end
end