diff --git a/plugin/src/App/StatusPages/Connected.lua b/plugin/src/App/StatusPages/Connected.lua index 588841289..810ce0953 100644 --- a/plugin/src/App/StatusPages/Connected.lua +++ b/plugin/src/App/StatusPages/Connected.lua @@ -13,6 +13,26 @@ local BorderedContainer = require(Plugin.App.Components.BorderedContainer) local e = Roact.createElement +local AGE_UNITS = { {31556909, "year"}, {2629743, "month"}, {604800, "week"}, {86400, "day"}, {3600, "hour"}, {60, "minute"}, } +function timeSinceText(elapsed: number): string + if elapsed < 3 then + return "just now" + end + + local ageText = string.format("%d seconds ago", 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 "") + break + end + end + + return ageText +end + local function ConnectionDetails(props) return Theme.with(function(theme) return e(BorderedContainer, { @@ -83,33 +103,59 @@ end local ConnectedPage = Roact.Component:extend("ConnectedPage") function ConnectedPage:render() - return Roact.createFragment({ - Header = e(Header, { - transparency = self.props.transparency, - layoutOrder = 1, - }), - - ConnectionDetails = e(ConnectionDetails, { - projectName = self.state.projectName, - address = self.state.address, - transparency = self.props.transparency, - layoutOrder = 2, - - onDisconnect = self.props.onDisconnect, - }), - - Layout = e("UIListLayout", { - VerticalAlignment = Enum.VerticalAlignment.Center, - FillDirection = Enum.FillDirection.Vertical, - SortOrder = Enum.SortOrder.LayoutOrder, - Padding = UDim.new(0, 10), - }), - - Padding = e("UIPadding", { - PaddingLeft = UDim.new(0, 20), - PaddingRight = UDim.new(0, 20), - }), - }) + return Theme.with(function(theme) + return Roact.createFragment({ + Header = e(Header, { + transparency = self.props.transparency, + layoutOrder = 1, + }), + + ConnectionDetails = e(ConnectionDetails, { + projectName = self.state.projectName, + address = self.state.address, + transparency = self.props.transparency, + layoutOrder = 2, + + onDisconnect = self.props.onDisconnect, + }), + + Info = e("TextLabel", { + Text = self.props.patchInfo:map(function(info) + return string.format( + "Synced %d change%s %s", + info.changes, + info.changes == 1 and "" or "s", + timeSinceText(os.time() - info.timestamp) + ) + end), + 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, + + Size = UDim2.new(1, 0, 0, 28), + + LayoutOrder = 3, + BackgroundTransparency = 1, + }), + + Layout = e("UIListLayout", { + VerticalAlignment = Enum.VerticalAlignment.Center, + FillDirection = Enum.FillDirection.Vertical, + SortOrder = Enum.SortOrder.LayoutOrder, + Padding = UDim.new(0, 10), + }), + + Padding = e("UIPadding", { + PaddingLeft = UDim.new(0, 20), + PaddingRight = UDim.new(0, 20), + }), + }) + end) end function ConnectedPage.getDerivedStateFromProps(props) diff --git a/plugin/src/App/init.lua b/plugin/src/App/init.lua index b477dd367..70ece3dfd 100644 --- a/plugin/src/App/init.lua +++ b/plugin/src/App/init.lua @@ -43,6 +43,10 @@ function App:init() self.host, self.setHost = Roact.createBinding("") self.port, self.setPort = Roact.createBinding("") + self.patchInfo, self.setPatchInfo = Roact.createBinding({ + changes = 0, + timestamp = os.time(), + }) self:setState({ appStatus = AppStatus.NotConnected, @@ -105,6 +109,34 @@ function App:startSession() twoWaySync = sessionOptions.twoWaySync, }) + serveSession:onPatchApplied(function(patch, unapplied) + local now = os.time() + local changes = 0 + + for _, set in patch do + for _ in set do + changes += 1 + end + end + for _, set in unapplied do + for _ in set do + changes -= 1 + end + end + + if changes == 0 then return end + + local old = self.patchInfo:getValue() + if now - old.timestamp < 2 then + changes += old.changes + end + + self.setPatchInfo({ + changes = changes, + timestamp = now, + }) + end) + serveSession:onStatusChanged(function(status, details) if status == ServeSession.Status.Connecting then self:setState({ @@ -148,6 +180,16 @@ 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() @@ -231,6 +273,7 @@ function App:render() Connected = createPageElement(AppStatus.Connected, { projectName = self.state.projectName, address = self.state.address, + patchInfo = self.patchInfo, onDisconnect = function() self:endSession() diff --git a/plugin/src/ServeSession.lua b/plugin/src/ServeSession.lua index 5ea3b11f7..90860ff7b 100644 --- a/plugin/src/ServeSession.lua +++ b/plugin/src/ServeSession.lua @@ -95,6 +95,7 @@ function ServeSession.new(options) __instanceMap = instanceMap, __changeBatcher = changeBatcher, __statusChangedCallback = nil, + __patchAppliedCallback = nil, __connections = connections, } @@ -122,6 +123,10 @@ function ServeSession:onStatusChanged(callback) self.__statusChangedCallback = callback end +function ServeSession:onPatchApplied(callback) + self.__patchAppliedCallback = callback +end + function ServeSession:start() self:__setStatus(Status.Connecting) @@ -234,6 +239,10 @@ function ServeSession:__initialSync(rootInstanceId) Log.warn("Could not apply all changes requested by the Rojo server:\n{}", PatchSet.humanSummary(self.__instanceMap, unappliedPatch)) end + + if self.__patchAppliedCallback then + pcall(self.__patchAppliedCallback, catchUpPatch, unappliedPatch) + end end) end @@ -247,6 +256,10 @@ function ServeSession:__mainSyncLoop() Log.warn("Could not apply all changes requested by the Rojo server:\n{}", PatchSet.humanSummary(self.__instanceMap, unappliedPatch)) end + + if self.__patchAppliedCallback then + pcall(self.__patchAppliedCallback, message, unappliedPatch) + end end if self.__status ~= Status.Disconnected then