mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
[Peek]Support for special folders like Recycle Bin and My PC (#33310)
* Peek support for special folders * Renamed ThisComputer->ThisPC * Used different variable name to avoid spellcheck issues * Made label of empty fields hidden * Removed ThisPC special handling and last modified date of recycle bin
This commit is contained in:
@@ -85,7 +85,7 @@
|
|||||||
<UsePrecompiledHeaders Condition="'$(TF_BUILD)' != ''">false</UsePrecompiledHeaders>
|
<UsePrecompiledHeaders Condition="'$(TF_BUILD)' != ''">false</UsePrecompiledHeaders>
|
||||||
|
|
||||||
<!-- Change this to bust the cache -->
|
<!-- Change this to bust the cache -->
|
||||||
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202310210737</MSBuildCacheCacheUniverse>
|
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202406130737</MSBuildCacheCacheUniverse>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Visual Studio telemetry reads various ApplicationInsights.config files and other files after the project is finished, likely in a detached process.
|
Visual Studio telemetry reads various ApplicationInsights.config files and other files after the project is finished, likely in a detached process.
|
||||||
|
|||||||
12
src/modules/peek/Peek.Common/Helpers/PathHelper.cs
Normal file
12
src/modules/peek/Peek.Common/Helpers/PathHelper.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Peek.Common.Helpers;
|
||||||
|
|
||||||
|
public static class PathHelper
|
||||||
|
{
|
||||||
|
public static bool IsUncPath(string path) => Uri.TryCreate(path, UriKind.Absolute, out Uri? uri) && uri != null && uri.IsUnc;
|
||||||
|
}
|
||||||
26
src/modules/peek/Peek.Common/Helpers/ThreadHelper.cs
Normal file
26
src/modules/peek/Peek.Common/Helpers/ThreadHelper.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Peek.Common.Helpers;
|
||||||
|
|
||||||
|
public static class ThreadHelper
|
||||||
|
{
|
||||||
|
public static void RunOnSTAThread(ThreadStart action)
|
||||||
|
{
|
||||||
|
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Thread staThread = new(action);
|
||||||
|
staThread.SetApartmentState(ApartmentState.STA);
|
||||||
|
staThread.Start();
|
||||||
|
staThread.Join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,8 @@ namespace Peek.Common.Models
|
|||||||
|
|
||||||
public string Name { get; init; }
|
public string Name { get; init; }
|
||||||
|
|
||||||
|
public string ParsingName => string.Empty;
|
||||||
|
|
||||||
public string Path { get; init; }
|
public string Path { get; init; }
|
||||||
|
|
||||||
public string Extension => System.IO.Path.GetExtension(Path).ToLower(CultureInfo.InvariantCulture);
|
public string Extension => System.IO.Path.GetExtension(Path).ToLower(CultureInfo.InvariantCulture);
|
||||||
|
|||||||
@@ -11,19 +11,15 @@ using Windows.Storage;
|
|||||||
|
|
||||||
namespace Peek.Common.Models
|
namespace Peek.Common.Models
|
||||||
{
|
{
|
||||||
public class FolderItem : IFileSystemItem
|
public class FolderItem(string path, string name, string parsingName) : IFileSystemItem
|
||||||
{
|
{
|
||||||
private StorageFolder? storageFolder;
|
private StorageFolder? storageFolder;
|
||||||
|
|
||||||
public FolderItem(string path, string name)
|
public string Name { get; init; } = name;
|
||||||
{
|
|
||||||
Path = path;
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; init; }
|
public string ParsingName { get; init; } = parsingName;
|
||||||
|
|
||||||
public string Path { get; init; }
|
public string Path { get; init; } = path;
|
||||||
|
|
||||||
public string Extension => string.Empty;
|
public string Extension => string.Empty;
|
||||||
|
|
||||||
@@ -38,7 +34,7 @@ namespace Peek.Common.Models
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
storageFolder = await StorageFolder.GetFolderFromPathAsync(Path);
|
storageFolder = string.IsNullOrEmpty(Path) ? null : await StorageFolder.GetFolderFromPathAsync(Path);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,25 +17,24 @@ namespace Peek.Common.Models
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
DateTime? dateModified;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
dateModified = System.IO.File.GetCreationTime(Path);
|
return string.IsNullOrEmpty(Path) ? null : System.IO.File.GetCreationTime(Path);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
dateModified = null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dateModified;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Extension { get; }
|
public string Extension { get; }
|
||||||
|
|
||||||
public string Name { get; init; }
|
public string Name { get; }
|
||||||
|
|
||||||
public string Path { get; init; }
|
public string ParsingName { get; }
|
||||||
|
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
public ulong FileSizeBytes => PropertyStoreHelper.TryGetUlongProperty(Path, PropertyKey.FileSizeBytes) ?? 0;
|
public ulong FileSizeBytes => PropertyStoreHelper.TryGetUlongProperty(Path, PropertyKey.FileSizeBytes) ?? 0;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||||
|
<!-- Licensed under the MIT License. -->
|
||||||
|
|
||||||
|
<UserControl
|
||||||
|
x:Class="Peek.FilePreviewer.Controls.SpecialFolderInformationalPreviewControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid ColumnSpacing="24">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition x:Name="Icon" Width="Auto" />
|
||||||
|
<ColumnDefinition x:Name="FileInfo" Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Image
|
||||||
|
x:Name="FileIcon"
|
||||||
|
Grid.Column="0"
|
||||||
|
Width="180"
|
||||||
|
Height="180"
|
||||||
|
Source="{x:Bind Source.IconPreview, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Spacing="4">
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
MaxLines="3"
|
||||||
|
Style="{StaticResource TitleTextBlockStyle}"
|
||||||
|
Text="{x:Bind Source.FileName, Mode=OneWay}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip Content="{x:Bind Source.FileName, Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Text="{x:Bind FormatFileType(Source.FileType), Mode=OneWay}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip Content="{x:Bind FormatFileType(Source.FileType), Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Text="{x:Bind FormatFileSize(Source.FileSize), Mode=OneWay}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip Content="{x:Bind FormatFileSize(Source.FileSize), Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Text="{x:Bind FormatFileDateModified(Source.DateModified), Mode=OneWay}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip Content="{x:Bind FormatFileDateModified(Source.DateModified), Mode=OneWay}" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Peek.Common.Helpers;
|
||||||
|
using Peek.FilePreviewer.Models;
|
||||||
|
|
||||||
|
namespace Peek.FilePreviewer.Controls;
|
||||||
|
|
||||||
|
public sealed partial class SpecialFolderInformationalPreviewControl : UserControl
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||||
|
nameof(Source),
|
||||||
|
typeof(SpecialFolderPreviewData),
|
||||||
|
typeof(SpecialFolderInformationalPreviewControl),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public SpecialFolderPreviewData? Source
|
||||||
|
{
|
||||||
|
get { return (SpecialFolderPreviewData)GetValue(SourceProperty); }
|
||||||
|
set { SetValue(SourceProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecialFolderInformationalPreviewControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FormatFileType(string? fileType) => FormatField("UnsupportedFile_FileType", fileType);
|
||||||
|
|
||||||
|
public string FormatFileSize(string? fileSize) => FormatField("UnsupportedFile_FileSize", fileSize);
|
||||||
|
|
||||||
|
public string FormatFileDateModified(string? fileDateModified) => FormatField("UnsupportedFile_DateModified", fileDateModified);
|
||||||
|
|
||||||
|
private static string FormatField(string resourceId, string? fieldValue)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(fieldValue) ? string.Empty : ReadableStringHelper.FormatResourceString(resourceId, fieldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||||
|
<!-- Licensed under the MIT License. -->
|
||||||
|
|
||||||
|
<UserControl
|
||||||
|
x:Class="Peek.FilePreviewer.Controls.SpecialFolderPreview"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Peek.FilePreviewer.Controls"
|
||||||
|
xmlns:conv="using:Peek.Common.Converters"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:prev="using:Peek.FilePreviewer.Previewers"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
Margin="24"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
|
||||||
|
<controls:SpecialFolderInformationalPreviewControl
|
||||||
|
x:Name="InformationPreview"
|
||||||
|
Source="{x:Bind Source, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind IsVisibleIfStatesMatch(LoadingState, prev:PreviewState.Loaded), Mode=OneWay}" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Peek.Common.Converters;
|
||||||
|
using Peek.FilePreviewer.Models;
|
||||||
|
using Peek.FilePreviewer.Previewers;
|
||||||
|
|
||||||
|
namespace Peek.FilePreviewer.Controls;
|
||||||
|
|
||||||
|
[INotifyPropertyChanged]
|
||||||
|
public sealed partial class SpecialFolderPreview : UserControl
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||||
|
nameof(Source),
|
||||||
|
typeof(SpecialFolderPreviewData),
|
||||||
|
typeof(SpecialFolderPreview),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty LoadingStateProperty = DependencyProperty.Register(
|
||||||
|
nameof(LoadingState),
|
||||||
|
typeof(PreviewState),
|
||||||
|
typeof(SpecialFolderPreview),
|
||||||
|
new PropertyMetadata(PreviewState.Uninitialized));
|
||||||
|
|
||||||
|
public SpecialFolderPreviewData? Source
|
||||||
|
{
|
||||||
|
get { return (SpecialFolderPreviewData)GetValue(SourceProperty); }
|
||||||
|
set { SetValue(SourceProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreviewState? LoadingState
|
||||||
|
{
|
||||||
|
get { return (PreviewState)GetValue(LoadingStateProperty); }
|
||||||
|
set { SetValue(LoadingStateProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecialFolderPreview()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Visibility IsVisibleIfStatesMatch(PreviewState? a, PreviewState? b) => VisibilityConverter.Convert(a == b);
|
||||||
|
}
|
||||||
@@ -83,6 +83,12 @@
|
|||||||
Source="{x:Bind DrivePreviewer.Preview, Mode=OneWay}"
|
Source="{x:Bind DrivePreviewer.Preview, Mode=OneWay}"
|
||||||
Visibility="{x:Bind IsPreviewVisible(DrivePreviewer, Previewer.State), Mode=OneWay}" />
|
Visibility="{x:Bind IsPreviewVisible(DrivePreviewer, Previewer.State), Mode=OneWay}" />
|
||||||
|
|
||||||
|
<controls:SpecialFolderPreview
|
||||||
|
x:Name="SpecialFolderPreview"
|
||||||
|
LoadingState="{x:Bind SpecialFolderPreviewer.State, Mode=OneWay}"
|
||||||
|
Source="{x:Bind SpecialFolderPreviewer.Preview, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind IsPreviewVisible(SpecialFolderPreviewer, Previewer.State), Mode=OneWay}" />
|
||||||
|
|
||||||
<controls:UnsupportedFilePreview
|
<controls:UnsupportedFilePreview
|
||||||
x:Name="UnsupportedFilePreview"
|
x:Name="UnsupportedFilePreview"
|
||||||
LoadingState="{x:Bind UnsupportedFilePreviewer.State, Mode=OneWay}"
|
LoadingState="{x:Bind UnsupportedFilePreviewer.State, Mode=OneWay}"
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ namespace Peek.FilePreviewer
|
|||||||
[NotifyPropertyChangedFor(nameof(ArchivePreviewer))]
|
[NotifyPropertyChangedFor(nameof(ArchivePreviewer))]
|
||||||
[NotifyPropertyChangedFor(nameof(ShellPreviewHandlerPreviewer))]
|
[NotifyPropertyChangedFor(nameof(ShellPreviewHandlerPreviewer))]
|
||||||
[NotifyPropertyChangedFor(nameof(DrivePreviewer))]
|
[NotifyPropertyChangedFor(nameof(DrivePreviewer))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(SpecialFolderPreviewer))]
|
||||||
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]
|
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]
|
||||||
private IPreviewer? previewer;
|
private IPreviewer? previewer;
|
||||||
|
|
||||||
@@ -105,6 +106,8 @@ namespace Peek.FilePreviewer
|
|||||||
|
|
||||||
public IDrivePreviewer? DrivePreviewer => Previewer as IDrivePreviewer;
|
public IDrivePreviewer? DrivePreviewer => Previewer as IDrivePreviewer;
|
||||||
|
|
||||||
|
public ISpecialFolderPreviewer? SpecialFolderPreviewer => Previewer as ISpecialFolderPreviewer;
|
||||||
|
|
||||||
public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;
|
public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;
|
||||||
|
|
||||||
public IFileSystemItem Item
|
public IFileSystemItem Item
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
|
||||||
|
namespace Peek.FilePreviewer.Models;
|
||||||
|
|
||||||
|
public partial class SpecialFolderPreviewData : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
private ImageSource? iconPreview;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? fileName;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? fileType;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? fileSize;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? dateModified;
|
||||||
|
}
|
||||||
@@ -21,6 +21,8 @@
|
|||||||
<None Remove="Controls\BrowserControl.xaml" />
|
<None Remove="Controls\BrowserControl.xaml" />
|
||||||
<None Remove="Controls\DriveControl.xaml" />
|
<None Remove="Controls\DriveControl.xaml" />
|
||||||
<None Remove="Controls\ShellPreviewHandlerControl.xaml" />
|
<None Remove="Controls\ShellPreviewHandlerControl.xaml" />
|
||||||
|
<None Remove="Controls\SpecialFolderPreview\SpecialFolderInformationalPreviewControl.xaml" />
|
||||||
|
<None Remove="Controls\SpecialFolderPreview\SpecialFolderPreview.xaml" />
|
||||||
<None Remove="Controls\UnsupportedFilePreview\FailedFallbackPreviewControl.xaml" />
|
<None Remove="Controls\UnsupportedFilePreview\FailedFallbackPreviewControl.xaml" />
|
||||||
<None Remove="Controls\UnsupportedFilePreview\InformationalPreviewControl.xaml" />
|
<None Remove="Controls\UnsupportedFilePreview\InformationalPreviewControl.xaml" />
|
||||||
<None Remove="FilePreview.xaml" />
|
<None Remove="FilePreview.xaml" />
|
||||||
@@ -42,6 +44,27 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<COMReference Include="Shell32">
|
||||||
|
<VersionMinor>0</VersionMinor>
|
||||||
|
<VersionMajor>1</VersionMajor>
|
||||||
|
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
|
||||||
|
<Lcid>0</Lcid>
|
||||||
|
<WrapperTool>tlbimp</WrapperTool>
|
||||||
|
<Isolated>false</Isolated>
|
||||||
|
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||||
|
</COMReference>
|
||||||
|
<COMReference Include="SHDocVw">
|
||||||
|
<VersionMinor>1</VersionMinor>
|
||||||
|
<VersionMajor>1</VersionMajor>
|
||||||
|
<Guid>eab22ac0-30c1-11cf-a7eb-0000c05bae0b</Guid>
|
||||||
|
<Lcid>0</Lcid>
|
||||||
|
<WrapperTool>tlbimp</WrapperTool>
|
||||||
|
<Isolated>false</Isolated>
|
||||||
|
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||||
|
</COMReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||||
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
||||||
@@ -71,6 +94,9 @@
|
|||||||
<Page Update="Controls\ArchiveControl.xaml">
|
<Page Update="Controls\ArchiveControl.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Update="Controls\SpecialFolderPreview\SpecialFolderInformationalPreviewControl.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using Peek.FilePreviewer.Models;
|
||||||
|
|
||||||
|
namespace Peek.FilePreviewer.Previewers;
|
||||||
|
|
||||||
|
public interface ISpecialFolderPreviewer : IPreviewer
|
||||||
|
{
|
||||||
|
public SpecialFolderPreviewData? Preview { get; }
|
||||||
|
}
|
||||||
@@ -53,6 +53,10 @@ namespace Peek.FilePreviewer.Previewers
|
|||||||
{
|
{
|
||||||
return new DrivePreviewer(item);
|
return new DrivePreviewer(item);
|
||||||
}
|
}
|
||||||
|
else if (SpecialFolderPreviewer.IsItemSupported(item))
|
||||||
|
{
|
||||||
|
return new SpecialFolderPreviewer(item);
|
||||||
|
}
|
||||||
|
|
||||||
// Other previewer types check their supported file types here
|
// Other previewer types check their supported file types here
|
||||||
return CreateDefaultPreviewer(item);
|
return CreateDefaultPreviewer(item);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Peek.Common;
|
||||||
|
using Peek.Common.Models;
|
||||||
|
|
||||||
|
namespace Peek.FilePreviewer.Previewers;
|
||||||
|
|
||||||
|
public enum KnownSpecialFolder
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
RecycleBin,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KnownSpecialFolders
|
||||||
|
{
|
||||||
|
private static readonly Lazy<IReadOnlyDictionary<string, KnownSpecialFolder>> FoldersByParsingNameDict = new(GetFoldersByParsingName);
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<string, KnownSpecialFolder> FoldersByParsingName => FoldersByParsingNameDict.Value;
|
||||||
|
|
||||||
|
private static Dictionary<string, KnownSpecialFolder> GetFoldersByParsingName()
|
||||||
|
{
|
||||||
|
var folders = new (KnownSpecialFolder Folder, string? ParsingName)[]
|
||||||
|
{
|
||||||
|
(KnownSpecialFolder.RecycleBin, GetParsingName("shell:RecycleBinFolder")),
|
||||||
|
};
|
||||||
|
|
||||||
|
return folders.Where(folder => !string.IsNullOrEmpty(folder.ParsingName))
|
||||||
|
.ToDictionary(folder => folder.ParsingName!, folder => folder.Folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetParsingName(string shellName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return CreateShellItemFromShellName(shellName)?.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_DESKTOPABSOLUTEPARSING);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IShellItem? CreateShellItemFromShellName(string shellName)
|
||||||
|
{
|
||||||
|
// Based on https://stackoverflow.com/a/42966899
|
||||||
|
const string ShellItem = "43826d1e-e718-42ee-bc55-a1e261c37bfe";
|
||||||
|
|
||||||
|
Guid shellItem2Guid = new(ShellItem);
|
||||||
|
int retCode = NativeMethods.SHCreateItemFromParsingName(shellName, IntPtr.Zero, ref shellItem2Guid, out IShellItem? nativeShellItem);
|
||||||
|
|
||||||
|
return retCode == 0 ? nativeShellItem : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using ManagedCommon;
|
||||||
|
using Microsoft.UI.Dispatching;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Media.Imaging;
|
||||||
|
using Peek.Common.Extensions;
|
||||||
|
using Peek.Common.Helpers;
|
||||||
|
using Peek.Common.Models;
|
||||||
|
using Peek.FilePreviewer.Models;
|
||||||
|
using Peek.FilePreviewer.Previewers.Helpers;
|
||||||
|
using Windows.Foundation;
|
||||||
|
|
||||||
|
namespace Peek.FilePreviewer.Previewers;
|
||||||
|
|
||||||
|
public partial class SpecialFolderPreviewer : ObservableObject, ISpecialFolderPreviewer, IDisposable
|
||||||
|
{
|
||||||
|
private readonly DispatcherTimer _syncDetailsDispatcherTimer = new();
|
||||||
|
private ulong _folderSize;
|
||||||
|
private DateTime? _dateModified;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private SpecialFolderPreviewData preview = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private PreviewState state;
|
||||||
|
|
||||||
|
public SpecialFolderPreviewer(IFileSystemItem file)
|
||||||
|
{
|
||||||
|
_syncDetailsDispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
|
||||||
|
_syncDetailsDispatcherTimer.Tick += DetailsDispatcherTimer_Tick;
|
||||||
|
|
||||||
|
Item = file;
|
||||||
|
Preview.FileName = file.Name;
|
||||||
|
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsItemSupported(IFileSystemItem item)
|
||||||
|
{
|
||||||
|
// Always allow know special folders.
|
||||||
|
bool isKnownSpecialFolder = KnownSpecialFolders.FoldersByParsingName.ContainsKey(item.ParsingName);
|
||||||
|
|
||||||
|
// Allow empty paths unless Unc; icons don't load correctly for Unc paths.
|
||||||
|
bool isEmptyNonUncPath = string.IsNullOrEmpty(item.Path) && !PathHelper.IsUncPath(item.ParsingName);
|
||||||
|
|
||||||
|
return isKnownSpecialFolder || isEmptyNonUncPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IFileSystemItem Item { get; }
|
||||||
|
|
||||||
|
private DispatcherQueue Dispatcher { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_syncDetailsDispatcherTimer.Tick -= DetailsDispatcherTimer_Tick;
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<PreviewSize> GetPreviewSizeAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Size? size = new(680, 500);
|
||||||
|
var previewSize = new PreviewSize { MonitorSize = size, UseEffectivePixels = true };
|
||||||
|
return Task.FromResult(previewSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadPreviewAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
State = PreviewState.Loading;
|
||||||
|
|
||||||
|
var tasks = await Task.WhenAll(LoadIconPreviewAsync(cancellationToken), LoadDisplayInfoAsync(cancellationToken));
|
||||||
|
|
||||||
|
State = tasks.All(task => task) ? PreviewState.Loaded : PreviewState.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CopyAsync()
|
||||||
|
{
|
||||||
|
await Dispatcher.RunOnUiThread(async () =>
|
||||||
|
{
|
||||||
|
var storageItem = await Item.GetStorageItemAsync();
|
||||||
|
ClipboardHelper.SaveToClipboard(storageItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> LoadIconPreviewAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
bool isIconValid = false;
|
||||||
|
|
||||||
|
var isTaskSuccessful = await TaskExtension.RunSafe(async () =>
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
await Dispatcher.RunOnUiThread(async () =>
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var iconBitmap = await IconHelper.GetIconAsync(Item.ParsingName, cancellationToken);
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
isIconValid = iconBitmap != null;
|
||||||
|
Preview.IconPreview = iconBitmap ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return isIconValid && isTaskSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> LoadDisplayInfoAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
bool isDisplayValid = false;
|
||||||
|
|
||||||
|
var isTaskSuccessful = await TaskExtension.RunSafe(async () =>
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var fileType = await Task.Run(Item.GetContentTypeAsync);
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
isDisplayValid = fileType != null;
|
||||||
|
|
||||||
|
await Dispatcher.RunOnUiThread(() =>
|
||||||
|
{
|
||||||
|
Preview.FileType = fileType;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
RunUpdateDetailsWorkflow(cancellationToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
return isDisplayValid && isTaskSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunUpdateDetailsWorkflow(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Dispatcher.RunOnUiThread(_syncDetailsDispatcherTimer.Start);
|
||||||
|
ComputeDetails(cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Failed to update special folder details", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await Dispatcher.RunOnUiThread(_syncDetailsDispatcherTimer.Stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Dispatcher.RunOnUiThread(SyncDetails);
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeDetails(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_dateModified = Item.DateModified;
|
||||||
|
|
||||||
|
switch (KnownSpecialFolders.FoldersByParsingName.GetValueOrDefault(Item.ParsingName, KnownSpecialFolder.None))
|
||||||
|
{
|
||||||
|
case KnownSpecialFolder.None:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KnownSpecialFolder.RecycleBin:
|
||||||
|
ThreadHelper.RunOnSTAThread(() => { ComputeRecycleBinDetails(cancellationToken); });
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComputeRecycleBinDetails(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var shell = new Shell32.Shell();
|
||||||
|
var recycleBin = shell.NameSpace(10); // CSIDL_BITBUCKET
|
||||||
|
|
||||||
|
foreach (dynamic item in recycleBin.Items())
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_folderSize += Convert.ToUInt64(item.Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncDetails()
|
||||||
|
{
|
||||||
|
Preview.FileSize = _folderSize == 0 ? string.Empty : ReadableStringHelper.BytesToReadableString(_folderSize);
|
||||||
|
Preview.DateModified = _dateModified?.ToString(CultureInfo.CurrentCulture) ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetailsDispatcherTimer_Tick(object? sender, object e)
|
||||||
|
{
|
||||||
|
SyncDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,8 +45,6 @@ namespace Peek.FilePreviewer.Previewers
|
|||||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPreviewLoaded => Preview.IconPreview != null;
|
|
||||||
|
|
||||||
private IFileSystemItem Item { get; }
|
private IFileSystemItem Item { get; }
|
||||||
|
|
||||||
private DispatcherQueue Dispatcher { get; }
|
private DispatcherQueue Dispatcher { get; }
|
||||||
|
|||||||
@@ -7,49 +7,41 @@ using System.IO;
|
|||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Peek.Common.Models;
|
using Peek.Common.Models;
|
||||||
|
|
||||||
namespace Peek.UI.Extensions
|
namespace Peek.UI.Extensions;
|
||||||
|
|
||||||
|
public static class IShellItemExtensions
|
||||||
{
|
{
|
||||||
public static class IShellItemExtensions
|
public static IFileSystemItem ToIFileSystemItem(this IShellItem shellItem)
|
||||||
{
|
{
|
||||||
public static IFileSystemItem ToIFileSystemItem(this IShellItem shellItem)
|
string path = shellItem.GetPath();
|
||||||
{
|
string name = shellItem.GetName();
|
||||||
string path = shellItem.GetPath();
|
|
||||||
string name = shellItem.GetName();
|
|
||||||
|
|
||||||
return File.Exists(path) ? new FileItem(path, name) : new FolderItem(path, name);
|
return File.Exists(path) ? new FileItem(path, name) : new FolderItem(path, name, shellItem.GetParsingName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPath(this IShellItem shellItem) =>
|
||||||
|
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH, logError: false);
|
||||||
|
|
||||||
|
private static string GetName(this IShellItem shellItem) =>
|
||||||
|
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_NORMALDISPLAY, logError: true);
|
||||||
|
|
||||||
|
private static string GetParsingName(this IShellItem shellItem) =>
|
||||||
|
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, logError: true);
|
||||||
|
|
||||||
|
private static string GetNameCore(this IShellItem shellItem, Windows.Win32.UI.Shell.SIGDN displayNameType, bool logError)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return shellItem.GetDisplayName(displayNameType);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private static string GetPath(this IShellItem shellItem)
|
|
||||||
{
|
{
|
||||||
string path = string.Empty;
|
if (logError)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
path = shellItem.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH);
|
Logger.LogError($"Getting {Enum.GetName(displayNameType)} failed. {ex.Message}");
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// TODO: Handle cases that do not have a file system path like Recycle Bin.
|
|
||||||
path = string.Empty;
|
|
||||||
Logger.LogError("Getting path failed. " + ex.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return string.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetName(this IShellItem shellItem)
|
|
||||||
{
|
|
||||||
string name = string.Empty;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
name = shellItem.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_NORMALDISPLAY);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
name = string.Empty;
|
|
||||||
Logger.LogError("Getting path failed. " + ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user