Skip to content

Commit

Permalink
Feature: Added experimental support for flattening folders (files-com…
Browse files Browse the repository at this point in the history
  • Loading branch information
devin-slothower authored and 0x5bfa committed Aug 28, 2024
1 parent 3593cb1 commit a0c7116
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 3 deletions.
139 changes: 139 additions & 0 deletions src/Files.App/Actions/FileSystem/FlattenFolderAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.

using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml.Controls;
using System.IO;
using Windows.Foundation.Metadata;
using Windows.Storage;

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

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

public string Description
=> "FlattenFolderDescription".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 FlattenFolderAction()
{
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 = "FlattenFolder".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
FlattenFolder,

// 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 FlattenFolder => commands[CommandCodes.FlattenFolder];
public IRichCommand RotateLeft => commands[CommandCodes.RotateLeft];
public IRichCommand RotateRight => commands[CommandCodes.RotateRight];
public IRichCommand OpenItem => commands[CommandCodes.OpenItem];
Expand Down Expand Up @@ -292,6 +293,7 @@ public IEnumerator<IRichCommand> GetEnumerator() =>
[CommandCodes.DecompressArchiveHere] = new DecompressArchiveHere(),
[CommandCodes.DecompressArchiveHereSmart] = new DecompressArchiveHereSmart(),
[CommandCodes.DecompressArchiveToChildFolder] = new DecompressArchiveToChildFolderAction(),
[CommandCodes.FlattenFolder] = new FlattenFolderAction(),
[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 FlattenFolder { 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.FlattenFolder).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
18 changes: 18 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,21 @@
<data name="Compress" xml:space="preserve">
<value>Compress</value>
</data>
<data name="FlattenFolderDescription" xml:space="preserve">
<value>Move all contents from subfolders into the selected location</value>
</data>
<data name="FlattenFolder" xml:space="preserve">
<value>Flatten folder</value>
</data>
<data name="Flatten" xml:space="preserve">
<value>Flatten</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

0 comments on commit a0c7116

Please sign in to comment.