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 escapeTemplateParam(s)
-- Prevent breaking template calls if odd characters appear
s = tostring(s or "")
s = s:gsub("|", "|")
return s
end
local function normalizeScope(s)
-- Accommodate plural forms of scopes
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, normalizeFn)
if type(list) ~= "table" then return false end
-- defaults to lowerTrim if not comparing scopes
normalizeFn = normalizeFn or lowerTrim
token = normalizeFn(token)
for _, v in ipairs(list) do
if normalizeFn(v) == token then return true end
end
return false
end
local function sortedSwatches()
local swatches = {}
for _, s in ipairs(data) do
swatches[#swatches + 1] = s
end
table.sort(swatches, function(a, b)
return lowerTrim(a.name) < lowerTrim(b.name)
end)
return swatches
end
-- ---------- Compatibility ----------
local function swatchAppliesTo(swatch, scope, item)
scope = normalizeScope(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", normalizeScope) then return true end
-- Otherwise must match the requested scope
if not listContains(swatchScopes, scope, normalizeScope) 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:
-- - If 'scopes' includes "All": display "All"
-- - Else if 'onlyItems' is present: list item names (without the "Scope:" prefix)
-- - Else, swatch applies to all items within its scopes in 'scopes':
-- * One scope -> "All Vehicles"
-- * Many scopes -> "All Vehicles • Weapons • Garments" (etc.)
local swatchScopes = swatch.scopes or { "All" }
if listContains(swatchScopes, "All", normalizeScope) 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 = {}
for _, t in ipairs(swatchScopes) do
local s = normalizeScope(t)
if s == "vehicle" then
scopes[#scopes + 1] = "Vehicles"
elseif s == "weapon" then
scopes[#scopes + 1] = "Weapons"
elseif s == "garment" then
scopes[#scopes + 1] = "Garments"
elseif s == "placeable" then
scopes[#scopes + 1] = "Placeables"
else
-- Fallback: use the scope as-is
scopes[#scopes + 1] = trim(t)
end
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, " • ")
end
-- ---------- Rendering ----------
local function renderSwatchInfoboxCall(swatch)
local title = escapeTemplateParam(swatch.name or "")
local image = escapeTemplateParam(swatch.image or "Placeholder.jpg")
local link = escapeTemplateParam(swatch.link or "")
local c1 = escapeTemplateParam((swatch.colors and swatch.colors[1]) or "")
local c2 = escapeTemplateParam((swatch.colors and swatch.colors[2]) or "")
local c3 = escapeTemplateParam((swatch.colors and swatch.colors[3]) or "")
local c4 = escapeTemplateParam((swatch.colors and swatch.colors[4]) or "")
local compat = escapeTemplateParam(getCompatibilityDisplay(swatch) or "All")
local out = {}
out[#out + 1] = "{{SwatchInfobox"
out[#out + 1] = "|title=" .. title
out[#out + 1] = "|image=" .. image
if link ~= "" then
out[#out + 1] = "|link=" .. link
end
out[#out + 1] = "|color1=" .. c1
out[#out + 1] = "|color2=" .. c2
out[#out + 1] = "|color3=" .. c3
out[#out + 1] = "|color4=" .. c4
out[#out + 1] = "|compatibility=" .. compat
out[#out + 1] = "}}"
return table.concat(out, "\n")
end
function p.list(frame)
local args = frame.args
local scope = 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] = renderSwatchInfoboxCall(swatch)
end
end
out[#out + 1] = "</div>"
return frame:preprocess(table.concat(out, "\n"))
end
return p