diff --git a/plugin/src/App/Components/PatchVisualizer/DomLabel.lua b/plugin/src/App/Components/PatchVisualizer/DomLabel.lua index 8d282ab9..9f967c32 100644 --- a/plugin/src/App/Components/PatchVisualizer/DomLabel.lua +++ b/plugin/src/App/Components/PatchVisualizer/DomLabel.lua @@ -40,11 +40,6 @@ end local DomLabel = Roact.Component:extend("DomLabel") function DomLabel:init() - self.maxElementHeight = 0 - if self.props.changeList then - self.maxElementHeight = math.clamp(#self.props.changeList * 30, 30, 30 * 6) - end - local initHeight = self.props.elementHeight:getValue() self.expanded = initHeight > 30 @@ -118,7 +113,8 @@ function DomLabel:render() Size = UDim2.new(1, 0, 1, 0), [Roact.Event.Activated] = function() self.expanded = not self.expanded - self.motor:setGoal(Flipper.Spring.new((self.expanded and self.maxElementHeight or 0) + 30, { + local goalHeight = 30 + (if self.expanded then math.clamp(#self.props.changeList * 30, 30, 30 * 6) else 0) + self.motor:setGoal(Flipper.Spring.new(goalHeight, { frequency = 5, dampingRatio = 1, })) diff --git a/plugin/src/App/Components/VirtualScroller.lua b/plugin/src/App/Components/VirtualScroller.lua index 2922a74f..69bcf5f1 100644 --- a/plugin/src/App/Components/VirtualScroller.lua +++ b/plugin/src/App/Components/VirtualScroller.lua @@ -99,18 +99,30 @@ function VirtualScroller:refresh() }) 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, - }, props.render(i)) + }, content) end return Theme.with(function(theme) diff --git a/plugin/src/App/StatusPages/Connected.lua b/plugin/src/App/StatusPages/Connected.lua index 91484926..af43e6a2 100644 --- a/plugin/src/App/StatusPages/Connected.lua +++ b/plugin/src/App/StatusPages/Connected.lua @@ -26,11 +26,11 @@ function timeSinceText(elapsed: number): string local ageText = string.format("%d seconds ago", elapsed) - for _,UnitData in ipairs(AGE_UNITS) do + 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 "") + local c = math.floor(elapsed / UnitSeconds) + ageText = string.format("%d %s%s ago", c, UnitName, c > 1 and "s" or "") break end end @@ -38,45 +38,53 @@ function timeSinceText(elapsed: number): string return ageText end -local function ChangesDrawer(props) - if props.rendered == false then +local ChangesDrawer = Roact.Component:extend("ConnectedPage") + +function ChangesDrawer: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 return nil end return Theme.with(function(theme) return e(BorderedContainer, { - transparency = props.transparency, - size = props.height:map(function(y) + transparency = self.props.transparency, + size = self.props.height:map(function(y) return UDim2.new(1, 0, y, -180 * y) end), position = UDim2.new(0, 0, 1, 0), anchorPoint = Vector2.new(0, 1), - layoutOrder = props.layoutOrder, + layoutOrder = self.props.layoutOrder, }, { Close = e(IconButton, { icon = Assets.Images.Icons.Close, iconSize = 24, color = theme.ConnectionDetails.DisconnectColor, - transparency = props.transparency, + transparency = self.props.transparency, position = UDim2.new(1, 0, 0, 0), anchorPoint = Vector2.new(1, 0), - onClick = props.onClose, + onClick = self.props.onClose, }, { Tip = e(Tooltip.Trigger, { - text = "Close the patch visualizer" + text = "Close the patch visualizer", }), }), PatchVisualizer = e(PatchVisualizer, { size = UDim2.new(1, 0, 1, 0), - transparency = props.transparency, + transparency = self.props.transparency, layoutOrder = 3, - columnVisibility = {true, false, true}, - patch = props.patchInfo:getValue().patch, - instanceMap = props.serveSession.__instanceMap, + columnVisibility = { true, false, true }, + patch = self.props.patch, + instanceMap = self.serveSession.__instanceMap, }), }) end) @@ -91,7 +99,7 @@ local function ConnectionDetails(props) }, { TextContainer = e("Frame", { Size = UDim2.new(1, 0, 1, 0), - BackgroundTransparency = 1 + BackgroundTransparency = 1, }, { ProjectName = e("TextLabel", { Text = props.projectName, @@ -141,7 +149,7 @@ local function ConnectionDetails(props) onClick = props.onDisconnect, }, { Tip = e(Tooltip.Trigger, { - text = "Disconnect from the Rojo sync server" + text = "Disconnect from the Rojo sync server", }), }), @@ -155,6 +163,18 @@ end local ConnectedPage = Roact.Component:extend("ConnectedPage") +function ConnectedPage:getChangeInfoText() + local patchData = self.props.patchData + if patchData == nil then + return "" + end + + local elapsed = os.time() - patchData.timestamp + local changes = PatchSet.countChanges(patchData.patch) + + return string.format("Synced %d change%s %s", changes, changes == 1 and "" or "s", timeSinceText(elapsed)) +end + function ConnectedPage:init() self.changeDrawerMotor = Flipper.SingleMotor.new(0) self.changeDrawerHeight = bindingUtil.fromMotor(self.changeDrawerMotor) @@ -176,6 +196,34 @@ function ConnectedPage:init() self:setState({ renderChanges = false, }) + + self.changeInfoText, self.setChangeInfoText = Roact.createBinding("") + + self.changeInfoTextUpdater = task.defer(function() + while true do + self.setChangeInfoText(self:getChangeInfoText()) + + local elapsed = os.time() - self.props.patchData.timestamp + local updateInterval = 1 + + -- Update timestamp text as frequently as currently needed + for _, UnitData in ipairs(AGE_UNITS) do + local UnitSeconds = UnitData[1] + if elapsed >= UnitSeconds then + updateInterval = UnitSeconds + break + end + end + + task.wait(updateInterval) + end + end) +end + +function ConnectedPage:willUnmount() + if self.changeInfoTextUpdater then + task.cancel(self.changeInfoTextUpdater) + end end function ConnectedPage:render() @@ -208,15 +256,7 @@ function ConnectedPage:render() }), ChangeInfo = e("TextButton", { - Text = self.props.patchInfo:map(function(info) - local changes = PatchSet.countChanges(info.patch) - return string.format( - "Synced %d change%s %s", - changes, - changes == 1 and "" or "s", - timeSinceText(os.time() - info.timestamp) - ) - end), + Text = self.changeInfoText, Font = Enum.Font.Gotham, TextSize = 14, TextWrapped = true, @@ -249,7 +289,7 @@ function ConnectedPage:render() ChangesDrawer = e(ChangesDrawer, { rendered = self.state.renderChanges, transparency = self.props.transparency, - patchInfo = self.props.patchInfo, + patch = self.props.patchData.patch, serveSession = self.props.serveSession, height = self.changeDrawerHeight, layoutOrder = 4, diff --git a/plugin/src/App/init.lua b/plugin/src/App/init.lua index 35df8ffb..8c705449 100644 --- a/plugin/src/App/init.lua +++ b/plugin/src/App/init.lua @@ -51,10 +51,6 @@ function App:init() self.host, self.setHost = Roact.createBinding(priorHost or "") self.port, self.setPort = Roact.createBinding(priorPort or "") - self.patchInfo, self.setPatchInfo = Roact.createBinding({ - patch = PatchSet.newEmpty(), - timestamp = os.time(), - }) self.confirmationBindable = Instance.new("BindableEvent") self.confirmationEvent = self.confirmationBindable.Event @@ -62,6 +58,10 @@ function App:init() appStatus = AppStatus.NotConnected, guiEnabled = false, confirmData = {}, + patchData = { + patch = PatchSet.newEmpty(), + timestamp = os.time(), + }, notifications = {}, toolbarIcon = Assets.Images.PluginButton, }) @@ -227,21 +227,25 @@ function App:startSession() local now = os.time() - local old = self.patchInfo:getValue() + local old = self.state.patchData if now - old.timestamp < 2 then -- Patches that apply in the same second are -- considered to be part of the same change for human clarity local merged = PatchSet.newEmpty() PatchSet.assign(merged, old.patch, patch) - self.setPatchInfo({ - patch = merged, - timestamp = now, + self:setState({ + patchData = { + patch = merged, + timestamp = now, + }, }) else - self.setPatchInfo({ - patch = patch, - timestamp = now, + self:setState({ + patchData = { + patch = patch, + timestamp = now, + }, }) end end) @@ -318,16 +322,6 @@ function App:startSession() serveSession:start() self.serveSession = serveSession - - task.defer(function() - while self.serveSession == serveSession do - -- Trigger rerender to update timestamp text - local patchInfo = table.clone(self.patchInfo:getValue()) - self.setPatchInfo(patchInfo) - local elapsed = os.time() - patchInfo.timestamp - task.wait(elapsed < 60 and 1 or elapsed / 5) - end - end) end function App:endSession() @@ -429,7 +423,7 @@ function App:render() Connected = createPageElement(AppStatus.Connected, { projectName = self.state.projectName, address = self.state.address, - patchInfo = self.patchInfo, + patchData = self.state.patchData, serveSession = self.serveSession, onDisconnect = function()