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)
|
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")
|
local PatchVisualizer = Roact.Component:extend("PatchVisualizer")
|
||||||
|
|
||||||
function PatchVisualizer:init()
|
function PatchVisualizer:init()
|
||||||
|
|
@ -36,6 +68,10 @@ function PatchVisualizer:shouldUpdate(nextProps)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self.props.filterText ~= nextProps.filterText then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
local currentPatch, nextPatch = self.props.patch, nextProps.patch
|
local currentPatch, nextPatch = self.props.patch, nextProps.patch
|
||||||
if currentPatch ~= nil or nextPatch ~= nil then
|
if currentPatch ~= nil or nextPatch ~= nil then
|
||||||
return not PatchSet.isEqual(currentPatch, nextPatch)
|
return not PatchSet.isEqual(currentPatch, nextPatch)
|
||||||
|
|
@ -58,23 +94,73 @@ function PatchVisualizer:render()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Recusively draw tree
|
-- Recursively draw tree
|
||||||
local scrollElements, elementHeights, elementIndex = {}, {}, 0
|
local scrollElements, elementHeights, elementIndex = {}, {}, 0
|
||||||
|
local filterText = self.props.filterText or ""
|
||||||
|
|
||||||
if patchTree then
|
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 depthsComplete = {}
|
||||||
local function drawNode(node, depth)
|
|
||||||
|
local function drawNode(node, depth, nodeIndex)
|
||||||
elementIndex += 1
|
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
|
local isFinalChild = true
|
||||||
if parentNode then
|
for i = nodeIndex + 1, #visibleNodes do
|
||||||
for _id, sibling in parentNode.children do
|
local nextEntry = visibleNodes[i]
|
||||||
if type(sibling) == "table" and sibling.name and sibling.name > node.name then
|
if nextEntry.depth < depth then
|
||||||
isFinalChild = false
|
-- Went back up the tree, we're done checking
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
if nextEntry.depth == depth and nextEntry.node.parentId == node.parentId then
|
||||||
|
-- Found a sibling at the same depth
|
||||||
|
isFinalChild = false
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -112,14 +198,16 @@ function PatchVisualizer:render()
|
||||||
end
|
end
|
||||||
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
|
depthsComplete[depth] = false
|
||||||
for i = depth + 1, #depthsComplete do
|
for j = depth + 1, #depthsComplete do
|
||||||
depthsComplete[i] = nil
|
depthsComplete[j] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
drawNode(node, depth)
|
drawNode(entry.node, depth, i)
|
||||||
end)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
|
|
@ -132,7 +220,9 @@ function PatchVisualizer:render()
|
||||||
}, {
|
}, {
|
||||||
CleanMerge = e("TextLabel", {
|
CleanMerge = e("TextLabel", {
|
||||||
Visible = #scrollElements == 0,
|
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,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = theme.TextSize.Medium,
|
TextSize = theme.TextSize.Medium,
|
||||||
TextColor3 = theme.TextColor,
|
TextColor3 = theme.TextColor,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ local Roact = require(Packages.Roact)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local PatchTree = require(Plugin.PatchTree)
|
local PatchTree = require(Plugin.PatchTree)
|
||||||
local TextButton = require(Plugin.App.Components.TextButton)
|
local TextButton = require(Plugin.App.Components.TextButton)
|
||||||
|
local TextInput = require(Plugin.App.Components.TextInput)
|
||||||
local StudioPluginGui = require(Plugin.App.Components.Studio.StudioPluginGui)
|
local StudioPluginGui = require(Plugin.App.Components.Studio.StudioPluginGui)
|
||||||
local Tooltip = require(Plugin.App.Components.Tooltip)
|
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||||
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
||||||
|
|
@ -37,6 +38,7 @@ function ConfirmingPage:init()
|
||||||
showingAcceptConfirm = false,
|
showingAcceptConfirm = false,
|
||||||
unselectedCount = 0,
|
unselectedCount = 0,
|
||||||
selections = initialSelections,
|
selections = initialSelections,
|
||||||
|
filterText = "",
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Callback to update individual selection
|
-- Callback to update individual selection
|
||||||
|
|
@ -116,27 +118,48 @@ function ConfirmingPage:render()
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local pageContent = Roact.createFragment({
|
local pageContent = Roact.createFragment({
|
||||||
Title = e("TextLabel", {
|
Header = e("Frame", {
|
||||||
Text = string.format(
|
Size = UDim2.new(1, 0, 0, 28),
|
||||||
"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),
|
|
||||||
BackgroundTransparency = 1,
|
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, {
|
PatchVisualizer = e(PatchVisualizer, {
|
||||||
size = UDim2.new(1, 0, 1, -100),
|
size = UDim2.new(1, 0, 1, -100),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 3,
|
layoutOrder = 2,
|
||||||
|
|
||||||
patchTree = self.props.patchTree,
|
patchTree = self.props.patchTree,
|
||||||
|
filterText = self.state.filterText,
|
||||||
selections = self.state.selections,
|
selections = self.state.selections,
|
||||||
onSelectionChange = self.onSelectionChange,
|
onSelectionChange = self.onSelectionChange,
|
||||||
onSubtreeSelectionChange = self.onSubtreeSelectionChange,
|
onSubtreeSelectionChange = self.onSubtreeSelectionChange,
|
||||||
|
|
@ -159,7 +182,7 @@ function ConfirmingPage:render()
|
||||||
|
|
||||||
Buttons = e("Frame", {
|
Buttons = e("Frame", {
|
||||||
Size = UDim2.new(1, 0, 0, 34),
|
Size = UDim2.new(1, 0, 0, 34),
|
||||||
LayoutOrder = 4,
|
LayoutOrder = 3,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
-- Only show Abort button when there are changes
|
-- Only show Abort button when there are changes
|
||||||
|
|
|
||||||
42
src/cli/cursor.rs
Normal file
42
src/cli/cursor.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use super::resolve_path;
|
||||||
|
|
||||||
|
/// Open the project directory in Cursor IDE.
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct CursorCommand {
|
||||||
|
/// Path to open. Defaults to the current directory.
|
||||||
|
#[clap(default_value = ".")]
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CursorCommand {
|
||||||
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
|
let path = resolve_path(&self.path);
|
||||||
|
|
||||||
|
// Use cmd /c to run cursor through the shell (cursor is a shell command, not an exe)
|
||||||
|
#[cfg(windows)]
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(["/c", "cursor", &path.to_string_lossy()])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to launch Cursor IDE. Is 'cursor' in your PATH?")?;
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
Command::new("cursor")
|
||||||
|
.arg(&*path)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to launch Cursor IDE. Is 'cursor' in your PATH?")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
//! Defines Rojo's CLI through clap types.
|
//! Defines Rojo's CLI through clap types.
|
||||||
|
|
||||||
mod build;
|
mod build;
|
||||||
|
mod cursor;
|
||||||
mod doc;
|
mod doc;
|
||||||
mod fmt_project;
|
mod fmt_project;
|
||||||
mod init;
|
mod init;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod serve;
|
mod serve;
|
||||||
mod sourcemap;
|
mod sourcemap;
|
||||||
|
mod studio;
|
||||||
mod syncback;
|
mod syncback;
|
||||||
mod upload;
|
mod upload;
|
||||||
|
|
||||||
|
|
@ -16,12 +18,14 @@ use clap::Parser;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use self::build::BuildCommand;
|
pub use self::build::BuildCommand;
|
||||||
|
pub use self::cursor::CursorCommand;
|
||||||
pub use self::doc::DocCommand;
|
pub use self::doc::DocCommand;
|
||||||
pub use self::fmt_project::FmtProjectCommand;
|
pub use self::fmt_project::FmtProjectCommand;
|
||||||
pub use self::init::{InitCommand, InitKind};
|
pub use self::init::{InitCommand, InitKind};
|
||||||
pub use self::plugin::{PluginCommand, PluginSubcommand};
|
pub use self::plugin::{PluginCommand, PluginSubcommand};
|
||||||
pub use self::serve::ServeCommand;
|
pub use self::serve::ServeCommand;
|
||||||
pub use self::sourcemap::SourcemapCommand;
|
pub use self::sourcemap::SourcemapCommand;
|
||||||
|
pub use self::studio::StudioCommand;
|
||||||
pub use self::syncback::SyncbackCommand;
|
pub use self::syncback::SyncbackCommand;
|
||||||
pub use self::upload::UploadCommand;
|
pub use self::upload::UploadCommand;
|
||||||
|
|
||||||
|
|
@ -46,8 +50,10 @@ impl Options {
|
||||||
Subcommand::Upload(subcommand) => subcommand.run(),
|
Subcommand::Upload(subcommand) => subcommand.run(),
|
||||||
Subcommand::Sourcemap(subcommand) => subcommand.run(),
|
Subcommand::Sourcemap(subcommand) => subcommand.run(),
|
||||||
Subcommand::FmtProject(subcommand) => subcommand.run(),
|
Subcommand::FmtProject(subcommand) => subcommand.run(),
|
||||||
|
Subcommand::Cursor(subcommand) => subcommand.run(),
|
||||||
Subcommand::Doc(subcommand) => subcommand.run(),
|
Subcommand::Doc(subcommand) => subcommand.run(),
|
||||||
Subcommand::Plugin(subcommand) => subcommand.run(),
|
Subcommand::Plugin(subcommand) => subcommand.run(),
|
||||||
|
Subcommand::Studio(subcommand) => subcommand.run(),
|
||||||
Subcommand::Syncback(subcommand) => subcommand.run(self.global),
|
Subcommand::Syncback(subcommand) => subcommand.run(self.global),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,8 +126,10 @@ pub enum Subcommand {
|
||||||
Upload(UploadCommand),
|
Upload(UploadCommand),
|
||||||
Sourcemap(SourcemapCommand),
|
Sourcemap(SourcemapCommand),
|
||||||
FmtProject(FmtProjectCommand),
|
FmtProject(FmtProjectCommand),
|
||||||
|
Cursor(CursorCommand),
|
||||||
Doc(DocCommand),
|
Doc(DocCommand),
|
||||||
Plugin(PluginCommand),
|
Plugin(PluginCommand),
|
||||||
|
Studio(StudioCommand),
|
||||||
Syncback(SyncbackCommand),
|
Syncback(SyncbackCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
79
src/cli/studio.rs
Normal file
79
src/cli/studio.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::Parser;
|
||||||
|
use memofs::Vfs;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::project::Project;
|
||||||
|
|
||||||
|
use super::resolve_path;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UniverseResponse {
|
||||||
|
#[serde(rename = "universeId")]
|
||||||
|
universe_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a Rojo project in Roblox Studio.
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct StudioCommand {
|
||||||
|
/// Path to the project. Defaults to the current directory.
|
||||||
|
#[clap(default_value = ".")]
|
||||||
|
pub project: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StudioCommand {
|
||||||
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
|
let vfs = Vfs::new_oneshot();
|
||||||
|
|
||||||
|
let base_path = resolve_path(&self.project);
|
||||||
|
let project = Project::load_fuzzy(&vfs, &base_path)?
|
||||||
|
.context("A project file is required to run 'rojo studio'")?;
|
||||||
|
|
||||||
|
let serve_place_ids = project
|
||||||
|
.serve_place_ids
|
||||||
|
.as_ref()
|
||||||
|
.context("No servePlaceIds in project file. Add servePlaceIds to your project file.")?;
|
||||||
|
|
||||||
|
let place_id = serve_place_ids
|
||||||
|
.iter()
|
||||||
|
.min()
|
||||||
|
.copied()
|
||||||
|
.context("servePlaceIds is empty in project file")?;
|
||||||
|
|
||||||
|
let universe_id = get_universe_id(place_id)?;
|
||||||
|
|
||||||
|
let url = format!(
|
||||||
|
"roblox-studio:1+launchmode:edit+task:EditPlace+placeId:{}+universeId:{}",
|
||||||
|
place_id, universe_id
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use cmd /c start to fully detach the process on Windows
|
||||||
|
#[cfg(windows)]
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(["/c", "start", "", &url])
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to launch Roblox Studio")?;
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
opener::open(&url).context("Failed to open Roblox Studio")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_universe_id(place_id: u64) -> anyhow::Result<u64> {
|
||||||
|
let url = format!(
|
||||||
|
"https://apis.roblox.com/universes/v1/places/{}/universe",
|
||||||
|
place_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let response: UniverseResponse = reqwest::blocking::get(&url)
|
||||||
|
.context("Failed to fetch universe ID from Roblox API")?
|
||||||
|
.json()
|
||||||
|
.context("Failed to parse universe ID response")?;
|
||||||
|
|
||||||
|
Ok(response.universe_id)
|
||||||
|
}
|
||||||
|
|
@ -83,6 +83,9 @@ impl SyncbackCommand {
|
||||||
// Determine if we need to download the input file
|
// Determine if we need to download the input file
|
||||||
let resolved_input = resolve_path(&self.input);
|
let resolved_input = resolve_path(&self.input);
|
||||||
let _temp_file: Option<NamedTempFile>;
|
let _temp_file: Option<NamedTempFile>;
|
||||||
|
// Track if we should delete the input file after successful syncback
|
||||||
|
// (when using default Project.rbxl and file exists locally)
|
||||||
|
let delete_input_after_syncback: Option<PathBuf>;
|
||||||
|
|
||||||
// Logic:
|
// Logic:
|
||||||
// - If --download=PLACEID: always download that specific place
|
// - If --download=PLACEID: always download that specific place
|
||||||
|
|
@ -100,11 +103,18 @@ impl SyncbackCommand {
|
||||||
);
|
);
|
||||||
let temp_path = temp.path().to_path_buf();
|
let temp_path = temp.path().to_path_buf();
|
||||||
_temp_file = Some(temp);
|
_temp_file = Some(temp);
|
||||||
|
delete_input_after_syncback = None;
|
||||||
temp_path
|
temp_path
|
||||||
}
|
}
|
||||||
None if resolved_input.exists() => {
|
None if resolved_input.exists() => {
|
||||||
// No --download flag, input file exists: use it
|
// No --download flag, input file exists: use it
|
||||||
_temp_file = None;
|
_temp_file = None;
|
||||||
|
// If using default input path, mark for deletion after success
|
||||||
|
delete_input_after_syncback = if self.input.as_os_str() == "Project.rbxl" {
|
||||||
|
Some(resolved_input.clone().into_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
resolved_input.into_owned()
|
resolved_input.into_owned()
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -123,6 +133,7 @@ impl SyncbackCommand {
|
||||||
);
|
);
|
||||||
let temp_path = temp.path().to_path_buf();
|
let temp_path = temp.path().to_path_buf();
|
||||||
_temp_file = Some(temp);
|
_temp_file = Some(temp);
|
||||||
|
delete_input_after_syncback = None;
|
||||||
temp_path
|
temp_path
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -251,6 +262,18 @@ impl SyncbackCommand {
|
||||||
|
|
||||||
// Refresh git index if in a git repository
|
// Refresh git index if in a git repository
|
||||||
refresh_git_index(base_path);
|
refresh_git_index(base_path);
|
||||||
|
|
||||||
|
// Delete input file if using default Project.rbxl location
|
||||||
|
if let Some(input_path) = &delete_input_after_syncback {
|
||||||
|
match std::fs::remove_file(input_path) {
|
||||||
|
Ok(()) => log::info!("Deleted input file: {}", input_path.display()),
|
||||||
|
Err(e) => log::warn!(
|
||||||
|
"Failed to delete input file {}: {}",
|
||||||
|
input_path.display(),
|
||||||
|
e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Would write {} files/folders and remove {} files/folders.",
|
"Would write {} files/folders and remove {} files/folders.",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue