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: | ||
--[[ | --[[ | ||
* Name: MasonryLayout | |||
* Author: Mark W. Datysgeld | |||
* Description: Intelligent masonry layout system for content distribution with card organization 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 | |||
]] | ]] | ||
| Line 25: | Line 11: | ||
local EMPTY_STRING = '' | local EMPTY_STRING = '' | ||
local DEFAULT_COLUMNS = 3 | local DEFAULT_COLUMNS = 3 | ||
local MOBILE_BREAKPOINT = | local MOBILE_BREAKPOINT = 656 -- 41rem to match CSS breakpoint | ||
-- 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 | desktop = { columns = 3, minWidth = MOBILE_BREAKPOINT + 1 }, | ||
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 | ||
-- | -- ALL cards should be wrapped in the card class for consistent styling | ||
cardHtml[j] = string.format( | |||
'<div class="%s" data-card-id="%s">%s</div>', | |||
cardClass, | |||
card.id or 'unknown', | |||
card.content or EMPTY_STRING | |||
) | |||
end | end | ||
| Line 680: | Line 653: | ||
if screenWidth <= MOBILE_BREAKPOINT then | if screenWidth <= MOBILE_BREAKPOINT then | ||
return 1 | return 1 | ||
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 | ||
-- | -- Branch based on render mode | ||
local | if isMobileMode or columnCount == 1 then | ||
-- MOBILE MODE: Single column with explicit ordering | |||
local orderedCards = {} | |||
local introCard = nil | |||
local infoBoxCard = nil | |||
local otherCards = {} | |||
-- 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, | '<!-- Masonry Debug: Total cards: %d, Mobile single column layout -->', | ||
# | #orderedCards | ||
) | ) | ||
-- Add per-card debug info | -- Render each card in order | ||
for _, card in ipairs(orderedCards) do | |||
masonryHtml = masonryHtml .. string.format( | |||
'\n<div class="%s" data-card-id="%s">%s</div>', | |||
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 | ||
end | end | ||
masonryHtml = masonryHtml .. '\n' .. p.renderDistributedLayout(distribution, options) | |||
masonryHtml = masonryHtml .. '</div>' | |||
return masonryHtml | |||
end | end | ||
end | end | ||