mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-22 21:41:37 +00:00
Enhance SelectionOption component with hover state and caret visibility logic
- Refactored the `SelectionOption` to be a Roact component that tracks hover state. - Added logic to determine caret visibility based on node properties (parent-only, has children, and selection state). - Updated background and text transparency based on hover state for improved UX. - Adjusted event handling for mouse enter, mouse leave, and click actions to accommodate new behavior. - Modified `SelectionRadio` to pass necessary props to `SelectionOption` for consistent behavior.
This commit is contained in:
parent
2b2ada219e
commit
6ed96f1f7e
27 changed files with 99 additions and 56 deletions
|
|
@ -50,10 +50,32 @@ local function ChangeTag(props)
|
|||
end)
|
||||
end
|
||||
|
||||
-- Radio button option for selection
|
||||
local function SelectionOption(props)
|
||||
-- Radio button option for selection (as a component to track hover state)
|
||||
local SelectionOption = Roact.Component:extend("SelectionOption")
|
||||
|
||||
function SelectionOption:init()
|
||||
self:setState({
|
||||
isHovered = false,
|
||||
})
|
||||
end
|
||||
|
||||
function SelectionOption:render()
|
||||
local props = self.props
|
||||
local isHovered = self.state.isHovered
|
||||
|
||||
return Theme.with(function(theme)
|
||||
local isSelected = props.isSelected
|
||||
local isParentOnly = props.isParentOnly
|
||||
local hasChildren = props.hasChildren
|
||||
|
||||
-- Determine if we should show the caret:
|
||||
-- - Parent-only nodes: always show caret (they only do subtree operations)
|
||||
-- - Normal nodes with children: show caret when already selected AND hovering
|
||||
local showCaret = if isParentOnly
|
||||
then true
|
||||
elseif hasChildren and isSelected and isHovered then true
|
||||
else false
|
||||
|
||||
local bgColor = if isSelected
|
||||
then (if props.optionType == "push"
|
||||
then Color3.fromHex("27AE60")
|
||||
|
|
@ -61,37 +83,60 @@ local function SelectionOption(props)
|
|||
else Color3.fromHex("7F8C8D"))
|
||||
else theme.BorderedContainer.BackgroundColor
|
||||
local textColor = if isSelected then Color3.new(1, 1, 1) else theme.TextColor
|
||||
-- Add caret if this button can cascade to children
|
||||
local displayText = if props.showCaret then props.text .. " ▼" else props.text
|
||||
local buttonWidth = if props.showCaret then 48 else 36
|
||||
|
||||
local displayText = if showCaret then props.text .. " ▼" else props.text
|
||||
local buttonWidth = if showCaret then 48 else 36
|
||||
|
||||
-- Transparency logic:
|
||||
-- - Parent-only nodes: more transparent when not hovered, less so when hovering button
|
||||
-- - Normal nodes: standard transparency based on selection
|
||||
local bgTransparency, txtTransparency
|
||||
if isParentOnly then
|
||||
-- Parent-only: partially transparent, less so when hovering the button
|
||||
bgTransparency = props.transparency:map(function(t)
|
||||
return if isHovered then 0.2 + (0.8 * t) else 0.5 + (0.5 * t)
|
||||
end)
|
||||
txtTransparency = props.transparency:map(function(t)
|
||||
return if isHovered then 0 + t else 0.3 + (0.7 * t)
|
||||
end)
|
||||
else
|
||||
bgTransparency = props.transparency:map(function(t)
|
||||
return if isSelected then 0.1 + (0.9 * t) else 0.7 + (0.3 * t)
|
||||
end)
|
||||
txtTransparency = props.transparency
|
||||
end
|
||||
|
||||
return e("TextButton", {
|
||||
Size = UDim2.new(0, buttonWidth, 0, 18),
|
||||
BackgroundColor3 = bgColor,
|
||||
BackgroundTransparency = props.transparency:map(function(t)
|
||||
return if isSelected then 0.1 + (0.9 * t) else 0.7 + (0.3 * t)
|
||||
end),
|
||||
BackgroundTransparency = bgTransparency,
|
||||
BorderSizePixel = 0,
|
||||
Text = displayText,
|
||||
FontFace = if isSelected then theme.Font.Bold else theme.Font.Main,
|
||||
TextSize = theme.TextSize.Small,
|
||||
TextColor3 = textColor,
|
||||
TextTransparency = props.transparency,
|
||||
TextTransparency = txtTransparency,
|
||||
LayoutOrder = props.layoutOrder,
|
||||
ZIndex = 10,
|
||||
[Roact.Event.Activated] = function(_rbx, _input, clickCount)
|
||||
-- For parent-only nodes, any click triggers subtree selection
|
||||
if props.isParentOnly then
|
||||
if props.onDoubleClick then
|
||||
props.onDoubleClick()
|
||||
[Roact.Event.MouseEnter] = function()
|
||||
self:setState({ isHovered = true })
|
||||
end,
|
||||
[Roact.Event.MouseLeave] = function()
|
||||
self:setState({ isHovered = false })
|
||||
end,
|
||||
[Roact.Event.Activated] = function()
|
||||
if isParentOnly then
|
||||
-- Parent-only: single click applies to subtree
|
||||
if props.onSubtreeClick then
|
||||
props.onSubtreeClick()
|
||||
end
|
||||
elseif clickCount == 1 then
|
||||
-- Double click: apply to subtree
|
||||
if props.onDoubleClick then
|
||||
props.onDoubleClick()
|
||||
elseif isSelected and hasChildren then
|
||||
-- Already selected with children: click applies to subtree
|
||||
if props.onSubtreeClick then
|
||||
props.onSubtreeClick()
|
||||
end
|
||||
else
|
||||
-- Single click: apply to this node only
|
||||
-- Normal click: select this node only
|
||||
if props.onClick then
|
||||
props.onClick()
|
||||
end
|
||||
|
|
@ -111,12 +156,12 @@ local function SelectionRadio(props)
|
|||
return nil
|
||||
end
|
||||
|
||||
local showCaret = props.hasChildren
|
||||
local frameWidth = if showCaret then 154 else 118
|
||||
local hasChildren = props.hasChildren
|
||||
local isParentOnly = props.isParentOnly
|
||||
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(0, frameWidth, 0, 18),
|
||||
Size = UDim2.new(0, 0, 0, 18),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.new(1, -5, 0, 3),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
|
|
@ -135,14 +180,14 @@ local function SelectionRadio(props)
|
|||
isSelected = props.selection == "pull",
|
||||
transparency = props.transparency,
|
||||
layoutOrder = 1,
|
||||
showCaret = showCaret,
|
||||
hasChildren = hasChildren,
|
||||
isParentOnly = isParentOnly,
|
||||
onClick = function()
|
||||
if props.onSelectionChange then
|
||||
props.onSelectionChange(props.nodeId, "pull")
|
||||
end
|
||||
end,
|
||||
onDoubleClick = function()
|
||||
onSubtreeClick = function()
|
||||
if props.onSubtreeSelectionChange then
|
||||
props.onSubtreeSelectionChange(props.nodeId, "pull")
|
||||
end
|
||||
|
|
@ -154,14 +199,14 @@ local function SelectionRadio(props)
|
|||
isSelected = props.selection == "ignore",
|
||||
transparency = props.transparency,
|
||||
layoutOrder = 2,
|
||||
showCaret = showCaret,
|
||||
hasChildren = hasChildren,
|
||||
isParentOnly = isParentOnly,
|
||||
onClick = function()
|
||||
if props.onSelectionChange then
|
||||
props.onSelectionChange(props.nodeId, "ignore")
|
||||
end
|
||||
end,
|
||||
onDoubleClick = function()
|
||||
onSubtreeClick = function()
|
||||
if props.onSubtreeSelectionChange then
|
||||
props.onSubtreeSelectionChange(props.nodeId, "ignore")
|
||||
end
|
||||
|
|
@ -173,14 +218,14 @@ local function SelectionRadio(props)
|
|||
isSelected = props.selection == "push",
|
||||
transparency = props.transparency,
|
||||
layoutOrder = 3,
|
||||
showCaret = showCaret,
|
||||
hasChildren = hasChildren,
|
||||
isParentOnly = isParentOnly,
|
||||
onClick = function()
|
||||
if props.onSelectionChange then
|
||||
props.onSelectionChange(props.nodeId, "push")
|
||||
end
|
||||
end,
|
||||
onDoubleClick = function()
|
||||
onSubtreeClick = function()
|
||||
if props.onSubtreeSelectionChange then
|
||||
props.onSubtreeSelectionChange(props.nodeId, "push")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing OnlyOneCopy/child_of_one.luau
|
||||
Writing ReplicatedStorage/child_replicated_storage.luau
|
||||
Created sourcemap at <TEMP>/child_but_not\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -5,4 +5,3 @@ expression: normalized_stdout
|
|||
Writing src/csv.csv
|
||||
Writing src/csv_init/init.csv
|
||||
Writing src/csv_init
|
||||
Created sourcemap at <TEMP>/csv\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -3,4 +3,3 @@ source: tests/rojo_test/syncback_util.rs
|
|||
expression: normalized_stdout
|
||||
---
|
||||
Writing container.model.json5
|
||||
Created sourcemap at <TEMP>/duplicate_rojo_id\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ Writing src/ignored_part.model.json5
|
|||
Writing src/int_value.model.json5
|
||||
Writing src/subfolder/string_value.txt
|
||||
Writing src/subfolder
|
||||
Created sourcemap at <TEMP>/ignore_paths_adding\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing src/non-init.luau
|
||||
Writing src/init-file
|
||||
Created sourcemap at <TEMP>/ignore_paths_init\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -3,4 +3,3 @@ source: tests/rojo_test/syncback_util.rs
|
|||
expression: normalized_stdout
|
||||
---
|
||||
Writing src/Message.model.json5
|
||||
Created sourcemap at <TEMP>/ignore_paths_removing\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing src/IncludeMe/.gitkeep
|
||||
Writing src/IncludeMe
|
||||
Created sourcemap at <TEMP>/ignore_trees_adding\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -3,4 +3,3 @@ source: tests/rojo_test/syncback_util.rs
|
|||
expression: normalized_stdout
|
||||
---
|
||||
Removing src
|
||||
Created sourcemap at <TEMP>/ignore_trees_removing\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ Writing src/dir_with_meta/init.meta.json5
|
|||
Writing src/model_json.model.json5
|
||||
Writing src/project_json.project.json5
|
||||
Writing src/dir_with_meta
|
||||
Created sourcemap at <TEMP>/json_middlewares\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing nested.project.json5
|
||||
Writing string_value.txt
|
||||
Created sourcemap at <TEMP>/nested_projects\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ Writing src/modules/ClientModule.luau
|
|||
Writing src/modules/ServerModule.luau
|
||||
Removing client-only.project.json5
|
||||
Removing server-only.project.json5
|
||||
Created sourcemap at <TEMP>/nested_projects_weird\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing src/init.luau
|
||||
Writing src
|
||||
Created sourcemap at <TEMP>/project_init\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing attribute_mismatch.luau
|
||||
Writing property_mismatch.project.json5
|
||||
Created sourcemap at <TEMP>/project_reserialize\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ Writing src/ChildWithoutDuplicates/Child/.gitkeep
|
|||
Writing src/ChildWithDuplicates
|
||||
Writing src/ChildWithoutDuplicates
|
||||
Writing src/ChildWithoutDuplicates/Child
|
||||
Created sourcemap at <TEMP>/rbxm_fallback\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing src/pointer.model.json5
|
||||
Writing src/target.model.json5
|
||||
Created sourcemap at <TEMP>/ref_properties\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing src/pointer.model.json5
|
||||
Writing src/target.txt
|
||||
Created sourcemap at <TEMP>/ref_properties_blank\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ Writing src/Pointer_1.model.json5
|
|||
Writing src/Pointer_2.model.json5
|
||||
Writing src/Target_1.model.json5
|
||||
Writing src/Target_2.model.json5
|
||||
Created sourcemap at <TEMP>/ref_properties_conflict\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -7,4 +7,3 @@ Writing src/Pointer_2.model.json5
|
|||
Writing src/Target/init.meta.json5
|
||||
Writing src/Target
|
||||
Removing src/Target.model.json5
|
||||
Created sourcemap at <TEMP>/ref_properties_duplicate\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -5,4 +5,3 @@ expression: normalized_stdout
|
|||
Writing src/Pointer1.model.json5
|
||||
Writing src/Pointer2.model.json5
|
||||
Writing src/Pointer3.model.json5
|
||||
Created sourcemap at <TEMP>/ref_properties_pruned\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ Writing default.project.json5
|
|||
Writing src/model_json.model.json5
|
||||
Writing src/rbxm.rbxm
|
||||
Writing src/rbxmx.rbxmx
|
||||
Created sourcemap at <TEMP>/respect_old_middleware\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -8,4 +8,3 @@ Writing src/adjacent.meta.json5
|
|||
Writing src/init/init.meta.json5
|
||||
Writing src/model.model.json5
|
||||
Writing src/init
|
||||
Created sourcemap at <TEMP>/schema_roundtrip\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing default.project.json5
|
||||
Writing string_value.txt
|
||||
Created sourcemap at <TEMP>/string_value_project\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ expression: normalized_stdout
|
|||
---
|
||||
Writing src/module.modulescript
|
||||
Writing src/text.text
|
||||
Created sourcemap at <TEMP>/sync_rules\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -3,4 +3,3 @@ source: tests/rojo_test/syncback_util.rs
|
|||
expression: normalized_stdout
|
||||
---
|
||||
Writing default.project.json5
|
||||
Created sourcemap at <TEMP>/unscriptable_properties\sourcemap.json
|
||||
|
|
|
|||
|
|
@ -91,7 +91,13 @@ impl SourcemapCommand {
|
|||
.build_global()
|
||||
.unwrap();
|
||||
|
||||
write_sourcemap(&session, self.output.as_deref(), filter, self.absolute)?;
|
||||
write_sourcemap(
|
||||
&session,
|
||||
self.output.as_deref(),
|
||||
filter,
|
||||
self.absolute,
|
||||
false,
|
||||
)?;
|
||||
|
||||
if self.watch {
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
|
@ -102,7 +108,13 @@ impl SourcemapCommand {
|
|||
cursor = new_cursor;
|
||||
|
||||
if patch_set_affects_sourcemap(&session, &patch_set, filter) {
|
||||
write_sourcemap(&session, self.output.as_deref(), filter, self.absolute)?;
|
||||
write_sourcemap(
|
||||
&session,
|
||||
self.output.as_deref(),
|
||||
filter,
|
||||
self.absolute,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -234,6 +246,7 @@ pub(crate) fn write_sourcemap(
|
|||
output: Option<&Path>,
|
||||
filter: fn(&InstanceWithMeta) -> bool,
|
||||
use_absolute_paths: bool,
|
||||
quiet: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let tree = session.tree();
|
||||
|
||||
|
|
@ -252,7 +265,9 @@ pub(crate) fn write_sourcemap(
|
|||
file.write_all(json_output.as_bytes())?;
|
||||
file.flush()?;
|
||||
|
||||
println!("Created sourcemap at {}", output_path.display());
|
||||
if !quiet {
|
||||
println!("Created sourcemap at {}", output_path.display());
|
||||
}
|
||||
} else {
|
||||
// Use standard JSON (not JSON5) for sourcemaps - required by external tools like LSPs
|
||||
let output = serde_json::to_string(&root_node)?;
|
||||
|
|
|
|||
|
|
@ -214,7 +214,14 @@ impl SyncbackCommand {
|
|||
|
||||
// Generate sourcemap after successful syncback
|
||||
let sourcemap_path = base_path.join("sourcemap.json");
|
||||
write_sourcemap(&session_old, Some(&sourcemap_path), filter_nothing, false)?;
|
||||
write_sourcemap(
|
||||
&session_old,
|
||||
Some(&sourcemap_path),
|
||||
filter_nothing,
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
eprintln!("Generated sourcemap at {}", sourcemap_path.display());
|
||||
|
||||
// Refresh git index if in a git repository
|
||||
refresh_git_index(base_path);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue