Remove variable and profile logic

This commit is contained in:
Stefan Markovic
2023-09-18 17:11:50 +02:00
parent fd1cfef3f8
commit d708e8bb7c
7 changed files with 282 additions and 41 deletions

View File

@@ -11,6 +11,105 @@
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="SubtleButtonBackground" Color="{ThemeResource SubtleFillColorTransparent}" />
<SolidColorBrush x:Key="SubtleButtonBackgroundPointerOver" Color="{ThemeResource SubtleFillColorSecondary}" />
<SolidColorBrush x:Key="SubtleButtonBackgroundPressed" Color="{ThemeResource SubtleFillColorTertiary}" />
<SolidColorBrush x:Key="SubtleButtonBackgroundDisabled" Color="{ThemeResource ControlFillColorDisabled}" />
<SolidColorBrush x:Key="SubtleButtonForeground" Color="{ThemeResource TextFillColorPrimary}" />
<SolidColorBrush x:Key="SubtleButtonForegroundPointerOver" Color="{ThemeResource TextFillColorPrimary}" />
<SolidColorBrush x:Key="SubtleButtonForegroundPressed" Color="{ThemeResource TextFillColorSecondary}" />
<SolidColorBrush x:Key="SubtleButtonForegroundDisabled" Color="{ThemeResource TextFillColorDisabled}" />
<Style x:Key="SubtleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter
x:Name="ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -10,10 +10,10 @@
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:labs="using:CommunityToolkit.Labs.WinUI"
xmlns:local="using:EnvironmentVariables.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:EnvironmentVariables.Models"
xmlns:ui="using:CommunityToolkit.WinUI.UI"
x:Name="RootPage"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="VariableTemplate" x:DataType="models:Variable">
@@ -21,11 +21,22 @@
Click="EditVariable_Click"
CommandParameter="{x:Bind (models:Variable)}"
Header="{x:Bind Name, Mode=TwoWay}"
IsClickEnabled="{x:Bind Editable, Mode=OneWay}">
IsClickEnabled="{x:Bind IsEditable, Mode=OneWay}">
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind Values, Mode=TwoWay}" />
<labs:SettingsCard.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem
x:Uid="DeleteMenuItem"
Click="Delete_Variable_Click"
CommandParameter="{x:Bind (models:Variable)}"
Icon="Delete"
IsEnabled="{x:Bind IsEditable, Mode=OneWay}" />
</MenuFlyout>
</labs:SettingsCard.ContextFlyout>
</labs:SettingsCard>
</DataTemplate>
@@ -98,7 +109,19 @@
ItemTemplate="{StaticResource VariableTemplate}"
ItemsSource="{x:Bind Variables, Mode=OneWay}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind Mode=TwoWay, Path=IsEnabled}" />
<StackPanel Orientation="Horizontal">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind Mode=TwoWay, Path=IsEnabled}" />
<Button
x:Name="RemoveProfileBtn"
x:Uid="RemoveProfileBtn"
Width="40"
Height="36"
Click="RemoveProfileBtn_Click"
CommandParameter="{x:Bind (models:ProfileVariablesSet)}"
Content="&#xE74D;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Style="{StaticResource SubtleButtonStyle}" />
</StackPanel>
</labs:SettingsExpander>
</DataTemplate>
</ItemsControl.ItemTemplate>

View File

@@ -17,7 +17,7 @@ namespace EnvironmentVariables.Views
{
public MainViewModel ViewModel { get; private set; }
public ICommand EditCommand => new RelayCommand<Variable>(EditVariable);
public ICommand EditCommand => new RelayCommand<SettingsCard>(EditVariable);
public ICommand NewProfileCommand => new AsyncRelayCommand(AddProfileAsync);
@@ -32,15 +32,16 @@ namespace EnvironmentVariables.Views
DataContext = ViewModel;
}
private async Task ShowEditDialogAsync(Variable variable)
private async Task ShowEditDialogAsync(SettingsCard card)
{
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
EditVariableDialog.Title = resourceLoader.GetString("EditVariableDialog_Title");
EditVariableDialog.PrimaryButtonText = resourceLoader.GetString("SaveBtn");
EditVariableDialog.PrimaryButtonCommand = EditCommand;
EditVariableDialog.PrimaryButtonCommandParameter = variable;
EditVariableDialog.PrimaryButtonCommandParameter = card;
var variable = card.CommandParameter as Variable;
var clone = variable.Clone();
EditVariableDialog.DataContext = clone;
@@ -52,14 +53,16 @@ namespace EnvironmentVariables.Views
SettingsCard card = sender as SettingsCard;
if (card != null)
{
await ShowEditDialogAsync(card.CommandParameter as Variable);
await ShowEditDialogAsync(card);
}
}
private void EditVariable(Variable original)
private void EditVariable(SettingsCard card)
{
var variableSet = card.DataContext as ProfileVariablesSet;
var original = card.CommandParameter as Variable;
var edited = EditVariableDialog.DataContext as Variable;
ViewModel.EditVariable(original, edited);
ViewModel.EditVariable(original, edited, variableSet);
}
private async Task AddProfileAsync()
@@ -80,6 +83,30 @@ namespace EnvironmentVariables.Views
ViewModel.AddProfile(profile);
}
private async void RemoveProfileBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var button = sender as Button;
var profile = button.CommandParameter as ProfileVariablesSet;
if (profile != null)
{
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
ContentDialog dialog = new ContentDialog();
dialog.XamlRoot = RootPage.XamlRoot;
dialog.Title = profile.Name;
dialog.PrimaryButtonText = resourceLoader.GetString("Yes");
dialog.CloseButtonText = resourceLoader.GetString("No");
dialog.DefaultButton = ContentDialogButton.Primary;
dialog.Content = new TextBlock() { Text = resourceLoader.GetString("Delete_Dialog_Description") };
dialog.PrimaryButtonClick += (s, args) =>
{
ViewModel.RemoveProfile(profile);
};
var result = await dialog.ShowAsync();
}
}
private void AddVariable()
{
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
@@ -104,5 +131,17 @@ namespace EnvironmentVariables.Views
ExistingVariablesListView.SelectedItems.Clear();
AddVariableFlyout.Hide();
}
private void Delete_Variable_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
MenuFlyoutItem menuItem = sender as MenuFlyoutItem;
var variableSet = menuItem.DataContext as ProfileVariablesSet;
var variable = menuItem.CommandParameter as Variable;
if (variable != null)
{
ViewModel.DeleteVariable(variable, variableSet);
}
}
}
}

View File

@@ -2,9 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using EnvironmentVariables.Helpers;
@@ -19,7 +19,7 @@ namespace EnvironmentVariables.Models
private string _values;
[JsonIgnore]
public bool Editable
public bool IsEditable
{
get
{
@@ -49,44 +49,37 @@ namespace EnvironmentVariables.Models
}
}
internal void Update(Variable edited)
internal Task Update(Variable edited, bool propagateChange)
{
bool changed = Name != edited.Name || Values != edited.Values;
bool nameChanged = Name != edited.Name;
bool success = false;
bool success = true;
if (changed)
var clone = this.Clone();
// Update state
Name = edited.Name;
Values = edited.Values;
ValuesList = new List<string>(Values.Split(';'));
return Task.Run(() =>
{
// Apply changes
if (nameChanged)
if (propagateChange)
{
success = EnvironmentVariablesHelper.UnsetVariable(this);
}
success = EnvironmentVariablesHelper.SetVariable(edited);
// Update state
if (success)
{
Name = edited.Name;
Values = edited.Values;
ValuesList = new List<string>();
var splitValues = Values.Split(';');
if (splitValues.Length > 0)
if (nameChanged)
{
foreach (var splitValue in splitValues)
{
ValuesList.Add(splitValue);
}
success = EnvironmentVariablesHelper.UnsetVariable(clone);
}
success = EnvironmentVariablesHelper.SetVariable(this);
}
else
if (!success)
{
// show error
// Show error
}
}
});
}
internal Variable Clone(bool profile = false)

View File

@@ -18,7 +18,7 @@ namespace EnvironmentVariables.Models
private static readonly string SystemIconPath = "/Assets/EnvironmentVariables/SystemIcon.png";
protected static readonly string ProfileIconPath = "/Assets/EnvironmentVariables/ProfileIcon.png";
public Guid Id { get; }
public Guid Id { get; set; }
[ObservableProperty]
private string _name;

View File

@@ -203,7 +203,19 @@
<data name="NewProfileVariablesListViewHeader.Text" xml:space="preserve">
<value>Variables</value>
</data>
<data name="DeleteMenuItem.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Delete_Dialog_Description" xml:space="preserve">
<value>Are you sure you want to delete this profile? Deleting applied profile will remove all profile variables.</value>
</data>
<data name="EditSystemDefaultSetInfoBar.Title" xml:space="preserve">
<value>You need to run as administrator to edit System environment variables</value>
</data>
<data name="No" xml:space="preserve">
<value>No</value>
</data>
<data name="Yes" xml:space="preserve">
<value>Yes</value>
</data>
</root>

View File

@@ -8,12 +8,14 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using EnvironmentVariables.Helpers;
using EnvironmentVariables.Models;
using ManagedCommon;
using Microsoft.UI.Dispatching;
using Windows.Foundation.Collections;
namespace EnvironmentVariables.ViewModels
{
@@ -111,9 +113,25 @@ namespace EnvironmentVariables.ViewModels
AppliedVariables = new ObservableCollection<Variable>(variables);
}
internal void EditVariable(Variable original, Variable edited)
internal void EditVariable(Variable original, Variable edited, ProfileVariablesSet variablesSet)
{
original.Update(edited);
bool propagateChange = variablesSet == null /* not a profile */ || variablesSet.Id.Equals(AppliedProfile?.Id);
bool changed = original.Name != edited.Name || original.Values != edited.Values;
if (changed)
{
ApplyingChanges = true;
var task = original.Update(edited, propagateChange);
PopulateAppliedVariables();
task.ContinueWith(x =>
{
_dispatcherQueue.TryEnqueue(() =>
{
ApplyingChanges = false;
});
});
_ = Task.Run(SaveAsync);
}
}
internal void AddProfile(ProfileVariablesSet profile)
@@ -202,5 +220,62 @@ namespace EnvironmentVariables.ViewModels
PopulateAppliedVariables();
}
}
internal void RemoveProfile(ProfileVariablesSet profile)
{
if (profile.IsEnabled)
{
UnsetAppliedProfile();
}
Profiles.Remove(profile);
_ = Task.Run(SaveAsync);
}
internal void DeleteVariable(Variable variable, ProfileVariablesSet profile)
{
bool propagateChange = true;
if (profile != null)
{
// Profile variable
profile.Variables.Remove(variable);
if (!profile.IsEnabled)
{
propagateChange = false;
}
}
else
{
if (variable.ParentType == VariablesSetType.User)
{
UserDefaultSet.Variables.Remove(variable);
}
else if (variable.ParentType == VariablesSetType.System)
{
SystemDefaultSet.Variables.Remove(variable);
}
}
if (propagateChange)
{
ApplyingChanges = true;
var task = Task.Run(() =>
{
EnvironmentVariablesHelper.UnsetVariable(variable);
});
task.ContinueWith((a) =>
{
_dispatcherQueue.TryEnqueue(() =>
{
ApplyingChanges = false;
});
});
}
PopulateAppliedVariables();
}
}
}