Merge branch 'feature/avalonia' into main
@@ -1,35 +1,36 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
public class Clipboard : IClipboard
|
||||
{
|
||||
private readonly List<ClipboardItem> _content;
|
||||
public IReadOnlyList<ClipboardItem> Content { get; }
|
||||
private readonly List<AbsolutePath> _content;
|
||||
public IReadOnlyList<AbsolutePath> Content { get; }
|
||||
public Type? CommandType { get; private set; }
|
||||
|
||||
public Clipboard()
|
||||
{
|
||||
_content = new List<ClipboardItem>();
|
||||
_content = new List<AbsolutePath>();
|
||||
Content = _content.AsReadOnly();
|
||||
}
|
||||
|
||||
public void AddContent(IContentProvider contentProvider, string path)
|
||||
public void AddContent(AbsolutePath absolutePath)
|
||||
{
|
||||
foreach (var content in _content)
|
||||
{
|
||||
if (content.ContentProvider == contentProvider && content.Path == path) return;
|
||||
if (content.IsEqual(absolutePath)) return;
|
||||
}
|
||||
|
||||
_content.Add(new ClipboardItem(contentProvider, path));
|
||||
_content.Add(new AbsolutePath(absolutePath));
|
||||
}
|
||||
|
||||
public void RemoveContent(IContentProvider contentProvider, string path)
|
||||
public void RemoveContent(AbsolutePath absolutePath)
|
||||
{
|
||||
for (var i = 0; i < _content.Count; i++)
|
||||
{
|
||||
if (_content[i].ContentProvider == contentProvider && _content[i].Path == path)
|
||||
if (_content[i].IsEqual(absolutePath))
|
||||
{
|
||||
_content.RemoveAt(i--);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
public class ClipboardItem : IAbsolutePath
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public ClipboardItem(IContentProvider contentProvider, string path)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
public interface IClipboard
|
||||
{
|
||||
IReadOnlyList<ClipboardItem> Content { get; }
|
||||
IReadOnlyList<AbsolutePath> Content { get; }
|
||||
Type? CommandType { get; }
|
||||
|
||||
void AddContent(IContentProvider contentProvider, string path);
|
||||
void AddContent(AbsolutePath absolutePath);
|
||||
void Clear();
|
||||
void RemoveContent(IContentProvider contentProvider, string path);
|
||||
void RemoveContent(AbsolutePath absolutePath);
|
||||
void SetCommand<T>() where T : ITransportationCommand;
|
||||
}
|
||||
}
|
||||
@@ -4,26 +4,32 @@ namespace FileTime.App.Core.Command
|
||||
{
|
||||
CloseTab,
|
||||
Copy,
|
||||
Cut,
|
||||
GoUp,
|
||||
MoveCursorDown,
|
||||
MoveCursorUp,
|
||||
Open,
|
||||
Paste,
|
||||
Select,
|
||||
ToggleHidden,
|
||||
CreateContainer,
|
||||
CreateElement,
|
||||
MoveCursorUpPage,
|
||||
Cut,
|
||||
EnterRapidTravel,
|
||||
GoToHome,
|
||||
GoToProvider,
|
||||
GoToRoot,
|
||||
GoUp,
|
||||
Delete,
|
||||
MoveCursorDown,
|
||||
MoveCursorDownPage,
|
||||
MoveToTop,
|
||||
MoveCursorUp,
|
||||
MoveCursorUpPage,
|
||||
MoveToBottom,
|
||||
MoveToFirst,
|
||||
MoveToLast,
|
||||
GoToRoot,
|
||||
GoToProvider,
|
||||
GoToHome,
|
||||
EnterRapidTravel,
|
||||
MoveToTop,
|
||||
Open,
|
||||
OpenOrRun,
|
||||
PasteMerge,
|
||||
PasteOverwrite,
|
||||
PasteSkip,
|
||||
Select,
|
||||
ToggleHidden,
|
||||
Rename,
|
||||
Dummy,
|
||||
Refresh,
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Tab
|
||||
{
|
||||
public class TabItem : IAbsolutePath
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public TabItem(IContentProvider contentProvider, string path)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,108 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Tab
|
||||
{
|
||||
public class TabState
|
||||
{
|
||||
private readonly Dictionary<IContainer, List<TabItem>> _selectedItems;
|
||||
private readonly Dictionary<IContainer, IReadOnlyList<TabItem>> _selectedItemsReadOnly;
|
||||
public IReadOnlyDictionary<IContainer, IReadOnlyList<TabItem>> SelectedItems { get; }
|
||||
private readonly Dictionary<IContainer, List<AbsolutePath>> _markedItems;
|
||||
private readonly Dictionary<IContainer, IReadOnlyList<AbsolutePath>> _markedItemsReadOnly;
|
||||
public IReadOnlyDictionary<IContainer, IReadOnlyList<AbsolutePath>> MarkedItems { get; }
|
||||
|
||||
public FileTime.Core.Components.Tab Tab { get; }
|
||||
|
||||
public AsyncEventHandler<TabState, AbsolutePath> ItemMarked { get; } = new();
|
||||
public AsyncEventHandler<TabState, AbsolutePath> ItemUnmarked { get; } = new();
|
||||
|
||||
public TabState(FileTime.Core.Components.Tab pane)
|
||||
{
|
||||
Tab = pane;
|
||||
|
||||
_selectedItems = new Dictionary<IContainer, List<TabItem>>();
|
||||
_selectedItemsReadOnly = new Dictionary<IContainer, IReadOnlyList<TabItem>>();
|
||||
SelectedItems = new ReadOnlyDictionary<IContainer, IReadOnlyList<TabItem>>(_selectedItemsReadOnly);
|
||||
_markedItems = new Dictionary<IContainer, List<AbsolutePath>>();
|
||||
_markedItemsReadOnly = new Dictionary<IContainer, IReadOnlyList<AbsolutePath>>();
|
||||
MarkedItems = new ReadOnlyDictionary<IContainer, IReadOnlyList<AbsolutePath>>(_markedItemsReadOnly);
|
||||
}
|
||||
|
||||
public void AddSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||
public async Task AddMarkedItem(IContainer container, AbsolutePath path)
|
||||
{
|
||||
if (!_selectedItems.ContainsKey(container))
|
||||
if (!_markedItems.ContainsKey(container))
|
||||
{
|
||||
var val = new List<TabItem>();
|
||||
_selectedItems.Add(container, val);
|
||||
_selectedItemsReadOnly.Add(container, val.AsReadOnly());
|
||||
var val = new List<AbsolutePath>();
|
||||
_markedItems.Add(container, val);
|
||||
_markedItemsReadOnly.Add(container, val.AsReadOnly());
|
||||
}
|
||||
|
||||
foreach (var content in _selectedItems[container])
|
||||
foreach (var content in _markedItems[container])
|
||||
{
|
||||
if (content.ContentProvider == contentProvider && content.Path == path) return;
|
||||
if (content.IsEqual(path)) return;
|
||||
}
|
||||
|
||||
_selectedItems[container].Add(new TabItem(contentProvider, path));
|
||||
var tabItem = new AbsolutePath(path);
|
||||
_markedItems[container].Add(tabItem);
|
||||
await ItemMarked.InvokeAsync(this, tabItem);
|
||||
}
|
||||
|
||||
public void RemoveSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||
public async Task RemoveMarkedItem(IContainer container, AbsolutePath path)
|
||||
{
|
||||
if (_selectedItems.ContainsKey(container))
|
||||
if (_markedItems.ContainsKey(container))
|
||||
{
|
||||
var selectedItems = _selectedItems[container];
|
||||
for (var i = 0; i < selectedItems.Count; i++)
|
||||
var markedItems = _markedItems[container];
|
||||
for (var i = 0; i < markedItems.Count; i++)
|
||||
{
|
||||
if (selectedItems[i].ContentProvider == contentProvider && selectedItems[i].Path == path)
|
||||
if (markedItems[i].IsEqual(path))
|
||||
{
|
||||
selectedItems.RemoveAt(i--);
|
||||
await ItemUnmarked.InvokeAsync(this, markedItems[i]);
|
||||
markedItems.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||
public bool ContainsMarkedItem(IContainer container, AbsolutePath path)
|
||||
{
|
||||
if (!_selectedItems.ContainsKey(container)) return false;
|
||||
if (!_markedItems.ContainsKey(container)) return false;
|
||||
|
||||
foreach (var content in _selectedItems[container])
|
||||
foreach (var content in _markedItems[container])
|
||||
{
|
||||
if (content.ContentProvider == contentProvider && content.Path == path) return true;
|
||||
if (content.Equals(path)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<TabItem>> GetCurrentSelectedItems()
|
||||
public async Task<IReadOnlyList<AbsolutePath>> GetCurrentMarkedItems()
|
||||
{
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
return GetCurrentMarkedItems(await Tab.GetCurrentLocation());
|
||||
}
|
||||
|
||||
return SelectedItems.ContainsKey(currentLocation)
|
||||
? SelectedItems[currentLocation]
|
||||
: new List<TabItem>().AsReadOnly();
|
||||
public IReadOnlyList<AbsolutePath> GetCurrentMarkedItems(IContainer container)
|
||||
{
|
||||
return MarkedItems.ContainsKey(container)
|
||||
? MarkedItems[container]
|
||||
: new List<AbsolutePath>().AsReadOnly();
|
||||
}
|
||||
|
||||
public async Task MakrCurrentItem()
|
||||
{
|
||||
var currentLocation = await Tab!.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
{
|
||||
var currentSelectedItem = await Tab.GetCurrentSelectedItem()!;
|
||||
if (currentSelectedItem != null)
|
||||
{
|
||||
if (ContainsMarkedItem(currentLocation, new AbsolutePath(currentSelectedItem)))
|
||||
{
|
||||
await RemoveMarkedItem(currentLocation, new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddMarkedItem(currentLocation, new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
}
|
||||
|
||||
await Tab.SelectNextItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using FileTime.App.Core.Clipboard;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.StateManagement;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Providers.Local;
|
||||
using FileTime.Providers.Smb;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -21,7 +22,8 @@ namespace FileTime.App.Core
|
||||
.AddSingleton<IContentProvider, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"))
|
||||
.AddSingleton<IContentProvider, SmbContentProvider>()
|
||||
.AddSingleton<ElementCreationStates>()
|
||||
.AddSingleton<CommandExecutor>();
|
||||
.AddSingleton<CommandExecutor>()
|
||||
.AddSingleton<TimeRunner>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using FileTime.ConsoleUI.App.UI.Color;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
@@ -28,8 +27,8 @@ namespace FileTime.ConsoleUI.App
|
||||
private async Task GoUp() => await _selectedTab!.GoUp();
|
||||
private async Task Open() => await _selectedTab!.Open();
|
||||
|
||||
private async Task MoveCursorUpPage() => await _selectedTab!.SelectPreviousItem(_renderers[_selectedTab].PageSize);
|
||||
private async Task MoveCursorDownPage() => await _selectedTab!.SelectNextItem(_renderers[_selectedTab].PageSize);
|
||||
private async Task MoveCursorUpPage() => await _selectedTab!.SelectPreviousItem(UI.Render.PageSize);
|
||||
private async Task MoveCursorDownPage() => await _selectedTab!.SelectNextItem(UI.Render.PageSize);
|
||||
private async Task MoveCursorToTop() => await _selectedTab!.SelectFirstItem();
|
||||
private async Task MoveCursorToBottom() => await _selectedTab!.SelectLastItem();
|
||||
|
||||
@@ -38,22 +37,7 @@ namespace FileTime.ConsoleUI.App
|
||||
const string hiddenFilterName = "filter_showhiddenelements";
|
||||
|
||||
var currentLocation = await _selectedTab!.GetCurrentLocation();
|
||||
|
||||
/*IContainer containerToOpen = currentLocation;
|
||||
|
||||
if (currentLocation is VirtualContainer oldVirtualContainer)
|
||||
{
|
||||
containerToOpen = oldVirtualContainer.HasWithName(hiddenFilterName)
|
||||
? oldVirtualContainer.ExceptWithName(hiddenFilterName)
|
||||
: GenerateHiddenFilterVirtualContainer(currentLocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
containerToOpen = GenerateHiddenFilterVirtualContainer(currentLocation);
|
||||
} */
|
||||
|
||||
var containerToOpen = await currentLocation.ToggleVirtualContainerInChain(hiddenFilterName, GenerateHiddenFilterVirtualContainer);
|
||||
|
||||
await _selectedTab.OpenContainer(containerToOpen);
|
||||
|
||||
static async Task<VirtualContainer> GenerateHiddenFilterVirtualContainer(IContainer container)
|
||||
@@ -81,20 +65,9 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
public async Task Select()
|
||||
{
|
||||
var currentLocation = await _selectedTab!.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
if (_selectedTab != null)
|
||||
{
|
||||
var currentSelectedItem = await _selectedTab.GetCurrentSelectedItem()!;
|
||||
if (_paneStates[_selectedTab].ContainsSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!))
|
||||
{
|
||||
_paneStates[_selectedTab].RemoveSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!);
|
||||
}
|
||||
else
|
||||
{
|
||||
_paneStates[_selectedTab].AddSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!);
|
||||
}
|
||||
|
||||
await _selectedTab.SelectNextItem();
|
||||
await _tabStates[_selectedTab].MakrCurrentItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,18 +76,18 @@ namespace FileTime.ConsoleUI.App
|
||||
_clipboard.Clear();
|
||||
_clipboard.SetCommand<CopyCommand>();
|
||||
|
||||
var currentSelectedItems = await _paneStates[_selectedTab!].GetCurrentSelectedItems();
|
||||
var currentSelectedItems = await _tabStates[_selectedTab!].GetCurrentMarkedItems();
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
foreach (var selectedItem in currentSelectedItems)
|
||||
{
|
||||
_clipboard.AddContent(selectedItem.ContentProvider, selectedItem.Path);
|
||||
_clipboard.AddContent(new AbsolutePath(selectedItem));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentSelectedItem = (await _selectedTab!.GetCurrentSelectedItem())!;
|
||||
_clipboard.AddContent(currentSelectedItem.Provider, currentSelectedItem.FullName!);
|
||||
_clipboard.AddContent(new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +130,7 @@ namespace FileTime.ConsoleUI.App
|
||||
? virtualContainer.BaseContainer
|
||||
: currentLocation;
|
||||
|
||||
_commandExecutor.ExecuteCommand(command);
|
||||
await _timeRunner.AddCommand(command);
|
||||
|
||||
_clipboard.Clear();
|
||||
}
|
||||
@@ -194,9 +167,9 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
private async Task HardDelete()
|
||||
{
|
||||
IList<IAbsolutePath>? itemsToDelete = null;
|
||||
IList<AbsolutePath>? itemsToDelete = null;
|
||||
|
||||
var currentSelectedItems = await _paneStates[_selectedTab!].GetCurrentSelectedItems();
|
||||
var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.Resolve()).ToList();
|
||||
var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem();
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
@@ -212,7 +185,7 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
if (delete)
|
||||
{
|
||||
itemsToDelete = currentSelectedItems.Cast<IAbsolutePath>().ToList();
|
||||
itemsToDelete = currentSelectedItems.Cast<AbsolutePath>().ToList();
|
||||
}
|
||||
}
|
||||
else if (currentSelectedItem != null)
|
||||
@@ -225,9 +198,9 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
if (delete)
|
||||
{
|
||||
itemsToDelete = new List<IAbsolutePath>()
|
||||
itemsToDelete = new List<AbsolutePath>()
|
||||
{
|
||||
new AbsolutePath(currentSelectedItem.Provider, currentSelectedItem.FullName!)
|
||||
new AbsolutePath(currentSelectedItem)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -241,7 +214,7 @@ namespace FileTime.ConsoleUI.App
|
||||
deleteCommand.ItemsToDelete.Add(itemToDelete);
|
||||
}
|
||||
|
||||
_commandExecutor.ExecuteCommand(deleteCommand);
|
||||
await _timeRunner.AddCommand(deleteCommand);
|
||||
_clipboard.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using FileTime.App.Core.Tab;
|
||||
using FileTime.ConsoleUI.App.UI.Color;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.App.Core.Command;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
@@ -17,14 +18,14 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
private readonly List<Tab> _tabs = new();
|
||||
private readonly Dictionary<Tab, Render> _renderers = new();
|
||||
private readonly Dictionary<Tab, TabState> _paneStates = new();
|
||||
private readonly Dictionary<Tab, TabState> _tabStates = new();
|
||||
private Tab? _selectedTab;
|
||||
|
||||
private readonly List<CommandBinding> _commandBindings = new();
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IClipboard _clipboard;
|
||||
private readonly IColoredConsoleRenderer _coloredConsoleRenderer;
|
||||
private readonly CommandExecutor _commandExecutor;
|
||||
private readonly TimeRunner _timeRunner;
|
||||
private readonly ConsoleReader _consoleReader;
|
||||
private readonly IStyles _styles;
|
||||
private readonly List<ConsoleKeyInfo> _previousKeys = new();
|
||||
@@ -35,14 +36,14 @@ namespace FileTime.ConsoleUI.App
|
||||
IServiceProvider serviceProvider,
|
||||
IClipboard clipboard,
|
||||
IColoredConsoleRenderer coloredConsoleRenderer,
|
||||
CommandExecutor commandExecutor,
|
||||
TimeRunner timeRunner,
|
||||
ConsoleReader consoleReader,
|
||||
IStyles styles)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_clipboard = clipboard;
|
||||
_coloredConsoleRenderer = coloredConsoleRenderer;
|
||||
_commandExecutor = commandExecutor;
|
||||
_timeRunner = timeRunner;
|
||||
_consoleReader = consoleReader;
|
||||
_styles = styles;
|
||||
InitCommandBindings();
|
||||
@@ -60,7 +61,7 @@ namespace FileTime.ConsoleUI.App
|
||||
_tabs.Add(tab);
|
||||
|
||||
var paneState = new TabState(tab);
|
||||
_paneStates.Add(tab, paneState);
|
||||
_tabStates.Add(tab, paneState);
|
||||
|
||||
var renderer = _serviceProvider.GetService<Render>()!;
|
||||
renderer.Init(tab, paneState);
|
||||
@@ -73,7 +74,7 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
_tabs.Remove(pane);
|
||||
_renderers.Remove(pane);
|
||||
_paneStates.Remove(pane);
|
||||
_tabStates.Remove(pane);
|
||||
}
|
||||
|
||||
private void InitCommandBindings()
|
||||
@@ -134,7 +135,7 @@ namespace FileTime.ConsoleUI.App
|
||||
Cut),
|
||||
new CommandBinding(
|
||||
"paste (merge)",
|
||||
Commands.Paste,
|
||||
Commands.PasteMerge,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||
@@ -143,7 +144,7 @@ namespace FileTime.ConsoleUI.App
|
||||
PasteMerge),
|
||||
new CommandBinding(
|
||||
"paste (overwrite)",
|
||||
Commands.Paste,
|
||||
Commands.PasteOverwrite,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||
@@ -152,7 +153,7 @@ namespace FileTime.ConsoleUI.App
|
||||
PasteOverwrite),
|
||||
new CommandBinding(
|
||||
"paste (skip)",
|
||||
Commands.Paste,
|
||||
Commands.PasteSkip,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
public Tab Tab { get; private set; }
|
||||
public TabState TabState { get; private set; }
|
||||
|
||||
public int PageSize => Console.WindowHeight - _contentPaddingTop - _contentPaddingBottom;
|
||||
public static int PageSize => Console.WindowHeight - _contentPaddingTop - _contentPaddingBottom;
|
||||
public Render(IColoredConsoleRenderer coloredRenderer, IStyles appStyle)
|
||||
{
|
||||
_coloredRenderer = coloredRenderer;
|
||||
@@ -219,7 +219,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
}
|
||||
}
|
||||
|
||||
var isSelected = TabState.ContainsSelectedItem(item.Provider, currentContainer, item.FullName!);
|
||||
var isSelected = TabState.ContainsMarkedItem(currentContainer, new AbsolutePath(item));
|
||||
if (isSelected)
|
||||
{
|
||||
backgroundColor = _appStyle.SelectedItemBackground;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace AsyncEvent
|
||||
{
|
||||
public class AsyncEventHandler<TSender, TArg> where TArg : AsyncEventArgs
|
||||
public class AsyncEventHandler<TSender, TArg>
|
||||
{
|
||||
private readonly List<Func<TSender, TArg, Task>> _handlers;
|
||||
private readonly Action<Func<TSender, TArg, Task>> _add;
|
||||
@@ -56,7 +56,7 @@
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, AsyncEventArgs> where TArg : AsyncEventArgs
|
||||
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, TArg>
|
||||
{
|
||||
public AsyncEventHandler(Action<Func<object?, TArg, Task>>? add = null, Action<Func<object?, TArg, Task>>? remove = null) : base(add, remove) { }
|
||||
}
|
||||
|
||||
9
src/Core/FileTime.Core/Command/CanCommandRun.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public enum CanCommandRun
|
||||
{
|
||||
True,
|
||||
False,
|
||||
Forceable
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CommandExecutor
|
||||
@@ -9,15 +11,15 @@ namespace FileTime.Core.Command
|
||||
_commandHandlers = commandHandlers.ToList();
|
||||
}
|
||||
|
||||
public void ExecuteCommand(ICommand command)
|
||||
public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner)
|
||||
{
|
||||
if (command is IExecutableCommand executableCommand)
|
||||
{
|
||||
executableCommand.Execute();
|
||||
await executableCommand.Execute(timeRunner);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandHandlers.Find(c => c.CanHandle(command))?.Execute(command);
|
||||
await _commandHandlers.Find(c => c.CanHandle(command))?.ExecuteAsync(command, timeRunner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,59 +5,115 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class CopyCommand : ITransportationCommand
|
||||
{
|
||||
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||
private Action<AbsolutePath, AbsolutePath>? _copyOperation;
|
||||
private Func<IContainer, string, Task<IContainer>>? _createContainer;
|
||||
private TimeRunner? _timeRunner;
|
||||
|
||||
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
|
||||
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
var newDiffs = new List<Difference>();
|
||||
|
||||
_copyOperation = (_, to) =>
|
||||
{
|
||||
var target = to.GetParentAsAbsolutePath().Resolve();
|
||||
newDiffs.Add(new Difference(
|
||||
target is IElement
|
||||
? DifferenceItemType.Element
|
||||
: DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
to
|
||||
));
|
||||
};
|
||||
|
||||
_createContainer = async (IContainer target, string name) =>
|
||||
{
|
||||
var newContainerDiff = new Difference(
|
||||
DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
AbsolutePath.FromParentAndChildName(target, name)
|
||||
);
|
||||
|
||||
newDiffs.Add(newContainerDiff);
|
||||
|
||||
return (IContainer)(await newContainerDiff.AbsolutePath.Resolve())!;
|
||||
};
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
|
||||
return startPoint.WithDifferences(newDiffs);
|
||||
}
|
||||
|
||||
public async Task Execute(Action<IAbsolutePath, IAbsolutePath> copy)
|
||||
public async Task Execute(Action<AbsolutePath, AbsolutePath> copy, TimeRunner timeRunner)
|
||||
{
|
||||
await DoCopy(Sources, Target, TransportMode, copy);
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
_copyOperation = copy;
|
||||
_createContainer = async (IContainer target, string name) => await target.CreateContainer(name);
|
||||
_timeRunner = timeRunner;
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
}
|
||||
|
||||
private async Task DoCopy(IEnumerable<IAbsolutePath> sources, IContainer target, TransportMode transportMode, Action<IAbsolutePath, IAbsolutePath> copy)
|
||||
private async Task DoCopy(
|
||||
IEnumerable<AbsolutePath> sources,
|
||||
IContainer target,
|
||||
TransportMode transportMode)
|
||||
{
|
||||
if (_copyOperation == null) throw new ArgumentException("No copy operation were given.");
|
||||
if (_createContainer == null) throw new ArgumentException("No container creation function were given.");
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var item = await source.ContentProvider.GetByPath(source.Path);
|
||||
var item = await source.Resolve();
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await target.CreateContainer(container.Name)!);
|
||||
var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await _createContainer?.Invoke(target, container.Name)!);
|
||||
|
||||
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(item.Provider, d.FullName!));
|
||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(item.Provider, f.FullName!));
|
||||
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(d));
|
||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(f));
|
||||
|
||||
await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode, copy);
|
||||
await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode);
|
||||
_timeRunner?.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
var targetName = element.Name;
|
||||
|
||||
var targetNameExists = await target.IsExists(targetName);
|
||||
if (transportMode == TransportMode.Merge)
|
||||
if (transportMode == Command.TransportMode.Merge)
|
||||
{
|
||||
for (var i = 0; targetNameExists; i++)
|
||||
{
|
||||
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
||||
}
|
||||
}
|
||||
else if (transportMode == TransportMode.Skip && targetNameExists)
|
||||
else if (transportMode == Command.TransportMode.Skip && targetNameExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetPath = target.FullName + Constants.SeparatorChar + targetName;
|
||||
_copyOperation?.Invoke(new AbsolutePath(element), AbsolutePath.FromParentAndChildName(target, targetName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copy(new AbsolutePath(source.ContentProvider, element.FullName!), new AbsolutePath(target.Provider, targetPath));
|
||||
}
|
||||
}
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
//TODO: implement
|
||||
return Task.FromResult(CanCommandRun.True);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,51 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CreateContainerCommand : ICommand
|
||||
public class CreateContainerCommand : IExecutableCommand
|
||||
{
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewContainerName { get; }
|
||||
|
||||
public CreateContainerCommand(AbsolutePath container, string newContainerName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Container = container;
|
||||
NewContainerName = newContainerName;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateContainer(NewContainerName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
//TODO: else
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Container, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewContainerName, Container.VirtualContentProvider))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = await Container.Resolve();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewContainerName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
|
||||
return CanCommandRun.True;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,50 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CreateElementCommand : ICommand
|
||||
public class CreateElementCommand : IExecutableCommand
|
||||
{
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewElementName { get; }
|
||||
|
||||
public CreateElementCommand(AbsolutePath container, string newElementName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Container = container;
|
||||
NewElementName = newElementName;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateElement(NewElementName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Element, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewElementName, Container.VirtualContentProvider))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = Container.Resolve();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewElementName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
|
||||
return CanCommandRun.True;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
@@ -5,37 +6,64 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class DeleteCommand : IExecutableCommand
|
||||
{
|
||||
public IList<IAbsolutePath> ItemsToDelete { get; } = new List<IAbsolutePath>();
|
||||
public IList<AbsolutePath> ItemsToDelete { get; } = new List<AbsolutePath>();
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var newDifferences = new List<Difference>();
|
||||
|
||||
foreach (var itemToDelete in ItemsToDelete)
|
||||
{
|
||||
var item = await itemToDelete.Resolve();
|
||||
newDifferences.Add(new Difference(
|
||||
item.ToDifferenceItemType(),
|
||||
DifferenceActionType.Delete,
|
||||
itemToDelete
|
||||
));
|
||||
}
|
||||
return startPoint.WithDifferences(newDifferences);
|
||||
}
|
||||
|
||||
public async Task Execute()
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
foreach (var item in ItemsToDelete)
|
||||
{
|
||||
await DoDelete(await item.ContentProvider.GetByPath(item.Path)!);
|
||||
await DoDelete((await item.Resolve())!, timeRunner);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoDelete(IItem item)
|
||||
private async Task DoDelete(IItem item, TimeRunner timeRunner)
|
||||
{
|
||||
if (item is IContainer container)
|
||||
{
|
||||
foreach (var child in await container.GetItems())
|
||||
foreach (var child in (await container.GetItems())!)
|
||||
{
|
||||
await DoDelete(child);
|
||||
await DoDelete(child, timeRunner);
|
||||
await child.Delete();
|
||||
}
|
||||
|
||||
await item.Delete();
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if(item is IElement element)
|
||||
else if (item is IElement element)
|
||||
{
|
||||
await element.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var result = CanCommandRun.True;
|
||||
foreach (var itemPath in ItemsToDelete)
|
||||
{
|
||||
var resolvedItem = await itemPath.Resolve();
|
||||
if (!(resolvedItem?.CanDelete ?? true))
|
||||
{
|
||||
result = CanCommandRun.Forceable;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ICommand
|
||||
{
|
||||
PointInTime SimulateCommand(PointInTime moment);
|
||||
Task<CanCommandRun> CanRun(PointInTime startPoint);
|
||||
Task<PointInTime> SimulateCommand(PointInTime startPoint);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ICommandHandler
|
||||
{
|
||||
bool CanHandle(object command);
|
||||
void Execute(object command);
|
||||
Task ExecuteAsync(object command, TimeRunner timeRunner);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public interface IExecutableCommand : ICommand
|
||||
{
|
||||
Task Execute();
|
||||
Task Execute(TimeRunner timeRunner);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ITransportationCommand : ICommand
|
||||
{
|
||||
IList<IAbsolutePath> Sources { get; }
|
||||
IContainer Target { get; set;}
|
||||
TransportMode TransportMode { get; set; }
|
||||
IList<AbsolutePath> Sources { get; }
|
||||
IContainer? Target { get; set;}
|
||||
TransportMode? TransportMode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,17 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class MoveCommand : ITransportationCommand
|
||||
{
|
||||
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||
public IList<AbsolutePath> Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
37
src/Core/FileTime.Core/Command/RenameCommand.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class RenameCommand : IExecutableCommand
|
||||
{
|
||||
public AbsolutePath Source { get; }
|
||||
public string Target { get; }
|
||||
|
||||
public RenameCommand(AbsolutePath source, string target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var itemToRename = await Source.Resolve();
|
||||
if (itemToRename != null)
|
||||
{
|
||||
await itemToRename.Rename(Target);
|
||||
timeRunner.RefreshContainer?.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,10 @@ namespace FileTime.Core.Components
|
||||
public class Tab
|
||||
{
|
||||
private IItem? _currentSelectedItem;
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private IContainer _currentLocation;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private string? _lastPath;
|
||||
|
||||
public int CurrentSelectedIndex { get; private set; }
|
||||
|
||||
@@ -33,10 +36,10 @@ namespace FileTime.Core.Components
|
||||
}
|
||||
|
||||
_currentLocation = value;
|
||||
await CurrentLocationChanged?.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null);
|
||||
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
||||
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
|
||||
}
|
||||
}
|
||||
@@ -50,18 +53,82 @@ namespace FileTime.Core.Components
|
||||
{
|
||||
if (_currentSelectedItem != value)
|
||||
{
|
||||
var contains = (await _currentLocation.GetItems())?.Contains(value) ?? false;
|
||||
if(value != null && !contains) throw new IndexOutOfRangeException("Provided item does not exists in the current container.");
|
||||
|
||||
_currentSelectedItem = value;
|
||||
CurrentSelectedIndex = await GetItemIndex(value);
|
||||
await CurrentSelectedItemChanged?.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
IItem? itemToSelect = null;
|
||||
if (value != null)
|
||||
{
|
||||
itemToSelect = (await _currentLocation.GetItems())?.FirstOrDefault(i =>
|
||||
i.FullName == null && value?.FullName == null
|
||||
? i.Name == value?.Name
|
||||
: i.FullName == value?.FullName);
|
||||
if (itemToSelect == null) throw new IndexOutOfRangeException("Provided item does not exists in the current container.");
|
||||
}
|
||||
|
||||
_currentSelectedItem = itemToSelect;
|
||||
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
|
||||
CurrentSelectedIndex = await GetItemIndex(itemToSelect);
|
||||
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
}
|
||||
}
|
||||
public async Task<IItem?> GetItemByLastPath(IContainer? container = null)
|
||||
{
|
||||
container ??= _currentLocation;
|
||||
var containerFullName = container.FullName;
|
||||
|
||||
if (_lastPath == null
|
||||
|| !container.IsLoaded
|
||||
|| (containerFullName != null && !_lastPath.StartsWith(containerFullName))
|
||||
)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var itemNameToSelect = _lastPath
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Skip(
|
||||
containerFullName == null
|
||||
? 0
|
||||
: containerFullName
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Count())
|
||||
.FirstOrDefault();
|
||||
|
||||
return (await container.GetItems())?.FirstOrDefault(i => i.Name == itemNameToSelect);
|
||||
}
|
||||
|
||||
private string GetCommonPath(string? oldPath, string? newPath)
|
||||
{
|
||||
var oldPathParts = oldPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
var newPathParts = newPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
|
||||
var commonPathParts = new List<string>();
|
||||
|
||||
var max = oldPathParts.Length > newPathParts.Length ? oldPathParts.Length : newPathParts.Length;
|
||||
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
if (newPathParts.Length <= i)
|
||||
{
|
||||
commonPathParts.AddRange(oldPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts.Length <= i || oldPathParts[i] != newPathParts[i])
|
||||
{
|
||||
commonPathParts.AddRange(newPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts[i] == newPathParts[i])
|
||||
{
|
||||
commonPathParts.Add(oldPathParts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(Constants.SeparatorChar, commonPathParts);
|
||||
}
|
||||
|
||||
private async Task HandleCurrentLocationRefresh(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName;
|
||||
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName ?? (await GetItemByLastPath())?.FullName;
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
if (currentSelectedName != null)
|
||||
{
|
||||
|
||||
15
src/Core/FileTime.Core/Extensions/TimelineExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Extensions
|
||||
{
|
||||
public static class TimelineExtensions
|
||||
{
|
||||
public static DifferenceItemType ToDifferenceItemType(this IItem? item)
|
||||
{
|
||||
if (item is IContainer) return DifferenceItemType.Container;
|
||||
else if (item is IElement) return DifferenceItemType.Element;
|
||||
else return DifferenceItemType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Core/FileTime.Core/Interactions/BasicInputHandler.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Interactions
|
||||
{
|
||||
public class BasicInputHandler : IInputInterface
|
||||
{
|
||||
public Func<IEnumerable<InputElement>, Task<string?[]>>? InputHandler { get; set; }
|
||||
public async Task<string?[]> ReadInputs(IEnumerable<InputElement> fields) =>
|
||||
InputHandler != null ? await InputHandler.Invoke(fields) : throw new NotImplementedException(nameof(InputHandler) + " is not set");
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ namespace FileTime.Core.Interactions
|
||||
{
|
||||
public string Text { get; }
|
||||
public InputType InputType { get; }
|
||||
public string? DefaultValue { get; }
|
||||
|
||||
public InputElement(string text, InputType inputType)
|
||||
public InputElement(string text, InputType inputType, string? defaultValue = null)
|
||||
{
|
||||
Text = text;
|
||||
InputType = inputType;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ namespace FileTime.Core.Interactions
|
||||
public enum InputType
|
||||
{
|
||||
Text,
|
||||
Password
|
||||
Password,
|
||||
Bool
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,97 @@
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public class AbsolutePath : IAbsolutePath
|
||||
public sealed class AbsolutePath
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public IContentProvider? VirtualContentProvider { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public AbsolutePath(IContentProvider contentProvider, string path)
|
||||
public AbsolutePath(AbsolutePath from)
|
||||
{
|
||||
ContentProvider = from.ContentProvider;
|
||||
Path = from.Path;
|
||||
VirtualContentProvider = from.VirtualContentProvider;
|
||||
}
|
||||
|
||||
public AbsolutePath(IContentProvider contentProvider, string path, IContentProvider? virtualContentProvider)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
VirtualContentProvider = virtualContentProvider;
|
||||
}
|
||||
|
||||
public AbsolutePath(IItem item)
|
||||
{
|
||||
if (item is TimeContainer timeContainer)
|
||||
{
|
||||
ContentProvider = timeContainer.Provider;
|
||||
VirtualContentProvider = timeContainer.VirtualProvider;
|
||||
Path = timeContainer.FullName!;
|
||||
}
|
||||
else if (item is TimeElement timeElement)
|
||||
{
|
||||
ContentProvider = timeElement.Provider;
|
||||
VirtualContentProvider = timeElement.VirtualProvider;
|
||||
Path = timeElement.FullName!;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentProvider = item.Provider;
|
||||
Path = item.FullName!;
|
||||
}
|
||||
}
|
||||
|
||||
public static AbsolutePath FromParentAndChildName(IContainer parent, string childName)
|
||||
{
|
||||
IContentProvider? contentProvider;
|
||||
IContentProvider? virtualContentProvider;
|
||||
string? path;
|
||||
|
||||
if (parent is TimeContainer timeContainer)
|
||||
{
|
||||
contentProvider = timeContainer.Provider;
|
||||
virtualContentProvider = timeContainer.VirtualProvider;
|
||||
path = timeContainer.FullName! + Constants.SeparatorChar + childName;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentProvider = parent.Provider;
|
||||
path = parent.FullName! + Constants.SeparatorChar + childName;
|
||||
virtualContentProvider = null;
|
||||
}
|
||||
|
||||
return new AbsolutePath(contentProvider, path, virtualContentProvider);
|
||||
}
|
||||
|
||||
public bool IsEqual(AbsolutePath path)
|
||||
{
|
||||
//TODO: sure??
|
||||
return path.ContentProvider == ContentProvider && path.Path == Path;
|
||||
}
|
||||
|
||||
public async Task<IItem?> Resolve()
|
||||
{
|
||||
var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExists(Path))
|
||||
? await VirtualContentProvider.GetByPath(Path)
|
||||
: null;
|
||||
|
||||
result ??= await ContentProvider.GetByPath(Path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetParent()
|
||||
{
|
||||
var pathParts = Path.Split(Constants.SeparatorChar);
|
||||
return string.Join(Constants.SeparatorChar, pathParts);
|
||||
}
|
||||
|
||||
public AbsolutePath GetParentAsAbsolutePath() => new(ContentProvider, GetParent(), VirtualContentProvider);
|
||||
|
||||
public string GetName() => Path.Split(Constants.SeparatorChar).Last();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IAbsolutePath
|
||||
{
|
||||
IContentProvider ContentProvider { get; }
|
||||
string Path { get; }
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ namespace FileTime.Core.Models
|
||||
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
|
||||
|
||||
Task Refresh();
|
||||
IContainer? GetParent();
|
||||
Task<IItem?> GetByPath(string path);
|
||||
Task<IContainer> CreateContainer(string name);
|
||||
Task<IElement> CreateElement(string name);
|
||||
@@ -18,6 +17,8 @@ namespace FileTime.Core.Models
|
||||
|
||||
Task<IContainer> Clone();
|
||||
|
||||
bool IsLoaded { get; }
|
||||
|
||||
AsyncEventHandler Refreshed { get; }
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,11 @@ namespace FileTime.Core.Models
|
||||
string Name { get; }
|
||||
string? FullName { get; }
|
||||
bool IsHidden { get; }
|
||||
bool CanDelete { get; }
|
||||
bool CanRename { get; }
|
||||
IContentProvider Provider { get; }
|
||||
Task Delete();
|
||||
Task Rename(string newName);
|
||||
IContainer? GetParent();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ namespace FileTime.Core.Models
|
||||
public string? FullName => BaseContainer.FullName;
|
||||
|
||||
public bool IsHidden => BaseContainer.IsHidden;
|
||||
public bool IsLoaded => BaseContainer.IsLoaded;
|
||||
public bool CanDelete => BaseContainer.CanDelete;
|
||||
public bool CanRename => BaseContainer.CanRename;
|
||||
|
||||
public IContentProvider Provider => BaseContainer.Provider;
|
||||
|
||||
@@ -159,5 +162,7 @@ namespace FileTime.Core.Models
|
||||
VirtualContainerName
|
||||
);
|
||||
}
|
||||
|
||||
public async Task Rename(string newName) => await BaseContainer.Rename(newName);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
using System;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
@@ -11,13 +12,20 @@ namespace FileTime.Core.Providers
|
||||
private readonly IReadOnlyList<IItem>? _items;
|
||||
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
public string Name => null;
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
public string? FullName => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => true;
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
public IContentProvider Provider => null;
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
public bool CanDelete => false;
|
||||
public bool CanRename => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
@@ -52,5 +60,7 @@ namespace FileTime.Core.Providers
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
27
src/Core/FileTime.Core/Timeline/CommandTimeState.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class CommandTimeState
|
||||
{
|
||||
public ICommand Command { get; }
|
||||
public CanCommandRun CanRun { get; private set; } = CanCommandRun.False;
|
||||
public bool ForceRun { get; set; }
|
||||
public TimeProvider? TimeProvider { get; private set; }
|
||||
|
||||
public CommandTimeState(ICommand command, PointInTime? startTime)
|
||||
{
|
||||
Command = command;
|
||||
UpdateState(startTime).Wait();
|
||||
}
|
||||
|
||||
public async Task UpdateState(PointInTime? startPoint)
|
||||
{
|
||||
CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint);
|
||||
if (startPoint != null)
|
||||
{
|
||||
TimeProvider = startPoint.Provider as TimeProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ContainerSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
31
src/Core/FileTime.Core/Timeline/Difference.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class Difference
|
||||
{
|
||||
public DifferenceItemType Type { get; }
|
||||
public string Name { get; }
|
||||
public AbsolutePath AbsolutePath { get; }
|
||||
public DifferenceActionType Action { get; }
|
||||
|
||||
public Difference(DifferenceItemType type, DifferenceActionType action, AbsolutePath absolutePath)
|
||||
{
|
||||
Type = type;
|
||||
AbsolutePath = absolutePath;
|
||||
Action = action;
|
||||
|
||||
Name = absolutePath.GetName();
|
||||
}
|
||||
|
||||
public Difference WithVirtualContentProvider(IContentProvider? virtualContentProvider)
|
||||
{
|
||||
return new Difference(
|
||||
Type,
|
||||
Action,
|
||||
new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, virtualContentProvider)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Core/FileTime.Core/Timeline/DifferenceActionType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceActionType
|
||||
{
|
||||
Create,
|
||||
Delete
|
||||
}
|
||||
}
|
||||
9
src/Core/FileTime.Core/Timeline/DifferenceItemType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceItemType
|
||||
{
|
||||
Container,
|
||||
Element,
|
||||
Unknown
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/ParallelCommands.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ParallelCommands
|
||||
{
|
||||
private static ushort _idCounter;
|
||||
public List<CommandTimeState> _commands;
|
||||
public ushort Id { get; }
|
||||
public IReadOnlyList<CommandTimeState> Commands { get; }
|
||||
public PointInTime? Result { get; private set; }
|
||||
|
||||
public ParallelCommands(PointInTime? result)
|
||||
: this(new List<CommandTimeState>(), result) { }
|
||||
|
||||
private ParallelCommands(List<CommandTimeState> commands, PointInTime? result)
|
||||
{
|
||||
Id = _idCounter++;
|
||||
|
||||
_commands = commands;
|
||||
Commands = _commands.AsReadOnly();
|
||||
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public static async Task<ParallelCommands> Create(PointInTime? startTime, IEnumerable<ICommand> commands)
|
||||
{
|
||||
var commandStates = new List<CommandTimeState>();
|
||||
var currentTime = startTime;
|
||||
foreach (var command in commands)
|
||||
{
|
||||
CommandTimeState commandTimeState = new(command, currentTime);
|
||||
if (currentTime != null)
|
||||
{
|
||||
var canRun = await command.CanRun(currentTime);
|
||||
if (canRun == CanCommandRun.True)
|
||||
{
|
||||
currentTime = await command.SimulateCommand(currentTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
commandStates.Add(commandTimeState);
|
||||
}
|
||||
|
||||
return new ParallelCommands(commandStates, currentTime);
|
||||
}
|
||||
|
||||
public async Task AddCommand(ICommand command)
|
||||
{
|
||||
_commands.Add(new CommandTimeState(command, Result));
|
||||
if (Result != null)
|
||||
{
|
||||
Result = await command.SimulateCommand(Result);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PointInTime?> RefreshResult(PointInTime? startPoint)
|
||||
{
|
||||
var result = startPoint;
|
||||
foreach (var command in _commands)
|
||||
{
|
||||
await command.UpdateState(result);
|
||||
if (result != null)
|
||||
{
|
||||
var canRun = await command.Command.CanRun(result);
|
||||
if (canRun == CanCommandRun.True || (canRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
{
|
||||
result = await command.Command.SimulateCommand(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result = result;
|
||||
return Result;
|
||||
}
|
||||
|
||||
public void RemoveAt(int number) => _commands.RemoveAt(number);
|
||||
|
||||
internal void Remove(CommandTimeState command) => _commands.Remove(command);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,40 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class PointInTime
|
||||
public sealed class PointInTime
|
||||
{
|
||||
private readonly Dictionary<IContentProvider, RootSnapshot> snapshots = new();
|
||||
private readonly List<Difference> _differences;
|
||||
|
||||
public IReadOnlyDictionary<IContentProvider, RootSnapshot> Snapshots => new Lazy<IReadOnlyDictionary<IContentProvider, RootSnapshot>>(() => new ReadOnlyDictionary<IContentProvider, RootSnapshot>(snapshots)).Value;
|
||||
public IReadOnlyList<Difference> Differences { get; }
|
||||
|
||||
public IContentProvider? Provider { get; }
|
||||
|
||||
private PointInTime() : this(new List<Difference>(), null) { }
|
||||
|
||||
private PointInTime(IEnumerable<Difference> differences, IContentProvider? provider)
|
||||
{
|
||||
_differences = new List<Difference>(differences);
|
||||
Differences = _differences.AsReadOnly();
|
||||
Provider = provider;
|
||||
}
|
||||
|
||||
private PointInTime(PointInTime previous, IEnumerable<Difference> differences, IContentProvider provider)
|
||||
: this(MergeDifferences(previous.Differences, differences, provider), provider) { }
|
||||
|
||||
public PointInTime WithDifferences(IEnumerable<Difference> differences) => new(this, differences, new TimeProvider(this));
|
||||
|
||||
private static List<Difference> MergeDifferences(IEnumerable<Difference> previouses, IEnumerable<Difference> differences, IContentProvider virtualProvider)
|
||||
{
|
||||
var merged = new List<Difference>();
|
||||
|
||||
merged.AddRange(previouses.Select(p => p.WithVirtualContentProvider(virtualProvider)));
|
||||
merged.AddRange(differences.Select(d => d.WithVirtualContentProvider(virtualProvider)));
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
public static PointInTime CreateEmpty(IContentProvider? parentProvder = null) =>
|
||||
parentProvder == null ? new PointInTime() : new PointInTime(new List<Difference>(), parentProvder);
|
||||
}
|
||||
}
|
||||
18
src/Core/FileTime.Core/Timeline/ReadOnlyCommandTimeState.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ReadOnlyCommandTimeState
|
||||
{
|
||||
public CanCommandRun CanRun { get; }
|
||||
public bool ForceRun { get; }
|
||||
public ICommand Command { get; }
|
||||
|
||||
public ReadOnlyCommandTimeState(CommandTimeState commandTimeState)
|
||||
{
|
||||
CanRun = commandTimeState.CanRun;
|
||||
ForceRun = commandTimeState.ForceRun;
|
||||
Command = commandTimeState.Command;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ReadOnlyParallelCommands
|
||||
{
|
||||
public IReadOnlyList<ReadOnlyCommandTimeState> Commands { get; }
|
||||
public ReadOnlyParallelCommands(ParallelCommands parallelCommands)
|
||||
{
|
||||
Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class RootSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
121
src/Core/FileTime.Core/Timeline/TimeContainer.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeContainer : IContainer
|
||||
{
|
||||
private readonly IContainer? _parent;
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
|
||||
{
|
||||
_parent = parent;
|
||||
_pointInTime = pointInTime;
|
||||
|
||||
Name = name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
}
|
||||
|
||||
public async Task<IContainer> Clone() => new TimeContainer(Name, await _parent!.Clone(), Provider, VirtualProvider, _pointInTime);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime));
|
||||
|
||||
public Task<IElement> CreateElement(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider));
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
|
||||
public async Task<IItem?> GetByPath(string path)
|
||||
{
|
||||
var paths = path.Split(Constants.SeparatorChar);
|
||||
|
||||
var item = (await GetItems())!.FirstOrDefault(i => i.Name == paths[0]);
|
||||
|
||||
if (paths.Length == 1)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) =>
|
||||
Task.FromResult(
|
||||
(IReadOnlyList<IContainer>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Container
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapContainer)
|
||||
.ToList()
|
||||
.AsReadOnly()
|
||||
);
|
||||
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) =>
|
||||
Task.FromResult(
|
||||
(IReadOnlyList<IElement>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Element
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapElement)
|
||||
.ToList()
|
||||
.AsReadOnly()
|
||||
);
|
||||
|
||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
var containers = (await GetContainers(token))!;
|
||||
var elements = (await GetElements(token))!;
|
||||
|
||||
return containers.Cast<IItem>().Concat(elements).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public async Task<bool> IsExists(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false;
|
||||
|
||||
public async Task Refresh() => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
|
||||
private static string GetParentPath(string path) => string.Join(Constants.SeparatorChar, path.Split(Constants.SeparatorChar).Take(-1));
|
||||
|
||||
private IContainer MapContainer(Difference containerDiff)
|
||||
{
|
||||
if (containerDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{nameof(containerDiff)}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Container}.");
|
||||
return new TimeContainer(containerDiff.Name, this, Provider, containerDiff.AbsolutePath.VirtualContentProvider ?? containerDiff.AbsolutePath.ContentProvider, _pointInTime);
|
||||
}
|
||||
|
||||
private IElement MapElement(Difference elementDiff)
|
||||
{
|
||||
if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}.");
|
||||
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Core/FileTime.Core/Timeline/TimeElement.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeElement : IElement
|
||||
{
|
||||
private readonly IContainer _parent;
|
||||
public TimeElement(string name, TimeContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
Name = name;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
}
|
||||
|
||||
public bool IsSpecial => false;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public string GetPrimaryAttributeText() => "";
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/TimeProvider.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeProvider : IContentProvider
|
||||
{
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public string Name => "time";
|
||||
|
||||
public string? FullName => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => false;
|
||||
|
||||
public bool CanRename => false;
|
||||
|
||||
public IContentProvider Provider => this;
|
||||
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public TimeProvider(PointInTime pointInTime)
|
||||
{
|
||||
_pointInTime = pointInTime;
|
||||
}
|
||||
|
||||
public bool CanHandlePath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IElement> CreateElement(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Delete() => throw new NotSupportedException();
|
||||
|
||||
public Task<IItem?> GetByPath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => null;
|
||||
|
||||
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> IsExists(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Refresh() => Task.CompletedTask;
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public void SetParent(IContainer container) { }
|
||||
}
|
||||
}
|
||||
217
src/Core/FileTime.Core/Timeline/TimeRunner.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeRunner
|
||||
{
|
||||
private readonly CommandExecutor _commandExecutor;
|
||||
private readonly List<ParallelCommands> _commandsToRun = new();
|
||||
private readonly object _guard = new();
|
||||
|
||||
private bool _resourceIsInUse;
|
||||
private readonly List<Thread> _commandRunners = new();
|
||||
private bool _enableRunning = true;
|
||||
|
||||
public bool EnableRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
bool result = true;
|
||||
RunWithLock(() => result = _enableRunning);
|
||||
return result;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
RunWithLock(() => _enableRunning = value);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> ParallelCommands { get; private set; } = new List<ReadOnlyParallelCommands>().AsReadOnly();
|
||||
|
||||
public AsyncEventHandler<AbsolutePath> RefreshContainer { get; } = new AsyncEventHandler<AbsolutePath>();
|
||||
|
||||
public event EventHandler? CommandsChanged;
|
||||
|
||||
public TimeRunner(CommandExecutor commandExecutor)
|
||||
{
|
||||
_commandExecutor = commandExecutor;
|
||||
}
|
||||
|
||||
public async Task AddCommand(ICommand command, ParallelCommands? batch = null, bool toNewBatch = false)
|
||||
{
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
ParallelCommands batchToAdd;
|
||||
|
||||
if (_commandsToRun.Count == 0)
|
||||
{
|
||||
batchToAdd = new ParallelCommands(PointInTime.CreateEmpty());
|
||||
_commandsToRun.Add(batchToAdd);
|
||||
}
|
||||
else if (toNewBatch)
|
||||
{
|
||||
batchToAdd = new ParallelCommands(_commandsToRun.Last().Result);
|
||||
_commandsToRun.Add(batchToAdd);
|
||||
}
|
||||
else if (batch != null && _commandsToRun.Contains(batch))
|
||||
{
|
||||
batchToAdd = batch;
|
||||
}
|
||||
else
|
||||
{
|
||||
batchToAdd = _commandsToRun[0];
|
||||
}
|
||||
await batchToAdd.AddCommand(command);
|
||||
|
||||
await RefreshCommands();
|
||||
|
||||
if (_commandRunners.Count == 0)
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
|
||||
await UpdateReadOnlyCommands();
|
||||
}
|
||||
|
||||
public async Task TryStartCommandRunner()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
if (_commandRunners.Count == 0 && _commandsToRun.Count > 0)
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void StartCommandRunner()
|
||||
{
|
||||
if (_enableRunning)
|
||||
{
|
||||
RunCommands();
|
||||
}
|
||||
}
|
||||
|
||||
private void RunCommands()
|
||||
{
|
||||
if (_commandsToRun.Count > 0)
|
||||
{
|
||||
foreach (var command in _commandsToRun[0].Commands)
|
||||
{
|
||||
if (command.CanRun == CanCommandRun.True || (command.CanRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
{
|
||||
var thread = new Thread(new ParameterizedThreadStart(RunCommand));
|
||||
thread.Start(command);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RunCommand(object? arg)
|
||||
{
|
||||
CommandTimeState? commandToRun = null;
|
||||
try
|
||||
{
|
||||
if (arg is CommandTimeState commandToRun2)
|
||||
{
|
||||
commandToRun = commandToRun2;
|
||||
_commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCommandThread(Thread.CurrentThread, commandToRun).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisposeCommandThread(Thread thread, CommandTimeState? command)
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
if (command != null)
|
||||
{
|
||||
_commandsToRun[0].Remove(command);
|
||||
}
|
||||
|
||||
_commandRunners.Remove(thread);
|
||||
});
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
await TryStartCommandRunner();
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
await RefreshCommands(PointInTime.CreateEmpty());
|
||||
});
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
}
|
||||
|
||||
private async Task RefreshCommands(PointInTime? fullStartTime = null)
|
||||
{
|
||||
var curretnTime = fullStartTime ?? _commandsToRun[0].Result;
|
||||
var startIndex = fullStartTime == null ? 1 : 0;
|
||||
|
||||
for (var i = startIndex; i < _commandsToRun.Count; i++)
|
||||
{
|
||||
curretnTime = await _commandsToRun[i].RefreshResult(curretnTime);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateReadOnlyCommands()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly();
|
||||
});
|
||||
CommandsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Action action)
|
||||
{
|
||||
await RunWithLockAsync(() => { action(); return Task.CompletedTask; });
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Func<Task> func)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
if (!_resourceIsInUse)
|
||||
{
|
||||
_resourceIsInUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await func();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
_resourceIsInUse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RunWithLock(Action action) => RunWithLockAsync(action).Wait();
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,8 @@ Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "FileTime.Uno.Windows.Packag
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncEvent", "Core\AsyncEvent\AsyncEvent.csproj", "{9BDAC126-200F-4056-8D35-36EC059B40F3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Avalonia", "GuiApp\FileTime.Avalonia\FileTime.Avalonia.csproj", "{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
GuiApp\FileTime.Uno\FileTime.Uno.Shared\FileTime.Uno.Shared.projitems*{02acef48-3be8-43e5-9358-a914bcbea7ca}*SharedItemsImports = 5
|
||||
@@ -378,6 +380,26 @@ Global
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3}.Release|x86.Build.0 = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -400,6 +422,7 @@ Global
|
||||
{02ACEF48-3BE8-43E5-9358-A914BCBEA7CA} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{6BB770CB-B0C1-4A82-86B1-3C56F4393BE1} = {7CF1A80E-709A-4CB0-8434-7B2F0A4BC497}
|
||||
{9BDAC126-200F-4056-8D35-36EC059B40F3} = {38B1B927-4201-4B7A-87EE-737B8C6D4090}
|
||||
{22B33BC6-3987-4BE6-8C54-BFC75C78CCE7} = {890275FF-943A-4D07-83BA-14E5C52D7846}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8D679DCE-AC84-4A91-BFED-8F8D8E1D8183}
|
||||
|
||||
454
src/GuiApp/FileTime.Avalonia/.gitignore
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual Studio Code
|
||||
##
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
149
src/GuiApp/FileTime.Avalonia/App.axaml
Normal file
@@ -0,0 +1,149 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:FileTime.Avalonia"
|
||||
xmlns:converters="using:FileTime.Avalonia.Converters"
|
||||
xmlns:views="using:FileTime.Avalonia.Views"
|
||||
x:Class="FileTime.Avalonia.App">
|
||||
<Application.DataTemplates>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<Color x:Key="AppBackgroundColor">#073642</Color>
|
||||
<Color x:Key="ContainerBackgroundColor">#083e4c</Color>
|
||||
|
||||
<Color x:Key="ItemBackgroundColor">#00000000</Color>
|
||||
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
|
||||
<Color x:Key="SelectedItemBackgroundColor">#93a1a1</Color>
|
||||
<Color x:Key="MarkedItemBackgroundColor">#00000000</Color>
|
||||
<Color x:Key="MarkedAlternativeItemBackgroundColor">#10000000</Color>
|
||||
<Color x:Key="MarkedSelectedItemBackgroundColor">#b58900</Color>
|
||||
|
||||
<Color x:Key="ForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="AccentForegroundColor">#268bd2</Color>
|
||||
<Color x:Key="LightForegroundColor">#7793a1a1</Color>
|
||||
<Color x:Key="AlternativeItemForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="SelectedItemForegroundColor">#073642</Color>
|
||||
<Color x:Key="MarkedItemForegroundColor">#b58900</Color>
|
||||
<Color x:Key="MarkedAlternativeItemForegroundColor">#b58900</Color>
|
||||
<Color x:Key="MarkedSelectedItemForegroundColor">#002b36</Color>
|
||||
|
||||
<Color x:Key="ErrorColor">#dc322f</Color>
|
||||
|
||||
<LinearGradientBrush x:Key="ContentSeparatorBrush">
|
||||
<GradientStop Offset="0" Color="#0093a1a1" />
|
||||
<GradientStop Offset="0.5" Color="#93a1a1" />
|
||||
<GradientStop Offset="1" Color="#0093a1a1" />
|
||||
</LinearGradientBrush>
|
||||
|
||||
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="AppBackgroundBrush"
|
||||
Color="{DynamicResource AppBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="ContainerBackgroundBrush"
|
||||
Color="{DynamicResource ContainerBackgroundColor}" />
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="ItemBackgroundBrush"
|
||||
Color="{DynamicResource ItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="AlternativeItemBackgroundBrush"
|
||||
Color="{DynamicResource AlternativeItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="SelectedItemBackgroundBrush"
|
||||
Color="{DynamicResource SelectedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedSelectedItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedSelectedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedAlternativeItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedAlternativeItemBackgroundColor}" />
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="ForegroundBrush"
|
||||
Color="{DynamicResource ForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="AccentForegroundBrush"
|
||||
Color="{DynamicResource AccentForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="LightForegroundBrush"
|
||||
Color="{DynamicResource LightForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="AlternativeItemForegroundBrush"
|
||||
Color="{DynamicResource AlternativeItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="SelectedItemForegroundBrush"
|
||||
Color="{DynamicResource SelectedItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedAlternativeItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedAlternativeItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedSelectedItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedSelectedItemForegroundColor}" />
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="ErrorBrush"
|
||||
Color="{DynamicResource ErrorColor}" />
|
||||
|
||||
|
||||
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="{DynamicResource SelectedItemBackgroundColor}" />
|
||||
|
||||
<converters:FormatSizeConverter x:Key="FormatSizeConverter"/>
|
||||
<converters:CompareConverter x:Key="EqualityConverter"/>
|
||||
<converters:CompareConverter x:Key="NotEqualsConverter" ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}"/>
|
||||
<converters:SplitStringConverter x:Key="SplitStringConverter" />
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToForegroundConverter"
|
||||
DefaultBrush="{StaticResource ForegroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"
|
||||
MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
|
||||
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}"
|
||||
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"/>
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToBackgroundConverter"
|
||||
DefaultBrush="{StaticResource ItemBackgroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
|
||||
MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
|
||||
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}"
|
||||
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"/>
|
||||
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator"/>
|
||||
<converters:ItemToImageConverter x:Key="ItemToImageConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNullConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNotNullConverter" Inverse="true"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Light"/>
|
||||
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.SmallText">
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView > ListBoxItem">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="ContextMenu">
|
||||
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
44
src/GuiApp/FileTime.Avalonia/App.axaml.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FileTime.App.Core;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Avalonia.Views;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
|
||||
namespace FileTime.Avalonia
|
||||
{
|
||||
public class App : global::Avalonia.Application
|
||||
{
|
||||
public static IServiceProvider ServiceProvider { get; private set; }
|
||||
|
||||
static App()
|
||||
{
|
||||
ServiceProvider ??= DependencyInjection
|
||||
.RegisterDefaultServices()
|
||||
.AddViewModels()
|
||||
.AddServices()
|
||||
.RegisterCommandHandlers()
|
||||
.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
ViewModel = ServiceProvider.GetService<MainPageViewModel>(),
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/GuiApp/FileTime.Avalonia/Application/AppState.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using MvvmGen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using FileTime.App.Core.Tab;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
[ViewModel]
|
||||
public partial class AppState
|
||||
{
|
||||
[Property]
|
||||
private ObservableCollection<TabContainer> _tabs = new();
|
||||
|
||||
[Property]
|
||||
private TabContainer _selectedTab;
|
||||
|
||||
[Property]
|
||||
private ViewMode _viewMode;
|
||||
|
||||
[Property]
|
||||
private string _rapidTravelText = "";
|
||||
|
||||
partial void OnInitialize()
|
||||
{
|
||||
_tabs.CollectionChanged += TabsChanged;
|
||||
}
|
||||
|
||||
private void TabsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
SelectedTab ??= Tabs.Count > 0 ? Tabs[0] : null;
|
||||
|
||||
List<TabContainer> itemsAdded = new();
|
||||
List<TabContainer> itemsRemoved = new();
|
||||
if (e.NewItems != null && e.OldItems != null)
|
||||
{
|
||||
itemsAdded.AddRange(e.NewItems.Cast<TabContainer>().Except(e.OldItems.Cast<TabContainer>()));
|
||||
itemsRemoved.AddRange(e.OldItems.Cast<TabContainer>().Except(e.NewItems.Cast<TabContainer>()));
|
||||
}
|
||||
else if (e.NewItems != null)
|
||||
{
|
||||
itemsAdded.AddRange(e.NewItems.Cast<TabContainer>());
|
||||
}
|
||||
else if (e.OldItems != null)
|
||||
{
|
||||
itemsRemoved.AddRange(e.OldItems.Cast<TabContainer>());
|
||||
}
|
||||
|
||||
foreach (var item in itemsAdded)
|
||||
{
|
||||
item.TabState.ItemMarked.Add(TabItemMarked);
|
||||
item.TabState.ItemUnmarked.Add(TabItemUnmarked);
|
||||
}
|
||||
|
||||
foreach (var item in itemsRemoved)
|
||||
{
|
||||
item.TabState.ItemMarked.Remove(TabItemMarked);
|
||||
item.TabState.ItemUnmarked.Remove(TabItemUnmarked);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TabItemMarked(TabState tabState, AbsolutePath item)
|
||||
{
|
||||
var tabContainer = Tabs.FirstOrDefault(t => t.TabState == tabState);
|
||||
if (tabContainer != null)
|
||||
{
|
||||
var item2 = (await tabContainer.CurrentLocation.GetItems()).FirstOrDefault(i => i.Item.FullName == item.Path);
|
||||
if (item2 != null)
|
||||
{
|
||||
item2.IsMarked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TabItemUnmarked(TabState tabState, AbsolutePath item)
|
||||
{
|
||||
var tabContainer = Tabs.FirstOrDefault(t => t.TabState == tabState);
|
||||
if (tabContainer != null)
|
||||
{
|
||||
var item2 = (await tabContainer.CurrentLocation.GetItems()).FirstOrDefault(i => i.Item.FullName == item.Path);
|
||||
if (item2 != null)
|
||||
{
|
||||
item2.IsMarked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
public interface INewItemProcessor
|
||||
{
|
||||
Task UpdateMarkedItems(ContainerViewModel containerViewModel);
|
||||
}
|
||||
}
|
||||
316
src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Components;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Local;
|
||||
using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.App.Core.Tab;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
[ViewModel]
|
||||
[Inject(typeof(ItemNameConverterService))]
|
||||
[Inject(typeof(LocalContentProvider))]
|
||||
[Inject(typeof(Tab))]
|
||||
public partial class TabContainer : INewItemProcessor
|
||||
{
|
||||
[Property]
|
||||
private TabState _tabState;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel _parent;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel _currentLocation;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel _childContainer;
|
||||
|
||||
[Property]
|
||||
private int _tabNumber;
|
||||
|
||||
[Property]
|
||||
private bool _isSelected;
|
||||
|
||||
private IItemViewModel? _selectedItem;
|
||||
|
||||
public IItemViewModel? SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
if (_selectedItem != value)// && value != null
|
||||
{
|
||||
_selectedItem = value;
|
||||
|
||||
OnPropertyChanged("SelectedItem");
|
||||
SelectedItemChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnInitialize()
|
||||
{
|
||||
_tabState = new TabState(Tab);
|
||||
}
|
||||
|
||||
public async Task Init(int tabNumber)
|
||||
{
|
||||
TabNumber = tabNumber;
|
||||
Tab.CurrentLocationChanged.Add(Tab_CurrentLocationChanged);
|
||||
Tab.CurrentSelectedItemChanged.Add(Tab_CurrentSelectedItemChanged);
|
||||
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
var parent = GenerateParent(currentLocation);
|
||||
CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService);
|
||||
await CurrentLocation.Init();
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||
Parent = parent;
|
||||
await Parent.Init();
|
||||
}
|
||||
else
|
||||
{
|
||||
Parent = null;
|
||||
}
|
||||
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
private ContainerViewModel? GenerateParent(IContainer? container, bool recursive = false)
|
||||
{
|
||||
var parentContainer = container?.GetParent();
|
||||
if (parentContainer == null) return null;
|
||||
var parentParent = recursive ? GenerateParent(parentContainer.GetParent(), recursive) : null;
|
||||
|
||||
var parent = new ContainerViewModel(this, parentParent, parentContainer, ItemNameConverterService);
|
||||
parentParent?.ChildrenToAdopt.Add(parent);
|
||||
return parent;
|
||||
}
|
||||
|
||||
private async Task Tab_CurrentLocationChanged(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
var parent = GenerateParent(currentLocation);
|
||||
CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService);
|
||||
await CurrentLocation.Init();
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||
Parent = parent;
|
||||
await Parent.Init();
|
||||
}
|
||||
else
|
||||
{
|
||||
Parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Tab_CurrentSelectedItemChanged(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
public async Task UpdateCurrentSelectedItem()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tabCurrentSelectenItem = await Tab.GetCurrentSelectedItem();
|
||||
IItemViewModel? currentSelectenItem = null;
|
||||
if (tabCurrentSelectenItem == null)
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
if (currentSelectenItem is ContainerViewModel currentSelectedContainer)
|
||||
{
|
||||
SelectedItem = currentSelectedContainer;
|
||||
ChildContainer = currentSelectedContainer;
|
||||
}
|
||||
else if (currentSelectenItem is ElementViewModel element)
|
||||
{
|
||||
SelectedItem = element;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
var items = await _currentLocation.GetItems();
|
||||
if (items != null && items.Count > 0)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
var isSelected = item == currentSelectenItem;
|
||||
item.IsSelected = isSelected;
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
var parent = item.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsSelected = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var child = item;
|
||||
while (child is ContainerViewModel containerViewModel && containerViewModel.Container.IsLoaded)
|
||||
{
|
||||
var activeChildItem = await Tab.GetItemByLastPath(containerViewModel.Container);
|
||||
child = (await containerViewModel.GetItems()).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
if (child != null)
|
||||
{
|
||||
child.IsSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//INFO collection modified exception on: child = (await containerViewModel.GetItems()).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
//TODO: handle or error message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parent = _currentLocation;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsSelected = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//INFO collection modified exception on: currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
//TODO: handle or error message
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectedItemChanged()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Tab.SetCurrentSelectedItem(SelectedItem?.Item);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public async Task Open()
|
||||
{
|
||||
if (ChildContainer != null)
|
||||
{
|
||||
await Tab.Open();
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GoUp()
|
||||
{
|
||||
await Tab.GoUp();
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
public async Task MoveCursorDown()
|
||||
{
|
||||
await Tab.SelectNextItem();
|
||||
}
|
||||
|
||||
public async Task MoveCursorDownPage()
|
||||
{
|
||||
await Tab.SelectNextItem(10);
|
||||
}
|
||||
|
||||
public async Task MoveCursorUp()
|
||||
{
|
||||
await Tab.SelectPreviousItem();
|
||||
}
|
||||
|
||||
public async Task MoveCursorUpPage()
|
||||
{
|
||||
await Tab.SelectPreviousItem(10);
|
||||
}
|
||||
|
||||
public async Task MoveCursorToFirst()
|
||||
{
|
||||
await Tab.SelectFirstItem();
|
||||
}
|
||||
|
||||
public async Task MoveCursorToLast()
|
||||
{
|
||||
await Tab.SelectLastItem();
|
||||
}
|
||||
|
||||
public async Task GotToProvider()
|
||||
{
|
||||
await Tab.GoToProvider();
|
||||
}
|
||||
|
||||
public async Task GotToRoot()
|
||||
{
|
||||
await Tab.GoToRoot();
|
||||
}
|
||||
|
||||
public async Task GotToHome()
|
||||
{
|
||||
var path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile).Replace(Path.DirectorySeparatorChar, Constants.SeparatorChar);
|
||||
var resolvedPath = await LocalContentProvider.GetByPath(path);
|
||||
if (resolvedPath is IContainer homeFolder)
|
||||
{
|
||||
await Tab.OpenContainer(homeFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateContainer(string name)
|
||||
{
|
||||
(await Tab.GetCurrentLocation())?.CreateContainer(name);
|
||||
}
|
||||
|
||||
public async Task CreateElement(string name)
|
||||
{
|
||||
(await Tab.GetCurrentLocation())?.CreateElement(name);
|
||||
}
|
||||
|
||||
public async Task OpenContainer(IContainer container)
|
||||
{
|
||||
await Tab.OpenContainer(container);
|
||||
}
|
||||
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
await _tabState.MakrCurrentItem();
|
||||
}
|
||||
|
||||
public async Task UpdateMarkedItems(ContainerViewModel containerViewModel)
|
||||
{
|
||||
if (containerViewModel == CurrentLocation && containerViewModel.Container.IsLoaded)
|
||||
{
|
||||
var selectedItems = TabState.GetCurrentMarkedItems(containerViewModel.Container);
|
||||
|
||||
foreach (var item in await containerViewModel.GetItems())
|
||||
{
|
||||
item.IsMarked = selectedItems.Any(c => c.Path == item.Item.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/GuiApp/FileTime.Avalonia/Application/ViewMode.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
public enum ViewMode
|
||||
{
|
||||
Default,
|
||||
RapidTravel
|
||||
}
|
||||
}
|
||||
BIN
src/GuiApp/FileTime.Avalonia/Assets/avalonia-logo.ico
Normal file
|
After Width: | Height: | Size: 172 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M103.3 344.3c-6.5-14.2-6.9-18.3 7.4-23.1 25.6-8 8 9.2 43.2 49.2h.3v-93.9c1.2-50.2 44-92.2 97.7-92.2 53.9 0 97.7 43.5 97.7 96.8 0 63.4-60.8 113.2-128.5 93.3-10.5-4.2-2.1-31.7 8.5-28.6 53 0 89.4-10.1 89.4-64.4 0-61-77.1-89.6-116.9-44.6-23.5 26.4-17.6 42.1-17.6 157.6 50.7 31 118.3 22 160.4-20.1 24.8-24.8 38.5-58 38.5-93 0-35.2-13.8-68.2-38.8-93.3-24.8-24.8-57.8-38.5-93.3-38.5s-68.8 13.8-93.5 38.5c-.3.3-16 16.5-21.2 23.9l-.5.6c-3.3 4.7-6.3 9.1-20.1 6.1-6.9-1.7-14.3-5.8-14.3-11.8V20c0-5 3.9-10.5 10.5-10.5h241.3c8.3 0 8.3 11.6 8.3 15.1 0 3.9 0 15.1-8.3 15.1H130.3v132.9h.3c104.2-109.8 282.8-36 282.8 108.9 0 178.1-244.8 220.3-310.1 62.8zm63.3-260.8c-.5 4.2 4.6 24.5 14.6 20.6C306 56.6 384 144.5 390.6 144.5c4.8 0 22.8-15.3 14.3-22.8-93.2-89-234.5-57-238.3-38.2zM393 414.7C283 524.6 94 475.5 61 310.5c0-12.2-30.4-7.4-28.9 3.3 24 173.4 246 256.9 381.6 121.3 6.9-7.8-12.6-28.4-20.7-20.4zM213.6 306.6c0 4 4.3 7.3 5.5 8.5 3 3 6.1 4.4 8.5 4.4 3.8 0 2.6.2 22.3-19.5 19.6 19.3 19.1 19.5 22.3 19.5 5.4 0 18.5-10.4 10.7-18.2L265.6 284l18.2-18.2c6.3-6.8-10.1-21.8-16.2-15.7L249.7 268c-18.6-18.8-18.4-19.5-21.5-19.5-5 0-18 11.7-12.4 17.3L234 284c-18.1 17.9-20.4 19.2-20.4 22.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M423.9 255.8L411 413.1c-3.3 40.7-63.9 35.1-60.6-4.9l10-122.5-41.1 2.3c10.1 20.7 15.8 43.9 15.8 68.5 0 41.2-16.1 78.7-42.3 106.5l-39.3-39.3c57.9-63.7 13.1-167.2-74-167.2-25.9 0-49.5 9.9-67.2 26L73 243.2c22-20.7 50.1-35.1 81.4-40.2l75.3-85.7-42.6-24.8-51.6 46c-30 26.8-70.6-18.5-40.5-45.4l68-60.7c9.8-8.8 24.1-10.2 35.5-3.6 0 0 139.3 80.9 139.5 81.1 16.2 10.1 20.7 36 6.1 52.6L285.7 229l106.1-5.9c18.5-1.1 33.6 14.4 32.1 32.7zm-64.9-154c28.1 0 50.9-22.8 50.9-50.9C409.9 22.8 387.1 0 359 0c-28.1 0-50.9 22.8-50.9 50.9 0 28.1 22.8 50.9 50.9 50.9zM179.6 456.5c-80.6 0-127.4-90.6-82.7-156.1l-39.7-39.7C36.4 287 24 320.3 24 356.4c0 130.7 150.7 201.4 251.4 122.5l-39.7-39.7c-16 10.9-35.3 17.3-56.1 17.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 955 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M322.1 252v-1l-51.2-65.8s-12 1.6-25 15.1c-9 9.3-242.1 239.1-243.4 240.9-7 10 1.6 6.8 15.7 1.7.8 0 114.5-36.6 114.5-36.6.5-.6-.1-.1.6-.6-.4-5.1-.8-26.2-1-27.7-.6-5.2 2.2-6.9 7-8.9l92.6-33.8c.6-.8 88.5-81.7 90.2-83.3zm160.1 120.1c13.3 16.1 20.7 13.3 30.8 9.3 3.2-1.2 115.4-47.6 117.8-48.9 8-4.3-1.7-16.7-7.2-23.4-2.1-2.5-205.1-245.6-207.2-248.3-9.7-12.2-14.3-12.9-38.4-12.8-10.2 0-106.8.5-116.5.6-19.2.1-32.9-.3-19.2 16.9C250 75 476.5 365.2 482.2 372.1zm152.7 1.6c-2.3-.3-24.6-4.7-38-7.2 0 0-115 50.4-117.5 51.6-16 7.3-26.9-3.2-36.7-14.6l-57.1-74c-5.4-.9-60.4-9.6-65.3-9.3-3.1.2-9.6.8-14.4 2.9-4.9 2.1-145.2 52.8-150.2 54.7-5.1 2-11.4 3.6-11.1 7.6.2 2.5 2 2.6 4.6 3.5 2.7.8 300.9 67.6 308 69.1 15.6 3.3 38.5 10.5 53.6 1.7 2.1-1.2 123.8-76.4 125.8-77.8 5.4-4 4.3-6.8-1.7-8.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M357.45 468.2c-1.2-7.7-1.3-7.6-9.6-7.6-99.8.2-111.8-2.4-112.7-2.6-12.3-1.7-20.6-10.5-21-23.1-.1-1.6-.2-71.6-1-129.1-.1-4.7 1.6-6.4 5.9-7.5 12.5-3 24.9-6.1 37.3-9.7 4.3-1.3 6.8-.2 8.4 3.5 4.5 10.3 8.8 20.6 13.2 30.9 1.6 3.7.1 4.4-3.4 4.4-10-.2-20-.1-30.4-.1v27h116c-1.4-9.5-2.7-18.1-4-27.5-7 0-13.8.4-20.4-.1-22.6-1.6-18.3-4.4-84-158.6-8.8-20.1-27.9-62.1-36.5-89.2-4.4-14 5.5-25.4 18.9-26.6 18.6-1.7 37.5-1.6 56.2-2 20.6-.4 41.2-.4 61.8-.5 3.1 0 4-1.4 4.3-4.3 1.2-9.8 2.7-19.5 4-29.2.8-5.3 1.6-10.7 2.4-16.1L23.75 0c-3.6 0-5.3 1.1-4.6 5.3 2.2 13.2-.8.8 6.4 45.3 63.4 0 71.8.9 101.8.5 12.3-.2 37 3.5 37.7 22.1.4 11.4-1.1 11.3-32.6 87.4-53.8 129.8-50.7 120.3-67.3 161-1.7 4.1-3.6 5.2-7.6 5.2-8.5-.2-17-.3-25.4.1-1.9.1-5.2 1.8-5.5 3.2-1.5 8.1-2.2 16.3-3.2 24.9h114.3v-27.6c-6.9 0-33.5.4-35.3-2.9 5.3-12.3 10.4-24.4 15.7-36.7 16.3 4 31.9 7.8 47.6 11.7 3.4.9 4.6 3 4.6 6.8-.1 42.9.1 85.9.2 128.8 0 10.2-5.5 19.1-14.9 23.1-6.5 2.7-3.3 3.4-121.4 2.4-5.3 0-7.1 2-7.6 6.8-1.5 12.9-2.9 25.9-5 38.8-.8 5 1.3 5.7 5.3 5.7 183.2.6-30.7 0 337.1 0-2.5-15-4.4-29.4-6.6-43.7zm-174.9-205.7c-13.3-4.2-26.6-8.2-39.9-12.5a44.53 44.53 0 0 1-5.8-2.9c17.2-44.3 34.2-88.1 51.3-132.1 7.5 2.4 7.9-.8 9.4 0 9.3 22.5 18.1 60.1 27 82.8 6.6 16.7 13 33.5 19.7 50.9a35.78 35.78 0 0 1-3.9 2.1c-13.1 3.9-26.4 7.5-39.4 11.7a27.66 27.66 0 0 1-18.4 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M248 167.5l64.9 98.8H183.1l64.9-98.8zM496 256c0 136.9-111.1 248-248 248S0 392.9 0 256 111.1 8 248 8s248 111.1 248 248zm-99.8 82.7L248 115.5 99.8 338.7h30.4l33.6-51.7h168.6l33.6 51.7h30.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M482.1 32H28.7C5.8 32 0 37.9 0 60.9v390.2C0 474.4 5.8 480 28.7 480h453.4c24.4 0 29.9-5.2 29.9-29.7V62.2c0-24.6-5.4-30.2-29.9-30.2zM178.4 220.3c-27.5-20.2-72.1-8.7-84.2 23.4-4.3 11.1-9.3 9.5-17.5 8.3-9.7-1.5-17.2-3.2-22.5-5.5-28.8-11.4 8.6-55.3 24.9-64.3 41.1-21.4 83.4-22.2 125.3-4.8 40.9 16.8 34.5 59.2 34.5 128.5 2.7 25.8-4.3 58.3 9.3 88.8 1.9 4.4.4 7.9-2.7 10.7-8.4 6.7-39.3 2.2-46.6-7.4-1.9-2.2-1.8-3.6-3.9-6.2-3.6-3.9-7.3-2.2-11.9 1-57.4 36.4-140.3 21.4-147-43.3-3.1-29.3 12.4-57.1 39.6-71 38.2-19.5 112.2-11.8 114-30.9 1.1-10.2-1.9-20.1-11.3-27.3zm286.7 222c0 15.1-11.1 9.9-17.8 9.9H52.4c-7.4 0-18.2 4.8-17.8-10.7.4-13.9 10.5-9.1 17.1-9.1 132.3-.4 264.5-.4 396.8 0 6.8 0 16.6-4.4 16.6 9.9zm3.8-340.5v291c0 5.7-.7 13.9-8.1 13.9-12.4-.4-27.5 7.1-36.1-5.6-5.8-8.7-7.8-4-12.4-1.2-53.4 29.7-128.1 7.1-144.4-85.2-6.1-33.4-.7-67.1 15.7-100 11.8-23.9 56.9-76.1 136.1-30.5v-71c0-26.2-.1-26.2 26-26.2 3.1 0 6.6.4 9.7 0 10.1-.8 13.6 4.4 13.6 14.3-.1.2-.1.3-.1.5zm-51.5 232.3c-19.5 47.6-72.9 43.3-90 5.2-15.1-33.3-15.5-68.2.4-101.5 16.3-34.1 59.7-35.7 81.5-4.8 20.6 28.8 14.9 84.6 8.1 101.1zm-294.8 35.3c-7.5-1.3-33-3.3-33.7-27.8-.4-13.9 7.8-23 19.8-25.8 24.4-5.9 49.3-9.9 73.7-14.7 8.9-2 7.4 4.4 7.8 9.5 1.4 33-26.1 59.2-67.6 58.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M159.7 237.4C108.4 308.3 43.1 348.2 14 326.6-15.2 304.9 2.8 230 54.2 159.1c51.3-70.9 116.6-110.8 145.7-89.2 29.1 21.6 11.1 96.6-40.2 167.5zm351.2-57.3C437.1 303.5 319 367.8 246.4 323.7c-25-15.2-41.3-41.2-49-73.8-33.6 64.8-92.8 113.8-164.1 133.2 49.8 59.3 124.1 96.9 207 96.9 150 0 271.6-123.1 271.6-274.9.1-8.5-.3-16.8-1-25z"/></svg>
|
||||
|
After Width: | Height: | Size: 584 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M224 373.12c-25.24-31.67-40.08-59.43-45-83.18-22.55-88 112.61-88 90.06 0-5.45 24.25-20.29 52-45 83.18zm138.15 73.23c-42.06 18.31-83.67-10.88-119.3-50.47 103.9-130.07 46.11-200-18.85-200-54.92 0-85.16 46.51-73.28 100.5 6.93 29.19 25.23 62.39 54.43 99.5-32.53 36.05-60.55 52.69-85.15 54.92-50 7.43-89.11-41.06-71.3-91.09 15.1-39.16 111.72-231.18 115.87-241.56 15.75-30.07 25.56-57.4 59.38-57.4 32.34 0 43.4 25.94 60.37 59.87 36 70.62 89.35 177.48 114.84 239.09 13.17 33.07-1.37 71.29-37.01 86.64zm47-136.12C280.27 35.93 273.13 32 224 32c-45.52 0-64.87 31.67-84.66 72.79C33.18 317.1 22.89 347.19 22 349.81-3.22 419.14 48.74 480 111.63 480c21.71 0 60.61-6.06 112.37-62.4 58.68 63.78 101.26 62.4 112.37 62.4 62.89.05 114.85-60.86 89.61-130.19.02-3.89-16.82-38.9-16.82-39.58z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M229.3 182.6c-49.3 0-89.2 39.9-89.2 89.2 0 49.3 39.9 89.2 89.2 89.2s89.2-39.9 89.2-89.2c0-49.3-40-89.2-89.2-89.2zm62.7 56.6l-58.9 30.6c-1.8.9-3.8-.4-3.8-2.3V201c0-1.5 1.3-2.7 2.7-2.6 26.2 1 48.9 15.7 61.1 37.1.7 1.3.2 3-1.1 3.7zM389.1 32H58.9C26.4 32 0 58.4 0 90.9V421c0 32.6 26.4 59 58.9 59H389c32.6 0 58.9-26.4 58.9-58.9V90.9C448 58.4 421.6 32 389.1 32zm-202.6 84.7c0-10.8 8.7-19.5 19.5-19.5h45.3c10.8 0 19.5 8.7 19.5 19.5v15.4c0 1.8-1.7 3-3.3 2.5-12.3-3.4-25.1-5.1-38.1-5.1-13.5 0-26.7 1.8-39.4 5.5-1.7.5-3.4-.8-3.4-2.5v-15.8zm-84.4 37l9.2-9.2c7.6-7.6 19.9-7.6 27.5 0l7.7 7.7c1.1 1.1 1 3-.3 4-6.2 4.5-12.1 9.4-17.6 14.9-5.4 5.4-10.4 11.3-14.8 17.4-1 1.3-2.9 1.5-4 .3l-7.7-7.7c-7.6-7.5-7.6-19.8 0-27.4zm127.2 244.8c-70 0-126.6-56.7-126.6-126.6s56.7-126.6 126.6-126.6c70 0 126.6 56.6 126.6 126.6 0 69.8-56.7 126.6-126.6 126.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M377.74 32H70.26C31.41 32 0 63.41 0 102.26v307.48C0 448.59 31.41 480 70.26 480h307.48c38.52 0 69.76-31.08 70.26-69.6-45.96-25.62-110.59-60.34-171.6-88.44-32.07 43.97-84.14 81-148.62 81-70.59 0-93.73-45.3-97.04-76.37-3.97-39.01 14.88-81.5 99.52-81.5 35.38 0 79.35 10.25 127.13 24.96 16.53-30.09 26.45-60.34 26.45-60.34h-178.2v-16.7h92.08v-31.24H88.28v-19.01h109.44V92.34h50.92v50.42h109.44v19.01H248.63v31.24h88.77s-15.21 46.62-38.35 90.92c48.93 16.7 100.01 36.04 148.62 52.74V102.26C447.83 63.57 416.43 32 377.74 32zM47.28 322.95c.99 20.17 10.25 53.73 69.93 53.73 52.07 0 92.58-39.68 117.87-72.9-44.63-18.68-84.48-31.41-109.44-31.41-67.45 0-79.35 33.06-78.36 50.58z"/></svg>
|
||||
|
After Width: | Height: | Size: 925 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M14 325.3c2.3-4.2 5.2-4.9 9.7-2.5 10.4 5.6 20.6 11.4 31.2 16.7a595.88 595.88 0 0 0 127.4 46.3 616.61 616.61 0 0 0 63.2 11.8 603.33 603.33 0 0 0 95 5.2c17.4-.4 34.8-1.8 52.1-3.8a603.66 603.66 0 0 0 163.3-42.8c2.9-1.2 5.9-2 9.1-1.2 6.7 1.8 9 9 4.1 13.9a70 70 0 0 1-9.6 7.4c-30.7 21.1-64.2 36.4-99.6 47.9a473.31 473.31 0 0 1-75.1 17.6 431 431 0 0 1-53.2 4.8 21.3 21.3 0 0 0-2.5.3H308a21.3 21.3 0 0 0-2.5-.3c-3.6-.2-7.2-.3-10.7-.4a426.3 426.3 0 0 1-50.4-5.3A448.4 448.4 0 0 1 164 420a443.33 443.33 0 0 1-145.6-87c-1.8-1.6-3-3.8-4.4-5.7zM172 65.1l-4.3.6a80.92 80.92 0 0 0-38 15.1c-2.4 1.7-4.6 3.5-7.1 5.4a4.29 4.29 0 0 1-.4-1.4c-.4-2.7-.8-5.5-1.3-8.2-.7-4.6-3-6.6-7.6-6.6h-11.5c-6.9 0-8.2 1.3-8.2 8.2v209.3c0 1 0 2 .1 3 .2 3 2 4.9 4.9 5 7 .1 14.1.1 21.1 0 2.9 0 4.7-2 5-5 .1-1 .1-2 .1-3v-72.4c1.1.9 1.7 1.4 2.2 1.9 17.9 14.9 38.5 19.8 61 15.4 20.4-4 34.6-16.5 43.8-34.9 7-13.9 9.9-28.7 10.3-44.1.5-17.1-1.2-33.9-8.1-49.8-8.5-19.6-22.6-32.5-43.9-36.9-3.2-.7-6.5-1-9.8-1.5-2.8-.1-5.5-.1-8.3-.1zM124.6 107a3.48 3.48 0 0 1 1.7-3.3c13.7-9.5 28.8-14.5 45.6-13.2 14.9 1.1 27.1 8.4 33.5 25.9 3.9 10.7 4.9 21.8 4.9 33 0 10.4-.8 20.6-4 30.6-6.8 21.3-22.4 29.4-42.6 28.5-14-.6-26.2-6-37.4-13.9a3.57 3.57 0 0 1-1.7-3.3c.1-14.1 0-28.1 0-42.2s.1-28 0-42.1zm205.7-41.9c-1 .1-2 .3-2.9.4a148 148 0 0 0-28.9 4.1c-6.1 1.6-12 3.8-17.9 5.8-3.6 1.2-5.4 3.8-5.3 7.7.1 3.3-.1 6.6 0 9.9.1 4.8 2.1 6.1 6.8 4.9 7.8-2 15.6-4.2 23.5-5.7 12.3-2.3 24.7-3.3 37.2-1.4 6.5 1 12.6 2.9 16.8 8.4 3.7 4.8 5.1 10.5 5.3 16.4.3 8.3.2 16.6.3 24.9a7.84 7.84 0 0 1-.2 1.4c-.5-.1-.9 0-1.3-.1a180.56 180.56 0 0 0-32-4.9c-11.3-.6-22.5.1-33.3 3.9-12.9 4.5-23.3 12.3-29.4 24.9-4.7 9.8-5.4 20.2-3.9 30.7 2 14 9 24.8 21.4 31.7 11.9 6.6 24.8 7.4 37.9 5.4 15.1-2.3 28.5-8.7 40.3-18.4a7.36 7.36 0 0 1 1.6-1.1c.6 3.8 1.1 7.4 1.8 11 .6 3.1 2.5 5.1 5.4 5.2 5.4.1 10.9.1 16.3 0a4.84 4.84 0 0 0 4.8-4.7 26.2 26.2 0 0 0 .1-2.8v-106a80 80 0 0 0-.9-12.9c-1.9-12.9-7.4-23.5-19-30.4-6.7-4-14.1-6-21.8-7.1-3.6-.5-7.2-.8-10.8-1.3-3.9.1-7.9.1-11.9.1zm35 127.7a3.33 3.33 0 0 1-1.5 3c-11.2 8.1-23.5 13.5-37.4 14.9-5.7.6-11.4.4-16.8-1.8a20.08 20.08 0 0 1-12.4-13.3 32.9 32.9 0 0 1-.1-19.4c2.5-8.3 8.4-13 16.4-15.6a61.33 61.33 0 0 1 24.8-2.2c8.4.7 16.6 2.3 25 3.4 1.6.2 2.1 1 2.1 2.6-.1 4.8 0 9.5 0 14.3s-.2 9.4-.1 14.1zm259.9 129.4c-1-5-4.8-6.9-9.1-8.3a88.42 88.42 0 0 0-21-3.9 147.32 147.32 0 0 0-39.2 1.9c-14.3 2.7-27.9 7.3-40 15.6a13.75 13.75 0 0 0-3.7 3.5 5.11 5.11 0 0 0-.5 4c.4 1.5 2.1 1.9 3.6 1.8a16.2 16.2 0 0 0 2.2-.1c7.8-.8 15.5-1.7 23.3-2.5 11.4-1.1 22.9-1.8 34.3-.9a71.64 71.64 0 0 1 14.4 2.7c5.1 1.4 7.4 5.2 7.6 10.4.4 8-1.4 15.7-3.5 23.3-4.1 15.4-10 30.3-15.8 45.1a17.6 17.6 0 0 0-1 3c-.5 2.9 1.2 4.8 4.1 4.1a10.56 10.56 0 0 0 4.8-2.5 145.91 145.91 0 0 0 12.7-13.4c12.8-16.4 20.3-35.3 24.7-55.6.8-3.6 1.4-7.3 2.1-10.9v-17.3zM493.1 199q-19.35-53.55-38.7-107.2c-2-5.7-4.2-11.3-6.3-16.9-1.1-2.9-3.2-4.8-6.4-4.8-7.6-.1-15.2-.2-22.9-.1-2.5 0-3.7 2-3.2 4.5a43.1 43.1 0 0 0 1.9 6.1q29.4 72.75 59.1 145.5c1.7 4.1 2.1 7.6.2 11.8-3.3 7.3-5.9 15-9.3 22.3-3 6.5-8 11.4-15.2 13.3a42.13 42.13 0 0 1-15.4 1.1c-2.5-.2-5-.8-7.5-1-3.4-.2-5.1 1.3-5.2 4.8q-.15 5 0 9.9c.1 5.5 2 8 7.4 8.9a108.18 108.18 0 0 0 16.9 2c17.1.4 30.7-6.5 39.5-21.4a131.63 131.63 0 0 0 9.2-18.4q35.55-89.7 70.6-179.6a26.62 26.62 0 0 0 1.6-5.5c.4-2.8-.9-4.4-3.7-4.4-6.6-.1-13.3 0-19.9 0a7.54 7.54 0 0 0-7.7 5.2c-.5 1.4-1.1 2.7-1.6 4.1l-34.8 100c-2.5 7.2-5.1 14.5-7.7 22.2-.4-1.1-.6-1.7-.9-2.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M257.2 162.7c-48.7 1.8-169.5 15.5-169.5 117.5 0 109.5 138.3 114 183.5 43.2 6.5 10.2 35.4 37.5 45.3 46.8l56.8-56S341 288.9 341 261.4V114.3C341 89 316.5 32 228.7 32 140.7 32 94 87 94 136.3l73.5 6.8c16.3-49.5 54.2-49.5 54.2-49.5 40.7-.1 35.5 29.8 35.5 69.1zm0 86.8c0 80-84.2 68-84.2 17.2 0-47.2 50.5-56.7 84.2-57.8v40.6zm136 163.5c-7.7 10-70 67-174.5 67S34.2 408.5 9.7 379c-6.8-7.7 1-11.3 5.5-8.3C88.5 415.2 203 488.5 387.7 401c7.5-3.7 13.3 2 5.5 12zm39.8 2.2c-6.5 15.8-16 26.8-21.2 31-5.5 4.5-9.5 2.7-6.5-3.8s19.3-46.5 12.7-55c-6.5-8.3-37-4.3-48-3.2-10.8 1-13 2-14-.3-2.3-5.7 21.7-15.5 37.5-17.5 15.7-1.8 41-.8 46 5.7 3.7 5.1 0 27.1-6.5 43.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 900 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M240.1 32c-61.9 0-131.5 16.9-184.2 55.4-5.1 3.1-9.1 9.2-7.2 19.4 1.1 5.1 5.1 27.4 10.2 39.6 4.1 10.2 14.2 10.2 20.3 6.1 32.5-22.3 96.5-47.7 152.3-47.7 57.9 0 58.9 28.4 58.9 73.1v38.5C203 227.7 78.2 251 46.7 264.2 11.2 280.5 16.3 357.7 16.3 376s15.2 104 124.9 104c47.8 0 113.7-20.7 153.3-42.1v25.4c0 3 2.1 8.2 6.1 9.1 3.1 1 50.7 2 59.9 2s62.5.3 66.5-.7c4.1-1 5.1-6.1 5.1-9.1V168c-.1-80.3-57.9-136-192-136zm50.2 348c-21.4 13.2-48.7 24.4-79.1 24.4-52.8 0-58.9-33.5-59-44.7 0-12.2-3-42.7 18.3-52.9 24.3-13.2 75.1-29.4 119.8-33.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 785 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M420.55,301.93a24,24,0,1,1,24-24,24,24,0,0,1-24,24m-265.1,0a24,24,0,1,1,24-24,24,24,0,0,1-24,24m273.7-144.48,47.94-83a10,10,0,1,0-17.27-10h0l-48.54,84.07a301.25,301.25,0,0,0-246.56,0L116.18,64.45a10,10,0,1,0-17.27,10h0l47.94,83C64.53,202.22,8.24,285.55,0,384H576c-8.24-98.45-64.54-181.78-146.85-226.55"/></svg>
|
||||
|
After Width: | Height: | Size: 561 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M347.1 215.4c11.7-32.6 45.4-126.9 45.4-157.1 0-26.6-15.7-48.9-43.7-48.9-44.6 0-84.6 131.7-97.1 163.1C242 144 196.6 0 156.6 0c-31.1 0-45.7 22.9-45.7 51.7 0 35.3 34.2 126.8 46.6 162-6.3-2.3-13.1-4.3-20-4.3-23.4 0-48.3 29.1-48.3 52.6 0 8.9 4.9 21.4 8 29.7-36.9 10-51.1 34.6-51.1 71.7C46 435.6 114.4 512 210.6 512c118 0 191.4-88.6 191.4-202.9 0-43.1-6.9-82-54.9-93.7zM311.7 108c4-12.3 21.1-64.3 37.1-64.3 8.6 0 10.9 8.9 10.9 16 0 19.1-38.6 124.6-47.1 148l-34-6 33.1-93.7zM142.3 48.3c0-11.9 14.5-45.7 46.3 47.1l34.6 100.3c-15.6-1.3-27.7-3-35.4 1.4-10.9-28.8-45.5-119.7-45.5-148.8zM140 244c29.3 0 67.1 94.6 67.1 107.4 0 5.1-4.9 11.4-10.6 11.4-20.9 0-76.9-76.9-76.9-97.7.1-7.7 12.7-21.1 20.4-21.1zm184.3 186.3c-29.1 32-66.3 48.6-109.7 48.6-59.4 0-106.3-32.6-128.9-88.3-17.1-43.4 3.8-68.3 20.6-68.3 11.4 0 54.3 60.3 54.3 73.1 0 4.9-7.7 8.3-11.7 8.3-16.1 0-22.4-15.5-51.1-51.4-29.7 29.7 20.5 86.9 58.3 86.9 26.1 0 43.1-24.2 38-42 3.7 0 8.3.3 11.7-.6 1.1 27.1 9.1 59.4 41.7 61.7 0-.9 2-7.1 2-7.4 0-17.4-10.6-32.6-10.6-50.3 0-28.3 21.7-55.7 43.7-71.7 8-6 17.7-9.7 27.1-13.1 9.7-3.7 20-8 27.4-15.4-1.1-11.2-5.7-21.1-16.9-21.1-27.7 0-120.6 4-120.6-39.7 0-6.7.1-13.1 17.4-13.1 32.3 0 114.3 8 138.3 29.1 18.1 16.1 24.3 113.2-31 174.7zm-98.6-126c9.7 3.1 19.7 4 29.7 6-7.4 5.4-14 12-20.3 19.1-2.8-8.5-6.2-16.8-9.4-25.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M640 238.2l-3.2 28.2-34.5 2.3-2 18.1 34.5-2.3-3.2 28.2-34.4 2.2-2.3 20.1 34.4-2.2-3 26.1-64.7 4.1 12.7-113.2L527 365.2l-31.9 2-23.8-117.8 30.3-2 13.6 79.4 31.7-82.4 93.1-6.2zM426.8 371.5l28.3-1.8L468 249.6l-28.4 1.9-12.8 120zM162 388.1l-19.4-36-3.5 37.4-28.2 1.7 2.7-29.1c-11 18-32 34.3-56.9 35.8C23.9 399.9-3 377 .3 339.7c2.6-29.3 26.7-62.8 67.5-65.4 37.7-2.4 47.6 23.2 51.3 28.8l2.8-30.8 38.9-2.5c20.1-1.3 38.7 3.7 42.5 23.7l2.6-26.6 64.8-4.2-2.7 27.9-36.4 2.4-1.7 17.9 36.4-2.3-2.7 27.9-36.4 2.3-1.9 19.9 36.3-2.3-2.1 20.8 55-117.2 23.8-1.6L370.4 369l8.9-85.6-22.3 1.4 2.9-27.9 75-4.9-3 28-24.3 1.6-9.7 91.9-58 3.7-4.3-15.6-39.4 2.5-8 16.3-126.2 7.7zm-44.3-70.2l-26.4 1.7C84.6 307.2 76.9 303 65 303.8c-19 1.2-33.3 17.5-34.6 33.3-1.4 16 7.3 32.5 28.7 31.2 12.8-.8 21.3-8.6 28.9-18.9l27-1.7 2.7-29.8zm56.1-7.7c1.2-12.9-7.6-13.6-26.1-12.4l-2.7 28.5c14.2-.9 27.5-2.1 28.8-16.1zm21.1 70.8l5.8-60c-5 13.5-14.7 21.1-27.9 26.6l22.1 33.4zm135.4-45l-7.9-37.8-15.8 39.3 23.7-1.5zm-170.1-74.6l-4.3-17.5-39.6 2.6-8.1 18.2-31.9 2.1 57-121.9 23.9-1.6 30.7 102 9.9-104.7 27-1.8 37.8 63.6 6.5-66.6 28.5-1.9-4 41.2c7.4-13.5 22.9-44.7 63.6-47.5 40.5-2.8 52.4 29.3 53.4 30.3l3.3-32 39.3-2.7c12.7-.9 27.8.3 36.3 9.7l-4.4-11.9 32.2-2.2 12.9 43.2 23-45.7 31-2.2-43.6 78.4-4.8 44.3-28.4 1.9 4.8-44.3-15.8-43c1 22.3-9.2 40.1-32 49.6l25.2 38.8-36.4 2.4-19.2-36.8-4 38.3-28.4 1.9 3.3-31.5c-6.7 9.3-19.7 35.4-59.6 38-26.2 1.7-45.6-10.3-55.4-39.2l-4 40.3-25 1.6-37.6-63.3-6.3 66.2-56.8 3.7zm276.6-82.1c10.2-.7 17.5-2.1 21.6-4.3 4.5-2.4 7-6.4 7.6-12.1.6-5.3-.6-8.8-3.4-10.4-3.6-2.1-10.6-2.8-22.9-2l-2.9 28.8zM327.7 214c5.6 5.9 12.7 8.5 21.3 7.9 4.7-.3 9.1-1.8 13.3-4.1 5.5-3 10.6-8 15.1-14.3l-34.2 2.3 2.4-23.9 63.1-4.3 1.2-12-31.2 2.1c-4.1-3.7-7.8-6.6-11.1-8.1-4-1.7-8.1-2.8-12.2-2.5-8 .5-15.3 3.6-22 9.2-7.7 6.4-12 14.5-12.9 24.4-1.1 9.6 1.4 17.3 7.2 23.3zm-201.3 8.2l23.8-1.6-8.3-37.6-15.5 39.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M185.7 268.1h76.2l-38.1-91.6-38.1 91.6zM223.8 32L16 106.4l31.8 275.7 176 97.9 176-97.9 31.8-275.7zM354 373.8h-48.6l-26.2-65.4H168.6l-26.2 65.4H93.7L223.8 81.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM127 384.5c-5.5 9.6-17.8 12.8-27.3 7.3-9.6-5.5-12.8-17.8-7.3-27.3l14.3-24.7c16.1-4.9 29.3-1.1 39.6 11.4L127 384.5zm138.9-53.9H84c-11 0-20-9-20-20s9-20 20-20h51l65.4-113.2-20.5-35.4c-5.5-9.6-2.2-21.8 7.3-27.3 9.6-5.5 21.8-2.2 27.3 7.3l8.9 15.4 8.9-15.4c5.5-9.6 17.8-12.8 27.3-7.3 9.6 5.5 12.8 17.8 7.3 27.3l-85.8 148.6h62.1c20.2 0 31.5 23.7 22.7 40zm98.1 0h-29l19.6 33.9c5.5 9.6 2.2 21.8-7.3 27.3-9.6 5.5-21.8 2.2-27.3-7.3-32.9-56.9-57.5-99.7-74-128.1-16.7-29-4.8-58 7.1-67.8 13.1 22.7 32.7 56.7 58.9 102h52c11 0 20 9 20 20 0 11.1-9 20-20 20z"/></svg>
|
||||
|
After Width: | Height: | Size: 906 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M255.9 120.9l9.1-15.7c5.6-9.8 18.1-13.1 27.9-7.5 9.8 5.6 13.1 18.1 7.5 27.9l-87.5 151.5h63.3c20.5 0 32 24.1 23.1 40.8H113.8c-11.3 0-20.4-9.1-20.4-20.4 0-11.3 9.1-20.4 20.4-20.4h52l66.6-115.4-20.8-36.1c-5.6-9.8-2.3-22.2 7.5-27.9 9.8-5.6 22.2-2.3 27.9 7.5l8.9 15.7zm-78.7 218l-19.6 34c-5.6 9.8-18.1 13.1-27.9 7.5-9.8-5.6-13.1-18.1-7.5-27.9l14.6-25.2c16.4-5.1 29.8-1.2 40.4 11.6zm168.9-61.7h53.1c11.3 0 20.4 9.1 20.4 20.4 0 11.3-9.1 20.4-20.4 20.4h-29.5l19.9 34.5c5.6 9.8 2.3 22.2-7.5 27.9-9.8 5.6-22.2 2.3-27.9-7.5-33.5-58.1-58.7-101.6-75.4-130.6-17.1-29.5-4.9-59.1 7.2-69.1 13.4 23 33.4 57.7 60.1 104zM256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm216 248c0 118.7-96.1 216-216 216-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M42.1 239.1c22.2 0 29 2.8 33.5 14.6h.8v-22.9c0-11.3-4.8-15.4-17.9-15.4-11.3 0-14.4 2.5-15.1 12.8H4.8c.3-13.9 1.5-19.1 5.8-24.4C17.9 195 29.5 192 56.7 192c33 0 47.1 5 53.9 18.9 2 4.3 4 15.6 4 23.7v76.3H76.3l1.3-19.1h-1c-5.3 15.6-13.6 20.4-35.5 20.4-30.3 0-41.1-10.1-41.1-37.3 0-25.2 12.3-35.8 42.1-35.8zm17.1 48.1c13.1 0 16.9-3 16.9-13.4 0-9.1-4.3-11.6-19.6-11.6-13.1 0-17.9 3-17.9 12.1-.1 10.4 3.7 12.9 20.6 12.9zm77.8-94.9h38.3l-1.5 20.6h.8c9.1-17.1 15.9-20.9 37.5-20.9 14.4 0 24.7 3 31.5 9.1 9.8 8.6 12.8 20.4 12.8 48.1 0 30-3 43.1-12.1 52.9-6.8 7.3-16.4 10.1-33.2 10.1-20.4 0-29.2-5.5-33.8-21.2h-.8v70.3H137v-169zm80.9 60.7c0-27.5-3.3-32.5-20.7-32.5-16.9 0-20.7 5-20.7 28.7 0 28 3.5 33.5 21.2 33.5 16.4 0 20.2-5.6 20.2-29.7zm57.9-60.7h38.3l-1.5 20.6h.8c9.1-17.1 15.9-20.9 37.5-20.9 14.4 0 24.7 3 31.5 9.1 9.8 8.6 12.8 20.4 12.8 48.1 0 30-3 43.1-12.1 52.9-6.8 7.3-16.4 10.1-33.3 10.1-20.4 0-29.2-5.5-33.8-21.2h-.8v70.3h-39.5v-169zm80.9 60.7c0-27.5-3.3-32.5-20.7-32.5-16.9 0-20.7 5-20.7 28.7 0 28 3.5 33.5 21.2 33.5 16.4 0 20.2-5.6 20.2-29.7zm53.8-3.8c0-25.4 3.3-37.8 12.3-45.8 8.8-8.1 22.2-11.3 45.1-11.3 42.8 0 55.7 12.8 55.7 55.7v11.1h-75.3c-.3 2-.3 4-.3 4.8 0 16.9 4.5 21.9 20.1 21.9 13.9 0 17.9-3 17.9-13.9h37.5v2.3c0 9.8-2.5 18.9-6.8 24.7-7.3 9.8-19.6 13.6-44.3 13.6-27.5 0-41.6-3.3-50.6-12.3-8.5-8.5-11.3-21.3-11.3-50.8zm76.4-11.6c-.3-1.8-.3-3.3-.3-3.8 0-12.3-3.3-14.6-19.6-14.6-14.4 0-17.1 3-18.1 15.1l-.3 3.3h38.3zm55.6-45.3h38.3l-1.8 19.9h.7c6.8-14.9 14.4-20.2 29.7-20.2 10.8 0 19.1 3.3 23.4 9.3 5.3 7.3 6.8 14.4 6.8 34 0 1.5 0 5 .2 9.3h-35c.3-1.8.3-3.3.3-4 0-15.4-2-19.4-10.3-19.4-6.3 0-10.8 3.3-13.1 9.3-1 3-1 4.3-1 12.3v68h-38.3V192.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M116.9 158.5c-7.5 8.9-19.5 15.9-31.5 14.9-1.5-12 4.4-24.8 11.3-32.6 7.5-9.1 20.6-15.6 31.3-16.1 1.2 12.4-3.7 24.7-11.1 33.8m10.9 17.2c-17.4-1-32.3 9.9-40.5 9.9-8.4 0-21-9.4-34.8-9.1-17.9.3-34.5 10.4-43.6 26.5-18.8 32.3-4.9 80 13.3 106.3 8.9 13 19.5 27.3 33.5 26.8 13.3-.5 18.5-8.6 34.5-8.6 16.1 0 20.8 8.6 34.8 8.4 14.5-.3 23.6-13 32.5-26 10.1-14.8 14.3-29.1 14.5-29.9-.3-.3-28-10.9-28.3-42.9-.3-26.8 21.9-39.5 22.9-40.3-12.5-18.6-32-20.6-38.8-21.1m100.4-36.2v194.9h30.3v-66.6h41.9c38.3 0 65.1-26.3 65.1-64.3s-26.4-64-64.1-64h-73.2zm30.3 25.5h34.9c26.3 0 41.3 14 41.3 38.6s-15 38.8-41.4 38.8h-34.8V165zm162.2 170.9c19 0 36.6-9.6 44.6-24.9h.6v23.4h28v-97c0-28.1-22.5-46.3-57.1-46.3-32.1 0-55.9 18.4-56.8 43.6h27.3c2.3-12 13.4-19.9 28.6-19.9 18.5 0 28.9 8.6 28.9 24.5v10.8l-37.8 2.3c-35.1 2.1-54.1 16.5-54.1 41.5.1 25.2 19.7 42 47.8 42zm8.2-23.1c-16.1 0-26.4-7.8-26.4-19.6 0-12.3 9.9-19.4 28.8-20.5l33.6-2.1v11c0 18.2-15.5 31.2-36 31.2zm102.5 74.6c29.5 0 43.4-11.3 55.5-45.4L640 193h-30.8l-35.6 115.1h-.6L537.4 193h-31.6L557 334.9l-2.8 8.6c-4.6 14.6-12.1 20.3-25.5 20.3-2.4 0-7-.3-8.9-.5v23.4c1.8.4 9.3.7 11.6.7z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 695 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M2 377.4l43 74.3A51.35 51.35 0 0 0 90.9 480h285.4l-59.2-102.6zM501.8 350L335.6 59.3A51.38 51.38 0 0 0 290.2 32h-88.4l257.3 447.6 40.7-70.5c1.9-3.2 21-29.7 2-59.1zM275 304.5l-115.5-200L44 304.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 453 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M517.5 309.2c38.8-40 58.1-80 58.5-116.1.8-65.5-59.4-118.2-169.4-135C277.9 38.4 118.1 73.6 0 140.5 52 114 110.6 92.3 170.7 82.3c74.5-20.5 153-25.4 221.3-14.8C544.5 91.3 588.8 195 490.8 299.2c-10.2 10.8-22 21.1-35 30.6L304.9 103.4 114.7 388.9c-65.6-29.4-76.5-90.2-19.1-151.2 20.8-22.2 48.3-41.9 79.5-58.1 20-12.2 39.7-22.6 62-30.7-65.1 20.3-122.7 52.9-161.6 92.9-27.7 28.6-41.4 57.1-41.7 82.9-.5 35.1 23.4 65.1 68.4 83l-34.5 51.7h101.6l22-34.4c22.2 1 45.3 0 68.6-2.7l-22.8 37.1h135.5L340 406.3c18.6-5.3 36.9-11.5 54.5-18.7l45.9 71.8H542L468.6 349c18.5-12.1 35-25.5 48.9-39.8zm-187.6 80.5l-25-40.6-32.7 53.3c-23.4 3.5-46.7 5.1-69.2 4.4l101.9-159.3 78.7 123c-17.2 7.4-35.3 13.9-53.7 19.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 944 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M152.2 236.4c-7.7-8.2-19.7-7.7-24.8 2.8L1.6 490.2c-5 10 2.4 21.7 13.4 21.7h175c5.8.1 11-3.2 13.4-8.4 37.9-77.8 15.1-196.3-51.2-267.1zM244.4 8.1c-122.3 193.4-8.5 348.6 65 495.5 2.5 5.1 7.7 8.4 13.4 8.4H497c11.2 0 18.4-11.8 13.4-21.7 0 0-234.5-470.6-240.4-482.3-5.3-10.6-18.8-10.8-25.6.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 546 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M640 199.9v54l-320 200L0 254v-54l320 200 320-200.1zm-194.5 72l47.1-29.4c-37.2-55.8-100.7-92.6-172.7-92.6-72 0-135.5 36.7-172.6 92.4h.3c2.5-2.3 5.1-4.5 7.7-6.7 89.7-74.4 219.4-58.1 290.2 36.3zm-220.1 18.8c16.9-11.9 36.5-18.7 57.4-18.7 34.4 0 65.2 18.4 86.4 47.6l45.4-28.4c-20.9-29.9-55.6-49.5-94.8-49.5-38.9 0-73.4 19.4-94.4 49zM103.6 161.1c131.8-104.3 318.2-76.4 417.5 62.1l.7 1 48.8-30.4C517.1 112.1 424.8 58.1 319.9 58.1c-103.5 0-196.6 53.5-250.5 135.6 9.9-10.5 22.7-23.5 34.2-32.6zm467 32.7z"/></svg>
|
||||
|
After Width: | Height: | Size: 754 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M318.4 16l-161 480h77.5l25.4-81.4h119.5L405 496h77.5L318.4 16zm-40.3 341.9l41.2-130.4h1.5l40.9 130.4h-83.6zM640 405l-10-31.4L462.1 358l19.4 56.5L640 405zm-462.1-47L10 373.7 0 405l158.5 9.4 19.4-56.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 459 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M453.1 32h-312c-38.9 0-76.2 31.2-83.3 69.7L1.2 410.3C-5.9 448.8 19.9 480 58.9 480h312c38.9 0 76.2-31.2 83.3-69.7l56.7-308.5c7-38.6-18.8-69.8-57.8-69.8zm-58.2 347.3l-32 13.5-115.4-110c-14.7 10-29.2 19.5-41.7 27.1l22.1 64.2-17.9 12.7-40.6-61-52.4-48.1 15.7-15.4 58 31.1c9.3-10.5 20.8-22.6 32.8-34.9L203 228.9l-68.8-99.8 18.8-28.9 8.9-4.8L265 207.8l4.9 4.5c19.4-18.8 33.8-32.4 33.8-32.4 7.7-6.5 21.5-2.9 30.7 7.9 9 10.5 10.6 24.7 2.7 31.3-1.8 1.3-15.5 11.4-35.3 25.6l4.5 7.3 94.9 119.4-6.3 7.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 751 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M107.2 283.5l-19-41.8H36.1l-19 41.8H0l62.2-131.4 62.2 131.4h-17.2zm-45-98.1l-19.6 42.5h39.2l-19.6-42.5zm112.7 102.4l-62.2-131.4h17.1l45.1 96 45.1-96h17l-62.1 131.4zm80.6-4.3V156.4H271v127.1h-15.5zm209.1-115.6v115.6h-17.3V167.9h-41.2v-11.5h99.6v11.5h-41.1zM640 218.8c0 9.2-1.7 17.8-5.1 25.8-3.4 8-8.2 15.1-14.2 21.1-6 6-13.1 10.8-21.1 14.2-8 3.4-16.6 5.1-25.8 5.1s-17.8-1.7-25.8-5.1c-8-3.4-15.1-8.2-21.1-14.2-6-6-10.8-13-14.2-21.1-3.4-8-5.1-16.6-5.1-25.8s1.7-17.8 5.1-25.8c3.4-8 8.2-15.1 14.2-21.1 6-6 13-8.4 21.1-11.9 8-3.4 16.6-5.1 25.8-5.1s17.8 1.7 25.8 5.1c8 3.4 15.1 5.8 21.1 11.9 6 6 10.7 13.1 14.2 21.1 3.4 8 5.1 16.6 5.1 25.8zm-15.5 0c0-7.3-1.3-14-3.9-20.3-2.6-6.3-6.2-11.7-10.8-16.3-4.6-4.6-10-8.2-16.2-10.9-6.2-2.7-12.8-4-19.8-4s-13.6 1.3-19.8 4c-6.2 2.7-11.6 6.3-16.2 10.9-4.6 4.6-8.2 10-10.8 16.3-2.6 6.3-3.9 13.1-3.9 20.3 0 7.3 1.3 14 3.9 20.3 2.6 6.3 6.2 11.7 10.8 16.3 4.6 4.6 10 8.2 16.2 10.9 6.2 2.7 12.8 4 19.8 4s13.6-1.3 19.8-4c6.2-2.7 11.6-6.3 16.2-10.9 4.6-4.6 8.2-10 10.8-16.3 2.6-6.3 3.9-13.1 3.9-20.3zm-94.8 96.7v-6.3l88.9-10-242.9 13.4c.6-2.2 1.1-4.6 1.4-7.2.3-2 .5-4.2.6-6.5l64.8-8.1-64.9 1.9c0-.4-.1-.7-.1-1.1-2.8-17.2-25.5-23.7-25.5-23.7l-1.1-26.3h23.8l19 41.8h17.1L348.6 152l-62.2 131.4h17.1l19-41.8h23.6L345 268s-22.7 6.5-25.5 23.7c-.1.3-.1.7-.1 1.1l-64.9-1.9 64.8 8.1c.1 2.3.3 4.4.6 6.5.3 2.6.8 5 1.4 7.2L78.4 299.2l88.9 10v6.3c-5.9.9-10.5 6-10.5 12.2 0 6.8 5.6 12.4 12.4 12.4 6.8 0 12.4-5.6 12.4-12.4 0-6.2-4.6-11.3-10.5-12.2v-5.8l80.3 9v5.4c-5.7 1.1-9.9 6.2-9.9 12.1 0 6.8 5.6 10.2 12.4 10.2 6.8 0 12.4-3.4 12.4-10.2 0-6-4.3-11-9.9-12.1v-4.9l28.4 3.2v23.7h-5.9V360h5.9v-6.6h5v6.6h5.9v-13.8h-5.9V323l38.3 4.3c8.1 11.4 19 13.6 19 13.6l-.1 6.7-5.1.2-.1 12.1h4.1l.1-5h5.2l.1 5h4.1l-.1-12.1-5.1-.2-.1-6.7s10.9-2.1 19-13.6l38.3-4.3v23.2h-5.9V360h5.9v-6.6h5v6.6h5.9v-13.8h-5.9v-23.7l28.4-3.2v4.9c-5.7 1.1-9.9 6.2-9.9 12.1 0 6.8 5.6 10.2 12.4 10.2 6.8 0 12.4-3.4 12.4-10.2 0-6-4.3-11-9.9-12.1v-5.4l80.3-9v5.8c-5.9.9-10.5 6-10.5 12.2 0 6.8 5.6 12.4 12.4 12.4 6.8 0 12.4-5.6 12.4-12.4-.2-6.3-4.7-11.4-10.7-12.3zm-200.8-87.6l19.6-42.5 19.6 42.5h-17.9l-1.7-40.3-1.7 40.3h-17.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M180.41 203.01c-.72 22.65 10.6 32.68 10.88 39.05a8.164 8.164 0 0 1-4.1 6.27l-12.8 8.96a10.66 10.66 0 0 1-5.63 1.92c-.43-.02-8.19 1.83-20.48-25.61a78.608 78.608 0 0 1-62.61 29.45c-16.28.89-60.4-9.24-58.13-56.21-1.59-38.28 34.06-62.06 70.93-60.05 7.1.02 21.6.37 46.99 6.27v-15.62c2.69-26.46-14.7-46.99-44.81-43.91-2.4.01-19.4-.5-45.84 10.11-7.36 3.38-8.3 2.82-10.75 2.82-7.41 0-4.36-21.48-2.94-24.2 5.21-6.4 35.86-18.35 65.94-18.18a76.857 76.857 0 0 1 55.69 17.28 70.285 70.285 0 0 1 17.67 52.36l-.01 69.29zM93.99 235.4c32.43-.47 46.16-19.97 49.29-30.47 2.46-10.05 2.05-16.41 2.05-27.4-9.67-2.32-23.59-4.85-39.56-4.87-15.15-1.14-42.82 5.63-41.74 32.26-1.24 16.79 11.12 31.4 29.96 30.48zm170.92 23.05c-7.86.72-11.52-4.86-12.68-10.37l-49.8-164.65c-.97-2.78-1.61-5.65-1.92-8.58a4.61 4.61 0 0 1 3.86-5.25c.24-.04-2.13 0 22.25 0 8.78-.88 11.64 6.03 12.55 10.37l35.72 140.83 33.16-140.83c.53-3.22 2.94-11.07 12.8-10.24h17.16c2.17-.18 11.11-.5 12.68 10.37l33.42 142.63L420.98 80.1c.48-2.18 2.72-11.37 12.68-10.37h19.72c.85-.13 6.15-.81 5.25 8.58-.43 1.85 3.41-10.66-52.75 169.9-1.15 5.51-4.82 11.09-12.68 10.37h-18.69c-10.94 1.15-12.51-9.66-12.68-10.75L328.67 110.7l-32.78 136.99c-.16 1.09-1.73 11.9-12.68 10.75h-18.3zm273.48 5.63c-5.88.01-33.92-.3-57.36-12.29a12.802 12.802 0 0 1-7.81-11.91v-10.75c0-8.45 6.2-6.9 8.83-5.89 10.04 4.06 16.48 7.14 28.81 9.6 36.65 7.53 52.77-2.3 56.72-4.48 13.15-7.81 14.19-25.68 5.25-34.95-10.48-8.79-15.48-9.12-53.13-21-4.64-1.29-43.7-13.61-43.79-52.36-.61-28.24 25.05-56.18 69.52-55.95 12.67-.01 46.43 4.13 55.57 15.62 1.35 2.09 2.02 4.55 1.92 7.04v10.11c0 4.44-1.62 6.66-4.87 6.66-7.71-.86-21.39-11.17-49.16-10.75-6.89-.36-39.89.91-38.41 24.97-.43 18.96 26.61 26.07 29.7 26.89 36.46 10.97 48.65 12.79 63.12 29.58 17.14 22.25 7.9 48.3 4.35 55.44-19.08 37.49-68.42 34.44-69.26 34.42zm40.2 104.86c-70.03 51.72-171.69 79.25-258.49 79.25A469.127 469.127 0 0 1 2.83 327.46c-6.53-5.89-.77-13.96 7.17-9.47a637.37 637.37 0 0 0 316.88 84.12 630.22 630.22 0 0 0 241.59-49.55c11.78-5 21.77 7.8 10.12 16.38zm29.19-33.29c-8.96-11.52-59.28-5.38-81.81-2.69-6.79.77-7.94-5.12-1.79-9.47 40.07-28.17 105.88-20.1 113.44-10.63 7.55 9.47-2.05 75.41-39.56 106.91-5.76 4.87-11.27 2.3-8.71-4.1 8.44-21.25 27.39-68.49 18.43-80.02z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm48.2,326.1h-181L207.9,178h181Z"/></svg>
|
||||
|
After Width: | Height: | Size: 360 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M448.61 225.62c26.87.18 35.57-7.43 38.92-12.37 12.47-16.32-7.06-47.6-52.85-71.33 17.76-33.58 30.11-63.68 36.34-85.3 3.38-11.83 1.09-19 .45-20.25-1.72 10.52-15.85 48.46-48.2 100.05-25-11.22-56.52-20.1-93.77-23.8-8.94-16.94-34.88-63.86-60.48-88.93C252.18 7.14 238.7 1.07 228.18.22h-.05c-13.83-1.55-22.67 5.85-27.4 11-17.2 18.53-24.33 48.87-25 84.07-7.24-12.35-17.17-24.63-28.5-25.93h-.18c-20.66-3.48-38.39 29.22-36 81.29-38.36 1.38-71 5.75-93 11.23-9.9 2.45-16.22 7.27-17.76 9.72 1-.38 22.4-9.22 111.56-9.22 5.22 53 29.75 101.82 26 93.19-9.73 15.4-38.24 62.36-47.31 97.7-5.87 22.88-4.37 37.61.15 47.14 5.57 12.75 16.41 16.72 23.2 18.26 25 5.71 55.38-3.63 86.7-21.14-7.53 12.84-13.9 28.51-9.06 39.34 7.31 19.65 44.49 18.66 88.44-9.45 20.18 32.18 40.07 57.94 55.7 74.12a39.79 39.79 0 0 0 8.75 7.09c5.14 3.21 8.58 3.37 8.58 3.37-8.24-6.75-34-38-62.54-91.78 22.22-16 45.65-38.87 67.47-69.27 122.82 4.6 143.29-24.76 148-31.64 14.67-19.88 3.43-57.44-57.32-93.69zm-77.85 106.22c23.81-37.71 30.34-67.77 29.45-92.33 27.86 17.57 47.18 37.58 49.06 58.83 1.14 12.93-8.1 29.12-78.51 33.5zM216.9 387.69c9.76-6.23 19.53-13.12 29.2-20.49 6.68 13.33 13.6 26.1 20.6 38.19-40.6 21.86-68.84 12.76-49.8-17.7zm215-171.35c-10.29-5.34-21.16-10.34-32.38-15.05a722.459 722.459 0 0 0 22.74-36.9c39.06 24.1 45.9 53.18 9.64 51.95zM279.18 398c-5.51-11.35-11-23.5-16.5-36.44 43.25 1.27 62.42-18.73 63.28-20.41 0 .07-25 15.64-62.53 12.25a718.78 718.78 0 0 0 85.06-84q13.06-15.31 24.93-31.11c-.36-.29-1.54-3-16.51-12-51.7 60.27-102.34 98-132.75 115.92-20.59-11.18-40.84-31.78-55.71-61.49-20-39.92-30-82.39-31.57-116.07 12.3.91 25.27 2.17 38.85 3.88-22.29 36.8-14.39 63-13.47 64.23 0-.07-.95-29.17 20.14-59.57a695.23 695.23 0 0 0 44.67 152.84c.93-.38 1.84.88 18.67-8.25-26.33-74.47-33.76-138.17-34-173.43 20-12.42 48.18-19.8 81.63-17.81 44.57 2.67 86.36 15.25 116.32 30.71q-10.69 15.66-23.33 32.47C365.63 152 339.1 145.84 337.5 146c.11 0 25.9 14.07 41.52 47.22a717.63 717.63 0 0 0-115.34-31.71 646.608 646.608 0 0 0-39.39-6.05c-.07.45-1.81 1.85-2.16 20.33C300 190.28 358.78 215.68 389.36 233c.74 23.55-6.95 51.61-25.41 79.57-24.6 37.31-56.39 67.23-84.77 85.43zm27.4-287c-44.56-1.66-73.58 7.43-94.69 20.67 2-52.3 21.31-76.38 38.21-75.28C267 52.15 305 108.55 306.58 111zm-130.65 3.1c.48 12.11 1.59 24.62 3.21 37.28-14.55-.85-28.74-1.25-42.4-1.26-.08 3.24-.12-51 24.67-49.59h.09c5.76 1.09 10.63 6.88 14.43 13.57zm-28.06 162c20.76 39.7 43.3 60.57 65.25 72.31-46.79 24.76-77.53 20-84.92 4.51-.2-.21-11.13-15.3 19.67-76.81zm210.06 74.8"/></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M186.5 293c0 19.3-14 25.4-31.2 25.4h-45.1v-52.9h46c18.6.1 30.3 7.8 30.3 27.5zm-7.7-82.3c0-17.7-13.7-21.9-28.9-21.9h-39.6v44.8H153c15.1 0 25.8-6.6 25.8-22.9zm132.3 23.2c-18.3 0-30.5 11.4-31.7 29.7h62.2c-1.7-18.5-11.3-29.7-30.5-29.7zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zM271.7 185h77.8v-18.9h-77.8V185zm-43 110.3c0-24.1-11.4-44.9-35-51.6 17.2-8.2 26.2-17.7 26.2-37 0-38.2-28.5-47.5-61.4-47.5H68v192h93.1c34.9-.2 67.6-16.9 67.6-55.9zM380 280.5c0-41.1-24.1-75.4-67.6-75.4-42.4 0-71.1 31.8-71.1 73.6 0 43.3 27.3 73 71.1 73 33.2 0 54.7-14.9 65.1-46.8h-33.7c-3.7 11.9-18.6 18.1-30.2 18.1-22.4 0-34.1-13.1-34.1-35.3h100.2c.1-2.3.3-4.8.3-7.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 963 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M232 237.2c31.8-15.2 48.4-38.2 48.4-74 0-70.6-52.6-87.8-113.3-87.8H0v354.4h171.8c64.4 0 124.9-30.9 124.9-102.9 0-44.5-21.1-77.4-64.7-89.7zM77.9 135.9H151c28.1 0 53.4 7.9 53.4 40.5 0 30.1-19.7 42.2-47.5 42.2h-79v-82.7zm83.3 233.7H77.9V272h84.9c34.3 0 56 14.3 56 50.6 0 35.8-25.9 47-57.6 47zm358.5-240.7H376V94h143.7v34.9zM576 305.2c0-75.9-44.4-139.2-124.9-139.2-78.2 0-131.3 58.8-131.3 135.8 0 79.9 50.3 134.7 131.3 134.7 61.3 0 101-27.6 120.1-86.3H509c-6.7 21.9-34.3 33.5-55.7 33.5-41.3 0-63-24.2-63-65.3h185.1c.3-4.2.6-8.7.6-13.2zM390.4 274c2.3-33.7 24.7-54.8 58.5-54.8 35.4 0 53.2 20.8 56.2 54.8H390.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 864 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M416 32H32C14.4 32 0 46.4 0 64v384c0 17.6 14.4 32 32 32h384c17.6 0 32-14.4 32-32V64c0-17.6-14.4-32-32-32zm-64 257.4c0 49.4-11.4 82.6-103.8 82.6h-16.9c-44.1 0-62.4-14.9-70.4-38.8h-.9V368H96V136h64v74.7h1.1c4.6-30.5 39.7-38.8 69.7-38.8h17.3c92.4 0 103.8 33.1 103.8 82.5v35zm-64-28.9v22.9c0 21.7-3.4 33.8-38.4 33.8h-45.3c-28.9 0-44.1-6.5-44.1-35.7v-19c0-29.3 15.2-35.7 44.1-35.7h45.3c35-.2 38.4 12 38.4 33.7z"/></svg>
|
||||
|
After Width: | Height: | Size: 665 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M22.2 32A16 16 0 0 0 6 47.8a26.35 26.35 0 0 0 .2 2.8l67.9 412.1a21.77 21.77 0 0 0 21.3 18.2h325.7a16 16 0 0 0 16-13.4L505 50.7a16 16 0 0 0-13.2-18.3 24.58 24.58 0 0 0-2.8-.2L22.2 32zm285.9 297.8h-104l-28.1-147h157.3l-25.2 147z"/></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zm-141.651-35.33c4.937-32.999-20.191-50.739-54.55-62.573l11.146-44.702-27.213-6.781-10.851 43.524c-7.154-1.783-14.502-3.464-21.803-5.13l10.929-43.81-27.198-6.781-11.153 44.686c-5.922-1.349-11.735-2.682-17.377-4.084l.031-.14-37.53-9.37-7.239 29.062s20.191 4.627 19.765 4.913c11.022 2.751 13.014 10.044 12.68 15.825l-12.696 50.925c.76.194 1.744.473 2.829.907-.907-.225-1.876-.473-2.876-.713l-17.796 71.338c-1.349 3.348-4.767 8.37-12.471 6.464.271.395-19.78-4.937-19.78-4.937l-13.51 31.147 35.414 8.827c6.588 1.651 13.045 3.379 19.4 5.006l-11.262 45.213 27.182 6.781 11.153-44.733a1038.209 1038.209 0 0 0 21.687 5.627l-11.115 44.523 27.213 6.781 11.262-45.128c46.404 8.781 81.299 5.239 95.986-36.727 11.836-33.79-.589-53.281-25.004-65.991 17.78-4.098 31.174-15.792 34.747-39.949zm-62.177 87.179c-8.41 33.79-65.308 15.523-83.755 10.943l14.944-59.899c18.446 4.603 77.6 13.717 68.811 48.956zm8.417-87.667c-7.673 30.736-55.031 15.12-70.393 11.292l13.548-54.327c15.363 3.828 64.836 10.973 56.845 43.035z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M78.4 67.2C173.8-22 324.5-24 421.5 71c14.3 14.1-6.4 37.1-22.4 21.5-84.8-82.4-215.8-80.3-298.9-3.2-16.3 15.1-36.5-8.3-21.8-22.1zm98.9 418.6c19.3 5.7 29.3-23.6 7.9-30C73 421.9 9.4 306.1 37.7 194.8c5-19.6-24.9-28.1-30.2-7.1-32.1 127.4 41.1 259.8 169.8 298.1zm148.1-2c121.9-40.2 192.9-166.9 164.4-291-4.5-19.7-34.9-13.8-30 7.9 24.2 107.7-37.1 217.9-143.2 253.4-21.2 7-10.4 36 8.8 29.7zm-62.9-79l.2-71.8c0-8.2-6.6-14.8-14.8-14.8-8.2 0-14.8 6.7-14.8 14.8l-.2 71.8c0 8.2 6.6 14.8 14.8 14.8s14.8-6.6 14.8-14.8zm71-269c2.1 90.9 4.7 131.9-85.5 132.5-92.5-.7-86.9-44.3-85.5-132.5 0-21.8-32.5-19.6-32.5 0v71.6c0 69.3 60.7 90.9 118 90.1 57.3.8 118-20.8 118-90.1v-71.6c0-19.6-32.5-21.8-32.5 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 939 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M0 32v448h448V32H0zm316.5 325.2L224 445.9l-92.5-88.7 64.5-184-64.5-86.6h184.9L252 173.2l64.5 184z"/></svg>
|
||||
|
After Width: | Height: | Size: 357 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M166 116.9c0 23.4-16.4 49.1-72.5 49.1H23.4l21-88.8h67.8c42.1 0 53.8 23.3 53.8 39.7zm126.2-39.7h-67.8L205.7 166h70.1c53.8 0 70.1-25.7 70.1-49.1.1-16.4-11.6-39.7-53.7-39.7zM88.8 208.1H21L0 296.9h70.1c56.1 0 72.5-23.4 72.5-49.1 0-16.3-11.7-39.7-53.8-39.7zm180.1 0h-67.8l-18.7 88.8h70.1c53.8 0 70.1-23.4 70.1-49.1 0-16.3-11.7-39.7-53.7-39.7zm189.3-53.8h-67.8l-18.7 88.8h70.1c53.8 0 70.1-23.4 70.1-49.1.1-16.3-11.6-39.7-53.7-39.7zm-28 137.9h-67.8L343.7 381h70.1c56.1 0 70.1-23.4 70.1-49.1 0-16.3-11.6-39.7-53.7-39.7zM240.8 346H173l-18.7 88.8h70.1c56.1 0 70.1-25.7 70.1-49.1.1-16.3-11.6-39.7-53.7-39.7z"/></svg>
|
||||
|
After Width: | Height: | Size: 856 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M446.6 222.7c-1.8-8-6.8-15.4-12.5-18.5-1.8-1-13-2.2-25-2.7-20.1-.9-22.3-1.3-28.7-5-10.1-5.9-12.8-12.3-12.9-29.5-.1-33-13.8-63.7-40.9-91.3-19.3-19.7-40.9-33-65.5-40.5-5.9-1.8-19.1-2.4-63.3-2.9-69.4-.8-84.8.6-108.4 10C45.9 59.5 14.7 96.1 3.3 142.9 1.2 151.7.7 165.8.2 246.8c-.6 101.5.1 116.4 6.4 136.5 15.6 49.6 59.9 86.3 104.4 94.3 14.8 2.7 197.3 3.3 216 .8 32.5-4.4 58-17.5 81.9-41.9 17.3-17.7 28.1-36.8 35.2-62.1 4.9-17.6 4.5-142.8 2.5-151.7zm-322.1-63.6c7.8-7.9 10-8.2 58.8-8.2 43.9 0 45.4.1 51.8 3.4 9.3 4.7 13.4 11.3 13.4 21.9 0 9.5-3.8 16.2-12.3 21.6-4.6 2.9-7.3 3.1-50.3 3.3-26.5.2-47.7-.4-50.8-1.2-16.6-4.7-22.8-28.5-10.6-40.8zm191.8 199.8l-14.9 2.4-77.5.9c-68.1.8-87.3-.4-90.9-2-7.1-3.1-13.8-11.7-14.9-19.4-1.1-7.3 2.6-17.3 8.2-22.4 7.1-6.4 10.2-6.6 97.3-6.7 89.6-.1 89.1-.1 97.6 7.8 12.1 11.3 9.5 31.2-4.9 39.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M162.4 196c4.8-4.9 6.2-5.1 36.4-5.1 27.2 0 28.1.1 32.1 2.1 5.8 2.9 8.3 7 8.3 13.6 0 5.9-2.4 10-7.6 13.4-2.8 1.8-4.5 1.9-31.1 2.1-16.4.1-29.5-.2-31.5-.8-10.3-2.9-14.1-17.7-6.6-25.3zm61.4 94.5c-53.9 0-55.8.2-60.2 4.1-3.5 3.1-5.7 9.4-5.1 13.9.7 4.7 4.8 10.1 9.2 12 2.2 1 14.1 1.7 56.3 1.2l47.9-.6 9.2-1.5c9-5.1 10.5-17.4 3.1-24.4-5.3-4.7-5-4.7-60.4-4.7zm223.4 130.1c-3.5 28.4-23 50.4-51.1 57.5-7.2 1.8-9.7 1.9-172.9 1.8-157.8 0-165.9-.1-172-1.8-8.4-2.2-15.6-5.5-22.3-10-5.6-3.8-13.9-11.8-17-16.4-3.8-5.6-8.2-15.3-10-22C.1 423 0 420.3 0 256.3 0 93.2 0 89.7 1.8 82.6 8.1 57.9 27.7 39 53 33.4c7.3-1.6 332.1-1.9 340-.3 21.2 4.3 37.9 17.1 47.6 36.4 7.7 15.3 7-1.5 7.3 180.6.2 115.8 0 164.5-.7 170.5zm-85.4-185.2c-1.1-5-4.2-9.6-7.7-11.5-1.1-.6-8-1.3-15.5-1.7-12.4-.6-13.8-.8-17.8-3.1-6.2-3.6-7.9-7.6-8-18.3 0-20.4-8.5-39.4-25.3-56.5-12-12.2-25.3-20.5-40.6-25.1-3.6-1.1-11.8-1.5-39.2-1.8-42.9-.5-52.5.4-67.1 6.2-27 10.7-46.3 33.4-53.4 62.4-1.3 5.4-1.6 14.2-1.9 64.3-.4 62.8 0 72.1 4 84.5 9.7 30.7 37.1 53.4 64.6 58.4 9.2 1.7 122.2 2.1 133.7.5 20.1-2.7 35.9-10.8 50.7-25.9 10.7-10.9 17.4-22.8 21.8-38.5 3.2-10.9 2.9-88.4 1.7-93.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |