Skip to content

Commit

Permalink
Update environments to show supported operations dynamically based th…
Browse files Browse the repository at this point in the history
…e state change events. (#2732)

* fix dynamic operations

* remove progress update after operation completion
  • Loading branch information
bbonaby authored May 1, 2024
1 parent 402697a commit 6d32f25
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using DevHome.Environments.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
Expand All @@ -27,25 +28,70 @@ public ObservableCollection<OperationsViewModel> ItemsViewModels
private static void ItemsLoaded(DependencyObject dependencyObj, DependencyPropertyChangedEventArgs args)
{
var flyout = (CardFlyout)dependencyObj;

if (args.OldValue != null)
{
var oldOperationsViewModel = (INotifyCollectionChanged)args.OldValue;

// Unsubscribe from CollectionChanged for the old collection
oldOperationsViewModel.CollectionChanged -= flyout.OperationsViewModelCollectionChanged;
}

if (args.NewValue != null)
{
var newOperationsViewModel = (ObservableCollection<OperationsViewModel>)args.NewValue;

// Subscribe to CollectionChanged for the new collection
newOperationsViewModel.CollectionChanged += flyout.OperationsViewModelCollectionChanged;
}

flyout.FillOperations();
}

private void FillOperations()
{
Items.Clear();

if (ItemsViewModels != null)
{
foreach (var item in ItemsViewModels)
{
var flyoutItem = new MenuFlyoutItem
Items.Add(CreateFlyoutItem(item));
}
}
}

private MenuFlyoutItem CreateFlyoutItem(OperationsViewModel viewModel)
{
return new MenuFlyoutItem
{
Text = viewModel.Name,
Icon = new FontIcon { Glyph = viewModel.IconGlyph },
Command = viewModel.InvokeActionCommand,
MinWidth = MinimumWidthOfItems,
};
}

private void OperationsViewModelCollectionChanged(object? sender, NotifyCollectionChangedEventArgs viewModelCollectionChangedArgs)
{
// Collection was cleared
if (viewModelCollectionChangedArgs.Action == NotifyCollectionChangedAction.Reset)
{
Items.Clear();
}

if (viewModelCollectionChangedArgs.NewItems == null)
{
return;
}

if (viewModelCollectionChangedArgs.NewItems.Count > 0 && viewModelCollectionChangedArgs.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in viewModelCollectionChangedArgs.NewItems)
{
if (item is OperationsViewModel operationViewModels)
{
Text = item.Name,
Icon = new FontIcon { Glyph = item.IconGlyph },
Command = item.InvokeActionCommand,
MinWidth = MinimumWidthOfItems,
};
Items.Add(flyoutItem);
Items.Add(CreateFlyoutItem(operationViewModels));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public abstract partial class ComputeSystemCardBase : ObservableObject
public bool IsCreateComputeSystemOperation { get; protected set; }

// Will hold the supported actions that the user can perform on in the UI. E.g Remove button
public ObservableCollection<OperationsViewModel>? DotOperations { get; protected set; }
public ObservableCollection<OperationsViewModel> DotOperations { get; protected set; } = new();

[ObservableProperty]
private ComputeSystemState _state;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Numerics;
using System.Runtime.InteropServices;
Expand All @@ -10,6 +11,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using DevHome.Common.Environments.Helpers;
using DevHome.Common.Environments.Models;
using DevHome.Common.Environments.Services;
Expand Down Expand Up @@ -42,7 +44,7 @@ public partial class ComputeSystemViewModel : ComputeSystemCardBase, IRecipient<
private readonly IComputeSystemManager _computeSystemManager;

// Launch button operations
public ObservableCollection<OperationsViewModel> LaunchOperations { get; set; }
public ObservableCollection<OperationsViewModel> LaunchOperations { get; set; } = new();

public ObservableCollection<CardProperty> Properties { get; set; } = new();

Expand All @@ -68,36 +70,32 @@ public ComputeSystemViewModel(
AssociatedProviderId = ComputeSystem.AssociatedProviderId!;
ComputeSystemId = ComputeSystem.Id!;
_removalAction = removalAction;
ShouldShowLaunchOperation = true;

if (!string.IsNullOrEmpty(ComputeSystem.SupplementalDisplayName))
{
AlternativeName = new string("(" + ComputeSystem.SupplementalDisplayName + ")");
}

LaunchOperations = new ObservableCollection<OperationsViewModel>(DataExtractor.FillLaunchButtonOperations(ComputeSystem));
DotOperations = new ObservableCollection<OperationsViewModel>(DataExtractor.FillDotButtonOperations(ComputeSystem, _windowEx));
HeaderImage = CardProperty.ConvertMsResourceToIcon(provider.Icon, packageFullName);
ComputeSystem.StateChanged += _computeSystemManager.OnComputeSystemStateChanged;
_computeSystemManager.ComputeSystemStateChanged += OnComputeSystemStateChanged;

_stringResource = new StringResource("DevHome.Environments.pri", "DevHome.Environments/Resources");
RegisterForAllOperationMessages();
}

public async Task InitializeCardDataAsync()
{
await InitializeStateAsync();
await SetBodyImageAsync();
await SetPropertiesAsync();
await InitializePinDataAsync();
await InitializeOperationDataAsync();
}

private async Task InitializePinDataAsync()
private async Task InitializeOperationDataAsync()
{
// We know ComputeSystem and DotOperations are initialized in the constructor so it's safe to use
var operations = new ObservableCollection<OperationsViewModel>(await DataExtractor.FillDotButtonPinOperationsAsync(ComputeSystem!));
foreach (var operation in operations)
RegisterForAllOperationMessages(DataExtractor.FillDotButtonOperations(ComputeSystem!, _windowEx), DataExtractor.FillLaunchButtonOperations(ComputeSystem!));

foreach (var operation in await DataExtractor.FillDotButtonPinOperationsAsync(ComputeSystem!))
{
DotOperations!.Add(operation);
}
Expand Down Expand Up @@ -132,11 +130,15 @@ private async Task SetPropertiesAsync()

public void OnComputeSystemStateChanged(ComputeSystem sender, ComputeSystemState newState)
{
_windowEx.DispatcherQueue.TryEnqueue(() =>
_windowEx.DispatcherQueue.EnqueueAsync(async () =>
{
if (sender.Id == ComputeSystem!.Id)
{
UpdateOperationsPostCreation(State, newState);
// The supported operations for a compute system can change based on the current state of the compute system.
// So we need to rebuild the dot and launch operations that appear in the UI based on the current
// supported operations of the compute system. InitializeOperationDataAsync will take care of this for us, by using
// the DataExtractor helper.
await InitializeOperationDataAsync();
State = newState;
StateColor = ComputeSystemHelpers.GetColorBasedOnState(newState);
SetupOperationProgressBasedOnState();
Expand Down Expand Up @@ -268,8 +270,6 @@ public void Receive(ComputeSystemOperationCompletedMessage message)
"Environment_OperationInvoked_Event",
LogLevel.Measure,
new EnvironmentOperationUserEvent(completionStatus, data.ComputeSystemOperation, ComputeSystem!.AssociatedProviderId, data.AdditionalContext, data.ActivityId));
IsOperationInProgress = false;
});
}

Expand All @@ -278,18 +278,25 @@ public void Receive(ComputeSystemOperationCompletedMessage message)
/// DotOperation and LaunchOperation lists. When there is an operation this ViewModel will receive the started and
/// the completed messages.
/// </summary>
private void RegisterForAllOperationMessages()
private void RegisterForAllOperationMessages(List<OperationsViewModel> dotOperations, List<OperationsViewModel> launchOperations)
{
_log.Information($"Registering ComputeSystemViewModel '{Name}' from provider '{ProviderDisplayName}' with WeakReferenceMessenger at {DateTime.Now}");

foreach (var dotOperation in DotOperations!)
// Unregister from all operation messages
WeakReferenceMessenger.Default.UnregisterAll(this);
LaunchOperations.Clear();
DotOperations!.Clear();

foreach (var dotOperation in dotOperations)
{
DotOperations.Add(dotOperation);
WeakReferenceMessenger.Default.Register<ComputeSystemOperationStartedMessage, OperationsViewModel>(this, dotOperation);
WeakReferenceMessenger.Default.Register<ComputeSystemOperationCompletedMessage, OperationsViewModel>(this, dotOperation);
}

foreach (var launchOperation in LaunchOperations!)
foreach (var launchOperation in launchOperations)
{
LaunchOperations.Add(launchOperation);
WeakReferenceMessenger.Default.Register<ComputeSystemOperationStartedMessage, OperationsViewModel>(this, launchOperation);
WeakReferenceMessenger.Default.Register<ComputeSystemOperationCompletedMessage, OperationsViewModel>(this, launchOperation);
}
Expand Down Expand Up @@ -323,7 +330,7 @@ private void SetupOperationProgressBasedOnState()
IsOperationInProgress = false;
}

if ((State != ComputeSystemState.Creating) || (State != ComputeSystemState.Deleting))
if ((State != ComputeSystemState.Creating) && (State != ComputeSystemState.Deleting))
{
ShouldShowLaunchOperation = true;
}
Expand All @@ -333,24 +340,4 @@ private void SetupOperationProgressBasedOnState()
RemoveComputeSystem();
}
}

private void UpdateOperationsPostCreation(ComputeSystemState previousState, ComputeSystemState newState)
{
// supported operations may have changed after creation, so we'll update them
if ((previousState == ComputeSystemState.Creating) && (previousState != newState))
{
LaunchOperations.Clear();
DotOperations!.Clear();

foreach (var buttonOperation in DataExtractor.FillLaunchButtonOperations(ComputeSystem!))
{
LaunchOperations.Add(buttonOperation);
}

foreach (var dotOperation in DataExtractor.FillDotButtonOperations(ComputeSystem!, _windowEx))
{
LaunchOperations.Add(dotOperation);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
x:Uid="ms-resource:///DevHome.Environments/Resources/LaunchButton"
Style="{StaticResource CardBodySplitButtonStyle}">
<SplitButton.Flyout>
<customControls:CardFlyout ItemsViewModels="{x:Bind LaunchOperations}"/>
<customControls:CardFlyout ItemsViewModels="{x:Bind LaunchOperations, Mode=OneWay}"/>
</SplitButton.Flyout>
</SplitButton>
</DataTemplate>
Expand All @@ -53,7 +53,7 @@
<Button
Style="{StaticResource HorizontalThreeDotsStyle}">
<Button.Flyout>
<customControls:CardFlyout ItemsViewModels="{x:Bind DotOperations}"/>
<customControls:CardFlyout ItemsViewModels="{x:Bind DotOperations, Mode=OneWay}"/>
</Button.Flyout>
</Button>
</Grid>
Expand All @@ -65,7 +65,7 @@
<Button
Style="{StaticResource HorizontalThreeDotsStyle}">
<Button.Flyout>
<customControls:CardFlyout ItemsViewModels="{x:Bind DotOperations}"/>
<customControls:CardFlyout ItemsViewModels="{x:Bind DotOperations, Mode=OneWay}" />
</Button.Flyout>
</Button>
</Grid>
Expand Down

0 comments on commit 6d32f25

Please sign in to comment.