Module:Infobox

From Against the Storm Official Wiki

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

---
--- Module of shortcuts and layout methods to standardize infoboxes on various
--- wiki pages. Supported modules should not use any mw.create or :tag methods,
--- but instead call the methods of this module with the facts and rely on
--- this module to build the content.
---
--- @module Infobox
local Infobox = {}



local GoodsData = require("Module:GoodsData")
local StyleUtils = require("Module:StyleUtils")



--region Private constants

local RESOURCE_LINK_TEMPLATE = "rl"
local RESOURCE_LINK_ICONSIZE = "med"

local CONSTRUCTION_TEMPLATE = "Construction"

local INDEX_REQ_STACK_SIZE= "stackSize"
local INDEX_REQ_GOOD_ID = "goodID"

local PATTERN_FORMAT_TWO_DECIMALS = "%1.2f"
local PATTERN_CAPTURE_FIRST_LETTER_IN_WORD = "(%a)([%w]*)"

--endregion



--region Public constants

Infobox.TITLE_ID = "ID"
Infobox.TITLE_CATEGORY = "Category"
Infobox.TITLE_SIZE = "Size"
Infobox.TITLE_EATABLE = "Eatable"
Infobox.TITLE_BURNABLE = "Burnable"
Infobox.TITLE_BURN_TIME = "Burn time"
Infobox.TITLE_MOVABLE = "Movable"
Infobox.TITLE_ESSENTIAL = "Essential"
Infobox.TITLE_STORAGE_CAPACITY = "Storage"
Infobox.TITLE_RANGE = "Range"
Infobox.TITLE_FARMING_AREA = "Farming Area"
Infobox.TITLE_GATHERING_AREA = "Gather Range"
Infobox.TITLE_CONSTRUCTION_TIME = "Build Time"
Infobox.TITLE_CONSTRUCTION_COST = "Build Cost"
Infobox.TITLE_SELL_VALUE = "Sell value"
Infobox.TITLE_BUY_VALUE = "Buy price"
Infobox.TITLE_WORKPLACES = "Workplaces"
Infobox.TITLE_RECIPES = "Recipes"

Infobox.ICONSIZE_MED = mw.getCurrentFrame():expandTemplate{ title="ImgM" }

-- *DEPRECATED* Infobox styling.
Infobox.CSS_MAIN = {
	["float"] = "right",
	["clear"] = "right",
	["width"] = "256px",
	["border"] = "1px solid #a2a9b1",
	["border-spacing"] = "3px",
	["border-collapse"] = "collapse",
	["margin"] = "0.5em 0 0.5em 1em"
}
Infobox.CSS_INNER_TABLE = {
	["border-collapse"] = "collapse"
}
Infobox.CSS_TR_BORDER_BOT = {
	["border-bottom"] = "1px solid #a2a9b1"
}
Infobox.CSS_TR_BORDER_TOP = {
	["border-top"] = "1px solid #a2a9b1"
}

-- Infobox icons
Infobox.ICON_FILENAMES_CATEGORIES = {
	-- Buildings
	["Camps"] = "Construct Camps.png",
	["City Buildings"] = "Construct City Buildings.png",
	["Food Production"] = "Construct Food Production.png",
	["Industry"] = "Construct Industry.png",
	-- Goods
	["Building Materials"]	= "Icon_UI_CategoryBuilding.png",
	["Consumable Items"]	= "Icon_UI_CategoryConsumable.png",
	["Crafting Resources"]	= "Icon_UI_CategoryCrafting.png",
	["Food"]				= "Icon UI CategoryFood.png",
	["Fuel & Exploration"]	= "Icon_UI_CategoryFuel.png",
	["Trade Goods"]			= "Icon_UI_CategoryTrade.png"
}

--endregion



--region Private methods

---
--- Adds a separator if needed.
---
---@param row table html table row we're building
---@param needsSeparator boolean whether we need a separator
---@return boolean whether we still need a separator
local function trySeparator(row, needsSeparator)

	if needsSeparator then
		StyleUtils.styleInfoboxSeparator(row)
		return false
	end

	return true
end



---
--- Shortcut method to populate the provided row with a header and a value.
---
---@param htmlRow table the html table row we're populating
---@param header string header text
---@param value string value text
---@return table the same htmlTable
local function populateStandardRow(htmlRow, header, value)

	htmlRow:tag("th"):wikitext(header):done()
		   :tag("td"):wikitext(value):done()
		   :done():newline()

	return htmlRow
end

--endregion



--region Public methods

---
--- Shortcut string to reformat numbers with two decimals.
---
---@param value number whole or with a fractional part
---@return string reformatted to force two decimal places
function Infobox.toTwoDecimalPlaces(value)

	return string.format(PATTERN_FORMAT_TWO_DECIMALS, value)
end



---
--- Capitalizes the first character of each word to "Make It Title Case." Also
--- converts any underscores to spaces.
---
--- @param title string to capitalize
--- @return string capitalized with title case
function Infobox.toTitleCase(title)

	title = title:gsub("_", " ")

	local newTitle = title:gsub(PATTERN_CAPTURE_FIRST_LETTER_IN_WORD, function(firstLetter, rest)
		return firstLetter:upper() .. rest:lower()
	end)

	return newTitle
end



---
--- A shortcut method for making resource links.
---
---@param goodID string the ID of the good to link to
---@return string wikimarkup representing an icon and link
function Infobox.resourceLink(goodID)

	local goodName = GoodsData.getGoodNameByID(goodID)

	return mw.getCurrentFrame():expandTemplate{ title=RESOURCE_LINK_TEMPLATE, args={
		resource=goodName, iconsize= RESOURCE_LINK_ICONSIZE } }
end



---
--- A shortcut method for looking up an icon and assigning its wiki markup.
---
---@param category string the category of the icon to make
---@return string the wiki markup for the icon
function Infobox.makeCategoryIcon(category, isGoodsCategory)

	local classStr = ""
	if isGoodsCategory then
		classStr = StyleUtils.CLASS_GOODS_CATEGORY_ICON
	else
		classStr = StyleUtils.CLASS_CATEGORY_ICON
	end

	return "[[File:" .. Infobox.ICON_FILENAMES_CATEGORIES[category] ..
			"|" .. Infobox.ICONSIZE_MED .. "|alt=" .. category ..
			"|class=" .. classStr .. "]]"
end



---
--- A shortcut method for making a standard title.
---
---@param parentNode table the html node to add to
---@param title string the title to write
---@return table the title node added
function Infobox.makeTitle(parentNode, title)

	local p = parentNode:tag("p")
	StyleUtils.styleInfoboxTitle(p)

	p:wikitext(title):done():newline()

	return p
end



---
--- A shortcut method for adding a description as flavor text.
---
---@param parentNode table the html node to add to
---@param description string the description to write out
---@return table the title node added
function Infobox.makeFlavorText(parentNode, description)

	local p = parentNode:tag("p")
	StyleUtils.styleInfoboxFlavorText(p)

	p:wikitext(description):done():newline()

	return p
end



---
--- A shortcut method for adding a large icon to the header.
---
---@param parentNode table the html node to add to
---@param iconFilename string the icon to use
---@return table the title node added
function Infobox.makeTitleIcon(parentNode, iconFilename)

	local div = parentNode:tag("div")
	StyleUtils.styleInfoboxTitleIcon(div)

	div:wikitext("[[File:" .. iconFilename .. "]]"):done():newline()

	return div
end



---
--- A shortcut method to make a table row based on the provided check, also
--- checks to see whether it needs to add a separator.
---
---@param parentNode table the html node to add to
---@param check boolean whether the row can be added
---@param needsSeparator boolean whether the row needs a separator before it
---@param header string the header of the row
---@param value string the value of the row
---@return boolean whether the next row still needs a separator before it
function Infobox.makeInnerRow(parentNode, check, needsSeparator, header, value)

	local row
	local nowNeedsSeparator = needsSeparator
	if check ~= nil and check ~= "" then

		row = parentNode:tag("tr")
		nowNeedsSeparator = trySeparator(row, needsSeparator)

		populateStandardRow(row, header, value)
	end

	return nowNeedsSeparator
end



--- *DEPRECATED* TODO DELETE THIS because we're not doing inline css anymore
--- Shortcut method to build a standard row with a header and a value.
---
---@param htmlNode table the MediaWiki HTML entity to build on
---@param cssInline string inline style to apply
---@param header string header text
---@param value string value text
---@return table the same htmlTable
function Infobox.buildStandardRow(htmlNode, cssInline, header, value)

	htmlNode:tag("tr"):css(cssInline)
			  :tag("th"):wikitext(header):done()
			  :tag("td"):wikitext(value):done()
			  :done():newline()

	return htmlNode
end



---
--- Lays out an inner, no-styling table to display construction goods with
--- their stack sizes.
---
--- @param requiredGoods table of required goods, with stack sizes and IDs
--- @return string wikimarkup for a table of required goods to insert in other markup
function Infobox.writeRequiredGoodsRows(requiredGoods)

	if not requiredGoods or #requiredGoods == 0 then
		error("Cannot write content with provided table of required goods.")
	end

	local templateArgs = {}
	for _, requiredGroup in ipairs(requiredGoods) do

		local stackSize = requiredGroup[INDEX_REQ_STACK_SIZE]
		local goodName = GoodsData.getGoodNameByID(requiredGroup[INDEX_REQ_GOOD_ID])

		templateArgs[goodName] = stackSize
	end

	return mw.getCurrentFrame():expandTemplate{ title = CONSTRUCTION_TEMPLATE,
			args = templateArgs }
end



---
--- Starts off a new infobox.
---
--- @return table the html node created
function Infobox.startNewInfobox()

	local wikiInfobox = mw.html.create("div")
	StyleUtils.styleInfobox(wikiInfobox)

	wikiInfobox:newline()

	return wikiInfobox
end



---
--- Creates a standard way of displaying the top content of the infobox and
--- returns the HTML node. Styles consistently.
---
---@param wikiInfobox table the overall context this is added to
---@return table the html node created
function Infobox.startNewHeader(wikiInfobox)

	local header = wikiInfobox:tag("div")
	StyleUtils.styleInfoboxHeader(header)

	header:newline()

	return header
end



---
--- Finalizes the standard top content
---
---@param wikiInfobox table the overall context
---@param header table the header content to add to the outer context
function Infobox.endInnerTable(wikiInfobox, header)

	header:done():newline()

	return wikiInfobox
end



---
--- Creates a standard way of displaying rows of headers and values and
--- returns the HTML node. Styles consistently.
---
---@param wikiInfobox table the overall context this is added to
---@return table the html node created
function Infobox.startNewInnerTable(wikiInfobox)

	local innerTable = wikiInfobox:tag("table")
	innerTable = StyleUtils.styleInfoboxInnerTable(innerTable)

	innerTable:newline()

	return innerTable
end


---
--- Finalizes the standard inner display of headers and values.
---
---@param wikiInfobox table the overall context
---@param innerTable table the inner content to add to the outer context
function Infobox.endInnerTable(wikiInfobox, innerTable)

	innerTable:done():newline()

	return wikiInfobox
end

--endregion

return Infobox