mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Add filter functionality to PatchVisualizer and ConfirmingPage
- Implemented a case-insensitive wildcard matching function in PatchVisualizer to filter visible nodes based on user input. - Enhanced the ConfirmingPage to include a TextInput for filtering, allowing users to search for specific nodes in the PatchVisualizer. - Updated the rendering logic to display appropriate messages based on the filter state, improving user experience and clarity.
This commit is contained in:
parent
ff96d98d79
commit
c092bb975c
6 changed files with 295 additions and 30 deletions
|
|
@ -15,6 +15,38 @@ local e = Roact.createElement
|
|||
|
||||
local DomLabel = require(script.DomLabel)
|
||||
|
||||
-- Convert wildcard pattern (with *) to case-insensitive match
|
||||
-- e.g., "player*sync" matches "PlayerDataSync"
|
||||
local function matchesWildcard(name: string, pattern: string): boolean
|
||||
if pattern == "" then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Escape Lua pattern special chars except *
|
||||
local escaped = pattern:gsub("([%.%+%-%?%^%$%(%)%[%]%%])", "%%%1")
|
||||
-- Convert * to .* (match any characters)
|
||||
local luaPattern = "^" .. escaped:gsub("%*", ".*") .. "$"
|
||||
|
||||
return string.match(name:lower(), luaPattern:lower()) ~= nil
|
||||
end
|
||||
|
||||
-- Check if node or any of its descendants match the filter
|
||||
local function nodeOrDescendantMatches(node, pattern: string): boolean
|
||||
if matchesWildcard(node.name or "", pattern) then
|
||||
return true
|
||||
end
|
||||
|
||||
if node.children then
|
||||
for _, child in node.children do
|
||||
if type(child) == "table" and nodeOrDescendantMatches(child, pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local PatchVisualizer = Roact.Component:extend("PatchVisualizer")
|
||||
|
||||
function PatchVisualizer:init()
|
||||
|
|
@ -36,6 +68,10 @@ function PatchVisualizer:shouldUpdate(nextProps)
|
|||
return true
|
||||
end
|
||||
|
||||
if self.props.filterText ~= nextProps.filterText then
|
||||
return true
|
||||
end
|
||||
|
||||
local currentPatch, nextPatch = self.props.patch, nextProps.patch
|
||||
if currentPatch ~= nil or nextPatch ~= nil then
|
||||
return not PatchSet.isEqual(currentPatch, nextPatch)
|
||||
|
|
@ -58,23 +94,73 @@ function PatchVisualizer:render()
|
|||
end
|
||||
end
|
||||
|
||||
-- Recusively draw tree
|
||||
-- Recursively draw tree
|
||||
local scrollElements, elementHeights, elementIndex = {}, {}, 0
|
||||
local filterText = self.props.filterText or ""
|
||||
|
||||
if patchTree then
|
||||
local elementTotal = patchTree:getCount()
|
||||
-- First pass: collect visible nodes in proper tree order (depth-first, alphabetical)
|
||||
local visibleNodes = {}
|
||||
|
||||
local function collectVisibleNodes(node, depth)
|
||||
local shouldShow = filterText == "" or nodeOrDescendantMatches(node, filterText)
|
||||
|
||||
if shouldShow then
|
||||
table.insert(visibleNodes, { node = node, depth = depth })
|
||||
|
||||
-- Process children in alphabetical order (depth-first)
|
||||
if node.children then
|
||||
local sortedChildren = {}
|
||||
for _, child in node.children do
|
||||
if type(child) == "table" then
|
||||
table.insert(sortedChildren, child)
|
||||
end
|
||||
end
|
||||
table.sort(sortedChildren, function(a, b)
|
||||
return (a.name or "") < (b.name or "")
|
||||
end)
|
||||
|
||||
for _, child in ipairs(sortedChildren) do
|
||||
collectVisibleNodes(child, depth + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Collect from root children in alphabetical order
|
||||
local rootChildren = {}
|
||||
for _, child in patchTree.ROOT.children do
|
||||
if type(child) == "table" then
|
||||
table.insert(rootChildren, child)
|
||||
end
|
||||
end
|
||||
table.sort(rootChildren, function(a, b)
|
||||
return (a.name or "") < (b.name or "")
|
||||
end)
|
||||
|
||||
for _, child in ipairs(rootChildren) do
|
||||
collectVisibleNodes(child, 1)
|
||||
end
|
||||
|
||||
local elementTotal = #visibleNodes
|
||||
local depthsComplete = {}
|
||||
local function drawNode(node, depth)
|
||||
|
||||
local function drawNode(node, depth, nodeIndex)
|
||||
elementIndex += 1
|
||||
|
||||
local parentNode = patchTree:getNode(node.parentId)
|
||||
-- Check if this is the final visible child among siblings
|
||||
-- Look ahead to see if there's another node at the same depth with the same parent
|
||||
local isFinalChild = true
|
||||
if parentNode then
|
||||
for _id, sibling in parentNode.children do
|
||||
if type(sibling) == "table" and sibling.name and sibling.name > node.name then
|
||||
isFinalChild = false
|
||||
break
|
||||
end
|
||||
for i = nodeIndex + 1, #visibleNodes do
|
||||
local nextEntry = visibleNodes[i]
|
||||
if nextEntry.depth < depth then
|
||||
-- Went back up the tree, we're done checking
|
||||
break
|
||||
end
|
||||
if nextEntry.depth == depth and nextEntry.node.parentId == node.parentId then
|
||||
-- Found a sibling at the same depth
|
||||
isFinalChild = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -112,14 +198,16 @@ function PatchVisualizer:render()
|
|||
end
|
||||
end
|
||||
|
||||
patchTree:forEach(function(node, depth)
|
||||
-- Draw all visible nodes in order
|
||||
for i, entry in ipairs(visibleNodes) do
|
||||
local depth = entry.depth
|
||||
depthsComplete[depth] = false
|
||||
for i = depth + 1, #depthsComplete do
|
||||
depthsComplete[i] = nil
|
||||
for j = depth + 1, #depthsComplete do
|
||||
depthsComplete[j] = nil
|
||||
end
|
||||
|
||||
drawNode(node, depth)
|
||||
end)
|
||||
drawNode(entry.node, depth, i)
|
||||
end
|
||||
end
|
||||
|
||||
return Theme.with(function(theme)
|
||||
|
|
@ -132,7 +220,9 @@ function PatchVisualizer:render()
|
|||
}, {
|
||||
CleanMerge = e("TextLabel", {
|
||||
Visible = #scrollElements == 0,
|
||||
Text = "No changes to sync, project is up to date.",
|
||||
Text = if filterText ~= ""
|
||||
then "No items match the filter."
|
||||
else "No changes to sync, project is up to date.",
|
||||
FontFace = theme.Font.Main,
|
||||
TextSize = theme.TextSize.Medium,
|
||||
TextColor3 = theme.TextColor,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ local Roact = require(Packages.Roact)
|
|||
local Theme = require(Plugin.App.Theme)
|
||||
local PatchTree = require(Plugin.PatchTree)
|
||||
local TextButton = require(Plugin.App.Components.TextButton)
|
||||
local TextInput = require(Plugin.App.Components.TextInput)
|
||||
local StudioPluginGui = require(Plugin.App.Components.Studio.StudioPluginGui)
|
||||
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
||||
|
|
@ -37,6 +38,7 @@ function ConfirmingPage:init()
|
|||
showingAcceptConfirm = false,
|
||||
unselectedCount = 0,
|
||||
selections = initialSelections,
|
||||
filterText = "",
|
||||
})
|
||||
|
||||
-- Callback to update individual selection
|
||||
|
|
@ -116,27 +118,48 @@ function ConfirmingPage:render()
|
|||
|
||||
return Theme.with(function(theme)
|
||||
local pageContent = Roact.createFragment({
|
||||
Title = e("TextLabel", {
|
||||
Text = string.format(
|
||||
"Sync changes for project '%s':",
|
||||
self.props.confirmData.serverInfo.projectName or "UNKNOWN"
|
||||
),
|
||||
FontFace = theme.Font.Thin,
|
||||
LineHeight = 1.2,
|
||||
TextSize = theme.TextSize.Body,
|
||||
TextColor3 = theme.TextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextTransparency = self.props.transparency,
|
||||
Size = UDim2.new(1, 0, 0, theme.TextSize.Large + 2),
|
||||
Header = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 28),
|
||||
BackgroundTransparency = 1,
|
||||
LayoutOrder = 1,
|
||||
}, {
|
||||
Title = e("TextLabel", {
|
||||
Text = string.format(
|
||||
"Sync changes for project '%s':",
|
||||
self.props.confirmData.serverInfo.projectName or "UNKNOWN"
|
||||
),
|
||||
FontFace = theme.Font.Thin,
|
||||
LineHeight = 1.2,
|
||||
TextSize = theme.TextSize.Body,
|
||||
TextColor3 = theme.TextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Center,
|
||||
TextTransparency = self.props.transparency,
|
||||
Size = UDim2.new(0.5, -5, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
|
||||
FilterInput = e(TextInput, {
|
||||
text = self.state.filterText,
|
||||
placeholder = "Filter (* = wildcard)",
|
||||
enabled = true,
|
||||
transparency = self.props.transparency,
|
||||
size = UDim2.new(0.5, -5, 1, 0),
|
||||
position = UDim2.new(0.5, 5, 0, 0),
|
||||
onEntered = function(text)
|
||||
self:setState({ filterText = text })
|
||||
end,
|
||||
}),
|
||||
}),
|
||||
|
||||
PatchVisualizer = e(PatchVisualizer, {
|
||||
size = UDim2.new(1, 0, 1, -100),
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 3,
|
||||
layoutOrder = 2,
|
||||
|
||||
patchTree = self.props.patchTree,
|
||||
filterText = self.state.filterText,
|
||||
selections = self.state.selections,
|
||||
onSelectionChange = self.onSelectionChange,
|
||||
onSubtreeSelectionChange = self.onSubtreeSelectionChange,
|
||||
|
|
@ -159,7 +182,7 @@ function ConfirmingPage:render()
|
|||
|
||||
Buttons = e("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 34),
|
||||
LayoutOrder = 4,
|
||||
LayoutOrder = 3,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
-- Only show Abort button when there are changes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue