(Created page with "--[[ This module is about creating navboxes autopopulated with in game data, with game extra user customization on top of it. Table generated try to mimic the game data & categories. Extra "fake" categories or forced categorisation can be done by registering objects with the NavCategory template on the specific pages --]] local p = {} local cargo = mw.ext.cargo local USER_NAV_CATEGORIES_TABLE = "userNavCategories" ---@class TypeData ---@comment Contains categoryfilte...") |
(= p.createNavBox(frame, "Buildings", "Building")) |
||
Line 3: | Line 3: | ||
Table generated try to mimic the game data & categories. | Table generated try to mimic the game data & categories. | ||
Extra "fake" categories or forced categorisation can be done by registering objects with the NavCategory template on the specific pages | Extra "fake" categories or forced categorisation can be done by registering objects with the NavCategory template on the specific pages | ||
Usage: | |||
There is only one public function here: createNavBox(<nav box title>, <navBoxType>) | |||
Where navBoxType is within NavboxType values below. | |||
--]] | --]] | ||
local p = {} | local p = {} | ||
local nav = {} -- you can use this one for debugging without exposing functions, return this instead of p | |||
---@type any | |||
mw = mw | |||
local cargo = mw.ext.cargo | local cargo = mw.ext.cargo | ||
local CATEGORY_FILTER_TABLE = "categoryfilter" | |||
local USER_NAV_CATEGORIES_TABLE = "userNavCategories" | local USER_NAV_CATEGORIES_TABLE = "userNavCategories" | ||
Line 16: | Line 25: | ||
---@field tab string | ---@field tab string | ||
---@field filterField string | ---@field filterField string | ||
---@field recipeType string | ---@field recipeType string|nil | ||
---@alias NavboxType "UNIT" | "BUILDING" | "COMPONENT" | "ITEM" | |||
-- Possible types to create a navbox with | -- Possible types to create a navbox with | ||
---@type table< | ---@type table<NavboxType, TypeData> | ||
local TYPES = { | local TYPES = { | ||
UNIT = { | UNIT = { | ||
Line 53: | Line 64: | ||
---@param typeData TypeData | ---@param typeData TypeData | ||
---@return table<string, CategoryData> | ---@return table<string, CategoryData> | ||
nav.queryBaseCategories = function (typeData) | |||
local categoriesMeta = cargo.query( | local categoriesMeta = cargo.query( | ||
CATEGORY_FILTER_TABLE .. "=cat", | |||
'name, filterVal, ordering', | |||
{ | { | ||
where = string.format('tab="%s" AND filterField="%s"', typeData.tab, typeData.filterField), | where = string.format('tab="%s" AND filterField="%s"', typeData.tab, typeData.filterField), | ||
Line 72: | Line 83: | ||
local foundObjects = cargo.query( | local foundObjects = cargo.query( | ||
typeData.cargo_table, | typeData.cargo_table, | ||
'name', | |||
{ | { | ||
where = wherePart, | where = wherePart, | ||
Line 89: | Line 100: | ||
---@param type string | ---@param type string | ||
---@return table<string, CategoryData> | ---@return table<string, CategoryData> | ||
nav.queryUserExtrasCategories = function(type) | |||
local userCategoriesRows = cargo.query( | local userCategoriesRows = cargo.query( | ||
USER_NAV_CATEGORIES_TABLE, | USER_NAV_CATEGORIES_TABLE, | ||
'category=catName, pagename', | |||
{ | { | ||
where = string.format('UPPER(type) = "%s"', type) | where = string.format('UPPER(type) = "%s"', type) | ||
Line 118: | Line 126: | ||
---@param extra table<string, CategoryData> | ---@param extra table<string, CategoryData> | ||
---@return table<string, CategoryData> | ---@return table<string, CategoryData> | ||
nav.mergeCategories = function (base, extra) | |||
-- Step 1: Remove from base any name also found in extra | -- Step 1: Remove from base any name also found in extra | ||
for _, extraCat in pairs(extra) do | for _, extraCat in pairs(extra) do | ||
Line 153: | Line 161: | ||
---@param categories table<string, CategoryData> | ---@param categories table<string, CategoryData> | ||
---@return SortedCategoryEntry[] | ---@return SortedCategoryEntry[] | ||
nav.sortCategories = function(categories) | |||
-- Convert map to array | -- Convert map to array | ||
local arr = {} | local arr = {} | ||
Line 172: | Line 180: | ||
-- @param {string} frame.args.title Navtable title | -- @param {string} frame.args.title Navtable title | ||
-- @param {string} frame.args.type from types above here, like Building or Item (any case) | -- @param {string} frame.args.type from types above here, like Building or Item (any case) | ||
---@param tableTitle string Navtable title | |||
---@param rawType string from types above here, like Building or Item (any case) | |||
---@return string | ---@return string | ||
nav.createNavBox = function(frame, tableTitle, rawType) | |||
---@type NavboxType | |||
---@type | |||
local type = mw.ustring.upper(rawType or "") | local type = mw.ustring.upper(rawType or "") | ||
local typeData = TYPES[type] | local typeData = TYPES[type] | ||
Line 189: | Line 191: | ||
end | end | ||
local baseCategories = queryBaseCategories(typeData) | local baseCategories = nav.queryBaseCategories(typeData) | ||
local extraCategories = queryUserExtrasCategories(type) | local extraCategories = nav.queryUserExtrasCategories(type) | ||
local categories = mergeCategories(baseCategories, extraCategories) | local categories = nav.mergeCategories(baseCategories, extraCategories) | ||
local sortedCategories = sortCategories(categories) | mw.logObject(categories) | ||
local sortedCategories = nav.sortCategories(categories) | |||
local rowsHtml = "" | local rowsHtml = "" | ||
Line 225: | Line 228: | ||
-- NavboxCategoryItems, NavRows, NavTableCategory, ... | -- NavboxCategoryItems, NavRows, NavTableCategory, ... | ||
return p | --- Build a navbox | ||
-- @param {table} frame current frame | |||
-- @param {string} frame.args.title Navtable title | |||
-- @param {string} frame.args.type from types above here, like Building or Item (any case) | |||
---@return string | |||
p.createNavBox = function(frame) | |||
local tableTitle = frame.args[1] | |||
local rawType = frame.args[2] | |||
if not tableTitle then | |||
return "(Navbox error: No tableTitle provided)" | |||
end | |||
if not rawType then | |||
return "(Navbox error: No type provided)" | |||
end | |||
return nav.createNavBox(frame, tableTitle, rawType) | |||
end | |||
return nav | |||
--[[ | --[[ | ||
Debugging: | |||
Return nav instead of p in the end to expose the internal function | |||
= p.createNavBox(frame, "Buildings", "Building") | |||
Resources: | Resources: | ||
https://www.mediawiki.org/wiki/Extension:Cargo/Querying_data | https://www.mediawiki.org/wiki/Extension:Cargo/Querying_data | ||
--]] | --]] |
Revision as of 01:34, 6 August 2025
Description
This module is about creating navboxes autopopulated with in game data, with game extra user customization on top of it.
For Buildings & Items: The generated table tries to mimic the game data & categories.
For Bots: Using custom sorting defined in this script.
For Components: ?
Tech is not covered by this module.
Extra "fake" categories or forced categorisation can be done by registering objects with the Template:NavCategory on the specific pages.
Usage
Usage:
{{#invoke:Navboxes|create|title=<NavboxTitle>|type=<navBoxType>}}
Where navBoxType is within NavboxType
values, see LUA source in this page. As of writing, valid values are: Unit, Building, Component, Item (Case insensitive)
Example
Buildings
{{#invoke:Navboxes|create|title=Buildings|type=building}}
Script error: The function "create" does not exist.
Items
{{#invoke:Navboxes|create|title=Items|type=item}}
Script error: The function "create" does not exist.
Bots
{{#invoke:Navboxes|create|title=Bots|type=bot}}
Script error: The function "create" does not exist.
Components
{{#invoke:Navboxes|create|title=Components|type=component}}
Script error: The function "create" does not exist.
--[[
This module is about creating navboxes autopopulated with in game data, with game extra user customization on top of it.
Table generated try to mimic the game data & categories.
Extra "fake" categories or forced categorisation can be done by registering objects with the NavCategory template on the specific pages
Usage:
There is only one public function here: createNavBox(<nav box title>, <navBoxType>)
Where navBoxType is within NavboxType values below.
--]]
local p = {}
local nav = {} -- you can use this one for debugging without exposing functions, return this instead of p
---@type any
mw = mw
local cargo = mw.ext.cargo
local CATEGORY_FILTER_TABLE = "categoryfilter"
local USER_NAV_CATEGORIES_TABLE = "userNavCategories"
---@class TypeData
---@comment Contains categoryfilter selectors for given type
---@field cargo_table string
---@field tab string
---@field filterField string
---@field recipeType string|nil
---@alias NavboxType "UNIT" | "BUILDING" | "COMPONENT" | "ITEM"
-- Possible types to create a navbox with
---@type table<NavboxType, TypeData>
local TYPES = {
UNIT = {
cargo_table = "entity",
tab = "frame",
filterField = "size",
recipeType = "Production"
},
BUILDING = {
cargo_table = "entity",
tab = "frame",
filterField = "size",
recipeType = "Construction",
},
COMPONENT = {
cargo_table = "component",
tab = "item",
filterField = "attachment_size",
recipeType = nil --unused
},
ITEM = {
cargo_table = "item",
tab = "item",
filterField = "tag",
recipeType = nil --unused
}
}
---@class CategoryData
---@field ordering number?
---@field names string[]
---@param typeData TypeData
---@return table<string, CategoryData>
nav.queryBaseCategories = function (typeData)
local categoriesMeta = cargo.query(
CATEGORY_FILTER_TABLE .. "=cat",
'name, filterVal, ordering',
{
where = string.format('tab="%s" AND filterField="%s"', typeData.tab, typeData.filterField),
groupBy = 'cat.filterVal'
}
)
local categories = {}
for _, cat in ipairs(categoriesMeta) do
local name, val, order = cat.name, cat.filterVal, cat.ordering
local wherePart = string.format('%s="%s"', typeData.filterField, val)
if typeData.recipeType then
wherePart = wherePart .. string.format(' AND recipeType="%s"', typeData.recipeType)
end
local foundObjects = cargo.query(
typeData.cargo_table,
'name',
{
where = wherePart,
}
)
local foundNames = {}
for _, row in ipairs(foundObjects) do
table.insert(foundNames, row.name)
end
categories[name] = { ordering = order, names = foundNames }
end
return categories
end
---@param type string
---@return table<string, CategoryData>
nav.queryUserExtrasCategories = function(type)
local userCategoriesRows = cargo.query(
USER_NAV_CATEGORIES_TABLE,
'category=catName, pagename',
{
where = string.format('UPPER(type) = "%s"', type)
}
)
local categories = {}
for _, row in ipairs(userCategoriesRows) do
local catName = row.catName;
if not categories[catName] then
categories[catName] = { ordering = 999, names = {} }
end
table.insert(categories[catName].names, row.pagename)
end
return categories
end
-- Names in extra have priority.
-- Does mutate base
---@param base table<string, CategoryData>
---@param extra table<string, CategoryData>
---@return table<string, CategoryData>
nav.mergeCategories = function (base, extra)
-- Step 1: Remove from base any name also found in extra
for _, extraCat in pairs(extra) do
for _, name in ipairs(extraCat.names) do
for _, baseCat in pairs(base) do
for i = #baseCat.names, 1, -1 do -- reverse loop to allow deletion while looping
if baseCat.names[i] == name then
table.remove(baseCat.names, i)
end
end
end
end
end
-- Step 2: Merge extra into base
for catName, extraCat in pairs(extra) do
if not base[catName] then
base[catName] = extraCat
else
local target = base[catName].names
for _, name in ipairs(extraCat.names) do
table.insert(target, name)
end
end
end
return base
end
---@class SortedCategoryEntry
---@field name string
---@field data CategoryData
---@param categories table<string, CategoryData>
---@return SortedCategoryEntry[]
nav.sortCategories = function(categories)
-- Convert map to array
local arr = {}
for name, data in pairs(categories) do
table.insert(arr, { name = name, data = data })
end
-- Sort by ordering (ascending)
table.sort(arr, function(a, b)
return (a.data.ordering) < (b.data.ordering)
end)
return arr
end
--- Build a navbox
-- @param {table} frame current frame
-- @param {string} frame.args.title Navtable title
-- @param {string} frame.args.type from types above here, like Building or Item (any case)
---@param tableTitle string Navtable title
---@param rawType string from types above here, like Building or Item (any case)
---@return string
nav.createNavBox = function(frame, tableTitle, rawType)
---@type NavboxType
local type = mw.ustring.upper(rawType or "")
local typeData = TYPES[type]
if not typeData then
return string.format("Navbox error: Unknown type: '%s'", type)
end
local baseCategories = nav.queryBaseCategories(typeData)
local extraCategories = nav.queryUserExtrasCategories(type)
local categories = nav.mergeCategories(baseCategories, extraCategories)
mw.logObject(categories)
local sortedCategories = nav.sortCategories(categories)
local rowsHtml = ""
for _, cat in pairs(sortedCategories) do
local objects = "" -- list of objects to insert in a row
for _, name in ipairs(cat.data.names) do
objects = objects .. frame:expandTemplate { title = "NavboxIconLink2", args = { name = name } }
end
-- Returns a table header + a table row
rowsHtml = rowsHtml .. frame:expandTemplate {
title = "NavTableCategory",
args = {
tableTitle = tableTitle,
catName = cat.name,
objects = objects
}
}
end
-- Final table
return frame:expandTemplate {
title = "NavTable",
args = {
title = tableTitle,
rows = rowsHtml
}
}
end
-- Cleanup existing templates after we're done, don't need the query ones, we only want the formatting ones
-- NavboxCategoryItems, NavRows, NavTableCategory, ...
--- Build a navbox
-- @param {table} frame current frame
-- @param {string} frame.args.title Navtable title
-- @param {string} frame.args.type from types above here, like Building or Item (any case)
---@return string
p.createNavBox = function(frame)
local tableTitle = frame.args[1]
local rawType = frame.args[2]
if not tableTitle then
return "(Navbox error: No tableTitle provided)"
end
if not rawType then
return "(Navbox error: No type provided)"
end
return nav.createNavBox(frame, tableTitle, rawType)
end
return nav
--[[
Debugging:
Return nav instead of p in the end to expose the internal function
= p.createNavBox(frame, "Buildings", "Building")
Resources:
https://www.mediawiki.org/wiki/Extension:Cargo/Querying_data
--]]