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

View File

@ -494,6 +494,7 @@ public partial class MainWindowViewModel(
}
_configReturnScreen = CurrentScreen;
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig);
ConfigScreenMessage = "Edit configuration values and save to apply changes.";
StatusMessage = "Editing configuration.";
@ -504,6 +505,7 @@ public partial class MainWindowViewModel(
private async Task CancelConfigAsync()
{
bool loaded = await configService.LoadConfigurationAsync([]);
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig);
if (!loaded)
@ -569,10 +571,12 @@ public partial class MainWindowViewModel(
return;
}
EnforceGuiOnlyConfigValues(newConfig);
configService.UpdateConfig(newConfig);
await configService.SaveConfigurationAsync();
bool reloaded = await configService.LoadConfigurationAsync([]);
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
if (!reloaded)
{
ConfigScreenMessage = "config.conf could not be loaded after saving. Please review your values.";
@ -901,11 +905,13 @@ public partial class MainWindowViewModel(
ApplyThemeFromConfigFileIfAvailable();
_configReturnScreen = CurrentScreen;
SetLoading("Loading configuration...");
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig);
UserLists.Clear();
AvailableUsers.Clear();
bool configLoaded = await configService.LoadConfigurationAsync([]);
EnforceGuiOnlyConfigValues(configService.CurrentConfig);
BuildConfigFields(configService.CurrentConfig);
if (!configLoaded)
{
@ -1110,6 +1116,7 @@ public partial class MainWindowViewModel(
private bool TryBuildConfig(out Config config)
{
config = CloneConfig(configService.CurrentConfig);
EnforceGuiOnlyConfigValues(config);
ClearSpecialConfigErrors();
Dictionary<string, object?> parsedValues = new(StringComparer.Ordinal);
Dictionary<string, ConfigFieldViewModel> fieldMap = ConfigFields
@ -1144,6 +1151,7 @@ public partial class MainWindowViewModel(
}
ApplySpecialConfigValues(config);
EnforceGuiOnlyConfigValues(config);
config.CreatorConfigs = CreatorConfigEditor.ToDictionary();
ValidateSpecialConfigValues();
if (HasSpecialConfigErrors())
@ -1218,25 +1226,36 @@ public partial class MainWindowViewModel(
.OrderBy(group => GetCategoryOrder(group.Key))
.ThenBy(group => group.Key);
int categoryIndex = 0;
List<ConfigCategoryViewModel> orderedCategories = [];
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 => field.DisplayName);
ConfigCategoryViewModel category = new(group.Key, orderedFields);
orderedCategories.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;
}
@ -1604,6 +1639,11 @@ public partial class MainWindowViewModel(
return JsonConvert.DeserializeObject<Config>(json) ?? new Config();
}
private static void EnforceGuiOnlyConfigValues(Config config)
{
config.ShowScrapeSize = false;
}
private static bool IsHiddenConfigField(string propertyName) =>
propertyName is nameof(Config.NonInteractiveMode)
or nameof(Config.NonInteractiveModeListName)
@ -1624,7 +1664,8 @@ public partial class MainWindowViewModel(
or nameof(Config.DownloadStories)
or nameof(Config.DownloadHighlights)
or nameof(Config.DownloadMessages)
or nameof(Config.DownloadPaidMessages);
or nameof(Config.DownloadPaidMessages)
or nameof(Config.ShowScrapeSize);
private static string GetConfigCategory(string propertyName) =>
propertyName switch
@ -1700,4 +1741,29 @@ public partial class MainWindowViewModel(
"Logging" => 9,
_ => 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">
<TextBlock FontSize="22" FontWeight="SemiBold" Text="Configuration" />
<Grid ColumnDefinitions="3*,5*" Margin="0,0,0,2">
<Border Grid.Column="0" Classes="surface" Padding="12" Margin="0,0,10,0">
<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">
<Border Classes="surface" Padding="12" Margin="0,0,0,2">
<StackPanel Spacing="8">
<TextBlock FontSize="16"
FontWeight="Bold"
Foreground="{DynamicResource TextPrimaryBrush}"
@ -282,13 +203,10 @@
Text="{Binding MediaSourcesError}"
TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</Border>
</Grid>
</StackPanel>
</Border>
<Border Classes="surface" Padding="12">
<StackPanel Spacing="14">
<Grid ColumnDefinitions="*,*">
<Grid ColumnDefinitions="*,*" Margin="0,0,0,2">
<ItemsControl x:Name="LeftConfigCategories"
Grid.Column="0"
Margin="0,0,6,0"
@ -305,8 +223,8 @@
Foreground="{DynamicResource TextPrimaryBrush}"
Text="{Binding CategoryName}" />
<StackPanel IsVisible="{Binding IsDownloadBehavior}"
Spacing="4"
Margin="0,0,0,10">
Spacing="10"
Margin="0,0,0,0">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"
@ -327,6 +245,7 @@
Content="?"
ToolTip.Tip="{Binding ViewModel.DownloadPathHelpText, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}" />
</StackPanel>
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
VerticalAlignment="Center"
@ -345,54 +264,8 @@
Text="{Binding ViewModel.DownloadPathError, RelativeSource={RelativeSource AncestorType=views:MainWindow}, FallbackValue=''}"
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}"
Margin="0,0,0,6"
Margin="0"
Spacing="6"
HorizontalAlignment="Stretch"
x:CompileBindings="False">
@ -443,6 +316,98 @@
SelectedDate="{Binding CustomDateField.DateValue, FallbackValue={x:Null}}" />
</Grid>
</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}"
Margin="0,0,0,10"
ColumnDefinitions="190,*"
@ -807,9 +772,7 @@
Margin="6,0,0,0"
ItemsSource="{Binding ConfigCategoriesRight}"
ItemTemplate="{Binding ItemTemplate, ElementName=LeftConfigCategories}" />
</Grid>
</StackPanel>
</Border>
</Grid>
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
<Button Content="Save Configuration"