Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions SentryReplay.Tests/MainWindowViewModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,47 +279,47 @@ public void DismissError_ClearsErrorState()
// --- Clip browsing: the injectable clip loader lets us populate clips without disk I/O ---

[Fact]
public void FilteredClips_OrderNewestFirst()
public async Task FilteredClips_OrderNewestFirst()
{
var clips = TestClips.Create(3); // timestamps increase with index
var vm = new MainWindowViewModel(() => null!, clipLoader: _ => clips);

vm.LoadClips(new[] { "root" });
await vm.LoadClipsAsync(new[] { "root" });

vm.FilteredClips.Select(c => c.Name).ShouldBe(new[] { "Clip 2", "Clip 1", "Clip 0" });
}

[Fact]
public void FilteredClips_FiltersByNameCaseInsensitively()
public async Task FilteredClips_FiltersByNameCaseInsensitively()
{
var clips = TestClips.Create(3);
var vm = new MainWindowViewModel(() => null!, clipLoader: _ => clips);
vm.LoadClips(new[] { "root" });
await vm.LoadClipsAsync(new[] { "root" });

vm.FilterText = "clip 1";

vm.FilteredClips.Single().Name.ShouldBe("Clip 1");
}

[Fact]
public void FilteredClips_FiltersByPath()
public async Task FilteredClips_FiltersByPath()
{
// TestClips share a folder path but have distinct names, so a path-only match keeps them all.
var clips = TestClips.Create(2);
var vm = new MainWindowViewModel(() => null!, clipLoader: _ => clips);
vm.LoadClips(new[] { "root" });
await vm.LoadClipsAsync(new[] { "root" });

vm.FilterText = clips[0].FullPath;

vm.FilteredClips.Count.ShouldBe(2);
}

[Fact]
public void FilteredClips_NoMatch_IsEmpty()
public async Task FilteredClips_NoMatch_IsEmpty()
{
var clips = TestClips.Create(3);
var vm = new MainWindowViewModel(() => null!, clipLoader: _ => clips);
vm.LoadClips(new[] { "root" });
await vm.LoadClipsAsync(new[] { "root" });

vm.FilterText = "no-such-clip";

Expand Down Expand Up @@ -410,8 +410,8 @@ public void ControllerError_ShowsErrorOverlay()
[Fact]
public void CanGoNextPrevious_ReflectControllerPlaylist()
{
var vm = CreateViewModelWithController(out _, out _, clipLoader: _ => TestClips.Create(3));
vm.LoadClips(new[] { "root" });
var vm = CreateViewModelWithController(out var controller, out _);
controller.LoadClips(TestClips.Create(3)); // set the playlist directly (synchronous, on the test thread)

// Playlist loaded, nothing playing yet: can advance, can't go back.
vm.CanGoNext.ShouldBeTrue();
Expand Down
155 changes: 125 additions & 30 deletions SentryReplay/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Padding="{TemplateBinding Padding}"
<Border x:Name="ButtonBorder"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CornerRadius="4">
CornerRadius="4"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<ScaleTransform />
</Border.RenderTransform>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
Expand All @@ -42,6 +47,34 @@
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{DynamicResource ControlFillColorTertiaryBrush}" />
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="RenderTransform.ScaleX"
To="0.96"
Duration="0:0:0.08" />
<DoubleAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="RenderTransform.ScaleY"
To="0.96"
Duration="0:0:0.08" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="RenderTransform.ScaleX"
To="1"
Duration="0:0:0.12" />
<DoubleAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="RenderTransform.ScaleY"
To="1"
Duration="0:0:0.12" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDisabledBrush}" />
Expand Down Expand Up @@ -84,6 +117,8 @@
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<!-- No scale animation here: these tiles host the live mini-camera previews, and the
Flyleaf surface follows the tile's render transform, so scaling would shift the video. -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="TileBorder" Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}" />
</Trigger>
Expand Down Expand Up @@ -147,6 +182,24 @@
</Style>
</Window.Resources>

<Window.Triggers>
<!-- Fade the window in on launch -->
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.25">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>

<Grid>
<Grid Visibility="{Binding ShowMainContent, Converter={local:BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
Expand Down Expand Up @@ -295,13 +348,36 @@
</ListBox.ItemTemplate>
</ListBox>

<!-- Subtle animated loading bar shown while the clip list is (re)scanned -->
<ProgressBar Grid.Row="1"
Height="3"
Margin="4,0"
VerticalAlignment="Top"
Background="Transparent"
BorderThickness="0"
IsIndeterminate="True"
Visibility="{Binding IsLoadingClips, Converter={local:BoolToVisibilityConverter}}" />

<!-- Folder picker -->
<StackPanel Grid.Row="2"
Margin="8">
<Button Content="📁 Select Folder"
Command="{Binding OpenFolderCommand}"
Padding="12"
Style="{StaticResource PlayerButton}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="📁 Select Folder"
Command="{Binding OpenFolderCommand}"
Padding="12"
Style="{StaticResource PlayerButton}" />
<Button Grid.Column="1"
Content="🔄"
Command="{Binding RefreshClipsCommand}"
Padding="12"
Style="{StaticResource PlayerButton}"
ToolTip="Rescan the current folder for clips" />
</Grid>
<Grid Margin="0,8,0,0">
<Button Content="ℹ️ About / Help"
Command="{Binding ToggleAboutCommand}"
Expand Down Expand Up @@ -532,12 +608,11 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="FrontTileHostSlot" />
<TextBlock Text="Main view"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Visibility="{Binding IsFrontViewSelected, Converter={local:BoolToVisibilityConverter}}" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsFrontViewSelected, Converter={local:BoolToVisibilityConverter}}" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand Down Expand Up @@ -565,12 +640,11 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="RearTileHostSlot" />
<TextBlock Text="Main view"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Visibility="{Binding IsRearViewSelected, Converter={local:BoolToVisibilityConverter}}" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsRearViewSelected, Converter={local:BoolToVisibilityConverter}}" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand Down Expand Up @@ -598,12 +672,11 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="LeftTileHostSlot" />
<TextBlock Text="Main view"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Visibility="{Binding IsLeftViewSelected, Converter={local:BoolToVisibilityConverter}}" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsLeftViewSelected, Converter={local:BoolToVisibilityConverter}}" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand Down Expand Up @@ -631,12 +704,11 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="RightTileHostSlot" />
<TextBlock Text="Main view"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Visibility="{Binding IsRightViewSelected, Converter={local:BoolToVisibilityConverter}}" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsRightViewSelected, Converter={local:BoolToVisibilityConverter}}" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand All @@ -655,6 +727,29 @@
<Border Grid.RowSpan="2"
Background="#80000000"
Visibility="{Binding ShowStatusOverlay, Converter={local:BoolToVisibilityConverter}}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowStatusOverlay}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel MaxWidth="500"
HorizontalAlignment="Center"
VerticalAlignment="Center">
Expand Down
Loading