Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Peek] ImagePreviewer - Handle error states #22637

Merged
merged 10 commits into from
Dec 8, 2022
2 changes: 1 addition & 1 deletion src/modules/peek/Peek.Common/Converters/BoolConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Peek.Common.Converters
{
public class BoolConverter
public static class BoolConverter
{
public static bool Invert(bool value)
{
Expand Down
37 changes: 37 additions & 0 deletions src/modules/peek/Peek.Common/Extensions/DispatcherExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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 Peek.Common.Extensions
{
using System;
using System.Threading.Tasks;
using Microsoft.UI.Dispatching;

public static class DispatcherExtensions
{
/// <summary>
/// Run work on UI thread safely.
/// </summary>
/// <returns>True if the work was run successfully, False otherwise.</returns>
public static Task RunOnUiThread(this DispatcherQueue dispatcher, Func<Task> work)
{
var tcs = new TaskCompletionSource();
dispatcher.TryEnqueue(async () =>
{
try
{
await work();

tcs.SetResult();
}
catch (Exception e)
{
tcs.SetException(e);
}
});

return tcs.Task;
}
}
}
32 changes: 32 additions & 0 deletions src/modules/peek/Peek.Common/Extensions/TaskExtension.cs
Original file line number Diff line number Diff line change
@@ -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.

namespace Peek.Common.Extensions
{
using System;
using System.Threading.Tasks;

public static class TaskExtension
{
public static Task<bool> RunSafe(Func<Task> work)
miksalmon marked this conversation as resolved.
Show resolved Hide resolved
{
var tcs = new TaskCompletionSource<bool>();
Task.Run(async () =>
{
try
{
await work();

tcs.SetResult(true);
}
catch (Exception)
{
tcs.SetResult(false);
}
});

return tcs.Task;
}
}
}
25 changes: 13 additions & 12 deletions src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,39 @@
x:Class="Peek.FilePreviewer.FilePreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="using:Peek.Common.Converters"
xmlns:controls="using:Peek.FilePreviewer.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Peek.FilePreviewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Peek.FilePreviewer.Controls"
xmlns:previewers="using:Peek.FilePreviewer.Previewers"
mc:Ignorable="d">

<Grid>
<ProgressRing
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsActive="{x:Bind conv:BoolConverter.Invert(Previewer.IsPreviewLoaded), Mode=OneWay}" />
IsActive="{x:Bind MatchPreviewState(Previewer.State, previewers:PreviewState.Loading), Mode=OneWay}" />

<Image
x:Name="PreviewImage"
x:Name="ImagePreview"
Source="{x:Bind BitmapPreviewer.Preview, Mode=OneWay}"
Visibility="{x:Bind IsImageVisible, Mode=OneWay}" />
Visibility="{x:Bind IsPreviewVisible(BitmapPreviewer, Previewer.State), Mode=OneWay}" />

<controls:BrowserControl x:Name="PreviewBrowser"
x:Load="True"
Source="{x:Bind BrowserPreviewer.Preview, Mode=OneWay}"
IsNavigationCompleted="{x:Bind BrowserPreviewer.IsPreviewLoaded, Mode=TwoWay}"
Visibility="{x:Bind IsBrowserVisible, Mode=OneWay, FallbackValue=Collapsed}"
NavigationCompleted="PreviewBrowser_NavigationCompleted"/>
<controls:BrowserControl
x:Name="BrowserPreview"
x:Load="True"
NavigationCompleted="PreviewBrowser_NavigationCompleted"
Source="{x:Bind BrowserPreviewer.Preview, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(BrowserPreviewer, Previewer.State), Mode=OneWay, FallbackValue=Collapsed}" />

<local:UnsupportedFilePreview
x:Name="UnsupportedFilePreview"
DateModified="{x:Bind UnsupportedFilePreviewer.DateModified, Mode=OneWay}"
FileName="{x:Bind UnsupportedFilePreviewer.FileName, Mode=OneWay}"
FileSize="{x:Bind UnsupportedFilePreviewer.FileSize, Mode=OneWay}"
FileType="{x:Bind UnsupportedFilePreviewer.FileType, Mode=OneWay}"
IconPreview="{x:Bind UnsupportedFilePreviewer.IconPreview, Mode=OneWay}"
Visibility="{x:Bind IsUnsupportedPreviewVisible, Mode=OneWay}" />
Visibility="{x:Bind IsPreviewVisible(UnsupportedFilePreviewer, Previewer.State), Mode=OneWay}" />

</Grid>
</UserControl>
71 changes: 51 additions & 20 deletions src/modules/peek/Peek.FilePreviewer/FilePreview.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,47 +31,53 @@ public sealed partial class FilePreview : UserControl

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(BitmapPreviewer))]
[NotifyPropertyChangedFor(nameof(IsImageVisible))]
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]
[NotifyPropertyChangedFor(nameof(IsUnsupportedPreviewVisible))]
[NotifyPropertyChangedFor(nameof(BrowserPreviewer))]
[NotifyPropertyChangedFor(nameof(IsBrowserVisible))]
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]
private IPreviewer? previewer;

public FilePreview()
{
InitializeComponent();
}

private async void Previewer_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// Fallback on DefaultPreviewer if we fail to load the correct Preview
if (e.PropertyName == nameof(IPreviewer.State))
{
if (Previewer?.State == PreviewState.Error)
{
Previewer = previewerFactory.CreateDefaultPreviewer(File);
await UpdatePreviewAsync();
}
}
}

public IBitmapPreviewer? BitmapPreviewer => Previewer as IBitmapPreviewer;

public IBrowserPreview? BrowserPreviewer => Previewer as IBrowserPreview;
public IBrowserPreviewer? BrowserPreviewer => Previewer as IBrowserPreviewer;

public bool IsImageVisible => BitmapPreviewer != null;

public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;

public bool IsUnsupportedPreviewVisible => UnsupportedFilePreviewer != null;

/* TODO: need a better way to switch visibility according to the Preview.
* Could use Enum + Converter to switch according to the current preview. */
public bool IsBrowserVisible
public File File
{
get
{
if (BrowserPreviewer != null)
{
return BrowserPreviewer.IsPreviewLoaded;
}
get => (File)GetValue(FilesProperty);
set => SetValue(FilesProperty, value);
}

return false;
}
public bool MatchPreviewState(PreviewState? value, PreviewState stateToMatch)
{
return value == stateToMatch;
}

public File File
public Visibility IsPreviewVisible(IPreviewer? previewer, PreviewState? state)
{
get => (File)GetValue(FilesProperty);
set => SetValue(FilesProperty, value);
var isValidPreview = previewer != null && MatchPreviewState(state, PreviewState.Loaded);
return isValidPreview ? Visibility.Visible : Visibility.Collapsed;
}

private async Task OnFilePropertyChanged()
Expand All @@ -80,10 +86,19 @@ private async Task OnFilePropertyChanged()
// https://github.com/microsoft/PowerToys/issues/22480
if (File == null)
{
Previewer = null;
ImagePreview.Visibility = Visibility.Collapsed;
BrowserPreview.Visibility = Visibility.Collapsed;
UnsupportedFilePreview.Visibility = Visibility.Collapsed;
miksalmon marked this conversation as resolved.
Show resolved Hide resolved
return;
}

Previewer = previewerFactory.Create(File);
await UpdatePreviewAsync();
}

private async Task UpdatePreviewAsync()
{
if (Previewer != null)
{
var size = await Previewer.GetPreviewSizeAsync();
Expand All @@ -92,10 +107,26 @@ private async Task OnFilePropertyChanged()
}
}

partial void OnPreviewerChanging(IPreviewer? value)
{
if (Previewer != null)
{
Previewer.PropertyChanged -= Previewer_PropertyChanged;
}

if (value != null)
{
value.PropertyChanged += Previewer_PropertyChanged;
}
}

private void PreviewBrowser_NavigationCompleted(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args)
{
// Once browser has completed navigation it is ready to be visible
OnPropertyChanged(nameof(IsBrowserVisible));
if (BrowserPreviewer != null)
{
BrowserPreviewer.State = PreviewState.Loaded;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Peek.FilePreviewer.Previewers
using Windows.Foundation;
using File = Peek.Common.Models.File;

public partial class HtmlPreviewer : ObservableObject, IBrowserPreview
public partial class HtmlPreviewer : ObservableObject, IBrowserPreviewer
{
private static readonly HashSet<string> _supportedFileTypes = new HashSet<string>
{
Expand All @@ -26,7 +26,7 @@ public partial class HtmlPreviewer : ObservableObject, IBrowserPreview
private Uri? preview;

[ObservableProperty]
private bool isPreviewLoaded;
private PreviewState state;

public HtmlPreviewer(File file)
{
Expand All @@ -44,6 +44,8 @@ public Task<Size> GetPreviewSizeAsync()

public Task LoadPreviewAsync()
{
State = PreviewState.Loading;

Preview = new Uri(File.Path);

return Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ namespace Peek.FilePreviewer.Previewers
{
using System;

public interface IBrowserPreview : IPreviewer
public interface IBrowserPreviewer : IPreviewer
{
public Uri? Preview { get; }

public new bool IsPreviewLoaded { get; set; }
}
}
10 changes: 9 additions & 1 deletion src/modules/peek/Peek.FilePreviewer/Previewers/IPreviewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ namespace Peek.FilePreviewer.Previewers

public interface IPreviewer : INotifyPropertyChanged
{
bool IsPreviewLoaded { get; }
PreviewState State { get; set; }

public static bool IsFileTypeSupported(string fileExt) => throw new NotImplementedException();

public Task<Size> GetPreviewSizeAsync();

Task LoadPreviewAsync();
}

public enum PreviewState
{
Uninitialized,
Loading,
Loaded,
Error,
}
}
Loading