-- 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")
local fieldHandlers = {}
--- 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 fields = mw.text.jsonDecode(frame.args.Fields or '[]')
-- Create an HTML div element to contain our component UI
local componentContainer = mw.html.create( 'div' )
componentContainer
:attr('class', frame.args.Inline and '' or 'floatright')
:cssText('color: rgb(224, 224, 224); background-color: rgb(18, 20, 28); width: 512px; display: flex; flex-direction: column; align-items: stretch; gap: 6px; padding: 8px;')
:tag('div') -- HTML div to contain the component title and buttons
:cssText('display:flex;gap:4px;')
:tag('div') -- component title
:cssText('color: #f8f770; /* Resonite Yellow */ border-radius: 8px; background-color: #2b2f35; /* Resonite Mid */ text-align:center; font-size: 1.25rem; font-weight: bold; flex-grow: 1;')
:wikitext(frame.args.Name)
:done() -- Close component title div
:tag('div') -- duplicate button
:cssText('background-color: #3f9e44; /* Resonite Sub-Green */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
:wikitext("[[File:Duplicate.svg|Duplicate|28px]]")
:done()
:tag('div') -- destroy button
:cssText('background-color: #ae5458; /* Resonite Sub-Red */ border-radius: 8px; width: 2.5rem; display: flex; align-items:center; justify-content:center;')
:wikitext("[[File:Destroy.svg|Destroy|28px]]")
:done()
:done() -- Close title and buttons div
-- 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,#fields do
CreateField(componentContainer, fields[i])
end
-- Return the HTML generated above to the wiki page this script is invoked from.
return tostring(componentContainer) .. '[[Category:Components:All]]'
end
--- Creates a new Component 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 Field Table containing a Name, FieldType, and Type for the field on this row.
function CreateField(Container, Field)
local fieldContentContainer = Container
:tag('div') -- HTML div to contain the field.
:cssText('display: flex;')
:tag('div') -- HTML div to contain the field
:cssText( 'flex-grow: 2; overflow: hidden; display: flex; flex-direction: row;')
if fieldHandlers[Field.FieldType] == nil then
fieldContentContainer:wikitext("ERROR: No handler for field type " .. Field.FieldType)
return
end
fieldHandlers[Field.FieldType](fieldContentContainer, Field)
end
local enumTypes = {
["TextHorizontalAlignment"] = true,
["TextVerticalAlignment"] = true,
["Elements.Assets.AlignmentMode"] = true,
["Alignment"] = true,
["DefaultSpace"] = true
}
function fieldHandlers.Sync(Container, Field)
Container
:tag('div') -- HTML div to contain the field label
:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: left; width:30%; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;')
:wikitext( (Field and Field.Name or '') .. ':' )
:done() -- Close field label div
if Field.Type == 'Bool' then
CreateUICheckbox(Container, Field.Value)
elseif Field.Type == 'Int' or Field.Type == 'String' or Field.Type == 'Float' or Field.Type == 'Double' then
CreateUIInputBox(Container, Field.Value)
elseif Field.Type == 'Float2' or Field.Type == 'Float3' then
CreateUIVector(Container, Field.Value)
elseif enumTypes[Field.Type] then
CreateUIForwardBackwards(Container, Field.Value)
elseif Field.Type == "ColorX" then
local c = Container
:tag('div')
:cssText('display:flex; flex-direction: column; align-items:stretch; flex-grow:1; gap: 4px;')
CreateUIColor(c, Field.Value)
local c2 = c:tag('div')
:cssText('display:flex; flex-direction: row; align-items: stretch; flex-grow: 1; gap: 4px;')
:tag('div')
:cssText('display:flex; justify-content: center; align-items:center;')
:wikitext('Profile:')
:done()
CreateUIForwardBackwards(c2, Field.Value[5])
else
Container
:wikitext( Field.FieldType .. '<' .. Field.Type .. '>')
end
end
function CreateUIVector(Container, Vector)
local c = Container
:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
local letters = {"x", "y", "z", "w"}
for i=1,#Vector do
CreateUIBasicText(c,letters[i] or "?")
CreateUIInputBox(c, Vector[1])
end
end
function fieldHandlers.SyncRef(Container,Field)
Container
:tag('div') -- HTML div to contain the field label
:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: left; width:30%; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;')
:wikitext( (Field and Field.Name or '') .. ':' )
:done() -- Close field label div
CreateUISyncRefEditor(Container, Field.Value)
end
function fieldHandlers.AssetRef(Container,Field)
Container
:tag('div') -- HTML div to contain the field label
:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: left; width:30%; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;')
:wikitext( (Field and Field.Name or '') .. ':' )
:done() -- Close field label div
CreateUISyncRefEditor(Container, Field.Value)
end
function fieldHandlers.SyncAssetList(Container,Field)
local c = Container
:tag('div')
:cssText('display: flex; flex-direction: column; flex-grow: 1; gap: 4px;')
:tag('div') -- HTML div to contain the field label
:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: center;')
:wikitext( (Field and Field.Name or '') .. ' (list):' )
:done() -- Close field label div
for i=1,#Field.Value do
local ref = CreateUISyncRefEditor(c, Field.Value[i])
CreateUISmallButton(ref, 'X')
end
CreateUIButton(c, 'Add')
end
function fieldHandlers.SyncObject(Container, Field)
Container = Container
:tag('div')
:cssText('display:flex; flex-direction:column;gap:6px;width:100%;')
Container
:tag('div') -- HTML div to contain the field label
:attr('title', Field and (Field.Name .. ' ' .. Field.FieldType .. '<' .. Field.Type .. '>') or '') -- Add a basic mouseover description
:cssText('text-align: left; width:30%; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;')
:wikitext( (Field and Field.Name or '') .. ':' )
:done() -- Close field label div
local c = Container
:tag('div') -- Indented contianer
:cssText('display:flex;flex:1;')
c:tag('div') -- Black line
:cssText('background-color:black;width:4px;margin: 0 6px;')
:done() -- Close Black line
fieldContainer = c:tag('div'):cssText("display:flex;flex-direction:column;gap:6px;flex:1;")
for i=1,#Field.Fields do
CreateField(fieldContainer, Field.Fields[i])
end
end
function fieldHandlers.DelegateButton(Container, Field)
CreateUIRefInputBox(Container, Field.Name)
end
function CreateUIColor(Container, Color)
local ColorNoAlpha = {Color[1], Color[2], Color[3], 1}
local c = Container
:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
CreateUIBasicText(c, 'R')
CreateUIInputBox(c, Color[1])
CreateUIBasicText(c, 'G')
CreateUIInputBox(c, Color[2])
CreateUIBasicText(c, 'B')
CreateUIInputBox(c, Color[3])
CreateUIBasicText(c, 'A')
CreateUIInputBox(c, Color[4])
c:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1;')
:tag('div')
:cssText('flex-grow: 1; background-color: ' .. ColorToCss(ColorNoAlpha) .. ';')
:done()
:tag('div')
:cssText('flex-grow: 1; background-color: ' .. ColorToCss(Color) .. ';')
:done()
:done()
end
function CreateUIBasicText(Container, Text)
Container
:wikitext(Text)
:done()
return Container;
end
function CreateUIForwardBackwards(Container, Text)
local c = Container
:tag('div')
:cssText('display:flex; height: 1.5rem; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
CreateUISmallButton(c, '<<')
:tag('div')
:cssText('border-radius: 8px; background: rgb(8, 8, 10); flex-grow:1; display:flex; justify-content: center; align-items:center;')
:wikitext(Text)
:done()
CreateUISmallButton(c, '>>')
return Container
end
function CreateUICheckbox(Container, Checked)
local c = Container
:tag('div')
:cssText('box-sizing: border-box; background-color: #2b2f35; /* Resonite Mid */ border-radius: 8px; width: 1.5rem; height: 1.5rem; padding: 2px;')
if Checked then
c:wikitext('[[File:Checkmark.png|checkmark|20px]]')
end
end
function CreateUISmallButton(Container, Text)
Container
:tag('div')
:cssText('display:flex; flex-direction: column; align-items:center; justify-content: center;')
:tag('div')
:cssText('width: 1.5rem; height: 1.5rem; background-color: rgb(43, 46, 54); border-radius: 8px; flex-grow:0; display:flex; justify-content: center; align-items:center;')
:wikitext(Text)
:done()
return Container
end
function CreateUIButton(Container, Text)
Container
:tag('div')
:cssText('display:flex; flex-direction: column; align-items:stretch; justify-content: center;')
:tag('div')
:cssText('height: 1.5rem; background-color: rgb(43, 46, 54); border-radius: 8px; flex-grow:0; display:flex; justify-content: center; align-items:center;')
:wikitext(Text)
:done()
return Container
end
function CreateUIInputBox(Container, Text)
local Italics = false
if not Text then
Text = 'null'
Italics = true
end
Container
:tag('div')
:cssText('box-sizing: border-box; height: 1.5rem; border: 3px solid rgb(43, 46, 54); border-radius: 8px; background: rgb(30, 33, 38); flex-grow:1; display:flex; justify-content: center; align-items:center;' .. (Italics and 'font-style:italic;' or ''))
:wikitext(Text)
:done()
end
function CreateUIRefInputBox(Container, Text)
local Italics = false
if not Text then
Text = 'null'
Italics = true
end
Container
:tag('div')
:cssText('height: 1.5rem; background: rgb(43, 46, 54); border-radius: 8px; flex-grow:1; display:flex; justify-content: center; align-items:center;' .. (Italics and 'font-style:italic;' or ''))
:wikitext(Text)
:done()
return Container
end
function CreateUISyncRefEditor(Container, Text)
local c = Container
:tag('div')
:cssText('display:flex; flex-direction: row; align-items:stretch; flex-grow:1; gap: 4px;')
CreateUISmallButton(c, '⤴')
CreateUISmallButton(c, '↑')
CreateUIRefInputBox(c, Text)
CreateUISmallButton(c, '∅')
return c
end
function ColorToCss(color)
return ("rgba(%s,%s,%s,%s)"):format(color[1]*255, color[2]*255, color[3]*255, color[4] or 1)
end
--- Returns an RGBA value representing the type color within Resonite
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
function GetTypeColor(Connector)
if connector == nil then error("connector is nil") end
if connector == nil or connector.Type == nil then error("Missing Type") end
return ProtofluxColor.get_type_color(connector.Type)
end
return p