From 9d382b0bdc36adf5a4e4de207b0a1d9ba40766b9 Mon Sep 17 00:00:00 2001 From: Alex Chen Date: Wed, 2 May 2018 16:59:18 +0800 Subject: [PATCH 001/158] added documentation for Graph controls --- docs/graph/AADLogin.md | 68 ++++++++++++++++++ docs/graph/PeoplePicker.md | 52 ++++++++++++++ docs/graph/ProfileCard.md | 52 ++++++++++++++ docs/graph/SharePointFiles.md | 63 ++++++++++++++++ docs/resources/images/Graph/AADLogin.png | Bin 0 -> 4236 bytes docs/resources/images/Graph/PeoplePicker.png | Bin 0 -> 22717 bytes docs/resources/images/Graph/ProfileCard.png | Bin 0 -> 1682 bytes .../images/Graph/SharePointFiles.png | Bin 0 -> 40287 bytes 8 files changed, 235 insertions(+) create mode 100644 docs/graph/AADLogin.md create mode 100644 docs/graph/PeoplePicker.md create mode 100644 docs/graph/ProfileCard.md create mode 100644 docs/graph/SharePointFiles.md create mode 100644 docs/resources/images/Graph/AADLogin.png create mode 100644 docs/resources/images/Graph/PeoplePicker.png create mode 100644 docs/resources/images/Graph/ProfileCard.png create mode 100644 docs/resources/images/Graph/SharePointFiles.png diff --git a/docs/graph/AADLogin.md b/docs/graph/AADLogin.md new file mode 100644 index 00000000000..673fda010e5 --- /dev/null +++ b/docs/graph/AADLogin.md @@ -0,0 +1,68 @@ +--- +title: AADLogin Control +author: OGcanviz +description: The AADLogin Control leverages existing .NET login libraries to support basic AAD sign-in processes for Microsoft Graph. +keywords: windows 10, uwp, uwp community toolkit, uwp toolkit, AADLogin Control +--- + +# AADLogin Control + +The [AADLogin Control](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.ui.controls.graph.aadlogin) leverages existing .NET login libraries to support basic AAD sign-in processes for Microsoft Graph. + +## Syntax + +```xaml + + + +``` + +## Example Image + +![AADLogin animation](../resources/images/Graph/AADLogin.png) + +## Properties + +| Property | Type | Description | +| -- | -- | -- | +| ClientId | String | The guid the app is registered in [Application Registration Portal](https://apps.dev.microsoft.com/) | +| Scopes | String | Scopes required by the app | +| DefaultImage | BitmapImage | The default image displayed when no user is signed in | +| View | [ViewType](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs) | The visual layout of the control. Default is `PictureOnly` | +| AllowSignInAsDifferentUser | Boolean | Whether or not the menu item for `Sign in as a different user` is enabled, default value is true | + +## Methods + +| Method | Return Type | Description | +| -- | -- | -- | +| SignInAsync | void | Method to call when to trigger the user signin. UX of the control is updated if successful | +| SignOutAsync | void | Method to call to signout the currently signed on user | + +## Events + +| Method | Type | Description | +| -- | -- | -- | +| SignInCompleted | RoutedEventHandler | Occurs when one of the menu items in the control is clicked. | +| SignOutCompleted | RoutedEventHandler | Occurs when the user clicks on SignOut, or the SignOutAsync() method is called. Developers should clear any cached usage of GraphServiceClient objects they receive this event | + +## Sample Code + +[AADLogin Sample Page Source](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin). You can see this in action in [UWP Community Toolkit Sample App](https://www.microsoft.com/store/apps/9NBLGGH4TLCQ). + +## Default Template + +[AADLogin XAML File](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.xaml) is the XAML template used in the toolkit for the default styling. + +## Requirements + +| Device family | Universal, 10.0.14393.0 or higher | +| -- | -- | +| Namespace | Microsoft.Toolkit.Uwp.UI.Controls.Graph | +| NuGet package | [Microsoft.Toolkit.Uwp.UI.Controls.Graph](https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI.Controls.Graph/) | + +## API + +* [AADLogin source code](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin) diff --git a/docs/graph/PeoplePicker.md b/docs/graph/PeoplePicker.md new file mode 100644 index 00000000000..97c8c96c881 --- /dev/null +++ b/docs/graph/PeoplePicker.md @@ -0,0 +1,52 @@ +--- +title: PeoplePicker Control +author: OGcanviz +description: The PeoplePicker Control is a simple control that allows for selection of one or more users from an organizational AD. +keywords: windows 10, uwp, uwp community toolkit, uwp toolkit, PeoplePicker Control +--- + +# PeoplePicker Control + +The [PeoplePicker Control](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.ui.controls.graph.peoplepicker) is a simple control that allows for selection of one or more users from an organizational AD. + +## Syntax + +```xaml + + + +``` + +## Example Image + +![PeoplePicker animation](../resources/images/Graph/PeoplePicker.png) + +## Properties + +| Property | Type | Description | +| -- | -- | -- | +| GraphAccessToken | String | The access token generated by the [AADLogin Control](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/) | +| AllowMultiple | Boolean | Whether multiple people can be selected | +| PersonNotSelectedMessage | String | Text to be displayed when no user is selected. When set to null, default is used. | + +## Sample Code + +[PeoplePicker Sample Page Source](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PeoplePicker). You can see this in action in [UWP Community Toolkit Sample App](https://www.microsoft.com/store/apps/9NBLGGH4TLCQ). + +## Default Template + +[PeoplePicker XAML File](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml) is the XAML template used in the toolkit for the default styling. + +## Requirements + +| Device family | Universal, 10.0.14393.0 or higher | +| -- | -- | +| Namespace | Microsoft.Toolkit.Uwp.UI.Controls.Graph | +| NuGet package | [Microsoft.Toolkit.Uwp.UI.Controls.Graph](https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI.Controls.Graph/) | + +## API + +* [PeoplePicker source code](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker) diff --git a/docs/graph/ProfileCard.md b/docs/graph/ProfileCard.md new file mode 100644 index 00000000000..a6beb01adf0 --- /dev/null +++ b/docs/graph/ProfileCard.md @@ -0,0 +1,52 @@ +--- +title: ProfileCard Control +author: OGcanviz +description: The ProfileCard Control is a simple way to display a user in multiple different formats and mixes of name/image/e-mail. +keywords: windows 10, uwp, uwp community toolkit, uwp toolkit, ProfileCard Control +--- + +# ProfileCard Control + +The [ProfileCard Control](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.ui.controls.graph.profilecard) is a simple way to display a user in multiple different formats and mixes of name/image/e-mail. + +## Syntax + +```xaml + + + +``` + +## Example Image + +![ProfileCard animation](../resources/images/Graph/ProfileCard.png) + +## Properties + +| Property | Type | Description | +| -- | -- | -- | +| GraphAccessToken | String | The access token generated by the [AADLogin Control](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/) | +| UserId | String | Identifier of the user being displayed | +| DisplayMode | [ViewType](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs) | The visual layout of the control. Default is `PictureOnly` | +| DefaultImage | BitmapImage | The default image displayed when no user is signed in | + +## Sample Code + +[ProfileCard Sample Page Source](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ProfileCard). You can see this in action in [UWP Community Toolkit Sample App](https://www.microsoft.com/store/apps/9NBLGGH4TLCQ). + +## Default Template + +[ProfileCard XAML File](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml) is the XAML template used in the toolkit for the default styling. + +## Requirements + +| Device family | Universal, 10.0.14393.0 or higher | +| -- | -- | +| Namespace | Microsoft.Toolkit.Uwp.UI.Controls.Graph | +| NuGet package | [Microsoft.Toolkit.Uwp.UI.Controls.Graph](https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI.Controls.Graph/) | + +## API + +* [ProfileCard source code](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard) diff --git a/docs/graph/SharePointFiles.md b/docs/graph/SharePointFiles.md new file mode 100644 index 00000000000..da98dad9c95 --- /dev/null +++ b/docs/graph/SharePointFiles.md @@ -0,0 +1,63 @@ +--- +title: SharePointFiles Control +author: OGcanviz +description: The SharePointFiles Control displays a simple list of SharePoint Files. +keywords: windows 10, uwp, uwp community toolkit, uwp toolkit, SharePointFiles Control +--- + +# SharePointFiles Control + +The [SharePointFiles Control](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.ui.controls.graph.sharepointfiles) displays a simple list of SharePoint Files. + +## Syntax + +```xaml + + + +``` + +## Example Image + +![SharePointFiles animation](../resources/images/Graph/SharePointFiles.png) + +## Properties + +| Property | Type | Description | +| -- | -- | -- | +| GraphAccessToken | String | The access token generated by the [AADLogin Control](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/) | +| DriveUrl | String | Full URL of the Drive being displayed | +| DetailPane | [DetailPaneDisplayMode](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DetailPaneDisplayMode.cs) | Determines whether file details are displayed, when a file is selected | + +## Methods + +| Method | Return Type | Description | +| -- | -- | -- | +| GetDriveUrlFromSharePointUrl | String | Retrieves an appropriate Drive URL from a SharePoint document library root URL | + +## Events + +| Event | Return Type | Description | +| -- | -- | -- | +| FileSelected | RoutedEventHandler | Occurs when one of the menu items in the control is clicked | + +## Sample Code + +[SharePointFiles Sample Page Source](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SharePointFiles). You can see this in action in [UWP Community Toolkit Sample App](https://www.microsoft.com/store/apps/9NBLGGH4TLCQ). + +## Default Template + +[SharePointFiles XAML File](https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.xaml) is the XAML template used in the toolkit for the default styling. + +## Requirements + +| Device family | Universal, 10.0.14393.0 or higher | +| -- | -- | +| Namespace | Microsoft.Toolkit.Uwp.UI.Controls.Graph | +| NuGet package | [Microsoft.Toolkit.Uwp.UI.Controls.Graph](https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI.Controls.Graph/) | + +## API + +* [SharePointFiles source code](https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles) diff --git a/docs/resources/images/Graph/AADLogin.png b/docs/resources/images/Graph/AADLogin.png new file mode 100644 index 0000000000000000000000000000000000000000..ab920baf113baf7af90132bc68a7dcdc965e012a GIT binary patch literal 4236 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 literal 0 HcmV?d00001 diff --git a/docs/resources/images/Graph/PeoplePicker.png b/docs/resources/images/Graph/PeoplePicker.png new file mode 100644 index 0000000000000000000000000000000000000000..924f4ef5a2d13bc0fe1cb7bc07d43a11d2250d31 GIT binary patch literal 22717 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 literal 0 HcmV?d00001 diff --git a/docs/resources/images/Graph/ProfileCard.png b/docs/resources/images/Graph/ProfileCard.png new file mode 100644 index 0000000000000000000000000000000000000000..906dfb40575e1006a92253af03325bafbd9890c7 GIT binary patch literal 1682 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 literal 0 HcmV?d00001 From 8d89db4c742a1347d8af319f78af95a9b461599a Mon Sep 17 00:00:00 2001 From: Alex Chen Date: Wed, 2 May 2018 18:54:11 +0800 Subject: [PATCH 002/158] added graph controls --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 10 + .../SamplePages/AADLogin/AADLogin.png | Bin 0 -> 4510 bytes .../SamplePages/AADLogin/AADLoginCode.bind | 8 + .../SamplePages/AADLogin/AADLoginPage.xaml | 28 + .../SamplePages/AADLogin/AADLoginPage.xaml.cs | 32 ++ .../SamplePages/AADLogin/AADLoginXaml.bind | 33 ++ .../SamplePages/samples.json | 16 + .../AADLogin/AADLogin.Events.cs | 31 ++ .../AADLogin/AADLogin.Properties.cs | 263 +++++++++ .../AADLogin/AADLogin.cs | 176 +++++++ .../AADLogin/AADLogin.xaml | 27 + .../AADLogin/SignInEventArgs.cs | 24 + .../Assets/folder.svg | 34 ++ .../Assets/genericfile.png | Bin 0 -> 389 bytes .../Assets/person.png | Bin 0 -> 5250 bytes .../Assets/photo.png | Bin 0 -> 610 bytes .../Common.cs | 49 ++ ...osoft.Toolkit.Uwp.UI.Controls.Graph.csproj | 183 +++++++ .../PeoplePicker/DelegateCommand.cs | 40 ++ .../PeoplePicker/PeoplePicker.Events.cs | 101 ++++ .../PeoplePicker/PeoplePicker.Properties.cs | 92 ++++ .../PeoplePicker/PeoplePicker.cs | 64 +++ .../PeoplePicker/PeoplePicker.xaml | 84 +++ .../ProfileCard/DisplayModeConverter.cs | 25 + .../ProfileCard/ProfileCard.Properties.cs | 263 +++++++++ .../ProfileCard/ProfileCard.cs | 87 +++ .../ProfileCard/ProfileCard.xaml | 110 ++++ .../ProfileCard/ViewType.cs | 13 + .../Properties/AssemblyInfo.cs | 29 + ...osoft.Toolkit.Uwp.UI.Controls.Graph.rd.xml | 33 ++ .../SharePointFiles/DetailPaneDisplayMode.cs | 7 + .../SharePointFiles/DriveItemIconConverter.cs | 53 ++ .../SharePointFiles/FileSelectedEventArgs.cs | 10 + .../SharePointFiles/FileSizeConverter.cs | 26 + .../SharePointFiles.Properties.cs | 173 ++++++ .../SharePointFiles/SharePointFiles.cs | 498 ++++++++++++++++++ .../SharePointFiles/SharePointFiles.xaml | 140 +++++ .../Themes/Generic.xaml | 9 + 38 files changed, 2771 insertions(+) create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLogin.png create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginCode.bind create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml.cs create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginXaml.bind create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Events.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/SignInEventArgs.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/folder.svg create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/genericfile.png create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/person.png create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/photo.png create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Common.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Microsoft.Toolkit.Uwp.UI.Controls.Graph.csproj create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/DelegateCommand.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Events.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/DisplayModeConverter.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/AssemblyInfo.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/Microsoft.Toolkit.Uwp.UI.Controls.Graph.rd.xml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DetailPaneDisplayMode.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DriveItemIconConverter.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSelectedEventArgs.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSizeConverter.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Graph/Themes/Generic.xaml diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 7e481027cd4..9b30f72a151 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -296,6 +296,7 @@ + @@ -496,6 +497,8 @@ Designer + + @@ -561,6 +564,9 @@ BackdropBlurBrushPage.xaml + + AADLoginPage.xaml + ImageBlendBrushPage.xaml @@ -906,6 +912,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLogin.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLogin.png new file mode 100644 index 0000000000000000000000000000000000000000..97fe26161a3953b20236127495e99b5fc099feba GIT binary patch literal 4510 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 + +// C# - Fade can be applied to any UIElement. In this case it is an image called ToolkitLogo. +using Microsoft.Toolkit.Uwp.UI.Animations; + +await ToolkitLogo.Fade(value: 0.5f, duration: 10, delay: 0).StartAsync(); + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml new file mode 100644 index 00000000000..16a0996fa41 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml.cs new file mode 100644 index 00000000000..e687ed4159f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginPage.xaml.cs @@ -0,0 +1,32 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +using Microsoft.Toolkit.Uwp.SampleApp.Models; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the opacity behavior. + /// + public sealed partial class AADLoginPage : Page + { + /// + /// Initializes a new instance of the class. + /// + public AADLoginPage() + { + InitializeComponent(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginXaml.bind new file mode 100644 index 00000000000..a8a707c5ab9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/AADLogin/AADLoginXaml.bind @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 7cc481e5955..eb58d67f37f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -861,5 +861,21 @@ "Icon": "/SamplePages/Analytics/Analytics.png" } ] + }, + { + "Name": "Graph", + "Icon": "Icons/Foundation.png", + "Samples": [ + { + "Name": "AADLogin", + "Type": "AADLoginPage", + "About": "The AADLogin Control leverages existing .NET login libraries to support basic AAD sign-in processes for Microsoft Graph.", + "CodeUrl": "https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin", + "XamlCodeFile": "AADLoginXaml.bind", + "CodeFile": "AADLoginCode.bind", + "Icon": "/SamplePages/AADLogin/AADLogin.png", + "DocumentationUrl": "https://raw.githubusercontent.com/Microsoft/UWPCommunityToolkit/master/docs/graph/AADLogin.md" + } + ] } ] \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Events.cs new file mode 100644 index 00000000000..6ea8b16712f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Events.cs @@ -0,0 +1,31 @@ +using System; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + /// + /// Defines the events for the control. + /// + public partial class AADLogin : Control + { + /// + /// Occurs when the user is logged in. + /// + public event EventHandler SignInCompleted; + + /// + /// Occurs when the user is logged out. + /// + public event EventHandler SignOutCompleted; + + private void OnSignInCompleted(SignInEventArgs e) + { + SignInCompleted?.Invoke(this, e); + } + + private void OnSignOutCompleted() + { + SignOutCompleted?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Properties.cs new file mode 100644 index 00000000000..e1fe4e15564 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.Properties.cs @@ -0,0 +1,263 @@ +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + /// + /// Defines the properties for the control. + /// + public partial class AADLogin : Control + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ClientIdProperty = DependencyProperty.Register( + nameof(ClientId), + typeof(string), + typeof(AADLogin), + new PropertyMetadata(string.Empty) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ScopesProperty = DependencyProperty.Register( + nameof(Scopes), + typeof(string), + typeof(AADLogin), + new PropertyMetadata(string.Empty) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DefaultImageProperty = DependencyProperty.Register( + nameof(DefaultImage), + typeof(BitmapImage), + typeof(AADLogin), + new PropertyMetadata(null) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ViewProperty = DependencyProperty.Register( + nameof(View), + typeof(ViewType), + typeof(AADLogin), + new PropertyMetadata(ViewType.PictureOnly) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AllowSignInAsDifferentUserProperty = DependencyProperty.Register( + nameof(AllowSignInAsDifferentUser), + typeof(bool), + typeof(AADLogin), + new PropertyMetadata(true) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SignInDefaultTextProperty = DependencyProperty.Register( + nameof(SignInDefaultText), + typeof(string), + typeof(AADLogin), + new PropertyMetadata("Sign In") + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SignOutDefaultTextProperty = DependencyProperty.Register( + nameof(SignOutDefaultText), + typeof(string), + typeof(AADLogin), + new PropertyMetadata("Sign Out") + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SignInAnotherUserDefaultTextProperty = DependencyProperty.Register( + nameof(SignInAnotherUserDefaultText), + typeof(string), + typeof(AADLogin), + new PropertyMetadata("Sign in with another account") + ); + + internal static readonly DependencyProperty _graphAccessTokenProperty = DependencyProperty.Register( + nameof(_graphAccessToken), + typeof(string), + typeof(AADLogin), + new PropertyMetadata(null) + ); + + internal static readonly DependencyProperty _currentUserIdProperty = DependencyProperty.Register( + nameof(_currentUserID), + typeof(string), + typeof(AADLogin), + new PropertyMetadata(null) + ); + + /// + /// + /// + public string ClientId + { + get + { + return (string)GetValue(ClientIdProperty); + } + set + { + if (string.IsNullOrEmpty(ClientId)) + { + SetValue(ClientIdProperty, value); + InitialPublicClientApplication(); + } + else + throw new ArgumentException("The Client Id field only allow be set once."); + } + } + + /// + /// + /// + public string Scopes + { + get + { + return (string)GetValue(ScopesProperty); + } + set + { + if (string.IsNullOrEmpty(Scopes)) + { + SetValue(ScopesProperty, value); + InitialPublicClientApplication(); + } + else + throw new ArgumentException("The Scopes field only allow be set once."); + } + } + + /// + /// Gets or sets the default user photo + /// + public BitmapImage DefaultImage + { + get + { + return (BitmapImage)GetValue(DefaultImageProperty); + } + set + { + SetValue(DefaultImageProperty, value); + } + } + + /// + /// Gets or sets a value indicating which view type will be presented, the default value is PictureOnly. + /// + public ViewType View + { + get + { + return (ViewType)GetValue(ViewProperty); + } + set + { + SetValue(ViewProperty, value); + } + } + + /// + /// Gets or sets a value indicating whether AllowSignInAsDifferentUser menu button is enabled for logged in user. + /// + public bool AllowSignInAsDifferentUser + { + get + { + return (bool)GetValue(AllowSignInAsDifferentUserProperty); + } + set + { + SetValue(AllowSignInAsDifferentUserProperty, value); + } + } + + /// + /// + /// + public string SignInDefaultText + { + get + { + return (string)GetValue(SignInDefaultTextProperty); + } + set + { + SetValue(SignInDefaultTextProperty, value); + } + } + + /// + /// + /// + public string SignOutDefaultText + { + get + { + return (string)GetValue(SignOutDefaultTextProperty); + } + set + { + SetValue(SignOutDefaultTextProperty, value); + } + } + + /// + /// + /// + public string SignInAnotherUserDefaultText + { + get + { + return (string)GetValue(SignInAnotherUserDefaultTextProperty); + } + set + { + SetValue(SignInAnotherUserDefaultTextProperty, value); + } + } + + internal string _currentUserID + { + get + { + return (string)GetValue(_currentUserIdProperty); + } + private set + { + SetValue(_currentUserIdProperty, value); + } + } + + internal string _graphAccessToken + { + get + { + return (string)GetValue(_graphAccessTokenProperty); + } + private set + { + SetValue(_graphAccessTokenProperty, value); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.cs new file mode 100644 index 00000000000..801480e00e9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.cs @@ -0,0 +1,176 @@ +using Microsoft.Identity.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + /// + /// The AAD Login Control leverages MSAL libraries to support basic AAD sign-in processes for Microsoft Graph and beyond. + /// + public partial class AADLogin : Control + { + private static PublicClientApplication _identityClientApp = null; + + private Button _mainButton = null; + + public AADLogin() + { + DefaultStyleKey = typeof(AADLogin); + IsEnabled = false; + } + + /// + /// Override default OnApplyTemplate to capture child controls + /// + protected override void OnApplyTemplate() + { + base.ApplyTemplate(); + + _mainButton = GetTemplateChild("btnMain") as Button; + + _mainButton.Click += async (object sender, RoutedEventArgs e) => + { + var btn = sender as Button; + + if (string.IsNullOrEmpty(_currentUserID)) + { + btn.IsEnabled = false; + if (await SignInAsync()) btn.Flyout = GenerateMenuItems(); + btn.IsEnabled = true; + } + }; + } + + public async Task SignInAsync() + { + if (!string.IsNullOrEmpty(ClientId) && !string.IsNullOrEmpty(Scopes)) + { + var token = await GetTokenForUserAsync(); + + if (!string.IsNullOrEmpty(token)) + { + var graphClient = Common.GetAuthenticatedClient(token); + + _graphAccessToken = token; + + _currentUserID = (await graphClient.Me.Request().GetAsync()).Id; + + OnSignInCompleted(new SignInEventArgs() + { + GraphClient = graphClient, + GraphAccessToken = token, + CurrentSignInUserId = _currentUserID + }); + + return true; + } + } + + return false; + } + + public void SignOut() + { + if (_identityClientApp.Users != null) + { + foreach (var user in _identityClientApp.Users) + { + _identityClientApp.Remove(user); + } + + _currentUserID = ""; + _mainButton.Flyout = null; + OnSignOutCompleted(); + } + } + + private void InitialPublicClientApplication() + { + if (!string.IsNullOrEmpty(ClientId) && !string.IsNullOrEmpty(Scopes)) + { + _identityClientApp = new PublicClientApplication(ClientId); + IsEnabled = true; + } + } + + private async Task GetTokenForUserAsync() + { + string TokenForUser = null; + + AuthenticationResult authResult; + try + { + authResult = await _identityClientApp.AcquireTokenSilentAsync(Scopes.Split(','), _identityClientApp.Users.First()); + TokenForUser = authResult.AccessToken; + } + catch + { + try + { + authResult = await _identityClientApp.AcquireTokenAsync(Scopes.Split(',')); + TokenForUser = authResult.AccessToken; + } + catch + { } + } + + return TokenForUser; + } + + private async Task GetTokenForAnotherUserAsync() + { + string TokenForUser = null; + + try + { + AuthenticationResult authResult = await _identityClientApp.AcquireTokenAsync(Scopes.Split(',')); + TokenForUser = authResult.AccessToken; + } + catch + { } + + return TokenForUser; + } + + private MenuFlyout GenerateMenuItems() + { + MenuFlyout menuFlyout = new MenuFlyout(); + + if (AllowSignInAsDifferentUser) + { + MenuFlyoutItem signinanotherItem = new MenuFlyoutItem(); + signinanotherItem.Text = SignInAnotherUserDefaultText; + signinanotherItem.Click += async (object sender, RoutedEventArgs e) => + { + var token = await GetTokenForAnotherUserAsync(); + if (!string.IsNullOrEmpty(token)) + { + var graphClient = Common.GetAuthenticatedClient(token); + + _graphAccessToken = token; + _currentUserID = (await graphClient.Me.Request().GetAsync()).Id; + + OnSignInCompleted(new SignInEventArgs() + { + GraphClient = graphClient, + GraphAccessToken = token, + CurrentSignInUserId = _currentUserID + }); + } + }; + menuFlyout.Items.Add(signinanotherItem); + } + + MenuFlyoutItem signoutItem = new MenuFlyoutItem(); + signoutItem.Text = SignOutDefaultText; + signoutItem.Click += (object sender, RoutedEventArgs e) => SignOut(); + menuFlyout.Items.Add(signoutItem); + + return menuFlyout; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.xaml new file mode 100644 index 00000000000..b1bedfb84bd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/AADLogin.xaml @@ -0,0 +1,27 @@ + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/SignInEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/SignInEventArgs.cs new file mode 100644 index 00000000000..7624218d5c6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/AADLogin/SignInEventArgs.cs @@ -0,0 +1,24 @@ +using Microsoft.Graph; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public class SignInEventArgs + { + internal SignInEventArgs() { } + + public GraphServiceClient GraphClient + { + get; internal set; + } + + public string GraphAccessToken + { + get; internal set; + } + + public string CurrentSignInUserId + { + get; internal set; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/folder.svg b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/folder.svg new file mode 100644 index 00000000000..bd9a64876bd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/folder.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/genericfile.png b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/genericfile.png new file mode 100644 index 0000000000000000000000000000000000000000..bf43bc2b680ba8337a1859da0d0186f805ac46b6 GIT binary patch literal 389 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 IM6N<$g8G;6L;wH) literal 0 HcmV?d00001 diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/photo.png b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/photo.png new file mode 100644 index 0000000000000000000000000000000000000000..ca21eb5c049f4eb796c866092468c0442c371e5d GIT binary patch literal 610 zcmV-o0-gPdP)w=HcK3|{Ocn_XJE-?uZ% zQh*?W`0o*-LAhKuq#p`}9AmM^rYMS|sj50_#bS{^5Edscl}amLv0)fctyX8BmA_0k z!ZD`P>Fio~mB8_B8^$*+6SSv6gf+Xs$}(U~J~n+ft2y~=c;gd@w*+1a~npXq)49LKp_3mez wezPfQ9B2K-^#IOoWk3mPK?D&5#g_mB0Ee!&h;;>VG5`Po07*qoM6N<$f}#`%NB{r; literal 0 HcmV?d00001 diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Common.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Common.cs new file mode 100644 index 00000000000..0db48c1947c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Common.cs @@ -0,0 +1,49 @@ +using Microsoft.Graph; +using System.IO; +using System.Net.Http.Headers; +using System.Reflection; +using System.Threading.Tasks; +using Windows.UI.Xaml.Media.Imaging; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + internal class Common + { + const string GraphAPIBaseUrl = "https://graph.microsoft.com/v1.0"; + + internal static GraphServiceClient GetAuthenticatedClient(string accessToken) + { + if (string.IsNullOrEmpty(accessToken)) return null; + + var graphClient = new GraphServiceClient( + GraphAPIBaseUrl, + new DelegateAuthenticationProvider( + (requestMessage) => + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); + + return Task.FromResult(0); + })); + return graphClient; + } + + internal static BitmapImage GetBitmapImagFromEmbedResource(string path) + { + BitmapImage bitmapImage = null; + Assembly assembly = typeof(Common).GetTypeInfo().Assembly; + using (var imageStream = assembly.GetManifestResourceStream(path)) + using (var memStream = new MemoryStream()) + { + if (imageStream == null) return null; + imageStream.CopyTo(memStream); + memStream.Position = 0; + using (var raStream = memStream.AsRandomAccessStream()) + { + bitmapImage = new BitmapImage(); + bitmapImage.SetSource(raStream); + } + } + return bitmapImage; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Microsoft.Toolkit.Uwp.UI.Controls.Graph.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Microsoft.Toolkit.Uwp.UI.Controls.Graph.csproj new file mode 100644 index 00000000000..89fc82b3544 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Microsoft.Toolkit.Uwp.UI.Controls.Graph.csproj @@ -0,0 +1,183 @@ + + + + + Debug + AnyCPU + {EDA54B05-371C-487B-9198-190F45DEB3FE} + Library + Properties + Microsoft.Toolkit.Uwp.UI.Controls.Graph + Microsoft.Toolkit.Uwp.UI.Controls.Graph + en-US + UAP + 10.0.16299.0 + 10.0.10586.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + x86 + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + + + x86 + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + + + ARM + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + + + ARM + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + + + x64 + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + + + x64 + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + + + PackageReference + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.8.1 + + + 1.1.2-preview0008 + + + 6.0.8 + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + + + + + + + + 14.0 + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/DelegateCommand.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/DelegateCommand.cs new file mode 100644 index 00000000000..74bcb6b8fac --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/DelegateCommand.cs @@ -0,0 +1,40 @@ +using System; +using System.Windows.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public class DelegateCommand : ICommand + { + private Action action; + private Action actionT; + + public DelegateCommand(Action action) + { + this.action = action; + } + + public DelegateCommand(Action action) + { + this.actionT = action; + } + + public bool CanExecute(object parameter) + { + return true; + } + + public event EventHandler CanExecuteChanged; + + public void Execute(object parameter) + { + if (action != null) + { + action(); + } + if (actionT != null) + { + actionT.Invoke(parameter); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Events.cs new file mode 100644 index 00000000000..68f4be11d51 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Events.cs @@ -0,0 +1,101 @@ +using Microsoft.Graph; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public partial class PeoplePicker : Control + { + private void ClearAndHideSearchResultListBox() + { + SearchResultList.Clear(); + SearchResultListBox.Visibility = Visibility.Collapsed; + } + + private async void SearchBox_OnTextChanged(object sender, TextChangedEventArgs e) + { + var textboxSender = (TextBox)sender; + string searchText = textboxSender.Text.Trim(); + if (string.IsNullOrWhiteSpace(searchText)) + { + ClearAndHideSearchResultListBox(); + return; + } + + Loading.IsActive = true; + + searchText = Regex.Replace(searchText, "[^0-9a-zA-Z .@]", ""); + int cursorPosition = textboxSender.SelectionStart; + textboxSender.Text = searchText; + textboxSender.SelectionStart = cursorPosition; + + try + { + var options = new List + { + new QueryOption("$search", searchText) + }; + IUserPeopleCollectionPage peopleList = await GraphClient.Me.People.Request(options).GetAsync(); + + if (peopleList.Any()) + { + List searchResult = peopleList.Where( + u => !string.IsNullOrWhiteSpace(u.UserPrincipalName)).ToList(); + + // Remove all selected items + foreach (Person selectedItem in Selections) + searchResult.RemoveAll(u => u.UserPrincipalName == selectedItem.UserPrincipalName); + + SearchResultList = new ObservableCollection(SearchResultLimit > 0 + ? searchResult.Take(SearchResultLimit).ToList() + : searchResult); + SearchResultListBox.Visibility = Visibility.Visible; + } + else + { + ClearAndHideSearchResultListBox(); + } + } + catch (Exception ex) + { + } + finally + { + Loading.IsActive = false; + } + } + + private void SearchResultListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!((sender as ListBox)?.SelectedItem is Person person)) + return; + if (!AllowMultiple && Selections.Any()) + { + Selections.Clear(); + Selections.Add(person); + } + else + { + Selections.Add(person); + } + SelectionsCounter.Text = $"{Selections.Count} selected"; + SearchBox.Text = ""; + } + + private void DeleteSelectionItem(object parameter) + { + var upn = parameter as string; + Person target = Selections.FirstOrDefault(u => u.UserPrincipalName == upn); + if (target != null) + { + Selections.Remove(target); + SelectionsCounter.Text = $"{Selections.Count} selected"; + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Properties.cs new file mode 100644 index 00000000000..b0a43526e1f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.Properties.cs @@ -0,0 +1,92 @@ +using Microsoft.Graph; +using System.Collections.ObjectModel; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public partial class PeoplePicker : Control + { + //GraphAccessToken + public static readonly DependencyProperty GraphAccessTokenProperty = + DependencyProperty.Register( + nameof(GraphAccessToken), typeof(string), + typeof(PeoplePicker), new PropertyMetadata("", GraphAccessTokenPropertyChanged) + ); + + // AllowMultiple + public static readonly DependencyProperty AllowMultipleProperty = + DependencyProperty.Register( + nameof(AllowMultiple), typeof(bool), + typeof(PeoplePicker), null + ); + + // SearchResultLimit + public static readonly DependencyProperty SearchResultLimitProperty = + DependencyProperty.Register( + nameof(SearchResultLimit), typeof(int), + typeof(PeoplePicker), null + ); + + // PersonNotSelectedMessage + public static readonly DependencyProperty PersonNotSelectedMessageProperty = + DependencyProperty.Register( + nameof(PersonNotSelectedMessage), typeof(string), + typeof(PeoplePicker), null + ); + + // Selections + public static readonly DependencyProperty SelectionsProperty = + DependencyProperty.Register( + nameof(Selections), typeof(ObservableCollection), + typeof(PeoplePicker), null + ); + + // PeopleList + internal static readonly DependencyProperty SearchResultListProperty = + DependencyProperty.Register( + nameof(SearchResultList), typeof(ObservableCollection), + typeof(PeoplePicker), null + ); + + public string GraphAccessToken + { + get => (string) this.GetValue(GraphAccessTokenProperty); + set => this.SetValue(GraphAccessTokenProperty, value); + } + + public bool AllowMultiple + { + get => (bool) this.GetValue(AllowMultipleProperty); + set => this.SetValue(AllowMultipleProperty, value); + } + + public int SearchResultLimit + { + get => (int) this.GetValue(SearchResultLimitProperty); + set => this.SetValue(SearchResultLimitProperty, value); + } + + public string PersonNotSelectedMessage + { + get => (string) this.GetValue(PersonNotSelectedMessageProperty); + set => this.SetValue(PersonNotSelectedMessageProperty, value); + } + + public ObservableCollection Selections + { + get => (ObservableCollection) this.GetValue(SelectionsProperty); + set => this.SetValue(SelectionsProperty, value); + } + + internal ObservableCollection SearchResultList + { + get => (ObservableCollection) this.GetValue(SearchResultListProperty); + set => this.SetValue(SearchResultListProperty, value); + } + + internal GraphServiceClient GraphClient { get; set; } + + internal DelegateCommand DeleteItem => new DelegateCommand(DeleteSelectionItem); + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.cs new file mode 100644 index 00000000000..8b2f9ecf2ed --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.cs @@ -0,0 +1,64 @@ +using Microsoft.Graph; +using Microsoft.Identity.Client; +using System; +using System.Collections.ObjectModel; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Linq; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public partial class PeoplePicker : Control + { + public PeoplePicker() + { + DefaultStyleKey = typeof(PeoplePicker); + DataContext = this; + } + + private TextBox SearchBox; + private ProgressRing Loading; + private ListBox SearchResultListBox; + private TextBlock SelectionsCounter; + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + SearchBox = GetTemplateChild("SearchBox") as TextBox; + Loading = GetTemplateChild("Loading") as ProgressRing; + SearchResultListBox = GetTemplateChild("SearchResultListBox") as ListBox; + SelectionsCounter = GetTemplateChild("SelectionsCounter") as TextBlock; + + SearchResultList = new ObservableCollection(); + Selections = Selections ?? new ObservableCollection(); + SelectionsCounter.Text = $"{Selections.Count} selected"; + if (!AllowMultiple) + SelectionsCounter.Visibility = Visibility.Collapsed; + + SearchBox.PlaceholderText = string.IsNullOrWhiteSpace(PersonNotSelectedMessage) + ? "Select a person" + : PersonNotSelectedMessage; + + SearchBox.TextChanged += SearchBox_OnTextChanged; + SearchResultListBox.SelectionChanged += SearchResultListBox_OnSelectionChanged; + } + + private static void GraphAccessTokenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = d as PeoplePicker; + control?.SignInCurrentUserAsync(); + } + + private async void SignInCurrentUserAsync() + { + GraphClient = Common.GetAuthenticatedClient(GraphAccessToken); + if (GraphClient != null) + { + var me = await GraphClient.Me.Request().GetAsync(); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml new file mode 100644 index 00000000000..b88a2350cf0 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/PeoplePicker/PeoplePicker.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/DisplayModeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/DisplayModeConverter.cs new file mode 100644 index 00000000000..1d443aad952 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/DisplayModeConverter.cs @@ -0,0 +1,25 @@ +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public class DisplayModeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is ViewType && parameter != null) + { + if (value.ToString() == parameter.ToString()) + return Visibility.Visible; + } + + return Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Properties.cs new file mode 100644 index 00000000000..fc53fedf9c7 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.Properties.cs @@ -0,0 +1,263 @@ +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + /// + /// Defines the properties for the control. + /// + public partial class ProfileCard : Control + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty GraphAccessTokenProperty = DependencyProperty.Register( + nameof(GraphAccessToken), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty, OnGraphAccessTokenChanged) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty UserIdProperty = DependencyProperty.Register( + nameof(UserId), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty, OnUserIdPropertyChanged) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register( + nameof(DisplayMode), + typeof(ViewType), + typeof(ProfileCard), + new PropertyMetadata(ViewType.PictureOnly, null) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DefaultImageProperty = DependencyProperty.Register( + nameof(DefaultImage), + typeof(BitmapImage), + typeof(ProfileCard), + new PropertyMetadata(null, OnDefaultImageChanged) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DefaultTitleTextProperty = DependencyProperty.Register( + nameof(DefaultTitleText), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DefaultSecondaryMailTextProperty = DependencyProperty.Register( + nameof(DefaultSecondaryMailText), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty) + ); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DefaultMailTextProperty = DependencyProperty.Register( + nameof(DefaultMailText), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty) + ); + + internal static readonly DependencyProperty _titleProperty = DependencyProperty.Register( + nameof(_title), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty) + ); + + internal static readonly DependencyProperty _secondaryMailProperty = DependencyProperty.Register( + nameof(_secondaryMail), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty) + ); + + internal static readonly DependencyProperty _mailProperty = DependencyProperty.Register( + nameof(_mail), + typeof(string), + typeof(ProfileCard), + new PropertyMetadata(string.Empty) + ); + + internal static readonly DependencyProperty _userPhotoProperty = DependencyProperty.Register( + nameof(_userPhoto), + typeof(BitmapImage), + typeof(ProfileCard), + new PropertyMetadata(null) + ); + + /// + /// Gets or sets graph access token. + /// + public string GraphAccessToken + { + get + { + return (string)GetValue(GraphAccessTokenProperty); + } + set + { + SetValue(GraphAccessTokenProperty, value); + } + } + + /// + /// Gets or sets user unique identifier. + /// + public string UserId + { + get + { + return (string)GetValue(UserIdProperty); + } + set + { + SetValue(UserIdProperty, value); + } + } + + /// + /// Gets or sets the visual layout of the control. Default is PictureOnly. + /// + public ViewType DisplayMode + { + get + { + return (ViewType)GetValue(DisplayModeProperty); + } + set + { + SetValue(DisplayModeProperty, value); + } + } + + /// + /// Gets or sets the default image when no user is signed in. + /// + public BitmapImage DefaultImage + { + get + { + return (BitmapImage)GetValue(DefaultImageProperty); + } + set + { + SetValue(DefaultImageProperty, value); + } + } + + /// + /// Gets or sets the default title text in LargeProfilePhotoLeft mode or LargeProfilePhotoRight mode when no user is signed in. + /// + public string DefaultTitleText + { + get + { + return (string)GetValue(DefaultTitleTextProperty); + } + set + { + SetValue(DefaultTitleTextProperty, value); + } + } + + /// + /// Gets or sets the default secondary mail text in LargeProfilePhotoLeft mode or LargeProfilePhotoRight mode when no user is signed in. + /// + public string DefaultSecondaryMailText + { + get + { + return (string)GetValue(DefaultSecondaryMailTextProperty); + } + set + { + SetValue(DefaultSecondaryMailTextProperty, value); + } + } + + /// + /// Gets or sets the default mail text in EmailOnly mode when no user is signed in. + /// + public string DefaultMailText + { + get + { + return (string)GetValue(DefaultMailTextProperty); + } + set + { + SetValue(DefaultMailTextProperty, value); + } + } + + internal string _title + { + get + { + return (string)GetValue(_titleProperty); + } + private set + { + SetValue(_titleProperty, value); + } + } + + internal string _secondaryMail + { + get + { + return (string)GetValue(_secondaryMailProperty); + } + private set + { + SetValue(_secondaryMailProperty, value); + } + } + + internal string _mail + { + get + { + return (string)GetValue(_mailProperty); + } + private set + { + SetValue(_mailProperty, value); + } + } + + internal BitmapImage _userPhoto + { + get + { + return (BitmapImage)GetValue(_userPhotoProperty); + } + private set + { + SetValue(_userPhotoProperty, value); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.cs new file mode 100644 index 00000000000..6605b3b31db --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + /// + /// The Profile Card control is a simple way to display a user in multiple different formats and mixes of name/image/e-mail. + /// + public partial class ProfileCard : Control + { + private static readonly BitmapImage PersonPhoto = new BitmapImage(new Uri("ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/person.png")); + + /// + /// Initializes a new instance of the class. + /// + public ProfileCard() + { + DefaultStyleKey = typeof(ProfileCard); + } + + /// + /// Override default OnApplyTemplate to initialize child controls + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + InitUserProfile(); + } + + private static void OnGraphAccessTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + => (d as ProfileCard).FetchUserInfo(); + + private static void OnUserIdPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + => (d as ProfileCard).FetchUserInfo(); + + private static void OnDefaultImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var profileCardControl = d as ProfileCard; + if (string.IsNullOrEmpty(profileCardControl.UserId)) + profileCardControl._userPhoto = profileCardControl.DefaultImage; + } + + private async void FetchUserInfo() + { + if (string.IsNullOrEmpty(GraphAccessToken) || string.IsNullOrEmpty(UserId)) + { + InitUserProfile(); + } + else + { + var graphClient = Common.GetAuthenticatedClient(GraphAccessToken); + var user = await graphClient.Users[UserId].Request().GetAsync(); + _title = user.DisplayName; + _mail = user.Mail; + _secondaryMail = user.Mail; + if (string.IsNullOrEmpty(_mail)) + { + _userPhoto = DefaultImage ?? PersonPhoto; + } + else + { + try + { + using (Stream photoStream = await graphClient.Users[UserId].Photo.Content.Request().GetAsync()) + using (var ras = photoStream.AsRandomAccessStream()) + { + _userPhoto = new BitmapImage(); + await _userPhoto.SetSourceAsync(ras); + } + } + catch { _userPhoto = DefaultImage ?? PersonPhoto; } + } + } + } + + private void InitUserProfile() + { + _userPhoto = DefaultImage ?? PersonPhoto; + _title = DefaultTitleText ?? ""; + _mail = DefaultMailText ?? ""; + _secondaryMail = DefaultSecondaryMailText ?? ""; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml new file mode 100644 index 00000000000..d6b4bcad4d4 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ProfileCard.xaml @@ -0,0 +1,110 @@ + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs new file mode 100644 index 00000000000..a57013db8f1 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/ProfileCard/ViewType.cs @@ -0,0 +1,13 @@ +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + /// + /// The view type. + /// + public enum ViewType + { + PictureOnly = 0, + EmailOnly = 1, + LargeProfilePhotoLeft = 2, + LargeProfilePhotoRight = 3 + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..86e6d257c35 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Toolkit.Uwp.UI.Controls.Graph")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.Toolkit.Uwp.UI.Controls.Graph")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/Microsoft.Toolkit.Uwp.UI.Controls.Graph.rd.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/Microsoft.Toolkit.Uwp.UI.Controls.Graph.rd.xml new file mode 100644 index 00000000000..17a888ccc66 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/Properties/Microsoft.Toolkit.Uwp.UI.Controls.Graph.rd.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DetailPaneDisplayMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DetailPaneDisplayMode.cs new file mode 100644 index 00000000000..d9a486596b2 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DetailPaneDisplayMode.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public enum DetailPaneDisplayMode + { + Disabled, Side, Bottom, Full + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DriveItemIconConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DriveItemIconConverter.cs new file mode 100644 index 00000000000..bf52bab5886 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/DriveItemIconConverter.cs @@ -0,0 +1,53 @@ +using Microsoft.Graph; +using System; +using Windows.UI.Xaml.Data; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public class DriveItemIconConverter : IValueConverter + { + private static readonly string s_officeIcon = "https://static2.sharepointonline.com/files/fabric/assets/brand-icons/document/png/{0}_32x1_5.png"; + private static readonly string s_localIcon = "ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls.Graph/Assets/{0}"; + + public object Convert(object value, Type targetType, object parameter, string language) + { + DriveItem driveItem = value as DriveItem; + + if (driveItem.Folder != null) + { + return string.Format(s_localIcon, "folder.svg"); + } + else if (driveItem.File != null) + { + if (driveItem.File.MimeType.StartsWith("image")) + { + return string.Format(s_localIcon, "photo.png"); + } + else if (driveItem.File.MimeType.StartsWith("application/vnd.openxmlformats-officedocument")) + { + int index = driveItem.Name.LastIndexOf('.'); + if (index != -1) + { + string ext = driveItem.Name.Substring(index + 1); + return string.Format(s_officeIcon, ext); + } + } + } + else if (driveItem.Package != null) + { + switch (driveItem.Package.Type) + { + case "oneNote": + return string.Format(s_officeIcon, "one"); + } + } + + return string.Format(s_localIcon, "genericfile.png"); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return null; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSelectedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSelectedEventArgs.cs new file mode 100644 index 00000000000..ba227f7c997 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSelectedEventArgs.cs @@ -0,0 +1,10 @@ +using Microsoft.Graph; +using System; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public class FileSelectedEventArgs : EventArgs + { + public DriveItem FileSelected { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSizeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSizeConverter.cs new file mode 100644 index 00000000000..c6d94498c31 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/FileSizeConverter.cs @@ -0,0 +1,26 @@ +using System; +using Windows.UI.Xaml.Data; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + internal class FileSizeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + long size = (long)value; + if (size > 1024 * 1024) + return Math.Round(size / 1024.0 / 1024, 1) + "MB"; + else if (size > 1024 * 2) + return Math.Round(size / 1024.0, 1) + "KB"; + else if (size == 1) + return size + " byte"; + else + return size + " bytes"; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.Properties.cs new file mode 100644 index 00000000000..21c07181286 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.Properties.cs @@ -0,0 +1,173 @@ +using Microsoft.Graph; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public partial class SharePointFiles : Control + { + public static readonly DependencyProperty GraphAccessTokenProperty = + DependencyProperty.Register( + nameof(GraphAccessToken), typeof(string), + typeof(SharePointFiles), new PropertyMetadata(string.Empty, GraphAccessTokenPropertyChanged) + ); + + public static readonly DependencyProperty DriveUrlProperty = + DependencyProperty.Register( + nameof(DriveUrl), typeof(string), + typeof(SharePointFiles), new PropertyMetadata(string.Empty, DriveUrlPropertyChanged) + ); + + public static readonly DependencyProperty DetailPaneProperty = + DependencyProperty.Register( + nameof(DetailPane), typeof(DetailPaneDisplayMode), + typeof(SharePointFiles), new PropertyMetadata(DetailPaneDisplayMode.Disabled, DetailPanePropertyChanged) + ); + + public static readonly DependencyProperty PageSizeProperty = + DependencyProperty.Register( + nameof(PageSize), typeof(int), + typeof(SharePointFiles), new PropertyMetadata(20) + ); + + internal static readonly DependencyProperty HasMoreProperty = + DependencyProperty.Register( + nameof(HasMore), typeof(bool), + typeof(SharePointFiles), new PropertyMetadata(false) + ); + + internal static readonly DependencyProperty SelectedFileProperty = + DependencyProperty.Register( + nameof(SelectedFile), typeof(DriveItem), + typeof(SharePointFiles), new PropertyMetadata(null) + ); + + internal static readonly DependencyProperty SizeProperty = + DependencyProperty.Register( + nameof(FileSize), typeof(long), + typeof(SharePointFiles), new PropertyMetadata(0L) + ); + + internal static readonly DependencyProperty LastModifiedProperty = + DependencyProperty.Register( + nameof(LastModified), typeof(string), + typeof(SharePointFiles), null + ); + + private static readonly DependencyProperty IsDetailPaneVisibleProperty = + DependencyProperty.Register( + nameof(IsDetailPaneVisible), typeof(bool), + typeof(SharePointFiles), new PropertyMetadata(false) + ); + + private int _fileUploading; + private string _errorMessage; + + public string GraphAccessToken + { + get { return (string)GetValue(GraphAccessTokenProperty); } + set { SetValue(GraphAccessTokenProperty, value); } + } + + public string DriveUrl + { + get { return (string)GetValue(DriveUrlProperty); } + set { SetValue(DriveUrlProperty, value); } + } + + public DetailPaneDisplayMode DetailPane + { + get { return (DetailPaneDisplayMode)GetValue(DetailPaneProperty); } + set { SetValue(DetailPaneProperty, value); } + } + + public int PageSize + { + get { return (int)GetValue(PageSizeProperty); } + set { SetValue(PageSizeProperty, value); } + } + + internal bool HasMore + { + get { return (bool)GetValue(HasMoreProperty); } + set { SetValue(HasMoreProperty, value); } + } + + internal DriveItem SelectedFile + { + get { return (DriveItem)GetValue(SelectedFileProperty); } + set { SetValue(SelectedFileProperty, value); } + } + + internal long FileSize + { + get { return (long)GetValue(SizeProperty); } + set { SetValue(SizeProperty, value); } + } + + internal string LastModified + { + get { return (string)GetValue(LastModifiedProperty); } + set { SetValue(LastModifiedProperty, value); } + } + + private int FileUploading + { + get { return _fileUploading; } + set + { + _fileUploading = value; + if (value > 0) + { + _status.Text = $"Uploading {value} files..."; + _status.TextDecorations = Windows.UI.Text.TextDecorations.None; + _status.Foreground = new SolidColorBrush(Windows.UI.Colors.Black); + if (string.IsNullOrEmpty(_errorMessage)) + _status.Visibility = Visibility.Visible; + else + _status.Visibility = Visibility.Collapsed; + _cancel.Visibility = Visibility.Visible; + } + else + { + _status.Visibility = Visibility.Collapsed; + _cancel.Visibility = Visibility.Collapsed; + } + } + } + + private string ErrorMessage + { + get { return _errorMessage; } + set + { + _errorMessage = value; + if (!string.IsNullOrEmpty(value)) + { + _error.Visibility = Visibility.Visible; + _status.Visibility = Visibility.Collapsed; + } + else + { + if (FileUploading > 0) + _status.Visibility = Visibility.Visible; + _error.Visibility = Visibility.Collapsed; + } + } + } + + private bool IsDetailPaneVisible + { + get { return (bool)GetValue(IsDetailPaneVisibleProperty); } + set + { + SetValue(IsDetailPaneVisibleProperty, value); + if (value) + ShowDetailsPane(); + else + HideDetailsPane(); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.cs new file mode 100644 index 00000000000..e1da9938277 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Graph/SharePointFiles/SharePointFiles.cs @@ -0,0 +1,498 @@ +using Microsoft.Graph; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; +using Windows.Storage.Pickers; +using Windows.UI.Popups; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Graph +{ + public partial class SharePointFiles : Control + { + public event EventHandler FileSelected; + + private GraphServiceClient _graphClient; + private string _driveId; + private Stack _driveItemPath = new Stack(); + private IDriveItemChildrenCollectionRequest _nextPageRequest = null; + private CancellationTokenSource _cancelUpload = new CancellationTokenSource(); + private CancellationTokenSource _cancelLoadFile = new CancellationTokenSource(); + private CancellationTokenSource _cancelGetDetails = new CancellationTokenSource(); + + private ListView _list; + private Button _back; + private Button _upload; + private Button _share; + private Button _download; + private Button _delete; + private HyperlinkButton _error; + private Button _cancel; + private Button _hasMore; + private Grid _thumbnail; + private Windows.UI.Xaml.Controls.Image _thumbnailImage; + private ScrollViewer _details; + private TextBlock _status; + + public SharePointFiles() + { + DefaultStyleKey = typeof(SharePointFiles); + } + + protected override void OnApplyTemplate() + { + _list = GetTemplateChild("list") as ListView; + _back = GetTemplateChild("back") as Button; + _upload = GetTemplateChild("upload") as Button; + _share = GetTemplateChild("share") as Button; + _download = GetTemplateChild("download") as Button; + _delete = GetTemplateChild("delete") as Button; + _error = GetTemplateChild("error") as HyperlinkButton; + _cancel = GetTemplateChild("cancel") as Button; + _hasMore = GetTemplateChild("hasMore") as Button; + _thumbnail = GetTemplateChild("thumbnail") as Grid; + _thumbnailImage = GetTemplateChild("thumbnailImage") as Windows.UI.Xaml.Controls.Image; + _details = GetTemplateChild("details") as ScrollViewer; + _status = GetTemplateChild("status") as TextBlock; + _list.SelectionChanged += list_SelectionChanged; + _list.ItemClick += _list_ItemClick; + _back.Click += Back_Click; + _upload.Click += Upload_Click; + _share.Click += Share_Click; + _download.Click += Download_Click; + _delete.Click += Delete_Click; + _error.Click += Error_Click; + _cancel.Click += Cancel_Click; + _hasMore.Click += LoadMore_Click; + base.OnApplyTemplate(); + } + + private async void _list_ItemClick(object sender, ItemClickEventArgs e) + { + DriveItem driveItem = e.ClickedItem as DriveItem; + if (driveItem != null && driveItem.Folder != null) + { + _driveItemPath.Push(driveItem.Id); + _back.Visibility = Visibility.Visible; + await LoadFiles(driveItem.Id); + } + } + + public async Task GetDriveUrlFromSharePointUrl(string rawDocLibUrl) + { + if (string.IsNullOrEmpty(rawDocLibUrl)) + { + return rawDocLibUrl; + } + + rawDocLibUrl = WebUtility.UrlDecode(rawDocLibUrl); + + Match match = Regex.Match(rawDocLibUrl, @"(https?://([^/]+)((/[^/?]+)*?)(/[^/?]+))(/(Forms/\w+.aspx)?)?(\?.*)?$", RegexOptions.IgnoreCase); + string docLibUrl = match.Groups[1].Value; + string hostName = match.Groups[2].Value; + string siteRelativePath = match.Groups[3].Value; + if (string.IsNullOrEmpty(siteRelativePath)) + { + siteRelativePath = "/"; + } + + Site site = await _graphClient.Sites.GetByPath(siteRelativePath, hostName).Request().GetAsync(); + ISiteDrivesCollectionPage drives = await _graphClient.Sites[site.Id].Drives.Request().GetAsync(); + + Drive drive = drives.SingleOrDefault(o => WebUtility.UrlDecode(o.WebUrl).Equals(docLibUrl, StringComparison.CurrentCultureIgnoreCase)); + if (drive == null) + { + throw new Exception("Drive not found"); + } + + return _graphClient.Drives[drive.Id].RequestUrl; + } + + public async Task InitDrive(string driveUrl) + { + HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, driveUrl); + await _graphClient.AuthenticationProvider.AuthenticateRequestAsync(message); + + HttpResponseMessage result = await _graphClient.HttpProvider.SendAsync(message); + if (result.StatusCode == HttpStatusCode.OK) + { + string json = await result.Content.ReadAsStringAsync(); + Drive drive = JsonConvert.DeserializeObject(json); + if (drive != null) + { + _driveId = drive.Id; + _driveItemPath.Clear(); + DriveItem rootDriveItem = await _graphClient.Drives[_driveId].Root.Request().GetAsync(); + _driveItemPath.Push(rootDriveItem.Id); + await LoadFiles(rootDriveItem.Id); + _back.Visibility = Visibility.Collapsed; + } + } + } + + public async Task LoadFiles(string driveItemId, int pageIndex = 0) + { + if (!string.IsNullOrEmpty(_driveId)) + { + try + { + _cancelLoadFile.Cancel(false); + _cancelLoadFile.Dispose(); + _cancelLoadFile = new CancellationTokenSource(); + _list.Items.Clear(); + _download.Visibility = Visibility.Collapsed; + _share.Visibility = Visibility.Collapsed; + _delete.Visibility = Visibility.Collapsed; + QueryOption queryOption = new QueryOption("$top", PageSize.ToString()); + Task taskFiles = _graphClient.Drives[_driveId].Items[driveItemId].Children.Request(new List