Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Environments - Updated UI #2432

Merged
merged 10 commits into from
Mar 26, 2024
4 changes: 2 additions & 2 deletions common/Environments/Styles/HorizontalCardStyles.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
x:Key="CardBodyStateTextBlockStyle"
BasedOn="{StaticResource CaptionTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorTertiaryBrush}" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>

Expand Down Expand Up @@ -227,7 +227,7 @@
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>

<Style
<Style
x:Key="CardBodySplitButtonStyle"
TargetType="SplitButton">
<Setter Property="HorizontalAlignment" Value="Right" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public static List<OperationsViewModel> FillDotButtonOperations(ComputeSystem co
}

// ToDo: Correct the function used
// operations.Add(new OperationsViewModel("Pin To Taskbar", "\uE718", computeSystem.DeleteAsync));
// operations.Add(new OperationsViewModel("Add to Start Menu", "\uF0DF", computeSystem.DeleteAsync));
// operations.Add(new OperationsViewModel("Pin To Taskbar", "\uE718", computeSystem.));
// operations.Add(new OperationsViewModel("Add to Start Menu", "\uE8A9", computeSystem.));
return operations;
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -85,12 +85,12 @@ public static List<OperationsViewModel> FillLaunchButtonOperations(ComputeSystem

if (supportedOperations.HasFlag(ComputeSystemOperations.Resume))
{
operations.Add(new OperationsViewModel("Resume", "\uF2C6", computeSystem.ResumeAsync));
operations.Add(new OperationsViewModel("Resume", "\uE768", computeSystem.ResumeAsync));
}
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved

if (supportedOperations.HasFlag(ComputeSystemOperations.Terminate))
{
operations.Add(new OperationsViewModel("Terminate", "\uE71A", computeSystem.TerminateAsync));
operations.Add(new OperationsViewModel("Terminate", "\uEE95", computeSystem.TerminateAsync));
}

return operations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@
<comment>Text for terminate button in the context menu that will turn off the virtual machine. This button turns off the power to the virtual machine without gracefully shutting it down first.</comment>
</data>
<data name="SortByName.Content" xml:space="preserve">
<value>Name</value>
<comment>Text for 1st option for sort by drop down box</comment>
<value>Name: Ascending</value>
<comment>Text for sorting in ascending order of name</comment>
</data>
<data name="SortByAltName.Content" xml:space="preserve">
<value>Alternative Name</value>
<comment>Text for 2nd option for sort by drop down box</comment>
<value>Alternative Name: Ascending</value>
<comment>Text for sorting by alternative name</comment>
</data>
<data name="SortByTextBlock.Text" xml:space="preserve">
<value>Sort By</value>
<value>Sort:</value>
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
<comment>Text labelling sort by text block</comment>
</data>
<data name="SyncButtonTextBlock.Text" xml:space="preserve">
Expand All @@ -131,11 +131,63 @@
<comment>Title text for the main landing page</comment>
</data>
<data name="SearchTextBox.PlaceholderText" xml:space="preserve">
<value>Search</value>
<value>Filter</value>
<comment>Text for the search box</comment>
</data>
<data name="SortSelectionComboBox.PlaceholderText" xml:space="preserve">
<value>None</value>
<comment>Text for the default selection of the sort drop down</comment>
</data>
<data name="SortSelectionComboBox.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Sort by</value>
<comment>Name of ComboBox where user can choose how to sort</comment>
</data>
<data name="ProviderSelectionComboBox.PlaceholderText" xml:space="preserve">
<value>All</value>
<comment>Text for the default selection of the sort provider drop down</comment>
</data>
<data name="ProviderTextBlock.Text" xml:space="preserve">
<value>Provider:</value>
<comment>Text labelling provider text block</comment>
</data>
<data name="ProviderSelectionComboBox.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Provider</value>
<comment>Name of ComboBox where user can choose to filter by a particular provider</comment>
</data>
<data name="LastSyncTextBlock.Text" xml:space="preserve">
<value>Last Synced: </value>
<comment>Text for last synced text box</comment>
</data>
<data name="SortByNameDesc.Content" xml:space="preserve">
<value>Name: Descending</value>
<comment>Text for sorting in descending order of name</comment>
</data>
<data name="SortByAltNameDesc.Content" xml:space="preserve">
<value>Alternative Name: Descending</value>
<comment>Text for sorting by alternative name in descending order</comment>
</data>
<data name="SortByLastConnected.Content" xml:space="preserve">
<value>Last Connected</value>
<comment>Text for sorting by last connected time</comment>
</data>
<data name="HourAgo" xml:space="preserve">
<value>More than an hour ago...</value>
<comment>Text denoting more than an hour has passed</comment>
</data>
<data name="MinuteAgo" xml:space="preserve">
<value>A minute ago...</value>
<comment>Text denoting a minute has passed</comment>
</data>
<data name="MinutesAgo" xml:space="preserve">
<value>minutes ago...</value>
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
<comment>Text denoting multiple minutes have passed</comment>
</data>
<data name="MomentsAgo" xml:space="preserve">
<value>Moments ago...</value>
<comment>Text denoting less than a minute has passed</comment>
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AllProviders" xml:space="preserve">
<value>All</value>
<comment>Text for the default value of all providers for filtering</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public partial class ComputeSystemViewModel : ObservableObject

public string AlternativeName { get; } = string.Empty;

public DateTime LastConnected { get; set; } = DateTime.Now;
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved

public string Type { get; }

public bool IsOperationInProgress { get; set; }
Expand Down Expand Up @@ -132,6 +134,8 @@ public void RemoveStateChangedHandler()
[RelayCommand]
public void LaunchAction()
{
LastConnected = DateTime.Now;

// We'll need to disable the card UI while the operation is in progress and handle failures.
Task.Run(async () =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
Expand All @@ -26,7 +27,7 @@ namespace DevHome.Environments.ViewModels;
/// <summary>
/// The main view model for the landing page of the Environments tool.
/// </summary>
public partial class LandingPageViewModel : ObservableObject
public partial class LandingPageViewModel : ObservableObject, IDisposable
{
private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcher;

Expand All @@ -36,6 +37,8 @@ public partial class LandingPageViewModel : ObservableObject

private readonly IComputeSystemManager _computeSystemManager;

private readonly StringResource _stringResource;

private readonly object _lock = new();

public bool IsLoading { get; set; }
Expand All @@ -49,12 +52,33 @@ public partial class LandingPageViewModel : ObservableObject
[ObservableProperty]
private bool _showLoadingShimmer = true;

public LandingPageViewModel(IComputeSystemManager manager, EnvironmentsExtensionsService extensionsService, ToastNotificationService toastNotificationService)
[ObservableProperty]
private int _selectedProviderIndex;

[ObservableProperty]
private int _selectedSortIndex;

[ObservableProperty]
private string _lastSyncTime;

public ObservableCollection<string> Providers { get; set; }

private CancellationTokenSource _cancellationTokenSource = new();

public LandingPageViewModel(
IComputeSystemManager manager,
EnvironmentsExtensionsService extensionsService,
ToastNotificationService toastNotificationService)
{
_dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
_extensionsService = extensionsService;
_notificationService = toastNotificationService;
_computeSystemManager = manager;
_stringResource = new StringResource("DevHome.Environments/Resources");

SelectedSortIndex = -1;
Providers = new() { _stringResource.GetLocalized("AllProviders") };
_lastSyncTime = _stringResource.GetLocalized("MomentsAgo");

ComputeSystemsView = new AdvancedCollectionView(ComputeSystems);
}
Expand All @@ -65,7 +89,69 @@ public async Task SyncButton()
// temporary, we'll need to give the users a way to disable this.
// if they don't want to use hyper-v
_notificationService.CheckIfUserIsAHyperVAdmin();

// Reset the sort and filter
SelectedSortIndex = -1;
Providers = new ObservableCollection<string> { _stringResource.GetLocalized("AllProviders") };
SelectedProviderIndex = 0;

// Reset the old sync timer
_cancellationTokenSource.Cancel();
await _dispatcher.EnqueueAsync(() => LastSyncTime = _stringResource.GetLocalized("MomentsAgo"));

await LoadModelAsync();

// Start a new sync timer
_ = Task.Run(async () =>
{
await RunSyncTimmer();
});
}

// Updates the last sync time on the UI thread after set delay
private async Task UpdateLastSyncTimeUI(string time, TimeSpan delay, CancellationToken token)
{
await Task.Delay(delay, token);

if (!token.IsCancellationRequested)
{
await _dispatcher.EnqueueAsync(() => LastSyncTime = time);
}
}

private async Task RunSyncTimmer()
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
{
_cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = _cancellationTokenSource.Token;

await UpdateLastSyncTimeUI(_stringResource.GetLocalized("MinuteAgo"), TimeSpan.FromMinutes(1), cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
return;
}

// For the first 2-5 minutes, in 1 minute increments
for (var i = 2; i <= 5; i++)
{
await UpdateLastSyncTimeUI($"{i} {_stringResource.GetLocalized("MinutesAgo")}", TimeSpan.FromMinutes(1), cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
return;
}
}

// For the 10-55 minutes, in 5 minute increments
for (var i = 2; i <= 11; i++)
{
await UpdateLastSyncTimeUI($"{i * 5} {_stringResource.GetLocalized("MinutesAgo")}", TimeSpan.FromMinutes(5), cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
return;
}
}

// For an hour and more
await UpdateLastSyncTimeUI(_stringResource.GetLocalized("HourAgo"), TimeSpan.FromMinutes(5), cancellationToken);
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand All @@ -84,6 +170,12 @@ public async Task LoadModelAsync(bool useDebugValues = false)
IsLoading = true;
}

// Start a new sync timer
_ = Task.Run(async () =>
{
await RunSyncTimmer();
});

// temporary, we'll need to give the users a way to disable this.
// if they don't want to use hyper-v
_notificationService.CheckIfUserIsAHyperVAdmin();
Expand All @@ -109,6 +201,7 @@ private async Task AddAllComputeSystemsFromAProvider(ComputeSystemsLoadedData da

await _dispatcher.EnqueueAsync(async () =>
{
Providers.Add(provider.DisplayName);
try
{
var computeSystemList = data.DevIdToComputeSystemMap.Values.SelectMany(x => x.ComputeSystems).ToList();
Expand Down Expand Up @@ -157,22 +250,60 @@ public void SearchHandler(string query)
};
}

/// <summary>
/// Updates the view model to filter the compute systems according to the provider.
/// </summary>
[RelayCommand]
public void ProviderHandler()
{
var currentProvider = Providers[SelectedProviderIndex];
ComputeSystemsView.Filter = system =>
{
if (currentProvider == _stringResource.GetLocalized("AllProviders"))
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
{
return true;
}

if (system is ComputeSystemViewModel computeSystemViewModel)
{
var type = computeSystemViewModel.Type;
return type.Equals(currentProvider, StringComparison.OrdinalIgnoreCase);
}

return false;
};
}

/// <summary>
/// Updates the view model to sort the compute systems according to the sort criteria.
/// </summary>
[RelayCommand]
public void SortHandler(string critieria)
public void SortHandler()
{
ComputeSystemsView.SortDescriptions.Clear();
if (critieria == "Name")
{
ComputeSystemsView.SortDescriptions.Add(new SortDescription("Name", SortDirection.Ascending));
return;
}
else if (critieria == "Alternative Name")

switch (SelectedSortIndex)
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
huzaifa-d marked this conversation as resolved.
Show resolved Hide resolved
{
ComputeSystemsView.SortDescriptions.Add(new SortDescription("AlternativeName", SortDirection.Ascending));
return;
case 0:
ComputeSystemsView.SortDescriptions.Add(new SortDescription("Name", SortDirection.Ascending));
break;
case 1:
ComputeSystemsView.SortDescriptions.Add(new SortDescription("Name", SortDirection.Descending));
break;
case 2:
ComputeSystemsView.SortDescriptions.Add(new SortDescription("AlternativeName", SortDirection.Ascending));
break;
case 3:
ComputeSystemsView.SortDescriptions.Add(new SortDescription("AlternativeName", SortDirection.Descending));
break;
case 4:
ComputeSystemsView.SortDescriptions.Add(new SortDescription("LastConnected", SortDirection.Ascending));
break;
}
}

public void Dispose()
{
GC.SuppressFinalize(this);
}
}
Loading