Module:Gallery List
Jump to navigation
Jump to search
This is the main module for the following templates:
In addition, this module exports the following functions.
parseEntry
parseEntry(subject, game, fileType, [options])
Allows other modules such as Module:Data Table to use Template:Gallery List's filename generation syntax.
Parameters
subject
- A wiki article name referring to the subject for which a filename is being generated.
game
- A game code from Data:Franchise.
fileType
- A file type such as
Sprite
orModel
.
[options]
- The options object is passed along to Module:Term#fetchTerm and Module:Term#printTerm, which is used to create the return object unless
useTerms
is false.
Returns
- An object containing a filename, a term link, a term string, and the
subject
received minus any Template:Gallery List syntax.
Examples
# | Input | Output | Status |
---|---|---|---|
1 | parseEntry("Candy (The Minish Cap)", "TMC", "Sprite")
| {
subject = "Candy (The Minish Cap)",
file = "File:TMC Candy Sprite.png",
term = "Candy",
link = "[[Candy (The Minish Cap)#The Minish Cap|Candy]]",
info = "",
}
| |
2 | parseEntry(
"Ore Chunk [Red] <span>(10)</span>",
"OoS",
"Sprite"
)
| {
subject = "Ore Chunk",
file = "File:OoS Ore Chunk Red Sprite.png",
link = "[[Ore Chunk#Oracle of Seasons|Ore Chunk]]",
term = "Ore Chunk",
variant = "Red",
info = " <span>(10)</span>",
}
| |
3 | parseEntry(
"Stained Glass [File:TMC Stained Glass Artwork.png]",
"TMC",
""
)
| {
info = "",
file = "File:TMC Stained Glass Artwork.png",
term = "Stained Glass",
link = "[[Stained Glass#The Minish Cap|Stained Glass]]",
subject = "Stained Glass",
}
| |
4 | parseEntry("Hyper Pico Bloom [No Image]", "TMC", "")
| {
info = "",
file = "File:No Image.png",
term = "Hyper Pico Bloom",
link = "[[Hyper Pico Bloom#The Minish Cap|Hyper Pico Bloom]]",
subject = "Hyper Pico Bloom",
}
| |
5 | parseEntry(
"Deku Tree",
"TWW",
"Figurine Model",
{ useTerms = false }
)
| {
subject = "Deku Tree",
file = "File:TWW Deku Tree Figurine Model.png",
term = "Deku Tree",
link = "[[Deku Tree]]",
info = "",
}
| |
6 | parseEntry("Bomb", "ALttP", "30 Sprite", { plural = true })
| {
subject = "Bomb",
file = "File:ALttP Bombs 30 Sprite.png",
term = "Bombs",
link = "[[Bomb#A Link to the Past|Bombs]]",
info = "",
}
|
local p = {}
local h = {}
local Data = mw.loadData("Module:Gallery List/Data")
local Franchise = require("Module:Franchise")
local Term = require("Module:Term")
local TermList = require("Module:Term List")
local utilsArg = require("Module:UtilsArg")
local utilsLayout = require("Module:UtilsLayout")
local utilsMarkup = require("Module:UtilsMarkup")
local utilsPage = require("Module:UtilsPage")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")
local CATEGORY_INVALID_ARGS = "[[Category:"..require("Module:Constants/category/invalidArgs").."]]"
-- Generally the latest remake is the default tab but some remakes don't have enough high quality images uploaded yet
local DEFAULT_TAB_DENYLIST = {
["MM3D"] = true,
["TWWHD"] = true,
["TPHD"] = true,
["SSHD"] = true,
}
-- Enhanced ports may have the same assets as the original game
-- There's no need to show a separate tab in those cases
local ENHANCED_PORTS = {
["OoTMQ"] = true,
--["FSAE"] = true -- turns out FSAE does have its own sprites
}
-- Gallery List stores data for [[Template:Spawn Locations]] if the page has any of the following infoboxes.
-- See comments on function h.isLocationArticle for more information
local LOCATION_INFOBOXES = {
"Template:Infobox Dungeon",
"Template:Infobox Location",
"Template:Infobox Minigame",
"Template:Infobox Scenario",
}
function h.warn(msg, ...)
local utilsError = require("Module:UtilsError")
local warnMessage = string.format(msg, ...)
utilsError.warn(warnMessage)
end
function p.Main(frame)
local args, err = utilsArg.parse(frame:getParent().args, p.Templates["Gallery List"])
local categories = err and err.categoryText or ""
local storeOnly = frame:getParent():getTitle() == "Template:Store Spawns"
local options = utilsTable.clone(args)
local subjectType = args.subjectType
local storeLocationData = storeOnly or h.isLocationArticle()
local tabData = {}
local defaultTab = 1
local allEntries = {}
for i, game in ipairs(Franchise.enumGames()) do
local rawEntries = args[game] or {}
rawEntries = utilsTable.filter(rawEntries, utilsString.notEmpty)
local fileType = options.fileType or h.fileType(subjectType, game)
local entries = utilsTable.map(rawEntries, function(rawEntry)
return p.parseEntry(rawEntry, game, fileType)
end)
allEntries = utilsTable.concat(allEntries, entries)
if #entries > 0 then
if not storeOnly then
local gallery = h.printGallery(entries, subjectType, game, options)
table.insert(tabData, {
label = Franchise.display(game),
content = gallery
})
if not DEFAULT_TAB_DENYLIST[game] then
defaultTab = #tabData
end
end
if args.storeAs then
local subjects = utilsTable.map(entries, "subject")
subjects = utilsTable.unique(subjects)
TermList.storeSequence(game, args.storeAs, subjects)
end
if storeLocationData then
local baseGame = Franchise.baseGame(game)
for i, entry in ipairs(entries) do
categories = categories..frame:expandTemplate({
title = "Location Features/Store",
args = {
feature = subjectType,
baseGame = baseGame,
game = game,
subject = entry.subject,
quantity= entry.quantity,
sublocations = entry.sublocations and table.concat(entry.sublocations, ","),
}
})
end
end
for j, remake in ipairs(Franchise.remakes(game)) do
if not storeOnly and not args[remake] and not ENHANCED_PORTS[remake] then
local fileType = options.fileType or h.fileType(subjectType, remake)
local entries = utilsTable.map(rawEntries, function(rawEntry)
return p.parseEntry(rawEntry, remake, fileType)
end)
local gallery = h.printGallery(entries, subjectType, remake, options)
table.insert(tabData, {
label = Franchise.display(remake),
content = gallery,
})
if not DEFAULT_TAB_DENYLIST[remake] then
defaultTab = #tabData
end
end
end
end
end
if storeOnly then
return "", categories
end
local tabs = utilsLayout.tabs(tabData, {
default = defaultTab,
tabOptions = {
collapse = true,
},
})
local html = mw.html.create("div")
:addClass("zw-gallery-list")
:wikitext(tabs)
local result = h.hatnote(allEntries)..tostring(html)
return result, categories
end
function p.LocationArticleCriteria(frame)
local links = utilsTable.map(LOCATION_INFOBOXES, utilsMarkup.link)
local list = utilsMarkup.bulletList(links)
return list
end
function p.parseEntry(rawEntry, game, fileType, options)
local options = options or {}
rawEntry = string.gsub(rawEntry, ",", ",") -- Unescape commas created using {{,}}
local subject, info = utilsMarkup.separateMarkup(rawEntry)
local tags, info = h.parseTags(info)
local link
local term
if options.useTerms ~= false then
link = Term.link(subject, game, options)
term = Term.fetchTerm(subject, game, options) or subject
else
link = string.format("[[%s]]", subject)
term = subject
end
local entry = utilsTable.merge({}, tags, {
link = link,
info = info,
term = term,
subject = subject,
})
entry.file = tags.file or h.file(game, entry, fileType, options)
return entry
end
-- Looks at the article's infobox to determine whether the Gallery List data should be stored for use by [[Template:Spawn Locations]]
-- Without this check, the Gallery List on [[Captain Construct]] causes that page to appear as a "habitat" for, say, [[Captain Construct I]]
--
-- Previous revisions checked the article's categories. However, there got to be so many categories in the query (Dungeons, Levels, Locations, Scenarios, Stages) that we exceeded the default DPL maximum
-- (see maxCategoryCount at https://www.mediawiki.org/wiki/Extension:DynamicPageList3)
function h.isLocationArticle()
local title = mw.title.getCurrentTitle()
local locationInfoboxes = table.concat(LOCATION_INFOBOXES, "|")
local dplResults = utilsPage.dpl({
uses = locationInfoboxes,
skipthispage= "no",
title = title.prefixedText,
})
return #dplResults ~= 0
end
local TAG_REGEX = "^%s*%[([^%]%[]+)%]"
function h.parseTags(info)
local tags = {}
local errorCategories = ""
local tagCount = 0
local matches
repeat
info, matches = string.gsub(info, TAG_REGEX, function(tag)
local tagParts = utilsString.split(tag, ":")
local tagName = tagParts[1]
local tagArgs = utilsTable.tail(tagParts)
if tagName == "File" then
tags.file = "File:"..tagArgs[1]
elseif tagName == "Qty" then
tags.quantity = tagArgs[1]
elseif tagName == "Sublocation" then
tags.sublocations = tagArgs
elseif #tagParts == 1 and tagCount == 0 then
if tagParts[1] == "No Image" then
tags.file = "File:No Image.png"
else
tags.variant = tagParts[1]
end
else
h.warn("Invalid tag <code>%s</code>. See [[Template:Gallery List#Tags]] for supported tags.", tagName)
errorCategories = CATEGORY_INVALID_ARGS
end
tagCount = tagCount + 1
return ""
end)
until matches == 0
return tags, info..errorCategories
end
function h.file(game, entry, fileType, options)
local suffix = fileType == "" and "" or " "..fileType
local variant = entry.variant
if variant then
if tonumber(variant) then
suffix = suffix .. " " .. variant
else
-- numbers can be placed in quotes to escape the above functionality
-- e.g. [[Items in Spirit Tracks]] uses Quiver ["50"] to produce `File:ST Quiver 50 Icon.png` instead of `File:ST Quiver Icon 50.png`
variant = utilsString.trim(variant, [["]])
suffix = " " .. variant .. suffix
end
end
local file
if variant == "No Image" then
file = "File:No Image.png"
elseif options.useTerms == false then
file = string.format("File:%s %s%s.png", game, entry.subject, suffix)
else
local term = entry.term
term = string.gsub(term, "#", "") -- filenames can't have # so we strip them from the terms
term = string.gsub(term, "''", "") -- same goes for any formatting in the term, e.g. ''Cuccodex''
file = string.format("File:%s %s%s.png", game, term, suffix)
end
return file
end
function h.printGallery(entries, subjectType, game, options)
options = options or {}
local galleryContent = ""
for i, entry in ipairs(entries) do
galleryContent = galleryContent..entry.file.."|"..h.printEntry(entry).."\n"
end
local size = Data.sizes[game] and Data.sizes[game][subjectType] or {}
local gallery = mw.getCurrentFrame():extensionTag({
name = "gallery",
content = galleryContent,
args = {
caption = options.caption,
widths = options.widths or size.widths,
heights = options.heights or size.heights,
perrow = options.perrow,
}
})
return gallery
end
function h.fileType(subjectType, game)
local graphics = Franchise.graphics(game)
if subjectType == "Locations" then
return ""
elseif graphics == "2D" then
return "Sprite"
elseif subjectType == "Items" then
return "Icon"
elseif subjectType == "Treasures" then
return "Icon"
elseif subjectType == "Zonai Devices" then
return "Icon"
else
return "Model"
end
end
function h.printEntry(entry)
local result = entry.link
if entry.quantity then
result = result.." ×"..entry.quantity
end
if entry.info then
result = result..entry.info
end
return result
end
function h.hatnote(entries)
local entriesWithSublocations = utilsTable.filter(entries, "sublocations")
if #entriesWithSublocations == 0 then
return ""
end
local sublocations = utilsTable.map(entries, "sublocations")
sublocations = utilsTable.compact(sublocations)
sublocations = utilsTable.flatten(sublocations)
sublocations = utilsTable.unique(sublocations)
table.sort(sublocations)
for i, sublocation in ipairs(sublocations) do
sublocations[i] = Term.link(sublocation, game)
end
sublocations = mw.text.listToText(sublocations)
local hatnote = mw.getCurrentFrame():expandTemplate({
title = "Hatnote",
args = {"Parts of this section are derived from the following location(s): "..sublocations}
})
return hatnote
end
function p.Schemas()
return {
parseEntry = {
subject = {
required = true,
type = "string",
desc = "A wiki article name referring to the subject for which a filename is being generated.",
},
game = {
required = true,
type = "string",
desc = "A game code from [[Data:Franchise]].",
},
fileType = {
required = true,
type = "string",
desc = "A file type such as <code>Sprite</code> or <code>Model</code>.",
},
options = {
type = "record",
desc = "The options object is passed along to [[Module:Term#fetchTerm]] and [[Module:Term#printTerm]], which is used to create the return object unless <code>useTerms</code> is false.",
properties = {
{
name = "useTerms",
type = "boolean",
},
},
},
},
Data = {
type = "record",
required = true,
properties = {
{
name = "sizes",
desc = "Sets the size of gallery thumbnails based on the <code>game</code> and <code>subjectType</code> parameters of [[Template:Gallery List]].",
required = true,
type = "map",
keyPlaceholder = "game",
keys = { type = "string" },
values = {
type = "map",
keyPlaceholder = "subjectType",
keys = { type = "string" },
values = {
type = "record",
properties = {
{
name = "widths",
type = "string",
required = true,
desc = "A value in pixels corresponding to the <code>widths</code> {{MediaWiki|Help:Images#Optional_gallery_attributes|gallery attribute}}."
},
{
name = "heights",
type = "string",
required = true,
desc = "A value in pixels corresponding to the <code>heights</code> {{MediaWiki|Help:Images#Optional_gallery_attributes|gallery attribute}}."
},
},
},
},
},
},
}
}
end
function p.Documentation()
return {
parseEntry = {
desc = "Allows other modules such as [[Module:Data Table]] to use [[Template:Gallery List]]'s [[Template:Gallery List#Variant Syntax|filename generation syntax]].",
params = {"subject", "game", "fileType", "options"},
returns = "An object containing a filename, a term link, a term string, and the <code>subject</code> received minus any [[Template:Gallery List]] syntax.",
cases = {
outputOnly = true,
{
args = {"Candy (The Minish Cap)", "TMC", "Sprite"},
expect = {
info = "",
file = "File:TMC Candy Sprite.png",
subject = "Candy (The Minish Cap)",
term = "Candy",
link = "[[Candy (The Minish Cap)#The Minish Cap|Candy]]",
}
},
{
args = {"Ore Chunk [Red] <span>(10)</span>", "OoS", "Sprite"},
expect = {
file = "File:OoS Ore Chunk Red Sprite.png",
subject = "Ore Chunk",
term = "Ore Chunk",
link = "[[Ore Chunk#Oracle of Seasons|Ore Chunk]]",
variant = "Red",
info = " <span>(10)</span>",
}
},
{
args = {"Stained Glass [File:TMC Stained Glass Artwork.png]", "TMC", ""},
expect = {
file = "File:TMC Stained Glass Artwork.png",
subject = "Stained Glass",
term = "Stained Glass",
link = "[[Stained Glass#The Minish Cap|Stained Glass]]",
info = "",
}
},
{
args = {"Hyper Pico Bloom [No Image]", "TMC", ""},
expect = {
file = "File:No Image.png",
term = "Hyper Pico Bloom",
subject = "Hyper Pico Bloom",
link = "[[Hyper Pico Bloom#The Minish Cap|Hyper Pico Bloom]]",
info = "",
}
},
{
args = {"Deku Tree", "TWW", "Figurine Model", { useTerms = false }},
expect = {
file = "File:TWW Deku Tree Figurine Model.png",
subject = "Deku Tree",
term = "Deku Tree",
link = "[[Deku Tree]]",
info = "",
},
},
{
args = {"Bomb", "ALttP", "30 Sprite", { plural = true } },
expect = {
file = "File:ALttP Bombs 30 Sprite.png",
subject = "Bomb",
term = "Bombs",
link = "[[Bomb#A Link to the Past|Bombs]]",
info = "",
}
},
},
},
}
end
function p.templateData()
local paramOrder = {1, "fileType", "storeAs", "caption", "perrow", "widths", "heights"}
local params = {
[1] = {
name = "subjectType",
required = true,
type = "string",
enum = {"Animals", "Bosses", "Characters", "Creatures", "Enemies", "Equipment", "Locations", "Items", "Materials", "Objects", "Treasures", "Zonai Devices"},
desc = "The type of subject being listed.",
trim = true,
nilIfEmpty = true,
},
fileType = {
type = "string",
desc = "Sets a custom filename suffix for gallery entries",
trim = true,
},
storeAs = {
type = "string",
desc = "Stores the list in the [[Special:CargoTables/Sequences|Sequences]] Cargo table for use by [[Template:Sort Value]] and [[Module:Sequences]].",
canOmit = true,
trim = true,
nilIfEmpty = true,
},
caption = {
type = "string",
desc = "Caption text for the gallery.",
trim = true,
nilIfEmpty = true,
canOmit= true,
},
perrow = {
type = "number",
desc = "Maximum number of thumbnails to show per row in the gallery.",
trim = true,
nilIfEmpty = true,
canOmit = true,
},
widths = {
type = "string",
desc = "A value in pixels. Sets the width of gallery entries, overriding any default set in [[Module:Gallery List/Data]]",
trim = true,
nilIfEmpty = true,
canOmit = true,
},
heights = {
type = "string",
desc = "A value in pixels. Sets the heights of gallery entries, overriding any default set in [[Module:Gallery List/Data]]",
trim = true,
nilIfEmpty = true,
canOmit = true,
}
}
for i, game in ipairs(Franchise.enumGames()) do
if Franchise.type(game) ~= "" then -- only games featured on the main page for now, to avoid adding too many parameters
table.insert(paramOrder, game)
params[game] = {
type = "string",
desc = "Comma-separated list of wiki page names referring to subjects in "..Franchise.display(game),
trim = true,
split = true,
}
end
end
return {
format = "block",
params = params,
paramOrder = paramOrder,
}
end
p.Templates = {
["Gallery List"] = p.templateData(),
["Store Spawns"] = {},
}
return p