Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions App/App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.1" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
<PackageReference Include="WinUIEx" Version="2.5.1" />
</ItemGroup>

<ItemGroup>
Expand Down
8 changes: 7 additions & 1 deletion App/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
<Application
x:Class="Coder.Desktop.App.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Coder.Desktop.App.Converters">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
</ResourceDictionary.MergedDictionaries>

<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:InverseBoolToVisibilityConverter x:Key="InverseBoolToVisibilityConverter" />
<converters:FriendlyByteConverter x:Key="FriendlyByteConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
5 changes: 5 additions & 0 deletions App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public App()
services.AddTransient<SignInViewModel>();
services.AddTransient<SignInWindow>();

// FileSyncListWindow views and view models
services.AddTransient<FileSyncListViewModel>();
// FileSyncListMainPage is created by FileSyncListWindow.
services.AddTransient<FileSyncListWindow>();

// TrayWindow views and view models
services.AddTransient<TrayWindowLoadingPage>();
services.AddTransient<TrayWindowDisconnectedViewModel>();
Expand Down
5 changes: 2 additions & 3 deletions App/Controls/SizedFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ public class SizedFrameEventArgs : EventArgs

/// <summary>
/// SizedFrame extends Frame by adding a SizeChanged event, which will be triggered when:
/// - The contained Page's content's size changes
/// - We switch to a different page.
///
/// - The contained Page's content's size changes
/// - We switch to a different page.
/// Sadly this is necessary because Window.Content.SizeChanged doesn't trigger when the Page's content changes.
/// </summary>
public class SizedFrame : Frame
Expand Down
33 changes: 0 additions & 33 deletions App/Converters/AgentStatusToColorConverter.cs

This file was deleted.

188 changes: 188 additions & 0 deletions App/Converters/DependencyObjectSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using System;
using System.Linq;
using Windows.Foundation.Collections;
using Windows.UI.Xaml.Markup;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;

namespace Coder.Desktop.App.Converters;

// This file uses manual DependencyProperty properties rather than
// DependencyPropertyGenerator since it doesn't seem to work properly with
// generics.

/// <summary>
/// An item in a DependencyObjectSelector. Each item has a key and a value.
/// The default item in a DependencyObjectSelector will be the only item
/// with a null key.
/// </summary>
/// <typeparam name="TK">Key type</typeparam>
/// <typeparam name="TV">Value type</typeparam>
public class DependencyObjectSelectorItem<TK, TV> : DependencyObject
where TK : IEquatable<TK>
{
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register(nameof(Key),
typeof(TK?),
typeof(DependencyObjectSelectorItem<TK, TV>),
new PropertyMetadata(null));

public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value),
typeof(TV?),
typeof(DependencyObjectSelectorItem<TK, TV>),
new PropertyMetadata(null));

public TK? Key
{
get => (TK?)GetValue(KeyProperty);
set => SetValue(KeyProperty, value);
}

public TV? Value
{
get => (TV?)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
}

/// <summary>
/// Allows selecting between multiple value references based on a selected
/// key. This allows for dynamic mapping of model values to other objects.
/// The main use case is for selecting between other bound values, which
/// you cannot do with a simple ValueConverter.
/// </summary>
/// <typeparam name="TK">Key type</typeparam>
/// <typeparam name="TV">Value type</typeparam>
[ContentProperty(Name = nameof(References))]
public class DependencyObjectSelector<TK, TV> : DependencyObject
where TK : IEquatable<TK>
{
public static readonly DependencyProperty ReferencesProperty =
DependencyProperty.Register(nameof(References),
typeof(DependencyObjectCollection),
typeof(DependencyObjectSelector<TK, TV>),
new PropertyMetadata(null, ReferencesPropertyChanged));

public static readonly DependencyProperty SelectedKeyProperty =
DependencyProperty.Register(nameof(SelectedKey),
typeof(TK?),
typeof(DependencyObjectSelector<TK, TV>),
new PropertyMetadata(null, SelectedKeyPropertyChanged));

public static readonly DependencyProperty SelectedObjectProperty =
DependencyProperty.Register(nameof(SelectedObject),
typeof(TV?),
typeof(DependencyObjectSelector<TK, TV>),
new PropertyMetadata(null));

public DependencyObjectCollection? References
{
get => (DependencyObjectCollection?)GetValue(ReferencesProperty);
set
{
// Ensure unique keys and that the values are DependencyObjectSelectorItem<K, V>.
if (value != null)
{
var items = value.OfType<DependencyObjectSelectorItem<TK, TV>>().ToArray();
var keys = items.Select(i => i.Key).Distinct().ToArray();
if (keys.Length != value.Count)
throw new ArgumentException("ObservableCollection Keys must be unique.");
}

SetValue(ReferencesProperty, value);
}
}

/// <summary>
/// The key of the selected item. This should be bound to a property on
/// the model.
/// </summary>
public TK? SelectedKey
{
get => (TK?)GetValue(SelectedKeyProperty);
set => SetValue(SelectedKeyProperty, value);
}

/// <summary>
/// The selected object. This can be read from to get the matching
/// object for the selected key. If the selected key doesn't match any
/// object, this will be the value of the null key. If there is no null
/// key, this will be null.
/// </summary>
public TV? SelectedObject
{
get => (TV?)GetValue(SelectedObjectProperty);
set => SetValue(SelectedObjectProperty, value);
}

public DependencyObjectSelector()
{
References = [];
}

private void UpdateSelectedObject()
{
if (References != null)
{
// Look for a matching item a matching key, or fallback to the null
// key.
var references = References.OfType<DependencyObjectSelectorItem<TK, TV>>().ToArray();
var item = references
.FirstOrDefault(i =>
(i.Key == null && SelectedKey == null) ||
(i.Key != null && SelectedKey != null && i.Key!.Equals(SelectedKey!)))
?? references.FirstOrDefault(i => i.Key == null);
if (item is not null)
{
// Bind the SelectedObject property to the reference's Value.
// If the underlying Value changes, it will propagate to the
// SelectedObject.
BindingOperations.SetBinding
(
this,
SelectedObjectProperty,
new Binding
{
Source = item,
Path = new PropertyPath(nameof(DependencyObjectSelectorItem<TK, TV>.Value)),
}
);
return;
}
}

ClearValue(SelectedObjectProperty);
}

// Called when the References property is replaced.
private static void ReferencesPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var self = obj as DependencyObjectSelector<TK, TV>;
if (self == null) return;
var oldValue = args.OldValue as DependencyObjectCollection;
if (oldValue != null)
oldValue.VectorChanged -= self.OnVectorChangedReferences;
var newValue = args.NewValue as DependencyObjectCollection;
if (newValue != null)
newValue.VectorChanged += self.OnVectorChangedReferences;
}

// Called when the References collection changes without being replaced.
private void OnVectorChangedReferences(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
{
UpdateSelectedObject();
}

// Called when SelectedKey changes.
private static void SelectedKeyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var self = obj as DependencyObjectSelector<TK, TV>;
self?.UpdateSelectedObject();
}
}

public sealed class StringToBrushSelectorItem : DependencyObjectSelectorItem<string, Brush>;

public sealed class StringToBrushSelector : DependencyObjectSelector<string, Brush>;
43 changes: 43 additions & 0 deletions App/Converters/FriendlyByteConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using Microsoft.UI.Xaml.Data;

namespace Coder.Desktop.App.Converters;

public class FriendlyByteConverter : IValueConverter
{
private static readonly string[] Suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];

public object Convert(object value, Type targetType, object parameter, string language)
{
switch (value)
{
case int i:
if (i < 0) i = 0;
return FriendlyBytes((ulong)i);
case uint ui:
return FriendlyBytes(ui);
case long l:
if (l < 0) l = 0;
return FriendlyBytes((ulong)l);
case ulong ul:
return FriendlyBytes(ul);
default:
return FriendlyBytes(0);
}
}

public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}

public static string FriendlyBytes(ulong bytes)
{
if (bytes == 0)
return $"0 {Suffixes[0]}";

var place = System.Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
var num = Math.Round(bytes / Math.Pow(1024, place), 1);
return $"{num} {Suffixes[place]}";
}
}
17 changes: 17 additions & 0 deletions App/Converters/InverseBoolConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using Microsoft.UI.Xaml.Data;

namespace Coder.Desktop.App.Converters;

public class InverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value is false;
}

public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
12 changes: 12 additions & 0 deletions App/Converters/InverseBoolToVisibilityConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.UI.Xaml;

namespace Coder.Desktop.App.Converters;

public partial class InverseBoolToVisibilityConverter : BoolToObjectConverter
{
public InverseBoolToVisibilityConverter()
{
TrueValue = Visibility.Collapsed;
FalseValue = Visibility.Visible;
}
}
Loading
Loading