Files
rojo/plugin/src/PatchSet.lua
boatbomber b5ed952d5c Add visual diffs to syncing (#603)
* Add user confirmation to initial sync

* Use "Accept" instead of "Confirm"

* Draw tree alphabetically for determinism

* Add diff table dropdown

* Add diff table to newly added objects

* Unblock keybind workflow

* Only show reject button when two way is enabled

* Try to patch back to the files when changes are rejected

* Improve text spacing of the prop diff table

* Skip user confirmation of perfect syncs

* Give instances names for debugging UI

* Optimize tree building

* Efficiency: dynamic virtual scrolling & lazy rendering

* Simplify virtual scroller logic and avoid wasteful rerenders

* Remove debug print

* Consistent naming

* Move new patch applied callback into accept

* Pcall archivable

* Keybinds open popup diff window

* Theme rows in diff

* Remove relic of prototype

* Color value visuals and better component name

* changeBatcher is not needed when no sync is active

* Simplify popup roact entrypoint

* Alphabetical prop lists and refactor

* Add a stroke to color blot for contrast

* Make color blots animate transparency with the rest of the page

* StyLua formatting on newly added files

* Remove wasteful table

* Fix diffing custom properties

* Display tables more meaningfully

* Allow children in the button components

* Create a rough tooltip component

* Add tooltips to buttons

* Use provider+trigger schema to avoid tooltip ZIndex issues

* Add triangle point to tooltip

* Tooltip underneath instead of covering

* Cancel hovers when unmounting

* Allow multiple canvases from one provider

* Display above or below depending on available space

* Move patch equality to PatchSet.isEqual

* Use Container

* Remove old submodules

* Reduce false positives in diff

* Add debug log

* Fuzzy equals CFrame in diffs to avoid floating point in

* Fix decodeValue usage

* Support the .changedName patches

* Fix content overlapping border

* Fix tooltip tail alignment

* Fix tooltip text fit

* Whoops, fix it properly

* Move PatchVisualizer to Components

* Provide Connected info with full patch data

* Avoid implicit nil return

* Add patch visualizer to connected page

* Make Current column invisible when visualizing applied patches

* Avoid floating point diffs in a numbers and vectors
2023-04-01 23:17:23 -04:00

246 lines
5.0 KiB
Lua

--[[
Methods to operate on either a patch created by the hydrate method, or a
patch returned from the API.
]]
local Packages = script.Parent.Parent.Packages
local t = require(Packages.t)
local Types = require(script.Parent.Types)
local function deepEqual(a: any, b: any): boolean
local typeA = typeof(a)
if typeA ~= typeof(b) then
return false
end
if typeof(a) == "table" then
local checkedKeys = {}
for key, value in a do
checkedKeys[key] = true
if deepEqual(value, b[key]) == false then
return false
end
end
for key, value in b do
if checkedKeys[key] then continue end
if deepEqual(value, a[key]) == false then
return false
end
end
return true
end
if a == b then
return true
end
return false
end
local PatchSet = {}
PatchSet.validate = t.interface({
removed = t.array(t.union(Types.RbxId, t.Instance)),
added = t.map(Types.RbxId, Types.ApiInstance),
updated = t.array(Types.ApiInstanceUpdate),
})
--[[
Create a new, empty PatchSet.
]]
function PatchSet.newEmpty()
return {
removed = {},
added = {},
updated = {},
}
end
--[[
Tells whether the given PatchSet is empty.
]]
function PatchSet.isEmpty(patchSet)
return next(patchSet.removed) == nil and
next(patchSet.added) == nil and
next(patchSet.updated) == nil
end
--[[
Tells whether the given PatchSet has any remove operations.
]]
function PatchSet.hasRemoves(patchSet)
return next(patchSet.removed) ~= nil
end
--[[
Tells whether the given PatchSet has any add operations.
]]
function PatchSet.hasAdditions(patchSet)
return next(patchSet.added) ~= nil
end
--[[
Tells whether the given PatchSet has any update operations.
]]
function PatchSet.hasUpdates(patchSet)
return next(patchSet.updated) ~= nil
end
--[[
Tells whether the given PatchSets are equal.
]]
function PatchSet.isEqual(patchA, patchB)
return deepEqual(patchA, patchB)
end
--[[
Count the number of changes in the given PatchSet.
]]
function PatchSet.countChanges(patch)
local count = 0
for _ in patch.added do
count += 1
end
for _ in patch.removed do
count += 1
end
for _ in patch.updated do
count += 1
end
return count
end
--[[
Merge multiple PatchSet objects into the given PatchSet.
]]
function PatchSet.assign(target, ...)
for i = 1, select("#", ...) do
local sourcePatch = select(i, ...)
for _, removed in ipairs(sourcePatch.removed) do
table.insert(target.removed, removed)
end
for id, added in pairs(sourcePatch.added) do
target.added[id] = added
end
for _, update in ipairs(sourcePatch.updated) do
table.insert(target.updated, update)
end
end
return target
end
--[[
Create a list of human-readable statements summarizing the contents of this
patch, intended to be displayed to users.
]]
function PatchSet.humanSummary(instanceMap, patchSet)
local statements = {}
for _, idOrInstance in ipairs(patchSet.removed) do
local instance, id
if type(idOrInstance) == "string" then
id = idOrInstance
instance = instanceMap.fromIds[id]
else
instance = idOrInstance
id = instanceMap.fromInstances[instance]
end
if instance ~= nil then
table.insert(statements, string.format("- Delete instance %s", instance:GetFullName()))
else
table.insert(statements, string.format("- Delete instance with ID %s", id))
end
end
local additionsMentioned = {}
local function addAllDescendents(virtualInstance)
additionsMentioned[virtualInstance.Id] = true
for _, childId in ipairs(virtualInstance.Children) do
addAllDescendents(patchSet.added[childId])
end
end
for id, addition in pairs(patchSet.added) do
if additionsMentioned[id] then
continue
end
local virtualInstance = addition
while true do
if virtualInstance.Parent == nil then
break
end
local virtualParent = patchSet.added[virtualInstance.Parent]
if virtualParent == nil then
break
end
virtualInstance = virtualParent
end
local parentDisplayName = "nil (how strange!)"
if virtualInstance.Parent ~= nil then
local parent = instanceMap.fromIds[virtualInstance.Parent]
if parent ~= nil then
parentDisplayName = parent:GetFullName()
end
end
table.insert(statements, string.format(
"- Add instance %q (ClassName %q) to %s",
virtualInstance.Name, virtualInstance.ClassName, parentDisplayName))
end
for _, update in ipairs(patchSet.updated) do
local updatedProperties = {}
if update.changedMetadata ~= nil then
table.insert(updatedProperties, "Rojo's Metadata")
end
if update.changedName ~= nil then
table.insert(updatedProperties, "Name")
end
if update.changedClassName ~= nil then
table.insert(updatedProperties, "ClassName")
end
for name in pairs(update.changedProperties) do
table.insert(updatedProperties, name)
end
local instance = instanceMap.fromIds[update.id]
local displayName
if instance ~= nil then
displayName = instance:GetFullName()
else
displayName = "[unknown instance]"
end
table.insert(statements, string.format(
"- Update properties on %s: %s",
displayName, table.concat(updatedProperties, ",")))
end
return table.concat(statements, "\n")
end
return PatchSet