Skip to content

Commit 1a1b651

Browse files
Feature: Drag to clone (#16920)
Signed-off-by: Yair <[email protected]> Co-authored-by: Filippo Ferrario <[email protected]>
1 parent 79b7621 commit 1a1b651

File tree

18 files changed

+529
-52
lines changed

18 files changed

+529
-52
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
namespace Files.App.Actions
5+
{
6+
internal sealed partial class GitCloneAction : ObservableObject, IAction
7+
{
8+
private readonly IContentPageContext pageContext = Ioc.Default.GetRequiredService<IContentPageContext>();
9+
private readonly IDialogService dialogService = Ioc.Default.GetRequiredService<IDialogService>();
10+
11+
public string Label { get; } = Strings.GitClone.GetLocalizedResource();
12+
13+
public string Description { get; } = Strings.GitCloneDescription.GetLocalizedResource();
14+
15+
public bool IsExecutable
16+
=> pageContext.CanCreateItem && !pageContext.IsGitRepository;
17+
18+
public RichGlyph Glyph
19+
=> new(themedIconStyle: "App.ThemedIcons.Git");
20+
21+
public GitCloneAction()
22+
{
23+
pageContext.PropertyChanged += Context_PropertyChanged;
24+
}
25+
26+
public Task ExecuteAsync(object? parameter = null)
27+
{
28+
if (pageContext.ShellPage is null)
29+
return Task.CompletedTask;
30+
31+
var repoUrl = parameter?.ToString() ?? string.Empty;
32+
var viewModel = new CloneRepoDialogViewModel(repoUrl, pageContext.ShellPage.ShellViewModel.WorkingDirectory);
33+
return dialogService.ShowDialogAsync(viewModel);
34+
}
35+
36+
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
37+
{
38+
switch (e.PropertyName)
39+
{
40+
case nameof(IContentPageContext.CanCreateItem):
41+
case nameof(IContentPageContext.IsGitRepository):
42+
OnPropertyChanged(nameof(IsExecutable));
43+
break;
44+
}
45+
}
46+
}
47+
}

src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ partial class StatusCenterStateToStateIconConverter : IValueConverter
2525
StatusCenterItemIconKind.Compress => Application.Current.Resources["App.Theme.PathIcon.ActionExtract"] as string,
2626
StatusCenterItemIconKind.Successful => Application.Current.Resources["App.Theme.PathIcon.ActionSuccess"] as string,
2727
StatusCenterItemIconKind.Error => Application.Current.Resources["App.Theme.PathIcon.ActionInfo"] as string,
28+
StatusCenterItemIconKind.GitClone => Application.Current.Resources["App.Theme.PathIcon.ActionGitClone"] as string,
2829
_ => ""
2930
};
3031

src/Files.App/Data/Commands/Manager/CommandCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ public enum CommandCodes
230230
PlayAll,
231231

232232
// Git
233+
GitClone,
233234
GitFetch,
234235
GitInit,
235236
GitPull,

src/Files.App/Data/Commands/Manager/CommandManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ public IRichCommand this[HotKey hotKey]
217217
public IRichCommand ArrangePanesHorizontally => commands[CommandCodes.ArrangePanesHorizontally];
218218
public IRichCommand OpenFileLocation => commands[CommandCodes.OpenFileLocation];
219219
public IRichCommand PlayAll => commands[CommandCodes.PlayAll];
220+
public IRichCommand GitClone => commands[CommandCodes.GitClone];
220221
public IRichCommand GitFetch => commands[CommandCodes.GitFetch];
221222
public IRichCommand GitInit => commands[CommandCodes.GitInit];
222223
public IRichCommand GitPull => commands[CommandCodes.GitPull];
@@ -421,6 +422,7 @@ public IEnumerator<IRichCommand> GetEnumerator() =>
421422
[CommandCodes.ArrangePanesHorizontally] = new ArrangePanesHorizontallyAction(),
422423
[CommandCodes.OpenFileLocation] = new OpenFileLocationAction(),
423424
[CommandCodes.PlayAll] = new PlayAllAction(),
425+
[CommandCodes.GitClone] = new GitCloneAction(),
424426
[CommandCodes.GitFetch] = new GitFetchAction(),
425427
[CommandCodes.GitInit] = new GitInitAction(),
426428
[CommandCodes.GitPull] = new GitPullAction(),

src/Files.App/Data/Commands/Manager/ICommandManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ public interface ICommandManager : IEnumerable<IRichCommand>
210210

211211
IRichCommand PlayAll { get; }
212212

213+
IRichCommand GitClone { get; }
213214
IRichCommand GitFetch { get; }
214215
IRichCommand GitInit { get; }
215216
IRichCommand GitPull { get; }

src/Files.App/Data/Enums/FileOperationType.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,10 @@ public enum FileOperationType : byte
6262
/// An item has been added to an archive
6363
/// </summary>
6464
Compressed = 11,
65+
66+
/// <summary>
67+
/// A git repo has been cloned
68+
/// </summary>
69+
GitClone = 12,
6570
}
6671
}

src/Files.App/Data/Enums/StatusCenterItemIconKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ public enum StatusCenterItemIconKind
1616
Compress,
1717
Successful,
1818
Error,
19+
GitClone
1920
}
2021
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!-- Copyright (c) Files Community. Licensed under the MIT License. -->
2+
<ContentDialog
3+
x:Class="Files.App.Dialogs.CloneRepoDialog"
4+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
7+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:helpers="using:Files.App.Helpers"
9+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
10+
Title="{helpers:ResourceString Name=CloneRepo}"
11+
HighContrastAdjustment="None"
12+
IsPrimaryButtonEnabled="{x:Bind ViewModel.CanCloneRepo, Mode=OneWay}"
13+
PrimaryButtonCommand="{x:Bind ViewModel.CloneRepoCommand, Mode=OneWay}"
14+
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
15+
PrimaryButtonText="{helpers:ResourceString Name=Clone}"
16+
RequestedTheme="{x:Bind RootAppElement.RequestedTheme, Mode=OneWay}"
17+
SecondaryButtonText="{helpers:ResourceString Name=Cancel}"
18+
Style="{StaticResource DefaultContentDialogStyle}"
19+
mc:Ignorable="d">
20+
21+
<Grid Width="340">
22+
<StackPanel Spacing="8">
23+
<TextBox
24+
Header="{helpers:ResourceString Name=RepositoryURL}"
25+
PlaceholderText="https://github.com/files-community/Files"
26+
Text="{x:Bind ViewModel.RepoUrl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
27+
</StackPanel>
28+
</Grid>
29+
30+
</ContentDialog>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.UI.Xaml;
5+
using Microsoft.UI.Xaml.Controls;
6+
using Windows.ApplicationModel.DataTransfer;
7+
8+
namespace Files.App.Dialogs
9+
{
10+
public sealed partial class CloneRepoDialog : ContentDialog, IDialog<CloneRepoDialogViewModel>
11+
{
12+
private FrameworkElement RootAppElement
13+
=> (FrameworkElement)MainWindow.Instance.Content;
14+
15+
public CloneRepoDialogViewModel ViewModel
16+
{
17+
get => (CloneRepoDialogViewModel)DataContext;
18+
set => DataContext = value;
19+
}
20+
21+
public CloneRepoDialog()
22+
{
23+
InitializeComponent();
24+
}
25+
26+
public new async Task<DialogResult> ShowAsync()
27+
{
28+
return (DialogResult)await base.ShowAsync();
29+
}
30+
}
31+
}

src/Files.App/Helpers/Dialog/DynamicDialogFactory.cs

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ public static DynamicDialog GetFor_PropertySaveErrorDialog()
1919
{
2020
DynamicDialog dialog = new DynamicDialog(new DynamicDialogViewModel()
2121
{
22-
TitleText = "PropertySaveErrorDialog/Title".GetLocalizedResource(),
23-
SubtitleText = "PropertySaveErrorMessage/Text".GetLocalizedResource(), // We can use subtitle here as our content
24-
PrimaryButtonText = "Retry".GetLocalizedResource(),
25-
SecondaryButtonText = "PropertySaveErrorDialog/SecondaryButtonText".GetLocalizedResource(),
26-
CloseButtonText = "Cancel".GetLocalizedResource(),
22+
TitleText = Strings.PropertySaveErrorDialog_Title.GetLocalizedResource(),
23+
SubtitleText = Strings.PropertySaveErrorMessage_Text.GetLocalizedResource(), // We can use subtitle here as our content
24+
PrimaryButtonText = Strings.Retry.GetLocalizedResource(),
25+
SecondaryButtonText = Strings.PropertySaveErrorDialog_SecondaryButtonText.GetLocalizedResource(),
26+
CloseButtonText = Strings.Cancel.GetLocalizedResource(),
2727
DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Secondary | DynamicDialogButtons.Cancel
2828
});
2929
return dialog;
@@ -33,9 +33,9 @@ public static DynamicDialog GetFor_ConsentDialog()
3333
{
3434
DynamicDialog dialog = new DynamicDialog(new DynamicDialogViewModel()
3535
{
36-
TitleText = "WelcomeDialog/Title".GetLocalizedResource(),
37-
SubtitleText = "WelcomeDialogTextBlock/Text".GetLocalizedResource(), // We can use subtitle here as our content
38-
PrimaryButtonText = "WelcomeDialog/PrimaryButtonText".GetLocalizedResource(),
36+
TitleText = Strings.WelcomeDialog_Title.GetLocalizedResource(),
37+
SubtitleText = Strings.WelcomeDialogTextBlock_Text.GetLocalizedResource(), // We can use subtitle here as our content
38+
PrimaryButtonText = Strings.WelcomeDialog_PrimaryButtonText.GetLocalizedResource(),
3939
PrimaryButtonAction = async (vm, e) => await Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-broadfilesystemaccess")),
4040
DynamicButtons = DynamicDialogButtons.Primary
4141
});
@@ -46,10 +46,10 @@ public static DynamicDialog GetFor_ShortcutNotFound(string targetPath)
4646
{
4747
DynamicDialog dialog = new(new DynamicDialogViewModel
4848
{
49-
TitleText = "ShortcutCannotBeOpened".GetLocalizedResource(),
50-
SubtitleText = string.Format("DeleteShortcutDescription".GetLocalizedResource(), targetPath),
51-
PrimaryButtonText = "Delete".GetLocalizedResource(),
52-
SecondaryButtonText = "No".GetLocalizedResource(),
49+
TitleText = Strings.ShortcutCannotBeOpened.GetLocalizedResource(),
50+
SubtitleText = string.Format(Strings.DeleteShortcutDescription.GetLocalizedResource(), targetPath),
51+
PrimaryButtonText = Strings.Delete.GetLocalizedResource(),
52+
SecondaryButtonText = Strings.No.GetLocalizedResource(),
5353
DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Secondary
5454
});
5555
return dialog;
@@ -60,12 +60,12 @@ public static DynamicDialog GetFor_CreateItemDialog(string itemType)
6060
DynamicDialog? dialog = null;
6161
TextBox inputText = new()
6262
{
63-
PlaceholderText = "EnterAnItemName".GetLocalizedResource()
63+
PlaceholderText = Strings.EnterAnItemName.GetLocalizedResource()
6464
};
6565

6666
TeachingTip warning = new()
6767
{
68-
Title = "InvalidFilename/Text".GetLocalizedResource(),
68+
Title = Strings.InvalidFilename_Text.GetLocalizedResource(),
6969
PreferredPlacement = TeachingTipPlacementMode.Bottom,
7070
DataContext = new CreateItemDialogViewModel(),
7171
};
@@ -101,7 +101,7 @@ public static DynamicDialog GetFor_CreateItemDialog(string itemType)
101101

102102
dialog = new DynamicDialog(new DynamicDialogViewModel()
103103
{
104-
TitleText = string.Format("CreateNewItemTitle".GetLocalizedResource(), itemType),
104+
TitleText = string.Format(Strings.CreateNewItemTitle.GetLocalizedResource(), itemType),
105105
SubtitleText = null,
106106
DisplayControl = new Grid()
107107
{
@@ -115,8 +115,8 @@ public static DynamicDialog GetFor_CreateItemDialog(string itemType)
115115
{
116116
vm.HideDialog(); // Rename successful
117117
},
118-
PrimaryButtonText = "Create".GetLocalizedResource(),
119-
CloseButtonText = "Cancel".GetLocalizedResource(),
118+
PrimaryButtonText = Strings.Create.GetLocalizedResource(),
119+
CloseButtonText = Strings.Cancel.GetLocalizedResource(),
120120
DynamicButtonsEnabled = DynamicDialogButtons.Cancel,
121121
DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Cancel
122122
});
@@ -133,9 +133,9 @@ public static DynamicDialog GetFor_FileInUseDialog(List<Win32Process> lockingPro
133133
{
134134
DynamicDialog dialog = new DynamicDialog(new DynamicDialogViewModel()
135135
{
136-
TitleText = "FileInUseDialog/Title".GetLocalizedResource(),
137-
SubtitleText = lockingProcess.IsEmpty() ? "FileInUseDialog/Text".GetLocalizedResource() :
138-
string.Format("FileInUseByDialog/Text".GetLocalizedResource(), string.Join(", ", lockingProcess.Select(x => $"{x.AppName ?? x.Name} (PID: {x.Pid})"))),
136+
TitleText = Strings.FileInUseDialog_Title.GetLocalizedResource(),
137+
SubtitleText = lockingProcess.IsEmpty() ? Strings.FileInUseDialog_Text.GetLocalizedResource() :
138+
string.Format(Strings.FileInUseByDialog_Text.GetLocalizedResource(), string.Join(", ", lockingProcess.Select(x => $"{x.AppName ?? x.Name} (PID: {x.Pid})"))),
139139
PrimaryButtonText = "OK",
140140
DynamicButtons = DynamicDialogButtons.Primary
141141
});
@@ -149,17 +149,17 @@ public static DynamicDialog GetFor_CredentialEntryDialog(string path)
149149

150150
TextBox inputUsername = new()
151151
{
152-
PlaceholderText = "CredentialDialogUserName/PlaceholderText".GetLocalizedResource()
152+
PlaceholderText = Strings.CredentialDialogUserName_PlaceholderText.GetLocalizedResource()
153153
};
154154

155155
PasswordBox inputPassword = new()
156156
{
157-
PlaceholderText = "Password".GetLocalizedResource()
157+
PlaceholderText = Strings.Password.GetLocalizedResource()
158158
};
159159

160160
CheckBox saveCreds = new()
161161
{
162-
Content = "NetworkAuthenticationSaveCheckbox".GetLocalizedResource()
162+
Content = Strings.NetworkAuthenticationSaveCheckbox.GetLocalizedResource()
163163
};
164164

165165
inputUsername.TextChanged += (textBox, args) =>
@@ -188,10 +188,10 @@ public static DynamicDialog GetFor_CredentialEntryDialog(string path)
188188

189189
dialog = new DynamicDialog(new DynamicDialogViewModel()
190190
{
191-
TitleText = "NetworkAuthenticationDialogTitle".GetLocalizedResource(),
192-
PrimaryButtonText = "OK".GetLocalizedResource(),
193-
CloseButtonText = "Cancel".GetLocalizedResource(),
194-
SubtitleText = string.Format("NetworkAuthenticationDialogMessage".GetLocalizedResource(), path.Substring(2)),
191+
TitleText = Strings.NetworkAuthenticationDialogTitle.GetLocalizedResource(),
192+
PrimaryButtonText = Strings.OK.GetLocalizedResource(),
193+
CloseButtonText = Strings.Cancel.GetLocalizedResource(),
194+
SubtitleText = string.Format(Strings.NetworkAuthenticationDialogMessage.GetLocalizedResource(), path.Substring(2)),
195195
DisplayControl = new Grid()
196196
{
197197
MinWidth = 250d,
@@ -228,9 +228,9 @@ public static DynamicDialog GetFor_GitCheckoutConflicts(string checkoutBranchNam
228228
{
229229
ItemsSource = new string[]
230230
{
231-
string.Format("BringChanges".GetLocalizedResource(), checkoutBranchName),
232-
string.Format("StashChanges".GetLocalizedResource(), headBranchName),
233-
"DiscardChanges".GetLocalizedResource()
231+
string.Format(Strings.BringChanges.GetLocalizedResource(), checkoutBranchName),
232+
string.Format(Strings.StashChanges.GetLocalizedResource(), headBranchName),
233+
Strings.DiscardChanges.GetLocalizedResource()
234234
},
235235
SelectionMode = ListViewSelectionMode.Single
236236
};
@@ -243,10 +243,10 @@ public static DynamicDialog GetFor_GitCheckoutConflicts(string checkoutBranchNam
243243

244244
dialog = new DynamicDialog(new DynamicDialogViewModel()
245245
{
246-
TitleText = "SwitchBranch".GetLocalizedResource(),
247-
PrimaryButtonText = "Switch".GetLocalizedResource(),
248-
CloseButtonText = "Cancel".GetLocalizedResource(),
249-
SubtitleText = "UncommittedChanges".GetLocalizedResource(),
246+
TitleText = Strings.SwitchBranch.GetLocalizedResource(),
247+
PrimaryButtonText = Strings.Switch.GetLocalizedResource(),
248+
CloseButtonText = Strings.Cancel.GetLocalizedResource(),
249+
SubtitleText = Strings.UncommittedChanges.GetLocalizedResource(),
250250
DisplayControl = new Grid()
251251
{
252252
MinWidth = 250d,
@@ -271,8 +271,8 @@ public static DynamicDialog GetFor_GitHubConnectionError()
271271
DynamicDialog dialog = new DynamicDialog(new DynamicDialogViewModel()
272272
{
273273
TitleText = "Error".GetLocalizedResource(),
274-
SubtitleText = "CannotReachGitHubError".GetLocalizedResource(),
275-
PrimaryButtonText = "Close".GetLocalizedResource(),
274+
SubtitleText = Strings.CannotReachGitHubError.GetLocalizedResource(),
275+
PrimaryButtonText = Strings.Close.GetLocalizedResource(),
276276
DynamicButtons = DynamicDialogButtons.Primary
277277
});
278278
return dialog;
@@ -283,8 +283,8 @@ public static DynamicDialog GetFor_GitCannotInitializeqRepositoryHere()
283283
return new DynamicDialog(new DynamicDialogViewModel()
284284
{
285285
TitleText = "Error".GetLocalizedResource(),
286-
SubtitleText = "CannotInitializeGitRepo".GetLocalizedResource(),
287-
PrimaryButtonText = "Close".GetLocalizedResource(),
286+
SubtitleText = Strings.CannotInitializeGitRepo.GetLocalizedResource(),
287+
PrimaryButtonText = Strings.Close.GetLocalizedResource(),
288288
DynamicButtons = DynamicDialogButtons.Primary
289289
});
290290
}
@@ -294,10 +294,10 @@ public static DynamicDialog GetFor_DeleteGitBranchConfirmation(string branchName
294294
DynamicDialog dialog = null!;
295295
dialog = new DynamicDialog(new DynamicDialogViewModel()
296296
{
297-
TitleText = "GitDeleteBranch".GetLocalizedResource(),
298-
SubtitleText = string.Format("GitDeleteBranchSubtitle".GetLocalizedResource(), branchName),
299-
PrimaryButtonText = "OK".GetLocalizedResource(),
300-
CloseButtonText = "Cancel".GetLocalizedResource(),
297+
TitleText = Strings.GitDeleteBranch.GetLocalizedResource(),
298+
SubtitleText = string.Format(Strings.GitDeleteBranchSubtitle.GetLocalizedResource(), branchName),
299+
PrimaryButtonText = Strings.OK.GetLocalizedResource(),
300+
CloseButtonText = Strings.Cancel.GetLocalizedResource(),
301301
AdditionalData = true,
302302
CloseButtonAction = (vm, e) =>
303303
{
@@ -314,10 +314,10 @@ public static DynamicDialog GetFor_RenameRequiresHigherPermissions(string path)
314314
DynamicDialog dialog = null!;
315315
dialog = new DynamicDialog(new DynamicDialogViewModel()
316316
{
317-
TitleText = "ItemRenameFailed".GetLocalizedResource(),
318-
SubtitleText = string.Format("HigherPermissionsRequired".GetLocalizedResource(), path),
319-
PrimaryButtonText = "OK".GetLocalizedResource(),
320-
SecondaryButtonText = "EditPermissions".GetLocalizedResource(),
317+
TitleText = Strings.ItemRenameFailed.GetLocalizedResource(),
318+
SubtitleText = string.Format(Strings.HigherPermissionsRequired.GetLocalizedResource(), path),
319+
PrimaryButtonText = Strings.OK.GetLocalizedResource(),
320+
SecondaryButtonText = Strings.EditPermissions.GetLocalizedResource(),
321321
SecondaryButtonAction = (vm, e) =>
322322
{
323323
var context = Ioc.Default.GetRequiredService<IContentPageContext>();
@@ -424,5 +424,18 @@ await commands.OpenSettings.ExecuteAsync(
424424
new SettingsNavigationParams() { PageKind = SettingsPageKind.DevToolsPage }
425425
);
426426
}
427+
428+
public static async Task ShowFor_CannotCloneRepo(string exception)
429+
{
430+
var dialog = new DynamicDialog(new DynamicDialogViewModel()
431+
{
432+
TitleText = Strings.CannotCloneRepoTitle.GetLocalizedResource(),
433+
SubtitleText = exception,
434+
PrimaryButtonText = Strings.OK.GetLocalizedResource(),
435+
DynamicButtons = DynamicDialogButtons.Primary
436+
});
437+
438+
await dialog.TryShowAsync();
439+
}
427440
}
428441
}

0 commit comments

Comments
 (0)