Skip to content

Commit

Permalink
Add TeamsApp
Browse files Browse the repository at this point in the history
  • Loading branch information
justinyoo committed Oct 14, 2023
1 parent e68f1bc commit ceec3e6
Show file tree
Hide file tree
Showing 58 changed files with 2,983 additions and 1 deletion.
11 changes: 10 additions & 1 deletion YouTubeSummariser.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F0754B28-F0B
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YouTubeSummariser.ApiApp", "src\YouTubeSummariser.ApiApp\YouTubeSummariser.ApiApp.csproj", "{D2B6ACCD-125F-4AB8-B072-0F3C104AEBB2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouTubeSummariser.ApiApp2", "src\YouTubeSummariser.ApiApp2\YouTubeSummariser.ApiApp2.csproj", "{CEB81668-3BE6-4BBC-BA78-7AD7F64F80C6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YouTubeSummariser.ApiApp2", "src\YouTubeSummariser.ApiApp2\YouTubeSummariser.ApiApp2.csproj", "{CEB81668-3BE6-4BBC-BA78-7AD7F64F80C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YouTubeSummariser.Services", "src\YouTubeSummariser.Services\YouTubeSummariser.Services.csproj", "{47F2E24C-23E7-4E07-B52A-7A33C5165645}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YouTubeSummariser.WebApp.Wasm", "src\YouTubeSummariser.WebApp.Wasm\YouTubeSummariser.WebApp.Wasm.csproj", "{BA0E2490-C014-4325-85C3-5132FEBE1844}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YouTubeSummariser.Components", "src\YouTubeSummariser.Components\YouTubeSummariser.Components.csproj", "{8F0B64D6-E1A2-487F-8978-AA91A2732AF3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YouTubeSummariser.WebApp.Teams", "src\YouTubeSummariser.WebApp.Teams\YouTubeSummariser.WebApp.Teams.csproj", "{277E49A0-CB06-427A-8066-9A8456310673}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -41,6 +43,12 @@ Global
{8F0B64D6-E1A2-487F-8978-AA91A2732AF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F0B64D6-E1A2-487F-8978-AA91A2732AF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F0B64D6-E1A2-487F-8978-AA91A2732AF3}.Release|Any CPU.Build.0 = Release|Any CPU
{277E49A0-CB06-427A-8066-9A8456310673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{277E49A0-CB06-427A-8066-9A8456310673}.Debug|Any CPU.Build.0 = Debug|Any CPU
{277E49A0-CB06-427A-8066-9A8456310673}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{277E49A0-CB06-427A-8066-9A8456310673}.Release|Any CPU.ActiveCfg = Release|Any CPU
{277E49A0-CB06-427A-8066-9A8456310673}.Release|Any CPU.Build.0 = Release|Any CPU
{277E49A0-CB06-427A-8066-9A8456310673}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -51,6 +59,7 @@ Global
{47F2E24C-23E7-4E07-B52A-7A33C5165645} = {F0754B28-F0B2-414C-83DB-06854DBF508E}
{BA0E2490-C014-4325-85C3-5132FEBE1844} = {F0754B28-F0B2-414C-83DB-06854DBF508E}
{8F0B64D6-E1A2-487F-8978-AA91A2732AF3} = {F0754B28-F0B2-414C-83DB-06854DBF508E}
{277E49A0-CB06-427A-8066-9A8456310673} = {F0754B28-F0B2-414C-83DB-06854DBF508E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8AEBE9-75D2-45E8-8795-38BB5E81FEFD}
Expand Down
25 changes: 25 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# TeamsFx files
build
appPackage/build
env/.env.*.user
env/.env.local
appsettings.Development.json
.deployment

# User-specific files
*.user

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Notification local store
.notification.localstore.json
12 changes: 12 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/App.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<FluentDesignSystemProvider AccentBaseColor="#6264A7">
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</FluentDesignSystemProvider>
16 changes: 16 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/Components/CurrentUser.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div>
<h2>Get the current user</h2>
<p>Access basic information about the user like this:</p>
<pre>var user = await teamsUserCredential.GetUserInfoAsync();</pre>
@if (!string.IsNullOrEmpty(UserName))
{
<p>
The currently logged in user's name is <b>@UserName</b>
</p>
}
</div>

@code {
[Parameter]
public string UserName { get; set; }
}
15 changes: 15 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/Components/Deploy.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div style="margin: 0 auto; display: block; width: 100%;">
<h2>Provision and Deploy to the Cloud</h2>
<p>
Before publishing your app to Teams App Catalog, you may want to provision and deploy your
app's resources to the cloud to make sure your app will be running smoothly!
</p>
<p>
Select the <strong>Project > Teams Toolkit > Provision in the Cloud</strong> menu item to provision your app's resources.
Next, select the <strong>Project > Teams Toolkit > Deploy to the Cloud</strong> menu item to deploy your app to Azure.
</p>
</div>

@code {

}
11 changes: 11 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/Components/EditCode.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
<h2>Change this code</h2>
<p>
The front end is a <code>Blazor Server app</code>. The entry point is
<code>./Program.cs</code>.
</p>
</div>

@code {

}
129 changes: 129 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/Components/Graph.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
@using System.IO
@using Azure.Core
@using Microsoft.Graph
@using Microsoft.Graph.Models;
@inject TeamsFx teamsfx
@inject TeamsUserCredential teamsUserCredential

<div>
<h2>Get the user's profile photo</h2>
@if (NeedConsent)
{
<p>Click below to authorize this app to read your profile photo using Microsoft Graph.</p>
<FluentButton Appearance="Appearance.Accent" Disabled="@IsLoading" @onclick="ConsentAndShow">Authorize</FluentButton>
}
@if (IsLoading)
{
<ProfileCard IsLoading="true" />
}
else if (!string.IsNullOrEmpty(@ErrorMessage))
{
<div class="error">@ErrorMessage</div>
}
else if (Profile != null)
{
<ProfileCard IsLoading="false" Profile="@Profile" UserPhotoUri="@UserPhotoUri" />
}
</div>

@code {
[Parameter]
public string ErrorMessage { get; set; }

public bool IsLoading { get; set; }
public bool NeedConsent { get; set; }
public User Profile { get; set; }
public string UserPhotoUri { get; set; }

private readonly string _scope = "User.Read";

protected override async Task OnInitializedAsync()
{
IsLoading = true;
if (await HasPermission(_scope))
{
await ShowProfile();
}
}

private async Task ShowProfile()
{
IsLoading = true;
var graph = GetGraphServiceClient();

Profile = await graph.Me.GetAsync();
UserPhotoUri = await GetPhotoAsync(graph);

IsLoading = false;
ErrorMessage = string.Empty;
}

private async Task ConsentAndShow()
{
try
{
await teamsUserCredential.LoginAsync(_scope);
NeedConsent = false;
await ShowProfile();
}
catch (ExceptionWithCode e)
{
ErrorMessage = e.Message;
}
}

private async Task<bool> HasPermission(string scope)
{
IsLoading = true;
try
{
await teamsUserCredential.GetTokenAsync(new TokenRequestContext(new string[] { _scope }), new System.Threading.CancellationToken());
return true;
}
catch (ExceptionWithCode e)
{
if (e.Code == ExceptionCode.UiRequiredError)
{
NeedConsent = true;
}
else
{
ErrorMessage = e.Message;
}

}
IsLoading = false;
return false;
}

private GraphServiceClient GetGraphServiceClient()
{
var msGraphAuthProvider = new MsGraphAuthProvider(teamsUserCredential, _scope);
var client = new GraphServiceClient(msGraphAuthProvider);
return client;
}

private async Task<string> GetPhotoAsync(GraphServiceClient graph)
{
string userPhoto = "";
try
{
var photoStream = await graph.Me.Photo.Content.GetAsync();

if (photoStream != null)
{
// Copy the photo stream to a memory stream
// to get the bytes out of it
var memoryStream = new MemoryStream();
photoStream.CopyTo(memoryStream);
var photoBytes = memoryStream.ToArray();

// Generate a data URI for the photo
userPhoto = $"data:image/png;base64,{Convert.ToBase64String(photoBytes)}";
}
}
catch (Exception) { /* Unable to get the users photo */ }

return userPhoto;
}
}
39 changes: 39 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/Components/Loading.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@using Microsoft.Extensions.Configuration
@using System.IO
@inject NavigationManager MyNavigationManager
@inject IConfiguration Configuration
@inject IJSRuntime jsRuntime
@inject MicrosoftTeams MicrosoftTeams

@if (isLoaded)
{
@ChildContent
}
else
{
<div style="display: flex; justify-content: center; align-items: center; min-height: 100vh;">
<FluentProgressRing/>
</div>
}

@code {
bool isLoaded;

[Parameter]
public RenderFragment ChildContent { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);

try
{
if (firstRender)
{
isLoaded = true;
StateHasChanged();
}
}
catch (Exception) { }
}
}
42 changes: 42 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/Components/ProfileCard.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@using Microsoft.Graph
@using Microsoft.Graph.Models;

<FluentCard style="height: max-content; margin: 0.5em 0; max-width: 380px;">
@if (IsLoading)
{
<div class="profile">
<div class="avatar">
<FluentSkeleton style="border-radius: 32px; width: 64px; height: 64px; margin: 15px;" Shape="SkeletonShape.Circle" Shimmer="true"></FluentSkeleton>
</div>
<div class="info">
<FluentSkeleton style="border-radius: 4px; height: 14px;" Shape="SkeletonShape.Rect" Shimmer="true"></FluentSkeleton>
<FluentSkeleton style="border-radius: 4px; margin-top: 5px; height: 10px;" Shape="SkeletonShape.Rect" Shimmer="true"></FluentSkeleton>
<FluentSkeleton style="border-radius: 4px; margin-top: 5px; height: 10px;" Shape="SkeletonShape.Rect" Shimmer="true"></FluentSkeleton>
</div>
</div>
}
else if (!IsLoading && Profile != null)
{
<div class="profile">
<div class="avatar">
@if (!string.IsNullOrEmpty(@UserPhotoUri)) {
<img src="@UserPhotoUri" />
}
</div>
<div class="info">
<h3>@Profile.DisplayName</h3>
<p>@Profile.Mail</p>
<p>@Profile.OfficeLocation</p>
</div>
</div>
}
</FluentCard>

@code {
[Parameter]
public bool IsLoading { get; set; }
[Parameter]
public User Profile { get; set; }
[Parameter]
public string UserPhotoUri { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.profile {
display: flex;
align-items: center;
}

.profile .avatar {
flex: 1 0 auto;
}

.profile .avatar img {
margin: 15px;
height: 64px;
width: 64px;
border-radius: 32px;
}

.profile .info {
margin: 0 2em;
flex: 4 0 auto;
}

.profile .info > h3 {
margin: 0;
}

.profile .info > p {
margin: 0;
}
14 changes: 14 additions & 0 deletions src/YouTubeSummariser.WebApp.Teams/Components/Publish.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div style="margin: 0 auto; display: block; width: 100%;">
<h2>Publish to Teams</h2>
<p>
Your app's resources and infrastructure are deployed and ready? Publish and register your
app to Teams app catalog to share your app with others in your organization!
</p>
<p>
For more information, see the <a href="https://aka.ms/teams-toolkit-distribute" target="_blank" rel="noreferrer">docs</a>.
</p>
</div>

@code {

}
Loading

0 comments on commit ceec3e6

Please sign in to comment.