From ce530e795a51026936c3dee46eb34cad00c0c4ff Mon Sep 17 00:00:00 2001 From: boatbomber Date: Wed, 5 Jul 2023 14:09:11 -0700 Subject: [PATCH] Fix Rojo breaking when users undo/redo in Studio (#708) --- CHANGELOG.md | 4 +- plugin/src/App/init.lua | 105 ++++++++++++++++++++++++--- plugin/src/Reconciler/applyPatch.lua | 6 ++ 3 files changed, 104 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec66d3c6..373eef77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Skip confirming patches that contain only a datamodel name change. ([#688]) * Added sync reminder notification. ([#689]) * Added protection against syncing a model to a place. ([#691]) +* Fix Rojo breaking when users undo/redo in Studio ([#708]) [#668]: https://github.com/rojo-rbx/rojo/pull/668 [#674]: https://github.com/rojo-rbx/rojo/pull/674 @@ -14,6 +15,7 @@ [#688]: https://github.com/rojo-rbx/rojo/pull/688 [#689]: https://github.com/rojo-rbx/rojo/pull/689 [#691]: https://github.com/rojo-rbx/rojo/pull/691 +[#708]: https://github.com/rojo-rbx/rojo/pull/708 ## [7.3.0] - April 22, 2023 * Added `$attributes` to project format. ([#574]) @@ -579,4 +581,4 @@ This is a general maintenance release for the Rojo 0.5.x release series. * More robust syncing with a new reconciler ## [0.1.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.1.0) (November 29, 2017) -* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs) \ No newline at end of file +* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs) diff --git a/plugin/src/App/init.lua b/plugin/src/App/init.lua index 9fe081d7..c7be7671 100644 --- a/plugin/src/App/init.lua +++ b/plugin/src/App/init.lua @@ -1,3 +1,4 @@ +local ChangeHistoryService = game:GetService("ChangeHistoryService") local Players = game:GetService("Players") local ServerStorage = game:GetService("ServerStorage") local RunService = game:GetService("RunService") @@ -57,6 +58,64 @@ function App:init() self.confirmationEvent = self.confirmationBindable.Event self.notifId = 0 + self.waypointConnection = ChangeHistoryService.OnUndo:Connect(function(action: string) + if not string.find(action, "^Rojo: Patch") then + return + end + + local undoConnection, redoConnection = nil, nil + local function cleanup() + undoConnection:Disconnect() + redoConnection:Disconnect() + end + + Log.warn( + string.format( + "You've undone '%s'.\nIf this was not intended, please Redo in the topbar or with Ctrl/⌘+Y.", + action + ) + ) + local dismissNotif = self:addNotification( + string.format("You've undone '%s'.\nIf this was not intended, please restore.", action), + 10, + { + Restore = { + text = "Restore", + style = "Solid", + layoutOrder = 1, + onClick = function(notification) + cleanup() + notification:dismiss() + ChangeHistoryService:Redo() + end, + }, + Dismiss = { + text = "Dismiss", + style = "Bordered", + layoutOrder = 2, + onClick = function(notification) + cleanup() + notification:dismiss() + end, + }, + } + ) + + undoConnection = ChangeHistoryService.OnUndo:Once(function() + -- Our notif is now out of date- redoing will not restore the patch + -- since we've undone even further. Dismiss the notif. + cleanup() + dismissNotif() + end) + redoConnection = ChangeHistoryService.OnRedo:Once(function(redoneAction: string) + if redoneAction == action then + -- The user has restored the patch, so we can dismiss the notif + cleanup() + dismissNotif() + end + end) + end) + self:setState({ appStatus = AppStatus.NotConnected, guiEnabled = false, @@ -83,7 +142,7 @@ function App:init() onClick = function(notification) notification:dismiss() self:startSession() - end + end, }, Dismiss = { text = "Dismiss", @@ -97,7 +156,16 @@ function App:init() end end -function App:addNotification(text: string, timeout: number?, actions: { [string]: {text: string, style: string, layoutOrder: number, onClick: (any) -> ()} }?) +function App:willUnmount() + self.waypointConnection:Disconnect() + self.confirmationBindable:Destroy() +end + +function App:addNotification( + text: string, + timeout: number?, + actions: { [string]: { text: string, style: string, layoutOrder: number, onClick: (any) -> () } }? +) if not Settings:get("showNotifications") then return end @@ -123,6 +191,10 @@ function App:addNotification(text: string, timeout: number?, actions: { [string] end function App:closeNotification(id: number) + if not self.state.notifications[id] then + return + end + local notifications = table.clone(self.state.notifications) notifications[id] = nil @@ -133,26 +205,38 @@ end function App:getPriorEndpoint() local priorEndpoints = Settings:get("priorEndpoints") - if not priorEndpoints then return end + if not priorEndpoints then + return + end local id = tostring(game.PlaceId) - if ignorePlaceIds[id] then return end + if ignorePlaceIds[id] then + return + end local place = priorEndpoints[id] - if not place then return end + if not place then + return + end return place.host, place.port end function App:getLastSyncTimestamp() local priorEndpoints = Settings:get("priorEndpoints") - if not priorEndpoints then return end + if not priorEndpoints then + return + end local id = tostring(game.PlaceId) - if ignorePlaceIds[id] then return end + if ignorePlaceIds[id] then + return + end local place = priorEndpoints[id] - if not place then return end + if not place then + return + end return place.timestamp end @@ -172,7 +256,9 @@ function App:setPriorEndpoint(host: string, port: string) end local id = tostring(game.PlaceId) - if ignorePlaceIds[id] then return end + if ignorePlaceIds[id] then + return + end priorEndpoints[id] = { host = if host ~= Config.defaultHost then host else nil, @@ -181,7 +267,6 @@ function App:setPriorEndpoint(host: string, port: string) } Log.trace("Saved last used endpoint for {}", game.PlaceId) - Settings:set("priorEndpoints", priorEndpoints) end diff --git a/plugin/src/Reconciler/applyPatch.lua b/plugin/src/Reconciler/applyPatch.lua index a625ebe9..4faa7310 100644 --- a/plugin/src/Reconciler/applyPatch.lua +++ b/plugin/src/Reconciler/applyPatch.lua @@ -5,6 +5,8 @@ Patches can come from the server or be generated by the client. ]] +local ChangeHistoryService = game:GetService("ChangeHistoryService") + local Packages = script.Parent.Parent.Parent.Packages local Log = require(Packages.Log) @@ -17,6 +19,8 @@ local reify = require(script.Parent.reify) local setProperty = require(script.Parent.setProperty) local function applyPatch(instanceMap, patch) + local patchTimestamp = DateTime.now():FormatLocalTime("LTS", "en-us") + -- Tracks any portions of the patch that could not be applied to the DOM. local unappliedPatch = PatchSet.newEmpty() @@ -199,6 +203,8 @@ local function applyPatch(instanceMap, patch) end end + ChangeHistoryService:SetWaypoint("Rojo: Patch " .. patchTimestamp) + return unappliedPatch end