Module:ItemsNav

local p = {} local h = {}

local data = mw.loadData("Module:Imagenav/Data") local tab2 = require("Module:Tab2") local terminology = require("Module:Term") local utilsError = require("Module:UtilsError") local utilsFile = require("Module:UtilsFile") local utilsGame = require("Module:UtilsGame") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable") local validate = require("Module:ArgsUtil").validate local wikitable = require("Module:Wikitable")

local classes = { stateContainer = "state-container", states = "state-container__states", state = "state-container__state", defaultState = "state-container__state state-container__state--active", controls = "state-container__controls state-container__controls--vertical", stateControlForward = "state-control state-control-forward", stateControlBack = "state-control state-control-back", stateControlDisabled = "state-control--disabled", } local defaultSpacing = "8px"

local currentPage = mw.title.getCurrentTitle.text

local game local articleType

function p.Main(frame) local args = frame:getParent.args local output = p.main({		game = args[1],		articleType = args[2],		frame = args.frame,		header = args.header,		footer = args.footer,		align = args.align,	}) return output end

local validators = { articleType = { required = true, values = utilsTable.keys(data) },	game = { required = true, values = function(args) return utilsTable.keys(data[args.articleType]) end } }

function p.main(args) local err = validate(args, validators) if err then utilsError.logWarnings(err) return utilsError.printErrorCategories(utilsError.INVALID_TEMPLATE_ARGUMENTS) end game = args.game articleType = args.articleType local navData = data[articleType] local nav = h.createTabbedNav(navData[game]) if not args.frame then return nav end local gameSubtitle = utilsGame.AbbToGame(game, true) local header = args.header or utilsString.interpolate(navData.defaults.header, { game = gameSubtitle }) local footer = args.footer or navData.defaults.footer return wikitable.createTable({		align = args.align or "center",		styles = {			["margin-top"] = "1em",			["margin-bottom"] = "1em",		},		rows = {			{				header = true,				cells = { header }			},			{				cells = { nav },			},			{				footer = true,				cells = { footer },			},		},	}) end

function h.createTabbedNav(config) if not config[1].tab then return tostring(h.createNav(config[1])) end local tabs = {} local defaultTab for i, v in pairs(config) do		local tab, pages = h.createNav(v) table.insert(tabs, {			tabName = v.tab,			tabCaption = v.caption,			tabContent = tostring(tab),		}) if not defaultTab then --defaultTab = utilsTable.keyOf(pages, currentPage) and i		end end defaultTab = defaultTab or 1 return tostring(tab2.Main(tabs, defaultTab, "top", nil, nil, nil, nil, "center")), pages end

function h.createNav(config) if config.subtabs then return h.createTabbedNav(config.subtabs) end if config.map then return h.createMapNav(config.map) end if config.row then return h.createRowNav(config.row) end if config.multirow then return h.createMultirowNav(config.multirow) end end

function h.createMultirowNav(data) local html = mw.html.create("div") :css("display", "flex") local rowSpacing = data.rowSpacing or defaultSpacing local marginDirection = data.singleLine and "margin-left" or "margin-top" local flexDirection = data.singleLine and "row" or "column" html:css({		[marginDirection] = "-" .. rowSpacing,		["flex-direction"] = flexDirection,		["align-items"] = data.singleLine and "flex-end" or "stretch",		["justify-content"] = "center",	}) local pages = {} for _, rowItems in ipairs(data.items) do		local rowData = { fileType = data.fileType, scale = data.scale, spacing = data.itemSpacing, items = rowItems, }		local row, rowPages = h.createRowNav(rowData) pages = utilsTable.mergeArrays(pages, rowPages) html:tag("div") :wikitext(row) :css({				[marginDirection] = rowSpacing,			}) :done end return tostring(html), pages end

function h.createRowNav(data, options) local vertical = options and options.vertical local styles = options and options.styles local pages = {} local row = mw.html.create("div") :css({			["display"] = "flex",			["flex-wrap"] = "wrap",			["flex-direction"] = (vertical and "column") or "row",			["justify-content"] = "center",			["align-items"] = "flex-end",			["margin-top"] = "-" .. defaultSpacing,			["margin-left"] = "-" .. (data.spacing or defaultSpacing),		}) :css(styles or {}) for _, item in ipairs(data.items) do		table.insert(pages, item) row :tag("div") :wikitext(h.rowItem(data, item)) :css({				["margin-top"] = defaultSpacing,				["margin-left"] = (data.spacing or defaultSpacing),			}) :done end return tostring(row), pages end

function h.rowItem(rowData, item) if utilsString.startsWith("[[File:", item) then		return item	end	local page, term = h.getLinkParts(item)	local fileName = table.concat({game, term, rowData.fileType}, " ")	local size = rowData.fileSize	if not size then		local fileWidth = rowData.scale and mw.title.makeTitle("File", fileName).file.width		local thumbnailWidth = fileWidth and fileWidth * rowData.scale		size = thumbnailWidth and thumbnailWidth .. "px"	end	return utilsFile.link({		file = fileName,		size = size,		link = page,		alt = term,	}) end function h.createMapNav(config)	local baseImageMap, pages = h.createImageMap(config)	if not config.upgrades then		return baseImageMap, pages	end

local leftColumn if config.leftColumn then leftColumn, columnPages = h.createRowNav(config.leftColumn, { 			vertical = true,			styles = {				["margin-right"] = defaultSpacing,			},		}) pages = utilsTable.mergeArrays(pages, columnPages) end local upgradeMaps, upgradePages, defaultState = h.resolveUpgradeMaps(config) local states = utilsTable.mergeArrays({ baseImageMap }, upgradeMaps) pages = utilsTable.mergeArrays(pages, upgradePages) return h.createStatefulMenuMap(states, defaultState, config.maxWidth, leftColumn), pages end

function h.resolveUpgradeMaps(data) local defaultState = 1 local resolvedMapData = {} local pages = {} local initialAreas = utilsTable.shallowClone(data.areas) for i, upgradeData in pairs(data.upgrades) do		local addedPages = {} local changedAreas = utilsTable.mapInPlace(initialAreas, function(area)			local change = upgradeData.changes[area.page]			if not change then				return area			end			if change == "" then				return nil			end			if type(change) == "string" then				change = { page = change }			end			table.insert(addedPages, change.page)			return {				area = change.area or area.area,				page = change.page or area.page,				display = change.display,			}		end) changedAreas = utilsTable.removeFalseEntries(changedAreas) local newAreas = utilsTable.shallowClone(upgradeData.areas or {}) local upgradeAreas = utilsTable.mergeArrays(newAreas, changedAreas) local imageMap, mapPages = h.createImageMap({			image = upgradeData.image,			areas = upgradeAreas,			maxWidth = data.maxWidth,		}) table.insert(resolvedMapData, imageMap)

pages = utilsTable.mergeArrays(pages, addedPages) defaultState = (utilsTable.keyOf(addedPages, currentPage) and i+1) or defaultState initialAreas = upgradeAreas end return resolvedMapData, pages, defaultState end

function h.createStatefulMenuMap(states, defaultState, maxWidth, leftColumn) local statesNode = mw.html.create("div") :addClass(classes.states) :css({				["display"] = "flex",				["align-items"] = "center",				["justify-content"] = "center",				["flex"] = "1",				["max-width"] = maxWidth and maxWidth .. "px",		}) for i, state in ipairs(states) do 		local default = i == defaultState statesNode :tag("div") :addClass(default and classes.defaultState or classes.state) :css("flex", "1") :wikitext(state) :done end local controlCss = { ["height"] = "12%", ["min-height"] = "32px", ["margin-left"] = "15%", }	local controlsNode = mw.html.create("div") :addClass(classes.controls) :tag("div") :addClass(classes.stateControlForward) :addClass(defaultState == #states and classes.stateControlDisabled or nil) :css(controlCss) :wikitext("") :done :tag("div") :addClass(classes.stateControlBack) :addClass(defaultState == 1 and classes.stateControlDisabled or nil) :css(controlCss) :wikitext("") :done local stateContainer = mw.html.create("div") :addClass(classes.stateContainer) :node(mw.clone(controlsNode):css("visibility", "hidden")) --Hack to "balance" the left side and keep the content centered :node(leftColumn) :node(statesNode) :node(controlsNode) :node(leftColumn and mw.html.create("div"):css("visibility", "hidden"):node(leftColumn)) :css({			["display"] = "flex",			["justify-content"] = "center",		}) return stateContainer end

function h.createImageMap(data) local lines = { data.image } local pages = {} for _, area in ipairs(data.areas) do		local link = ("[%s %s]"):format(h.getLinkParts(area.page, area.display)) table.insert(pages, area.page) table.insert(lines, area.area .. link) end table.insert(lines, " desc none") local imagemap = mw.getCurrentFrame:extensionTag("imagemap", table.concat(lines, "\n")) local responsiveImageMap = mw.html.create("div") :addClass("resizable-image-map") :css({			["max-width"] = data.maxWidth and data.maxWidth .. "px",			["margin"] = "0 auto",		}) :wikitext(imagemap) return tostring(responsiveImageMap), pages end

-- For rendering a page link as an external link, so as to not spam Special:WhatLinksHere function h.getLinkParts(page, display) local url = mw.title.new(page):fullUrl if display then return url, display end local row = terminology.fetchRow({ term = page, game = game }) local alt = row and row.term or page alt = mw.text.trim(alt, "'") -- workaround for the fact that the term string can include formatting characters (e.g. Cuccodex) return url, alt end

return p