mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
[EnvVar][Hosts][RegPrev]Decouple and refactor to make it "packable" as nuget package (#32604)
* WIP Hosts - remove deps * Add consumer app * Move App and MainWindow to Consumer app. Make Hosts dll * Try consume it * Fix errors * Make it work with custom build targets * Dependency injection Refactor Explicit page creation Wire missing dependencies * Fix installer * Remove unneeded stuff * Fix build again * Extract UI and logic from MainWindow to RegistryPreviewMainPage * Convert to lib Change namespace to RegistryPreviewUILib Remove PT deps * Add exe app and move App.xaml and MainWindow.xaml * Consume the lib * Update Hosts package creation * Fix RegistryPreview package creation * Rename RegistryPreviewUI back to RegistryPreview * Back to consuming lib * Ship icons and assets in nuget packages * Rename to EnvironmentVariablesUILib and convert to lib * Add app and consume * Telemetry * GPO * nuget * Rename HostsPackageConsumer to Hosts and Hosts lib to HostsUILib * Assets cleanup * nuget struct * v0 * assets * [Hosts] Re-add AppList to Lib Assets, [RegPrev] Copy lib assets to out dir * Sign UI dlls * Revert WinUIEx bump * Cleanup * Align deps * version exception dll * Fix RegistryPreview crashes * XAML format * XAML format 2 * Pack .pri files in lib/ dir --------- Co-authored-by: Darshak Bhatti <dabhatti@microsoft.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 460 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 432 B |
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 432 B |
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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 EnvironmentVariablesUILib.Models;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var type = (EnvironmentState)value;
|
||||
return type switch
|
||||
{
|
||||
EnvironmentState.Unchanged => false,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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 EnvironmentVariablesUILib.Helpers;
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToMessageConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
var type = (EnvironmentState)value;
|
||||
return type switch
|
||||
{
|
||||
EnvironmentState.Unchanged => string.Empty,
|
||||
EnvironmentState.ChangedOnStartup => resourceLoader.GetString("StateNotUpToDateOnStartupMsg"),
|
||||
EnvironmentState.EnvironmentMessageReceived => resourceLoader.GetString("StateNotUpToDateEnvironmentMessageReceivedMsg"),
|
||||
EnvironmentState.ProfileNotApplicable => resourceLoader.GetString("StateProfileNotApplicableMsg"),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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 EnvironmentVariablesUILib.Helpers;
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToTitleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
|
||||
var type = (EnvironmentState)value;
|
||||
return type switch
|
||||
{
|
||||
EnvironmentState.ProfileNotApplicable => resourceLoader.GetString("ProfileNotApplicableTitle"),
|
||||
_ => resourceLoader.GetString("StateNotUpToDateTitle"),
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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 EnvironmentVariablesUILib.Models;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class EnvironmentStateToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var type = (EnvironmentState)value;
|
||||
return type switch
|
||||
{
|
||||
EnvironmentState.Unchanged => Visibility.Collapsed,
|
||||
_ => Visibility.Visible,
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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 EnvironmentVariablesUILib.Models;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Converters;
|
||||
|
||||
public class VariableTypeToGlyphConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var type = (VariablesSetType)value;
|
||||
return type switch
|
||||
{
|
||||
VariablesSetType.User => "\uE77B",
|
||||
VariablesSetType.System => "\uE977",
|
||||
VariablesSetType.Profile => "\uEDE3",
|
||||
VariablesSetType.Path => "\uE8AC",
|
||||
VariablesSetType.Duplicate => "\uE8C8",
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,912 @@
|
||||
<Page
|
||||
x:Class="EnvironmentVariablesUILib.EnvironmentVariablesMainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="using:EnvironmentVariablesUILib.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:EnvironmentVariablesUILib.Models"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
x:Name="RootPage"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<ResourceDictionary>
|
||||
<!-- These resource dictionaries are needed to reference styles part of SettingsControls -->
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsCard/SettingsCard.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SubtleFillColorTransparent" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SubtleFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SubtleFillColorTertiary" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SubtleFillColorTransparent" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="TextFillColorPrimary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="TextFillColorSecondary" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="TextFillColorDisabled" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="SubtleButtonBackground" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPointerOver" ResourceKey="SystemColorHighlightTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundPressed" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonBorderBrush" ResourceKey="SystemColorWindowColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPointerOver" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushPressed" ResourceKey="SystemColorHighlightColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonBorderBrushDisabled" ResourceKey="SystemColorGrayTextColor" />
|
||||
|
||||
<StaticResource x:Key="SubtleButtonForeground" ResourceKey="SystemColorButtonTextColorBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
|
||||
<StaticResource x:Key="SubtleButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
|
||||
</ResourceDictionary>
|
||||
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<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="BorderBrush" Value="{ThemeResource SubtleButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<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}"
|
||||
Foreground="{TemplateBinding Foreground}">
|
||||
<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="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPointerOver}" />
|
||||
</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="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushPressed}" />
|
||||
</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="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBorderBrushDisabled}" />
|
||||
</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>
|
||||
|
||||
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>
|
||||
<Style x:Key="SecondaryTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="VariableTemplate" x:DataType="models:Variable">
|
||||
<tkcontrols:SettingsCard
|
||||
CommandParameter="{x:Bind (models:Variable)}"
|
||||
IsActionIconVisible="False"
|
||||
IsClickEnabled="False"
|
||||
Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<tkcontrols:SettingsCard.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{x:Bind Name, Mode=TwoWay}" />
|
||||
<FontIcon
|
||||
Margin="6,0,6,0"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph=""
|
||||
Visibility="{x:Bind IsAppliedFromProfile, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="VariableIsAppliedByActiveProfileTooltip" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard.Header>
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<ItemsControl
|
||||
x:Name="VariableValuesList"
|
||||
HorizontalAlignment="Left"
|
||||
ItemsSource="{x:Bind ValuesList, Mode=TwoWay}"
|
||||
Visibility="{x:Bind ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="x:String">
|
||||
<TextBlock
|
||||
Margin="0,2,0,2"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Text}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Values, Mode=TwoWay}"
|
||||
Visibility="{x:Bind ShowAsList, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
<Button
|
||||
Content="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind IsEditable}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="EditItem"
|
||||
Click="EditVariable_Click"
|
||||
CommandParameter="{x:Bind (models:Variable)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="RemoveItem"
|
||||
Click="Delete_Variable_Click"
|
||||
CommandParameter="{x:Bind (models:Variable)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="More_Options_ButtonTooltip" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</tkcontrols:SettingsCard>
|
||||
</DataTemplate>
|
||||
|
||||
<converters:VariableTypeToGlyphConverter x:Key="VariableTypeToGlyphConverter" />
|
||||
<converters:EnvironmentStateToBoolConverter x:Key="EnvironmentStateToBoolConverter" />
|
||||
<converters:EnvironmentStateToMessageConverter x:Key="EnvironmentStateToMessageConverter" />
|
||||
<converters:EnvironmentStateToTitleConverter x:Key="EnvironmentStateToTitleConverter" />
|
||||
<converters:EnvironmentStateToVisibilityConverter x:Key="EnvironmentStateToVisibilityConverter" />
|
||||
|
||||
<tkconverters:BoolToVisibilityConverter
|
||||
x:Key="BoolToInvertedVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
<tkconverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<tkconverters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
|
||||
|
||||
<Grid Margin="16" RowSpacing="8">
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
<RowDefinition Height="Auto" />
|
||||
<!-- buttons -->
|
||||
<RowDefinition Height="Auto" />
|
||||
<!-- Warning messages -->
|
||||
<RowDefinition Height="*" />
|
||||
<!-- content -->
|
||||
<RowDefinition Height="Auto" />
|
||||
<!-- content -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- buttons -->
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Command="{x:Bind NewProfileCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
x:Name="Icon"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="NewProfileBtn" />
|
||||
</StackPanel>
|
||||
<Button.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="N" Modifiers="Control" />
|
||||
</Button.KeyboardAccelerators>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<InfoBar
|
||||
x:Name="InvalidStateInfoBar"
|
||||
x:Uid="InvalidStateInfoBar"
|
||||
Title="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToTitleConverter}}"
|
||||
CloseButtonClick="InvalidStateInfoBar_CloseButtonClick"
|
||||
IsOpen="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToBoolConverter}}"
|
||||
Message="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToMessageConverter}}"
|
||||
Severity="Warning"
|
||||
Visibility="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToVisibilityConverter}}">
|
||||
<InfoBar.ActionButton>
|
||||
<Button
|
||||
Click="ReloadButton_Click"
|
||||
Content="{ui:FontIcon Glyph=}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.IsInfoBarButtonVisible, Mode=OneWay}" />
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
Margin="0,24,0,0"
|
||||
ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- Left side -->
|
||||
<ColumnDefinition Width="*" />
|
||||
<!-- GridSplitter -->
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<!-- Applied values -->
|
||||
<ColumnDefinition Width="480" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollViewer
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="4">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock x:Uid="ProfilesLbl" Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="ProfilesDescriptionLbl" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
</StackPanel>
|
||||
<ItemsControl Margin="0,4,0,0" ItemsSource="{x:Bind ViewModel.Profiles, Mode=TwoWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:ProfileVariablesSet">
|
||||
<tkcontrols:SettingsExpander
|
||||
Margin="0,0,0,4"
|
||||
Header="{x:Bind Name, Mode=TwoWay}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<ItemsRepeater
|
||||
ItemTemplate="{StaticResource VariableTemplate}"
|
||||
ItemsSource="{x:Bind Variables, Mode=OneWay}"
|
||||
TabFocusNavigation="Local"
|
||||
VerticalCacheLength="10" />
|
||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<ToggleSwitch
|
||||
x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind IsEnabled, Mode=TwoWay}"
|
||||
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}" />
|
||||
<Button Content="{ui:FontIcon Glyph=}" Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="EditItem"
|
||||
Click="EditProfileBtn_Click"
|
||||
CommandParameter="{x:Bind (models:ProfileVariablesSet)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="RemoveItem"
|
||||
Click="RemoveProfileBtn_Click"
|
||||
CommandParameter="{x:Bind (models:ProfileVariablesSet)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="More_Options_ButtonTooltip" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
|
||||
<StackPanel
|
||||
Margin="0,24,0,0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock x:Uid="DefaultVariablesLbl" Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="DefaultVariablesDescriptionLbl" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
</StackPanel>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
Margin="0,4,0,0"
|
||||
Header="{x:Bind ViewModel.UserDefaultSet.Name}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<ItemsRepeater
|
||||
ItemTemplate="{StaticResource VariableTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.UserDefaultSet.Variables, Mode=OneWay}"
|
||||
TabFocusNavigation="Local"
|
||||
VerticalCacheLength="10" />
|
||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="AddDefaultVariableUserBtn"
|
||||
x:Uid="AddVariableContent"
|
||||
Click="AddDefaultVariableBtn_Click"
|
||||
CommandParameter="{x:Bind ViewModel.UserDefaultSet}"
|
||||
Content="Add variable"
|
||||
Style="{StaticResource AccentButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="AddVariableTooltip" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsExpander>
|
||||
|
||||
<tkcontrols:SettingsExpander Header="{x:Bind ViewModel.SystemDefaultSet.Name}" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<ItemsRepeater
|
||||
ItemTemplate="{StaticResource VariableTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.SystemDefaultSet.Variables, Mode=OneWay}"
|
||||
TabFocusNavigation="Local"
|
||||
VerticalCacheLength="10" />
|
||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<tkcontrols:SettingsExpander.Description>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="4"
|
||||
Visibility="{x:Bind ViewModel.IsElevated, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
<Border
|
||||
Width="12"
|
||||
Height="12"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="12">
|
||||
<FontIcon
|
||||
FontSize="8"
|
||||
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
</Border>
|
||||
<TextBlock x:Uid="EditSystemDefaultSetInfoBar" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsExpander.Description>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="AddDefaultVariableSystemBtn"
|
||||
x:Uid="AddVariableContent"
|
||||
Click="AddDefaultVariableBtn_Click"
|
||||
CommandParameter="{x:Bind ViewModel.SystemDefaultSet}"
|
||||
IsEnabled="{x:Bind ViewModel.IsElevated}"
|
||||
Style="{StaticResource AccentButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="AddVariableTooltip" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<tkcontrols:GridSplitter
|
||||
x:Name="Splitter"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="8"
|
||||
Foreground="Transparent"
|
||||
ResizeBehavior="BasedOnAlignment"
|
||||
ResizeDirection="Auto" />
|
||||
|
||||
<Grid
|
||||
x:Name="AppliedValuesPanel"
|
||||
Grid.Row="2"
|
||||
Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock x:Uid="AppliedVariablesLbl" Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="AppliedVariablesDescriptionLbl" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
</StackPanel>
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="0,8,0,0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<ScrollViewer x:Name="AppliedVariablesScrollViewer" HorizontalScrollBarVisibility="Auto">
|
||||
<ItemsControl Margin="12,8,12,0" ItemsSource="{x:Bind ViewModel.AppliedVariables, Mode=TwoWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Variable">
|
||||
<Grid Height="56" ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="24" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="{x:Bind ParentType, Mode=OneWay, Converter={StaticResource VariableTypeToGlyphConverter}}" />
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
Text="{x:Bind Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Values}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="{x:Bind Values}" TextWrapping="WrapWholeWords" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
<ContentDialog
|
||||
x:Name="AddDefaultVariableDialog"
|
||||
x:Uid="AddDefaultVariableDialog"
|
||||
IsPrimaryButtonEnabled="{Binding Valid, Mode=OneWay}"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<ContentDialog.DataContext>
|
||||
<models:Variable />
|
||||
</ContentDialog.DataContext>
|
||||
<ScrollViewer>
|
||||
<StackPanel
|
||||
MinWidth="320"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="16">
|
||||
<TextBox
|
||||
x:Uid="AddNewVariableName"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextChanged="AddDefaultVariableNameTxtBox_TextChanged" />
|
||||
<TextBox
|
||||
x:Uid="AddNewVariableValue"
|
||||
AcceptsReturn="False"
|
||||
IsSpellCheckEnabled="False"
|
||||
ScrollViewer.IsVerticalRailEnabled="True"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Enabled"
|
||||
Text="{Binding Values, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
x:Name="EditVariableDialog"
|
||||
x:Uid="EditVariableDialog"
|
||||
IsPrimaryButtonEnabled="{Binding Valid, Mode=OneWay}"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<ContentDialog.DataContext>
|
||||
<models:Variable />
|
||||
</ContentDialog.DataContext>
|
||||
<ScrollViewer>
|
||||
<StackPanel
|
||||
MinWidth="320"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="16">
|
||||
<TextBox
|
||||
x:Name="EditVariableDialogNameTxtBox"
|
||||
x:Uid="AddNewVariableName"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextChanged="EditVariableDialogNameTxtBox_TextChanged" />
|
||||
<TextBox
|
||||
x:Name="EditVariableDialogValueTxtBox"
|
||||
x:Uid="AddNewVariableValue"
|
||||
AcceptsReturn="False"
|
||||
IsSpellCheckEnabled="False"
|
||||
ScrollViewer.IsVerticalRailEnabled="True"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Enabled"
|
||||
Text="{Binding Values, Mode=TwoWay}"
|
||||
TextChanged="EditVariableDialogValueTxtBox_TextChanged"
|
||||
TextWrapping="Wrap" />
|
||||
<MenuFlyoutSeparator Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<ItemsControl
|
||||
x:Name="EditVariableValuesList"
|
||||
Margin="0,-8,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding ValuesList, Mode=TwoWay}"
|
||||
Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="40" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
LostFocus="EditVariableValuesListTextBox_LostFocus"
|
||||
Text="{Binding Text}" />
|
||||
<Button
|
||||
x:Uid="More_Options_Button"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveUp"
|
||||
Click="ReorderButtonUp_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveDown"
|
||||
Click="ReorderButtonDown_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="RemoveListItem"
|
||||
Click="RemoveListVariableButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="InsertListEntryBefore"
|
||||
Click="InsertListEntryBeforeButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="InsertListEntryAfter"
|
||||
Click="InsertListEntryAfterButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="More_Options_ButtonTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
x:Name="AddProfileDialog"
|
||||
x:Uid="AddProfileDialog"
|
||||
IsPrimaryButtonEnabled="{Binding Valid, Mode=OneWay}"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<ContentDialog.DataContext>
|
||||
<models:ProfileVariablesSet />
|
||||
</ContentDialog.DataContext>
|
||||
<ScrollViewer>
|
||||
<StackPanel
|
||||
MinWidth="360"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="12">
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
x:Uid="NewProfileNameTxtBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<ToggleSwitch
|
||||
x:Uid="NewProfileEnabled"
|
||||
Grid.Column="1"
|
||||
MinWidth="0"
|
||||
Margin="0,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
IsOn="{Binding IsEnabled, Mode=TwoWay}"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Uid="NewProfileVariablesListViewHeader" Margin="0,12,0,8" />
|
||||
<TextBlock
|
||||
x:Uid="NewProfileVariablesListViewApplyToSystemHeader"
|
||||
Grid.Column="1"
|
||||
Margin="0,12,0,8"
|
||||
HorizontalAlignment="Right"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<ItemsControl
|
||||
x:Name="NewProfileVariablesListView"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,-8,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Variables, Mode=TwoWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Variable">
|
||||
<Grid Height="48" ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="40" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Vertical">
|
||||
<TextBlock
|
||||
Text="{x:Bind Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Values}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
<ToggleSwitch
|
||||
x:Uid="ApplyAsSystemBtn"
|
||||
Grid.Column="1"
|
||||
IsOn="{x:Bind Path=ApplyToSystem, Mode=TwoWay}"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Button>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
x:Name="AddVariableIcon"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="AddVariableBtn" />
|
||||
</StackPanel>
|
||||
<Button.Flyout>
|
||||
<Flyout
|
||||
x:Name="AddVariableFlyout"
|
||||
Closed="AddVariableFlyout_Closed"
|
||||
Placement="Right"
|
||||
ShouldConstrainToRootBounds="False">
|
||||
<Grid Width="320" Height="480">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<tkcontrols:Segmented
|
||||
x:Name="SwitchViewsSegmentedView"
|
||||
MaxWidth="500"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectionMode="Single">
|
||||
<tkcontrols:SegmentedItem
|
||||
x:Name="AddNewVariableSegmentedItem"
|
||||
x:Uid="NewVariableSegmentedButton"
|
||||
Tag="NewVariable" />
|
||||
<tkcontrols:SegmentedItem
|
||||
x:Name="AddExistingVariableSegmentedItem"
|
||||
x:Uid="ExistingVariableSegmentedButton"
|
||||
Tag="ExistingVariable" />
|
||||
</tkcontrols:Segmented>
|
||||
|
||||
<tkcontrols:SwitchPresenter
|
||||
x:Name="AddVariableSwitchPresenter"
|
||||
Grid.Row="1"
|
||||
Value="{Binding SelectedItem.Tag, ElementName=SwitchViewsSegmentedView}">
|
||||
<tkcontrols:Case Value="NewVariable">
|
||||
<StackPanel Grid.Row="1" Orientation="Vertical">
|
||||
|
||||
<!-- Adding new variable -->
|
||||
<TextBox
|
||||
x:Name="AddNewVariableName"
|
||||
x:Uid="AddNewVariableName"
|
||||
Margin="0,16,0,0"
|
||||
TextChanged="AddNewVariableName_TextChanged" />
|
||||
|
||||
<TextBox
|
||||
x:Name="AddNewVariableValue"
|
||||
x:Uid="AddNewVariableValue"
|
||||
Margin="0,16,0,0" />
|
||||
</StackPanel>
|
||||
</tkcontrols:Case>
|
||||
<tkcontrols:Case Value="ExistingVariable">
|
||||
<!-- Adding existing variables -->
|
||||
<ListView
|
||||
x:Name="ExistingVariablesListView"
|
||||
Grid.Row="1"
|
||||
Margin="-12,12,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{x:Bind ViewModel.DefaultVariables.Variables, Mode=OneWay}"
|
||||
Loaded="ExistingVariablesListView_Loaded"
|
||||
SelectionChanged="ExistingVariablesListView_SelectionChanged"
|
||||
SelectionMode="Multiple">
|
||||
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Variable">
|
||||
<Grid Height="48" ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="20" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="{x:Bind ParentType, Mode=OneWay, Converter={StaticResource VariableTypeToGlyphConverter}}" />
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical">
|
||||
<TextBlock
|
||||
Text="{x:Bind Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Values}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
</tkcontrols:Case>
|
||||
</tkcontrols:SwitchPresenter>
|
||||
<Grid Grid.Row="2" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button
|
||||
x:Name="ConfirmAddVariableBtn"
|
||||
x:Uid="ConfirmAddVariableBtn"
|
||||
Margin="4,0,4,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Bind AddVariableCommand}"
|
||||
IsEnabled="False"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
|
||||
<Button
|
||||
x:Name="CancelAddVariableBtn"
|
||||
x:Uid="CancelAddVariableBtn"
|
||||
Grid.Column="1"
|
||||
Margin="4,0,4,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Bind CancelAddVariableCommand}" />
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,551 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
using EnvironmentVariablesUILib.ViewModels;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace EnvironmentVariablesUILib
|
||||
{
|
||||
public sealed partial class EnvironmentVariablesMainPage : Page
|
||||
{
|
||||
private sealed class RelayCommandParameter
|
||||
{
|
||||
public RelayCommandParameter(Variable variable, VariablesSet set)
|
||||
{
|
||||
Variable = variable;
|
||||
this.Set = set;
|
||||
}
|
||||
|
||||
public Variable Variable { get; set; }
|
||||
|
||||
public VariablesSet Set { get; set; }
|
||||
}
|
||||
|
||||
public MainViewModel ViewModel { get; private set; }
|
||||
|
||||
public ICommand EditCommand => new RelayCommand<RelayCommandParameter>(EditVariable);
|
||||
|
||||
public ICommand NewProfileCommand => new AsyncRelayCommand(AddProfileAsync);
|
||||
|
||||
public ICommand AddProfileCommand => new RelayCommand(AddProfile);
|
||||
|
||||
public ICommand UpdateProfileCommand => new RelayCommand(UpdateProfile);
|
||||
|
||||
public ICommand AddVariableCommand => new RelayCommand(AddVariable);
|
||||
|
||||
public ICommand CancelAddVariableCommand => new RelayCommand(CancelAddVariable);
|
||||
|
||||
public ICommand AddDefaultVariableCommand => new RelayCommand<DefaultVariablesSet>(AddDefaultVariable);
|
||||
|
||||
public EnvironmentVariablesMainPage(MainViewModel viewModel)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
ViewModel = viewModel;
|
||||
DataContext = ViewModel;
|
||||
|
||||
ViewModel.LoadEnvironmentVariables();
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(Variable variable, VariablesSet parentSet)
|
||||
{
|
||||
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
|
||||
EditVariableDialog.Title = resourceLoader.GetString("EditVariableDialog_Title");
|
||||
EditVariableDialog.PrimaryButtonText = resourceLoader.GetString("SaveBtn");
|
||||
EditVariableDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
|
||||
EditVariableDialog.PrimaryButtonCommand = EditCommand;
|
||||
EditVariableDialog.PrimaryButtonCommandParameter = new RelayCommandParameter(variable, parentSet);
|
||||
|
||||
var clone = variable.Clone();
|
||||
EditVariableDialog.DataContext = clone;
|
||||
|
||||
await EditVariableDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async void EditVariable_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var btn = sender as MenuFlyoutItem;
|
||||
var variablesSet = btn.DataContext as VariablesSet;
|
||||
var variable = btn.CommandParameter as Variable;
|
||||
|
||||
if (variable != null)
|
||||
{
|
||||
await ShowEditDialogAsync(variable, variablesSet);
|
||||
}
|
||||
}
|
||||
|
||||
private void EditVariable(RelayCommandParameter param)
|
||||
{
|
||||
var variableSet = param.Set as ProfileVariablesSet;
|
||||
var original = param.Variable;
|
||||
var edited = EditVariableDialog.DataContext as Variable;
|
||||
ViewModel.EditVariable(original, edited, variableSet);
|
||||
}
|
||||
|
||||
private async Task AddProfileAsync()
|
||||
{
|
||||
SwitchViewsSegmentedView.SelectedIndex = 0;
|
||||
|
||||
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
AddProfileDialog.Title = resourceLoader.GetString("AddNewProfileDialog_Title");
|
||||
AddProfileDialog.PrimaryButtonText = resourceLoader.GetString("AddBtn");
|
||||
AddProfileDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
|
||||
AddProfileDialog.PrimaryButtonCommand = AddProfileCommand;
|
||||
AddProfileDialog.DataContext = new ProfileVariablesSet(Guid.NewGuid(), string.Empty);
|
||||
|
||||
await AddProfileDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void AddProfile()
|
||||
{
|
||||
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
|
||||
ViewModel.AddProfile(profile);
|
||||
}
|
||||
|
||||
private void UpdateProfile()
|
||||
{
|
||||
var updatedProfile = AddProfileDialog.DataContext as ProfileVariablesSet;
|
||||
ViewModel.UpdateProfile(updatedProfile);
|
||||
}
|
||||
|
||||
private async void RemoveProfileBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var button = sender as MenuFlyoutItem;
|
||||
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"), TextWrapping = Microsoft.UI.Xaml.TextWrapping.WrapWholeWords };
|
||||
dialog.PrimaryButtonClick += (s, args) =>
|
||||
{
|
||||
ViewModel.RemoveProfile(profile);
|
||||
};
|
||||
|
||||
var result = await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddVariable()
|
||||
{
|
||||
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
|
||||
if (profile != null)
|
||||
{
|
||||
if (AddVariableSwitchPresenter.Value as string == "NewVariable")
|
||||
{
|
||||
profile.Variables.Add(new Variable(AddNewVariableName.Text, AddNewVariableValue.Text, VariablesSetType.Profile));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Variable variable in ExistingVariablesListView.SelectedItems)
|
||||
{
|
||||
if (!profile.Variables.Where(x => x.Name == variable.Name).Any())
|
||||
{
|
||||
var clone = variable.Clone(true);
|
||||
profile.Variables.Add(clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddNewVariableName.Text = string.Empty;
|
||||
AddNewVariableValue.Text = string.Empty;
|
||||
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
|
||||
ExistingVariablesListView.SelectedItems.Clear();
|
||||
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
|
||||
AddVariableFlyout.Hide();
|
||||
}
|
||||
|
||||
private void CancelAddVariable()
|
||||
{
|
||||
AddNewVariableName.Text = string.Empty;
|
||||
AddNewVariableValue.Text = string.Empty;
|
||||
|
||||
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
|
||||
ExistingVariablesListView.SelectedItems.Clear();
|
||||
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
|
||||
|
||||
AddVariableFlyout.Hide();
|
||||
}
|
||||
|
||||
private void AddDefaultVariable(DefaultVariablesSet set)
|
||||
{
|
||||
var variable = AddDefaultVariableDialog.DataContext as Variable;
|
||||
var type = set.Type;
|
||||
|
||||
ViewModel.AddDefaultVariable(variable, type);
|
||||
}
|
||||
|
||||
private async void Delete_Variable_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
MenuFlyoutItem selectedItem = sender as MenuFlyoutItem;
|
||||
var variableSet = selectedItem.DataContext as ProfileVariablesSet;
|
||||
var variable = selectedItem.CommandParameter as Variable;
|
||||
|
||||
if (variable != null)
|
||||
{
|
||||
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
ContentDialog dialog = new ContentDialog();
|
||||
dialog.XamlRoot = RootPage.XamlRoot;
|
||||
dialog.Title = variable.Name;
|
||||
dialog.PrimaryButtonText = resourceLoader.GetString("Yes");
|
||||
dialog.CloseButtonText = resourceLoader.GetString("No");
|
||||
dialog.DefaultButton = ContentDialogButton.Primary;
|
||||
dialog.Content = new TextBlock() { Text = resourceLoader.GetString("Delete_Variable_Description"), TextWrapping = Microsoft.UI.Xaml.TextWrapping.WrapWholeWords };
|
||||
dialog.PrimaryButtonClick += (s, args) =>
|
||||
{
|
||||
ViewModel.DeleteVariable(variable, variableSet);
|
||||
};
|
||||
var result = await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddNewVariableName_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
TextBox nameTxtBox = sender as TextBox;
|
||||
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
|
||||
|
||||
if (nameTxtBox != null)
|
||||
{
|
||||
if (nameTxtBox.Text.Length == 0 || nameTxtBox.Text.Length >= 255 || profile.Variables.Where(x => x.Name.Equals(nameTxtBox.Text, StringComparison.OrdinalIgnoreCase)).Any())
|
||||
{
|
||||
ConfirmAddVariableBtn.IsEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfirmAddVariableBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReloadButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.LoadEnvironmentVariables();
|
||||
ViewModel.EnvironmentState = EnvironmentState.Unchanged;
|
||||
}
|
||||
|
||||
private void ExistingVariablesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
|
||||
|
||||
int toRemove = -1;
|
||||
|
||||
if (e.AddedItems.Count > 0)
|
||||
{
|
||||
var list = sender as ListView;
|
||||
var duplicates = list.SelectedItems.GroupBy(x => ((Variable)x).Name.ToLowerInvariant()).Where(g => g.Count() > 1).ToList();
|
||||
|
||||
foreach (var dup in duplicates)
|
||||
{
|
||||
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
|
||||
list.SelectedItems.Remove(dup.ElementAt(1));
|
||||
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.RemovedItems.Count > 0)
|
||||
{
|
||||
Variable removedVariable = e.RemovedItems[0] as Variable;
|
||||
for (int i = 0; i < profile.Variables.Count; i++)
|
||||
{
|
||||
if (profile.Variables[i].Name == removedVariable.Name && profile.Variables[i].Values == removedVariable.Values)
|
||||
{
|
||||
toRemove = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != -1)
|
||||
{
|
||||
profile.Variables.RemoveAt(toRemove);
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmAddVariableBtn.IsEnabled = false;
|
||||
foreach (Variable variable in ExistingVariablesListView.SelectedItems)
|
||||
{
|
||||
if (variable != null)
|
||||
{
|
||||
if (!profile.Variables.Where(x => x.Name.Equals(variable.Name, StringComparison.Ordinal) && x.Values.Equals(variable.Values, StringComparison.Ordinal)).Any())
|
||||
{
|
||||
ConfirmAddVariableBtn.IsEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void EditProfileBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
SwitchViewsSegmentedView.SelectedIndex = 0;
|
||||
|
||||
var button = sender as MenuFlyoutItem;
|
||||
var profile = button.CommandParameter as ProfileVariablesSet;
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
AddProfileDialog.Title = resourceLoader.GetString("EditProfileDialog_Title");
|
||||
AddProfileDialog.PrimaryButtonText = resourceLoader.GetString("SaveBtn");
|
||||
AddProfileDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
|
||||
AddProfileDialog.PrimaryButtonCommand = UpdateProfileCommand;
|
||||
AddProfileDialog.DataContext = profile.Clone();
|
||||
await AddProfileDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExistingVariablesListView_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
|
||||
|
||||
foreach (Variable item in ExistingVariablesListView.Items)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
foreach (var profileItem in profile.Variables)
|
||||
{
|
||||
if (profileItem.Name == item.Name && profileItem.Values == item.Values)
|
||||
{
|
||||
if (ExistingVariablesListView.SelectedItems.Where(x => ((Variable)x).Name.Equals(profileItem.Name, StringComparison.OrdinalIgnoreCase)).Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
|
||||
ExistingVariablesListView.SelectedItems.Add(item);
|
||||
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowAddDefaultVariableDialogAsync(DefaultVariablesSet set)
|
||||
{
|
||||
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
|
||||
AddDefaultVariableDialog.Title = resourceLoader.GetString("AddVariable_Title");
|
||||
AddDefaultVariableDialog.PrimaryButtonText = resourceLoader.GetString("SaveBtn");
|
||||
AddDefaultVariableDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
|
||||
AddDefaultVariableDialog.PrimaryButtonCommand = AddDefaultVariableCommand;
|
||||
AddDefaultVariableDialog.PrimaryButtonCommandParameter = set;
|
||||
|
||||
var variableType = set.Id == VariablesSet.SystemGuid ? VariablesSetType.System : VariablesSetType.User;
|
||||
AddDefaultVariableDialog.DataContext = new Variable(string.Empty, string.Empty, variableType);
|
||||
|
||||
await AddDefaultVariableDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async void AddDefaultVariableBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var button = sender as Button;
|
||||
var defaultVariableSet = button.CommandParameter as DefaultVariablesSet;
|
||||
|
||||
if (defaultVariableSet != null)
|
||||
{
|
||||
await ShowAddDefaultVariableDialogAsync(defaultVariableSet);
|
||||
}
|
||||
}
|
||||
|
||||
private void EditVariableDialogNameTxtBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
var param = EditVariableDialog.PrimaryButtonCommandParameter as RelayCommandParameter;
|
||||
var variableSet = param.Set;
|
||||
|
||||
if (variableSet == null)
|
||||
{
|
||||
// default set
|
||||
variableSet = variable.ParentType == VariablesSetType.User ? ViewModel.UserDefaultSet : ViewModel.SystemDefaultSet;
|
||||
}
|
||||
|
||||
if (variableSet != null)
|
||||
{
|
||||
if (variableSet.Variables.Where(x => x.Name.Equals(EditVariableDialogNameTxtBox.Text, StringComparison.OrdinalIgnoreCase)).Any() || !variable.Valid)
|
||||
{
|
||||
EditVariableDialog.IsPrimaryButtonEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditVariableDialog.IsPrimaryButtonEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!variable.Validate())
|
||||
{
|
||||
EditVariableDialog.IsPrimaryButtonEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDefaultVariableNameTxtBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
TextBox nameTxtBox = sender as TextBox;
|
||||
var variable = AddDefaultVariableDialog.DataContext as Variable;
|
||||
var defaultSet = variable.ParentType == VariablesSetType.User ? ViewModel.UserDefaultSet : ViewModel.SystemDefaultSet;
|
||||
|
||||
if (nameTxtBox != null)
|
||||
{
|
||||
if (nameTxtBox.Text.Length == 0 || defaultSet.Variables.Where(x => x.Name.Equals(nameTxtBox.Text, StringComparison.OrdinalIgnoreCase)).Any())
|
||||
{
|
||||
AddDefaultVariableDialog.IsPrimaryButtonEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddDefaultVariableDialog.IsPrimaryButtonEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!variable.Validate())
|
||||
{
|
||||
AddDefaultVariableDialog.IsPrimaryButtonEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void EditVariableDialogValueTxtBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var txtBox = sender as TextBox;
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
EditVariableDialog.IsPrimaryButtonEnabled = true;
|
||||
|
||||
variable.ValuesList = Variable.ValuesStringToValuesListItemCollection(txtBox.Text);
|
||||
}
|
||||
|
||||
private void ReorderButtonUp_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var listItem = ((MenuFlyoutItem)sender).DataContext as Variable.ValuesListItem;
|
||||
if (listItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
if (index > 0)
|
||||
{
|
||||
variable.ValuesList.Move(index, index - 1);
|
||||
}
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
private void ReorderButtonDown_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var listItem = ((MenuFlyoutItem)sender).DataContext as Variable.ValuesListItem;
|
||||
if (listItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
var btn = EditVariableDialog.PrimaryButtonCommandParameter as Button;
|
||||
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
if (index < variable.ValuesList.Count - 1)
|
||||
{
|
||||
variable.ValuesList.Move(index, index + 1);
|
||||
}
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
private void RemoveListVariableButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var listItem = ((MenuFlyoutItem)sender).DataContext as Variable.ValuesListItem;
|
||||
if (listItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
variable.ValuesList.Remove(listItem);
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
}
|
||||
|
||||
private void InsertListEntryBeforeButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var listItem = (sender as MenuFlyoutItem)?.DataContext as Variable.ValuesListItem;
|
||||
if (listItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
variable.ValuesList.Insert(index, new Variable.ValuesListItem { Text = string.Empty });
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
}
|
||||
|
||||
private void InsertListEntryAfterButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var listItem = (sender as MenuFlyoutItem)?.DataContext as Variable.ValuesListItem;
|
||||
if (listItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
var index = variable.ValuesList.IndexOf(listItem);
|
||||
variable.ValuesList.Insert(index + 1, new Variable.ValuesListItem { Text = string.Empty });
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
}
|
||||
|
||||
private void EditVariableValuesListTextBox_LostFocus(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
var listItem = (sender as TextBox)?.DataContext as Variable.ValuesListItem;
|
||||
if (listItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (listItem.Text == (sender as TextBox)?.Text)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
listItem.Text = (sender as TextBox)?.Text;
|
||||
var variable = EditVariableDialog.DataContext as Variable;
|
||||
|
||||
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
|
||||
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
|
||||
EditVariableDialogValueTxtBox.Text = newValues;
|
||||
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
|
||||
}
|
||||
|
||||
private void InvalidStateInfoBar_CloseButtonClick(InfoBar sender, object args)
|
||||
{
|
||||
ViewModel.EnvironmentState = EnvironmentState.Unchanged;
|
||||
}
|
||||
|
||||
private void AddVariableFlyout_Closed(object sender, object e)
|
||||
{
|
||||
CancelAddVariable();
|
||||
ConfirmAddVariableBtn.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Condition="exists('..\..\..\Version.props')" Project="..\..\..\Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.20348</TargetFramework>
|
||||
<RootNamespace>EnvironmentVariablesUILib</RootNamespace>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<AssemblyName>PowerToys.EnvironmentVariablesUILib</AssemblyName>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>PowerToys.EnvironmentVariablesUILib.pri</ProjectPriFileName>
|
||||
<GenerateLibraryLayout>true</GenerateLibraryLayout>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(OutDir)\PowerToys.EnvironmentVariablesUILib.pri" Pack="True" PackageCopyToOutput="true" PackagePath="lib/$(TargetFramework)" />
|
||||
<None Include="$(OutDir)\PowerToys.EnvironmentVariablesUILib.pri" Pack="True" PackageCopyToOutput="True" PackagePath="contentFiles\any\$(TargetFramework)" />
|
||||
<XBFFile Include="$(OutDir)**\*.xbf" />
|
||||
<None Include="@(XBFFile)" Pack="True" PackageCopyToOutput="True" PackagePath="contentFiles\any\$(TargetFramework)" />
|
||||
<None Include="$(OutDir)\PowerToys.EnvironmentVariablesUILib.pdb" Pack="True" PackageCopyToOutput="true" PackagePath="lib/$(TargetFramework)" />
|
||||
|
||||
<None Include="Assets\**\*.png" Pack="true" PackageCopyToOutput="true" PackagePath="contentFiles\any\$(TargetFramework)\Assets"/>
|
||||
<None Include="Assets\**\*.ico" Pack="true" PackageCopyToOutput="true" PackagePath="contentFiles\any\$(TargetFramework)\Assets"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Assets\**\*.png" Pack="false" />
|
||||
<Content Remove="Assets\**\*.ico" Pack="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="WinUIEx" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Security.Principal;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public class ElevationHelper : IElevationHelper
|
||||
{
|
||||
private readonly bool _isElevated;
|
||||
|
||||
public bool IsElevated => _isElevated;
|
||||
|
||||
public ElevationHelper()
|
||||
{
|
||||
_isElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
|
||||
public static IElevationHelper ElevationHelperInstance { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using EnvironmentVariablesUILib.Helpers.Win32;
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
internal sealed class EnvironmentVariablesHelper
|
||||
{
|
||||
internal static string GetBackupVariableName(Variable variable, string profileName)
|
||||
{
|
||||
return variable.Name + "_PowerToys_" + profileName;
|
||||
}
|
||||
|
||||
internal static Variable GetExisting(string variableName)
|
||||
{
|
||||
DefaultVariablesSet userSet = new DefaultVariablesSet(Guid.NewGuid(), "tmpUser", VariablesSetType.User);
|
||||
GetVariables(EnvironmentVariableTarget.User, userSet);
|
||||
|
||||
foreach (var variable in userSet.Variables)
|
||||
{
|
||||
if (variable.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Variable(variable.Name, variable.Values, VariablesSetType.User);
|
||||
}
|
||||
}
|
||||
|
||||
DefaultVariablesSet systemSet = new DefaultVariablesSet(Guid.NewGuid(), "tmpSystem", VariablesSetType.System);
|
||||
GetVariables(EnvironmentVariableTarget.Machine, systemSet);
|
||||
|
||||
foreach (var variable in systemSet.Variables)
|
||||
{
|
||||
if (variable.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Variable(variable.Name, variable.Values, VariablesSetType.System);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static RegistryKey OpenEnvironmentKeyIfExists(bool fromMachine, bool writable)
|
||||
{
|
||||
RegistryKey baseKey;
|
||||
string keyName;
|
||||
|
||||
if (fromMachine)
|
||||
{
|
||||
baseKey = Registry.LocalMachine;
|
||||
keyName = @"System\CurrentControlSet\Control\Session Manager\Environment";
|
||||
}
|
||||
else
|
||||
{
|
||||
baseKey = Registry.CurrentUser;
|
||||
keyName = "Environment";
|
||||
}
|
||||
|
||||
return baseKey.OpenSubKey(keyName, writable: writable);
|
||||
}
|
||||
|
||||
// Code taken from https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Environment.Win32.cs
|
||||
// Set variables directly to registry instead of using Environment API - Environment.SetEnvironmentVariable() has 1 second timeout for SendNotifyMessage(WM_SETTINGSCHANGED).
|
||||
// When applying profile, this would take num_of_variables * 1s to propagate the changes. We do manually SendNotifyMessage with no timeout where needed.
|
||||
private static void SetEnvironmentVariableFromRegistryWithoutNotify(string variable, string value, bool fromMachine)
|
||||
{
|
||||
const int MaxUserEnvVariableLength = 255; // User-wide env vars stored in the registry have names limited to 255 chars
|
||||
if (!fromMachine && variable.Length >= MaxUserEnvVariableLength)
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Can't apply variable - name too long.");
|
||||
return;
|
||||
}
|
||||
|
||||
using (RegistryKey environmentKey = OpenEnvironmentKeyIfExists(fromMachine, writable: true))
|
||||
{
|
||||
if (environmentKey != null)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
environmentKey.DeleteValue(variable, throwOnMissingValue: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If a variable contains %, we save it as a REG_EXPAND_SZ, which is the same behavior as the Windows default environment variables editor.
|
||||
if (value.Contains('%'))
|
||||
{
|
||||
environmentKey.SetValue(variable, value, RegistryValueKind.ExpandString);
|
||||
}
|
||||
else
|
||||
{
|
||||
environmentKey.SetValue(variable, value, RegistryValueKind.String);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void NotifyEnvironmentChange()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// send a WM_SETTINGCHANGE message to all windows
|
||||
fixed (char* lParam = "Environment")
|
||||
{
|
||||
_ = NativeMethods.SendNotifyMessage(new IntPtr(NativeMethods.HWND_BROADCAST), NativeMethods.WindowMessage.WM_SETTINGSCHANGED, (IntPtr)0x12345, (IntPtr)lParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Code taken from https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Environment.Win32.cs
|
||||
// Reading variables from registry instead of using Environment API, because Environment API expands variables by default.
|
||||
internal static void GetVariables(EnvironmentVariableTarget target, VariablesSet set)
|
||||
{
|
||||
var sortedList = new SortedList<string, Variable>();
|
||||
|
||||
bool fromMachine = target == EnvironmentVariableTarget.Machine ? true : false;
|
||||
|
||||
using (RegistryKey environmentKey = OpenEnvironmentKeyIfExists(fromMachine, writable: false))
|
||||
{
|
||||
if (environmentKey != null)
|
||||
{
|
||||
foreach (string name in environmentKey.GetValueNames())
|
||||
{
|
||||
string value = environmentKey.GetValue(name, string.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames).ToString();
|
||||
try
|
||||
{
|
||||
Variable entry = new Variable(name, value, set.Type);
|
||||
sortedList.Add(name, entry);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Throw and catch intentionally to provide non-fatal notification about corrupted environment block
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set.Variables = new System.Collections.ObjectModel.ObservableCollection<Variable>(sortedList.Values);
|
||||
}
|
||||
|
||||
internal static bool SetVariableWithoutNotify(Variable variable)
|
||||
{
|
||||
bool fromMachine = variable.ParentType switch
|
||||
{
|
||||
VariablesSetType.Profile => false,
|
||||
VariablesSetType.User => false,
|
||||
VariablesSetType.System => true,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, variable.Values, fromMachine);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool SetVariable(Variable variable)
|
||||
{
|
||||
bool fromMachine = variable.ParentType switch
|
||||
{
|
||||
VariablesSetType.Profile => false,
|
||||
VariablesSetType.User => false,
|
||||
VariablesSetType.System => true,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, variable.Values, fromMachine);
|
||||
NotifyEnvironmentChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool UnsetVariableWithoutNotify(Variable variable)
|
||||
{
|
||||
bool fromMachine = variable.ParentType switch
|
||||
{
|
||||
VariablesSetType.Profile => false,
|
||||
VariablesSetType.User => false,
|
||||
VariablesSetType.System => true,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, null, fromMachine);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool UnsetVariable(Variable variable)
|
||||
{
|
||||
bool fromMachine = variable.ParentType switch
|
||||
{
|
||||
VariablesSetType.Profile => false,
|
||||
VariablesSetType.User => false,
|
||||
VariablesSetType.System => true,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, null, fromMachine);
|
||||
NotifyEnvironmentChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public sealed class EnvironmentVariablesService : IEnvironmentVariablesService
|
||||
{
|
||||
private const string ProfilesJsonFileSubPath = "Microsoft\\PowerToys\\EnvironmentVariables\\";
|
||||
|
||||
private readonly string _profilesJsonFilePath;
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
};
|
||||
|
||||
public string ProfilesJsonFilePath => _profilesJsonFilePath;
|
||||
|
||||
public EnvironmentVariablesService(IFileSystem fileSystem)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
_profilesJsonFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ProfilesJsonFileSubPath, "profiles.json");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public List<ProfileVariablesSet> ReadProfiles()
|
||||
{
|
||||
if (!_fileSystem.File.Exists(ProfilesJsonFilePath))
|
||||
{
|
||||
return new List<ProfileVariablesSet>();
|
||||
}
|
||||
|
||||
var fileContent = _fileSystem.File.ReadAllText(ProfilesJsonFilePath);
|
||||
var profiles = JsonSerializer.Deserialize<List<ProfileVariablesSet>>(fileContent);
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(IEnumerable<ProfileVariablesSet> profiles)
|
||||
{
|
||||
string jsonData = JsonSerializer.Serialize(profiles, _serializerOptions);
|
||||
await _fileSystem.File.WriteAllTextAsync(ProfilesJsonFilePath, jsonData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public interface IElevationHelper
|
||||
{
|
||||
bool IsElevated { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Threading.Tasks;
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public interface IEnvironmentVariablesService : IDisposable
|
||||
{
|
||||
string ProfilesJsonFilePath { get; }
|
||||
|
||||
List<ProfileVariablesSet> ReadProfiles();
|
||||
|
||||
Task WriteAsync(IEnumerable<ProfileVariablesSet> profiles);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
public void LogError(string message);
|
||||
|
||||
public void LogError(string message, Exception ex);
|
||||
|
||||
public void LogWarning(string message);
|
||||
|
||||
public void LogInfo(string message);
|
||||
|
||||
public void LogDebug(string message);
|
||||
|
||||
public void LogTrace();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public static class LoggerInstance
|
||||
{
|
||||
public static ILogger Logger { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers.Win32
|
||||
{
|
||||
public static class NativeMethods
|
||||
{
|
||||
internal const int HWND_BROADCAST = 0xffff;
|
||||
|
||||
internal delegate IntPtr WinProc(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern int SendNotifyMessage(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("User32.dll")]
|
||||
internal static extern int GetDpiForWindow(IntPtr hwnd);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
|
||||
internal static extern int SetWindowLong32(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
|
||||
internal static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[Flags]
|
||||
internal enum WindowLongIndexFlags : int
|
||||
{
|
||||
GWL_WNDPROC = -4,
|
||||
}
|
||||
|
||||
internal enum WindowMessage : int
|
||||
{
|
||||
WM_SETTINGSCHANGED = 0x001A,
|
||||
}
|
||||
|
||||
internal static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
{
|
||||
return SetWindowLongPtr64(hWnd, nIndex, newProc);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new IntPtr(SetWindowLong32(hWnd, nIndex, newProc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public static class ResourceLoaderInstance
|
||||
{
|
||||
public static ResourceLoader ResourceLoader { get; private set; }
|
||||
|
||||
static ResourceLoaderInstance()
|
||||
{
|
||||
ResourceLoader = new ResourceLoader("PowerToys.EnvironmentVariablesUILib.pri", "PowerToys.EnvironmentVariablesUILib/Resources");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Models
|
||||
{
|
||||
public class DefaultVariablesSet : VariablesSet
|
||||
{
|
||||
public DefaultVariablesSet(Guid id, string name, VariablesSetType type)
|
||||
: base(id, name, type)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace EnvironmentVariablesUILib.Models
|
||||
{
|
||||
public enum EnvironmentState
|
||||
{
|
||||
Unchanged = 0,
|
||||
ChangedOnStartup,
|
||||
EnvironmentMessageReceived,
|
||||
ProfileNotApplicable,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using EnvironmentVariablesUILib.Helpers;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Models
|
||||
{
|
||||
public partial class ProfileVariablesSet : VariablesSet
|
||||
{
|
||||
[ObservableProperty]
|
||||
private bool _isEnabled;
|
||||
|
||||
public ProfileVariablesSet()
|
||||
: base()
|
||||
{
|
||||
Type = VariablesSetType.Profile;
|
||||
IconPath = ProfileIconPath;
|
||||
}
|
||||
|
||||
public ProfileVariablesSet(Guid id, string name)
|
||||
: base(id, name, VariablesSetType.Profile)
|
||||
{
|
||||
IconPath = ProfileIconPath;
|
||||
}
|
||||
|
||||
public Task Apply()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
foreach (var variable in Variables)
|
||||
{
|
||||
var applyToSystem = variable.ApplyToSystem;
|
||||
|
||||
// Get existing variable with the same name if it exist
|
||||
var variableToOverride = EnvironmentVariablesHelper.GetExisting(variable.Name);
|
||||
|
||||
// It exists. Rename it to preserve it.
|
||||
if (variableToOverride != null && variableToOverride.ParentType == VariablesSetType.User)
|
||||
{
|
||||
variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, this.Name);
|
||||
|
||||
// Backup the variable
|
||||
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToOverride))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to set backup variable.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variable))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to set profile variable.");
|
||||
}
|
||||
}
|
||||
|
||||
EnvironmentVariablesHelper.NotifyEnvironmentChange();
|
||||
});
|
||||
}
|
||||
|
||||
public Task UnApply()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
foreach (var variable in Variables)
|
||||
{
|
||||
UnapplyVariable(variable);
|
||||
}
|
||||
|
||||
EnvironmentVariablesHelper.NotifyEnvironmentChange();
|
||||
});
|
||||
}
|
||||
|
||||
public void UnapplyVariable(Variable variable)
|
||||
{
|
||||
// Unset the variable
|
||||
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(variable))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to unset variable.");
|
||||
}
|
||||
|
||||
var originalName = variable.Name;
|
||||
var backupName = EnvironmentVariablesHelper.GetBackupVariableName(variable, this.Name);
|
||||
|
||||
// Get backup variable if it exist
|
||||
var backupVariable = EnvironmentVariablesHelper.GetExisting(backupName);
|
||||
|
||||
if (backupVariable != null)
|
||||
{
|
||||
var variableToRestore = new Variable(originalName, backupVariable.Values, backupVariable.ParentType);
|
||||
|
||||
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(backupVariable))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to unset backup variable.");
|
||||
}
|
||||
|
||||
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToRestore))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to restore backup variable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCorrectlyApplied()
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var variable in Variables)
|
||||
{
|
||||
var applied = EnvironmentVariablesHelper.GetExisting(variable.Name);
|
||||
if (applied != null && applied.Values == variable.Values && applied.ParentType == VariablesSetType.User)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsApplicable()
|
||||
{
|
||||
foreach (var variable in Variables)
|
||||
{
|
||||
if (!variable.Validate())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get existing variable with the same name if it exist
|
||||
var variableToOverride = EnvironmentVariablesHelper.GetExisting(variable.Name);
|
||||
|
||||
// It exists. Backup is needed.
|
||||
if (variableToOverride != null && variableToOverride.ParentType == VariablesSetType.User)
|
||||
{
|
||||
variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, this.Name);
|
||||
if (!variableToOverride.Validate())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ProfileVariablesSet Clone()
|
||||
{
|
||||
var clone = new ProfileVariablesSet(this.Id, this.Name);
|
||||
clone.Variables = new ObservableCollection<Variable>(this.Variables);
|
||||
clone.IsEnabled = this.IsEnabled;
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using EnvironmentVariablesUILib.Helpers;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Models
|
||||
{
|
||||
public partial class Variable : ObservableObject, IJsonOnDeserialized
|
||||
{
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(Valid))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowAsList))]
|
||||
private string _name;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _values;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _applyToSystem;
|
||||
|
||||
[JsonIgnore]
|
||||
[property: JsonIgnore]
|
||||
[ObservableProperty]
|
||||
private bool _isAppliedFromProfile; // Used to mark that a variable in a default set is applied by a profile. Used to disable editing / mark it in the UI.
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsEditable
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ParentType != VariablesSetType.System || ElevationHelper.ElevationHelperInstance.IsElevated) && !IsAppliedFromProfile;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public VariablesSetType ParentType { get; set; }
|
||||
|
||||
// To store the strings in the Values List with actual objects that can be referenced and identity compared
|
||||
public class ValuesListItem
|
||||
{
|
||||
public string Text { get; set; }
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[property: JsonIgnore]
|
||||
[JsonIgnore]
|
||||
private ObservableCollection<ValuesListItem> _valuesList;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool Valid => Validate();
|
||||
|
||||
[JsonIgnore]
|
||||
public bool ShowAsList => IsList();
|
||||
|
||||
private bool IsList()
|
||||
{
|
||||
List<string> listVariables = new() { "PATH", "PATHEXT", "PSMODULEPATH" };
|
||||
|
||||
foreach (var name in listVariables)
|
||||
{
|
||||
if (Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
// No need to save ValuesList to the Json, so we are generating it after deserializing
|
||||
ValuesList = ValuesStringToValuesListItemCollection(Values);
|
||||
}
|
||||
|
||||
public Variable()
|
||||
{
|
||||
}
|
||||
|
||||
public Variable(string name, string values, VariablesSetType parentType)
|
||||
{
|
||||
Name = name;
|
||||
Values = values;
|
||||
ParentType = parentType;
|
||||
|
||||
ValuesList = ValuesStringToValuesListItemCollection(Values);
|
||||
}
|
||||
|
||||
internal static ObservableCollection<ValuesListItem> ValuesStringToValuesListItemCollection(string values)
|
||||
{
|
||||
return new ObservableCollection<ValuesListItem>(values.Split(';').Select(x => new ValuesListItem { Text = x }));
|
||||
}
|
||||
|
||||
internal Task Update(Variable edited, bool propagateChange, ProfileVariablesSet parentProfile)
|
||||
{
|
||||
bool nameChanged = Name != edited.Name;
|
||||
|
||||
var clone = this.Clone();
|
||||
|
||||
// Update state
|
||||
Name = edited.Name;
|
||||
Values = edited.Values;
|
||||
|
||||
ValuesList = ValuesStringToValuesListItemCollection(Values);
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
// Apply changes
|
||||
if (propagateChange)
|
||||
{
|
||||
if (nameChanged)
|
||||
{
|
||||
if (!EnvironmentVariablesHelper.UnsetVariable(clone))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to unset original variable.");
|
||||
}
|
||||
|
||||
if (parentProfile != null)
|
||||
{
|
||||
var backupName = EnvironmentVariablesHelper.GetBackupVariableName(clone, parentProfile.Name);
|
||||
|
||||
// Get backup variable if it exist
|
||||
var backupVariable = EnvironmentVariablesHelper.GetExisting(backupName);
|
||||
if (backupVariable != null)
|
||||
{
|
||||
var variableToRestore = new Variable(clone.Name, backupVariable.Values, backupVariable.ParentType);
|
||||
|
||||
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(backupVariable))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to unset backup variable.");
|
||||
}
|
||||
|
||||
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToRestore))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to restore backup variable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get existing variable with the same name if it exist
|
||||
var variableToOverride = EnvironmentVariablesHelper.GetExisting(Name);
|
||||
|
||||
// It exists. Rename it to preserve it.
|
||||
if (variableToOverride != null && variableToOverride.ParentType == VariablesSetType.User && parentProfile != null)
|
||||
{
|
||||
// Gets which name the backup variable should have.
|
||||
variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, parentProfile.Name);
|
||||
|
||||
// Only create a backup variable if there's not one already, to avoid overriding. (solves Path nuking errors, for example, after editing path on an enabled profile)
|
||||
if (EnvironmentVariablesHelper.GetExisting(variableToOverride.Name) == null)
|
||||
{
|
||||
// Backup the variable
|
||||
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToOverride))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to set backup variable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!EnvironmentVariablesHelper.SetVariable(this))
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Failed to set new variable.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal Variable Clone(bool profile = false)
|
||||
{
|
||||
return new Variable
|
||||
{
|
||||
Name = Name,
|
||||
Values = Values,
|
||||
ParentType = profile ? VariablesSetType.Profile : ParentType,
|
||||
ValuesList = ValuesStringToValuesListItemCollection(Values),
|
||||
};
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int MaxUserEnvVariableLength = 255; // User-wide env vars stored in the registry have names limited to 255 chars
|
||||
if (ParentType != VariablesSetType.System && Name.Length >= MaxUserEnvVariableLength)
|
||||
{
|
||||
LoggerInstance.Logger.LogError("Variable name too long.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using EnvironmentVariablesUILib.ViewModels;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Models
|
||||
{
|
||||
public partial class VariablesSet : ObservableObject
|
||||
{
|
||||
public static readonly Guid UserGuid = new Guid("92F7AA9A-AE31-49CD-83C8-80A71E432AA5");
|
||||
public static readonly Guid SystemGuid = new Guid("F679C74D-DB00-4795-92E1-B1F6A4833279");
|
||||
|
||||
private static readonly string UserIconPath = "/Assets/EnvironmentVariables/UserIcon.png";
|
||||
private static readonly string SystemIconPath = "/Assets/EnvironmentVariables/SystemIcon.png";
|
||||
protected static readonly string ProfileIconPath = "/Assets/EnvironmentVariables/ProfileIcon.png";
|
||||
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(Valid))]
|
||||
private string _name;
|
||||
|
||||
[JsonIgnore]
|
||||
public VariablesSetType Type { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string IconPath { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Variable> _variables;
|
||||
|
||||
public bool Valid => Validate();
|
||||
|
||||
public VariablesSet()
|
||||
{
|
||||
}
|
||||
|
||||
public VariablesSet(Guid id, string name, VariablesSetType type)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Type = type;
|
||||
Variables = new ObservableCollection<Variable>();
|
||||
|
||||
IconPath = Type switch
|
||||
{
|
||||
VariablesSetType.User => UserIconPath,
|
||||
VariablesSetType.System => SystemIconPath,
|
||||
VariablesSetType.Profile => ProfileIconPath,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace EnvironmentVariablesUILib.Models
|
||||
{
|
||||
public enum VariablesSetType
|
||||
{
|
||||
Path = 0,
|
||||
Duplicate,
|
||||
Profile,
|
||||
User,
|
||||
System,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="DefaultSetsLabel.Text" xml:space="preserve">
|
||||
<value>Default variables</value>
|
||||
</data>
|
||||
<data name="DefaultVariablesLbl.Text" xml:space="preserve">
|
||||
<value>Default variables</value>
|
||||
</data>
|
||||
<data name="NewProfileBtn.Text" xml:space="preserve">
|
||||
<value>New profile</value>
|
||||
</data>
|
||||
<data name="ProfilesDescriptionLbl.Text" xml:space="preserve">
|
||||
<value>You can create profiles to quickly apply a set of preconfigured variables</value>
|
||||
</data>
|
||||
<data name="ProfilesLbl.Text" xml:space="preserve">
|
||||
<value>Profiles</value>
|
||||
</data>
|
||||
<data name="System" xml:space="preserve">
|
||||
<value>System</value>
|
||||
</data>
|
||||
<data name="User" xml:space="preserve">
|
||||
<value>User</value>
|
||||
</data>
|
||||
<data name="WindowTitle" xml:space="preserve">
|
||||
<value>Environment Variables</value>
|
||||
<comment>Title of the window when running as user</comment>
|
||||
</data>
|
||||
<data name="EditDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="EditVariableDialog_Title" xml:space="preserve">
|
||||
<value>Edit variable</value>
|
||||
</data>
|
||||
<data name="NewProfileNameTxtBox.Header" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="SaveBtn" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="ValueTxtBox.Header" xml:space="preserve">
|
||||
<value>Value</value>
|
||||
</data>
|
||||
<data name="AddBtn" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="AddNewProfileDialog_Title" xml:space="preserve">
|
||||
<value>New profile</value>
|
||||
</data>
|
||||
<data name="NewProfileEnabled.Header" xml:space="preserve">
|
||||
<value>Enabled</value>
|
||||
</data>
|
||||
<data name="AddVariableBtn.Text" xml:space="preserve">
|
||||
<value>Add variable</value>
|
||||
</data>
|
||||
<data name="AddNewVariableName.Header" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
<comment>Name as in Environment variable name</comment>
|
||||
</data>
|
||||
<data name="AddNewVariableValue.Header" xml:space="preserve">
|
||||
<value>Value</value>
|
||||
<comment>Value as in Environment variable value</comment>
|
||||
</data>
|
||||
<data name="ExistingVariableSegmentedButton.Content" xml:space="preserve">
|
||||
<value>Existing</value>
|
||||
</data>
|
||||
<data name="NewVariableSegmentedButton.Content" xml:space="preserve">
|
||||
<value>New</value>
|
||||
</data>
|
||||
<data name="WindowAdminTitle" xml:space="preserve">
|
||||
<value>Administrator: Environment Variables</value>
|
||||
<comment>Title of the window when running as administrator</comment>
|
||||
</data>
|
||||
<data name="CancelAddVariableBtn.Content" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="ConfirmAddVariableBtn.Content" xml:space="preserve">
|
||||
<value>Add</value>
|
||||
</data>
|
||||
<data name="AppliedVariablesDescriptionLbl.Text" xml:space="preserve">
|
||||
<value>List of applied variables</value>
|
||||
</data>
|
||||
<data name="AppliedVariablesLbl.Text" xml:space="preserve">
|
||||
<value>Applied variables</value>
|
||||
</data>
|
||||
<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.Text" xml:space="preserve">
|
||||
<value>Administrator permissions are required to edit System variables</value>
|
||||
</data>
|
||||
<data name="No" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="Yes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="StateNotUpToDateTitle" xml:space="preserve">
|
||||
<value>Changes were made outside of this app.</value>
|
||||
</data>
|
||||
<data name="StateNotUpToDateOnStartupMsg" xml:space="preserve">
|
||||
<value>Variables included in applied profile have been modified. Review the latest changes before applying the profile again.</value>
|
||||
</data>
|
||||
<data name="CancelBtn" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="StateNotUpToDateEnvironmentMessageReceivedMsg" xml:space="preserve">
|
||||
<value>Variables have been modified. Reload to get the latest changes.</value>
|
||||
</data>
|
||||
<data name="AddVariable_Title" xml:space="preserve">
|
||||
<value>Add variable</value>
|
||||
</data>
|
||||
<data name="Delete_Variable_Description" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this variable?</value>
|
||||
</data>
|
||||
<data name="EditProfileDialog_Title" xml:space="preserve">
|
||||
<value>Edit profile</value>
|
||||
</data>
|
||||
<data name="AddVariableTooltip.Text" xml:space="preserve">
|
||||
<value>Add variable</value>
|
||||
</data>
|
||||
<data name="DefaultVariablesDescriptionLbl.Text" xml:space="preserve">
|
||||
<value>Add, remove or edit USER and SYSTEM variables</value>
|
||||
</data>
|
||||
<data name="EditItem.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="More_Options_ButtonTooltip.Text" xml:space="preserve">
|
||||
<value>More options</value>
|
||||
</data>
|
||||
<data name="MoveDown.Text" xml:space="preserve">
|
||||
<value>Move down</value>
|
||||
</data>
|
||||
<data name="MoveUp.Text" xml:space="preserve">
|
||||
<value>Move up</value>
|
||||
</data>
|
||||
<data name="InsertListEntryBefore.Text" xml:space="preserve">
|
||||
<value>Insert Before</value>
|
||||
</data>
|
||||
<data name="InsertListEntryAfter.Text" xml:space="preserve">
|
||||
<value>Insert After</value>
|
||||
</data>
|
||||
<data name="NewProfileVariablesListViewApplyToSystemHeader.Text" xml:space="preserve">
|
||||
<value>Apply to SYSTEM?</value>
|
||||
</data>
|
||||
<data name="RemoveListItem.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="RemoveItem.Text" xml:space="preserve">
|
||||
<value>Remove</value>
|
||||
</data>
|
||||
<data name="AddVariableContent.Content" xml:space="preserve">
|
||||
<value>Add variable</value>
|
||||
</data>
|
||||
<data name="ProfileNotApplicableTitle" xml:space="preserve">
|
||||
<value>Profile can not be applied.</value>
|
||||
</data>
|
||||
<data name="StateProfileNotApplicableMsg" xml:space="preserve">
|
||||
<value>Variables or backup variables are invalid.</value>
|
||||
</data>
|
||||
<data name="VariableIsAppliedByActiveProfileTooltip.Text" xml:space="preserve">
|
||||
<value>This variable is written by the active profile</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Telemetry
|
||||
{
|
||||
public interface ITelemetry
|
||||
{
|
||||
abstract void LogEnvironmentVariablesProfileEnabledEvent(bool enabled);
|
||||
|
||||
abstract void LogEnvironmentVariablesVariableChangedEvent(VariablesSetType type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using EnvironmentVariablesUILib.Telemetry;
|
||||
|
||||
namespace EnvironmentVariablesUILib.Helpers
|
||||
{
|
||||
public static class TelemetryInstance
|
||||
{
|
||||
public static ITelemetry Telemetry { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using EnvironmentVariablesUILib.Helpers;
|
||||
using EnvironmentVariablesUILib.Models;
|
||||
using EnvironmentVariablesUILib.Telemetry;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace EnvironmentVariablesUILib.ViewModels
|
||||
{
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private readonly IEnvironmentVariablesService _environmentVariablesService;
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public DefaultVariablesSet UserDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.UserGuid, ResourceLoaderInstance.ResourceLoader.GetString("User"), VariablesSetType.User);
|
||||
|
||||
public DefaultVariablesSet SystemDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.SystemGuid, ResourceLoaderInstance.ResourceLoader.GetString("System"), VariablesSetType.System);
|
||||
|
||||
public VariablesSet DefaultVariables { get; private set; } = new DefaultVariablesSet(Guid.NewGuid(), "DefaultVariables", VariablesSetType.User);
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ProfileVariablesSet> _profiles;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Variable> _appliedVariables = new ObservableCollection<Variable>();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isElevated;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsInfoBarButtonVisible))]
|
||||
private EnvironmentState _environmentState;
|
||||
|
||||
public bool IsInfoBarButtonVisible => EnvironmentState == EnvironmentState.EnvironmentMessageReceived;
|
||||
|
||||
public ProfileVariablesSet AppliedProfile { get; set; }
|
||||
|
||||
public MainViewModel(IElevationHelper elevationHelper, IEnvironmentVariablesService environmentVariablesService, ILogger logger, ITelemetry telemetry)
|
||||
{
|
||||
_environmentVariablesService = environmentVariablesService;
|
||||
|
||||
ElevationHelper.ElevationHelperInstance = elevationHelper;
|
||||
LoggerInstance.Logger = logger;
|
||||
TelemetryInstance.Telemetry = telemetry;
|
||||
|
||||
var isElevated = ElevationHelper.ElevationHelperInstance.IsElevated;
|
||||
IsElevated = isElevated;
|
||||
}
|
||||
|
||||
private void LoadDefaultVariables()
|
||||
{
|
||||
UserDefaultSet.Variables.Clear();
|
||||
SystemDefaultSet.Variables.Clear();
|
||||
DefaultVariables.Variables.Clear();
|
||||
|
||||
EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.Machine, SystemDefaultSet);
|
||||
EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.User, UserDefaultSet);
|
||||
|
||||
foreach (var variable in UserDefaultSet.Variables)
|
||||
{
|
||||
DefaultVariables.Variables.Add(variable);
|
||||
if (AppliedProfile != null)
|
||||
{
|
||||
if (AppliedProfile.Variables.Where(
|
||||
x => (x.Name.Equals(variable.Name, StringComparison.OrdinalIgnoreCase) && x.Values.Equals(variable.Values, StringComparison.OrdinalIgnoreCase))
|
||||
|| variable.Name.Equals(EnvironmentVariablesHelper.GetBackupVariableName(x, AppliedProfile.Name), StringComparison.OrdinalIgnoreCase)).Any())
|
||||
{
|
||||
// If it's a user variable that's also in the profile or is a backup variable, mark it as applied from profile.
|
||||
variable.IsAppliedFromProfile = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var variable in SystemDefaultSet.Variables)
|
||||
{
|
||||
DefaultVariables.Variables.Add(variable);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadEnvironmentVariables()
|
||||
{
|
||||
LoadDefaultVariables();
|
||||
LoadProfiles();
|
||||
PopulateAppliedVariables();
|
||||
}
|
||||
|
||||
private void LoadProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profiles = _environmentVariablesService.ReadProfiles();
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
profile.PropertyChanged += Profile_PropertyChanged;
|
||||
|
||||
foreach (var variable in profile.Variables)
|
||||
{
|
||||
variable.ParentType = VariablesSetType.Profile;
|
||||
}
|
||||
}
|
||||
|
||||
var appliedProfiles = profiles.Where(x => x.IsEnabled).ToList();
|
||||
if (appliedProfiles.Count > 0)
|
||||
{
|
||||
var appliedProfile = appliedProfiles.First();
|
||||
if (appliedProfile.IsCorrectlyApplied())
|
||||
{
|
||||
AppliedProfile = appliedProfile;
|
||||
EnvironmentState = EnvironmentState.Unchanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnvironmentState = EnvironmentState.ChangedOnStartup;
|
||||
appliedProfile.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
Profiles = new ObservableCollection<ProfileVariablesSet>(profiles);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Show some error
|
||||
LoggerInstance.Logger.LogError("Failed to load profiles.json file", ex);
|
||||
|
||||
Profiles = new ObservableCollection<ProfileVariablesSet>();
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateAppliedVariables()
|
||||
{
|
||||
LoadDefaultVariables();
|
||||
|
||||
var variables = new List<Variable>();
|
||||
if (AppliedProfile != null)
|
||||
{
|
||||
variables = variables.Concat(AppliedProfile.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.Profile)).OrderBy(x => x.Name)).ToList();
|
||||
}
|
||||
|
||||
// Variables are expanded to be shown in the applied variables section, so the user sees their actual values.
|
||||
variables = variables.Concat(UserDefaultSet.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.User)).OrderBy(x => x.Name))
|
||||
.Concat(SystemDefaultSet.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.System)).OrderBy(x => x.Name))
|
||||
.ToList();
|
||||
|
||||
// Handle PATH variable - add USER value to the end of the SYSTEM value
|
||||
var profilePath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.Profile).FirstOrDefault();
|
||||
var userPath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.User).FirstOrDefault();
|
||||
var systemPath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.System).FirstOrDefault();
|
||||
|
||||
if (systemPath != null)
|
||||
{
|
||||
var clone = systemPath.Clone();
|
||||
clone.ParentType = VariablesSetType.Path;
|
||||
|
||||
if (userPath != null)
|
||||
{
|
||||
clone.Values += ";" + userPath.Values;
|
||||
variables.Remove(userPath);
|
||||
}
|
||||
|
||||
if (profilePath != null)
|
||||
{
|
||||
variables.Remove(profilePath);
|
||||
}
|
||||
|
||||
variables.Insert(variables.IndexOf(systemPath), clone);
|
||||
variables.Remove(systemPath);
|
||||
}
|
||||
|
||||
variables = variables.GroupBy(x => x.Name).Select(y => y.First()).ToList();
|
||||
|
||||
// Find duplicates
|
||||
var duplicates = variables.Where(x => !x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase)).GroupBy(x => x.Name.ToLower(CultureInfo.InvariantCulture)).Where(g => g.Count() > 1);
|
||||
foreach (var duplicate in duplicates)
|
||||
{
|
||||
var userVar = duplicate.ElementAt(0);
|
||||
var systemVar = duplicate.ElementAt(1);
|
||||
|
||||
var clone = userVar.Clone();
|
||||
clone.ParentType = VariablesSetType.Duplicate;
|
||||
clone.Name = systemVar.Name;
|
||||
variables.Insert(variables.IndexOf(userVar), clone);
|
||||
variables.Remove(userVar);
|
||||
variables.Remove(systemVar);
|
||||
}
|
||||
|
||||
variables = variables.OrderBy(x => x.ParentType).ToList();
|
||||
AppliedVariables = new ObservableCollection<Variable>(variables);
|
||||
}
|
||||
|
||||
internal void AddDefaultVariable(Variable variable, VariablesSetType type)
|
||||
{
|
||||
if (type == VariablesSetType.User)
|
||||
{
|
||||
UserDefaultSet.Variables.Add(variable);
|
||||
UserDefaultSet.Variables = new ObservableCollection<Variable>(UserDefaultSet.Variables.OrderBy(x => x.Name).ToList());
|
||||
}
|
||||
else if (type == VariablesSetType.System)
|
||||
{
|
||||
SystemDefaultSet.Variables.Add(variable);
|
||||
SystemDefaultSet.Variables = new ObservableCollection<Variable>(SystemDefaultSet.Variables.OrderBy(x => x.Name).ToList());
|
||||
}
|
||||
|
||||
EnvironmentVariablesHelper.SetVariable(variable);
|
||||
PopulateAppliedVariables();
|
||||
}
|
||||
|
||||
internal void EditVariable(Variable original, Variable edited, ProfileVariablesSet variablesSet)
|
||||
{
|
||||
bool propagateChange = variablesSet == null /* not a profile */ || variablesSet.Id.Equals(AppliedProfile?.Id);
|
||||
bool changed = original.Name != edited.Name || original.Values != edited.Values;
|
||||
if (changed)
|
||||
{
|
||||
var task = original.Update(edited, propagateChange, variablesSet);
|
||||
task.ContinueWith(x =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
PopulateAppliedVariables();
|
||||
});
|
||||
});
|
||||
|
||||
TelemetryInstance.Telemetry.LogEnvironmentVariablesVariableChangedEvent(original.ParentType);
|
||||
_ = Task.Run(SaveAsync);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddProfile(ProfileVariablesSet profile)
|
||||
{
|
||||
profile.PropertyChanged += Profile_PropertyChanged;
|
||||
if (profile.IsEnabled)
|
||||
{
|
||||
UnsetAppliedProfile();
|
||||
SetAppliedProfile(profile);
|
||||
}
|
||||
|
||||
Profiles.Add(profile);
|
||||
|
||||
_ = Task.Run(SaveAsync);
|
||||
}
|
||||
|
||||
internal void UpdateProfile(ProfileVariablesSet updatedProfile)
|
||||
{
|
||||
var existingProfile = Profiles.Where(x => x.Id == updatedProfile.Id).FirstOrDefault();
|
||||
if (existingProfile != null)
|
||||
{
|
||||
if (updatedProfile.IsEnabled)
|
||||
{
|
||||
// Let's unset the profile before applying the update. Even if this one is the one that's currently set.
|
||||
UnsetAppliedProfile();
|
||||
}
|
||||
|
||||
existingProfile.Name = updatedProfile.Name;
|
||||
existingProfile.IsEnabled = updatedProfile.IsEnabled;
|
||||
existingProfile.Variables = updatedProfile.Variables;
|
||||
}
|
||||
|
||||
_ = Task.Run(SaveAsync);
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _environmentVariablesService.WriteAsync(Profiles);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Show some error
|
||||
LoggerInstance.Logger.LogError("Failed to save to profiles.json file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void Profile_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var profile = sender as ProfileVariablesSet;
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
if (e.PropertyName == nameof(ProfileVariablesSet.IsEnabled))
|
||||
{
|
||||
if (profile.IsEnabled)
|
||||
{
|
||||
UnsetAppliedProfile();
|
||||
SetAppliedProfile(profile);
|
||||
|
||||
TelemetryInstance.Telemetry.LogEnvironmentVariablesProfileEnabledEvent(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnsetAppliedProfile();
|
||||
|
||||
TelemetryInstance.Telemetry.LogEnvironmentVariablesProfileEnabledEvent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = Task.Run(SaveAsync);
|
||||
}
|
||||
|
||||
private void SetAppliedProfile(ProfileVariablesSet profile)
|
||||
{
|
||||
if (profile != null)
|
||||
{
|
||||
if (!profile.IsApplicable())
|
||||
{
|
||||
profile.PropertyChanged -= Profile_PropertyChanged;
|
||||
profile.IsEnabled = false;
|
||||
profile.PropertyChanged += Profile_PropertyChanged;
|
||||
|
||||
EnvironmentState = EnvironmentState.ProfileNotApplicable;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var task = profile.Apply();
|
||||
task.ContinueWith((a) =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
PopulateAppliedVariables();
|
||||
});
|
||||
});
|
||||
AppliedProfile = profile;
|
||||
}
|
||||
|
||||
private void UnsetAppliedProfile()
|
||||
{
|
||||
if (AppliedProfile != null)
|
||||
{
|
||||
var appliedProfile = AppliedProfile;
|
||||
appliedProfile.PropertyChanged -= Profile_PropertyChanged;
|
||||
var task = AppliedProfile.UnApply();
|
||||
task.ContinueWith((a) =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
PopulateAppliedVariables();
|
||||
});
|
||||
});
|
||||
AppliedProfile.IsEnabled = false;
|
||||
AppliedProfile = null;
|
||||
appliedProfile.PropertyChanged += Profile_PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_ = Task.Run(SaveAsync);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (variable.ParentType == VariablesSetType.User)
|
||||
{
|
||||
UserDefaultSet.Variables.Remove(variable);
|
||||
}
|
||||
else if (variable.ParentType == VariablesSetType.System)
|
||||
{
|
||||
SystemDefaultSet.Variables.Remove(variable);
|
||||
}
|
||||
}
|
||||
|
||||
if (propagateChange)
|
||||
{
|
||||
var task = Task.Run(() =>
|
||||
{
|
||||
if (profile == null)
|
||||
{
|
||||
EnvironmentVariablesHelper.UnsetVariable(variable);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.UnapplyVariable(variable);
|
||||
}
|
||||
});
|
||||
task.ContinueWith((a) =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
PopulateAppliedVariables();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="EnvironmentVariables.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
|
||||
For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
|
||||
It is also necessary to support features in unpackaged applications, for example the custom title bar implementation.-->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
Reference in New Issue
Block a user