mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Clicking on the "X changes X ago" message opens up a handy diff visualizer to see what those changes were. However, it had quite a few issues that needed fixing. - Disconnecting a session with it expanded caused an error as it tried to read the serveSession that no longer exists during the page fade transition. (#671) - Resolved by converting to stateful component and holding the serveSession during the lifetime to ensure it can render the last known changes during the fade transition - Leaving it open while new changes are synced did not update the visualizer - The patch data was piggybacking on an existing binding, which meant that new patches did not trigger rerender. - Resolved by converting to state - Also made some improvements to that old binding - Moved from app to connected page for better organization and separation of duties - No more useless updates causing rerenders with no real change - Scroll window child component wouldn't actually display the updated visuals - Resolved by making major improvements to VirtualScroller - Made more robust against edge case states - Made smarter about knowing when it needs to refresh As you can see in this slow motion GIF, it works now. 
169 lines
4.2 KiB
Lua
169 lines
4.2 KiB
Lua
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 bindingUtil = require(Plugin.App.bindingUtil)
|
|
|
|
local e = Roact.createElement
|
|
|
|
local VirtualScroller = Roact.Component:extend("VirtualScroller")
|
|
|
|
function VirtualScroller:init()
|
|
self.scrollFrameRef = Roact.createRef()
|
|
self:setState({
|
|
WindowSize = Vector2.new(),
|
|
CanvasPosition = Vector2.new(),
|
|
})
|
|
|
|
self.totalCanvas, self.setTotalCanvas = Roact.createBinding(0)
|
|
self.padding, self.setPadding = Roact.createBinding(0)
|
|
|
|
self:refresh()
|
|
if self.props.updateEvent then
|
|
self.connection = self.props.updateEvent:Connect(function()
|
|
self:refresh()
|
|
end)
|
|
end
|
|
end
|
|
|
|
function VirtualScroller:didMount()
|
|
local rbx = self.scrollFrameRef:getValue()
|
|
|
|
local windowSizeSignal = rbx:GetPropertyChangedSignal("AbsoluteWindowSize")
|
|
self.windowSizeChanged = windowSizeSignal:Connect(function()
|
|
self:setState({ WindowSize = rbx.AbsoluteWindowSize })
|
|
self:refresh()
|
|
end)
|
|
|
|
local canvasPositionSignal = rbx:GetPropertyChangedSignal("CanvasPosition")
|
|
self.canvasPositionChanged = canvasPositionSignal:Connect(function()
|
|
if math.abs(rbx.CanvasPosition.Y - self.state.CanvasPosition.Y) > 5 then
|
|
self:setState({ CanvasPosition = rbx.CanvasPosition })
|
|
self:refresh()
|
|
end
|
|
end)
|
|
|
|
self:refresh()
|
|
end
|
|
|
|
function VirtualScroller:willUnmount()
|
|
self.windowSizeChanged:Disconnect()
|
|
self.canvasPositionChanged:Disconnect()
|
|
if self.connection then
|
|
self.connection:Disconnect()
|
|
self.connection = nil
|
|
end
|
|
end
|
|
|
|
function VirtualScroller:refresh()
|
|
local props = self.props
|
|
local state = self.state
|
|
|
|
local count = props.count
|
|
local windowSize, canvasPosition = state.WindowSize.Y, state.CanvasPosition.Y
|
|
local bottom = canvasPosition + windowSize
|
|
|
|
local minIndex, maxIndex = 1, count
|
|
local padding, canvasSize = 0, 0
|
|
|
|
local pos = 0
|
|
for i = 1, count do
|
|
local height = props.getHeightBinding(i):getValue()
|
|
canvasSize += height
|
|
|
|
if pos > bottom then
|
|
-- Below window
|
|
if maxIndex > i then
|
|
maxIndex = i
|
|
end
|
|
end
|
|
|
|
pos += height
|
|
|
|
if pos < canvasPosition then
|
|
-- Above window
|
|
minIndex = i
|
|
padding = pos - height
|
|
end
|
|
end
|
|
|
|
self.setPadding(padding)
|
|
self.setTotalCanvas(canvasSize)
|
|
self:setState({
|
|
Start = minIndex,
|
|
End = maxIndex,
|
|
})
|
|
end
|
|
|
|
function VirtualScroller:didUpdate(previousProps)
|
|
if self.props.count ~= previousProps.count then
|
|
-- Items have changed, so we need to refresh
|
|
self:refresh()
|
|
end
|
|
end
|
|
|
|
function VirtualScroller:render()
|
|
local props, state = self.props, self.state
|
|
|
|
local items = {}
|
|
for i = state.Start, state.End do
|
|
local content = props.render(i)
|
|
if content == nil then
|
|
continue
|
|
end
|
|
|
|
items["Item" .. i] = e("Frame", {
|
|
LayoutOrder = i,
|
|
Size = props.getHeightBinding(i):map(function(height)
|
|
return UDim2.new(1, 0, 0, height)
|
|
end),
|
|
BackgroundTransparency = 1,
|
|
}, content)
|
|
end
|
|
|
|
return Theme.with(function(theme)
|
|
return e("ScrollingFrame", {
|
|
Size = props.size,
|
|
Position = props.position,
|
|
AnchorPoint = props.anchorPoint,
|
|
BackgroundTransparency = props.backgroundTransparency or 1,
|
|
BackgroundColor3 = props.backgroundColor3,
|
|
BorderColor3 = props.borderColor3,
|
|
CanvasSize = self.totalCanvas:map(function(s)
|
|
return UDim2.fromOffset(0, s)
|
|
end),
|
|
ScrollBarThickness = 9,
|
|
ScrollBarImageColor3 = theme.ScrollBarColor,
|
|
ScrollBarImageTransparency = props.transparency:map(function(value)
|
|
return bindingUtil.blendAlpha({ 0.65, value })
|
|
end),
|
|
TopImage = Assets.Images.ScrollBar.Top,
|
|
MidImage = Assets.Images.ScrollBar.Middle,
|
|
BottomImage = Assets.Images.ScrollBar.Bottom,
|
|
|
|
ElasticBehavior = Enum.ElasticBehavior.Always,
|
|
ScrollingDirection = Enum.ScrollingDirection.Y,
|
|
VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
|
|
[Roact.Ref] = self.scrollFrameRef,
|
|
}, {
|
|
Layout = e("UIListLayout", {
|
|
Padding = UDim.new(0, 0),
|
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
FillDirection = Enum.FillDirection.Vertical,
|
|
}),
|
|
Padding = e("UIPadding", {
|
|
PaddingTop = self.padding:map(function(p)
|
|
return UDim.new(0, p)
|
|
end),
|
|
}),
|
|
Content = Roact.createFragment(items),
|
|
})
|
|
end)
|
|
end
|
|
|
|
return VirtualScroller
|