Module:Chemistry: Difference between revisions

From Space Station 14 Wiki
Aliser (talk | contribs)
No edit summary
Aliser (talk | contribs)
fix collapsible config unique counter not persisting between module calls
 
(10 intermediate revisions by the same user 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 Color = require("Module:Color")
local VariablesLua = mw.ext.VariablesLua


-- local reagents_table = mw.loadJsonData("Module:Chemistry/data/auto/reagents.json");
-- local reagents_table = mw.loadJsonData("Module:Chemistry/data/auto/reagents.json");
-- local reactions_table = mw.loadJsonData("Module:Chemistry/data/auto/reactions.json");
-- local reactions_table = mw.loadJsonData("Module:Chemistry/data/auto/reactions.json");
local reagent_card_templatestyles_src = "Template:Reagent card/styles.css"
-- ==============================
local are_template_styles_loaded = false


-- ==============================
-- ==============================
Line 149: Line 157:
-- ==============================
-- ==============================


-- Converts a hex color value to rgb, returning 3 components as separate values.
-- Creates a template styles node, loading styles from `src`.
-- source: https://gist.github.com/fernandohenriques/12661bf250c8c2d8047188222cab7e28
local function create_template_styles_node(frame, src)
function hex2rgb(hex)
     return frame:extensionTag("templatestyles", "", { src = src })
     local hex = hex:gsub("#","")
end
     if hex:len() == 3 then
 
      return (tonumber("0x"..hex:sub(1,1))*17)/255, (tonumber("0x"..hex:sub(2,2))*17)/255, (tonumber("0x"..hex:sub(3,3))*17)/255
local function ensure_frame(maybe_frame)
     if type(maybe_frame) == "table" and maybe_frame.expandTemplate then
        return maybe_frame -- valid frame
     else
     else
      return tonumber("0x"..hex:sub(1,2))/255, tonumber("0x"..hex:sub(3,4))/255, tonumber("0x"..hex:sub(5,6))/255
        return mw.getCurrentFrame()
     end
     end  
end
end


-- ==============================
-- ==============================
local collapsible_config_uniq_counter_var_name = "module-collapsible-config-uniq-counter"


-- Generates a config for custom collapsible elements using a unique ID.
-- Generates a config for custom collapsible elements using a unique ID.
Line 167: Line 179:
-- Returns 2 values: a class for a toggle element and an ID for a collapsible element.
-- Returns 2 values: a class for a toggle element and an ID for a collapsible element.
local function generate_collapsible_config(unique_id)
local function generate_collapsible_config(unique_id)
     return ("mw-customtoggle-" .. unique_id),  
    -- Ensures that in case of duplicates of collapsibles there would be basically no collisions.
     ("mw-customcollapsible-" .. unique_id)
    local unique_counter = VariablesLua.var(collapsible_config_uniq_counter_var_name, 0)
    unique_counter = VariablesLua.vardefineecho(collapsible_config_uniq_counter_var_name, unique_counter + 1)
 
     return ("mw-customtoggle-" .. unique_id .. "-" .. unique_counter),  
     ("mw-customcollapsible-" .. unique_id .. "-" .. unique_counter)
end
end


Line 174: Line 190:
-- Returns either `black` or `white`.
-- Returns either `black` or `white`.
-- Uses the same algorithm the game uses.
-- Uses the same algorithm the game uses.
local function calculate_reagent_header_text_color(hex)
local function calculate_reagent_header_text_color(col_obj)
     local r, g, b = hex2rgb(hex)
     local r, g, b = col_obj:rgba()
     return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.5
     return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.5
         and "black"
         and "black"
Line 181: Line 197:
end
end


-- Creates a collapsible section to use in reagent card.
-- @param label Label to use as a "header" for the section, as well as a collapse/show button.
-- @param parent_el Element to use as parent for node creation.
-- @param collapsible_id A unique ID to use to bind collapsible elements.
local function reagent_card_collapsible_section(label, parent_el, collapsible_id)
    local toggle_class, collapsible_id = generate_collapsible_config(collapsible_id)
    local label_el = parent_el:tag("p")
        :wikitext(label)
        :addClass("reagent-card-spoiler-section-label " .. toggle_class)
    local content_el = parent_el:tag("div")
        :attr("id", collapsible_id)
        :addClass("reagent-card-spoiler-section mw-collapsible mw-collapsed")
    return content_el
end
-- Creates a text section to use in reagent card.
-- @param parent_el Element to use as parent for node creation.
local function reagent_card_text_section(parent_el)
    return parent_el:tag("p")
        :addClass("reagent-card-text-section")
end


local function auto_reagent_card(frame, args)
local function auto_reagent_card(frame, args)
Line 205: Line 244:


     if product_id then
     if product_id then
         card_container_el:attr("id", "reagent-" .. product_id)
         card_container_el:attr("id", "chem_" .. product_id)
     end
     end


Line 215: Line 254:


     if color then
     if color then
         header_el:css("background-color", color);
         local success, col_obj = Color.tryColor(color)
        header_label_el:css("color", calculate_reagent_header_text_color(color))
        if success then
            header_el:css("background-color", color);
            header_label_el:css("color", calculate_reagent_header_text_color(col_obj))
        end
     end
     end


     local id_for_all_collapsibles = product_id or product_name
     local collapsible_id_base = product_id or product_name


    if sources and #sources > 0 then
        local section = reagent_card_collapsible_section("Sources", card_container_el, collapsible_id_base .. "-sources")
        if type(sources) == "string" then section:wikitext(sources) else section:node(sources) end
    end
   
     if recipes and #recipes > 0 then
     if recipes and #recipes > 0 then
         local toggle_class, collapsible_id = generate_collapsible_config(id_for_all_collapsibles .. "-recipes")
         local section = reagent_card_collapsible_section("Recipe", card_container_el, collapsible_id_base .. "-recipes")
         local label = card_container_el:tag("p")
         if type(recipes) == "string" then section:wikitext(recipes) else section:node(recipes) end
            :wikitext("Recipe")
            :addClass("reagent-card-spoiler-section-label " .. toggle_class)
 
        local recipes_el = card_container_el:tag("div")
            :attr("id", collapsible_id)
            :addClass("reagent-card-spoiler-section mw-collapsible mw-collapsed " .. toggle_class)
 
        recipes_el:wikitext(recipes) -- todo make proper
     end
     end
 
   
     if effects and #effects > 0 then
     if effects and #effects > 0 then
         local toggle_class, collapsible_id = generate_collapsible_config(id_for_all_collapsibles .. "-effects")
         local section = reagent_card_collapsible_section("Effects", card_container_el, collapsible_id_base .. "-effects")
         local label = card_container_el:tag("p")
         if type(effects) == "string" then section:wikitext(effects) else section:node(effects) end
            :wikitext("Effects")
    end
            :addClass("reagent-card-spoiler-section-label " .. toggle_class)
           
        local effects_el = card_container_el:tag("div")
            :attr("id", collapsible_id)
            :addClass("reagent-card-spoiler-section mw-collapsible mw-collapsed " .. toggle_class)


 
    if plant_effects and #plant_effects > 0 then
         effects_el:wikitext(effects) -- todo make proper
         local section = reagent_card_collapsible_section("Plant Metabolism", card_container_el, collapsible_id_base .. "-plant-effects")
        if type(plant_effects) == "string" then section:wikitext(plant_effects) else section:node(plant_effects) end
     end
     end


     if description then
     if description then
         local description_el = card_container_el:tag("p")
         local section = reagent_card_text_section(card_container_el)
            :addClass("reagent-card-text-section")
             :wikitext(description)
             :wikitext(description)
     end
     end


     if physical_description then
     if physical_description then
         local phys_description_el = card_container_el:tag("p")
         local section = reagent_card_text_section(card_container_el)
            :addClass("reagent-card-text-section cursive")
:addClass("cursive")
             :wikitext("Seems to be " .. physical_description .. ".")
             :wikitext("Seems to be " .. physical_description .. ".")
    end
    if not are_template_styles_loaded then
        card_container_el:node(create_template_styles_node(frame, reagent_card_templatestyles_src))
        are_template_styles_loaded = true
     end
     end


Line 282: Line 321:
function p.reagent_card(frame)
function p.reagent_card(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
    frame = ensure_frame(frame)
     if args[1] == nil then
     if args[1] == nil then
         return manual_reagent_card(frame, args)
         return manual_reagent_card(frame, args)
Line 297: Line 338:
function p.reaction_card(frame)
function p.reaction_card(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
    frame = ensure_frame(frame)
     if args[1] == nil then
     if args[1] == nil then
         return manual_reaction_card(frame, args)
         return manual_reaction_card(frame, args)

Latest revision as of 11:50, 29 May 2025

Module documentation
View or edit this documentation (about module documentation)
Unfinished page!

This page or template is a work in progress. If you wish to make changes, make sure the page is not currently being worked on by using History tab.


local p = {} --p stands for package
local getArgs = require("Module:Arguments").getArgs
local yesNo = require("Module:Yesno")
local Color = require("Module:Color")
local VariablesLua = mw.ext.VariablesLua

-- local reagents_table = mw.loadJsonData("Module:Chemistry/data/auto/reagents.json");
-- local reactions_table = mw.loadJsonData("Module:Chemistry/data/auto/reactions.json");

local reagent_card_templatestyles_src = "Template:Reagent card/styles.css"

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

local are_template_styles_loaded = false

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

local function assert_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

local function starts_with(str, substr)
    return string.sub(str, 1, string.len(substr)) == substr
end

local function ends_with(str, substr)
    local substr_length = string.len(substr)
    return string.sub(str, string.len(str) - substr_length + 1, string.len(str)) == substr
end

local function starts_with_insensitive(str, substr)
    return starts_with(string.lower(str), string.lower(substr))
end

local function ends_with_insensitive(str, substr)
    return ends_with(string.lower(str), string.lower(substr))
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

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_find(tbl, findFn)
    for k, v in pairs(tbl) do
        if findFn(v, k, tbl) then return v end
    end
end

local function table_contains(tbl, findFn)
    return table_find(tbl, findFn) ~= nil
end

local function table_contains_value(tbl, value)
    return table_find(tbl, function(iter_value)
        return iter_value == value
    end) ~= nil
end

local function table_find_key(tbl, findFn)
    for k, v in pairs(tbl) do
        if findFn(v, k, tbl) then return k end
    end
end

local function table_find_index(tbl, findFn)
    for i, v in ipairs(tbl) do
        if findFn(v, i, tbl) then return i 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 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


-- Returns keys of a table.
local function table_keys(tbl)
    local arr = {}
    for key, _ in pairs(tbl) do
        table.insert(arr, key)
    end
    return arr
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

-- Given a table and a property key, attempts to retrieve said property.
-- If property does not exists, creates it with by assigning a value from `create_value_fn`.
-- The created value is then returned.
function get_table_prop_or_create(tbl, prop_key, create_value_fn)
    local value = tbl[prop_key]
    if value == nil then
        value = create_value_fn()
        tbl[prop_key] = value
    end

    return value;
end

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

-- Creates a template styles node, loading styles from `src`.
local function create_template_styles_node(frame, src)
    return frame:extensionTag("templatestyles", "", { src = src })
end

local function ensure_frame(maybe_frame)
    if type(maybe_frame) == "table" and maybe_frame.expandTemplate then
        return maybe_frame -- valid frame
    else
        return mw.getCurrentFrame()
    end 
end

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

local collapsible_config_uniq_counter_var_name = "module-collapsible-config-uniq-counter"

-- Generates a config for custom collapsible elements using a unique ID.
-- The ID must be unique for the current page.
-- 
-- Returns 2 values: a class for a toggle element and an ID for a collapsible element.
local function generate_collapsible_config(unique_id)
    -- Ensures that in case of duplicates of collapsibles there would be basically no collisions.
    local unique_counter = VariablesLua.var(collapsible_config_uniq_counter_var_name, 0)
    unique_counter = VariablesLua.vardefineecho(collapsible_config_uniq_counter_var_name, unique_counter + 1)

    return ("mw-customtoggle-" .. unique_id .. "-" .. unique_counter), 
    ("mw-customcollapsible-" .. unique_id .. "-" .. unique_counter)
end

-- Calculates text color of a reagent header based on its background color.
-- Returns either `black` or `white`.
-- Uses the same algorithm the game uses.
local function calculate_reagent_header_text_color(col_obj)
    local r, g, b = col_obj:rgba()
    return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.5
        and "black"
        or "white"
end

-- Creates a collapsible section to use in reagent card.
-- @param label Label to use as a "header" for the section, as well as a collapse/show button.
-- @param parent_el Element to use as parent for node creation.
-- @param collapsible_id A unique ID to use to bind collapsible elements.
local function reagent_card_collapsible_section(label, parent_el, collapsible_id)
    local toggle_class, collapsible_id = generate_collapsible_config(collapsible_id)
    local label_el = parent_el:tag("p")
        :wikitext(label)
        :addClass("reagent-card-spoiler-section-label " .. toggle_class)

    local content_el = parent_el:tag("div")
        :attr("id", collapsible_id)
        :addClass("reagent-card-spoiler-section mw-collapsible mw-collapsed")

    return content_el
end

-- Creates a text section to use in reagent card.
-- @param parent_el Element to use as parent for node creation.
local function reagent_card_text_section(parent_el)
    return parent_el:tag("p")
        :addClass("reagent-card-text-section")
end

local function auto_reagent_card(frame, args)
    error("not impl")
end

local function manual_reagent_card(frame, args)
    local product_name = args.name
    assert_not_nil(product_name, "failed to generate manual reagent card: product name not provided")

    local product_id = args.id
    local color = args.color
    local recipes = args.recipe or args.recipes
    local description = args.desc
    local physical_description = args.physicalDesc
    local effects = args.effect or args.effects or args.metabolisms
    local plant_effects = args.plantMetabolism or args.plantMetabolisms -- todo; label spelled "Plant Metabolism"
    local sources = args.source or args.sources -- todo; label spelled "Sources"

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

    local card_container_el = mw.html.create("div")
        :addClass("reagent-card")

    if product_id then
        card_container_el:attr("id", "chem_" .. product_id)
    end

    local header_el = card_container_el:tag("p")
        :addClass("reagent-card-header")

    local header_label_el = header_el:tag("span")
        :wikitext(product_name)

    if color then
        local success, col_obj = Color.tryColor(color)
        if success then
            header_el:css("background-color", color);
            header_label_el:css("color", calculate_reagent_header_text_color(col_obj))
        end
    end

    local collapsible_id_base = product_id or product_name

    if sources and #sources > 0 then
        local section = reagent_card_collapsible_section("Sources", card_container_el, collapsible_id_base .. "-sources")
        if type(sources) == "string" then section:wikitext(sources) else section:node(sources) end
    end
    
    if recipes and #recipes > 0 then
        local section = reagent_card_collapsible_section("Recipe", card_container_el, collapsible_id_base .. "-recipes")
        if type(recipes) == "string" then section:wikitext(recipes) else section:node(recipes) end
    end
    
    if effects and #effects > 0 then
        local section = reagent_card_collapsible_section("Effects", card_container_el, collapsible_id_base .. "-effects")
        if type(effects) == "string" then section:wikitext(effects) else section:node(effects) end
    end

    if plant_effects and #plant_effects > 0 then
        local section = reagent_card_collapsible_section("Plant Metabolism", card_container_el, collapsible_id_base .. "-plant-effects")
        if type(plant_effects) == "string" then section:wikitext(plant_effects) else section:node(plant_effects) end
    end

    if description then
        local section = reagent_card_text_section(card_container_el)
            :wikitext(description)
    end

    if physical_description then
        local section = reagent_card_text_section(card_container_el)
			:addClass("cursive")
            :wikitext("Seems to be " .. physical_description .. ".")
    end

    if not are_template_styles_loaded then
        card_container_el:node(create_template_styles_node(frame, reagent_card_templatestyles_src))
        are_template_styles_loaded = true
    end

    return card_container_el
end

local function auto_reaction_card(frame, args)
    error("not impl")
end

local function manual_reaction_card(frame, args)
    error("not impl")
end


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

-- Generates a reagent card element, containing
-- info about a reagent such as description, recipes, effects, etc.
-- 
-- Has 2 modes depending on what is passed:
-- - Automatic - when first argument is a reagent ID. This triggers automatic data lookup.
-- - Manual - when there are only named arguments. This allows to fill every parameter manually.
function p.reagent_card(frame)
    local args = getArgs(frame)
    frame = ensure_frame(frame)

    if args[1] == nil then
        return manual_reagent_card(frame, args)
    else
        return auto_reagent_card(frame, args)
    end
end

-- Generates a reaction card element, containing
-- info about a chemical reaction.
-- 
-- Has 2 modes depending on what is passed:
-- - Automatic - when first argument is a reagent ID. This triggers automatic data lookup.
-- - Manual - when there are only named arguments. This allows to fill every parameter manually.
function p.reaction_card(frame)
    local args = getArgs(frame)
    frame = ensure_frame(frame)

    if args[1] == nil then
        return manual_reaction_card(frame, args)
    else
        return auto_reaction_card(frame, args)
    end
end

return p