Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Added experimental support for flattening folders #15992

Merged
merged 28 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
250ad3e
Feature: Add support for flattening folders
devin-slothower Aug 11, 2024
e8f9117
Merge branch 'files-community:main' into main
devin-slothower Aug 11, 2024
1676208
Add resources to be translated
devin-slothower Aug 11, 2024
6d2403f
Merge branch 'main' of https://github.com/devin-slothower/Files
devin-slothower Aug 11, 2024
dbb54bf
Copyright header
devin-slothower Aug 11, 2024
386836b
Delete src/Files.App/Properties/Resources.Designer.cs
devin-slothower Aug 11, 2024
99041e8
Delete src/Files.App/Properties/Resources.resx
devin-slothower Aug 11, 2024
155e20d
Revert changes to resource files
yaira2 Aug 12, 2024
b1846c4
Merge branch 'files-community:main' into main
devin-slothower Aug 12, 2024
8e957ca
Code Review: Changed to synchronous execution
devin-slothower Aug 12, 2024
1937828
Code Review: Flatten option defaulted to false
devin-slothower Aug 12, 2024
b964919
Code Review: Flyout Icon
devin-slothower Aug 13, 2024
f0e9fd2
Cleanup
yaira2 Aug 26, 2024
bf9b68a
Update CommandManager.cs
yaira2 Aug 26, 2024
4cf6ce5
Update Resources.resw
yaira2 Aug 26, 2024
87ec819
Update CommandCodes.cs
yaira2 Aug 26, 2024
108bf27
Rename
yaira2 Aug 26, 2024
a63a797
Update Resources.resw
yaira2 Aug 26, 2024
f6d5b54
Update src/Files.App/Actions/FileSystem/FlattenToRootAction.cs
yaira2 Aug 26, 2024
76b73a2
Update IsExecutable
yaira2 Aug 27, 2024
136dfa8
Merge actions
yaira2 Aug 27, 2024
182b1b4
Update FlattenToRootAction.cs
yaira2 Aug 27, 2024
8598d64
Update Resources.resw
yaira2 Aug 27, 2024
9c35da2
Update FlattenToRootAction.cs
yaira2 Aug 27, 2024
819473d
Update Resources.resw
yaira2 Aug 27, 2024
e0d7ec6
Strings
yaira2 Aug 27, 2024
52f55d8
Update FlattenFolderAction.cs
yaira2 Aug 27, 2024
96d6912
Update Resources.resw
yaira2 Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions src/Files.App/Actions/FileSystem/FlattenToRootAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.

using Microsoft.Extensions.Logging;
using System.IO;
using Windows.Storage;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation.Metadata;
using Files.Shared.Helpers;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using Microsoft.Extensions.Logging;
using System.IO;
using Windows.Storage;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation.Metadata;
using Files.Shared.Helpers;
using Files.Shared.Helpers;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml.Controls;
using System.IO;
using Windows.Storage;
using Windows.Foundation.Metadata;


namespace Files.App.Actions
{
internal sealed class FlattenToRootAction : ObservableObject, IAction
{
private readonly IContentPageContext context;
private readonly IGeneralSettingsService GeneralSettingsService = Ioc.Default.GetRequiredService<IGeneralSettingsService>();

public string Label
=> "FlattenToRoot".GetLocalizedResource();

public string Description
=> "FlattenToRootDescription".GetLocalizedResource();

public RichGlyph Glyph
=> new(themedIconStyle: "App.ThemedIcons.Folder");

public bool IsExecutable =>
GeneralSettingsService.ShowFlattenOptions &&
context.ShellPage is not null &&
context.HasSelection &&
context.SelectedItems.Count is 1 &&
context.SelectedItem is not null &&
context.SelectedItem.PrimaryItemAttribute is StorageItemTypes.Folder;

public FlattenToRootAction()
{
context = Ioc.Default.GetRequiredService<IContentPageContext>();

context.PropertyChanged += Context_PropertyChanged;
GeneralSettingsService.PropertyChanged += GeneralSettingsService_PropertyChanged;
}

public async Task ExecuteAsync(object? parameter = null)
{
if (context.SelectedItem is null)
return;

var optionsDialog = new ContentDialog()
{
Title = "FlattenFolderDialogTitle".GetLocalizedResource(),
Content = "FlattenFolderDialogContent".GetLocalizedResource(),
PrimaryButtonText = "Flatten".GetLocalizedResource(),
SecondaryButtonText = "Cancel".GetLocalizedResource(),
DefaultButton = ContentDialogButton.Primary
};

if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
optionsDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;

var result = await optionsDialog.TryShowAsync();
if (result != ContentDialogResult.Primary)
return;

FlattenFolder(context.SelectedItem.ItemPath);
}

private void FlattenFolder(string path)
{
var containedFolders = Directory.GetDirectories(path);
var containedFiles = Directory.GetFiles(path);

foreach (var containedFolder in containedFolders)
{
FlattenFolder(containedFolder);

var folderName = Path.GetFileName(containedFolder);
var destinationPath = Path.Combine(context?.SelectedItem?.ItemPath ?? string.Empty, folderName);

if (Directory.Exists(destinationPath))
continue;

try
{
Directory.Move(containedFolder, destinationPath);
}
catch (Exception ex)
{
App.Logger.LogWarning(ex.Message, $"Folder '{folderName}' already exists in the destination folder.");
}
}

foreach (var containedFile in containedFiles)
{
var fileName = Path.GetFileName(containedFile);
var destinationPath = Path.Combine(context?.SelectedItem?.ItemPath ?? string.Empty, fileName);

if (File.Exists(destinationPath))
continue;

try
{
File.Move(containedFile, destinationPath);
}
catch (Exception ex)
{
App.Logger.LogWarning(ex.Message, $"Failed to move file '{fileName}'.");
}
}

if (Directory.GetFiles(path).Length == 0 && Directory.GetDirectories(path).Length == 0)
{
try
{
Directory.Delete(path);
}
catch (Exception ex)
{
App.Logger.LogWarning(ex.Message, $"Failed to delete folder '{path}'.");
}
}
}

private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(IContentPageContext.HasSelection):
case nameof(IContentPageContext.SelectedItem):
OnPropertyChanged(nameof(IsExecutable));
break;
}
}

private void GeneralSettingsService_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(IGeneralSettingsService.ShowFlattenOptions))
OnPropertyChanged(nameof(IsExecutable));
}
}
}
3 changes: 3 additions & 0 deletions src/Files.App/Data/Commands/Manager/CommandCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public enum CommandCodes
DecompressArchiveHereSmart,
DecompressArchiveToChildFolder,

// Folders
FlattenToRoot,

// Image Manipulation
RotateLeft,
RotateRight,
Expand Down
6 changes: 4 additions & 2 deletions src/Files.App/Data/Commands/Manager/CommandManager.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.

using System.Collections.Frozen;
using System.Collections.Immutable;
using Files.App.Actions;
using Microsoft.Extensions.Logging;
using System.Collections.Frozen;
using System.Collections.Immutable;

namespace Files.App.Data.Commands
{
Expand Down Expand Up @@ -102,6 +102,7 @@ public IRichCommand this[HotKey hotKey]
public IRichCommand DecompressArchiveHere => commands[CommandCodes.DecompressArchiveHere];
public IRichCommand DecompressArchiveHereSmart => commands[CommandCodes.DecompressArchiveHereSmart];
public IRichCommand DecompressArchiveToChildFolder => commands[CommandCodes.DecompressArchiveToChildFolder];
public IRichCommand FlattenToRoot => commands[CommandCodes.FlattenToRoot];
public IRichCommand RotateLeft => commands[CommandCodes.RotateLeft];
public IRichCommand RotateRight => commands[CommandCodes.RotateRight];
public IRichCommand OpenItem => commands[CommandCodes.OpenItem];
Expand Down Expand Up @@ -291,6 +292,7 @@ public IEnumerator<IRichCommand> GetEnumerator() =>
[CommandCodes.DecompressArchiveHere] = new DecompressArchiveHere(),
[CommandCodes.DecompressArchiveHereSmart] = new DecompressArchiveHereSmart(),
[CommandCodes.DecompressArchiveToChildFolder] = new DecompressArchiveToChildFolderAction(),
[CommandCodes.FlattenToRoot] = new FlattenToRootAction(),
[CommandCodes.RotateLeft] = new RotateLeftAction(),
[CommandCodes.RotateRight] = new RotateRightAction(),
[CommandCodes.OpenItem] = new OpenItemAction(),
Expand Down
2 changes: 2 additions & 0 deletions src/Files.App/Data/Commands/Manager/ICommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public interface ICommandManager : IEnumerable<IRichCommand>
IRichCommand DecompressArchiveHereSmart { get; }
IRichCommand DecompressArchiveToChildFolder { get; }

IRichCommand FlattenToRoot { get; }

IRichCommand RotateLeft { get; }
IRichCommand RotateRight { get; }

Expand Down
5 changes: 5 additions & 0 deletions src/Files.App/Data/Contracts/IGeneralSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ public interface IGeneralSettingsService : IBaseSettingsService, INotifyProperty
/// </summary>
bool ShowCompressionOptions { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not to show the flatten options e.g. single, recursive.
/// </summary>
bool ShowFlattenOptions { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not to show the Send To menu.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ public static List<ContextMenuFlyoutItemViewModel> GetBaseItemMenuItems(
],
ShowItem = UserSettingsService.GeneralSettingsService.ShowCompressionOptions && StorageArchiveService.CanDecompress(selectedItems)
},
new ContextMenuFlyoutItemViewModelBuilder(Commands.FlattenToRoot).Build(),
new ContextMenuFlyoutItemViewModel()
{
Text = "SendTo".GetLocalizedResource(),
Expand Down Expand Up @@ -587,7 +588,7 @@ public static List<ContextMenuFlyoutItemViewModel> GetBaseItemMenuItems(
ShowItem = isDriveRoot,
IsEnabled = false
},
new ContextMenuFlyoutItemViewModelBuilder(Commands.EditInNotepad).Build(),
new ContextMenuFlyoutItemViewModelBuilder(Commands.EditInNotepad).Build(),
new ContextMenuFlyoutItemViewModel()
{
ItemType = ContextMenuFlyoutItemType.Separator,
Expand Down
6 changes: 6 additions & 0 deletions src/Files.App/Services/Settings/GeneralSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ public bool ShowCompressionOptions
set => Set(value);
}

public bool ShowFlattenOptions
{
get => Get(false);
set => Set(value);
}

public bool ShowSendToMenu
{
get => Get(true);
Expand Down
21 changes: 21 additions & 0 deletions src/Files.App/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,9 @@
<data name="SettingsSetAsDefaultFileManagerDescription" xml:space="preserve">
<value>This option modifies the system registry and can have unexpected side effects on your device. Continue at your own risk.</value>
</data>
<data name="ShowFlattenOptionsDescription" xml:space="preserve">
<value>The flatten operations are permanent and not recommended. Continue at your own risk.</value>
</data>
<data name="FolderWidgetCreateNewLibraryDialogTitleText" xml:space="preserve">
<value>Create Library</value>
</data>
Expand Down Expand Up @@ -2027,6 +2030,24 @@
<data name="Compress" xml:space="preserve">
<value>Compress</value>
</data>
<data name="Flatten" xml:space="preserve">
<value>Flatten</value>
</data>
<data name="FlattenToRoot" xml:space="preserve">
<value>Flatten to root</value>
</data>
<data name="FlattenToRootDescription" xml:space="preserve">
<value>Move all contents from subfolders into the selected location</value>
</data>
<data name="FlattenFolderDialogTitle" xml:space="preserve">
<value>Flatten folder</value>
</data>
<data name="FlattenFolderDialogContent" xml:space="preserve">
<value>Flattening a folder will move all contents from its subfolders to the selected location. This operation is permanent and cannot be undone. By using this experimental feature, you acknowledge the risk and agree not to hold the Files team responsible for any data loss.</value>
</data>
<data name="ShowFlattenOptions" xml:space="preserve">
<value>Show flatten options</value>
</data>
<data name="SelectFilesAndFoldersOnHover" xml:space="preserve">
<value>Select files and folders when hovering over them</value>
</data>
Expand Down
14 changes: 14 additions & 0 deletions src/Files.App/ViewModels/Settings/AdvancedViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,20 @@ public bool ShowSystemTrayIcon
}
}

// TODO remove when feature is marked as stable
public bool ShowFlattenOptions
{
get => UserSettingsService.GeneralSettingsService.ShowFlattenOptions;
set
{
if (value == UserSettingsService.GeneralSettingsService.ShowFlattenOptions)
return;

UserSettingsService.GeneralSettingsService.ShowFlattenOptions = value;
OnPropertyChanged();
}
}

public async Task OpenFilesOnWindowsStartupAsync()
{
var stateMode = await ReadState();
Expand Down
14 changes: 14 additions & 0 deletions src/Files.App/ViewModels/Settings/GeneralViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,20 @@ public bool ShowCompressionOptions
}
}

// TODO uncomment code when feature is marked as stable
//public bool ShowFlattenOptions
//{
// get => UserSettingsService.GeneralSettingsService.ShowFlattenOptions;
// set
// {
// if (value == UserSettingsService.GeneralSettingsService.ShowFlattenOptions)
// return;

// UserSettingsService.GeneralSettingsService.ShowFlattenOptions = value;
// OnPropertyChanged();
// }
//}

public bool ShowSendToMenu
{
get => UserSettingsService.GeneralSettingsService.ShowSendToMenu;
Expand Down
14 changes: 14 additions & 0 deletions src/Files.App/Views/Settings/AdvancedPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,20 @@
</i:Interaction.Behaviors>
</ToggleSwitch>
</local:SettingsBlockControl>

<!-- Flatten options -->
<local:SettingsBlockControl
Title="{helpers:ResourceString Name=ShowFlattenOptions}"
HorizontalAlignment="Stretch"
Description="{helpers:ResourceString Name=ShowFlattenOptionsDescription}">
<local:SettingsBlockControl.Icon>
<FontIcon Glyph="&#xE8B7;" />
</local:SettingsBlockControl.Icon>
<ToggleSwitch
AutomationProperties.Name="{helpers:ResourceString Name=ShowFlattenOptions}"
IsOn="{x:Bind ViewModel.ShowFlattenOptions, Mode=TwoWay}"
Style="{StaticResource RightAlignedToggleSwitchStyle}" />
</local:SettingsBlockControl>
</StackPanel>
</Grid>
</Page>
9 changes: 9 additions & 0 deletions src/Files.App/Views/Settings/GeneralPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,15 @@
Style="{StaticResource RightAlignedToggleSwitchStyle}" />
</local:SettingsBlockControl>

<!-- Flatten options -->
<!-- TODO uncomment code when feature is marked as stable -->
<!--<local:SettingsBlockControl Title="{helpers:ResourceString Name=ShowFlattenOptions}" HorizontalAlignment="Stretch">
<ToggleSwitch
AutomationProperties.Name="{helpers:ResourceString Name=ShowFlattenOptions}"
IsOn="{x:Bind ViewModel.ShowFlattenOptions, Mode=TwoWay}"
Style="{StaticResource RightAlignedToggleSwitchStyle}" />
</local:SettingsBlockControl>-->

<!-- Send To -->
<local:SettingsBlockControl Title="{helpers:ResourceString Name=ShowSendToMenu}" HorizontalAlignment="Stretch">
<ToggleSwitch
Expand Down