├── .gitattributes ├── .gitignore ├── BlazorGraphExample.sln ├── BlazorGraphExample ├── App.razor ├── BlazorGraphExample.csproj ├── Components │ ├── DriveItem.razor │ ├── DriveItemList.razor │ ├── DrivePath.razor │ ├── InfoPanel.razor │ ├── Loading.razor │ ├── LoginButton.razor │ ├── LoginPanel.razor │ └── Paginator.razor ├── Pages │ ├── Index.razor │ └── _Imports.razor ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ └── FolderProfile.pubxml │ └── launchSettings.json ├── Services │ ├── AppState.cs │ ├── AuthService.cs │ ├── GraphAccount.cs │ ├── IPagingState.cs │ ├── Json │ │ ├── CamelCase.cs │ │ ├── JsonSerializer.cs │ │ ├── README.txt │ │ └── SimpleJson.cs │ ├── LoginStatus.cs │ ├── PageChangedEvent.cs │ └── TokenResult.cs ├── Shared │ ├── Format.cs │ └── MainLayout.razor ├── Startup.cs ├── _Imports.razor ├── bundleconfig.json ├── package-lock.json ├── package.json └── wwwroot │ ├── css │ ├── bulma │ │ └── bulma.min.css │ ├── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ │ ├── css │ │ │ ├── open-iconic-bootstrap.min.css │ │ │ └── open-iconic.min.css │ │ │ └── fonts │ │ │ ├── open-iconic.eot │ │ │ ├── open-iconic.otf │ │ │ ├── open-iconic.svg │ │ │ ├── open-iconic.ttf │ │ │ └── open-iconic.woff │ └── site.css │ ├── index.html │ └── scripts │ ├── appload.js │ ├── login.js │ └── msal │ └── msal.min.js ├── Docs └── onedrive.png ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /BlazorGraphExample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28606.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorGraphExample", "BlazorGraphExample\BlazorGraphExample.csproj", "{D5AEC440-6134-4869-A3FC-023474B2BA54}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D5AEC440-6134-4869-A3FC-023474B2BA54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {D5AEC440-6134-4869-A3FC-023474B2BA54}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {D5AEC440-6134-4869-A3FC-023474B2BA54}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {D5AEC440-6134-4869-A3FC-023474B2BA54}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0271F2D6-72E4-4F37-A871-3B5B2ECB6665} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /BlazorGraphExample/App.razor: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /BlazorGraphExample/BlazorGraphExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; 7 | https://dotnet.myget.org/F/blazor-dev/api/v3/index.json; 8 | 9 | 7.3 10 | 3.0 11 | 12 | 13 | 14 | TRACE;DEBUG;SIMPLE_JSON_DATACONTRACT 15 | 16 | 17 | 18 | TRACE;RELEASE;SIMPLE_JSON_DATACONTRACT 19 | 20 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /BlazorGraphExample/Components/DriveItem.razor: -------------------------------------------------------------------------------- 1 | @inject AppState state 2 | @inject IGraphService graphService 3 | 4 | @if(Item.IsFolder()) { 5 | 6 | @Item.Name 7 | 8 | files 9 | @Item.Folder.ChildCount 10 | 11 | 12 | } else { 13 | 14 | 15 | 16 | 17 | @Item.Name 18 |
19 | @if (Item.Shared != null) 20 | { 21 | 24 | } 25 | 26 | @Format.FormatFileSize(Item.Size) 27 | 28 |
29 |
30 | } 31 | 32 | @code { 33 | [Parameter] 34 | public W8lessLabs.GraphAPI.DriveItem Item { get; set; } 35 | 36 | void SelectItem(W8lessLabs.GraphAPI.DriveItem item) 37 | { 38 | if (!state.InProgress) 39 | { 40 | if (item.IsFolder()) 41 | { 42 | // is a folder 43 | state.PushFolder(item); 44 | } 45 | else 46 | { 47 | state.SelectFile(item); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /BlazorGraphExample/Components/DriveItemList.razor: -------------------------------------------------------------------------------- 1 | @inject AppState state 2 | @inject IGraphService graphService 3 | 4 |
5 |
6 |
7 | @if (state.InProgress && state.DriveItems == null) 8 | { 9 | 10 | } 11 | else 12 | { 13 | @if (state.DriveItems != null) 14 | { 15 | var orderedDriveItems = state.DriveItems.OrderByDescending(item => item.IsFolder()).ThenBy(item => item.Name); 16 | 17 | 35 | } 36 | } 37 |
38 |
39 | 40 |
41 |
42 |
43 | 44 | @code { 45 | 46 | protected async Task ReloadAsync() 47 | { 48 | await LoadDriveItemsAsync(allowCache: false); 49 | } 50 | 51 | protected async Task LoadDriveItemsAsync(string skipToken = null, bool allowCache = true) 52 | { 53 | if (!state.InProgress) 54 | { 55 | List items = null; 56 | try 57 | { 58 | state.SetInProgress(true); 59 | var request = new GetDriveItemsRequest(state.Path, state.PageSize, skipToken, allowCache); 60 | var response = await graphService.GetDriveItemsAsync(state.AccountId, request); 61 | 62 | if (response != null) 63 | { 64 | items = response.DriveItems; 65 | 66 | bool hasAnotherPage = (response.SkipToken != null); 67 | 68 | if(state.CurrentPage == 1) 69 | { 70 | if(hasAnotherPage) 71 | { 72 | int childCount = await graphService.GetChildItemsCountAsync(state.AccountId, state.Path); 73 | state.SetPageCount(childCount, request.PageSize); 74 | } 75 | else 76 | state.SetPageCount(1, request.PageSize); 77 | } 78 | 79 | if (hasAnotherPage) 80 | state.SetPageToken(state.CurrentPage, response.SkipToken); 81 | } 82 | } 83 | finally 84 | { 85 | state.SetInProgress(false); 86 | } 87 | 88 | state.SetDriveItems(items); 89 | } 90 | } 91 | 92 | protected override async Task OnInitializedAsync() 93 | { 94 | state.InProgressChanged += StateHasChanged; 95 | state.DriveItemsChanged += StateHasChanged; 96 | state.SelectedFolderChanged += async () => await LoadDriveItemsAsync(); 97 | state.SelectedFileChanged += StateHasChanged; 98 | state.CurrentPageChanged += async pageChanged => 99 | { 100 | (int oldPage, int newPage) = pageChanged; 101 | string skipToken = null; 102 | if(newPage > 1) 103 | state.TryGetPageToken(newPage - 1, out skipToken); 104 | await LoadDriveItemsAsync(skipToken); 105 | }; 106 | 107 | // load root folder 108 | var rootFolder = await graphService.GetDriveItemAsync(state.AccountId, "/"); 109 | 110 | state.PushFolder(rootFolder); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /BlazorGraphExample/Components/DrivePath.razor: -------------------------------------------------------------------------------- 1 | @inject AppState state 2 | 3 | 4 | 18 | @if(!string.IsNullOrEmpty(state.Path)) { 19 | 32 | } 33 | 34 | 35 | @code { 36 | int _loadedCount = 0; 37 | 38 | private Stack pathSlashes = new Stack(); 39 | 40 | void PopFolder() { 41 | state.PopFolder(); 42 | } 43 | 44 | string GetDisplayPath() 45 | { 46 | pathSlashes.Clear(); 47 | string path = state.Path; 48 | for (int i = 0; i < path.Length; i++) 49 | if (path[i] == '/') pathSlashes.Push(i); 50 | if(pathSlashes.Count > 2) 51 | { 52 | pathSlashes.Pop(); 53 | int index = pathSlashes.Peek(); 54 | return ".." + path.Substring(index); 55 | } 56 | return path; 57 | } 58 | 59 | protected override void OnInitialized() 60 | { 61 | state.InProgressChanged += StateHasChanged; 62 | state.LoadProgressChanged += (count) => { 63 | _loadedCount = count; 64 | StateHasChanged(); 65 | }; 66 | state.DriveItemsChanged += () => { 67 | _loadedCount = 0; 68 | StateHasChanged(); 69 | }; 70 | } 71 | } -------------------------------------------------------------------------------- /BlazorGraphExample/Components/InfoPanel.razor: -------------------------------------------------------------------------------- 1 | @inject AppState state 2 | @inject IGraphService graphService 3 | 4 | @if (state.SelectedFolder != null) 5 | { 6 |
7 |
8 |

9 | 10 | 11 | 12 | Folder Info 13 |

14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Name@state.SelectedFolder.Name
Created@state.SelectedFolder.CreatedDateTime.DateTime.ToShortDateString()
Items@state.SelectedFolder.Folder.ChildCount.ToString("#,###,##0")
30 |
31 |
32 | @if (state.SelectedFile != null && state.SelectedFile.ParentReference.Id == state.SelectedFolder.Id) 33 | { 34 |
35 |
36 |

37 | 38 | 39 | 40 | File Info 41 |

42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | @*@if (state.SelectedFile.Shared != null) 66 | { 67 | if (filePermissions == null) 68 | { 69 | 70 | 73 | 74 | } 75 | else 76 | { 77 | foreach(Permission perm in filePermissions) 78 | { 79 | 80 | 83 | 86 | 87 | } 88 | } 89 | }*@ 90 | 91 | 92 |
Name@state.SelectedFile.Name
Created@state.SelectedFile.CreatedDateTime.DateTime.ToShortDateString()
Items@Format.FormatFileSize(state.SelectedFile.Size)
59 | 60 | Open File 61 | 62 | 63 |
71 | 72 |
81 | @(GetSharedWithName(perm)) 82 | 84 | @(string.Join(",", perm.Roles)) 85 |
93 |
94 |
95 | } 96 | } 97 | 98 | @code { 99 | 100 | IEnumerable filePermissions = null; 101 | 102 | void PathChangedHandler() 103 | { 104 | StateHasChanged(); 105 | } 106 | 107 | void FileChangedHandler() 108 | { 109 | StateHasChanged(); 110 | } 111 | 112 | async Task LoadFilePermissions() 113 | { 114 | filePermissions = null; 115 | if(state.SelectedFile.Shared != null) 116 | filePermissions = await graphService.GetPermissionsByIdAsync(state.AccountId, state.SelectedFile.Id); 117 | 118 | StateHasChanged(); 119 | } 120 | 121 | string GetSharedWithName(Permission perm) 122 | { 123 | var grantedTo = perm.GrantedTo; 124 | if (grantedTo != null) 125 | { 126 | if (grantedTo.User != null) 127 | return grantedTo.User.DisplayName; 128 | else if (grantedTo.Application != null) 129 | return grantedTo.Application.DisplayName; 130 | else if (grantedTo.Device != null) 131 | return grantedTo.Device.DisplayName; 132 | } 133 | 134 | if (perm.Link != null) 135 | return "Link"; 136 | else 137 | return "N/A"; 138 | } 139 | 140 | protected override void OnInitialized() 141 | { 142 | state.InProgressChanged += StateHasChanged; 143 | state.DriveItemsChanged += StateHasChanged; 144 | state.SelectedFolderChanged += PathChangedHandler; 145 | state.SelectedFileChanged += FileChangedHandler; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /BlazorGraphExample/Components/Loading.razor: -------------------------------------------------------------------------------- 1 | @Text 2 | 3 | @code { 4 | [Parameter] 5 | public string Text {get ; set; } = "Loading..."; 6 | } -------------------------------------------------------------------------------- /BlazorGraphExample/Components/LoginButton.razor: -------------------------------------------------------------------------------- 1 | @inject AppState state 2 | @inject AuthService authService 3 | 4 | @if (state.LoginStatus == LoginStatus.LoggedIn) 5 | { 6 |

7 | 11 |

12 | } 13 | else 14 | { 15 |

16 | 20 |

21 | } 22 | 23 | @code { 24 | 25 | async Task LoginUser() 26 | { 27 | (GraphTokenResult token, GraphAccount account) = await authService.LoginAsync(); 28 | var isLoggedIn = token.Success; 29 | var status = isLoggedIn ? LoginStatus.LoggedIn : LoginStatus.LoggedOut; 30 | 31 | state.SetAccountId(account?.AccountId); 32 | state.SetLoginStatus(status); 33 | } 34 | 35 | async Task LogoutUser() 36 | { 37 | await authService.LogoutAsync(); 38 | state.SetLoginStatus(LoginStatus.LoggedOut); 39 | state.SetAccountId(null); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BlazorGraphExample/Components/LoginPanel.razor: -------------------------------------------------------------------------------- 1 | @inject AppState state 2 | @inject AuthService authService 3 | 4 | @if (state.LoginStatus == LoginStatus.LoggedIn) 5 | { 6 | @ChildContent 7 | } 8 | else if (state.LoginStatus == LoginStatus.LoggedOut) 9 | { 10 |
11 |
12 |

You are not logged in

13 |

Please click on Login to get started.

14 |

15 | 16 |

17 |
18 |
19 | } 20 | else 21 | { 22 | 23 | } 24 | 25 | @code { 26 | 27 | [Parameter] 28 | public RenderFragment ChildContent { get; set; } 29 | 30 | protected override async Task OnInitializedAsync() 31 | { 32 | var account = await authService.GetUserAccountAsync(); 33 | if(authService.IsLoggedIn(account)) 34 | { 35 | state.SetAccountId(account.AccountId); 36 | state.SetLoginStatus(LoginStatus.LoggedIn); 37 | } 38 | else 39 | { 40 | state.SetLoginStatus(LoginStatus.LoggedOut); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /BlazorGraphExample/Components/Paginator.razor: -------------------------------------------------------------------------------- 1 | @inject IPagingState state 2 | 3 | 11 | 12 | @code { 13 | 14 | protected override void OnInitialized() 15 | { 16 | state.PageCountChanged += StateHasChanged; 17 | state.CurrentPageChanged += pageChanged => StateHasChanged(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /BlazorGraphExample/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | @inject AppState state 4 | @inject IGraphService graphService 5 | 6 |
7 |
8 |
9 | Blazor Graph API Example: Welcome to an example of using the Microsoft Graph API from Blazor. 10 | View the source on Github to see how it works. 11 |
12 | 13 | 14 |
15 | 28 | 29 |
30 |
31 |
32 | 33 | @code { 34 | 35 | protected override void OnInitialized() 36 | { 37 | state.LoginStatusChanged += async () => 38 | { 39 | await LoadUserAsync(); 40 | 41 | StateHasChanged(); 42 | }; 43 | } 44 | 45 | protected async Task LoadUserAsync() 46 | { 47 | if (state.LoginStatus == LoginStatus.LoggedIn) 48 | { 49 | var user = await graphService.GetMeAsync(state.AccountId); 50 | state.SetUser(user); 51 | } 52 | else 53 | state.SetUser(null); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /BlazorGraphExample/Pages/_Imports.razor: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /BlazorGraphExample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Blazor.Hosting; 2 | 3 | namespace BlazorGraphExample 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) => 8 | CreateHostBuilder(args).Build().Run(); 9 | 10 | public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => 11 | BlazorWebAssemblyHost.CreateDefaultBuilder() 12 | .UseBlazorStartup(); 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BlazorGraphExample/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | FileSystem 9 | FileSystem 10 | Release 11 | Any CPU 12 | 13 | True 14 | False 15 | d5aec440-6134-4869-a3fc-023474b2ba54 16 | bin\Release\netstandard2.0\publish\ 17 | True 18 | netstandard2.0 19 | 20 | -------------------------------------------------------------------------------- /BlazorGraphExample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "https://localhost:44395/", 7 | "sslPort": 44395 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorGraphExample": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:51495/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /BlazorGraphExample/Services/AppState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using W8lessLabs.GraphAPI; 4 | 5 | namespace BlazorGraphExample.Services 6 | { 7 | public class AppState : IPagingState 8 | { 9 | private Dictionary _pageTokens; 10 | private Stack _folders; 11 | 12 | public AppState() 13 | { 14 | _pageTokens = new Dictionary(); 15 | _folders = new Stack(); 16 | SelectedFolderChanged += _ResetPaging; 17 | } 18 | 19 | public LoginStatus LoginStatus { get; private set; } = LoginStatus.Undetermined; 20 | public string AccountId { get; set; } 21 | public GraphUser User { get; private set; } 22 | public IReadOnlyList DriveItems { get; private set; } 23 | public DriveItem SelectedFile { get; private set; } 24 | public DriveItem SelectedFolder { get; private set; } 25 | public int PageSize { get; private set; } = 15; 26 | public int PageCount { get; private set; } = 1; 27 | public int CurrentPage { get; private set; } = 1; 28 | public string Path { get; private set; } 29 | public bool InProgress { get; private set; } 30 | 31 | public event Action LoginStatusChanged; 32 | public event Action AccountIdChanged; 33 | public event Action UserChanged; 34 | public event Action DriveItemsChanged; 35 | public event Action SelectedFileChanged; 36 | public event Action SelectedFolderChanged; 37 | public event Action PageSizeChanged; 38 | public event Action PageCountChanged; 39 | public event Action<(int oldPage, int newPage)> CurrentPageChanged; 40 | //public event Action PathChanged; 41 | public event Action InProgressChanged; 42 | 43 | public event Action LoadProgressChanged; 44 | 45 | public void SetLoginStatus(LoginStatus loginStatus) => 46 | _Set(LoginStatus, loginStatus, LoginStatusChanged, val => LoginStatus = val); 47 | 48 | public void SetAccountId(string accountId) => 49 | _Set(AccountId, accountId, AccountIdChanged, val => AccountId = val); 50 | 51 | public void SetUser(GraphUser user) => 52 | _Set(User, user, UserChanged, val => User = val); 53 | 54 | public void SetDriveItems(IReadOnlyList driveItems) => 55 | _Set>(DriveItems, driveItems, DriveItemsChanged, val => DriveItems = val); 56 | 57 | public void SetSelectedDriveItem(DriveItem driveItem) => 58 | _Set(SelectedFile, driveItem, SelectedFileChanged, val => SelectedFile = val); 59 | 60 | public void SetPageSize(int newPageSize) => 61 | _Set(PageSize, newPageSize, PageSizeChanged, val => PageSize = val); 62 | 63 | public void SetPageCount(int newPageCount) => 64 | _Set(PageCount, newPageCount, PageCountChanged, val => PageCount = val); 65 | 66 | public void SetCurrentPage(int newCurrentPage) 67 | { 68 | if (newCurrentPage != CurrentPage) 69 | { 70 | int oldPage = CurrentPage; 71 | CurrentPage = newCurrentPage; 72 | CurrentPageChanged?.Invoke((oldPage, newCurrentPage)); 73 | } 74 | } 75 | 76 | public void SetPath(string path) => 77 | _Set(Path, path, null, val => Path = val); 78 | 79 | public void SetInProgress(bool inProgress) => 80 | _Set(InProgress, inProgress, InProgressChanged, val => InProgress = val); 81 | 82 | public void FireLoadProgressChanged(int count) => 83 | LoadProgressChanged?.Invoke(count); 84 | 85 | public void PushFolder(DriveItem folder) 86 | { 87 | if(folder?.IsFolder() == true && (_folders.Count == 0 || !_folders.Contains(folder))) 88 | { 89 | _folders.Push(folder); 90 | 91 | string newPath = Path; 92 | string folderName = folder.Name; 93 | 94 | if (IsRootFolder) 95 | newPath = string.Empty; 96 | else 97 | newPath += "/" + folderName; 98 | 99 | SetPath(newPath); 100 | 101 | _Set(SelectedFolder, folder, SelectedFolderChanged, val => SelectedFolder = val); 102 | } 103 | } 104 | 105 | public void PopFolder() 106 | { 107 | if (_folders.Count > 1) 108 | { 109 | _folders.Pop(); 110 | 111 | if (!string.IsNullOrEmpty(Path) && Path != "/") 112 | { 113 | int index = Path.LastIndexOf('/'); 114 | if (index == 0) 115 | SetPath(string.Empty); 116 | if (index != -1) 117 | SetPath(Path.Substring(0, index)); 118 | } 119 | 120 | _Set(SelectedFolder, _folders.Peek(), SelectedFolderChanged, val => SelectedFolder = val); 121 | } 122 | } 123 | 124 | public bool IsRootFolder => _folders.Count < 2; 125 | 126 | public void SelectFile(DriveItem item) 127 | { 128 | _Set(SelectedFile, item, SelectedFileChanged, val => SelectedFile = val); 129 | } 130 | 131 | private void _ResetPaging() 132 | { 133 | _pageTokens.Clear(); 134 | SetCurrentPage(1); 135 | SetPageCount(1); 136 | } 137 | 138 | public void SetPageCount(int totalItemCount, int pageSize) 139 | { 140 | if (totalItemCount > 1 && pageSize > 0) 141 | SetPageCount((int)Math.Ceiling((double)totalItemCount / pageSize)); 142 | else 143 | SetPageCount(1); 144 | } 145 | 146 | public void PreviousPage() 147 | { 148 | if (CurrentPage > 1) 149 | SetCurrentPage(CurrentPage - 1); 150 | } 151 | 152 | public void NextPage() 153 | { 154 | if (CurrentPage < PageCount) 155 | SetCurrentPage(CurrentPage + 1); 156 | } 157 | 158 | public void SetPageToken(int pageNumber, string skipToken) => 159 | _pageTokens[pageNumber] = skipToken; 160 | 161 | public bool TryGetPageToken(int pageNumber, out string skipToken) => 162 | _pageTokens.TryGetValue(pageNumber, out skipToken); 163 | 164 | public bool HasPages() => PageCount > 1; 165 | 166 | private bool _Set(T existing, T updated, Action changeEvent, Action setter) 167 | { 168 | if (!EqualityComparer.Default.Equals(existing, updated)) 169 | { 170 | setter((T)updated); 171 | changeEvent?.Invoke(); 172 | return true; 173 | } 174 | return false; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /BlazorGraphExample/Services/AuthService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | using System; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using W8lessLabs.GraphAPI; 6 | 7 | namespace BlazorGraphExample.Services 8 | { 9 | public class AuthService : IAuthTokenProvider 10 | { 11 | private class MSALAuthConfig 12 | { 13 | public string ClientId { get; set; } 14 | public string[] Scopes { get; set; } 15 | } 16 | 17 | private readonly MSALAuthConfig _config; 18 | private IJSInProcessRuntime _jsRuntime; 19 | 20 | public AuthService(AuthConfig config, IJSRuntime jsRuntime) 21 | { 22 | _config = new MSALAuthConfig() 23 | { 24 | ClientId = config.ClientId, 25 | Scopes = config.Scopes.ToArray() 26 | }; 27 | _jsRuntime = (IJSInProcessRuntime)jsRuntime; 28 | } 29 | 30 | public async Task GetUserAccountAsync() => 31 | await _jsRuntime.InvokeAsync("getUserAccount", _config); 32 | 33 | public bool IsLoggedIn(GraphAccount account) => 34 | (account != null && account.Expires > DateTime.Now); 35 | 36 | public async Task<(GraphTokenResult token, GraphAccount account)> LoginAsync() 37 | { 38 | var tokenResult = await GetTokenAsync(); 39 | return (tokenResult, await GetUserAccountAsync()); 40 | } 41 | 42 | public async Task LogoutAsync() => 43 | await _jsRuntime.InvokeAsync("logout", _config); 44 | 45 | public async Task GetTokenAsync(string accountId = null, bool forceRefresh = false) 46 | { 47 | try 48 | { 49 | var result = await _jsRuntime.InvokeAsync("getTokenAsync", _config); 50 | return new GraphTokenResult(result?.IdToken != null, result.IdToken, result.Expires); 51 | } 52 | catch (Exception) 53 | { 54 | return GraphTokenResult.Failed; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BlazorGraphExample/Services/GraphAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BlazorGraphExample.Services 4 | { 5 | public class GraphAccount 6 | { 7 | public GraphAccount() { } 8 | 9 | public GraphAccount( 10 | string accountId, 11 | string accountName, 12 | DateTime expires, 13 | string tenantId, 14 | string identityProvider, 15 | string azureADObjectId) 16 | { 17 | AccountId = accountId; 18 | AccountName = accountName; 19 | Expires = expires; 20 | TenantId = tenantId; 21 | IdentityProvider = identityProvider; 22 | AzureADObjectId = azureADObjectId; 23 | } 24 | 25 | 26 | public string AccountId { get; set; } 27 | public string AccountName { get; set; } 28 | public DateTime Expires { get; set; } 29 | public string TenantId { get; set; } 30 | public string IdentityProvider { get; set; } 31 | public string AzureADObjectId { get; set; } 32 | 33 | public void Deconstruct( 34 | out string accountId, 35 | out string accountName, 36 | out DateTime expires, 37 | out string tenantId, 38 | out string identityProvider, 39 | out string azureADObjectId) 40 | { 41 | accountId = AccountId; 42 | accountName = AccountName; 43 | expires = Expires; 44 | tenantId = TenantId; 45 | identityProvider = IdentityProvider; 46 | azureADObjectId = AzureADObjectId; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BlazorGraphExample/Services/IPagingState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BlazorGraphExample.Services 4 | { 5 | public interface IPagingState 6 | { 7 | event Action PageCountChanged; 8 | event Action<(int oldPage, int newPage)> CurrentPageChanged; 9 | 10 | bool HasPages(); 11 | int CurrentPage { get; } 12 | int PageCount { get; } 13 | void NextPage(); 14 | void PreviousPage(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlazorGraphExample/Services/Json/CamelCase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.JSInterop 7 | { 8 | internal static class CamelCase 9 | { 10 | public static string MemberNameToCamelCase(string value) 11 | { 12 | if (string.IsNullOrEmpty(value)) 13 | { 14 | throw new ArgumentException( 15 | $"The value '{value ?? "null"}' is not a valid member name.", 16 | nameof(value)); 17 | } 18 | 19 | // If we don't need to modify the value, bail out without creating a char array 20 | if (!char.IsUpper(value[0])) 21 | { 22 | return value; 23 | } 24 | 25 | // We have to modify at least one character 26 | var chars = value.ToCharArray(); 27 | 28 | var length = chars.Length; 29 | if (length < 2 || !char.IsUpper(chars[1])) 30 | { 31 | // Only the first character needs to be modified 32 | // Note that this branch is functionally necessary, because the 'else' branch below 33 | // never looks at char[1]. It's always looking at the n+2 character. 34 | chars[0] = char.ToLowerInvariant(chars[0]); 35 | } 36 | else 37 | { 38 | // If chars[0] and chars[1] are both upper, then we'll lowercase the first char plus 39 | // any consecutive uppercase ones, stopping if we find any char that is followed by a 40 | // non-uppercase one 41 | var i = 0; 42 | while (i < length) 43 | { 44 | chars[i] = char.ToLowerInvariant(chars[i]); 45 | 46 | i++; 47 | 48 | // If the next-plus-one char isn't also uppercase, then we're now on the last uppercase, so stop 49 | if (i < length - 1 && !char.IsUpper(chars[i + 1])) 50 | { 51 | break; 52 | } 53 | } 54 | } 55 | 56 | return new string(chars); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /BlazorGraphExample/Services/Json/JsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using W8lessLabs.GraphAPI; 2 | 3 | namespace BlazorGraphExample.Services 4 | { 5 | public class JsonSerializer : IJsonSerializer 6 | { 7 | static SimpleJson.IJsonSerializerStrategy _Strategy = new SimpleJson.DataContractJsonSerializerStrategy(); 8 | 9 | public T Deserialize(string value) => SimpleJson.SimpleJson.DeserializeObject(value, _Strategy); 10 | public string Serialize(T value) => SimpleJson.SimpleJson.SerializeObject(value, _Strategy); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BlazorGraphExample/Services/Json/README.txt: -------------------------------------------------------------------------------- 1 | SimpleJson is from https://github.com/facebook-csharp-sdk/simple-json 2 | Copied from Blazor project at: https://github.com/aspnet/Blazor/blob/e1bbf087b57db5f1ef59ebd78c10795979441d62/src/Microsoft.JSInterop/Json/SimpleJson/SimpleJson.cs 3 | 4 | CHANGES MADE 5 | ============ 6 | * Better handling of System.DateTime serialized by Json.NET 7 | as suggested in https://github.com/facebook-csharp-sdk/simple-json/issues/78 8 | 9 | LICENSE (from https://github.com/facebook-csharp-sdk/simple-json/blob/08b6871e8f63e866810d25e7a03c48502c9a234b/LICENSE.txt): 10 | ===== 11 | Copyright (c) 2011, The Outercurve Foundation 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining 14 | a copy of this software and associated documentation files (the 15 | "Software"), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 28 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 29 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 30 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | 33 | CamelCase.cs is from https://raw.githubusercontent.com/aspnet/Blazor/e1bbf087b57db5f1ef59ebd78c10795979441d62/src/Microsoft.JSInterop/Json/CamelCase.cs 34 | 35 | NO CHANGES MADE 36 | ============ 37 | 38 | LICENSE (from https://github.com/aspnet/Blazor/blob/a51a3e60bf15ce5d92bbf392091c68398e978b02/LICENSE.txt) 39 | ============ 40 | 41 | Copyright (c) .NET Foundation and Contributors 42 | 43 | All rights reserved. 44 | 45 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 46 | this file except in compliance with the License. You may obtain a copy of the 47 | License at 48 | 49 | http://www.apache.org/licenses/LICENSE-2.0 50 | 51 | Unless required by applicable law or agreed to in writing, software distributed 52 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 53 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 54 | specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /BlazorGraphExample/Services/LoginStatus.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorGraphExample.Services 2 | { 3 | public enum LoginStatus 4 | { 5 | Undetermined = 0, 6 | LoggedOut = 1, 7 | LoggedIn = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BlazorGraphExample/Services/PageChangedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorGraphExample.Services 2 | { 3 | public struct PageChangedEvent 4 | { 5 | public int OldPage; 6 | public int NewPage; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BlazorGraphExample/Services/TokenResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BlazorGraphExample.Services 4 | { 5 | public class TokenResult 6 | { 7 | public string IdToken { get; set; } 8 | public DateTime Expires { get; set; } 9 | public string AccountId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BlazorGraphExample/Shared/Format.cs: -------------------------------------------------------------------------------- 1 | using ByteSizeLib; 2 | 3 | namespace BlazorGraphExample 4 | { 5 | public static class Format 6 | { 7 | public static string FormatFileSize(long size) 8 | { 9 | var byteSize = ByteSize.FromBytes(size); 10 | if (size > 1_000_000) 11 | return byteSize.ToString("MB"); 12 | else 13 | return byteSize.ToString("KB"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlazorGraphExample/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 |
5 | @Body 6 |
7 |
8 | -------------------------------------------------------------------------------- /BlazorGraphExample/Startup.cs: -------------------------------------------------------------------------------- 1 | using BlazorGraphExample.Services; 2 | using Microsoft.AspNetCore.Components.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using W8lessLabs.GraphAPI; 5 | 6 | namespace BlazorGraphExample 7 | { 8 | 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | var appState = new AppState(); 14 | services.AddSingleton(appState); 15 | services.AddSingleton(appState); 16 | 17 | var authConfig = new AuthConfig( 18 | clientId: "CLIENT ID HERE", 19 | scopes: new[] { "https://graph.microsoft.com/user.read https://graph.microsoft.com/files.read" } 20 | ); 21 | services.AddSingleton(authConfig); 22 | services.AddSingleton(); // Used by HttpService 23 | services.AddSingleton(); // Used by GraphService 24 | services.AddSingleton(); 25 | services.AddSingleton(); // Used by GraphService 26 | services.AddSingleton(); 27 | } 28 | 29 | public void Configure(IComponentsApplicationBuilder app) 30 | { 31 | app.AddComponent("app"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BlazorGraphExample/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Linq 3 | @using Microsoft.AspNetCore.Components.Routing 4 | @using BlazorGraphExample 5 | @using BlazorGraphExample.Components 6 | @using BlazorGraphExample.Shared 7 | @using BlazorGraphExample.Services 8 | @using W8lessLabs.GraphAPI -------------------------------------------------------------------------------- /BlazorGraphExample/bundleconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFileName": "wwwroot/css/bulma/bulma.min.css", 4 | "inputFiles": [ 5 | "node_modules/bulma/css/bulma.min.css", 6 | "node_modules/bulma-pageloader/dist/css/bulma-pageloader.min.css", 7 | "node_modules/bulma-tooltip/dist/css/bulma-tooltip.min.css" 8 | ] 9 | }, 10 | //{ 11 | // "outputFileName": "wwwroot/css/open-iconic/font/css/open-iconic.min.css", 12 | // "inputFiles": [ 13 | // "node_modules/open-iconic/font/css/open-iconic.min.css" 14 | // ], 15 | // "minify": { "enabled": false } 16 | //}, 17 | { 18 | "outputFileName": "wwwroot/scripts/msal/msal.min.js", 19 | "inputFiles": [ 20 | "node_modules/msal/dist/msal.min.js" 21 | ] 22 | } 23 | ] -------------------------------------------------------------------------------- /BlazorGraphExample/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myproject", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bulma": { 8 | "version": "0.7.5", 9 | "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz", 10 | "integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw==", 11 | "dev": true 12 | }, 13 | "bulma-pageloader": { 14 | "version": "2.1.0", 15 | "resolved": "https://registry.npmjs.org/bulma-pageloader/-/bulma-pageloader-2.1.0.tgz", 16 | "integrity": "sha512-yI1SBxeGfj57BYtN8tIcvaGlfQgxylPwHHaDNSYLhF+1EVwUjWz97Ufga+8VOv9PZuhhnD05rb8nZ+p0isWw9A==", 17 | "dev": true 18 | }, 19 | "bulma-tooltip": { 20 | "version": "2.0.2", 21 | "resolved": "https://registry.npmjs.org/bulma-tooltip/-/bulma-tooltip-2.0.2.tgz", 22 | "integrity": "sha512-xsqWeWV7tsUn3uH04SqJeP7/CyC1RaDVIyVzr4/sIO3friIIOi7L6jc5g7qUwDxuBQl72yH/yRPuefpXoQ4hWg==", 23 | "dev": true 24 | }, 25 | "msal": { 26 | "version": "1.1.1", 27 | "resolved": "https://registry.npmjs.org/msal/-/msal-1.1.1.tgz", 28 | "integrity": "sha512-vPyMqFChZU3MDYrrbeKaI9xQe+LOdH2RhK2STi38vX5jVNefIiotNGbjrsyOqmrE8AWSTAP1mr8FZwEeJ6JIhg==", 29 | "dev": true, 30 | "requires": { 31 | "tslib": "^1.9.3", 32 | "uuid": "^3.3.2" 33 | } 34 | }, 35 | "open-iconic": { 36 | "version": "1.1.1", 37 | "resolved": "https://registry.npmjs.org/open-iconic/-/open-iconic-1.1.1.tgz", 38 | "integrity": "sha1-nc/Ix808Yc20ojaxo0eJTJetwMY=", 39 | "dev": true 40 | }, 41 | "tslib": { 42 | "version": "1.10.0", 43 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 44 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", 45 | "dev": true 46 | }, 47 | "uuid": { 48 | "version": "3.3.2", 49 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 50 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 51 | "dev": true 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BlazorGraphExample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myproject", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "bulma": "^0.7.5", 6 | "bulma-pageloader": "2.1.0", 7 | "bulma-tooltip": "2.0.2", 8 | "msal": "1.1.1", 9 | "open-iconic": "^1.1.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/font/css/open-iconic.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi[data-glyph].oi-text-replace{font-size:0;line-height:0}.oi[data-glyph].oi-text-replace:before{width:1em;text-align:center}.oi[data-glyph]:before{font-family:Icons;display:inline-block;speak:none;line-height:1;vertical-align:baseline;font-weight:400;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi[data-glyph]:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi[data-glyph].oi-align-left:before{text-align:left}.oi[data-glyph].oi-align-right:before{text-align:right}.oi[data-glyph].oi-align-center:before{text-align:center}.oi[data-glyph].oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi[data-glyph].oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi[data-glyph].oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi[data-glyph=account-login]:before{content:'\e000'}.oi[data-glyph=account-logout]:before{content:'\e001'}.oi[data-glyph=action-redo]:before{content:'\e002'}.oi[data-glyph=action-undo]:before{content:'\e003'}.oi[data-glyph=align-center]:before{content:'\e004'}.oi[data-glyph=align-left]:before{content:'\e005'}.oi[data-glyph=align-right]:before{content:'\e006'}.oi[data-glyph=aperture]:before{content:'\e007'}.oi[data-glyph=arrow-bottom]:before{content:'\e008'}.oi[data-glyph=arrow-circle-bottom]:before{content:'\e009'}.oi[data-glyph=arrow-circle-left]:before{content:'\e00a'}.oi[data-glyph=arrow-circle-right]:before{content:'\e00b'}.oi[data-glyph=arrow-circle-top]:before{content:'\e00c'}.oi[data-glyph=arrow-left]:before{content:'\e00d'}.oi[data-glyph=arrow-right]:before{content:'\e00e'}.oi[data-glyph=arrow-thick-bottom]:before{content:'\e00f'}.oi[data-glyph=arrow-thick-left]:before{content:'\e010'}.oi[data-glyph=arrow-thick-right]:before{content:'\e011'}.oi[data-glyph=arrow-thick-top]:before{content:'\e012'}.oi[data-glyph=arrow-top]:before{content:'\e013'}.oi[data-glyph=audio-spectrum]:before{content:'\e014'}.oi[data-glyph=audio]:before{content:'\e015'}.oi[data-glyph=badge]:before{content:'\e016'}.oi[data-glyph=ban]:before{content:'\e017'}.oi[data-glyph=bar-chart]:before{content:'\e018'}.oi[data-glyph=basket]:before{content:'\e019'}.oi[data-glyph=battery-empty]:before{content:'\e01a'}.oi[data-glyph=battery-full]:before{content:'\e01b'}.oi[data-glyph=beaker]:before{content:'\e01c'}.oi[data-glyph=bell]:before{content:'\e01d'}.oi[data-glyph=bluetooth]:before{content:'\e01e'}.oi[data-glyph=bold]:before{content:'\e01f'}.oi[data-glyph=bolt]:before{content:'\e020'}.oi[data-glyph=book]:before{content:'\e021'}.oi[data-glyph=bookmark]:before{content:'\e022'}.oi[data-glyph=box]:before{content:'\e023'}.oi[data-glyph=briefcase]:before{content:'\e024'}.oi[data-glyph=british-pound]:before{content:'\e025'}.oi[data-glyph=browser]:before{content:'\e026'}.oi[data-glyph=brush]:before{content:'\e027'}.oi[data-glyph=bug]:before{content:'\e028'}.oi[data-glyph=bullhorn]:before{content:'\e029'}.oi[data-glyph=calculator]:before{content:'\e02a'}.oi[data-glyph=calendar]:before{content:'\e02b'}.oi[data-glyph=camera-slr]:before{content:'\e02c'}.oi[data-glyph=caret-bottom]:before{content:'\e02d'}.oi[data-glyph=caret-left]:before{content:'\e02e'}.oi[data-glyph=caret-right]:before{content:'\e02f'}.oi[data-glyph=caret-top]:before{content:'\e030'}.oi[data-glyph=cart]:before{content:'\e031'}.oi[data-glyph=chat]:before{content:'\e032'}.oi[data-glyph=check]:before{content:'\e033'}.oi[data-glyph=chevron-bottom]:before{content:'\e034'}.oi[data-glyph=chevron-left]:before{content:'\e035'}.oi[data-glyph=chevron-right]:before{content:'\e036'}.oi[data-glyph=chevron-top]:before{content:'\e037'}.oi[data-glyph=circle-check]:before{content:'\e038'}.oi[data-glyph=circle-x]:before{content:'\e039'}.oi[data-glyph=clipboard]:before{content:'\e03a'}.oi[data-glyph=clock]:before{content:'\e03b'}.oi[data-glyph=cloud-download]:before{content:'\e03c'}.oi[data-glyph=cloud-upload]:before{content:'\e03d'}.oi[data-glyph=cloud]:before{content:'\e03e'}.oi[data-glyph=cloudy]:before{content:'\e03f'}.oi[data-glyph=code]:before{content:'\e040'}.oi[data-glyph=cog]:before{content:'\e041'}.oi[data-glyph=collapse-down]:before{content:'\e042'}.oi[data-glyph=collapse-left]:before{content:'\e043'}.oi[data-glyph=collapse-right]:before{content:'\e044'}.oi[data-glyph=collapse-up]:before{content:'\e045'}.oi[data-glyph=command]:before{content:'\e046'}.oi[data-glyph=comment-square]:before{content:'\e047'}.oi[data-glyph=compass]:before{content:'\e048'}.oi[data-glyph=contrast]:before{content:'\e049'}.oi[data-glyph=copywriting]:before{content:'\e04a'}.oi[data-glyph=credit-card]:before{content:'\e04b'}.oi[data-glyph=crop]:before{content:'\e04c'}.oi[data-glyph=dashboard]:before{content:'\e04d'}.oi[data-glyph=data-transfer-download]:before{content:'\e04e'}.oi[data-glyph=data-transfer-upload]:before{content:'\e04f'}.oi[data-glyph=delete]:before{content:'\e050'}.oi[data-glyph=dial]:before{content:'\e051'}.oi[data-glyph=document]:before{content:'\e052'}.oi[data-glyph=dollar]:before{content:'\e053'}.oi[data-glyph=double-quote-sans-left]:before{content:'\e054'}.oi[data-glyph=double-quote-sans-right]:before{content:'\e055'}.oi[data-glyph=double-quote-serif-left]:before{content:'\e056'}.oi[data-glyph=double-quote-serif-right]:before{content:'\e057'}.oi[data-glyph=droplet]:before{content:'\e058'}.oi[data-glyph=eject]:before{content:'\e059'}.oi[data-glyph=elevator]:before{content:'\e05a'}.oi[data-glyph=ellipses]:before{content:'\e05b'}.oi[data-glyph=envelope-closed]:before{content:'\e05c'}.oi[data-glyph=envelope-open]:before{content:'\e05d'}.oi[data-glyph=euro]:before{content:'\e05e'}.oi[data-glyph=excerpt]:before{content:'\e05f'}.oi[data-glyph=expand-down]:before{content:'\e060'}.oi[data-glyph=expand-left]:before{content:'\e061'}.oi[data-glyph=expand-right]:before{content:'\e062'}.oi[data-glyph=expand-up]:before{content:'\e063'}.oi[data-glyph=external-link]:before{content:'\e064'}.oi[data-glyph=eye]:before{content:'\e065'}.oi[data-glyph=eyedropper]:before{content:'\e066'}.oi[data-glyph=file]:before{content:'\e067'}.oi[data-glyph=fire]:before{content:'\e068'}.oi[data-glyph=flag]:before{content:'\e069'}.oi[data-glyph=flash]:before{content:'\e06a'}.oi[data-glyph=folder]:before{content:'\e06b'}.oi[data-glyph=fork]:before{content:'\e06c'}.oi[data-glyph=fullscreen-enter]:before{content:'\e06d'}.oi[data-glyph=fullscreen-exit]:before{content:'\e06e'}.oi[data-glyph=globe]:before{content:'\e06f'}.oi[data-glyph=graph]:before{content:'\e070'}.oi[data-glyph=grid-four-up]:before{content:'\e071'}.oi[data-glyph=grid-three-up]:before{content:'\e072'}.oi[data-glyph=grid-two-up]:before{content:'\e073'}.oi[data-glyph=hard-drive]:before{content:'\e074'}.oi[data-glyph=header]:before{content:'\e075'}.oi[data-glyph=headphones]:before{content:'\e076'}.oi[data-glyph=heart]:before{content:'\e077'}.oi[data-glyph=home]:before{content:'\e078'}.oi[data-glyph=image]:before{content:'\e079'}.oi[data-glyph=inbox]:before{content:'\e07a'}.oi[data-glyph=infinity]:before{content:'\e07b'}.oi[data-glyph=info]:before{content:'\e07c'}.oi[data-glyph=italic]:before{content:'\e07d'}.oi[data-glyph=justify-center]:before{content:'\e07e'}.oi[data-glyph=justify-left]:before{content:'\e07f'}.oi[data-glyph=justify-right]:before{content:'\e080'}.oi[data-glyph=key]:before{content:'\e081'}.oi[data-glyph=laptop]:before{content:'\e082'}.oi[data-glyph=layers]:before{content:'\e083'}.oi[data-glyph=lightbulb]:before{content:'\e084'}.oi[data-glyph=link-broken]:before{content:'\e085'}.oi[data-glyph=link-intact]:before{content:'\e086'}.oi[data-glyph=list-rich]:before{content:'\e087'}.oi[data-glyph=list]:before{content:'\e088'}.oi[data-glyph=location]:before{content:'\e089'}.oi[data-glyph=lock-locked]:before{content:'\e08a'}.oi[data-glyph=lock-unlocked]:before{content:'\e08b'}.oi[data-glyph=loop-circular]:before{content:'\e08c'}.oi[data-glyph=loop-square]:before{content:'\e08d'}.oi[data-glyph=loop]:before{content:'\e08e'}.oi[data-glyph=magnifying-glass]:before{content:'\e08f'}.oi[data-glyph=map-marker]:before{content:'\e090'}.oi[data-glyph=map]:before{content:'\e091'}.oi[data-glyph=media-pause]:before{content:'\e092'}.oi[data-glyph=media-play]:before{content:'\e093'}.oi[data-glyph=media-record]:before{content:'\e094'}.oi[data-glyph=media-skip-backward]:before{content:'\e095'}.oi[data-glyph=media-skip-forward]:before{content:'\e096'}.oi[data-glyph=media-step-backward]:before{content:'\e097'}.oi[data-glyph=media-step-forward]:before{content:'\e098'}.oi[data-glyph=media-stop]:before{content:'\e099'}.oi[data-glyph=medical-cross]:before{content:'\e09a'}.oi[data-glyph=menu]:before{content:'\e09b'}.oi[data-glyph=microphone]:before{content:'\e09c'}.oi[data-glyph=minus]:before{content:'\e09d'}.oi[data-glyph=monitor]:before{content:'\e09e'}.oi[data-glyph=moon]:before{content:'\e09f'}.oi[data-glyph=move]:before{content:'\e0a0'}.oi[data-glyph=musical-note]:before{content:'\e0a1'}.oi[data-glyph=paperclip]:before{content:'\e0a2'}.oi[data-glyph=pencil]:before{content:'\e0a3'}.oi[data-glyph=people]:before{content:'\e0a4'}.oi[data-glyph=person]:before{content:'\e0a5'}.oi[data-glyph=phone]:before{content:'\e0a6'}.oi[data-glyph=pie-chart]:before{content:'\e0a7'}.oi[data-glyph=pin]:before{content:'\e0a8'}.oi[data-glyph=play-circle]:before{content:'\e0a9'}.oi[data-glyph=plus]:before{content:'\e0aa'}.oi[data-glyph=power-standby]:before{content:'\e0ab'}.oi[data-glyph=print]:before{content:'\e0ac'}.oi[data-glyph=project]:before{content:'\e0ad'}.oi[data-glyph=pulse]:before{content:'\e0ae'}.oi[data-glyph=puzzle-piece]:before{content:'\e0af'}.oi[data-glyph=question-mark]:before{content:'\e0b0'}.oi[data-glyph=rain]:before{content:'\e0b1'}.oi[data-glyph=random]:before{content:'\e0b2'}.oi[data-glyph=reload]:before{content:'\e0b3'}.oi[data-glyph=resize-both]:before{content:'\e0b4'}.oi[data-glyph=resize-height]:before{content:'\e0b5'}.oi[data-glyph=resize-width]:before{content:'\e0b6'}.oi[data-glyph=rss-alt]:before{content:'\e0b7'}.oi[data-glyph=rss]:before{content:'\e0b8'}.oi[data-glyph=script]:before{content:'\e0b9'}.oi[data-glyph=share-boxed]:before{content:'\e0ba'}.oi[data-glyph=share]:before{content:'\e0bb'}.oi[data-glyph=shield]:before{content:'\e0bc'}.oi[data-glyph=signal]:before{content:'\e0bd'}.oi[data-glyph=signpost]:before{content:'\e0be'}.oi[data-glyph=sort-ascending]:before{content:'\e0bf'}.oi[data-glyph=sort-descending]:before{content:'\e0c0'}.oi[data-glyph=spreadsheet]:before{content:'\e0c1'}.oi[data-glyph=star]:before{content:'\e0c2'}.oi[data-glyph=sun]:before{content:'\e0c3'}.oi[data-glyph=tablet]:before{content:'\e0c4'}.oi[data-glyph=tag]:before{content:'\e0c5'}.oi[data-glyph=tags]:before{content:'\e0c6'}.oi[data-glyph=target]:before{content:'\e0c7'}.oi[data-glyph=task]:before{content:'\e0c8'}.oi[data-glyph=terminal]:before{content:'\e0c9'}.oi[data-glyph=text]:before{content:'\e0ca'}.oi[data-glyph=thumb-down]:before{content:'\e0cb'}.oi[data-glyph=thumb-up]:before{content:'\e0cc'}.oi[data-glyph=timer]:before{content:'\e0cd'}.oi[data-glyph=transfer]:before{content:'\e0ce'}.oi[data-glyph=trash]:before{content:'\e0cf'}.oi[data-glyph=underline]:before{content:'\e0d0'}.oi[data-glyph=vertical-align-bottom]:before{content:'\e0d1'}.oi[data-glyph=vertical-align-center]:before{content:'\e0d2'}.oi[data-glyph=vertical-align-top]:before{content:'\e0d3'}.oi[data-glyph=video]:before{content:'\e0d4'}.oi[data-glyph=volume-high]:before{content:'\e0d5'}.oi[data-glyph=volume-low]:before{content:'\e0d6'}.oi[data-glyph=volume-off]:before{content:'\e0d7'}.oi[data-glyph=warning]:before{content:'\e0d8'}.oi[data-glyph=wifi]:before{content:'\e0d9'}.oi[data-glyph=wrench]:before{content:'\e0da'}.oi[data-glyph=x]:before{content:'\e0db'}.oi[data-glyph=yen]:before{content:'\e0dc'}.oi[data-glyph=zoom-in]:before{content:'\e0dd'}.oi[data-glyph=zoom-out]:before{content:'\e0de'} -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jburman/BlazorGraphExample/2f361cf790a13db541dcaf956b406bb4be048dd6/BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jburman/BlazorGraphExample/2f361cf790a13db541dcaf956b406bb4be048dd6/BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 9 | By P.J. Onori 10 | Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) 11 | 12 | 13 | 14 | 27 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 74 | 76 | 79 | 81 | 84 | 86 | 88 | 91 | 93 | 95 | 98 | 100 | 102 | 104 | 106 | 109 | 112 | 115 | 117 | 121 | 123 | 125 | 127 | 130 | 132 | 134 | 136 | 138 | 141 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 157 | 159 | 162 | 165 | 167 | 169 | 172 | 174 | 177 | 179 | 181 | 183 | 185 | 189 | 191 | 194 | 196 | 198 | 200 | 202 | 205 | 207 | 209 | 211 | 213 | 215 | 218 | 220 | 222 | 224 | 226 | 228 | 230 | 232 | 234 | 236 | 238 | 241 | 243 | 245 | 247 | 249 | 251 | 253 | 256 | 259 | 261 | 263 | 265 | 267 | 269 | 272 | 274 | 276 | 280 | 282 | 285 | 287 | 289 | 292 | 295 | 298 | 300 | 302 | 304 | 306 | 309 | 312 | 314 | 316 | 318 | 320 | 322 | 324 | 326 | 330 | 334 | 338 | 340 | 343 | 345 | 347 | 349 | 351 | 353 | 355 | 358 | 360 | 363 | 365 | 367 | 369 | 371 | 373 | 375 | 377 | 379 | 381 | 383 | 386 | 388 | 390 | 392 | 394 | 396 | 399 | 401 | 404 | 406 | 408 | 410 | 412 | 414 | 416 | 419 | 421 | 423 | 425 | 428 | 431 | 435 | 438 | 440 | 442 | 444 | 446 | 448 | 451 | 453 | 455 | 457 | 460 | 462 | 464 | 466 | 468 | 471 | 473 | 477 | 479 | 481 | 483 | 486 | 488 | 490 | 492 | 494 | 496 | 499 | 501 | 504 | 506 | 509 | 512 | 515 | 517 | 520 | 522 | 524 | 526 | 529 | 532 | 534 | 536 | 539 | 542 | 543 | 544 | -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jburman/BlazorGraphExample/2f361cf790a13db541dcaf956b406bb4be048dd6/BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jburman/BlazorGraphExample/2f361cf790a13db541dcaf956b406bb4be048dd6/BlazorGraphExample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | app { 8 | position: relative; 9 | display: flex; 10 | flex-direction: column; 11 | } 12 | 13 | .top-row { 14 | height: 3.5rem; 15 | display: flex; 16 | align-items: center; 17 | } 18 | 19 | .main { 20 | flex: 1; 21 | } 22 | 23 | .main .top-row { 24 | background-color: #e6e6e6; 25 | border-bottom: 1px solid #d6d5d5; 26 | } 27 | 28 | .sidebar { 29 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 30 | } 31 | 32 | .sidebar .top-row { 33 | background-color: rgba(0,0,0,0.4); 34 | } 35 | 36 | .sidebar .navbar-brand { 37 | font-size: 1.1rem; 38 | } 39 | 40 | .sidebar .oi { 41 | width: 2rem; 42 | font-size: 1.1rem; 43 | vertical-align: text-top; 44 | top: -2px; 45 | } 46 | 47 | .nav-item { 48 | font-size: 0.9rem; 49 | padding-bottom: 0.5rem; 50 | } 51 | 52 | .nav-item:first-of-type { 53 | padding-top: 1rem; 54 | } 55 | 56 | .nav-item:last-of-type { 57 | padding-bottom: 1rem; 58 | } 59 | 60 | .nav-item a { 61 | color: #d7d7d7; 62 | border-radius: 4px; 63 | height: 3rem; 64 | display: flex; 65 | align-items: center; 66 | line-height: 3rem; 67 | } 68 | 69 | .nav-item a.active { 70 | background-color: rgba(255,255,255,0.25); 71 | color: white; 72 | } 73 | 74 | .nav-item a:hover { 75 | background-color: rgba(255,255,255,0.1); 76 | color: white; 77 | } 78 | 79 | .content { 80 | padding-top: 1.1rem; 81 | } 82 | 83 | .navbar-toggler { 84 | background-color: rgba(255, 255, 255, 0.1); 85 | } 86 | 87 | @media (max-width: 767.98px) { 88 | .main .top-row { 89 | display: none; 90 | } 91 | } 92 | 93 | @media (min-width: 768px) { 94 | app { 95 | flex-direction: row; 96 | } 97 | 98 | .sidebar { 99 | width: 250px; 100 | height: 100vh; 101 | position: sticky; 102 | top: 0; 103 | } 104 | 105 | .main .top-row { 106 | position: sticky; 107 | top: 0; 108 | } 109 | 110 | .main > div { 111 | padding-left: 2rem !important; 112 | padding-right: 1.5rem !important; 113 | } 114 | 115 | .navbar-toggler { 116 | display: none; 117 | } 118 | 119 | .sidebar .collapse { 120 | /* Never collapse the sidebar for wide screens */ 121 | display: block; 122 | } 123 | } 124 | 125 | /* Look for Bulma replacements */ 126 | .px-3 { 127 | padding-left: 1rem !important; 128 | padding-right: 1rem !important; 129 | } 130 | 131 | .pl-4 { 132 | padding-left: 1.5rem !important; 133 | } 134 | 135 | .ml-md-auto { 136 | margin-left: auto !important; 137 | } 138 | 139 | /* Custom */ 140 | .navbar-brand a { 141 | color: white; 142 | } 143 | 144 | .content ul { 145 | list-style: none; 146 | } 147 | 148 | .panel-block i.oi { 149 | margin-right: 10px; 150 | } 151 | 152 | .folder-files-tag { 153 | margin-left: auto; 154 | } 155 | 156 | .folder-files-count { 157 | min-width: 65px; 158 | } 159 | 160 | .tags-right { 161 | margin-left: auto; 162 | } 163 | 164 | .file-size-tag { 165 | min-width: 105px; 166 | } 167 | 168 | .share-tag { 169 | min-width: 105px; 170 | } 171 | -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | BlazorGraphExample 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/scripts/appload.js: -------------------------------------------------------------------------------- 1 | // MSAL loads the entire app in an iframe after successful login. 2 | // This script is designed to detect the re-load and to then load the minimal 3 | // login script needed inside the iframe to complete the auth call. 4 | if (window !== window.parent && !window.opener) { 5 | // in iframe - just load MSAL script... 6 | if (window.parent.applicationConfig) { 7 | var msalJs = document.createElement('script'); 8 | msalJs.src = 'scripts/msal/msal.min.js'; 9 | msalJs.onload = function () { 10 | var loginJs = document.createElement('script'); 11 | loginJs.src = 'scripts/login.js'; 12 | loginJs.onload = function () { 13 | var url = document.location.href; 14 | initMSAL(window.parent.applicationConfig); 15 | }; 16 | document.body.appendChild(loginJs); 17 | }; 18 | document.body.appendChild(msalJs); 19 | } 20 | } else { 21 | var loadingHtml = '' + 22 | '
' + 23 | '' + 24 | '' + 25 | '' + 26 | '' + 27 | '' + 28 | 'Hang tight!' + 29 | '' + 30 | '' + 31 | '
' + 32 | '
'; 33 | 34 | document.writeln(loadingHtml); 35 | document.writeln(''); 36 | document.writeln(''); 37 | document.writeln(''); 38 | document.writeln(''); 39 | document.writeln(''); 40 | } -------------------------------------------------------------------------------- /BlazorGraphExample/wwwroot/scripts/login.js: -------------------------------------------------------------------------------- 1 | var userAgentApplication = null; 2 | 3 | function initMSAL(applicationConfig) { 4 | if (userAgentApplication === null) { 5 | var authConfig = { 6 | clientId: applicationConfig.clientId, 7 | authority: 'https://login.microsoftonline.com/common', 8 | postLogoutRedirectUri: window.location.href // redirect doesn't seem to work ?? 9 | }; 10 | 11 | userAgentApplication = new Msal.UserAgentApplication({ 12 | auth: authConfig 13 | }); 14 | 15 | userAgentApplication.handleRedirectCallback((error, response) => { 16 | }); 17 | } 18 | window.applicationConfig = applicationConfig; // store for applogin.js to access on callback 19 | } 20 | 21 | function createGraphAccount(user) { 22 | if (user && user.idToken) { 23 | var idToken = user.idToken; 24 | var tenantId = idToken.tid; 25 | var objectId = idToken.oid; 26 | var accountId = objectId + '.' + tenantId; 27 | var accountName = user.idToken.preferred_username; 28 | var expires = new Date(); 29 | expires = new Date(expires.getTime() + idToken.exp); 30 | 31 | var account = { 32 | accountId: accountId, 33 | accountName: accountName, 34 | expires: expires, 35 | identityProvider: user.identityProvider, 36 | azureADObjectId: objectId, 37 | tenantId: tenantId 38 | }; 39 | return account; 40 | } else { 41 | return null; 42 | } 43 | } 44 | 45 | function getUserAccount(applicationConfig) { 46 | initMSAL(applicationConfig); 47 | var user = userAgentApplication.getAccount(); 48 | return createGraphAccount(user); 49 | } 50 | 51 | function doLogin(applicationConfig, success, error) { 52 | initMSAL(applicationConfig); 53 | userAgentApplication.loginPopup(applicationConfig) 54 | .then(function (response) { 55 | success(); 56 | }) 57 | .catch(function (err) { 58 | error(); 59 | }); 60 | return ""; 61 | } 62 | 63 | function getTokenWithRetry(applicationConfig, resolve, reject, retry) { 64 | userAgentApplication.acquireTokenSilent(applicationConfig) 65 | .then(function (token) { 66 | var user = userAgentApplication.getAccount(); 67 | var account = createGraphAccount(user); 68 | var expires = new Date(); 69 | expires = new Date(expires.getTime() + user.idToken.exp); 70 | 71 | resolve({ 72 | idToken: token.accessToken, 73 | expires: expires, 74 | accountId: account.accountId 75 | }); 76 | }).catch(function (error) { 77 | if (retry) { 78 | doLogin(applicationConfig, 79 | function () { 80 | getTokenWithRetry(applicationConfig, resolve, reject, false); 81 | }, 82 | function () { 83 | reject("Unable to acquire token"); 84 | }); 85 | } else { 86 | reject("Unable to acquire token"); 87 | } 88 | }); 89 | } 90 | 91 | function getTokenAsync(applicationConfig) { 92 | initMSAL(applicationConfig); 93 | 94 | return new Promise(function (resolve, reject) { 95 | 96 | getTokenWithRetry(applicationConfig, resolve, reject, true); 97 | }); 98 | } 99 | 100 | function logout(applicationConfig) { 101 | initMSAL(applicationConfig); 102 | userAgentApplication.logout(); 103 | return ""; 104 | } 105 | -------------------------------------------------------------------------------- /Docs/onedrive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jburman/BlazorGraphExample/2f361cf790a13db541dcaf956b406bb4be048dd6/Docs/onedrive.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Jeremy Burman 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | BlazorGraphExample demonstrates a self contained [Blazor](https://blazor.net) application that connects to Microsoft's Graph API, queries the user's information and lets them browse through their OneDrive. There is not any server side code and you can try a [demo](https://blazorgraph.z20.web.core.windows.net/) hosted in an Azure Static Web site. 3 | 4 | **Example** 5 | 6 | 7 | 8 | Authentication calls are handled by the [AuthService](https://github.com/jburman/BlazorGraphExample/blob/master/BlazorGraphExample/Services/AuthService.cs), 9 | which provides a thin wrapper over the [MSAL.js library](https://github.com/AzureAD/microsoft-authentication-library-for-js) 10 | that is designed for SPA applications. Once a token is acquired through MSAL, all of the Graph API calls are made 11 | from the [GraphService](https://github.com/jburman/W8lessLabs.GraphAPI/blob/master/src/W8lessLabs.GraphAPI/Services/GraphService.cs) 12 | using a few hand coded classes. (**Note:** In the latest release of this sample, the Graph API Service code has now been moved out to a separate [NuGet package](https://www.nuget.org/packages/W8lessLabs.GraphAPI/) to make it reusable.) 13 | 14 | To try the sample out yourself: 15 | - Clone the repository 16 | - [Register a new app](https://apps.dev.microsoft.com/) with Microsoft. Be sure to select Web Platform and check Allow Implicit Flow. 17 | - Fill in the Redirect URI (the project uses https://localhost:44395/ by default) 18 | - Copy the Client ID and update Startup.cs to use it. 19 | 20 | You can now either run the project from Visual Studio using IISExpress or with something like dotnet-serve. 21 | 22 | **Run with [dotnet-serve](https://www.nuget.org/packages/dotnet-serve/)** 23 | 24 | 1. From the folder with the BlazorGraphExample.sln file run a dotnet publish. 25 | Example: `dotnet publish -o c:\publish` 26 | 2. Change to the dist folder (for example: c:\publish\BlazorGraphExample\dist) 27 | 3. Launch the index.html: `dotnet-serve -S -p 44395 -o` 28 | --------------------------------------------------------------------------------