Module:Swatches
Jump to navigation
Jump to search
Module:Swatches
Lists cosmetic swatches from Module:Swatches/data and renders them using Template:SwatchInfobox.
The rendered infobox cards are wrapped in a flex container with the following styling: display:flex; flex-wrap:wrap; overflow-x:auto; align-items:flex-start; gap:1em;
Usage
List all swatches
If no scope is provided, all swatches in the dataset are shown:
{{#invoke:Swatches|list}}
List swatches for a scope page
Scope can be specified to show all swatches that are compatible with at least one item in that scope:
{{#invoke:Swatches|list|scope=<SCOPE>}}
List swatches for an item page
Item can additionally be specified to show only swatches that are compatible with that item:
{{#invoke:Swatches|list|scope=<SCOPE>|item=<ITEM>}}
Parameters
- scope (optional)
- Compatibility scope to filter swatches. Accepted values:
VehicleWeaponGarmentPlaceable
- item (optional)
- Name of item within the specified scope, to further filter swatch compatibility. This must match the item name used in
onlyItemstokens in Module:Swatches/data. {{PAGENAME}}can be used if the page title matches the dataset naming.
Notes
- Swatch compatibility rules are defined in Module:Swatches/data using the
scopesand optionalonlyItemsfields. - If
scopeis omitted,itemis ignored and all swatches are shown. - If a swatch uses
scopes={"All"}, it will appear for any scope/item filter.
local p = {}
local data = require("Module:Swatches/data")
-- ---------- Helpers ----------
local function trim(s)
return mw.text.trim(tostring(s or ""))
end
local function lowerTrim(s)
return mw.ustring.lower(trim(s))
end
local function escape(s)
s = tostring(s or "")
return s:gsub("|", "|")
end
local function normalizeInputScope(s)
-- Accommodate plural forms of scopes during invocation
s = lowerTrim(s)
if s == "vehicles" then return "vehicle" end
if s == "weapons" then return "weapon" end
if s == "garments" then return "garment" end
if s == "placeables" then return "placeable" end
return s
end
local function listContains(list, token)
if type(list) ~= "table" then return false end
token = lowerTrim(token)
for _, v in ipairs(list) do
if lowerTrim(v) == token then return true end
end
return false
end
local function sortedSwatches()
table.sort(data, function(a, b)
return lowerTrim(a.name) < lowerTrim(b.name)
end)
return data
end
-- ---------- Compatibility ----------
local function swatchAppliesTo(swatch, scope, item)
scope = lowerTrim(scope or "")
item = trim(item or "")
-- If no scope provided, show all swatches
if scope == "" then return true end
-- If 'scopes' not provided, default to All
local swatchScopes = swatch.scopes or { "All" }
-- Swatches with an "All" scope match all filters
if listContains(swatchScopes, "All") then return true end
-- Otherwise must match the requested scope
if not listContains(swatchScopes, scope) then return false end
-- If no item specified, scope match is enough
if item == "" then return true end
-- If 'onlyItems' is not present, applies to all items in this scope
if type(swatch.onlyItems) ~= "table" or #swatch.onlyItems == 0 then return true end
-- Otherwise, must match one of the "Scope:Item" tokens
local token = scope .. ":" .. item
return listContains(swatch.onlyItems, token)
end
local function getCompatibilityDisplay(swatch)
-- Derive the Compatibility value to display, based on 'scopes' and 'onlyItems' variables
local swatchScopes = swatch.scopes or { "All" }
if listContains(swatchScopes, "All") then return "All" end
if type(swatch.onlyItems) == "table" and #swatch.onlyItems > 0 then
local items = {}
for _, tok in ipairs(swatch.onlyItems) do
local t = trim(tok)
-- Split on first colon: "Scope:Item" -> "Item"
local scopePart, itemPart = t:match("^%s*([^:]+)%s*:%s*(.+)%s*$")
if itemPart then
items[#items + 1] = itemPart
else
-- Display warning if malformed token (missing "Scope:Item")
items[#items + 1] = "[INVALID onlyItems token: " .. t .. "]"
end
end
-- One item: return plain text
if #items == 1 then return items[1] end
-- Many items: return bullet list
local out = {}
for _, item in ipairs(items) do
out[#out + 1] = "* " .. item
end
return "\n" .. table.concat(out, "\n")
end
-- Convert scopes into display phrases like "Vehicles"
local scopes = {}
local scopeLabels = {
vehicle = "Vehicles",
weapon = "Weapons",
garment = "Garments",
placeable = "Placeables"
}
for _, t in ipairs(swatchScopes) do
scopes[#scopes + 1] = scopeLabels[lowerTrim(t)] or trim(t)
end
-- Sort for stable output
table.sort(scopes, function(a, b)
return lowerTrim(a) < lowerTrim(b)
end)
if #scopes == 0 then return "All" end
return "*All " .. table.concat(scopes, "\n* All ")
end
-- ---------- Rendering ----------
local function renderInfobox(swatch)
local out = {}
out[#out + 1] = "{{SwatchInfobox"
out[#out + 1] = "|title=" .. escape(swatch.name)
out[#out + 1] = "|image=" .. escape(swatch.image or "Placeholder.jpg")
local link = escape(swatch.link)
if link ~= "" then
out[#out + 1] = "|link=" .. link
end
out[#out + 1] = "|color1=" .. escape(swatch.colors and swatch.colors[1])
out[#out + 1] = "|color2=" .. escape(swatch.colors and swatch.colors[2])
out[#out + 1] = "|color3=" .. escape(swatch.colors and swatch.colors[3])
out[#out + 1] = "|color4=" .. escape(swatch.colors and swatch.colors[4])
out[#out + 1] = "|compatibility=" .. escape(getCompatibilityDisplay(swatch) or "All")
out[#out + 1] = "}}"
return table.concat(out, "\n")
end
function p.list(frame)
local args = frame.args
local scope = normalizeInputScope(args.scope or "")
local item = args.item or ""
local out = {}
out[#out + 1] = '<div style="display:flex; flex-wrap:wrap; overflow-x:auto; align-items:flex-start; gap:1em;">'
for _, swatch in ipairs(sortedSwatches()) do
if swatchAppliesTo(swatch, scope, item) then
out[#out + 1] = renderInfobox(swatch)
end
end
out[#out + 1] = "</div>"
return frame:preprocess(table.concat(out, "\n"))
end
return p