forked from sim0n00ps/OF-DL
Add a notification modal when some users could not be selected from a list
This commit is contained in:
parent
d0de99a00c
commit
0af7066086
@ -294,8 +294,9 @@ public class Program(IServiceProvider serviceProvider)
|
|||||||
}
|
}
|
||||||
else if (config.NonInteractiveMode && !string.IsNullOrEmpty(config.NonInteractiveModeListName))
|
else if (config.NonInteractiveMode && !string.IsNullOrEmpty(config.NonInteractiveModeListName))
|
||||||
{
|
{
|
||||||
Dictionary<string, long> selectedUsers =
|
ListUserSelectionResult listSelectionResult =
|
||||||
await orchestrationService.GetUsersForListAsync(config.NonInteractiveModeListName, users, lists);
|
await orchestrationService.GetUsersForListAsync(config.NonInteractiveModeListName, users, lists);
|
||||||
|
Dictionary<string, long> selectedUsers = listSelectionResult.SelectedUsers;
|
||||||
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, long>>(true, selectedUsers);
|
hasSelectedUsersKVP = new KeyValuePair<bool, Dictionary<string, long>>(true, selectedUsers);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -27,3 +27,10 @@ public class UserListResult
|
|||||||
|
|
||||||
public string? IgnoredListError { get; set; }
|
public string? IgnoredListError { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ListUserSelectionResult
|
||||||
|
{
|
||||||
|
public Dictionary<string, long> SelectedUsers { get; set; } = new();
|
||||||
|
|
||||||
|
public List<string> UnavailableUsernames { get; set; } = [];
|
||||||
|
}
|
||||||
|
|||||||
@ -110,13 +110,26 @@ public class DownloadOrchestrationService(
|
|||||||
/// <param name="allUsers">All available users.</param>
|
/// <param name="allUsers">All available users.</param>
|
||||||
/// <param name="lists">Known lists keyed by name.</param>
|
/// <param name="lists">Known lists keyed by name.</param>
|
||||||
/// <returns>The users that belong to the list.</returns>
|
/// <returns>The users that belong to the list.</returns>
|
||||||
public async Task<Dictionary<string, long>> GetUsersForListAsync(
|
public async Task<ListUserSelectionResult> GetUsersForListAsync(
|
||||||
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists)
|
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists)
|
||||||
{
|
{
|
||||||
|
ListUserSelectionResult result = new();
|
||||||
long listId = lists[listName];
|
long listId = lists[listName];
|
||||||
List<string> listUsernames = await apiService.GetListUsers($"/lists/{listId}/users") ?? [];
|
List<string> listUsernames = await apiService.GetListUsers($"/lists/{listId}/users") ?? [];
|
||||||
return allUsers.Where(x => listUsernames.Contains(x.Key))
|
HashSet<string> listUsernamesSet = listUsernames.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
HashSet<string> allUsernamesSet = allUsers.Keys.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
result.SelectedUsers = allUsers
|
||||||
|
.Where(x => listUsernamesSet.Contains(x.Key))
|
||||||
.ToDictionary(x => x.Key, x => x.Value);
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
result.UnavailableUsernames = listUsernames
|
||||||
|
.Where(username => !allUsernamesSet.Contains(username))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(username => username, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public interface IDownloadOrchestrationService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get users for a specific list by name.
|
/// Get users for a specific list by name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<Dictionary<string, long>> GetUsersForListAsync(
|
Task<ListUserSelectionResult> GetUsersForListAsync(
|
||||||
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists);
|
string listName, Dictionary<string, long> allUsers, Dictionary<string, long> lists);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -371,10 +371,23 @@ public partial class MainWindowViewModel(
|
|||||||
[NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
[NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
||||||
[NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))]
|
[NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))]
|
||||||
[NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))]
|
[NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(SelectUsersFromListCommand))]
|
||||||
private bool _isDownloadSelectionWarningModalOpen;
|
private bool _isDownloadSelectionWarningModalOpen;
|
||||||
|
|
||||||
[ObservableProperty] private string _downloadSelectionWarningMessage = string.Empty;
|
[ObservableProperty] private string _downloadSelectionWarningMessage = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(OpenSinglePostOrMessageModalCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(SelectUsersFromListCommand))]
|
||||||
|
private bool _isListSelectionWarningModalOpen;
|
||||||
|
|
||||||
|
[ObservableProperty] private string _listSelectionWarningMessage = string.Empty;
|
||||||
|
|
||||||
|
public ObservableCollection<string> ListSelectionUnavailableUsers { get; } = [];
|
||||||
|
|
||||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))]
|
||||||
private string _singlePostOrMessageUrl = string.Empty;
|
private string _singlePostOrMessageUrl = string.Empty;
|
||||||
|
|
||||||
@ -827,20 +840,24 @@ public partial class MainWindowViewModel(
|
|||||||
user.IsSelected = false;
|
user.IsSelected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<string, long> listUsers = await downloadOrchestrationService.GetUsersForListAsync(
|
ListUserSelectionResult listSelectionResult = await downloadOrchestrationService.GetUsersForListAsync(
|
||||||
SelectedListName,
|
SelectedListName,
|
||||||
_allUsers,
|
_allUsers,
|
||||||
_allLists);
|
_allLists);
|
||||||
HashSet<string> selectedUsernames = listUsers.Keys.ToHashSet(StringComparer.Ordinal);
|
HashSet<string> selectedUsernames = listSelectionResult.SelectedUsers.Keys
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (SelectableUserViewModel user in AvailableUsers)
|
foreach (SelectableUserViewModel user in AvailableUsers)
|
||||||
{
|
{
|
||||||
user.IsSelected = selectedUsernames.Contains(user.Username);
|
user.IsSelected = selectedUsernames.Contains(user.Username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShowListSelectionWarningIfNeeded(listSelectionResult.UnavailableUsernames);
|
||||||
|
|
||||||
OnPropertyChanged(nameof(SelectedUsersSummary));
|
OnPropertyChanged(nameof(SelectedUsersSummary));
|
||||||
OnPropertyChanged(nameof(AllUsersSelected));
|
OnPropertyChanged(nameof(AllUsersSelected));
|
||||||
DownloadSelectedCommand.NotifyCanExecuteChanged();
|
DownloadSelectedCommand.NotifyCanExecuteChanged();
|
||||||
|
SelectUsersFromListCommand.NotifyCanExecuteChanged();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -935,6 +952,13 @@ public partial class MainWindowViewModel(
|
|||||||
EditConfig();
|
EditConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CloseListSelectionWarning()
|
||||||
|
{
|
||||||
|
IsListSelectionWarningModalOpen = false;
|
||||||
|
ListSelectionUnavailableUsers.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanStopWork))]
|
[RelayCommand(CanExecute = nameof(CanStopWork))]
|
||||||
private void StopWork()
|
private void StopWork()
|
||||||
{
|
{
|
||||||
@ -1075,6 +1099,7 @@ public partial class MainWindowViewModel(
|
|||||||
private bool CanApplySelectedList() =>
|
private bool CanApplySelectedList() =>
|
||||||
CurrentScreen == AppScreen.UserSelection &&
|
CurrentScreen == AppScreen.UserSelection &&
|
||||||
!string.IsNullOrWhiteSpace(SelectedListName) &&
|
!string.IsNullOrWhiteSpace(SelectedListName) &&
|
||||||
|
!IsListSelectionWarningModalOpen &&
|
||||||
!IsDownloading;
|
!IsDownloading;
|
||||||
|
|
||||||
private bool CanDownloadSelected() =>
|
private bool CanDownloadSelected() =>
|
||||||
@ -1083,6 +1108,7 @@ public partial class MainWindowViewModel(
|
|||||||
!IsDownloadSelectionWarningModalOpen &&
|
!IsDownloadSelectionWarningModalOpen &&
|
||||||
!IsMissingCdmWarningModalOpen &&
|
!IsMissingCdmWarningModalOpen &&
|
||||||
!IsShowScrapeSizeWarningModalOpen &&
|
!IsShowScrapeSizeWarningModalOpen &&
|
||||||
|
!IsListSelectionWarningModalOpen &&
|
||||||
!IsDownloading;
|
!IsDownloading;
|
||||||
|
|
||||||
private bool CanDownloadPurchasedTab() =>
|
private bool CanDownloadPurchasedTab() =>
|
||||||
@ -1091,6 +1117,7 @@ public partial class MainWindowViewModel(
|
|||||||
!IsDownloadSelectionWarningModalOpen &&
|
!IsDownloadSelectionWarningModalOpen &&
|
||||||
!IsMissingCdmWarningModalOpen &&
|
!IsMissingCdmWarningModalOpen &&
|
||||||
!IsShowScrapeSizeWarningModalOpen &&
|
!IsShowScrapeSizeWarningModalOpen &&
|
||||||
|
!IsListSelectionWarningModalOpen &&
|
||||||
!IsDownloading;
|
!IsDownloading;
|
||||||
|
|
||||||
private bool CanOpenSinglePostOrMessageModal() =>
|
private bool CanOpenSinglePostOrMessageModal() =>
|
||||||
@ -1099,6 +1126,7 @@ public partial class MainWindowViewModel(
|
|||||||
!IsDownloadSelectionWarningModalOpen &&
|
!IsDownloadSelectionWarningModalOpen &&
|
||||||
!IsMissingCdmWarningModalOpen &&
|
!IsMissingCdmWarningModalOpen &&
|
||||||
!IsShowScrapeSizeWarningModalOpen &&
|
!IsShowScrapeSizeWarningModalOpen &&
|
||||||
|
!IsListSelectionWarningModalOpen &&
|
||||||
!IsSinglePostOrMessageModalOpen;
|
!IsSinglePostOrMessageModalOpen;
|
||||||
|
|
||||||
private bool CanSubmitSinglePostOrMessage() =>
|
private bool CanSubmitSinglePostOrMessage() =>
|
||||||
@ -1106,6 +1134,7 @@ public partial class MainWindowViewModel(
|
|||||||
!IsDownloadSelectionWarningModalOpen &&
|
!IsDownloadSelectionWarningModalOpen &&
|
||||||
!IsMissingCdmWarningModalOpen &&
|
!IsMissingCdmWarningModalOpen &&
|
||||||
!IsShowScrapeSizeWarningModalOpen &&
|
!IsShowScrapeSizeWarningModalOpen &&
|
||||||
|
!IsListSelectionWarningModalOpen &&
|
||||||
!IsDownloading &&
|
!IsDownloading &&
|
||||||
!string.IsNullOrWhiteSpace(SinglePostOrMessageUrl);
|
!string.IsNullOrWhiteSpace(SinglePostOrMessageUrl);
|
||||||
|
|
||||||
@ -1195,7 +1224,8 @@ public partial class MainWindowViewModel(
|
|||||||
partial void OnSelectedListNameChanged(string? value)
|
partial void OnSelectedListNameChanged(string? value)
|
||||||
{
|
{
|
||||||
SelectUsersFromListCommand.NotifyCanExecuteChanged();
|
SelectUsersFromListCommand.NotifyCanExecuteChanged();
|
||||||
if (_isApplyingListSelection || IsDownloading || CurrentScreen != AppScreen.UserSelection ||
|
if (_isApplyingListSelection || IsDownloading || IsListSelectionWarningModalOpen ||
|
||||||
|
CurrentScreen != AppScreen.UserSelection ||
|
||||||
string.IsNullOrWhiteSpace(value))
|
string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -1533,6 +1563,42 @@ public partial class MainWindowViewModel(
|
|||||||
return confirmed;
|
return confirmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowListSelectionWarningIfNeeded(IReadOnlyCollection<string> unavailableUsers)
|
||||||
|
{
|
||||||
|
if (unavailableUsers.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListSelectionUnavailableUsers.Clear();
|
||||||
|
foreach (string username in unavailableUsers.OrderBy(name => name, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ListSelectionUnavailableUsers.Add(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> recommendations = [];
|
||||||
|
if (!configService.CurrentConfig.IncludeExpiredSubscriptions)
|
||||||
|
{
|
||||||
|
recommendations.Add("Include Expired Subscriptions");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configService.CurrentConfig.IncludeRestrictedSubscriptions)
|
||||||
|
{
|
||||||
|
recommendations.Add("Include Restricted Subscriptions");
|
||||||
|
}
|
||||||
|
|
||||||
|
string recommendationText = recommendations.Count > 0
|
||||||
|
? $"Recommendation:\nEnable {string.Join(" and ", recommendations.Select(option => $"\"{option}\""))} in the subscriptions section of the configuration page. After doing so, select \"Refresh\" from the file menu."
|
||||||
|
: "These users may be unavailable or inaccessible with your current account.";
|
||||||
|
|
||||||
|
ListSelectionWarningMessage =
|
||||||
|
$"{unavailableUsers.Count} user(s) from \"{SelectedListName}\" could not be selected.\n\n" +
|
||||||
|
$"{recommendationText}\n\n" +
|
||||||
|
"Users that could not be selected:";
|
||||||
|
|
||||||
|
IsListSelectionWarningModalOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryParseSinglePostOrMessageUrl(
|
private static bool TryParseSinglePostOrMessageUrl(
|
||||||
string url,
|
string url,
|
||||||
out SingleDownloadRequest request,
|
out SingleDownloadRequest request,
|
||||||
|
|||||||
@ -1573,6 +1573,58 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Grid.RowSpan="3"
|
||||||
|
IsVisible="{Binding IsListSelectionWarningModalOpen}"
|
||||||
|
Background="{DynamicResource OverlayBackgroundBrush}"
|
||||||
|
ZIndex="1002"
|
||||||
|
PointerPressed="OnModalOverlayClicked">
|
||||||
|
<Border Background="{DynamicResource ModalBackgroundBrush}"
|
||||||
|
BorderBrush="{DynamicResource ModalBorderBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="16"
|
||||||
|
Padding="28"
|
||||||
|
Width="720"
|
||||||
|
MaxHeight="560"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
BoxShadow="0 20 25 -5 #19000000, 0 10 10 -5 #0F000000"
|
||||||
|
PointerPressed="OnModalContentClicked">
|
||||||
|
<StackPanel Spacing="16">
|
||||||
|
<TextBlock FontSize="20"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{DynamicResource TextPrimaryBrush}"
|
||||||
|
Text="Some Users Could Not Be Selected" />
|
||||||
|
|
||||||
|
<TextBlock Foreground="{DynamicResource TextSecondaryBrush}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="{Binding ListSelectionWarningMessage}" />
|
||||||
|
|
||||||
|
<Border BorderBrush="{DynamicResource SurfaceBorderBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
Background="{DynamicResource SurfaceBackgroundBrush}"
|
||||||
|
Padding="8"
|
||||||
|
MaxHeight="240">
|
||||||
|
<ListBox ItemsSource="{Binding ListSelectionUnavailableUsers}"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
||||||
|
<Button Content="Close"
|
||||||
|
Classes="primary"
|
||||||
|
Command="{Binding CloseListSelectionWarningCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="0" Grid.RowSpan="3"
|
<Grid Grid.Row="0" Grid.RowSpan="3"
|
||||||
IsVisible="{Binding IsMissingCdmWarningModalOpen}"
|
IsVisible="{Binding IsMissingCdmWarningModalOpen}"
|
||||||
Background="{DynamicResource OverlayBackgroundBrush}"
|
Background="{DynamicResource OverlayBackgroundBrush}"
|
||||||
|
|||||||
@ -283,6 +283,7 @@ public partial class MainWindow : Window
|
|||||||
vm.CreatorConfigEditor.ModalViewModel.CancelCommand.Execute(null);
|
vm.CreatorConfigEditor.ModalViewModel.CancelCommand.Execute(null);
|
||||||
vm.CancelSinglePostOrMessageCommand.Execute(null);
|
vm.CancelSinglePostOrMessageCommand.Execute(null);
|
||||||
vm.CancelDownloadSelectionWarningCommand.Execute(null);
|
vm.CancelDownloadSelectionWarningCommand.Execute(null);
|
||||||
|
vm.CloseListSelectionWarningCommand.Execute(null);
|
||||||
vm.CancelMissingCdmWarningCommand.Execute(null);
|
vm.CancelMissingCdmWarningCommand.Execute(null);
|
||||||
vm.CancelShowScrapeSizeWarningCommand.Execute(null);
|
vm.CancelShowScrapeSizeWarningCommand.Execute(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,10 +79,30 @@ public class DownloadOrchestrationServiceTests
|
|||||||
Dictionary<string, long> allUsers = new() { { "alice", 1 }, { "bob", 2 } };
|
Dictionary<string, long> allUsers = new() { { "alice", 1 }, { "bob", 2 } };
|
||||||
Dictionary<string, long> lists = new() { { "mylist", 5 } };
|
Dictionary<string, long> lists = new() { { "mylist", 5 } };
|
||||||
|
|
||||||
Dictionary<string, long> result = await service.GetUsersForListAsync("mylist", allUsers, lists);
|
ListUserSelectionResult result = await service.GetUsersForListAsync("mylist", allUsers, lists);
|
||||||
|
|
||||||
Assert.Single(result);
|
Assert.Single(result.SelectedUsers);
|
||||||
Assert.Equal(2, result["bob"]);
|
Assert.Equal(2, result.SelectedUsers["bob"]);
|
||||||
|
Assert.Empty(result.UnavailableUsernames);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetUsersForListAsync_ReturnsUnavailableUsers()
|
||||||
|
{
|
||||||
|
FakeConfigService configService = new(CreateConfig());
|
||||||
|
ConfigurableApiService apiService = new()
|
||||||
|
{
|
||||||
|
ListUsersHandler = _ => Task.FromResult<List<string>?>(["bob", "carol"])
|
||||||
|
};
|
||||||
|
DownloadOrchestrationService service =
|
||||||
|
new(apiService, configService, new OrchestrationDownloadServiceStub(), new UserTrackingDbService());
|
||||||
|
Dictionary<string, long> allUsers = new() { { "alice", 1 }, { "bob", 2 } };
|
||||||
|
Dictionary<string, long> lists = new() { { "mylist", 5 } };
|
||||||
|
|
||||||
|
ListUserSelectionResult result = await service.GetUsersForListAsync("mylist", allUsers, lists);
|
||||||
|
|
||||||
|
Assert.Single(result.SelectedUsers);
|
||||||
|
Assert.Equal("carol", Assert.Single(result.UnavailableUsernames));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user