DataBrain

Command line - Instance to JSX

Jan 21st, 2019
394
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.30 KB | None | 0 0
  1. -- NOTE: HTTP must be enabled to retrieve the latest API!
  2.  
  3. -- Table of instances to convert to JSX
  4. local INPUT = game.Selection:Get()
  5.  
  6. -- SourceContainer to dump the JSX into
  7. local OUTPUT = workspace:FindFirstChild("InstanceToJsx Result") or (function()local m = Instance.new("ModuleScript"); m.Name = "InstanceToJsx Result"; m.Parent = workspace return m end)()
  8.  
  9. -- Priority of props to display; those at the top of the list will be displayed first.
  10. local PROP_ORDER = {
  11.     "Name",
  12.     "className", -- Special key for provided CSS classes
  13.     "Text",
  14.     "Image",
  15.     "Visible",
  16.     "Active",
  17.     "ZIndex",
  18.     "AnchorPoint",
  19.     "Position",
  20.     "Size",
  21. }
  22.  
  23. -- Map of JSX tags from their instance class names
  24. local INTRINSIC = {
  25.     UIAspectRatioConstraint = "uiaspectratioconstraint",
  26.    
  27.     ScreenGui = "screengui",
  28.     BillboardGui = "billboardgui",
  29.     SurfaceGui = "surfacegui",
  30.    
  31.     ImageLabel = "imagelabel",
  32.     ImageButton = "imagebutton",
  33.    
  34.     TextLabel = "textlabel",
  35.     TextButton = "textbutton",
  36.     TextBox = "textbox",
  37.    
  38.     Frame = "frame",
  39.     ViewportFrame = "viewportframe",
  40.     ScrollingFrame = "scrollingframe",
  41.    
  42.     UIGridLayout = "uigridlayout",
  43.     UIListLayout = "uilistlayout",
  44.     UIPageLayout = "uipagelayout",
  45.     UITableLayout = "uitablelayout",
  46.    
  47.     UIPadding = "uipadding",
  48.     UIScale = "uiscale",
  49.    
  50.     UISizeConstraint = "uisizeconstraint",
  51.     UITextSizeConstraint = "uitextsizeconstraint",
  52. }
  53.  
  54. -- Function for controlling how numbers are rounded when converted to JSX literals
  55. local function ROUND_TO(number, precision)
  56.     if precision > 0 then
  57.         local rounded = string.format("%0." .. tonumber(precision) .. "f", number)
  58.        
  59.         -- Remove trailing zeroes
  60.         while (rounded:sub(#rounded) == '0') do
  61.             rounded = rounded:sub(1, #rounded - 1)
  62.         end
  63.        
  64.         -- Remove trailing dot
  65.         if (rounded:sub(#rounded) == ".") then
  66.             rounded = rounded:sub(1, #rounded - 1)
  67.         end
  68.         return rounded
  69.     else
  70.         return tostring(math.floor(number + 0.5))
  71.     end
  72. end
  73.  
  74. -- Function to convert a prop value to its JSX literal representation
  75. local function PROP_TO_STRING(prop)
  76.     if type(prop) == "string" then
  77.         return '"' .. prop .. '"'
  78.     elseif type(prop) == "number" then
  79.         return '{' .. ROUND_TO(prop, 5) .. '}'
  80.     elseif type(prop) == "boolean" or typeof(prop) == "EnumItem" then
  81.         return '{' .. tostring(prop) .. '}'
  82.     elseif typeof(prop) == "UDim2" then
  83.         return (
  84.             '{new UDim2('
  85.             .. ROUND_TO(prop.X.Scale, 3)    .. ", "
  86.             .. ROUND_TO(prop.X.Offset, 3)   .. ", "
  87.             .. ROUND_TO(prop.Y.Scale, 3)    .. ", "
  88.             .. ROUND_TO(prop.Y.Offset, 3)   .. ')}'
  89.         )
  90.     elseif typeof(prop) == "Vector2" then
  91.         return (
  92.             '{new Vector2('
  93.             .. ROUND_TO(prop.X, 5)  .. ", "
  94.             .. ROUND_TO(prop.Y, 5)  .. ')}'
  95.         )
  96.     elseif typeof(prop) == "Vector3" then
  97.         return (
  98.             '{new Vector3('
  99.             .. ROUND_TO(prop.X, 5)  .. ", "
  100.             .. ROUND_TO(prop.Y, 5)  .. ", "
  101.             .. ROUND_TO(prop.Z, 5)  .. ')}'
  102.         )
  103.     elseif typeof(prop) == "CFrame" then
  104.         local componentsStr = ""
  105.         local prefix = ""
  106.         for _,v in pairs({prop:components()}) do
  107.             componentsStr = componentsStr .. prefix .. ROUND_TO(v, 5)
  108.             prefix = ", "
  109.         end
  110.         return (
  111.             '{new CFrame(' .. componentsStr .. ')}'
  112.         )
  113.     elseif typeof(prop) == "Color3" then
  114.         return (
  115.             '{Color3.fromRGB('
  116.             .. ROUND_TO(prop.r * 255, 0)    .. ", "
  117.             .. ROUND_TO(prop.g * 255, 0)    .. ", "
  118.             .. ROUND_TO(prop.b * 255, 0)    .. ')}'
  119.         )
  120.     end
  121. end
  122.  
  123. --[[
  124.         Optional: a map of CSS class information by name.
  125.  
  126. Class information has two values: {
  127.     Precondition: A map from property names to the necessary value for this CSS class to be used
  128.     Override: An array of property names that this class overrides (which will be ommitted from the JSX)
  129. }
  130.    
  131.     e.g. I have a .ts module that exports the following:
  132.    
  133. export namespace CSS {
  134.     export const noBackground = {
  135.         BackgroundTransparency: 1,
  136.         BorderSizePixel: 0,
  137.     }
  138.     export const centered = {
  139.         Position: new UDim2(0.5, 0, 0.5, 0),
  140.         AnchorPoint: new Vector2(0.5, 0.5),
  141.     }
  142. }
  143.    
  144.     To generate the use of these CSS styles, I could set this table to the following:
  145.    
  146. local CSS_CLASSES = {
  147.     ["CSS.noBackground"] = {
  148.         Precondition = {
  149.             BackgroundTransparency = 1,
  150.         },
  151.         Override = {"BackgroundTransparency", "BackgroundColor3", "BorderSizePixel"},
  152.     },
  153.     ["CSS.centered"] = {
  154.         Precondition = {
  155.             Position = UDim2.new(0.5, 0, 0.5, 0),
  156.             AnchorPoint = Vector2.new(0.5, 0.5),
  157.         },
  158.         Override = {"Position", "AnchorPoint"},
  159.     },
  160. }
  161.  
  162.     A frame with a BackgroundTransparency of 1 will then output as follows:
  163.    
  164. <frame {...CSS.noBackground} Name="MyFrame"/>
  165.  
  166. --]]
  167. local CSS_CLASSES = {}
  168.  
  169. do
  170.     local dictionary = {}
  171.     for i,v in ipairs(PROP_ORDER) do
  172.         dictionary[v] = i
  173.     end
  174.     PROP_ORDER = dictionary
  175. end
  176. local HttpService = game:GetService("HttpService")
  177. _G.API = _G.API or HttpService:JSONDecode(HttpService:GetAsync('http://anaminus.github.io/rbx/json/api/latest.json'))
  178.  
  179. -- Add props to base classes
  180. local classProps
  181. local classSupers
  182.  
  183. local dummies = {}
  184.  
  185. if (_G.InstanceToJSXCache) then
  186.     classProps = _G.InstanceToJSXCache.Props
  187.     classSupers = _G.InstanceToJSXCache.Supers
  188.     dummies = _G.InstanceToJSXCache.Dummies
  189. else
  190.     baseProps = {}
  191.     classSupers = {}
  192.     for _,data in pairs(_G.API) do
  193.         if data.type == "Property" then
  194.             local readOnly = false
  195.             for _,v in pairs(data.tags) do
  196.                 if v == "readonly" or v == "deprecated" or v == "hidden" then
  197.                     readOnly = true
  198.                     break
  199.                 end
  200.             end
  201.            
  202.             local dummy = dummies[data.Class]
  203.             if (not dummy) then
  204.                 local instPtr = {}
  205.                 local creatable = pcall(function()
  206.                     instPtr.instance = Instance.new(data.Class)
  207.                 end)
  208.                 if (creatable) then
  209.                     dummy = instPtr.instance
  210.                     dummies[data.Class] = dummy
  211.                 end
  212.             end
  213.            
  214.            
  215.             if (not readOnly) then
  216.                 local props = baseProps[data.Class]
  217.                 if (not props) then
  218.                     props = {}
  219.                     baseProps[data.Class] = props
  220.                 end
  221.                
  222.                 table.insert(props, data.Name)
  223.             end
  224.         elseif data.type == "Class" then
  225.             local super = data.Superclass
  226.             if (super) then
  227.                 classSupers[data.Name] = super
  228.             end
  229.            
  230.             if (not dummies[data.Name]) then
  231.                 local instPtr = {}
  232.                 local creatable = pcall(function()
  233.                     instPtr.instance = Instance.new(data.Name)
  234.                 end)
  235.                 if (creatable) then
  236.                     dummies[data.Name] = instPtr.instance
  237.                 end
  238.             end
  239.            
  240.             if (not baseProps[data.Name]) then
  241.                 baseProps[data.Name] = {}
  242.             end
  243.         end
  244.     end
  245.    
  246.     local function getProps(className)
  247.         local props = {}
  248.         while className do
  249.             if baseProps[className] then
  250.                 for _,v in pairs(baseProps[className]) do
  251.                     table.insert(props, v)
  252.                 end
  253.             end
  254.             className = classSupers[className]
  255.         end
  256.         return props
  257.     end
  258.    
  259.     classProps = {}
  260.     for className in pairs(baseProps) do
  261.         -- Filter for classes that can be created
  262.         local dummy = dummies[className]
  263.         if dummy then
  264.             -- Combine props for all instantiable classes
  265.             local allProps = getProps(className)
  266.             local assigned = {}
  267.             for _,propName in pairs(allProps) do
  268.                 if (not assigned[propName]) then
  269.                     assigned[propName] = true
  270.                    
  271.                     local defaultValuePtr = {}
  272.                     local writeable = pcall(function()
  273.                         local defaultVal = dummy[propName]
  274.                         dummy[propName] = defaultVal
  275.                         defaultValuePtr.exists = true --defaultVal ~= nil
  276.                     end)
  277.                    
  278.                     if (writeable and defaultValuePtr.exists) then
  279.                         local props = classProps[className]
  280.                         if (not props) then
  281.                             props = {}
  282.                             classProps[className] = props
  283.                         end
  284.                        
  285.                         table.insert(props, propName)
  286.                     end
  287.                 end
  288.             end
  289.         end
  290.     end
  291.    
  292.     _G.InstanceToJSXCache = {
  293.         Props = classProps,
  294.         Supers = classSupers,
  295.         Dummies = dummies,
  296.     }
  297. end
  298.  
  299. local getJSX; function getJSX(element, indentation)
  300.     local dummy = dummies[element.ClassName]
  301.     local writeableProps = classProps[element.ClassName] or {}
  302.    
  303.     -- Collect and format props
  304.     local propsToDisplay = {}
  305.     for _,propName in pairs(writeableProps) do
  306.         local prop = element[propName]
  307.        
  308.         local propToStr = PROP_TO_STRING(prop)
  309.        
  310.         if (propToStr and dummy[propName] ~= prop) then
  311.             table.insert(propsToDisplay, {Name = propName, Value = prop, ValueToString = propToStr})
  312.         end
  313.     end
  314.    
  315.     -- Filter for CSS replacement
  316.     for cssClassName, cssClassData in pairs(CSS_CLASSES) do
  317.         local matchesPrecondition = true
  318.         for propName, value in pairs(cssClassData.Precondition) do
  319.             local propMatchFound = false
  320.             for i,prop in ipairs(propsToDisplay) do
  321.                 if prop.Name == propName then
  322.                     if (prop.Value == value) then
  323.                         propMatchFound = true
  324.                     end
  325.                     break
  326.                 end
  327.             end
  328.            
  329.             if (not propMatchFound) then
  330.                 matchesPrecondition = false
  331.                 break
  332.             end
  333.         end
  334.        
  335.         if (matchesPrecondition) then
  336.             -- Mark indices for removal
  337.             local indicesToRemove = {}
  338.             for _,propName in ipairs(cssClassData.Override) do
  339.                 for i,prop in ipairs(propsToDisplay) do
  340.                     if prop.Name == propName then
  341.                         table.insert(indicesToRemove, i)
  342.                         break
  343.                     end
  344.                 end
  345.             end
  346.            
  347.             -- Remove overridden props
  348.             table.sort(indicesToRemove)
  349.             for removed, index in ipairs(indicesToRemove) do
  350.                 local offset = removed - 1
  351.                 table.remove(propsToDisplay, index - offset)
  352.             end
  353.            
  354.             -- Add class prop
  355.             table.insert(propsToDisplay, {
  356.                 Name = "className",
  357.                 ValueToString = "{..." .. cssClassName .. "}",
  358.             })
  359.         end
  360.     end
  361.    
  362.     -- Sort props
  363.     table.sort(propsToDisplay, function(tifb, tifa)
  364.         if not PROP_ORDER[tifb.Name] then
  365.             return false
  366.         end
  367.         if not PROP_ORDER[tifa.Name] then
  368.             return true
  369.         end
  370.         return PROP_ORDER[tifb.Name] < PROP_ORDER[tifa.Name]
  371.     end)
  372.    
  373.     local indentation = indentation or 0
  374.     local outputStr
  375.     local shortForm = INTRINSIC[element.ClassName]
  376.     if (shortForm) then
  377.         -- Type header
  378.         outputStr = string.rep("\t", indentation) .. "<" .. shortForm
  379.        
  380.         -- Display props
  381.         for _,propData in pairs(propsToDisplay) do
  382.             if (propData.Name == "className") then
  383.                 -- Display as CSS class
  384.                 outputStr = outputStr .. " " .. propData.ValueToString
  385.             elseif (propData.Name == "Name") then
  386.                 -- Display as Key
  387.                 outputStr = outputStr .. " Key=" .. propData.ValueToString
  388.             else
  389.                 outputStr = outputStr .. " " .. propData.Name .. "=" .. propData.ValueToString
  390.             end
  391.         end
  392.        
  393.         -- Children
  394.         local children = element:GetChildren()
  395.         if #children == 0 then
  396.             outputStr = outputStr .. "/>"
  397.         else
  398.             outputStr = outputStr .. ">\n"
  399.             for _,child in pairs(children) do
  400.                 outputStr = outputStr .. getJSX(child, indentation + 1) .. "\n"
  401.             end
  402.             outputStr = outputStr .. string.rep("\t", indentation) .. "</" .. shortForm .. ">"
  403.         end
  404.     else
  405.         -- Type header
  406.         outputStr = string.rep("\t", indentation) .. "Roact.createElement('" .. element.ClassName .. "', {"
  407.        
  408.         -- Props
  409.         if (#propsToDisplay == 0) then
  410.             outputStr = outputStr .. "}, {"
  411.         else
  412.             for _,propData in pairs(propsToDisplay) do
  413.                 -- Omit braces for non-JSX literals
  414.                 local propToStr = propData.ValueToString
  415.                 if propToStr:sub(1, 1) == "{" and propToStr:sub(#propToStr) == "}" then
  416.                     propToStr = propToStr:sub(2, #propToStr - 1)
  417.                 end
  418.                
  419.                 outputStr = outputStr .. "\n" .. string.rep("\t", indentation + 1) .. propData.Name .. ": " .. propToStr .. ","
  420.             end
  421.             outputStr = outputStr .. "\n" .. string.rep("\t", indentation) .. "}, {"
  422.         end
  423.        
  424.         -- Children
  425.         local children = element:GetChildren()
  426.         if #children == 0 then
  427.             outputStr = outputStr .. "})"
  428.         else
  429.             for i,child in ipairs(children) do
  430.                 local name = "[" .. tostring(i) .. "]"
  431.                 local isUnique = true
  432.                 for i2,child2 in ipairs(children) do
  433.                     if i2 ~= i then
  434.                         if child2.Name == child.Name then
  435.                             isUnique = false
  436.                             break
  437.                         end
  438.                     end
  439.                 end
  440.                 if isUnique then
  441.                     name = child.Name
  442.                 end
  443.                
  444.                 outputStr = outputStr .. "\n" .. string.rep("\t", indentation + 1) .. name .. ": ("
  445.                 outputStr = outputStr .. "\n" .. getJSX(child, indentation + 2)
  446.                 outputStr = outputStr .. "\n" .. string.rep("\t", indentation + 1) .. "),"
  447.             end
  448.             outputStr = outputStr .. "\n" .. string.rep("\t", indentation) .. "})"
  449.         end
  450.     end
  451.    
  452.     return outputStr
  453. end
  454.  
  455. local output = ""
  456. local prefix = ""
  457. for _,v in pairs(INPUT) do
  458.     local jsx = getJSX(v)
  459.     if (jsx) then
  460.         output = output .. prefix .. jsx
  461.         prefix = "\n"
  462.     end
  463. end
  464.  
  465. OUTPUT.Source = output
  466.  
  467. if (_G.ExposedPlugin) then
  468.     _G.ExposedPlugin:OpenScript(OUTPUT)
  469. else
  470.     game.Selection:Set({OUTPUT})
  471. end
Advertisement
Add Comment
Please, Sign In to add comment