Module:Chem box: Difference between revisions

From Space Station 14 Wiki
Aliser (talk | contribs)
a bit more legible header shadow colors
Aliser (talk | contribs)
preprocess effects (allows template usage inside effects)
 
(5 intermediate revisions by 2 users not shown)
Line 2: Line 2:
local getArgs = require("Module:Arguments").getArgs
local getArgs = require("Module:Arguments").getArgs
local yesNo = require("Module:Yesno")
local yesNo = require("Module:Yesno")
local reagent_card = require('Module:Chemistry').reagent_card


local chem_data = mw.loadJsonData("Module:Chem box/chem data.json")
local chem_data = mw.loadJsonData("Module:Chem box/chem data.json")
Line 83: Line 84:
return valueIfFalse
return valueIfFalse
end
end
end
local function table_map(tbl, mapFn)
    local res = {}
    for k, v in pairs(tbl) do
        table.insert(res, mapFn(v, k, tbl))
    end
    return res
end
local function num_table_reduce(tbl, reduceFn, initial_value)
    local out = initial_value
    for i, v in ipairs(tbl) do
        out = reduceFn(out, v, i, tbl)
    end
    return out
end
local function table_filter(tbl, filterFn)
    local out = {}
    for k, v in pairs(tbl) do
        if filterFn(v, k, tbl) then out[k] = v end
    end
    return out
end
-- Sorts a table into a new table.
-- An alternative to cases such as when table.sort is not working,
-- like when trying to sort JSON tables (thanks fuckass lua).
local function table_to_sorted(tbl, sortfn)
    local keys = {}
    for key, _ in pairs(tbl) do
        table.insert(keys, key)
    end
    table.sort(keys, function(keyA, keyB) return sortfn(tbl[keyA], tbl[keyB]) end)
    local t2 = {}
    for _, key in ipairs(keys) do
        table.insert(t2, tbl[key])
    end
    return t2
end
end


Line 107: Line 158:


return passthrough_assert_true(no_error, nil, "failed to lookup reagent: no match was found")
return passthrough_assert_true(no_error, nil, "failed to lookup reagent: no match was found")
end
local function get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order, a, b)
return function (a, b)
return map_of_reagent_name_or_id_to_order[string.lower(a.name or a.id)]
< map_of_reagent_name_or_id_to_order[string.lower(b.name or b.id)]
end
end
-- Sorts an array of reagents in place by name (or ID if no name), a to z.
-- @param reagents Reagents to sort.
-- @param to_sorted Whether to create a new sorted table instead of sorting in place.
local function sort_reagents_az(reagents, to_sorted)
local reagents_names_or_ids = table_map(reagents, function (reagent)
return string.lower(reagent.name or reagent.id)
end)
table.sort(reagents_names_or_ids)
local map_of_reagent_name_or_id_to_order = num_table_reduce(reagents_names_or_ids, function (accum, value, i)
accum[value] = i
return accum
end, {})
if to_sorted then
return table_to_sorted(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order))
else
table.sort(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order))
end
end
end


Line 147: Line 227:


for reactant_i, reactant in ipairs(recipe.reactants) do
for reactant_i, reactant in ipairs(recipe.reactants) do
local reactant_id = capitalize(reactant.name)
local reactant_name = capitalize(reactant.name)
local reactant_amount = reactant.count
local reactant_amount = reactant.count
local reactant_is_catalyst = reactant.isCatalyst or false
local reactant_is_catalyst = reactant.isCatalyst or false


local label = reactant_id
local label = reactant_name
local link = reactant.id


if reactant_is_catalyst then
if reactant_is_catalyst then
table.insert(catalyst_reactants, {
table.insert(catalyst_reactants, {
name = reactant_name,
id = reactant_id,
id = reactant_id,
amount = reactant_amount,
amount = reactant_amount,
Line 165: Line 247:
item = label,
item = label,
amount = reactant_amount,
amount = reactant_amount,
itemlink = link
-- TODO
-- TODO
-- image = ""
-- image = ""
Line 210: Line 293:
-- add catalysts as products, if any
-- add catalysts as products, if any
for catalyst_product_i, catalyst_product_entry in ipairs(catalyst_reactants) do
for catalyst_product_i, catalyst_product_entry in ipairs(catalyst_reactants) do
local product_id = catalyst_product_entry.id
local product_name = catalyst_product_entry.name
local product_amount = catalyst_product_entry.amount
local product_amount = catalyst_product_entry.amount


Line 219: Line 302:
'<span class="quickbox-catalyst-note-tooltip">This product is a catalyst reactant, remaining after the reaction.</span>',
'<span class="quickbox-catalyst-note-tooltip">This product is a catalyst reactant, remaining after the reaction.</span>',
},
},
}) .. product_id
}) .. product_name


local component_el = current_frame:expandTemplate({
local component_el = current_frame:expandTemplate({
Line 244: Line 327:
end
end


local effects = concat_numberic_table(reagent.effectStrings, "")
local effects = current_frame:preprocess(concat_numberic_table(reagent.effectStrings, ""))


local text_color = ternary_strict(
return reagent_card {
reagent.textColorTheme == "light",
id = reagent.id,
"var(--quickbox-header-text-theme-light)",
color = reagent.color,
"var(--quickbox-header-text-theme-dark)"
name = capitalize(reagent.name),
)
recipes = tostring(recipes_container_el:allDone()),
metabolisms = effects,
-- inverse of text color
desc = reagent.description,
local text_outline_color = ternary_strict(
physicalDesc = reagent.physicalDescription,
reagent.textColorTheme == "light",
"hsl(0deg 0% 0% / 57%)",
"hsl(0deg 0% 100% / 26%)"
)


return current_frame:expandTemplate({
}
title = "Manual Chem Box",
args = {
id = reagent.id,
color = reagent.color,
textcolor = text_color,
textcolor_outline = text_outline_color,
name = capitalize(reagent.name),
recipes = tostring(recipes_container_el:allDone()),
metabolisms = effects,
desc = reagent.description,
physicalDesc = reagent.physicalDescription,
},
})
end
end


Line 278: Line 344:
local container_el = mw.html.create("div"):addClass("reagents-list")
local container_el = mw.html.create("div"):addClass("reagents-list")


for _, reagent in ipairs(chem_data) do
-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort
local reagents_sorted = sort_reagents_az(chem_data, true)
 
for _, reagent in ipairs(reagents_sorted) do
assert_value_not_nil(reagent.id)
assert_value_not_nil(reagent.id)


Line 293: Line 362:
local query = args[1]
local query = args[1]
assert_value_not_nil(query)
assert_value_not_nil(query)
query = string.lower(query)
-- ========
local query_lc = string.lower(query)
local container_el = mw.html.create("div"):addClass("reagents-list")
local container_el = mw.html.create("div"):addClass("reagents-list")


for _, reagent in ipairs(chem_data) do
local reagents = table_filter(chem_data, function (reagent)
assert_value_not_nil(reagent.id)
return reagent.group  
assert_value_not_nil(reagent.group)
and string.lower(reagent.group) == query_lc
if string.lower(reagent.group) == query then
end)
container_el:node(p.generate_chem_box({
-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort,
reagent.id,
-- even when filtered smh
}))
reagents = sort_reagents_az(reagents, true)
end
 
for _, reagent in ipairs(reagents) do
container_el:node(p.generate_chem_box({
reagent.id,
}))
end
end



Latest revision as of 15:53, 28 May 2025

Module documentation
View or edit this documentation (about module documentation)
Uses JSON data
This module uses JSON data pages:

Implements {{chem box}}.


local p = {} --p stands for package
local getArgs = require("Module:Arguments").getArgs
local yesNo = require("Module:Yesno")
local reagent_card = require('Module:Chemistry').reagent_card

local chem_data = mw.loadJsonData("Module:Chem box/chem data.json")

local current_frame = mw.getCurrentFrame()

-- ====================

local function numeric_table_length(t)
	local count = 0
	for _ in ipairs(t) do
		count = count + 1
	end
	return count
end

local function table_length(t)
	local count = 0
	for _ in pairs(t) do
		count = count + 1
	end
	return count
end

local function table_has_value(tab, val)
	for _, value in ipairs(tab) do
		if value == val then
			return true
		end
	end

	return false
end

local function assert_value_not_nil(value, error_message)
	if value == nil then
		if error_message == nil then
			error("value is nil")
		else
			error(error_message)
		end
	end
end

-- Makes the first letter uppercase.
-- Source: https://stackoverflow.com/a/2421746
local function capitalize(str)
	return (str:gsub("^%l", string.upper))
end

local function passthrough_assert_true(value, valueToReturnIfTrue, errorMessageOnFalse)
	if value then
		return valueToReturnIfTrue
	else
		error(errorMessageOnFalse)
	end
end

local function find_first_numeric_table_item_matching_condition(table, condition)
	for i, item in ipairs(table) do
		if condition(item, i, table) then
			return item
		end
	end
end

-- table concat function because ofcourse fucking table.concat doesn't work
local function concat_numberic_table(tbl, sep)
	local temp_table = {}
	for i = 1, numeric_table_length(tbl) do
		table.insert(temp_table, tbl[i])
	end

	return table.concat(temp_table, sep)
end

local function ternary_strict(valueToCheck, valueIfTrue, valueIfFalse)
	if valueToCheck == true then
		return valueIfTrue
	else
		return valueIfFalse
	end
end

local function table_map(tbl, mapFn)
    local res = {}
    for k, v in pairs(tbl) do
        table.insert(res, mapFn(v, k, tbl))
    end
    return res
end

local function num_table_reduce(tbl, reduceFn, initial_value)
    local out = initial_value

    for i, v in ipairs(tbl) do
        out = reduceFn(out, v, i, tbl)
    end

    return out
end

local function table_filter(tbl, filterFn)
    local out = {}

    for k, v in pairs(tbl) do
        if filterFn(v, k, tbl) then out[k] = v end
    end

    return out
end


-- Sorts a table into a new table.
-- An alternative to cases such as when table.sort is not working,
-- like when trying to sort JSON tables (thanks fuckass lua).
local function table_to_sorted(tbl, sortfn)
    local keys = {}

    for key, _ in pairs(tbl) do
        table.insert(keys, key)
    end

    table.sort(keys, function(keyA, keyB) return sortfn(tbl[keyA], tbl[keyB]) end)

    local t2 = {}

    for _, key in ipairs(keys) do
        table.insert(t2, tbl[key])
    end

    return t2
end

-- ====================

-- Lookups reagent by ID or name.
--
-- Raises an error if no reagent was found.
-- Set `no_error` to `true` to return `nil` instead.
function p.lookup_reagent(query, no_error)
	local query_lower = string.lower(query)

	for _, reagent in ipairs(chem_data) do
		assert_value_not_nil(reagent.id)
		assert_value_not_nil(reagent.name)

		if reagent.id == query then
			return reagent
		end
		if string.lower(reagent.name) == query_lower then
			return reagent
		end
	end

	return passthrough_assert_true(no_error, nil, "failed to lookup reagent: no match was found")
end

local function get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order, a, b)
	return function (a, b)
		return map_of_reagent_name_or_id_to_order[string.lower(a.name or a.id)]
			< map_of_reagent_name_or_id_to_order[string.lower(b.name or b.id)]
	end 
end

-- Sorts an array of reagents in place by name (or ID if no name), a to z.
-- @param reagents Reagents to sort.
-- @param to_sorted Whether to create a new sorted table instead of sorting in place.
local function sort_reagents_az(reagents, to_sorted)
	local reagents_names_or_ids = table_map(reagents, function (reagent)
		return string.lower(reagent.name or reagent.id)
	end)
	table.sort(reagents_names_or_ids)

	local map_of_reagent_name_or_id_to_order = num_table_reduce(reagents_names_or_ids, function (accum, value, i)
		accum[value] = i

		return accum
	end, {})

	if to_sorted then
		return table_to_sorted(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order))
	else
		table.sort(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order))
	end
end

-- ====================

function p.generate_chem_box(frame)
	local args = getArgs(frame)

	local query = args[1]
	assert_value_not_nil(query, "failed to generate chem box: query was not provided")

	-- ===================

	local reagent = p.lookup_reagent(query)
	assert_value_not_nil(reagent.color)
	assert_value_not_nil(reagent.name)
	assert_value_not_nil(reagent.recipes)
	assert_value_not_nil(reagent.effectStrings)
	assert_value_not_nil(reagent.description)
	assert_value_not_nil(reagent.physicalDescription)
	assert_value_not_nil(reagent.textColorTheme)

	local recipes_container_el = mw.html.create("div")
	if numeric_table_length(reagent.recipes) == 0 then
		local no_recipes_text_el = mw.html.create("span"):css("color", "gray"):node("No recipes")

		recipes_container_el:node(no_recipes_text_el)
	else
		for _, recipe in ipairs(reagent.recipes) do
			assert_value_not_nil(recipe.id)
			assert_value_not_nil(recipe.reactants)
			assert_value_not_nil(recipe.products)

			local recipe_box_template_args = {}
			recipe_box_template_args.name = recipe.id

			-- a list of catalist reactants.
			-- contains "id" and "amount"
			local catalyst_reactants = {}

			for reactant_i, reactant in ipairs(recipe.reactants) do
				local reactant_name = capitalize(reactant.name)
				local reactant_amount = reactant.count
				local reactant_is_catalyst = reactant.isCatalyst or false

				local label = reactant_name
				local link = reactant.id

				if reactant_is_catalyst then
					table.insert(catalyst_reactants, {
						name = reactant_name,
						id = reactant_id,
						amount = reactant_amount,
					})
				end

				local component_el = current_frame:expandTemplate({
					title = "Recipe Component",
					args = {
						item = label,
						amount = reactant_amount,
						itemlink = link
						-- TODO
						-- image = ""
					},
				})

				recipe_box_template_args["component-" .. reactant_i] = component_el
			end

			local temperature_args = {}
			if recipe.minTemp ~= nil or recipe.maxTemp ~= nil then
				local temperature_string = ""
				if recipe.minTemp ~= nil then
					temperature_string = tostring(recipe.minTemp) .. "k "
				end
				temperature_string = temperature_string .. "<"
				if recipe.maxTemp ~= nil then
					temperature_string = temperature_string .. " " .. tostring(recipe.maxTemp) .. "k"
				end
				temperature_args = {temperature = temperature_string}
			end
			local required_mixer = "Beaker"
			if recipe.requiredMixerCategories ~= nil then
				required_mixer = recipe.requiredMixerCategories[1]
			end
			recipe_box_template_args.transformer = current_frame:expandTemplate({
				title = required_mixer,
				args = temperature_args
			})

			for product_i, product in ipairs(recipe.products) do
				local component_el = current_frame:expandTemplate({
					title = "Recipe Component",
					args = {
						item = capitalize(product.name),
						amount = product.count,
						-- TODO
						-- image = ""
					},
				})

				recipe_box_template_args["result-" .. product_i] = component_el
			end

			-- add catalysts as products, if any
			for catalyst_product_i, catalyst_product_entry in ipairs(catalyst_reactants) do
				local product_name = catalyst_product_entry.name
				local product_amount = catalyst_product_entry.amount

				local label = current_frame:expandTemplate({
					title = "Tooltip",
					args = {
						'<sup class="quickbox-catalyst-note">+</sup>',
						'<span class="quickbox-catalyst-note-tooltip">This product is a catalyst reactant, remaining after the reaction.</span>',
					},
				}) .. product_name

				local component_el = current_frame:expandTemplate({
					title = "Recipe Component",
					args = {
						item = label,
						amount = product_amount,
						-- TODO
						-- image = ""
					},
				})

				local index = numeric_table_length(recipe.products) + catalyst_product_i
				recipe_box_template_args["result-" .. index] = component_el
			end

			local recipes_el = current_frame:expandTemplate({
				title = "Recipe Box",
				args = recipe_box_template_args,
			})

			recipes_container_el:node(recipes_el)
		end
	end

	local effects = current_frame:preprocess(concat_numberic_table(reagent.effectStrings, ""))

	return reagent_card {
		id = reagent.id,
		color = reagent.color,
		name = capitalize(reagent.name),
		recipes = tostring(recipes_container_el:allDone()),
		metabolisms = effects,
		desc = reagent.description,
		physicalDesc = reagent.physicalDescription,

	}
end

function p.generate_chem_boxes_for_all_reagents()
	local container_el = mw.html.create("div"):addClass("reagents-list")

	-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort
	local reagents_sorted = sort_reagents_az(chem_data, true)

	for _, reagent in ipairs(reagents_sorted) do
		assert_value_not_nil(reagent.id)

		container_el:node(p.generate_chem_box({
			reagent.id,
		}))
	end

	return container_el:allDone()
end

function p.generate_chem_boxes_for_group(frame)
	local args = getArgs(frame)
	local query = args[1]
	assert_value_not_nil(query)
	
	-- ========
	
	local query_lc = string.lower(query)
	local container_el = mw.html.create("div"):addClass("reagents-list")

	local reagents = table_filter(chem_data, function (reagent)
		return reagent.group 
			and string.lower(reagent.group) == query_lc
	end)
	-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort,
	-- even when filtered smh
	reagents = sort_reagents_az(reagents, true)

	for _, reagent in ipairs(reagents) do
		container_el:node(p.generate_chem_box({
			reagent.id,
		}))
	end

	return container_el:allDone()
end

return p