mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-21 21:25:16 +00:00
Visualize table changes (#834)
Implements a pop out diff view for table properties like Attributes and Tags
This commit is contained in:
192
plugin/src/App/Components/TableDiffVisualizer/Array.lua
Normal file
192
plugin/src/App/Components/TableDiffVisualizer/Array.lua
Normal file
@@ -0,0 +1,192 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
|
||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||
local DisplayValue = require(Plugin.App.Components.PatchVisualizer.DisplayValue)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Array = Roact.Component:extend("Array")
|
||||
|
||||
function Array:init()
|
||||
self:setState({
|
||||
diff = self:calculateDiff(),
|
||||
})
|
||||
end
|
||||
|
||||
function Array:calculateDiff()
|
||||
--[[
|
||||
Find the indexes that are added or removed from the array,
|
||||
and display them side by side with gaps for the indexes that
|
||||
dont exist in the opposite array.
|
||||
]]
|
||||
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||
|
||||
local i, j = 1, 1
|
||||
local diff = {}
|
||||
|
||||
while i <= #oldTable and j <= #newTable do
|
||||
if oldTable[i] == newTable[j] then
|
||||
table.insert(diff, { oldTable[i], newTable[j] }) -- Unchanged
|
||||
i += 1
|
||||
j += 1
|
||||
elseif not table.find(newTable, oldTable[i], j) then
|
||||
table.insert(diff, { oldTable[i], nil }) -- Removal
|
||||
i += 1
|
||||
elseif not table.find(oldTable, newTable[j], i) then
|
||||
table.insert(diff, { nil, newTable[j] }) -- Addition
|
||||
j += 1
|
||||
else
|
||||
if table.find(newTable, oldTable[i], j) then
|
||||
table.insert(diff, { nil, newTable[j] }) -- Addition
|
||||
j += 1
|
||||
else
|
||||
table.insert(diff, { oldTable[i], nil }) -- Removal
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle remaining elements
|
||||
while i <= #oldTable do
|
||||
table.insert(diff, { oldTable[i], nil }) -- Remaining Removals
|
||||
i += 1
|
||||
end
|
||||
while j <= #newTable do
|
||||
table.insert(diff, { nil, newTable[j] }) -- Remaining Additions
|
||||
j += 1
|
||||
end
|
||||
|
||||
return diff
|
||||
end
|
||||
|
||||
function Array:didUpdate(previousProps)
|
||||
if previousProps.oldTable ~= self.props.oldTable or previousProps.newTable ~= self.props.newTable then
|
||||
self:setState({
|
||||
diff = self:calculateDiff(),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function Array:render()
|
||||
return Theme.with(function(theme)
|
||||
local diff = self.state.diff
|
||||
local lines = table.create(#diff)
|
||||
|
||||
for i, element in diff do
|
||||
local oldValue = element[1]
|
||||
local newValue = element[2]
|
||||
|
||||
local patchType = if oldValue == nil then "Add" elseif newValue == nil then "Remove" else "Remain"
|
||||
|
||||
table.insert(
|
||||
lines,
|
||||
e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 25),
|
||||
BackgroundTransparency = if patchType == "Remain" then 1 else self.props.transparency,
|
||||
BackgroundColor3 = if patchType == "Remain" then theme.Diff.Row else theme.Diff[patchType],
|
||||
BorderSizePixel = 0,
|
||||
LayoutOrder = i,
|
||||
}, {
|
||||
DiffIcon = if patchType ~= "Remain"
|
||||
then e("ImageLabel", {
|
||||
Image = Assets.Images.Diff[patchType],
|
||||
ImageColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 15, 0, 15),
|
||||
Position = UDim2.new(0, 7, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
})
|
||||
else nil,
|
||||
Old = e("Frame", {
|
||||
Size = UDim2.new(0.5, -30, 1, 0),
|
||||
Position = UDim2.new(0, 30, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Display = if oldValue ~= nil
|
||||
then e(DisplayValue, {
|
||||
value = oldValue,
|
||||
transparency = self.props.transparency,
|
||||
textColor = theme.Settings.Setting.DescriptionColor,
|
||||
})
|
||||
else nil,
|
||||
}),
|
||||
New = e("Frame", {
|
||||
Size = UDim2.new(0.5, -10, 1, 0),
|
||||
Position = UDim2.new(0.5, 5, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Display = if newValue ~= nil
|
||||
then e(DisplayValue, {
|
||||
value = newValue,
|
||||
transparency = self.props.transparency,
|
||||
textColor = theme.Settings.Setting.DescriptionColor,
|
||||
})
|
||||
else nil,
|
||||
}),
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
return Roact.createFragment({
|
||||
Headers = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 25),
|
||||
BackgroundTransparency = self.props.transparency:map(function(t)
|
||||
return 0.95 + (0.05 * t)
|
||||
end),
|
||||
BackgroundColor3 = theme.Diff.Row,
|
||||
}, {
|
||||
ColumnA = e("TextLabel", {
|
||||
Size = UDim2.new(0.5, -30, 1, 0),
|
||||
Position = UDim2.new(0, 30, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "Old",
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
}),
|
||||
ColumnB = e("TextLabel", {
|
||||
Size = UDim2.new(0.5, -10, 1, 0),
|
||||
Position = UDim2.new(0.5, 5, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "New",
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
}),
|
||||
Separator = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundTransparency = 0,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
}),
|
||||
}),
|
||||
KeyValues = e(ScrollingFrame, {
|
||||
position = UDim2.new(0, 1, 0, 25),
|
||||
size = UDim2.new(1, -2, 1, -27),
|
||||
scrollingDirection = Enum.ScrollingDirection.Y,
|
||||
transparency = self.props.transparency,
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
}),
|
||||
Lines = Roact.createFragment(lines),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Array
|
||||
208
plugin/src/App/Components/TableDiffVisualizer/Dictionary.lua
Normal file
208
plugin/src/App/Components/TableDiffVisualizer/Dictionary.lua
Normal file
@@ -0,0 +1,208 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
|
||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||
local DisplayValue = require(Plugin.App.Components.PatchVisualizer.DisplayValue)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local Dictionary = Roact.Component:extend("Dictionary")
|
||||
|
||||
function Dictionary:init()
|
||||
self:setState({
|
||||
diff = self:calculateDiff(),
|
||||
})
|
||||
end
|
||||
|
||||
function Dictionary:calculateDiff()
|
||||
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||
|
||||
-- Diff the two tables and find the added keys, removed keys, and changed keys
|
||||
local diff = {}
|
||||
|
||||
for key, oldValue in oldTable do
|
||||
local newValue = newTable[key]
|
||||
if newValue == nil then
|
||||
table.insert(diff, {
|
||||
key = key,
|
||||
patchType = "Remove",
|
||||
})
|
||||
elseif newValue ~= oldValue then
|
||||
-- Note: should this do some sort of deep comparison for various types?
|
||||
table.insert(diff, {
|
||||
key = key,
|
||||
patchType = "Edit",
|
||||
})
|
||||
else
|
||||
table.insert(diff, {
|
||||
key = key,
|
||||
patchType = "Remain",
|
||||
})
|
||||
end
|
||||
end
|
||||
for key in newTable do
|
||||
if oldTable[key] == nil then
|
||||
table.insert(diff, {
|
||||
key = key,
|
||||
patchType = "Add",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(diff, function(a, b)
|
||||
return a.key < b.key
|
||||
end)
|
||||
|
||||
return diff
|
||||
end
|
||||
|
||||
function Dictionary:didUpdate(previousProps)
|
||||
if previousProps.oldTable ~= self.props.oldTable or previousProps.newTable ~= self.props.newTable then
|
||||
self:setState({
|
||||
diff = self:calculateDiff(),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function Dictionary:render()
|
||||
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||
local diff = self.state.diff
|
||||
|
||||
return Theme.with(function(theme)
|
||||
local lines = table.create(#diff)
|
||||
for order, line in diff do
|
||||
local key = line.key
|
||||
local oldValue = oldTable[key]
|
||||
local newValue = newTable[key]
|
||||
|
||||
table.insert(
|
||||
lines,
|
||||
e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 25),
|
||||
LayoutOrder = order,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = if line.patchType == "Remain" then 1 else self.props.transparency,
|
||||
BackgroundColor3 = if line.patchType == "Remain"
|
||||
then theme.Diff.Row
|
||||
else theme.Diff[line.patchType],
|
||||
}, {
|
||||
DiffIcon = if line.patchType ~= "Remain"
|
||||
then e("ImageLabel", {
|
||||
Image = Assets.Images.Diff[line.patchType],
|
||||
ImageColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 15, 0, 15),
|
||||
Position = UDim2.new(0, 7, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
})
|
||||
else nil,
|
||||
KeyName = e("TextLabel", {
|
||||
Size = UDim2.new(0.3, -15, 1, 0),
|
||||
Position = UDim2.new(0, 30, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = key,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
}),
|
||||
OldValue = e("Frame", {
|
||||
Size = UDim2.new(0.35, -7, 1, 0),
|
||||
Position = UDim2.new(0.3, 15, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
e(DisplayValue, {
|
||||
value = oldValue,
|
||||
transparency = self.props.transparency,
|
||||
textColor = theme.Settings.Setting.DescriptionColor,
|
||||
}),
|
||||
}),
|
||||
NewValue = e("Frame", {
|
||||
Size = UDim2.new(0.35, -8, 1, 0),
|
||||
Position = UDim2.new(0.65, 8, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
e(DisplayValue, {
|
||||
value = newValue,
|
||||
transparency = self.props.transparency,
|
||||
textColor = theme.Settings.Setting.DescriptionColor,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
return Roact.createFragment({
|
||||
Headers = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 25),
|
||||
BackgroundTransparency = self.props.transparency:map(function(t)
|
||||
return 0.95 + (0.05 * t)
|
||||
end),
|
||||
BackgroundColor3 = theme.Diff.Row,
|
||||
}, {
|
||||
ColumnA = e("TextLabel", {
|
||||
Size = UDim2.new(0.3, -15, 1, 0),
|
||||
Position = UDim2.new(0, 30, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "Key",
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
}),
|
||||
ColumnB = e("TextLabel", {
|
||||
Size = UDim2.new(0.35, -7, 1, 0),
|
||||
Position = UDim2.new(0.3, 15, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "Old",
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
}),
|
||||
ColumnC = e("TextLabel", {
|
||||
Size = UDim2.new(0.35, -8, 1, 0),
|
||||
Position = UDim2.new(0.65, 8, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "New",
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.GothamBold,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
}),
|
||||
Separator = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BackgroundTransparency = 0,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
}),
|
||||
}),
|
||||
KeyValues = e(ScrollingFrame, {
|
||||
position = UDim2.new(0, 1, 0, 25),
|
||||
size = UDim2.new(1, -2, 1, -27),
|
||||
scrollingDirection = Enum.ScrollingDirection.Y,
|
||||
transparency = self.props.transparency,
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
}),
|
||||
Lines = Roact.createFragment(lines),
|
||||
}),
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return Dictionary
|
||||
48
plugin/src/App/Components/TableDiffVisualizer/init.lua
Normal file
48
plugin/src/App/Components/TableDiffVisualizer/init.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
local Array = require(script:FindFirstChild("Array"))
|
||||
local Dictionary = require(script:FindFirstChild("Dictionary"))
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local TableDiffVisualizer = Roact.Component:extend("TableDiffVisualizer")
|
||||
|
||||
function TableDiffVisualizer:render()
|
||||
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||
|
||||
-- Ensure we're diffing tables, not mixing types
|
||||
if type(oldTable) ~= "table" then
|
||||
oldTable = {}
|
||||
end
|
||||
if type(newTable) ~= "table" then
|
||||
newTable = {}
|
||||
end
|
||||
|
||||
local isArray = next(newTable) == 1 or next(oldTable) == 1
|
||||
|
||||
return e(BorderedContainer, {
|
||||
size = self.props.size,
|
||||
position = self.props.position,
|
||||
anchorPoint = self.props.anchorPoint,
|
||||
transparency = self.props.transparency,
|
||||
}, {
|
||||
Content = if isArray
|
||||
then e(Array, {
|
||||
oldTable = oldTable,
|
||||
newTable = newTable,
|
||||
transparency = self.props.transparency,
|
||||
})
|
||||
else e(Dictionary, {
|
||||
oldTable = oldTable,
|
||||
newTable = newTable,
|
||||
transparency = self.props.transparency,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return TableDiffVisualizer
|
||||
Reference in New Issue
Block a user