Improve configuration layout

This commit is contained in:
whimsical-c4lic0 2026-02-18 00:04:37 -06:00
parent 56b951ace0
commit 34ad00ce03
3 changed files with 188 additions and 152 deletions

View File

@ -16,6 +16,8 @@ public sealed class ConfigCategoryViewModel : ViewModelBase
string.Equals(field.PropertyName, nameof(Config.DownloadDateSelection), StringComparison.Ordinal)); string.Equals(field.PropertyName, nameof(Config.DownloadDateSelection), StringComparison.Ordinal));
CustomDateField = fieldList.FirstOrDefault(field => CustomDateField = fieldList.FirstOrDefault(field =>
string.Equals(field.PropertyName, nameof(Config.CustomDate), StringComparison.Ordinal)); string.Equals(field.PropertyName, nameof(Config.CustomDate), StringComparison.Ordinal));
DownloadVideoResolutionField = fieldList.FirstOrDefault(field =>
string.Equals(field.PropertyName, nameof(Config.DownloadVideoResolution), StringComparison.Ordinal));
LimitDownloadRateField = fieldList.FirstOrDefault(field => LimitDownloadRateField = fieldList.FirstOrDefault(field =>
string.Equals(field.PropertyName, nameof(Config.LimitDownloadRate), StringComparison.Ordinal)); string.Equals(field.PropertyName, nameof(Config.LimitDownloadRate), StringComparison.Ordinal));
@ -34,7 +36,8 @@ public sealed class ConfigCategoryViewModel : ViewModelBase
IEnumerable<ConfigFieldViewModel> visibleFields = IsDownloadBehavior IEnumerable<ConfigFieldViewModel> visibleFields = IsDownloadBehavior
? fieldList.Where(field => field.PropertyName is not nameof(Config.DownloadOnlySpecificDates) ? fieldList.Where(field => field.PropertyName is not nameof(Config.DownloadOnlySpecificDates)
and not nameof(Config.DownloadDateSelection) and not nameof(Config.DownloadDateSelection)
and not nameof(Config.CustomDate)) and not nameof(Config.CustomDate)
and not nameof(Config.DownloadVideoResolution))
: IsPerformance : IsPerformance
? fieldList.Where(field => field.PropertyName is not nameof(Config.LimitDownloadRate) ? fieldList.Where(field => field.PropertyName is not nameof(Config.LimitDownloadRate)
and not nameof(Config.DownloadLimitInMbPerSec)) and not nameof(Config.DownloadLimitInMbPerSec))
@ -71,6 +74,8 @@ public sealed class ConfigCategoryViewModel : ViewModelBase
public ConfigFieldViewModel? CustomDateField { get; } public ConfigFieldViewModel? CustomDateField { get; }
public ConfigFieldViewModel? DownloadVideoResolutionField { get; }
public ConfigFieldViewModel? LimitDownloadRateField { get; } public ConfigFieldViewModel? LimitDownloadRateField { get; }
public ConfigFieldViewModel? DownloadLimitInMbPerSecField { get; } public ConfigFieldViewModel? DownloadLimitInMbPerSecField { get; }
@ -88,6 +93,8 @@ public sealed class ConfigCategoryViewModel : ViewModelBase
DownloadDateSelectionField != null && DownloadDateSelectionField != null &&
CustomDateField != null; CustomDateField != null;
public bool HasDownloadVideoResolutionField => DownloadVideoResolutionField != null;
public bool HasRateLimitFields => public bool HasRateLimitFields =>
LimitDownloadRateField != null && LimitDownloadRateField != null &&
DownloadLimitInMbPerSecField != null; DownloadLimitInMbPerSecField != null;

View File

@ -494,6 +494,7 @@ public partial class MainWindowViewModel(
} }
_configReturnScreen = CurrentScreen; _configReturnScreen = CurrentScreen;
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig); BuildConfigFields(configService.CurrentConfig);
ConfigScreenMessage = "Edit configuration values and save to apply changes."; ConfigScreenMessage = "Edit configuration values and save to apply changes.";
StatusMessage = "Editing configuration."; StatusMessage = "Editing configuration.";
@ -504,6 +505,7 @@ public partial class MainWindowViewModel(
private async Task CancelConfigAsync() private async Task CancelConfigAsync()
{ {
bool loaded = await configService.LoadConfigurationAsync([]); bool loaded = await configService.LoadConfigurationAsync([]);
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig); BuildConfigFields(configService.CurrentConfig);
if (!loaded) if (!loaded)
@ -569,10 +571,12 @@ public partial class MainWindowViewModel(
return; return;
} }
EnforceGuiOnlyConfigValues(newConfig);
configService.UpdateConfig(newConfig); configService.UpdateConfig(newConfig);
await configService.SaveConfigurationAsync(); await configService.SaveConfigurationAsync();
bool reloaded = await configService.LoadConfigurationAsync([]); bool reloaded = await configService.LoadConfigurationAsync([]);
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
if (!reloaded) if (!reloaded)
{ {
ConfigScreenMessage = "config.conf could not be loaded after saving. Please review your values."; ConfigScreenMessage = "config.conf could not be loaded after saving. Please review your values.";
@ -901,11 +905,13 @@ public partial class MainWindowViewModel(
ApplyThemeFromConfigFileIfAvailable(); ApplyThemeFromConfigFileIfAvailable();
_configReturnScreen = CurrentScreen; _configReturnScreen = CurrentScreen;
SetLoading("Loading configuration..."); SetLoading("Loading configuration...");
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig); BuildConfigFields(configService.CurrentConfig);
UserLists.Clear(); UserLists.Clear();
AvailableUsers.Clear(); AvailableUsers.Clear();
bool configLoaded = await configService.LoadConfigurationAsync([]); bool configLoaded = await configService.LoadConfigurationAsync([]);
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig); BuildConfigFields(configService.CurrentConfig);
if (!configLoaded) if (!configLoaded)
{ {
@ -1110,6 +1116,7 @@ public partial class MainWindowViewModel(
private bool TryBuildConfig(out Config config) private bool TryBuildConfig(out Config config)
{ {
config = CloneConfig(configService.CurrentConfig); config = CloneConfig(configService.CurrentConfig);
EnforceGuiOnlyConfigValues(config);
ClearSpecialConfigErrors(); ClearSpecialConfigErrors();
Dictionary<string, object?> parsedValues = new(StringComparer.Ordinal); Dictionary<string, object?> parsedValues = new(StringComparer.Ordinal);
Dictionary<string, ConfigFieldViewModel> fieldMap = ConfigFields Dictionary<string, ConfigFieldViewModel> fieldMap = ConfigFields
@ -1144,6 +1151,7 @@ public partial class MainWindowViewModel(
} }
ApplySpecialConfigValues(config); ApplySpecialConfigValues(config);
EnforceGuiOnlyConfigValues(config);
config.CreatorConfigs = CreatorConfigEditor.ToDictionary(); config.CreatorConfigs = CreatorConfigEditor.ToDictionary();
ValidateSpecialConfigValues(); ValidateSpecialConfigValues();
if (HasSpecialConfigErrors()) if (HasSpecialConfigErrors())
@ -1218,25 +1226,36 @@ public partial class MainWindowViewModel(
.OrderBy(group => GetCategoryOrder(group.Key)) .OrderBy(group => GetCategoryOrder(group.Key))
.ThenBy(group => group.Key); .ThenBy(group => group.Key);
int categoryIndex = 0; List<ConfigCategoryViewModel> orderedCategories = [];
foreach (IGrouping<string, ConfigFieldViewModel> group in grouped) foreach (IGrouping<string, ConfigFieldViewModel> group in grouped)
{ {
IEnumerable<ConfigFieldViewModel> orderedFields = group.Key == "File Naming" IEnumerable<ConfigFieldViewModel> orderedFields = group.Key is "File Naming" or "Download Behavior"
? group.OrderBy(field => GetFieldOrder(group.Key, field.PropertyName)) ? group.OrderBy(field => GetFieldOrder(group.Key, field.PropertyName))
: group.OrderBy(field => field.DisplayName); : group.OrderBy(field => field.DisplayName);
ConfigCategoryViewModel category = new(group.Key, orderedFields); ConfigCategoryViewModel category = new(group.Key, orderedFields);
orderedCategories.Add(category);
ConfigCategories.Add(category); ConfigCategories.Add(category);
if (categoryIndex % 2 == 0) }
{
ConfigCategoriesLeft.Add(category);
}
else
{
ConfigCategoriesRight.Add(category);
}
categoryIndex++; foreach (ConfigCategoryViewModel category in orderedCategories
.Where(category => IsLeftColumnCategory(category.CategoryName))
.OrderBy(category => GetLeftColumnCategoryOrder(category.CategoryName)))
{
ConfigCategoriesLeft.Add(category);
}
foreach (ConfigCategoryViewModel category in orderedCategories
.Where(category => IsRightColumnCategory(category.CategoryName))
.OrderBy(category => GetRightColumnCategoryOrder(category.CategoryName)))
{
ConfigCategoriesRight.Add(category);
}
foreach (ConfigCategoryViewModel category in orderedCategories.Where(category =>
!IsLeftColumnCategory(category.CategoryName) && !IsRightColumnCategory(category.CategoryName)))
{
ConfigCategoriesLeft.Add(category);
} }
} }
@ -1256,6 +1275,22 @@ public partial class MainWindowViewModel(
}; };
} }
if (categoryName == "Download Behavior")
{
return propertyName switch
{
nameof(Config.DownloadVideoResolution) => 0,
nameof(Config.DownloadPostsIncrementally) => 1,
nameof(Config.DownloadDuplicatedMedia) => 2,
nameof(Config.SkipAds) => 3,
nameof(Config.IgnoreOwnMessages) => 4,
nameof(Config.DisableTextSanitization) => 5,
nameof(Config.BypassContentForCreatorsWhoNoLongerExist) => 6,
nameof(Config.ShowScrapeSize) => 7,
_ => 100
};
}
return 0; return 0;
} }
@ -1604,6 +1639,11 @@ public partial class MainWindowViewModel(
return JsonConvert.DeserializeObject<Config>(json) ?? new Config(); return JsonConvert.DeserializeObject<Config>(json) ?? new Config();
} }
private static void EnforceGuiOnlyConfigValues(Config config)
{
config.ShowScrapeSize = false;
}
private static bool IsHiddenConfigField(string propertyName) => private static bool IsHiddenConfigField(string propertyName) =>
propertyName is nameof(Config.NonInteractiveMode) propertyName is nameof(Config.NonInteractiveMode)
or nameof(Config.NonInteractiveModeListName) or nameof(Config.NonInteractiveModeListName)
@ -1624,7 +1664,8 @@ public partial class MainWindowViewModel(
or nameof(Config.DownloadStories) or nameof(Config.DownloadStories)
or nameof(Config.DownloadHighlights) or nameof(Config.DownloadHighlights)
or nameof(Config.DownloadMessages) or nameof(Config.DownloadMessages)
or nameof(Config.DownloadPaidMessages); or nameof(Config.DownloadPaidMessages)
or nameof(Config.ShowScrapeSize);
private static string GetConfigCategory(string propertyName) => private static string GetConfigCategory(string propertyName) =>
propertyName switch propertyName switch
@ -1700,4 +1741,29 @@ public partial class MainWindowViewModel(
"Logging" => 9, "Logging" => 9,
_ => 100 _ => 100
}; };
private static bool IsLeftColumnCategory(string categoryName) =>
categoryName is "Appearance" or "Logging" or "Download Behavior" or "Subscriptions";
private static bool IsRightColumnCategory(string categoryName) =>
categoryName is "File Naming" or "Folder Structure" or "Performance";
private static int GetLeftColumnCategoryOrder(string categoryName) =>
categoryName switch
{
"Appearance" => 1,
"Logging" => 2,
"Download Behavior" => 3,
"Subscriptions" => 4,
_ => 100
};
private static int GetRightColumnCategoryOrder(string categoryName) =>
categoryName switch
{
"File Naming" => 1,
"Folder Structure" => 2,
"Performance" => 3,
_ => 100
};
} }

View File

@ -117,87 +117,8 @@
<StackPanel Spacing="14" Margin="2,0,4,0"> <StackPanel Spacing="14" Margin="2,0,4,0">
<TextBlock FontSize="22" FontWeight="SemiBold" Text="Configuration" /> <TextBlock FontSize="22" FontWeight="SemiBold" Text="Configuration" />
<Grid ColumnDefinitions="3*,5*" Margin="0,0,0,2"> <Border Classes="surface" Padding="12" Margin="0,0,0,2">
<Border Grid.Column="0" Classes="surface" Padding="12" Margin="0,0,10,0"> <StackPanel Spacing="8">
<StackPanel Spacing="8">
<TextBlock FontSize="16" FontWeight="Bold"
Foreground="{DynamicResource TextPrimaryBrush}" Text="External" />
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"
Text="FFmpeg Path" />
<Button Width="20"
Height="20"
MinWidth="20"
MinHeight="20"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="12"
FontWeight="Bold"
Background="{DynamicResource HelpBadgeBackgroundBrush}"
BorderBrush="{DynamicResource HelpBadgeBorderBrush}"
BorderThickness="1"
CornerRadius="10"
Content="?"
ToolTip.Tip="{Binding FfmpegPathHelpText}" />
</StackPanel>
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding FfmpegPath}"
Watermark="Select ffmpeg executable" />
<Button Grid.Column="1"
Margin="8,0,0,0"
Classes="secondary"
Content="Browse..."
Click="OnBrowseFfmpegPathClick" />
</Grid>
<TextBlock IsVisible="{Binding HasFfmpegPathError}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding FfmpegPathError}"
TextWrapping="Wrap" />
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,8,0,0">
<TextBlock FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"
Text="FFprobe Path" />
<Button Width="20"
Height="20"
MinWidth="20"
MinHeight="20"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="12"
FontWeight="Bold"
Background="{DynamicResource HelpBadgeBackgroundBrush}"
BorderBrush="{DynamicResource HelpBadgeBorderBrush}"
BorderThickness="1"
CornerRadius="10"
Content="?"
ToolTip.Tip="{Binding FfprobePathHelpText}" />
</StackPanel>
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding FfprobePath}"
Watermark="Select ffprobe executable" />
<Button Grid.Column="1"
Margin="8,0,0,0"
Classes="secondary"
Content="Browse..."
Click="OnBrowseFfprobePathClick" />
</Grid>
<TextBlock IsVisible="{Binding HasFfprobePathError}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding FfprobePathError}"
TextWrapping="Wrap" />
</StackPanel>
</Border>
<Border Grid.Column="1" Classes="surface" Padding="12">
<StackPanel Spacing="8">
<TextBlock FontSize="16" <TextBlock FontSize="16"
FontWeight="Bold" FontWeight="Bold"
Foreground="{DynamicResource TextPrimaryBrush}" Foreground="{DynamicResource TextPrimaryBrush}"
@ -282,13 +203,10 @@
Text="{Binding MediaSourcesError}" Text="{Binding MediaSourcesError}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
</Grid>
<Border Classes="surface" Padding="12"> <Grid ColumnDefinitions="*,*" Margin="0,0,0,2">
<StackPanel Spacing="14">
<Grid ColumnDefinitions="*,*">
<ItemsControl x:Name="LeftConfigCategories" <ItemsControl x:Name="LeftConfigCategories"
Grid.Column="0" Grid.Column="0"
Margin="0,0,6,0" Margin="0,0,6,0"
@ -305,8 +223,8 @@
Foreground="{DynamicResource TextPrimaryBrush}" Foreground="{DynamicResource TextPrimaryBrush}"
Text="{Binding CategoryName}" /> Text="{Binding CategoryName}" />
<StackPanel IsVisible="{Binding IsDownloadBehavior}" <StackPanel IsVisible="{Binding IsDownloadBehavior}"
Spacing="4" Spacing="10"
Margin="0,0,0,10"> Margin="0,0,0,0">
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock FontWeight="SemiBold" <TextBlock FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}" Foreground="{DynamicResource TextPrimaryBrush}"
@ -327,6 +245,7 @@
Content="?" Content="?"
ToolTip.Tip="{Binding ViewModel.DownloadPathHelpText, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}" /> ToolTip.Tip="{Binding ViewModel.DownloadPathHelpText, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}" />
</StackPanel> </StackPanel>
<Grid ColumnDefinitions="*,Auto"> <Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0" <TextBox Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -345,54 +264,8 @@
Text="{Binding ViewModel.DownloadPathError, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}" Text="{Binding ViewModel.DownloadPathError, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<Grid Margin="0,8,0,2" ColumnDefinitions="190,*">
<Grid Grid.Column="0"
ColumnDefinitions="*,Auto"
Margin="0,6,10,0"
ClipToBounds="True">
<TextBlock Grid.Column="0"
FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"
Text="DRM Video Duration Match Threshold"
TextWrapping="Wrap"
VerticalAlignment="Top" />
<Button Grid.Column="1"
Width="20"
Height="20"
MinWidth="20"
MinHeight="20"
Margin="6,0,0,0"
Padding="0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="12"
FontWeight="Bold"
Background="{DynamicResource HelpBadgeBackgroundBrush}"
BorderBrush="{DynamicResource HelpBadgeBorderBrush}"
BorderThickness="1"
CornerRadius="10"
Content="?"
ToolTip.Tip="{Binding ViewModel.DrmVideoDurationMatchThresholdHelpText, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}" />
</Grid>
<Grid Grid.Column="1" ColumnDefinitions="*,Auto"
VerticalAlignment="Center">
<Slider Grid.Column="0"
Minimum="0"
Maximum="100"
Value="{Binding ViewModel.DrmVideoDurationMatchThresholdPercent, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=98}" />
<TextBlock Grid.Column="1"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="{DynamicResource TextSecondaryBrush}"
Text="{Binding ViewModel.DrmVideoDurationMatchThresholdPercentLabel, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue='98%'}" />
</Grid>
</Grid>
</StackPanel>
<StackPanel IsVisible="{Binding HasSpecificDateFilterFields}" <StackPanel IsVisible="{Binding HasSpecificDateFilterFields}"
Margin="0,0,0,6" Margin="0"
Spacing="6" Spacing="6"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
x:CompileBindings="False"> x:CompileBindings="False">
@ -443,6 +316,98 @@
SelectedDate="{Binding CustomDateField.DateValue, FallbackValue={x:Null}}" /> SelectedDate="{Binding CustomDateField.DateValue, FallbackValue={x:Null}}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
<Grid IsVisible="{Binding HasDownloadVideoResolutionField}"
Margin="0"
ColumnDefinitions="190,*"
x:CompileBindings="False">
<Grid Grid.Column="0"
ColumnDefinitions="*,Auto"
Margin="0,6,10,0"
ClipToBounds="True">
<TextBlock Grid.Column="0"
FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"
Text="{Binding DownloadVideoResolutionField.DisplayName}"
TextWrapping="Wrap"
VerticalAlignment="Top" />
<Button Grid.Column="1"
IsVisible="{Binding DownloadVideoResolutionField.HasHelpText}"
Width="20"
Height="20"
MinWidth="20"
MinHeight="20"
Margin="6,0,0,0"
Padding="0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="12"
FontWeight="Bold"
Background="{DynamicResource HelpBadgeBackgroundBrush}"
BorderBrush="{DynamicResource HelpBadgeBorderBrush}"
BorderThickness="1"
CornerRadius="10"
Content="?"
ToolTip.Tip="{Binding DownloadVideoResolutionField.HelpText}" />
</Grid>
<StackPanel Grid.Column="1" Spacing="4">
<ComboBox HorizontalAlignment="Left"
MinWidth="160"
ItemsSource="{Binding DownloadVideoResolutionField.EnumOptions, FallbackValue={x:Null}}"
SelectedItem="{Binding DownloadVideoResolutionField.EnumValue, FallbackValue={x:Null}}" />
<TextBlock IsVisible="{Binding DownloadVideoResolutionField.HasError, FallbackValue=False}"
Foreground="{DynamicResource ErrorTextBrush}"
Text="{Binding DownloadVideoResolutionField.ErrorMessage, FallbackValue=''}"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
<Grid Margin="0" ColumnDefinitions="190,*">
<Grid Grid.Column="0"
ColumnDefinitions="*,Auto"
Margin="0,6,10,0"
ClipToBounds="True">
<TextBlock Grid.Column="0"
FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"
Text="DRM Video Duration Match Threshold"
TextWrapping="Wrap"
VerticalAlignment="Top" />
<Button Grid.Column="1"
Width="20"
Height="20"
MinWidth="20"
MinHeight="20"
Margin="6,0,0,0"
Padding="0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="12"
FontWeight="Bold"
Background="{DynamicResource HelpBadgeBackgroundBrush}"
BorderBrush="{DynamicResource HelpBadgeBorderBrush}"
BorderThickness="1"
CornerRadius="10"
Content="?"
ToolTip.Tip="{Binding ViewModel.DrmVideoDurationMatchThresholdHelpText, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}" />
</Grid>
<Grid Grid.Column="1" ColumnDefinitions="*,Auto"
VerticalAlignment="Center">
<Slider Grid.Column="0"
Minimum="0"
Maximum="100"
Value="{Binding ViewModel.DrmVideoDurationMatchThresholdPercent, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=98}" />
<TextBlock Grid.Column="1"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="{DynamicResource TextSecondaryBrush}"
Text="{Binding ViewModel.DrmVideoDurationMatchThresholdPercentLabel, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue='98%'}" />
</Grid>
</Grid>
</StackPanel>
<Grid IsVisible="{Binding HasRateLimitFields}" <Grid IsVisible="{Binding HasRateLimitFields}"
Margin="0,0,0,10" Margin="0,0,0,10"
ColumnDefinitions="190,*" ColumnDefinitions="190,*"
@ -807,9 +772,7 @@
Margin="6,0,0,0" Margin="6,0,0,0"
ItemsSource="{Binding ConfigCategoriesRight}" ItemsSource="{Binding ConfigCategoriesRight}"
ItemTemplate="{Binding ItemTemplate, ElementName=LeftConfigCategories}" /> ItemTemplate="{Binding ItemTemplate, ElementName=LeftConfigCategories}" />
</Grid> </Grid>
</StackPanel>
</Border>
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right"> <StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
<Button Content="Save Configuration" <Button Content="Save Configuration"