Visualize table changes (#834)

Implements a pop out diff view for table properties like Attributes and
Tags
This commit is contained in:
boatbomber
2024-01-22 12:26:41 -08:00
committed by GitHub
parent 1cc720ad34
commit 4018607b77
12 changed files with 755 additions and 176 deletions

View File

@@ -1,10 +1,8 @@
# Rojo Changelog
## Unreleased Changes
* Added popout diff visualizer for table properties like Attributes and Tags ([#834])
* Updated Theme to use Studio colors ([#838])
[#838]: https://github.com/rojo-rbx/rojo/pull/838
* Projects may now specify rules for syncing files as if they had a different file extension. ([#813])
This is specified via a new field on project files, `syncRules`:
@@ -53,6 +51,8 @@
**All** sync rules are reset between project files, so they must be specified in each one when nesting them. This is to ensure that nothing can break other projects by changing how files are synced!
[#813]: https://github.com/rojo-rbx/rojo/pull/813
[#834]: https://github.com/rojo-rbx/rojo/pull/834
[#838]: https://github.com/rojo-rbx/rojo/pull/838
## [7.4.0] - January 16, 2024
* Improved the visualization for array properties like Tags ([#829])

View File

@@ -14,6 +14,123 @@ local EMPTY_TABLE = {}
local e = Roact.createElement
local function ViewDiffButton(props)
return Theme.with(function(theme)
return e("TextButton", {
Text = "",
Size = UDim2.new(0.7, 0, 1, -4),
LayoutOrder = 2,
BackgroundTransparency = 1,
[Roact.Event.Activated] = props.onClick,
}, {
e(BorderedContainer, {
size = UDim2.new(1, 0, 1, 0),
transparency = props.transparency:map(function(t)
return 0.5 + (0.5 * t)
end),
}, {
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
Padding = UDim.new(0, 5),
}),
Label = e("TextLabel", {
Text = "View Diff",
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,
TextColor3 = theme.Settings.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
TextTruncate = Enum.TextTruncate.AtEnd,
Size = UDim2.new(0, 65, 1, 0),
LayoutOrder = 1,
}),
Icon = e("ImageLabel", {
Image = Assets.Images.Icons.Expand,
ImageColor3 = theme.Settings.Setting.DescriptionColor,
ImageTransparency = props.transparency,
Size = UDim2.new(0, 16, 0, 16),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
LayoutOrder = 2,
}),
}),
})
end)
end
local function RowContent(props)
local values = props.values
local metadata = props.metadata
if props.showStringDiff and values[1] == "Source" then
-- Special case for .Source updates
return e(ViewDiffButton, {
transparency = props.transparency,
onClick = function()
if not props.showStringDiff then
return
end
props.showStringDiff(tostring(values[2]), tostring(values[3]))
end,
})
end
if props.showTableDiff and (type(values[2]) == "table" or type(values[3]) == "table") then
-- Special case for table properties (like Attributes/Tags)
return e(ViewDiffButton, {
transparency = props.transparency,
onClick = function()
if not props.showTableDiff then
return
end
props.showTableDiff(values[2], values[3])
end,
})
end
return Theme.with(function(theme)
return Roact.createFragment({
ColumnB = e(
"Frame",
{
BackgroundTransparency = 1,
Size = UDim2.new(0.35, 0, 1, 0),
LayoutOrder = 2,
},
e(DisplayValue, {
value = values[2],
transparency = props.transparency,
textColor = if metadata.isWarning
then theme.Diff.Warning
else theme.Settings.Setting.DescriptionColor,
})
),
ColumnC = e(
"Frame",
{
BackgroundTransparency = 1,
Size = UDim2.new(0.35, 0, 1, 0),
LayoutOrder = 3,
},
e(DisplayValue, {
value = values[3],
transparency = props.transparency,
textColor = if metadata.isWarning
then theme.Diff.Warning
else theme.Settings.Setting.DescriptionColor,
})
),
})
end)
end
local ChangeList = Roact.Component:extend("ChangeList")
function ChangeList:init()
@@ -36,6 +153,7 @@ function ChangeList:render()
PaddingRight = UDim.new(0, 5),
}
local headerRow = changes[1]
local headers = e("Frame", {
Size = UDim2.new(1, 0, 0, 30),
BackgroundTransparency = rowTransparency,
@@ -49,8 +167,8 @@ function ChangeList:render()
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
A = e("TextLabel", {
Text = tostring(changes[1][1]),
ColumnA = e("TextLabel", {
Text = tostring(headerRow[1]),
BackgroundTransparency = 1,
Font = Enum.Font.GothamBold,
TextSize = 14,
@@ -61,8 +179,8 @@ function ChangeList:render()
Size = UDim2.new(0.3, 0, 1, 0),
LayoutOrder = 1,
}),
B = e("TextLabel", {
Text = tostring(changes[1][2]),
ColumnB = e("TextLabel", {
Text = tostring(headerRow[2]),
BackgroundTransparency = 1,
Font = Enum.Font.GothamBold,
TextSize = 14,
@@ -73,8 +191,8 @@ function ChangeList:render()
Size = UDim2.new(0.35, 0, 1, 0),
LayoutOrder = 2,
}),
C = e("TextLabel", {
Text = tostring(changes[1][3]),
ColumnC = e("TextLabel", {
Text = tostring(headerRow[3]),
BackgroundTransparency = 1,
Font = Enum.Font.GothamBold,
TextSize = 14,
@@ -95,89 +213,6 @@ function ChangeList:render()
local metadata = values[4] or EMPTY_TABLE
local isWarning = metadata.isWarning
-- Special case for .Source updates
-- because we want to display a syntax highlighted diff for better UX
if self.props.showSourceDiff and tostring(values[1]) == "Source" then
rows[row] = e("Frame", {
Size = UDim2.new(1, 0, 0, 30),
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
BackgroundColor3 = theme.Diff.Row,
BorderSizePixel = 0,
LayoutOrder = row,
}, {
Padding = e("UIPadding", pad),
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
A = e("TextLabel", {
Text = (if isWarning then "" else "") .. tostring(values[1]),
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,
TextColor3 = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
TextTruncate = Enum.TextTruncate.AtEnd,
Size = UDim2.new(0.3, 0, 1, 0),
LayoutOrder = 1,
}),
Button = e("TextButton", {
Text = "",
Size = UDim2.new(0.7, 0, 1, -4),
LayoutOrder = 2,
BackgroundTransparency = 1,
[Roact.Event.Activated] = function()
if props.showSourceDiff then
props.showSourceDiff(tostring(values[2]), tostring(values[3]))
end
end,
}, {
e(BorderedContainer, {
size = UDim2.new(1, 0, 1, 0),
transparency = self.props.transparency:map(function(t)
return 0.5 + (0.5 * t)
end),
}, {
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
Padding = UDim.new(0, 5),
}),
Label = e("TextLabel", {
Text = "View Diff",
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,
TextColor3 = theme.Settings.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
TextTruncate = Enum.TextTruncate.AtEnd,
Size = UDim2.new(0, 65, 1, 0),
LayoutOrder = 1,
}),
Icon = e("ImageLabel", {
Image = Assets.Images.Icons.Expand,
ImageColor3 = theme.Settings.Setting.DescriptionColor,
ImageTransparency = self.props.transparency,
Size = UDim2.new(0, 16, 0, 16),
Position = UDim2.new(0.5, 0, 0.5, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
LayoutOrder = 2,
}),
}),
}),
})
continue
end
rows[row] = e("Frame", {
Size = UDim2.new(1, 0, 0, 30),
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
@@ -192,7 +227,7 @@ function ChangeList:render()
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
A = e("TextLabel", {
ColumnA = e("TextLabel", {
Text = (if isWarning then "" else "") .. tostring(values[1]),
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
@@ -204,32 +239,13 @@ function ChangeList:render()
Size = UDim2.new(0.3, 0, 1, 0),
LayoutOrder = 1,
}),
B = e(
"Frame",
{
BackgroundTransparency = 1,
Size = UDim2.new(0.35, 0, 1, 0),
LayoutOrder = 2,
},
e(DisplayValue, {
value = values[2],
transparency = props.transparency,
textColor = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
})
),
C = e(
"Frame",
{
BackgroundTransparency = 1,
Size = UDim2.new(0.35, 0, 1, 0),
LayoutOrder = 3,
},
e(DisplayValue, {
value = values[3],
transparency = props.transparency,
textColor = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
})
),
Content = e(RowContent, {
values = values,
metadata = metadata,
transparency = props.transparency,
showStringDiff = props.showStringDiff,
showTableDiff = props.showTableDiff,
}),
})
end

View File

@@ -30,7 +30,7 @@ local function DisplayValue(props)
}),
}),
Label = e("TextLabel", {
Text = string.format("%d,%d,%d", props.value.R * 255, props.value.G * 255, props.value.B * 255),
Text = string.format("%d, %d, %d", props.value.R * 255, props.value.G * 255, props.value.B * 255),
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,
@@ -104,8 +104,13 @@ local function DisplayValue(props)
-- Or special text handling tostring for some?
-- Will add as needed, let's see what cases arise.
local textRepresentation = string.gsub(tostring(props.value), "%s", " ")
if t == "string" then
textRepresentation = '"' .. textRepresentation .. '"'
end
return e("TextLabel", {
Text = string.gsub(tostring(props.value), "%s", " "),
Text = textRepresentation,
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,

View File

@@ -34,7 +34,8 @@ function Expansion:render()
ChangeList = e(ChangeList, {
changes = props.changeList,
transparency = props.transparency,
showSourceDiff = props.showSourceDiff,
showStringDiff = props.showStringDiff,
showTableDiff = props.showTableDiff,
}),
})
end
@@ -171,7 +172,8 @@ function DomLabel:render()
indent = indent,
transparency = props.transparency,
changeList = props.changeList,
showSourceDiff = props.showSourceDiff,
showStringDiff = props.showStringDiff,
showTableDiff = props.showTableDiff,
})
else nil,
DiffIcon = if props.patchType

View File

@@ -76,7 +76,8 @@ function PatchVisualizer:render()
changeList = node.changeList,
depth = depth,
transparency = self.props.transparency,
showSourceDiff = self.props.showSourceDiff,
showStringDiff = self.props.showStringDiff,
showTableDiff = self.props.showTableDiff,
})
)
end

View File

@@ -10,6 +10,12 @@ local bindingUtil = require(Plugin.App.bindingUtil)
local e = Roact.createElement
local scrollDirToAutoSize = {
[Enum.ScrollingDirection.X] = Enum.AutomaticSize.X,
[Enum.ScrollingDirection.Y] = Enum.AutomaticSize.Y,
[Enum.ScrollingDirection.XY] = Enum.AutomaticSize.XY,
}
local function ScrollingFrame(props)
return Theme.with(function(theme)
return e("ScrollingFrame", {
@@ -28,16 +34,21 @@ local function ScrollingFrame(props)
Size = props.size,
Position = props.position,
AnchorPoint = props.anchorPoint,
CanvasSize = props.contentSize:map(function(value)
return UDim2.new(
0,
if (props.scrollingDirection and props.scrollingDirection ~= Enum.ScrollingDirection.Y)
then value.X
else 0,
0,
value.Y
)
end),
CanvasSize = if props.contentSize
then props.contentSize:map(function(value)
return UDim2.new(
0,
if (props.scrollingDirection and props.scrollingDirection ~= Enum.ScrollingDirection.Y)
then value.X
else 0,
0,
value.Y
)
end)
else UDim2.new(),
AutomaticCanvasSize = if props.contentSize == nil
then scrollDirToAutoSize[props.scrollingDirection or Enum.ScrollingDirection.XY]
else nil,
BorderSizePixel = 0,
BackgroundTransparency = 1,

View File

@@ -52,7 +52,7 @@ function StringDiffVisualizer:updateScriptBackground()
end
function StringDiffVisualizer:didUpdate(previousProps)
if previousProps.oldText ~= self.props.oldText or previousProps.newText ~= self.props.newText then
if previousProps.oldString ~= self.props.oldString or previousProps.newString ~= self.props.newString then
self:calculateContentSize()
local add, remove = self:calculateDiffLines()
self:setState({
@@ -63,28 +63,28 @@ function StringDiffVisualizer:didUpdate(previousProps)
end
function StringDiffVisualizer:calculateContentSize()
local oldText, newText = self.props.oldText, self.props.newText
local oldString, newString = self.props.oldString, self.props.newString
local oldTextBounds = TextService:GetTextSize(oldText, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
local newTextBounds = TextService:GetTextSize(newText, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
local oldStringBounds = TextService:GetTextSize(oldString, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
local newStringBounds = TextService:GetTextSize(newString, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
self.setContentSize(
Vector2.new(math.max(oldTextBounds.X, newTextBounds.X), math.max(oldTextBounds.Y, newTextBounds.Y))
Vector2.new(math.max(oldStringBounds.X, newStringBounds.X), math.max(oldStringBounds.Y, newStringBounds.Y))
)
end
function StringDiffVisualizer:calculateDiffLines()
local oldText, newText = self.props.oldText, self.props.newText
local oldString, newString = self.props.oldString, self.props.newString
-- Diff the two texts
local startClock = os.clock()
local diffs = StringDiff.findDiffs(oldText, newText)
local diffs = StringDiff.findDiffs(oldString, newString)
local stopClock = os.clock()
Log.trace(
"Diffing {} byte and {} byte strings took {} microseconds and found {} diff sections",
#oldText,
#newText,
#oldString,
#newString,
math.round((stopClock - startClock) * 1000 * 1000),
#diffs
)
@@ -137,7 +137,7 @@ function StringDiffVisualizer:calculateDiffLines()
end
function StringDiffVisualizer:render()
local oldText, newText = self.props.oldText, self.props.newText
local oldString, newString = self.props.oldString, self.props.newString
return Theme.with(function(theme)
return e(BorderedContainer, {
@@ -175,7 +175,7 @@ function StringDiffVisualizer:render()
Source = e(CodeLabel, {
size = UDim2.new(1, 0, 1, 0),
position = UDim2.new(0, 0, 0, 0),
text = oldText,
text = oldString,
lineBackground = theme.Diff.Remove,
markedLines = self.state.remove,
}),
@@ -190,7 +190,7 @@ function StringDiffVisualizer:render()
Source = e(CodeLabel, {
size = UDim2.new(1, 0, 1, 0),
position = UDim2.new(0, 0, 0, 0),
text = newText,
text = newString,
lineBackground = theme.Diff.Add,
markedLines = self.state.add,
}),

View 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

View 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

View 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

View File

@@ -14,6 +14,7 @@ local StudioPluginGui = require(Plugin.App.Components.Studio.StudioPluginGui)
local Tooltip = require(Plugin.App.Components.Tooltip)
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
local StringDiffVisualizer = require(Plugin.App.Components.StringDiffVisualizer)
local TableDiffVisualizer = require(Plugin.App.Components.TableDiffVisualizer)
local e = Roact.createElement
@@ -24,9 +25,12 @@ function ConfirmingPage:init()
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
self:setState({
showingSourceDiff = false,
oldSource = "",
newSource = "",
showingStringDiff = false,
oldString = "",
newString = "",
showingTableDiff = false,
oldTable = {},
newTable = {},
})
end
@@ -63,11 +67,18 @@ function ConfirmingPage:render()
patch = self.props.confirmData.patch,
instanceMap = self.props.confirmData.instanceMap,
showSourceDiff = function(oldSource: string, newSource: string)
showStringDiff = function(oldString: string, newString: string)
self:setState({
showingSourceDiff = true,
oldSource = oldSource,
newSource = newSource,
showingStringDiff = true,
oldString = oldString,
newString = newString,
})
end,
showTableDiff = function(oldTable: { [any]: any? }, newTable: { [any]: any? })
self:setState({
showingTableDiff = true,
oldTable = oldTable,
newTable = newTable,
})
end,
}),
@@ -136,10 +147,10 @@ function ConfirmingPage:render()
PaddingRight = UDim.new(0, 20),
}),
SourceDiff = e(StudioPluginGui, {
id = "Rojo_ConfirmingSourceDiff",
title = "Source diff",
active = self.state.showingSourceDiff,
StringDiff = e(StudioPluginGui, {
id = "Rojo_ConfirmingStringDiff",
title = "String diff",
active = self.state.showingStringDiff,
isEphemeral = true,
initDockState = Enum.InitialDockState.Float,
@@ -151,7 +162,7 @@ function ConfirmingPage:render()
onClose = function()
self:setState({
showingSourceDiff = false,
showingStringDiff = false,
})
end,
}, {
@@ -167,8 +178,46 @@ function ConfirmingPage:render()
anchorPoint = Vector2.new(0, 0),
transparency = self.props.transparency,
oldText = self.state.oldSource,
newText = self.state.newSource,
oldString = self.state.oldString,
newString = self.state.newString,
}),
}),
}),
}),
TableDiff = e(StudioPluginGui, {
id = "Rojo_ConfirmingTableDiff",
title = "Table diff",
active = self.state.showingTableDiff,
isEphemeral = true,
initDockState = Enum.InitialDockState.Float,
overridePreviousState = true,
floatingSize = Vector2.new(500, 350),
minimumSize = Vector2.new(400, 250),
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
onClose = function()
self:setState({
showingTableDiff = false,
})
end,
}, {
TooltipsProvider = e(Tooltip.Provider, nil, {
Tooltips = e(Tooltip.Container, nil),
Content = e("Frame", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
}, {
e(TableDiffVisualizer, {
size = UDim2.new(1, -10, 1, -10),
position = UDim2.new(0, 5, 0, 5),
anchorPoint = Vector2.new(0, 0),
transparency = self.props.transparency,
oldTable = self.state.oldTable,
newTable = self.state.newTable,
}),
}),
}),

View File

@@ -18,6 +18,7 @@ local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
local Tooltip = require(Plugin.App.Components.Tooltip)
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
local StringDiffVisualizer = require(Plugin.App.Components.StringDiffVisualizer)
local TableDiffVisualizer = require(Plugin.App.Components.TableDiffVisualizer)
local e = Roact.createElement
@@ -97,7 +98,8 @@ function ChangesDrawer:render()
patchTree = self.props.patchTree,
showSourceDiff = self.props.showSourceDiff,
showStringDiff = self.props.showStringDiff,
showTableDiff = self.props.showTableDiff,
}),
})
end)
@@ -239,9 +241,9 @@ function ConnectedPage:init()
self:setState({
renderChanges = false,
hoveringChangeInfo = false,
showingSourceDiff = false,
oldSource = "",
newSource = "",
showingStringDiff = false,
oldString = "",
newString = "",
})
self.changeInfoText, self.setChangeInfoText = Roact.createBinding("")
@@ -258,7 +260,7 @@ function ConnectedPage:didUpdate(previousProps)
-- New patch recieved
self:startChangeInfoTextUpdater()
self:setState({
showingSourceDiff = false,
showingStringDiff = false,
})
end
end
@@ -387,11 +389,18 @@ function ConnectedPage:render()
height = self.changeDrawerHeight,
layoutOrder = 5,
showSourceDiff = function(oldSource: string, newSource: string)
showStringDiff = function(oldString: string, newString: string)
self:setState({
showingSourceDiff = true,
oldSource = oldSource,
newSource = newSource,
showingStringDiff = true,
oldString = oldString,
newString = newString,
})
end,
showTableDiff = function(oldTable: { [any]: any? }, newTable: { [any]: any? })
self:setState({
showingTableDiff = true,
oldTable = oldTable,
newTable = newTable,
})
end,
@@ -403,10 +412,10 @@ function ConnectedPage:render()
end,
}),
SourceDiff = e(StudioPluginGui, {
id = "Rojo_ConnectedSourceDiff",
title = "Source diff",
active = self.state.showingSourceDiff,
StringDiff = e(StudioPluginGui, {
id = "Rojo_ConnectedStringDiff",
title = "String diff",
active = self.state.showingStringDiff,
isEphemeral = true,
initDockState = Enum.InitialDockState.Float,
@@ -418,7 +427,7 @@ function ConnectedPage:render()
onClose = function()
self:setState({
showingSourceDiff = false,
showingStringDiff = false,
})
end,
}, {
@@ -434,8 +443,46 @@ function ConnectedPage:render()
anchorPoint = Vector2.new(0, 0),
transparency = self.props.transparency,
oldText = self.state.oldSource,
newText = self.state.newSource,
oldString = self.state.oldString,
newString = self.state.newString,
}),
}),
}),
}),
TableDiff = e(StudioPluginGui, {
id = "Rojo_ConnectedTableDiff",
title = "Table diff",
active = self.state.showingTableDiff,
isEphemeral = true,
initDockState = Enum.InitialDockState.Float,
overridePreviousState = false,
floatingSize = Vector2.new(500, 350),
minimumSize = Vector2.new(400, 250),
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
onClose = function()
self:setState({
showingTableDiff = false,
})
end,
}, {
TooltipsProvider = e(Tooltip.Provider, nil, {
Tooltips = e(Tooltip.Container, nil),
Content = e("Frame", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
}, {
e(TableDiffVisualizer, {
size = UDim2.new(1, -10, 1, -10),
position = UDim2.new(0, 5, 0, 5),
anchorPoint = Vector2.new(0, 0),
transparency = self.props.transparency,
oldTable = self.state.oldTable,
newTable = self.state.newTable,
}),
}),
}),