Module:ProtoFlux: Difference between revisions

From Resonite Wiki
add resumptions
Make code more readable
Line 3: Line 3:
local p = {}
local p = {}


local color_crc_module = require("Module:ProtoFlux_Type_Color")
local ProtofluxColor = require("Module:ProtoFlux_Type_Color")


--- Method that generates the HTML output
--- Method that generates the HTML output
Line 64: Line 64:
-- Return the HTML generated above to the wiki page this script is invoked from.
-- Return the HTML generated above to the wiki page this script is invoked from.
return tostring(protofluxContainer) .. '[[Category:ProtoFlux:All]]'
return tostring(protofluxContainer) .. '[[Category:ProtoFlux:All]]'
end
local css = {
connector_row = "display: flex; min-height: 70px;",
connector_row_labels = "flex-grow: 2; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; order: 2;",
multi_button = "font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content: center;",
input_label = function(color)
return string.format(
"text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px; "
.."border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; "
.."background-color: %s;",
color and ColorToCss(MultA(color, 0.6)) or "transparent"
)
end,
output_label = function(color)
return string.format(
"text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; "
.."border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; "
.."background-color: %s;",
color and ColorToCss(MultA(color, 0.6)) or "transparent"
)
end,
attachment_point = function(color, order)
return string.format(
"width: 30px; background-color: black; fill: %s; stroke: %s; order: %s;",
ColorToCss(MultA(color, 0.3)),
ColorToCss(color),
order
)
end,
global = function(color)
return string.format(
"display: flex; min-height: 70px; flex-direction: column; border-left: 10px solid %s;",
ColorToCss(color)
)
end,
}
--- Creates a text which should be shown when hovering over a connector
-- @param Input Table containing a Name (optional) and Type for the input on this row.
function connector_hover_text(connector)
if connector.Name == nil then
return ("<%s>"):format(connector.Type)
end
return ("%s <%s>"):format(connector.Name, connector.Type)
end
end


--- Creates a new ProtoFlux connector row in the output
--- Creates a new ProtoFlux connector row in the output
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Input Table containing a Name and Type for the input on this row. Can be nil if no input is to be placed on this row.
-- @param input Table containing a Name and Type for the input on this row. Can be nil if no input is to be placed on this row.
-- @param Output Table containing a Name and Type for the output on this row. Can be nil if no output is to be placed on this row.
-- @param output Table containing a Name and Type for the output on this row. Can be nil if no output is to be placed on this row.
function CreateConnectorRow(Container, Input, Output)
function CreateConnectorRow(container, input, output)
local connectorRow = Container
local connector_row = container
:tag('div') -- HTML div to contain the connector row. We specify a min-height of 70px for consistency.
:tag('div') -- HTML div to contain the connector row. We specify a min-height of 70px for consistency.
:cssText('display: flex; min-height: 70px;')
:cssText(css.connector_row)
-- Create the input (left) attachement point
CreateConnectorAttachmentPoint(connectorRow, Input, true)


local c = connectorRow
local connector_row_labels = connector_row
:tag('div') -- HTML div to contain the input and output labels in the connector row
:tag('div') -- HTML div to contain the input and output labels in the connector row
:cssText( 'flex-grow: 2; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between;')
:cssText(css.connector_row_labels)
if Input and Input.Multi and Input.MultiIndex == 1 then
 
c
if input then
:tag('div') -- HTML div to contain the input label
local color, is_impulse = GetColor(output.Type)
:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
local hover_text = connector_hover_text(connector)
:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;  border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
 
:wikitext(Input and Input.Name or '' )
if input.Multi and input.MultiIndex == 1 then
:done() -- Close input label div
connector_row_labels
elseif Input and Input.Multi and Input.MultiIndex > 1 then
:tag('div') -- HTML div to contain the input label
local c2 = c
:attr('title', hover_text) -- Add a basic mouseover description
:tag('div') -- HTML div to contain the input label
:cssText(css.input_label())
:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
:wikitext(input.Name or "")
:cssText('text-align: left; padding-left: 4px;  border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
:done() -- Close input label div
if Input.Multi == Input.MultiIndex then
elseif Input and Input.Multi and Input.MultiIndex > 1 then
c2
local label = connector_row_labels
:tag('div')
:tag('div') -- HTML div to contain the input label
:cssText('position:relative; top:2rem; display:flex; gap: 4px;')
:attr('title', hover_text) -- Add a basic mouseover description
:tag('div')
:cssText(css.input_label())
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
if Input.Multi == Input.MultiIndex then
:wikitext('+')
label
:done()
:tag('div')
:tag('div')
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
:cssText('position:relative; top:2rem; display: flex; gap: 4px;')
:wikitext('-')
:tag('div')
:done()
:cssText(css.multi_button)
:wikitext('+')
:done()
:tag('div')
:cssText(css.multi_button)
:wikitext('-')
:done()
end
else
connector_row_labels
:tag('div') -- HTML div to contain the input label
:attr('title', hover_text) -- Add a basic mouseover description
:cssText(css.input_label(color))
:wikitext(input.Name or '')
:done() -- Close input label div
end
end
else
 
c
-- Create the input (left) attachement point
:tag('div') -- HTML div to contain the input label
CreateConnectorAttachmentPoint(connectorRow, hover_text, color, is_impulse, true)
:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
end -- if input
:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;  border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color:' .. GetTypeColor(Input, 0.6) .. ';')
 
:wikitext(Input and Input.Name or '' )
if output then
:done() -- Close input label div
local color, is_impulse = GetColor(output.Type)
end
local hover_text = connector_hover_text(connector)
if Output and Output.Multi and Output.MultiIndex == 1 then
 
c
if output.Multi and output.MultiIndex == 1 then
:tag('div') -- HTML div to contain the output label
connector_row_labels
:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
:tag('div') -- HTML div to contain the output label
:cssText('text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
:attr('title', hover_text) -- Add a basic mouseover description
:wikitext(Output and Output.Name or '')
:cssText(css.output_label())
:done() -- Close output label div
:wikitext(output.Name or '')
elseif Output and Output.Multi and Output.MultiIndex > 1 then
:done() -- Close output label div
local c2 = c
elseif output.Multi and output.MultiIndex > 1 then
:tag('div') -- HTML div to contain the output label
local label = connector_row_labels
:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
:tag('div') -- HTML div to contain the output label
:cssText('text-align: right; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;;')
:attr('title', hover_text) -- Add a basic mouseover description
if Output.Multi == Output.MultiIndex then
:cssText(css.output_label())
c2
if output.Multi == output.MultiIndex then
:tag('div')
label
:cssText('display:flex; justify-content:flex-end; gap: 4px;')
:tag('div')
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
:wikitext('+')
:done()
:tag('div')
:tag('div')
:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
:cssText('display:flex; justify-content: flex-end; gap: 4px;')
:wikitext('-')
:tag('div')
:done()
:cssText(css.multi_button)
:wikitext('+')
:done()
:tag('div')
:cssText(css.multi_button)
:wikitext('-')
:done()
end
else
connector_row_labels
:tag('div') -- HTML div to contain the output label
:attr('title', hover_text) -- Add a basic mouseover description
:cssText(css.output_label(color))
:wikitext(output.Name or '')
:done() -- Close output label div
end
end
else
 
c
-- Create the output (right) attachment point
:tag('div') -- HTML div to contain the output label
CreateConnectorAttachmentPoint(connectorRow, hover_text, color, is_impulse, false);
:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
end -- if output
:cssText('text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: ' .. GetTypeColor(Output, 0.6) .. ';')
:wikitext(Output and Output.Name or '')
:done() -- Close output label div
end
-- Create the output (right) attachment point
CreateConnectorAttachmentPoint(connectorRow, Output, false);
end
end


--- Creates a new ProtoFlux global input field row in the output
--- Creates a new ProtoFlux global input field row in the output
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Global Table containing a Name and Type for the global field on this row.
-- @param global Table containing a Name and Type for the global field on this row.
function CreateGlobalsRow(Container, Global)
function CreateGlobalsRow(container, global)
Container
local color = GetTypeColor(container.Type)
local hover_text = connector_hover_text(connector)
 
container
:tag( 'div' )
:tag( 'div' )
:attr('title', Global.Name .. ' <' .. Global.Type .. '>') -- Add a basic mouseover description
:attr('title', hover_text) -- Add a basic mouseover description
:cssText('display: flex; min-height: 70px; flex-direction: column; border-left: 10px solid ' .. GetTypeColor(Global, 1.0) .. ';')
:cssText(css.global(color))
:tag( 'div' )
:tag( 'div' )
:cssText('display: flex; flex-direction: row; align-items: center; flex-grow:1; overflow: hidden;')
:cssText('display: flex; flex-direction: row; align-items: center; flex-grow:1; overflow: hidden;')
:tag( 'span' )
:tag( 'span' )
:cssText('text-align: center; overflow: hidden; text-overflow: ellipsis; font-size: 14pt; font-weight: bold; flex-grow:1;')
:cssText('text-align: center; overflow: hidden; text-overflow: ellipsis; font-size: 14pt; font-weight: bold; flex-grow:1;')
:wikitext(Global.Name)
:wikitext(global.Name)
:done()
:done()
:done()
:done()
:tag( 'div' )
:tag( 'div' )
:attr('style', 'display:flex; gap: 10px; flex-grow: 1;')
:attr('style', 'display: flex; gap: 10px; flex-grow: 1;')
:tag('div')
:tag('div')
:cssText('border-radius: 16px; background-color: #777; font-style: italic; text-align:center; flex-grow: 3; display: flex; flex-direction: column; justify-content: center;')
:cssText('border-radius: 16px; background-color: #777; font-style: italic; text-align:center; flex-grow: 3; display: flex; flex-direction: column; justify-content: center;')
Line 174: Line 235:
:done()
:done()
:done()
:done()
:tag( 'div')
:tag( 'div' )
:cssText('border-radius: 16px; background-color: #333; text-align:center; flex-grow: 1; display: flex; flex-direction: column; justify-content: center;')
:cssText('border-radius: 16px; background-color: #333; text-align:center; flex-grow: 1; display: flex; flex-direction: column; justify-content: center;')
:tag ( 'span' )
:tag ( 'span' )
Line 181: Line 242:


--- Creates a new input or output attachement point for a connector row.
--- Creates a new input or output attachement point for a connector row.
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
--  @param hover_text Text to show when hovering, possibly nil
-- @param isInput True if this is an input side attachment point, false if it is an output side attachment point.
-- @param color The color of this attachment point (table with r, g, b, optionally a).
function CreateConnectorAttachmentPoint(Container, Connector, isInput)
-- @param is_impulse True if this is an impulse, false if it is a data type.
if Connector and (Connector.Type == "Continuation" or Connector.Type == "Call" or Connector.Type == "AsyncCall" or Connector.Type == "SyncResumption" or Connector.Type == "AsyncResumption")  then
-- @param is_input True if this is an input side attachment point, false if it is an output side attachment point.
Container
function CreateConnectorAttachmentPoint(container, hover_text, color, is_impulse, is_input)
:tag( 'div' )
local connector_type = nil
:attr('title', Connector and (Connector.Name .. ' <' .. Connector.Type .. '>') or '') -- Add a basic mouseover description
if is_impulse then connector_type = "InputArrow"
:cssText('width:30px; background-color: black; fill: ' .. GetTypeColor(Connector, 0.3) .. '; stroke: ' .. GetTypeColor(Connector, 1.0))
elseif is_input then connector_type = "InputBox"
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type="InputArrow"}}))
else connector_type = "OutputBox"
end
 
container
:tag( 'div' )
:attr('title', hover_text or "") -- Add a basic mouseover description
:cssText(css.attachment_point(color, is_output and 3 or 1))
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=connector_type}}))
end


elseif Connector then
--- Returns a CSS color for the data type or impulse and whether the type is an impulse
Container
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output.
:tag( 'div' )
function GetColor(connector)
:attr('title', Connector and (Connector.Name .. ' <' .. Connector.Type .. '>') or '') -- Add a basic mouseover description
if connector == nil then return end
:cssText('width:30px; fill: ' .. GetTypeColor(Connector, 0.3) .. '; stroke: ' .. GetTypeColor(Connector, 1.0))
if connector.Type == nil then error("Missing Type") end
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=(isInput and "InputBox" or "OutputBox")}}))
local color = ProtofluxColor.get_impulse_color(connector.Type)
if color ~= nil then
return color, true
end
end
return GetTypeColor(connector), false
end
end


--- Returns an RGBA value representing the type color within Resonite
--- Returns a CSS color for the data type
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output or global.
-- @param Alpha The alpha to use in the RGBA value.  
function GetTypeColor(connector)
function GetTypeColor(Connector, Alpha)
local color = ProtofluxColor.get_type_color(connector.Type)
return (Connector and 'rgba(' .. (create_color_crc(Connector.Type)) .. ',' .. Alpha .. ')' or 'rgba(0, 0, 0, 0)')
return MultRGB(color, 1.5)
end
end


function create_color_crc( str )
function MultRGB(color, fact)
return {
local table = color_crc_module.get_type_color( str )
r=color.r*fact,
g=color.g*fact,
return (table.r*255 .. "," .. table.g*255 .. "," .. table.b*255)
b=color.b*fact,
a=color.a,
}
end
 
function MultA(color, fact)
return {
r=color.r,
g=color.g,
b=color.b,
a=color.a*fact,
}
end
 
function ColorToCss(color)
return ("rgba(%s,%s,%s,%s)"):format(color.r, color.g, color.b, color.a or 1)
end
end


return p
return p

Revision as of 13:02, 10 February 2024

This is the Lua module for generating ProtoFlux UI elements on pages such as To String (ProtoFlux)

Please note, due to caching, updates made to this module will not immediately take effect on pages using it. If you need to see changes right away, use the editor view of a given page, which will update immediately.

Parameters

Name

Name of this ProtoFlux node. This should be the same as the name you see at the top of a given node in Resonite.

Category

Category for this ProtoFlux node. This is not the full category path, but only the direct parent - it should be the same as what appears at the bottom of a given node in Resonite.

Inline

Controls whether this node is displayed inline, or floats to the right. If this parameter is set, the node will draw inline, regardless of the value passed to it.

If you need content to flow below the element, use <div style="clear:right;"></div>, add the class .floatnone to an element, or use other elements with a similar CSS style tag.

Inputs

A string containing a JSON-encoded array of Name/Type information for each input on this node. Example input might be similar to the following, which adds two inputs of type String, one named Input1 and one named Input2

[{"Name":"Input1", "Type":"String"}, {"Name":"Input2", "Type":"String"}]

Outputs

A string containing a JSON-encoded array of Name/Type information for each output on this node. Example input might be similar to the following, which adds two outputs of type bool, one named Output1 and one named Output2

[{"Name":"Output1", "Type":"bool"}, {"Name":"Output2", "Type":"bool"}]

Globals

A string containing a JSON-encoded array of Name/Type information for each global on this node. Example input might be similar to the following, which adds two inputs of type User, one named Global1 and one named Global2

[{"Name":"Global1", "Type":"User"}, {"Name":"Global2", "Type":"User"}]

Custom

Either true or false (not case sensitive, defaults to false), if true the page is not added to ProtoFlux:All. This is mainly used for custom nodes.

Custom=false

Example Usage

If we combine all of the above examples together, we end up with this template invocation:

{{#Invoke:ProtoFlux|GenerateUI
|Name=Example Name
|Category=Example Category
|Inputs=
[
{"Name": "Input1", "Type": "Call"},
{"Name": "Input2", "Type": "String"}
]
|Outputs=
[
{"Name": "Output1", "Type": "bool"},
{"Name": "Output2", "Type": "bool"}
]
|Globals=
[
{"Name": "Global1", "Type": "User"},
{"Name": "Global2", "Type": "User"}
]
|}}

which results in the following:

Lua error at line 267: Missing Type.


-- Package definition to return to Scribunto - this allows us to define methods that can be called
-- when this module is targeted.
local p = {}

local ProtofluxColor = require("Module:ProtoFlux_Type_Color")

--- Method that generates the HTML output
--	@param frame A Scribunto frame instance. frame.args contains the parameters passed into the module call
function p.GenerateUI( frame )
	-- Parse the JSON input, returning an empty array if no argument was passed
	local inputs = mw.text.jsonDecode(frame.args.Inputs or '[]')
	local outputs = mw.text.jsonDecode(frame.args.Outputs or '[]')
	local globals = mw.text.jsonDecode(frame.args.Globals or '[]')
	
	-- Create an HTML div element to contain our node UI
	local protofluxContainer = mw.html.create( 'div' )
	protofluxContainer
	    :attr('class', frame.args.Inline and '' or 'floatright')
		:cssText('color: rgb(224, 224, 224); background-color: rgb(18, 20, 28); width: 256px; display: flex; flex-direction: column; align-items: stretch;')
		:tag('div') -- HTML div to contain the node title
			:cssText('padding: 10px 0px 10px 0px; text-align:center; font-weight: bold; background-color: rgb(26, 41, 54); font-size: 18pt; flex-grow: 1;')
			:wikitext(frame.args.Name)
			:done() -- Close node title div
	
	local processedInputs = {}
	local processedOutputs = {}
	
	for i=1,#inputs do
		for j=1,(inputs and inputs[i].Multi or 1) do
			inputs[i].MultiIndex = j
			table.insert(processedInputs, { Name=inputs[i].Name, Type=inputs[i].Type, Multi=inputs[i].Multi, MultiIndex=j })
		end
	end
	
	for i=1,#outputs do
		for j=1,(outputs and outputs[i].Multi or 1) do
			table.insert(processedOutputs, { Name=outputs[i].Name, Type=outputs[i].Type, Multi=outputs[i].Multi, MultiIndex=j })
		end
	end
	
	-- Calculate the larger number of rows required (either for inputs or outputs, if the node is asymmetric)
	local maxRows = math.max(#processedInputs, #processedOutputs)
	
	
	-- Iterate over each row, and populate it with the inputs/outputs. If the node is asymmetric,
	-- the value passed for either input or output might be nil.
	
	for i=1,maxRows do
		CreateConnectorRow(protofluxContainer, processedInputs[i], processedOutputs[i])
	end
	
	-- Iterate over each global value in the node, and create a row. These elements take up the entire width
	-- of the node, and so don't need to be balanced in any way.
	for i=1,#globals do
		CreateGlobalsRow(protofluxContainer, globals[i])
	end
	
	protofluxContainer
		:tag('div') -- HTML div to contain the node category footer
			:cssText('text-align: center; padding: 10px; font-size: 18pt; color: rgb(64,64,64); font-weight: bold;')
			:wikitext(frame.args.Category)
			:done() -- Close category footer div
	
	-- Return the HTML generated above to the wiki page this script is invoked from.
	return tostring(protofluxContainer) .. '[[Category:ProtoFlux:All]]'
end

local css = {
	connector_row = "display: flex; min-height: 70px;",
	connector_row_labels = "flex-grow: 2; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; order: 2;",
	multi_button = "font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content: center;",
	input_label = function(color)
		return string.format(
			"text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px; "
			.."border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; "
			.."background-color: %s;",
			color and ColorToCss(MultA(color, 0.6)) or "transparent"
		)
	end,
	output_label = function(color)
		return string.format(
			"text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; "
			.."border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; "
			.."background-color: %s;",
			color and ColorToCss(MultA(color, 0.6)) or "transparent"
		)
	end,
	attachment_point = function(color, order)
		return string.format(
			"width: 30px; background-color: black; fill: %s; stroke: %s; order: %s;",
			ColorToCss(MultA(color, 0.3)),
			ColorToCss(color),
			order
		)
	end,
	global = function(color)
		return string.format(
			"display: flex; min-height: 70px; flex-direction: column; border-left: 10px solid %s;",
			ColorToCss(color)
		)
	end,
}

--- Creates a text which should be shown when hovering over a connector
-- @param Input Table containing a Name (optional) and Type for the input on this row.
function connector_hover_text(connector)
	if connector.Name == nil then
		return ("<%s>"):format(connector.Type)
	end
	return ("%s <%s>"):format(connector.Name, connector.Type)
end

--- Creates a new ProtoFlux connector row in the output
-- @param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param input Table containing a Name and Type for the input on this row. Can be nil if no input is to be placed on this row.
-- @param output Table containing a Name and Type for the output on this row. Can be nil if no output is to be placed on this row.
function CreateConnectorRow(container, input, output)
	local connector_row = container
		:tag('div') -- HTML div to contain the connector row. We specify a min-height of 70px for consistency.
			:cssText(css.connector_row)

	local connector_row_labels = connector_row
		:tag('div') -- HTML div to contain the input and output labels in the connector row
			:cssText(css.connector_row_labels)

	if input then
		local color, is_impulse = GetColor(output.Type)
		local hover_text = connector_hover_text(connector)

		if input.Multi and input.MultiIndex == 1 then
			connector_row_labels
				:tag('div') -- HTML div to contain the input label
					:attr('title', hover_text) -- Add a basic mouseover description
					:cssText(css.input_label())
					:wikitext(input.Name or "")
					:done() -- Close input label div
		elseif Input and Input.Multi and Input.MultiIndex > 1 then
			local label = connector_row_labels
				:tag('div') -- HTML div to contain the input label
					:attr('title', hover_text) -- Add a basic mouseover description
					:cssText(css.input_label())
			if Input.Multi == Input.MultiIndex then
				label
					:tag('div')
						:cssText('position:relative; top:2rem; display: flex; gap: 4px;')
						:tag('div')
							:cssText(css.multi_button)
							:wikitext('+')
							:done()
						:tag('div')
							:cssText(css.multi_button)
							:wikitext('-')
							:done()
			end
		else
			connector_row_labels
				:tag('div') -- HTML div to contain the input label
					:attr('title', hover_text) -- Add a basic mouseover description
					:cssText(css.input_label(color))
					:wikitext(input.Name or '')
					:done() -- Close input label div
		end

		-- Create the input (left) attachement point
		CreateConnectorAttachmentPoint(connectorRow, hover_text, color, is_impulse, true)
	end -- if input

	if output then
		local color, is_impulse = GetColor(output.Type)
		local hover_text = connector_hover_text(connector)

		if output.Multi and output.MultiIndex == 1 then
			connector_row_labels
				:tag('div') -- HTML div to contain the output label
					:attr('title', hover_text) -- Add a basic mouseover description
					:cssText(css.output_label())
					:wikitext(output.Name or '')
					:done() -- Close output label div
		elseif output.Multi and output.MultiIndex > 1 then
			local label = connector_row_labels
				:tag('div') -- HTML div to contain the output label
					:attr('title', hover_text) -- Add a basic mouseover description
					:cssText(css.output_label())
			if output.Multi == output.MultiIndex then
				label
					:tag('div')
						:cssText('display:flex; justify-content: flex-end; gap: 4px;')
						:tag('div')
							:cssText(css.multi_button)
							:wikitext('+')
							:done()
						:tag('div')
							:cssText(css.multi_button)
							:wikitext('-')
							:done()
			end
		else
			connector_row_labels
				:tag('div') -- HTML div to contain the output label
					:attr('title', hover_text) -- Add a basic mouseover description
					:cssText(css.output_label(color))
					:wikitext(output.Name or '')
					:done() -- Close output label div
		end

		-- Create the output (right) attachment point
		CreateConnectorAttachmentPoint(connectorRow, hover_text, color, is_impulse, false);
	end -- if output
end

--- Creates a new ProtoFlux global input field row in the output
--	@param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
--	@param global Table containing a Name and Type for the global field on this row.
function CreateGlobalsRow(container, global)
	local color = GetTypeColor(container.Type)
	local hover_text = connector_hover_text(connector)

	container
		:tag( 'div' )
			:attr('title', hover_text) -- Add a basic mouseover description
			:cssText(css.global(color))
			:tag( 'div' )
				:cssText('display: flex; flex-direction: row; align-items: center; flex-grow:1; overflow: hidden;')
				:tag( 'span' )
					:cssText('text-align: center; overflow: hidden; text-overflow: ellipsis; font-size: 14pt; font-weight: bold; flex-grow:1;')
					:wikitext(global.Name)
					:done()
				:done()
			:tag( 'div' )
				:attr('style', 'display: flex; gap: 10px; flex-grow: 1;')
				:tag('div')
					:cssText('border-radius: 16px; background-color: #777; font-style: italic; text-align:center; flex-grow: 3; display: flex; flex-direction: column; justify-content: center;')
					:tag( 'span' )
						:wikitext('null')
						:done()
					:done()
				:tag( 'div' )
					:cssText('border-radius: 16px; background-color: #333; text-align:center; flex-grow: 1; display: flex; flex-direction: column; justify-content: center;')
					:tag ( 'span' )
						:wikitext('∅')   
end

--- Creates a new input or output attachement point for a connector row.
--	@param container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
--  @param hover_text Text to show when hovering, possibly nil
--	@param color The color of this attachment point (table with r, g, b, optionally a).
--	@param is_impulse True if this is an impulse, false if it is a data type.
--	@param is_input True if this is an input side attachment point, false if it is an output side attachment point.
function CreateConnectorAttachmentPoint(container, hover_text, color, is_impulse, is_input)
	local connector_type = nil
	if is_impulse then connector_type = "InputArrow"
	elseif is_input then connector_type = "InputBox"
	else connector_type = "OutputBox"
	end

	container
		:tag( 'div' )
			:attr('title', hover_text or "") -- Add a basic mouseover description
			:cssText(css.attachment_point(color, is_output and 3 or 1))
			:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=connector_type}}))
end

--- Returns a CSS color for the data type or impulse and whether the type is an impulse
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output.
function GetColor(connector)
	if connector == nil then return end
	if connector.Type == nil then error("Missing Type") end
	local color = ProtofluxColor.get_impulse_color(connector.Type)
	if color ~= nil then
		return color, true
	end
	return GetTypeColor(connector), false
end

--- Returns a CSS color for the data type
-- @param connector Table containing a Name (Optional) and Type (Required) for the input/output or global.
function GetTypeColor(connector)
	local color = ProtofluxColor.get_type_color(connector.Type)
	return MultRGB(color, 1.5)
end

function MultRGB(color, fact)
	return {
		r=color.r*fact,
		g=color.g*fact,
		b=color.b*fact,
		a=color.a,
	}
end

function MultA(color, fact)
	return {
		r=color.r,
		g=color.g,
		b=color.b,
		a=color.a*fact,
	}
end

function ColorToCss(color)
	return ("rgba(%s,%s,%s,%s)"):format(color.r, color.g, color.b, color.a or 1)
end

return p