Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Update environments to show supported operations dynamically based the state change events. #2732

Merged
merged 4 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This'll crash if args.OldValue isn't an INotifyCollectionChanged is that okay in this context?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest using "as" rather than c style casting so you can null check.


// 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