mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Patch visualizer redesign (#883)
This commit is contained in:
126
plugin/src/App/Components/ClassIcon.lua
Normal file
126
plugin/src/App/Components/ClassIcon.lua
Normal file
@@ -0,0 +1,126 @@
|
||||
local StudioService = game:GetService("StudioService")
|
||||
local AssetService = game:GetService("AssetService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local EditableImage = require(Plugin.App.Components.EditableImage)
|
||||
|
||||
local imageCache = {}
|
||||
local function getImageSizeAndPixels(image)
|
||||
if not imageCache[image] then
|
||||
local editableImage = AssetService:CreateEditableImageAsync(image)
|
||||
imageCache[image] = {
|
||||
Size = editableImage.Size,
|
||||
Pixels = editableImage:ReadPixels(Vector2.zero, editableImage.Size),
|
||||
}
|
||||
end
|
||||
|
||||
return imageCache[image].Size, table.clone(imageCache[image].Pixels)
|
||||
end
|
||||
|
||||
local function getRecoloredClassIcon(className, color)
|
||||
local iconProps = StudioService:GetClassIcon(className)
|
||||
|
||||
if iconProps and color then
|
||||
local success, editableImageSize, editableImagePixels = pcall(function()
|
||||
local size, pixels = getImageSizeAndPixels(iconProps.Image)
|
||||
|
||||
local minVal, maxVal = math.huge, -math.huge
|
||||
for i = 1, #pixels, 4 do
|
||||
if pixels[i + 3] == 0 then
|
||||
continue
|
||||
end
|
||||
local pixelVal = math.max(pixels[i], pixels[i + 1], pixels[i + 2])
|
||||
|
||||
minVal = math.min(minVal, pixelVal)
|
||||
maxVal = math.max(maxVal, pixelVal)
|
||||
end
|
||||
|
||||
local hue, sat, val = color:ToHSV()
|
||||
for i = 1, #pixels, 4 do
|
||||
if pixels[i + 3] == 0 then
|
||||
continue
|
||||
end
|
||||
|
||||
local pixelVal = math.max(pixels[i], pixels[i + 1], pixels[i + 2])
|
||||
local newVal = val
|
||||
if minVal < maxVal then
|
||||
-- Remap minVal - maxVal to val*0.9 - val
|
||||
newVal = val * (0.9 + 0.1 * (pixelVal - minVal) / (maxVal - minVal))
|
||||
end
|
||||
|
||||
local newPixelColor = Color3.fromHSV(hue, sat, newVal)
|
||||
pixels[i], pixels[i + 1], pixels[i + 2] = newPixelColor.R, newPixelColor.G, newPixelColor.B
|
||||
end
|
||||
return size, pixels
|
||||
end)
|
||||
if success then
|
||||
iconProps.EditableImagePixels = editableImagePixels
|
||||
iconProps.EditableImageSize = editableImageSize
|
||||
end
|
||||
end
|
||||
|
||||
return iconProps
|
||||
end
|
||||
|
||||
local ClassIcon = Roact.PureComponent:extend("ClassIcon")
|
||||
|
||||
function ClassIcon:init()
|
||||
self.state = {
|
||||
iconProps = nil,
|
||||
}
|
||||
end
|
||||
|
||||
function ClassIcon:updateIcon()
|
||||
local props = self.props
|
||||
local iconProps = getRecoloredClassIcon(props.className, props.color)
|
||||
self:setState({
|
||||
iconProps = iconProps,
|
||||
})
|
||||
end
|
||||
|
||||
function ClassIcon:didMount()
|
||||
self:updateIcon()
|
||||
end
|
||||
|
||||
function ClassIcon:didUpdate(lastProps)
|
||||
if lastProps.className ~= self.props.className or lastProps.color ~= self.props.color then
|
||||
self:updateIcon()
|
||||
end
|
||||
end
|
||||
|
||||
function ClassIcon:render()
|
||||
local iconProps = self.state.iconProps
|
||||
if not iconProps then
|
||||
return nil
|
||||
end
|
||||
|
||||
return e(
|
||||
"ImageLabel",
|
||||
{
|
||||
Size = self.props.size,
|
||||
Position = self.props.position,
|
||||
LayoutOrder = self.props.layoutOrder,
|
||||
AnchorPoint = self.props.anchorPoint,
|
||||
ImageTransparency = self.props.transparency,
|
||||
Image = iconProps.Image,
|
||||
ImageRectOffset = iconProps.ImageRectOffset,
|
||||
ImageRectSize = iconProps.ImageRectSize,
|
||||
BackgroundTransparency = 1,
|
||||
},
|
||||
if iconProps.EditableImagePixels
|
||||
then e(EditableImage, {
|
||||
size = iconProps.EditableImageSize,
|
||||
pixels = iconProps.EditableImagePixels,
|
||||
})
|
||||
else nil
|
||||
)
|
||||
end
|
||||
|
||||
return ClassIcon
|
||||
41
plugin/src/App/Components/EditableImage.lua
Normal file
41
plugin/src/App/Components/EditableImage.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local EditableImage = Roact.PureComponent:extend("EditableImage")
|
||||
|
||||
function EditableImage:init()
|
||||
self.ref = Roact.createRef()
|
||||
end
|
||||
|
||||
function EditableImage:writePixels()
|
||||
local image = self.ref.current
|
||||
if not image then
|
||||
return
|
||||
end
|
||||
if not self.props.pixels then
|
||||
return
|
||||
end
|
||||
|
||||
image:WritePixels(Vector2.zero, self.props.size, self.props.pixels)
|
||||
end
|
||||
|
||||
function EditableImage:render()
|
||||
return e("EditableImage", {
|
||||
Size = self.props.size,
|
||||
[Roact.Ref] = self.ref,
|
||||
})
|
||||
end
|
||||
|
||||
function EditableImage:didMount()
|
||||
self:writePixels()
|
||||
end
|
||||
|
||||
function EditableImage:didUpdate()
|
||||
self:writePixels()
|
||||
end
|
||||
|
||||
return EditableImage
|
||||
@@ -155,7 +155,7 @@ function ChangeList:render()
|
||||
|
||||
local headerRow = changes[1]
|
||||
local headers = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 30),
|
||||
Size = UDim2.new(1, 0, 0, 24),
|
||||
BackgroundTransparency = rowTransparency,
|
||||
BackgroundColor3 = theme.Diff.Row,
|
||||
LayoutOrder = 0,
|
||||
@@ -214,7 +214,7 @@ function ChangeList:render()
|
||||
local isWarning = metadata.isWarning
|
||||
|
||||
rows[row] = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 30),
|
||||
Size = UDim2.new(1, 0, 0, 24),
|
||||
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
|
||||
BackgroundColor3 = theme.Diff.Row,
|
||||
BorderSizePixel = 0,
|
||||
@@ -269,8 +269,8 @@ function ChangeList:render()
|
||||
}, {
|
||||
Headers = headers,
|
||||
Values = e(ScrollingFrame, {
|
||||
size = UDim2.new(1, 0, 1, -30),
|
||||
position = UDim2.new(0, 0, 0, 30),
|
||||
size = UDim2.new(1, 0, 1, -24),
|
||||
position = UDim2.new(0, 0, 0, 24),
|
||||
contentSize = self.contentSize,
|
||||
transparency = props.transparency,
|
||||
}, rows),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
local SelectionService = game:GetService("Selection")
|
||||
local StudioService = game:GetService("StudioService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
@@ -15,7 +14,8 @@ local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
local e = Roact.createElement
|
||||
|
||||
local ChangeList = require(script.Parent.ChangeList)
|
||||
local Tooltip = require(script.Parent.Parent.Tooltip)
|
||||
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||
local ClassIcon = require(Plugin.App.Components.ClassIcon)
|
||||
|
||||
local Expansion = Roact.Component:extend("Expansion")
|
||||
|
||||
@@ -28,8 +28,8 @@ function Expansion:render()
|
||||
|
||||
return e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, -props.indent, 1, -30),
|
||||
Position = UDim2.new(0, props.indent, 0, 30),
|
||||
Size = UDim2.new(1, -props.indent, 1, -24),
|
||||
Position = UDim2.new(0, props.indent, 0, 24),
|
||||
}, {
|
||||
ChangeList = e(ChangeList, {
|
||||
changes = props.changeList,
|
||||
@@ -44,7 +44,7 @@ local DomLabel = Roact.Component:extend("DomLabel")
|
||||
|
||||
function DomLabel:init()
|
||||
local initHeight = self.props.elementHeight:getValue()
|
||||
self.expanded = initHeight > 30
|
||||
self.expanded = initHeight > 24
|
||||
|
||||
self.motor = Flipper.SingleMotor.new(initHeight)
|
||||
self.binding = bindingUtil.fromMotor(self.motor)
|
||||
@@ -53,7 +53,7 @@ function DomLabel:init()
|
||||
renderExpansion = self.expanded,
|
||||
})
|
||||
self.motor:onStep(function(value)
|
||||
local renderExpansion = value > 30
|
||||
local renderExpansion = value > 24
|
||||
|
||||
self.props.setElementHeight(value)
|
||||
if self.props.updateEvent then
|
||||
@@ -81,7 +81,7 @@ function DomLabel:didUpdate(prevProps)
|
||||
then
|
||||
-- Close the expansion when the domlabel is changed to a different thing
|
||||
self.expanded = false
|
||||
self.motor:setGoal(Flipper.Spring.new(30, {
|
||||
self.motor:setGoal(Flipper.Spring.new(24, {
|
||||
frequency = 5,
|
||||
dampingRatio = 1,
|
||||
}))
|
||||
@@ -90,17 +90,49 @@ end
|
||||
|
||||
function DomLabel:render()
|
||||
local props = self.props
|
||||
local depth = props.depth or 1
|
||||
|
||||
return Theme.with(function(theme)
|
||||
local iconProps = StudioService:GetClassIcon(props.className)
|
||||
local indent = (props.depth or 0) * 20 + 25
|
||||
local color = if props.isWarning
|
||||
then theme.Diff.Warning
|
||||
elseif props.patchType then theme.Diff[props.patchType]
|
||||
else theme.TextColor
|
||||
|
||||
local indent = (depth - 1) * 12 + 15
|
||||
|
||||
-- Line guides help indent depth remain readable
|
||||
local lineGuides = {}
|
||||
for i = 1, props.depth or 0 do
|
||||
lineGuides["Line_" .. i] = e("Frame", {
|
||||
Size = UDim2.new(0, 2, 1, 2),
|
||||
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
||||
for i = 2, depth do
|
||||
if props.depthsComplete[i] then
|
||||
continue
|
||||
end
|
||||
if props.isFinalChild and i == depth then
|
||||
-- This line stops halfway down to merge with our connector for the right angle
|
||||
lineGuides["Line_" .. i] = e("Frame", {
|
||||
Size = UDim2.new(0, 2, 0, 15),
|
||||
Position = UDim2.new(0, (12 * (i - 1)) + 6, 0, -1),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = props.transparency,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
})
|
||||
else
|
||||
-- All other lines go all the way
|
||||
-- with the exception of the final element, which stops halfway down
|
||||
lineGuides["Line_" .. i] = e("Frame", {
|
||||
Size = UDim2.new(0, 2, 1, if props.isFinalElement then -9 else 2),
|
||||
Position = UDim2.new(0, (12 * (i - 1)) + 6, 0, -1),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = props.transparency,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if depth ~= 1 then
|
||||
lineGuides["Connector"] = e("Frame", {
|
||||
Size = UDim2.new(0, 8, 0, 2),
|
||||
Position = UDim2.new(0, 2 + (12 * props.depth), 0, 12),
|
||||
AnchorPoint = Vector2.xAxis,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = props.transparency,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||
@@ -109,9 +141,8 @@ function DomLabel:render()
|
||||
|
||||
return e("Frame", {
|
||||
ClipsDescendants = true,
|
||||
BackgroundColor3 = if props.patchType then theme.Diff[props.patchType] else nil,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = props.patchType and props.transparency or 1,
|
||||
BackgroundTransparency = if props.elementIndex % 2 == 0 then 0.985 else 1,
|
||||
BackgroundColor3 = theme.Diff.Row,
|
||||
Size = self.binding:map(function(expand)
|
||||
return UDim2.new(1, 0, 0, expand)
|
||||
end),
|
||||
@@ -141,8 +172,8 @@ function DomLabel:render()
|
||||
|
||||
if props.changeList then
|
||||
self.expanded = not self.expanded
|
||||
local goalHeight = 30
|
||||
+ (if self.expanded then math.clamp(#props.changeList * 30, 30, 30 * 6) else 0)
|
||||
local goalHeight = 24
|
||||
+ (if self.expanded then math.clamp(#props.changeList * 24, 24, 24 * 6) else 0)
|
||||
self.motor:setGoal(Flipper.Spring.new(goalHeight, {
|
||||
frequency = 5,
|
||||
dampingRatio = 1,
|
||||
@@ -174,40 +205,74 @@ function DomLabel:render()
|
||||
DiffIcon = if props.patchType
|
||||
then e("ImageLabel", {
|
||||
Image = Assets.Images.Diff[props.patchType],
|
||||
ImageColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
ImageColor3 = color,
|
||||
ImageTransparency = props.transparency,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 20, 0, 20),
|
||||
Position = UDim2.new(0, 0, 0, 15),
|
||||
Size = UDim2.new(0, 14, 0, 14),
|
||||
Position = UDim2.new(0, 0, 0, 12),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
})
|
||||
else nil,
|
||||
ClassIcon = e("ImageLabel", {
|
||||
Image = iconProps.Image,
|
||||
ImageTransparency = props.transparency,
|
||||
ImageRectOffset = iconProps.ImageRectOffset,
|
||||
ImageRectSize = iconProps.ImageRectSize,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 20, 0, 20),
|
||||
Position = UDim2.new(0, indent, 0, 15),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
ClassIcon = e(ClassIcon, {
|
||||
className = props.className,
|
||||
color = color,
|
||||
transparency = props.transparency,
|
||||
size = UDim2.new(0, 16, 0, 16),
|
||||
position = UDim2.new(0, indent + 2, 0, 12),
|
||||
anchorPoint = Vector2.new(0, 0.5),
|
||||
}),
|
||||
InstanceName = e("TextLabel", {
|
||||
Text = (if props.isWarning then "⚠ " else "") .. props.name .. (props.hint and string.format(
|
||||
' <font color="#%s">%s</font>',
|
||||
theme.AddressEntry.PlaceholderColor:ToHex(),
|
||||
props.hint
|
||||
) or ""),
|
||||
Text = (if props.isWarning then "⚠ " else "") .. props.name,
|
||||
RichText = true,
|
||||
BackgroundTransparency = 1,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
Font = if props.patchType then Enum.Font.GothamBold else Enum.Font.GothamMedium,
|
||||
TextSize = 14,
|
||||
TextColor3 = if props.isWarning then theme.Diff.Warning else theme.TextColor,
|
||||
TextColor3 = color,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = props.transparency,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
Size = UDim2.new(1, -indent - 50, 0, 30),
|
||||
Position = UDim2.new(0, indent + 30, 0, 0),
|
||||
Size = UDim2.new(1, -indent - 50, 0, 24),
|
||||
Position = UDim2.new(0, indent + 22, 0, 0),
|
||||
}),
|
||||
ChangeInfo = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, -indent - 80, 0, 24),
|
||||
Position = UDim2.new(1, -2, 0, 0),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 4),
|
||||
}),
|
||||
Edits = if props.changeInfo and props.changeInfo.edits
|
||||
then e("TextLabel", {
|
||||
Text = props.changeInfo.edits .. if props.changeInfo.failed then "," else "",
|
||||
BackgroundTransparency = 1,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.SubTextColor,
|
||||
TextTransparency = props.transparency,
|
||||
Size = UDim2.new(0, 0, 0, 16),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
LayoutOrder = 2,
|
||||
})
|
||||
else nil,
|
||||
Failed = if props.changeInfo and props.changeInfo.failed
|
||||
then e("TextLabel", {
|
||||
Text = props.changeInfo.failed,
|
||||
BackgroundTransparency = 1,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 14,
|
||||
TextColor3 = theme.Diff.Warning,
|
||||
TextTransparency = props.transparency,
|
||||
Size = UDim2.new(0, 0, 0, 16),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
LayoutOrder = 6,
|
||||
})
|
||||
else nil,
|
||||
}),
|
||||
LineGuides = e("Folder", nil, lineGuides),
|
||||
})
|
||||
|
||||
@@ -8,8 +8,8 @@ local PatchTree = require(Plugin.PatchTree)
|
||||
local PatchSet = require(Plugin.PatchSet)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
local VirtualScroller = require(Plugin.App.Components.VirtualScroller)
|
||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
@@ -55,34 +55,60 @@ function PatchVisualizer:render()
|
||||
end
|
||||
|
||||
-- Recusively draw tree
|
||||
local scrollElements, elementHeights = {}, {}
|
||||
local scrollElements, elementHeights, elementIndex = {}, {}, 0
|
||||
|
||||
if patchTree then
|
||||
local elementTotal = patchTree:getCount()
|
||||
local depthsComplete = {}
|
||||
local function drawNode(node, depth)
|
||||
local elementHeight, setElementHeight = Roact.createBinding(30)
|
||||
table.insert(elementHeights, elementHeight)
|
||||
table.insert(
|
||||
scrollElements,
|
||||
e(DomLabel, {
|
||||
updateEvent = self.updateEvent,
|
||||
elementHeight = elementHeight,
|
||||
setElementHeight = setElementHeight,
|
||||
patchType = node.patchType,
|
||||
className = node.className,
|
||||
isWarning = node.isWarning,
|
||||
instance = node.instance,
|
||||
name = node.name,
|
||||
hint = node.hint,
|
||||
changeList = node.changeList,
|
||||
depth = depth,
|
||||
transparency = self.props.transparency,
|
||||
showStringDiff = self.props.showStringDiff,
|
||||
showTableDiff = self.props.showTableDiff,
|
||||
})
|
||||
)
|
||||
elementIndex += 1
|
||||
|
||||
local parentNode = patchTree:getNode(node.parentId)
|
||||
local isFinalChild = true
|
||||
if parentNode then
|
||||
for _id, sibling in parentNode.children do
|
||||
if type(sibling) == "table" and sibling.name and sibling.name > node.name then
|
||||
isFinalChild = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local elementHeight, setElementHeight = Roact.createBinding(24)
|
||||
elementHeights[elementIndex] = elementHeight
|
||||
scrollElements[elementIndex] = e(DomLabel, {
|
||||
transparency = self.props.transparency,
|
||||
showStringDiff = self.props.showStringDiff,
|
||||
showTableDiff = self.props.showTableDiff,
|
||||
updateEvent = self.updateEvent,
|
||||
elementHeight = elementHeight,
|
||||
setElementHeight = setElementHeight,
|
||||
elementIndex = elementIndex,
|
||||
isFinalElement = elementIndex == elementTotal,
|
||||
depth = depth,
|
||||
depthsComplete = table.clone(depthsComplete),
|
||||
hasChildren = (node.children ~= nil and next(node.children) ~= nil),
|
||||
isFinalChild = isFinalChild,
|
||||
patchType = node.patchType,
|
||||
className = node.className,
|
||||
isWarning = node.isWarning,
|
||||
instance = node.instance,
|
||||
name = node.name,
|
||||
changeInfo = node.changeInfo,
|
||||
changeList = node.changeList,
|
||||
})
|
||||
|
||||
if isFinalChild then
|
||||
depthsComplete[depth] = true
|
||||
end
|
||||
end
|
||||
|
||||
patchTree:forEach(function(node, depth)
|
||||
depthsComplete[depth] = false
|
||||
for i = depth + 1, #depthsComplete do
|
||||
depthsComplete[i] = nil
|
||||
end
|
||||
|
||||
drawNode(node, depth)
|
||||
end)
|
||||
end
|
||||
@@ -92,6 +118,7 @@ function PatchVisualizer:render()
|
||||
transparency = self.props.transparency,
|
||||
size = self.props.size,
|
||||
position = self.props.position,
|
||||
anchorPoint = self.props.anchorPoint,
|
||||
layoutOrder = self.props.layoutOrder,
|
||||
}, {
|
||||
CleanMerge = e("TextLabel", {
|
||||
@@ -106,7 +133,8 @@ function PatchVisualizer:render()
|
||||
}),
|
||||
|
||||
VirtualScroller = e(VirtualScroller, {
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
size = UDim2.new(1, 0, 1, -2),
|
||||
position = UDim2.new(0, 0, 0, 2),
|
||||
transparency = self.props.transparency,
|
||||
count = #scrollElements,
|
||||
updateEvent = self.updateEvent.Event,
|
||||
|
||||
@@ -131,8 +131,8 @@ function VirtualScroller:render()
|
||||
Position = props.position,
|
||||
AnchorPoint = props.anchorPoint,
|
||||
BackgroundTransparency = props.backgroundTransparency or 1,
|
||||
BackgroundColor3 = props.backgroundColor3,
|
||||
BorderColor3 = props.borderColor3,
|
||||
BackgroundColor3 = props.backgroundColor3 or theme.BorderedContainer.BackgroundColor,
|
||||
BorderColor3 = props.borderColor3 or theme.BorderedContainer.BorderColor,
|
||||
CanvasSize = self.totalCanvas:map(function(s)
|
||||
return UDim2.fromOffset(0, s)
|
||||
end),
|
||||
|
||||
@@ -9,7 +9,6 @@ local PatchTree = require(Plugin.PatchTree)
|
||||
local Settings = require(Plugin.Settings)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local TextButton = require(Plugin.App.Components.TextButton)
|
||||
local Header = require(Plugin.App.Components.Header)
|
||||
local StudioPluginGui = require(Plugin.App.Components.Studio.StudioPluginGui)
|
||||
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
||||
@@ -60,17 +59,11 @@ end
|
||||
function ConfirmingPage:render()
|
||||
return Theme.with(function(theme)
|
||||
local pageContent = Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
Title = e("TextLabel", {
|
||||
Text = string.format(
|
||||
"Sync changes for project '%s':",
|
||||
self.props.confirmData.serverInfo.projectName or "UNKNOWN"
|
||||
),
|
||||
LayoutOrder = 2,
|
||||
Font = Enum.Font.Gotham,
|
||||
LineHeight = 1.2,
|
||||
TextSize = 14,
|
||||
@@ -82,7 +75,7 @@ function ConfirmingPage:render()
|
||||
}),
|
||||
|
||||
PatchVisualizer = e(PatchVisualizer, {
|
||||
size = UDim2.new(1, 0, 1, -150),
|
||||
size = UDim2.new(1, 0, 1, -100),
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 3,
|
||||
|
||||
@@ -155,6 +148,11 @@ function ConfirmingPage:render()
|
||||
}),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 8),
|
||||
PaddingRight = UDim.new(0, 8),
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
@@ -163,11 +161,6 @@ function ConfirmingPage:render()
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
|
||||
StringDiff = e(StudioPluginGui, {
|
||||
id = "Rojo_ConfirmingStringDiff",
|
||||
title = "String diff",
|
||||
|
||||
@@ -3,9 +3,7 @@ local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
local Flipper = require(Packages.Flipper)
|
||||
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
local PatchSet = require(Plugin.PatchSet)
|
||||
@@ -23,28 +21,20 @@ local TableDiffVisualizer = require(Plugin.App.Components.TableDiffVisualizer)
|
||||
local e = Roact.createElement
|
||||
|
||||
local AGE_UNITS = {
|
||||
{ 31556909, "year" },
|
||||
{ 2629743, "month" },
|
||||
{ 604800, "week" },
|
||||
{ 86400, "day" },
|
||||
{ 3600, "hour" },
|
||||
{
|
||||
60,
|
||||
"minute",
|
||||
},
|
||||
{ 31556909, "y" },
|
||||
{ 2629743, "mon" },
|
||||
{ 604800, "w" },
|
||||
{ 86400, "d" },
|
||||
{ 3600, "h" },
|
||||
{ 60, "m" },
|
||||
}
|
||||
function timeSinceText(elapsed: number): string
|
||||
if elapsed < 3 then
|
||||
return "just now"
|
||||
end
|
||||
|
||||
local ageText = string.format("%d seconds ago", elapsed)
|
||||
local ageText = string.format("%ds", elapsed)
|
||||
|
||||
for _, UnitData in ipairs(AGE_UNITS) do
|
||||
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
|
||||
if elapsed > UnitSeconds then
|
||||
local c = math.floor(elapsed / UnitSeconds)
|
||||
ageText = string.format("%d %s%s ago", c, UnitName, c > 1 and "s" or "")
|
||||
ageText = elapsed // UnitSeconds .. UnitName
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -52,49 +42,179 @@ function timeSinceText(elapsed: number): string
|
||||
return ageText
|
||||
end
|
||||
|
||||
local ChangesDrawer = Roact.Component:extend("ChangesDrawer")
|
||||
local ChangesViewer = Roact.Component:extend("ChangesViewer")
|
||||
|
||||
function ChangesDrawer:init()
|
||||
function ChangesViewer:init()
|
||||
-- Hold onto the serve session during the lifecycle of this component
|
||||
-- so that it can still render during the fade out after disconnecting
|
||||
self.serveSession = self.props.serveSession
|
||||
end
|
||||
|
||||
function ChangesDrawer:render()
|
||||
if self.props.rendered == false or self.serveSession == nil then
|
||||
function ChangesViewer:render()
|
||||
if self.props.rendered == false or self.serveSession == nil or self.props.patchData == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local unapplied = PatchSet.countChanges(self.props.patchData.unapplied)
|
||||
local applied = PatchSet.countChanges(self.props.patchData.patch) - unapplied
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return e(BorderedContainer, {
|
||||
transparency = self.props.transparency,
|
||||
size = self.props.height:map(function(y)
|
||||
return UDim2.new(1, 0, y, -220 * y)
|
||||
end),
|
||||
position = UDim2.new(0, 0, 1, 0),
|
||||
anchorPoint = Vector2.new(0, 1),
|
||||
layoutOrder = self.props.layoutOrder,
|
||||
}, {
|
||||
Close = e(IconButton, {
|
||||
icon = Assets.Images.Icons.Close,
|
||||
iconSize = 24,
|
||||
color = theme.ConnectionDetails.DisconnectColor,
|
||||
transparency = self.props.transparency,
|
||||
|
||||
position = UDim2.new(1, 0, 0, 0),
|
||||
anchorPoint = Vector2.new(1, 0),
|
||||
|
||||
onClick = self.props.onClose,
|
||||
return Roact.createFragment({
|
||||
Navbar = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 40),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Tip = e(Tooltip.Trigger, {
|
||||
text = "Close the patch visualizer",
|
||||
Close = e(IconButton, {
|
||||
icon = Assets.Images.Icons.Close,
|
||||
iconSize = 24,
|
||||
color = theme.Settings.Navbar.BackButtonColor,
|
||||
transparency = self.props.transparency,
|
||||
|
||||
position = UDim2.new(0, 0, 0.5, 0),
|
||||
anchorPoint = Vector2.new(0, 0.5),
|
||||
|
||||
onClick = self.props.onBack,
|
||||
}, {
|
||||
Tip = e(Tooltip.Trigger, {
|
||||
text = "Close",
|
||||
}),
|
||||
}),
|
||||
|
||||
Title = e("TextLabel", {
|
||||
Text = "Sync",
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 17,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextColor3 = theme.TextColor,
|
||||
TextTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, -40, 0, 20),
|
||||
Position = UDim2.new(0, 40, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Subtitle = e("TextLabel", {
|
||||
Text = DateTime.fromUnixTimestamp(self.props.patchData.timestamp):FormatLocalTime("LTS", "en-us"),
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 15,
|
||||
TextColor3 = theme.SubTextColor,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
TextTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, -40, 0, 16),
|
||||
Position = UDim2.new(0, 40, 0, 20),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
Info = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 10, 0, 24),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
Position = UDim2.new(1, -5, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(1, 0.5),
|
||||
}, {
|
||||
Tooltip = e(Tooltip.Trigger, {
|
||||
text = `{applied} changes applied`
|
||||
.. (if unapplied > 0 then `, {unapplied} changes failed` else ""),
|
||||
}),
|
||||
Content = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 4),
|
||||
}),
|
||||
|
||||
StatusIcon = e("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Image = if unapplied > 0
|
||||
then Assets.Images.Icons.SyncWarning
|
||||
else Assets.Images.Icons.SyncSuccess,
|
||||
ImageColor3 = if unapplied > 0 then theme.Diff.Warning else theme.TextColor,
|
||||
Size = UDim2.new(0, 24, 0, 24),
|
||||
LayoutOrder = 10,
|
||||
}),
|
||||
StatusSpacer = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 6, 0, 4),
|
||||
LayoutOrder = 9,
|
||||
}),
|
||||
AppliedIcon = e("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Image = Assets.Images.Icons.Checkmark,
|
||||
ImageColor3 = theme.TextColor,
|
||||
Size = UDim2.new(0, 16, 0, 16),
|
||||
LayoutOrder = 1,
|
||||
}),
|
||||
AppliedText = e("TextLabel", {
|
||||
Text = applied,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 15,
|
||||
TextColor3 = theme.TextColor,
|
||||
TextTransparency = self.props.transparency,
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 2,
|
||||
}),
|
||||
Warnings = if unapplied > 0
|
||||
then Roact.createFragment({
|
||||
WarningsSpacer = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 4, 0, 4),
|
||||
LayoutOrder = 3,
|
||||
}),
|
||||
UnappliedIcon = e("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Image = Assets.Images.Icons.Exclamation,
|
||||
ImageColor3 = theme.Diff.Warning,
|
||||
Size = UDim2.new(0, 4, 0, 16),
|
||||
LayoutOrder = 4,
|
||||
}),
|
||||
UnappliedText = e("TextLabel", {
|
||||
Text = unapplied,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 15,
|
||||
TextColor3 = theme.Diff.Warning,
|
||||
TextTransparency = self.props.transparency,
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 5,
|
||||
}),
|
||||
})
|
||||
else nil,
|
||||
}),
|
||||
}),
|
||||
|
||||
Divider = e("Frame", {
|
||||
BackgroundColor3 = theme.Settings.DividerColor,
|
||||
BackgroundTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, 0, 0, 1),
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
Gradient = e("UIGradient", {
|
||||
Transparency = NumberSequence.new({
|
||||
NumberSequenceKeypoint.new(0, 1),
|
||||
NumberSequenceKeypoint.new(0.1, 0),
|
||||
NumberSequenceKeypoint.new(0.9, 0),
|
||||
NumberSequenceKeypoint.new(1, 1),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
PatchVisualizer = e(PatchVisualizer, {
|
||||
size = UDim2.new(1, 0, 1, 0),
|
||||
Patch = e(PatchVisualizer, {
|
||||
size = UDim2.new(1, -10, 1, -65),
|
||||
position = UDim2.new(0, 5, 1, -5),
|
||||
anchorPoint = Vector2.new(0, 1),
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 3,
|
||||
layoutOrder = self.props.layoutOrder,
|
||||
|
||||
patchTree = self.props.patchTree,
|
||||
|
||||
@@ -167,20 +287,7 @@ function ConnectedPage:getChangeInfoText()
|
||||
if patchData == nil then
|
||||
return ""
|
||||
end
|
||||
|
||||
local elapsed = os.time() - patchData.timestamp
|
||||
local unapplied = PatchSet.countChanges(patchData.unapplied)
|
||||
|
||||
return "<i>Synced "
|
||||
.. timeSinceText(elapsed)
|
||||
.. (if unapplied > 0
|
||||
then string.format(
|
||||
', <font color="#FF8E3C">but %d change%s failed to apply</font>',
|
||||
unapplied,
|
||||
unapplied == 1 and "" or "s"
|
||||
)
|
||||
else "")
|
||||
.. "</i>"
|
||||
return timeSinceText(DateTime.now().UnixTimestamp - patchData.timestamp)
|
||||
end
|
||||
|
||||
function ConnectedPage:startChangeInfoTextUpdater()
|
||||
@@ -190,13 +297,9 @@ function ConnectedPage:startChangeInfoTextUpdater()
|
||||
-- Start a new updater
|
||||
self.changeInfoTextUpdater = task.defer(function()
|
||||
while true do
|
||||
if self.state.hoveringChangeInfo then
|
||||
self.setChangeInfoText("<u>" .. self:getChangeInfoText() .. "</u>")
|
||||
else
|
||||
self.setChangeInfoText(self:getChangeInfoText())
|
||||
end
|
||||
self.setChangeInfoText(self:getChangeInfoText())
|
||||
|
||||
local elapsed = os.time() - self.props.patchData.timestamp
|
||||
local elapsed = DateTime.now().UnixTimestamp - self.props.patchData.timestamp
|
||||
local updateInterval = 1
|
||||
|
||||
-- Update timestamp text as frequently as currently needed
|
||||
@@ -221,23 +324,6 @@ function ConnectedPage:stopChangeInfoTextUpdater()
|
||||
end
|
||||
|
||||
function ConnectedPage:init()
|
||||
self.changeDrawerMotor = Flipper.SingleMotor.new(0)
|
||||
self.changeDrawerHeight = bindingUtil.fromMotor(self.changeDrawerMotor)
|
||||
|
||||
self.changeDrawerMotor:onStep(function(value)
|
||||
local renderChanges = value > 0.05
|
||||
|
||||
self:setState(function(state)
|
||||
if state.renderChanges == renderChanges then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
renderChanges = renderChanges,
|
||||
}
|
||||
end)
|
||||
end)
|
||||
|
||||
self:setState({
|
||||
renderChanges = false,
|
||||
hoveringChangeInfo = false,
|
||||
@@ -266,6 +352,10 @@ function ConnectedPage:didUpdate(previousProps)
|
||||
end
|
||||
|
||||
function ConnectedPage:render()
|
||||
local syncWarning = self.props.patchData
|
||||
and self.props.patchData.unapplied
|
||||
and PatchSet.countChanges(self.props.patchData.unapplied) > 0
|
||||
|
||||
return Theme.with(function(theme)
|
||||
return Roact.createFragment({
|
||||
Padding = e("UIPadding", {
|
||||
@@ -280,9 +370,88 @@ function ConnectedPage:render()
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
Heading = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 0, 32),
|
||||
}, {
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
}),
|
||||
|
||||
ChangeInfo = e("TextButton", {
|
||||
Text = "",
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
BackgroundColor3 = theme.BorderedContainer.BorderedColor,
|
||||
BackgroundTransparency = if self.state.hoveringChangeInfo then 0.7 else 1,
|
||||
BorderSizePixel = 0,
|
||||
Position = UDim2.new(1, -5, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(1, 0.5),
|
||||
[Roact.Event.MouseEnter] = function()
|
||||
self:setState({
|
||||
hoveringChangeInfo = true,
|
||||
})
|
||||
end,
|
||||
[Roact.Event.MouseLeave] = function()
|
||||
self:setState({
|
||||
hoveringChangeInfo = false,
|
||||
})
|
||||
end,
|
||||
[Roact.Event.Activated] = function()
|
||||
self:setState(function(prevState)
|
||||
prevState = prevState or {}
|
||||
return {
|
||||
renderChanges = not prevState.renderChanges,
|
||||
}
|
||||
end)
|
||||
end,
|
||||
}, {
|
||||
Corner = e("UICorner", {
|
||||
CornerRadius = UDim.new(0, 5),
|
||||
}),
|
||||
Tooltip = e(Tooltip.Trigger, {
|
||||
text = if self.state.renderChanges then "Hide changes" else "View changes",
|
||||
}),
|
||||
Content = e("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
}, {
|
||||
Layout = e("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 5),
|
||||
}),
|
||||
Padding = e("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 5),
|
||||
PaddingRight = UDim.new(0, 5),
|
||||
}),
|
||||
Text = e("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Text = self.changeInfoText,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 15,
|
||||
TextColor3 = if syncWarning then theme.Diff.Warning else theme.Header.VersionColor,
|
||||
TextTransparency = self.props.transparency,
|
||||
TextXAlignment = Enum.TextXAlignment.Right,
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
LayoutOrder = 1,
|
||||
}),
|
||||
Icon = e("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Image = if syncWarning
|
||||
then Assets.Images.Icons.SyncWarning
|
||||
else Assets.Images.Icons.SyncSuccess,
|
||||
ImageColor3 = if syncWarning then theme.Diff.Warning else theme.Header.VersionColor,
|
||||
ImageTransparency = self.props.transparency,
|
||||
Size = UDim2.new(0, 24, 0, 24),
|
||||
LayoutOrder = 2,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
ConnectionDetails = e(ConnectionDetails, {
|
||||
@@ -332,84 +501,59 @@ function ConnectedPage:render()
|
||||
}),
|
||||
}),
|
||||
|
||||
ChangeInfo = e("TextButton", {
|
||||
Text = self.changeInfoText,
|
||||
Font = Enum.Font.Gotham,
|
||||
TextSize = 14,
|
||||
TextWrapped = true,
|
||||
RichText = true,
|
||||
TextColor3 = theme.Header.VersionColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextTransparency = self.props.transparency,
|
||||
ChangesViewer = e(StudioPluginGui, {
|
||||
id = "Rojo_ChangesViewer",
|
||||
title = "View changes",
|
||||
active = self.state.renderChanges,
|
||||
isEphemeral = true,
|
||||
|
||||
Size = UDim2.new(1, 0, 0, 28),
|
||||
initDockState = Enum.InitialDockState.Float,
|
||||
overridePreviousState = true,
|
||||
floatingSize = Vector2.new(400, 500),
|
||||
minimumSize = Vector2.new(300, 300),
|
||||
|
||||
LayoutOrder = 4,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Event.MouseEnter] = function()
|
||||
self:setState({
|
||||
hoveringChangeInfo = true,
|
||||
})
|
||||
self.setChangeInfoText("<u>" .. self:getChangeInfoText() .. "</u>")
|
||||
end,
|
||||
|
||||
[Roact.Event.MouseLeave] = function()
|
||||
self:setState({
|
||||
hoveringChangeInfo = false,
|
||||
})
|
||||
self.setChangeInfoText(self:getChangeInfoText())
|
||||
end,
|
||||
|
||||
[Roact.Event.Activated] = function()
|
||||
if self.state.renderChanges then
|
||||
self.changeDrawerMotor:setGoal(Flipper.Spring.new(0, {
|
||||
frequency = 4,
|
||||
dampingRatio = 1,
|
||||
}))
|
||||
else
|
||||
self.changeDrawerMotor:setGoal(Flipper.Spring.new(1, {
|
||||
frequency = 3,
|
||||
dampingRatio = 1,
|
||||
}))
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
Tooltip = e(Tooltip.Trigger, {
|
||||
text = if self.state.renderChanges then "Hide the changes" else "View the changes",
|
||||
}),
|
||||
}),
|
||||
|
||||
ChangesDrawer = e(ChangesDrawer, {
|
||||
rendered = self.state.renderChanges,
|
||||
transparency = self.props.transparency,
|
||||
patchTree = self.props.patchTree,
|
||||
serveSession = self.props.serveSession,
|
||||
height = self.changeDrawerHeight,
|
||||
layoutOrder = 5,
|
||||
|
||||
showStringDiff = function(oldString: string, newString: string)
|
||||
self:setState({
|
||||
showingStringDiff = true,
|
||||
oldString = oldString,
|
||||
newString = newString,
|
||||
})
|
||||
end,
|
||||
showTableDiff = function(oldTable: { [any]: any? }, newTable: { [any]: any? })
|
||||
self:setState({
|
||||
showingTableDiff = true,
|
||||
oldTable = oldTable,
|
||||
newTable = newTable,
|
||||
})
|
||||
end,
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
|
||||
onClose = function()
|
||||
self.changeDrawerMotor:setGoal(Flipper.Spring.new(0, {
|
||||
frequency = 4,
|
||||
dampingRatio = 1,
|
||||
}))
|
||||
self:setState({
|
||||
renderChanges = false,
|
||||
})
|
||||
end,
|
||||
}, {
|
||||
TooltipsProvider = e(Tooltip.Provider, nil, {
|
||||
Tooltips = e(Tooltip.Container, nil),
|
||||
Content = e("Frame", {
|
||||
Size = UDim2.fromScale(1, 1),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
Changes = e(ChangesViewer, {
|
||||
transparency = self.props.transparency,
|
||||
rendered = self.state.renderChanges,
|
||||
patchData = self.props.patchData,
|
||||
patchTree = self.props.patchTree,
|
||||
serveSession = self.props.serveSession,
|
||||
showStringDiff = function(oldString: string, newString: string)
|
||||
self:setState({
|
||||
showingStringDiff = true,
|
||||
oldString = oldString,
|
||||
newString = newString,
|
||||
})
|
||||
end,
|
||||
showTableDiff = function(oldTable: { [any]: any? }, newTable: { [any]: any? })
|
||||
self:setState({
|
||||
showingTableDiff = true,
|
||||
oldTable = oldTable,
|
||||
newTable = newTable,
|
||||
})
|
||||
end,
|
||||
onBack = function()
|
||||
self:setState({
|
||||
renderChanges = false,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
StringDiff = e(StudioPluginGui, {
|
||||
|
||||
@@ -32,9 +32,12 @@ local StudioProvider = Roact.Component:extend("StudioProvider")
|
||||
function StudioProvider:updateTheme()
|
||||
local studioTheme = getStudio().Theme
|
||||
|
||||
local isDark = studioTheme.Name == "Dark"
|
||||
|
||||
local theme = strict(studioTheme.Name .. "Theme", {
|
||||
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
|
||||
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText),
|
||||
SubTextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.SubText),
|
||||
Button = {
|
||||
Solid = {
|
||||
-- Solid uses brand theming, not Studio theming.
|
||||
@@ -139,9 +142,10 @@ function StudioProvider:updateTheme()
|
||||
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.InputFieldBackground),
|
||||
},
|
||||
Diff = {
|
||||
Add = studioTheme:GetColor(Enum.StudioStyleGuideColor.DiffTextAdditionBackground),
|
||||
Remove = studioTheme:GetColor(Enum.StudioStyleGuideColor.DiffTextDeletionBackground),
|
||||
Edit = studioTheme:GetColor(Enum.StudioStyleGuideColor.DiffLineNumSeparatorBackground),
|
||||
-- Studio doesn't have good colors since their diffs use backgrounds, not text
|
||||
Add = if isDark then Color3.fromRGB(143, 227, 154) else Color3.fromRGB(41, 164, 45),
|
||||
Remove = if isDark then Color3.fromRGB(242, 125, 125) else Color3.fromRGB(150, 29, 29),
|
||||
Edit = if isDark then Color3.fromRGB(120, 154, 248) else Color3.fromRGB(0, 70, 160),
|
||||
Row = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||
Warning = studioTheme:GetColor(Enum.StudioStyleGuideColor.WarningText),
|
||||
},
|
||||
|
||||
@@ -457,7 +457,7 @@ function App:startSession()
|
||||
end)
|
||||
|
||||
serveSession:hookPostcommit(function(patch, _instanceMap, unapplied)
|
||||
local now = os.time()
|
||||
local now = DateTime.now().UnixTimestamp
|
||||
local old = self.state.patchData
|
||||
|
||||
if PatchSet.isEmpty(patch) then
|
||||
|
||||
@@ -25,6 +25,10 @@ local Assets = {
|
||||
Back = "rbxassetid://6017213752",
|
||||
Reset = "rbxassetid://10142422327",
|
||||
Expand = "rbxassetid://12045401097",
|
||||
Checkmark = "rbxassetid://16571012729",
|
||||
Exclamation = "rbxassetid://16571172190",
|
||||
SyncSuccess = "rbxassetid://16565035221",
|
||||
SyncWarning = "rbxassetid://16565325171",
|
||||
},
|
||||
Diff = {
|
||||
Add = "rbxassetid://10434145835",
|
||||
|
||||
@@ -211,9 +211,11 @@ end
|
||||
function PatchSet.countChanges(patch)
|
||||
local count = 0
|
||||
|
||||
for _ in patch.added do
|
||||
-- Adding an instance is 1 change
|
||||
count += 1
|
||||
for _, add in patch.added do
|
||||
-- Adding an instance is 1 change per property
|
||||
for _ in add.Properties do
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
for _ in patch.removed do
|
||||
-- Removing an instance is 1 change
|
||||
|
||||
@@ -79,6 +79,15 @@ function Tree.new()
|
||||
return setmetatable(tree, Tree)
|
||||
end
|
||||
|
||||
-- Iterates over all nodes and counts them up
|
||||
function Tree:getCount()
|
||||
local count = 0
|
||||
self:forEach(function()
|
||||
count += 1
|
||||
end)
|
||||
return count
|
||||
end
|
||||
|
||||
-- Iterates over all sub-nodes, depth first
|
||||
-- node is where to start from, defaults to root
|
||||
-- depth is used for recursion but can be used to set the starting depth
|
||||
@@ -219,42 +228,14 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
||||
tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
|
||||
|
||||
-- Gather detail text
|
||||
local changeList, hint = nil, nil
|
||||
local changeList, changeInfo = nil, nil
|
||||
if next(change.changedProperties) or change.changedName then
|
||||
changeList = {}
|
||||
|
||||
local hintBuffer, hintBufferSize, hintOverflow = table.create(3), 0, 0
|
||||
local changeIndex = 0
|
||||
local function addProp(prop: string, current: any?, incoming: any?, metadata: any?)
|
||||
changeIndex += 1
|
||||
changeList[changeIndex] = { prop, current, incoming, metadata }
|
||||
|
||||
if hintBufferSize < 3 then
|
||||
hintBufferSize += 1
|
||||
hintBuffer[hintBufferSize] = prop
|
||||
return
|
||||
end
|
||||
|
||||
-- We only want to have 3 hints
|
||||
-- to keep it deterministic, we sort them alphabetically
|
||||
|
||||
-- Either this prop overflows, or it makes another one move to overflow
|
||||
hintOverflow += 1
|
||||
|
||||
-- Shortcut for the common case
|
||||
if hintBuffer[3] <= prop then
|
||||
-- This prop is below the last hint, no need to insert
|
||||
return
|
||||
end
|
||||
|
||||
-- Find the first available spot
|
||||
for i, hintItem in hintBuffer do
|
||||
if prop < hintItem then
|
||||
-- This prop is before the currently selected hint,
|
||||
-- so take its place and then continue to find a spot for the old hint
|
||||
hintBuffer[i], prop = prop, hintBuffer[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Gather the changes
|
||||
@@ -274,8 +255,9 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
||||
)
|
||||
end
|
||||
|
||||
-- Finalize detail values
|
||||
hint = table.concat(hintBuffer, ", ") .. (if hintOverflow == 0 then "" else ", " .. hintOverflow .. " more")
|
||||
changeInfo = {
|
||||
edits = changeIndex,
|
||||
}
|
||||
|
||||
-- Sort changes and add header
|
||||
table.sort(changeList, function(a, b)
|
||||
@@ -291,7 +273,7 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
||||
className = instance.ClassName,
|
||||
name = instance.Name,
|
||||
instance = instance,
|
||||
hint = hint,
|
||||
changeInfo = changeInfo,
|
||||
changeList = changeList,
|
||||
})
|
||||
end
|
||||
@@ -376,42 +358,14 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
||||
tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
|
||||
|
||||
-- Gather detail text
|
||||
local changeList, hint = nil, nil
|
||||
local changeList, changeInfo = nil, nil
|
||||
if next(change.Properties) then
|
||||
changeList = {}
|
||||
|
||||
local hintBuffer, hintBufferSize, hintOverflow = table.create(3), 0, 0
|
||||
local changeIndex = 0
|
||||
local function addProp(prop: string, incoming: any)
|
||||
changeIndex += 1
|
||||
changeList[changeIndex] = { prop, "N/A", incoming }
|
||||
|
||||
if hintBufferSize < 3 then
|
||||
hintBufferSize += 1
|
||||
hintBuffer[hintBufferSize] = prop
|
||||
return
|
||||
end
|
||||
|
||||
-- We only want to have 3 hints
|
||||
-- to keep it deterministic, we sort them alphabetically
|
||||
|
||||
-- Either this prop overflows, or it makes another one move to overflow
|
||||
hintOverflow += 1
|
||||
|
||||
-- Shortcut for the common case
|
||||
if hintBuffer[3] <= prop then
|
||||
-- This prop is below the last hint, no need to insert
|
||||
return
|
||||
end
|
||||
|
||||
-- Find the first available spot
|
||||
for i, hintItem in hintBuffer do
|
||||
if prop < hintItem then
|
||||
-- This prop is before the currently selected hint,
|
||||
-- so take its place and then continue to find a spot for the old hint
|
||||
hintBuffer[i], prop = prop, hintBuffer[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for prop, incoming in change.Properties do
|
||||
@@ -419,8 +373,9 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
||||
addProp(prop, if success then incomingValue else select(2, next(incoming)))
|
||||
end
|
||||
|
||||
-- Finalize detail values
|
||||
hint = table.concat(hintBuffer, ", ") .. (if hintOverflow == 0 then "" else ", " .. hintOverflow .. " more")
|
||||
changeInfo = {
|
||||
edits = changeIndex,
|
||||
}
|
||||
|
||||
-- Sort changes and add header
|
||||
table.sort(changeList, function(a, b)
|
||||
@@ -435,7 +390,7 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
||||
patchType = "Add",
|
||||
className = change.ClassName,
|
||||
name = change.Name,
|
||||
hint = hint,
|
||||
changeInfo = changeInfo,
|
||||
changeList = changeList,
|
||||
instance = instanceMap.fromIds[id],
|
||||
})
|
||||
@@ -473,6 +428,8 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
||||
if not node.changeList then
|
||||
continue
|
||||
end
|
||||
|
||||
local warnings = 0
|
||||
for _, change in node.changeList do
|
||||
local property = change[1]
|
||||
local propertyFailedToApply = if property == "Name"
|
||||
@@ -483,6 +440,8 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
||||
-- This change didn't fail, no need to mark
|
||||
continue
|
||||
end
|
||||
|
||||
warnings += 1
|
||||
if change[4] == nil then
|
||||
change[4] = { isWarning = true }
|
||||
else
|
||||
@@ -490,6 +449,11 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
||||
end
|
||||
Log.trace(" Marked property as warning: {}.{}", node.name, property)
|
||||
end
|
||||
|
||||
node.changeInfo = {
|
||||
edits = (node.changeInfo.edits or (#node.changeList - 1)) - warnings,
|
||||
failed = if warnings > 0 then warnings else nil,
|
||||
}
|
||||
end
|
||||
for failedAdditionId in unappliedPatch.added do
|
||||
local node = tree:getNode(failedAdditionId)
|
||||
@@ -503,6 +467,7 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
||||
if not node.changeList then
|
||||
continue
|
||||
end
|
||||
|
||||
for _, change in node.changeList do
|
||||
-- Failed addition means that all properties failed to be added
|
||||
if change[4] == nil then
|
||||
@@ -512,6 +477,10 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
||||
end
|
||||
Log.trace(" Marked property as warning: {}.{}", node.name, change[1])
|
||||
end
|
||||
|
||||
node.changeInfo = {
|
||||
failed = node.changeInfo.edits or (#node.changeList - 1),
|
||||
}
|
||||
end
|
||||
for _, failedRemovalIdOrInstance in unappliedPatch.removed do
|
||||
local failedRemovalId = if Types.RbxId(failedRemovalIdOrInstance)
|
||||
|
||||
Reference in New Issue
Block a user