diff --git a/1-welcome/README.md b/1-welcome/README.md index 55f0499..13120db 100644 --- a/1-welcome/README.md +++ b/1-welcome/README.md @@ -7,29 +7,29 @@ In this Welcome, we’ll introduce ourselves, give you the lesson rundown, take In this series, you can use your editor of choice or GitHub Codespaces. Here are some options we suggest: - [GitHub Codespaces](https://code.visualstudio.com/docs/remote/codespaces) for an in-browser coding environment - [Visual Studio Code](https://code.visualstudio.com/), with the [C# Dev Kit Extension](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) -- [Visual Studio](https://aka.ms/WebLearningSeries-git-vsDownload) or [Visual Studio for Mac](https://aka.ms/WebLearningSeries-git-vsmacDownload) - check out our [more detailed instructions](/1-welcome/how-to-install-vs.md) for extra help -- [.NET 6 SDK](https://aka.ms/WebLearningSeries-git-dotnetDownload) +- [Visual Studio](https://aka.ms/WebLearningSeries-git-vsDownload) - check out our [more detailed instructions](/1-welcome/how-to-install-vs.md) for extra help +- [.NET SDK](https://dot.net/download) -During the series you will learn the basics of C#, building websites and web APIs, and even how to publish your apps to the cloud. Here is a rundown of what the next few weeks will look like. +During the series you will learn the basics of C#, building websites and web APIs, and even how to publish your apps to the cloud. Here is a rundown of what the next few weeks will look like. ## Who are we? -Let us introduce ourselves. The content in this series is all written by .NET Developer Community Team from Microsoft. More specifically, Jon, James, Jeff, and Katie will be teaching you web development and all things .NET. +Let us introduce ourselves. The content in this series is all written by .NET Developer Community Team from Microsoft. More specifically, Jon, James, Jeff, and Katie will be teaching you web development and all things .NET. ## What to expect - Week 1 – Welcome! -- Week 2 – C# for web development crash course -- Week 3 – We'll build our first pizza website with Razor Pages 🍕 -- Week 4 – Upgrade our pizza website with a backend using Minimal web APIs -- Week 5 – Create a Connect 4 Interactive Web Applications with Blazor -- Week 6 – Connect to the cloud by publishing with Azure +- Week 2 – C# for web development crash course +- Week 3 – We'll build our first pizza website with Razor Pages 🍕 +- Week 4 – Upgrade our pizza website with a backend using Minimal web APIs +- Week 5 – Create a Connect 4 Interactive Web Applications with Blazor +- Week 6 – Connect to the cloud by publishing with Azure To start you on your journey I want to give you a brief overview of what C# and .NET are, and what tools you will need to get going. ## What is .NET? -.NET is a free, cross-platform, open source developer platform for building many different types of applications. This platform is used by companies of all different industries and different sizes. If you’ve ever used Stack Overflow, eaten Chipotle, or received a package delivered by UPS, then you’ve interacted with .NET! +.NET is a free, cross-platform, open source developer platform for building many different types of applications. This platform is used by companies of all different industries and different sizes. If you’ve ever used Stack Overflow, eaten Chipotle, or received a package delivered by UPS, then you’ve interacted with .NET! -With .NET, you can use multiple languages, editors, and libraries to build for web, mobile, desktop, games, IoT, and more! +With .NET, you can use multiple languages, editors, and libraries to build for web, mobile, desktop, games, IoT, and more! ![.NET, a unified development platform](../images/what-is-dotnet.png) -For this series we’ll use ASP.NET Core and Blazor to build web apps! +For this series we’ll use ASP.NET Core and Blazor to build web apps! diff --git a/1-welcome/how-to-install-vs.md b/1-welcome/how-to-install-vs.md index c6e402e..07e18fa 100644 --- a/1-welcome/how-to-install-vs.md +++ b/1-welcome/how-to-install-vs.md @@ -1,6 +1,8 @@ # Install Visual Studio -## If you're on Windows +If you're using Windows for development, we recommend using Visual Studio for .NET development. Alternatively, you can use Visual Studio Code on Windows, macOS, and Linux. + +## To set up Visual Studio on Windows 1. Go to the [Develop .NET applications page](https://visualstudio.microsoft.com/vs/features/net-development/) of the Visual Studio website 1. Find the "Download Visual Studio with .NET" dropdown and select "Community 2022" 1. Run the exe and let Visual Studio download @@ -8,20 +10,15 @@ 1. Sign in to Visual Studio 1. Done ✔️ -## If you're on Mac -1. Go to the [Build Web Apps using .NET Core page](https://visualstudio.microsoft.com/vs/mac/net/) of the Visual Studio website -1. Select the VisualStudioforMacInstaller.dmg to mount the installer, then run it by double-clicking the arrow logo -2. Select **Open** -1. An alert will appear asking you to acknowledge the privacy and license terms. Follow the links to read them, then press **Continue** if you agree -1. The list of available workloads is displayed. Select the .NET Core component. -1. Press **Install** -1. Sign in and choose your keyboard preferences +## To set up Visual Studio Code on Windows, macOS, or Linux +1. Install the [.NET SDK](https://dot.net/download) +1. Install [Visual Studio Code](https://code.visualstudio.com) +1. Install the [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) extension 1. Done ✔️ ## Want more help? If you want more detailed installation instructions, check out the Microsoft Documentation. -* If you're on Windows, go to [Install Visual Studio](https://docs.microsoft.com/visualstudio/install/install-visual-studio?view=vs-2022) -* If you're on Mac, go to [Install Visual Studio for Mac](https://docs.microsoft.com/visualstudio/mac/installation?view=vsmac-2019) +* [Install Visual Studio](https://docs.microsoft.com/visualstudio/install/install-visual-studio?view=vs-2022) +* [Getting Started with C# in VS Code](https://code.visualstudio.com/docs/csharp/get-started) Check out the [Learn to code in Visual Studio](https://visualstudio.microsoft.com/vs/getting-started/) page to learn more about the installation process, how to get started with Visual Studio, and how to make it your own with themes! 🤗 - diff --git a/2-csharp/lesson-2-projects/challenge-project/Final/Final.csproj b/2-csharp/lesson-2-projects/challenge-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-2-projects/challenge-project/Final/Final.csproj +++ b/2-csharp/lesson-2-projects/challenge-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-2-projects/challenge-project/Starter/Starter.csproj b/2-csharp/lesson-2-projects/challenge-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-2-projects/challenge-project/Starter/Starter.csproj +++ b/2-csharp/lesson-2-projects/challenge-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-2-projects/guided-project/Final/Final.csproj b/2-csharp/lesson-2-projects/guided-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-2-projects/guided-project/Final/Final.csproj +++ b/2-csharp/lesson-2-projects/guided-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-2-projects/guided-project/Starter/Starter.csproj b/2-csharp/lesson-2-projects/guided-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-2-projects/guided-project/Starter/Starter.csproj +++ b/2-csharp/lesson-2-projects/guided-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-3-projects/challenge-project/Final/Final.csproj b/2-csharp/lesson-3-projects/challenge-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-3-projects/challenge-project/Final/Final.csproj +++ b/2-csharp/lesson-3-projects/challenge-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-3-projects/challenge-project/Starter/Starter.csproj b/2-csharp/lesson-3-projects/challenge-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-3-projects/challenge-project/Starter/Starter.csproj +++ b/2-csharp/lesson-3-projects/challenge-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-3-projects/guided-project/Final/Final.csproj b/2-csharp/lesson-3-projects/guided-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-3-projects/guided-project/Final/Final.csproj +++ b/2-csharp/lesson-3-projects/guided-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-3-projects/guided-project/Starter/Starter.csproj b/2-csharp/lesson-3-projects/guided-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-3-projects/guided-project/Starter/Starter.csproj +++ b/2-csharp/lesson-3-projects/guided-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-4-projects/challenge-project/Final/Final.csproj b/2-csharp/lesson-4-projects/challenge-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-4-projects/challenge-project/Final/Final.csproj +++ b/2-csharp/lesson-4-projects/challenge-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-4-projects/challenge-project/Starter/Starter.csproj b/2-csharp/lesson-4-projects/challenge-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-4-projects/challenge-project/Starter/Starter.csproj +++ b/2-csharp/lesson-4-projects/challenge-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-4-projects/guided-project/Final/Final.csproj b/2-csharp/lesson-4-projects/guided-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-4-projects/guided-project/Final/Final.csproj +++ b/2-csharp/lesson-4-projects/guided-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-4-projects/guided-project/Starter/Starter.csproj b/2-csharp/lesson-4-projects/guided-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-4-projects/guided-project/Starter/Starter.csproj +++ b/2-csharp/lesson-4-projects/guided-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-5-projects/challenge-project/Final/Final.csproj b/2-csharp/lesson-5-projects/challenge-project/Final/Final.csproj index 74abf5c..91b464a 100644 --- a/2-csharp/lesson-5-projects/challenge-project/Final/Final.csproj +++ b/2-csharp/lesson-5-projects/challenge-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable diff --git a/2-csharp/lesson-5-projects/challenge-project/Starter/Starter.csproj b/2-csharp/lesson-5-projects/challenge-project/Starter/Starter.csproj index 74abf5c..91b464a 100644 --- a/2-csharp/lesson-5-projects/challenge-project/Starter/Starter.csproj +++ b/2-csharp/lesson-5-projects/challenge-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable diff --git a/2-csharp/lesson-6-projects/challenge-project/Final/Final.csproj b/2-csharp/lesson-6-projects/challenge-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-6-projects/challenge-project/Final/Final.csproj +++ b/2-csharp/lesson-6-projects/challenge-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-6-projects/challenge-project/Starter/Starter.csproj b/2-csharp/lesson-6-projects/challenge-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-6-projects/challenge-project/Starter/Starter.csproj +++ b/2-csharp/lesson-6-projects/challenge-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-6-projects/guided-project/Final/Final.csproj b/2-csharp/lesson-6-projects/guided-project/Final/Final.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-6-projects/guided-project/Final/Final.csproj +++ b/2-csharp/lesson-6-projects/guided-project/Final/Final.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/2-csharp/lesson-6-projects/guided-project/Starter/Starter.csproj b/2-csharp/lesson-6-projects/guided-project/Starter/Starter.csproj index f02677b..91b464a 100644 --- a/2-csharp/lesson-6-projects/guided-project/Starter/Starter.csproj +++ b/2-csharp/lesson-6-projects/guided-project/Starter/Starter.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/3-razor-pages/0-start/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj b/3-razor-pages/0-start/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj index c78c9c7..1b28a01 100644 --- a/3-razor-pages/0-start/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj +++ b/3-razor-pages/0-start/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable diff --git a/3-razor-pages/1-complete/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj b/3-razor-pages/1-complete/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj index e0a691c..82fe666 100644 --- a/3-razor-pages/1-complete/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj +++ b/3-razor-pages/1-complete/RazorPagesPizza/RazorPagesPizza/RazorPagesPizza.csproj @@ -1,18 +1,15 @@ - net6.0 + net8.0 enable enable - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + diff --git a/3-razor-pages/README.md b/3-razor-pages/README.md index b672464..41fd143 100644 --- a/3-razor-pages/README.md +++ b/3-razor-pages/README.md @@ -8,7 +8,9 @@ Welcome back! In the last lesson, you got a crash course in C# language fundamen ## Okay, so what are Razor Pages? -There are a few different kinds of web apps out there. If you’re building highly interactive web apps – like a game or in-browser experience – you would probably want to use Blazor. You’ll learn about that in Week 5. But the huge majority of web sites out there run most of their logic on the server - think shopping and commerce websites, web sites for small businesses and portfolios, news sites, etc. And that’s what Razor Pages is really good at. +There are a few different kinds of web apps out there. Many web apps render content and handle requests from the server - think shopping and commerce websites, web sites for small businesses and portfolios, news sites, etc. Other web apps are highly interactive – like a game or in-browser experiences. Razor Pages is a web UI framework for building web apps that run from the server. + +> Note: Starting in .NET 8, Blazor is the preferred approach for building web UI with ASP.NET Core. You can use Blazor's rich component model to build both server rendered web apps and interactive client web apps. We'll learn more about Blazor in Week 5. In Razor Pages applications, you’ll write your logic in a Page Model class, and you’ll write your markup in a Razor file. Razor is a nifty language that blends HTML markup with C# logic, so you can pull in your dynamic information from your Page Model class and display it in Razor. If that sounds complicated, don’t worry, because you’re about to see how easy it is to write a simple Pizza store web app using Razor Pages. @@ -41,7 +43,7 @@ We'll be using Visual Studio 2022 for whole course. If you don't have Visual Stu 1. Select **Next**. -1. In the **Additional information** dialog, select **.NET 6.0 (Long-term support)** and then select **Create**. +1. In the **Additional information** dialog, select **.NET 8.0 (Long-term support)** and then select **Create**. ![Additional information](additional-info.png) diff --git a/4-minimal-api/0-start/PizzaStore/PizzaStore.csproj b/4-minimal-api/0-start/PizzaStore/PizzaStore.csproj index c78c9c7..1b28a01 100644 --- a/4-minimal-api/0-start/PizzaStore/PizzaStore.csproj +++ b/4-minimal-api/0-start/PizzaStore/PizzaStore.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable diff --git a/4-minimal-api/1-complete/PizzaStore/PizzaStore.csproj b/4-minimal-api/1-complete/PizzaStore/PizzaStore.csproj index 2c33b1c..137bb2c 100644 --- a/4-minimal-api/1-complete/PizzaStore/PizzaStore.csproj +++ b/4-minimal-api/1-complete/PizzaStore/PizzaStore.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable diff --git a/5-blazor/0-start/App.razor b/5-blazor/0-start/App.razor deleted file mode 100644 index 6fd3ed1..0000000 --- a/5-blazor/0-start/App.razor +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
diff --git a/5-blazor/0-start/Blazor.ConnectFour.sln b/5-blazor/0-start/Blazor.ConnectFour.sln new file mode 100644 index 0000000..31c463c --- /dev/null +++ b/5-blazor/0-start/Blazor.ConnectFour.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32414.248 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectFour", "ConnectFour\ConnectFour.csproj", "{A39A8B5F-BB76-48CD-BA4E-F0733604EB79}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A39A8B5F-BB76-48CD-BA4E-F0733604EB79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A39A8B5F-BB76-48CD-BA4E-F0733604EB79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A39A8B5F-BB76-48CD-BA4E-F0733604EB79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A39A8B5F-BB76-48CD-BA4E-F0733604EB79}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {19E21953-6482-4D77-B071-6C6EE7515DDB} + EndGlobalSection +EndGlobal diff --git a/5-blazor/0-start/ConnectFour.csproj b/5-blazor/0-start/ConnectFour.csproj deleted file mode 100644 index 697e8fb..0000000 --- a/5-blazor/0-start/ConnectFour.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - - diff --git a/5-blazor/0-start/ConnectFour/Components/App.razor b/5-blazor/0-start/ConnectFour/Components/App.razor new file mode 100644 index 0000000..7827683 --- /dev/null +++ b/5-blazor/0-start/ConnectFour/Components/App.razor @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/5-blazor/0-start/Shared/Board.razor b/5-blazor/0-start/ConnectFour/Components/Board.razor similarity index 100% rename from 5-blazor/0-start/Shared/Board.razor rename to 5-blazor/0-start/ConnectFour/Components/Board.razor diff --git a/5-blazor/1-complete/ConnectFour/Shared/Board.razor.css b/5-blazor/0-start/ConnectFour/Components/Board.razor.css similarity index 100% rename from 5-blazor/1-complete/ConnectFour/Shared/Board.razor.css rename to 5-blazor/0-start/ConnectFour/Components/Board.razor.css diff --git a/5-blazor/1-complete/ConnectFour/Shared/MainLayout.razor b/5-blazor/0-start/ConnectFour/Components/Layout/MainLayout.razor similarity index 53% rename from 5-blazor/1-complete/ConnectFour/Shared/MainLayout.razor rename to 5-blazor/0-start/ConnectFour/Components/Layout/MainLayout.razor index 839b8fe..5a24bb1 100644 --- a/5-blazor/1-complete/ConnectFour/Shared/MainLayout.razor +++ b/5-blazor/0-start/ConnectFour/Components/Layout/MainLayout.razor @@ -7,7 +7,7 @@
- About + About
@@ -15,3 +15,9 @@
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/5-blazor/0-start/Shared/MainLayout.razor.css b/5-blazor/0-start/ConnectFour/Components/Layout/MainLayout.razor.css similarity index 78% rename from 5-blazor/0-start/Shared/MainLayout.razor.css rename to 5-blazor/0-start/ConnectFour/Components/Layout/MainLayout.razor.css index c865427..038baf1 100644 --- a/5-blazor/0-start/Shared/MainLayout.razor.css +++ b/5-blazor/0-start/ConnectFour/Components/Layout/MainLayout.razor.css @@ -37,11 +37,7 @@ main { } @media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { + .top-row { justify-content: space-between; } @@ -79,3 +75,22 @@ main { padding-right: 1.5rem !important; } } + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/5-blazor/0-start/ConnectFour/Components/Layout/NavMenu.razor b/5-blazor/0-start/ConnectFour/Components/Layout/NavMenu.razor new file mode 100644 index 0000000..825b598 --- /dev/null +++ b/5-blazor/0-start/ConnectFour/Components/Layout/NavMenu.razor @@ -0,0 +1,30 @@ + + + + + + diff --git a/5-blazor/0-start/ConnectFour/Components/Layout/NavMenu.razor.css b/5-blazor/0-start/ConnectFour/Components/Layout/NavMenu.razor.css new file mode 100644 index 0000000..4e15395 --- /dev/null +++ b/5-blazor/0-start/ConnectFour/Components/Layout/NavMenu.razor.css @@ -0,0 +1,105 @@ +.navbar-toggler { + appearance: none; + cursor: pointer; + width: 3.5rem; + height: 2.5rem; + color: white; + position: absolute; + top: 0.5rem; + right: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +} + +.navbar-toggler:checked { + background-color: rgba(255, 255, 255, 0.5); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep .nav-link { + color: #d7d7d7; + background: none; + border: none; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + width: 100%; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep .nav-link:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +.nav-scrollable { + display: none; +} + +.navbar-toggler:checked ~ .nav-scrollable { + display: block; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .nav-scrollable { + /* Never collapse the sidebar for wide screens */ + display: block; + + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/5-blazor/0-start/Pages/Counter.razor b/5-blazor/0-start/ConnectFour/Components/Pages/Counter.razor similarity index 91% rename from 5-blazor/0-start/Pages/Counter.razor rename to 5-blazor/0-start/ConnectFour/Components/Pages/Counter.razor index ef23cb3..1a4f8e7 100644 --- a/5-blazor/0-start/Pages/Counter.razor +++ b/5-blazor/0-start/ConnectFour/Components/Pages/Counter.razor @@ -1,4 +1,5 @@ @page "/counter" +@rendermode InteractiveServer Counter diff --git a/5-blazor/0-start/ConnectFour/Components/Pages/Error.razor b/5-blazor/0-start/ConnectFour/Components/Pages/Error.razor new file mode 100644 index 0000000..576cc2d --- /dev/null +++ b/5-blazor/0-start/ConnectFour/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/5-blazor/1-complete/ConnectFour/Pages/Index.razor b/5-blazor/0-start/ConnectFour/Components/Pages/Home.razor similarity index 54% rename from 5-blazor/1-complete/ConnectFour/Pages/Index.razor rename to 5-blazor/0-start/ConnectFour/Components/Pages/Home.razor index 29eff51..dd3400a 100644 --- a/5-blazor/1-complete/ConnectFour/Pages/Index.razor +++ b/5-blazor/0-start/ConnectFour/Components/Pages/Home.razor @@ -2,4 +2,4 @@ Connect Four - + diff --git a/5-blazor/0-start/Pages/FetchData.razor b/5-blazor/0-start/ConnectFour/Components/Pages/Weather.razor similarity index 54% rename from 5-blazor/0-start/Pages/FetchData.razor rename to 5-blazor/0-start/ConnectFour/Components/Pages/Weather.razor index 7d004a5..43a1ecb 100644 --- a/5-blazor/0-start/Pages/FetchData.razor +++ b/5-blazor/0-start/ConnectFour/Components/Pages/Weather.razor @@ -1,11 +1,11 @@ -@page "/fetchdata" -@inject HttpClient Http +@page "/weather" +@attribute [StreamRendering] -Weather forecast +Weather -

Weather forecast

+

Weather

-

This component demonstrates fetching data from the server.

+

This component demonstrates showing data.

@if (forecasts == null) { @@ -41,17 +41,24 @@ else protected override async Task OnInitializedAsync() { - forecasts = await Http.GetFromJsonAsync("sample-data/weather.json"); + // Simulate asynchronous loading to demonstrate streaming rendering + await Task.Delay(500); + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; + forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }).ToArray(); } - public class WeatherForecast + private class WeatherForecast { - public DateTime Date { get; set; } - + public DateOnly Date { get; set; } public int TemperatureC { get; set; } - public string? Summary { get; set; } - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } } diff --git a/5-blazor/0-start/ConnectFour/Components/Routes.razor b/5-blazor/0-start/ConnectFour/Components/Routes.razor new file mode 100644 index 0000000..faa2a8c --- /dev/null +++ b/5-blazor/0-start/ConnectFour/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/5-blazor/0-start/_Imports.razor b/5-blazor/0-start/ConnectFour/Components/_Imports.razor similarity index 76% rename from 5-blazor/0-start/_Imports.razor rename to 5-blazor/0-start/ConnectFour/Components/_Imports.razor index 95923a4..6538591 100644 --- a/5-blazor/0-start/_Imports.razor +++ b/5-blazor/0-start/ConnectFour/Components/_Imports.razor @@ -3,8 +3,8 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop @using ConnectFour -@using ConnectFour.Shared +@using ConnectFour.Components diff --git a/5-blazor/0-start/ConnectFour/ConnectFour.csproj b/5-blazor/0-start/ConnectFour/ConnectFour.csproj new file mode 100644 index 0000000..1b28a01 --- /dev/null +++ b/5-blazor/0-start/ConnectFour/ConnectFour.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/5-blazor/1-complete/ConnectFour/Shared/GameState.cs b/5-blazor/0-start/ConnectFour/GameState.cs similarity index 99% rename from 5-blazor/1-complete/ConnectFour/Shared/GameState.cs rename to 5-blazor/0-start/ConnectFour/GameState.cs index 4698ea3..0a921f9 100644 --- a/5-blazor/1-complete/ConnectFour/Shared/GameState.cs +++ b/5-blazor/0-start/ConnectFour/GameState.cs @@ -1,4 +1,4 @@ -namespace ConnectFour.Shared; +namespace ConnectFour; public class GameState { diff --git a/5-blazor/0-start/ConnectFour/Program.cs b/5-blazor/0-start/ConnectFour/Program.cs new file mode 100644 index 0000000..f712a67 --- /dev/null +++ b/5-blazor/0-start/ConnectFour/Program.cs @@ -0,0 +1,28 @@ +using ConnectFour; +using ConnectFour.Components; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseStaticFiles(); +app.UseAntiforgery(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.Run(); diff --git a/5-blazor/0-start/Properties/launchSettings.json b/5-blazor/0-start/ConnectFour/Properties/launchSettings.json similarity index 84% rename from 5-blazor/0-start/Properties/launchSettings.json rename to 5-blazor/0-start/ConnectFour/Properties/launchSettings.json index d29a83f..75c94c8 100644 --- a/5-blazor/0-start/Properties/launchSettings.json +++ b/5-blazor/0-start/ConnectFour/Properties/launchSettings.json @@ -3,8 +3,8 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:54251", - "sslPort": 44330 + "applicationUrl": "http://localhost:16315", + "sslPort": 44390 } }, "profiles": { @@ -13,7 +13,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7296;http://localhost:5136", + "applicationUrl": "https://localhost:7000;http://localhost:5007", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/app.css b/5-blazor/0-start/ConnectFour/wwwroot/app.css similarity index 80% rename from 5-blazor/1-complete/ConnectFour/wwwroot/css/app.css rename to 5-blazor/0-start/ConnectFour/wwwroot/app.css index 9cd148f..b250c2f 100644 --- a/5-blazor/1-complete/ConnectFour/wwwroot/css/app.css +++ b/5-blazor/0-start/ConnectFour/wwwroot/app.css @@ -1,15 +1,9 @@ -@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); - -html, body { +html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } -h1:focus { - outline: none; -} - a, .btn-link { - color: #0071c1; + color: #006bb7; } .btn-primary { @@ -18,41 +12,30 @@ a, .btn-link { border-color: #1861ac; } +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + .content { padding-top: 1.1rem; } +h1:focus { + outline: none; +} + .valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { - outline: 1px solid red; + outline: 1px solid #e50000; } .validation-message { - color: red; + color: #e50000; } -#blazor-error-ui { - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } - .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; padding: 1rem 1rem 1rem 3.7rem; @@ -62,3 +45,7 @@ a, .btn-link { .blazor-error-boundary::after { content: "An error has occurred." } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} diff --git a/5-blazor/0-start/wwwroot/css/bootstrap/bootstrap.min.css b/5-blazor/0-start/ConnectFour/wwwroot/bootstrap/bootstrap.min.css similarity index 100% rename from 5-blazor/0-start/wwwroot/css/bootstrap/bootstrap.min.css rename to 5-blazor/0-start/ConnectFour/wwwroot/bootstrap/bootstrap.min.css diff --git a/5-blazor/0-start/wwwroot/css/bootstrap/bootstrap.min.css.map b/5-blazor/0-start/ConnectFour/wwwroot/bootstrap/bootstrap.min.css.map similarity index 100% rename from 5-blazor/0-start/wwwroot/css/bootstrap/bootstrap.min.css.map rename to 5-blazor/0-start/ConnectFour/wwwroot/bootstrap/bootstrap.min.css.map diff --git a/5-blazor/0-start/ConnectFour/wwwroot/favicon.png b/5-blazor/0-start/ConnectFour/wwwroot/favicon.png new file mode 100644 index 0000000..8422b59 Binary files /dev/null and b/5-blazor/0-start/ConnectFour/wwwroot/favicon.png differ diff --git a/5-blazor/0-start/Pages/Index.razor b/5-blazor/0-start/Pages/Index.razor deleted file mode 100644 index 6085c4a..0000000 --- a/5-blazor/0-start/Pages/Index.razor +++ /dev/null @@ -1,9 +0,0 @@ -@page "/" - -Index - -

Hello, world!

- -Welcome to your new app. - - diff --git a/5-blazor/0-start/Program.cs b/5-blazor/0-start/Program.cs deleted file mode 100644 index eb2b9fb..0000000 --- a/5-blazor/0-start/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using ConnectFour; - -var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); - -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - -await builder.Build().RunAsync(); diff --git a/5-blazor/0-start/Shared/Board.razor.css b/5-blazor/0-start/Shared/Board.razor.css deleted file mode 100644 index 3e80ca4..0000000 --- a/5-blazor/0-start/Shared/Board.razor.css +++ /dev/null @@ -1,255 +0,0 @@ -div { - position: relative; -} - -nav { - top: 4em; - width: 30em; - display: inline-flex; - flex-direction: row; - margin-left: 10px; -} - - nav span { - width: 4em; - text-align: center; - cursor: pointer; - font-size: 1em; - } - -div.board { - margin-top: 1em; - flex-wrap: wrap; - width: 30em; - height: 24em; - overflow: hidden; - display: inline-flex; - flex-direction: row; - flex-wrap: wrap; - z-index: -5; - row-gap: 0; - pointer-events: none; - border-left: 10px solid var(--board-bg); -} - -span.container { - width: 4em; - height: 4em; - margin: 0; - padding: 4px; - overflow: hidden; - background-color: transparent; - position: relative; - z-index: -2; - pointer-events: none; -} - -.container span { - width: 3.5em; - height: 3.5em; - border-radius: 50%; - box-shadow: 0 0 0 3em var(--board-bg); - left: 0px; - position: absolute; - display: block; - z-index: 5; - pointer-events: none; -} - -.player1, .player2 { - width: 3.5em; - height: 3.5em; - border-radius: 50%; - left: 0px; - top: 0px; - position: absolute; - display: block; - z-index: -8; -} - -.player1 { - background-color: var(--player1); - animation-timing-function: cubic-bezier(.5, 0.05, 1, .5); - animation-iteration-count: 1; - animation-fill-mode: forwards; - box-shadow: 0 0 0 4px var(--player1); -} - -.player2 { - background-color: var(--player2); - animation-timing-function: cubic-bezier(.5, 0.05, 1, .5); - animation-iteration-count: 1; - animation-fill-mode: forwards; - box-shadow: 0 0 0 4px var(--player2); -} - -.col1 { - left: calc( 0em + 9px ); -} - -.col2 { - left: calc( 4em + 9px ); -} - -.col3 { - left: calc( 8em + 9px ); -} - -.col4 { - left: calc( 12em + 9px ); -} - -.col5 { - left: calc( 16em + 9px ); -} - -.col6 { - left: calc( 20em + 9px ); -} - -.col7 { - left: calc( 24em + 9px ); -} - -.drop1 { - animation-duration: 1s; - animation-name: drop1; -} - -.drop2 { - animation-duration: 1.5s; - animation-name: drop2; -} - -.drop3 { - animation-duration: 1.6s; - animation-name: drop3; -} - -.drop4 { - animation-duration: 1.7s; - animation-name: drop4; -} - -.drop5 { - animation-duration: 1.8s; - animation-name: drop5; -} - -.drop6 { - animation-duration: 1.9s; - animation-name: drop6; -} - -@keyframes drop1 { - 75%, 90%, 97%, 100% { - transform: translateY(1.27em); - } - - 80% { - transform: translateY(0.4em); - } - /*-15% */ - 95% { - transform: translateY(0.8em); - } - /* -7% */ - 99% { - transform: translateY(1.0em); - } - /* -3% */ -} - -@keyframes drop2 { - 75%, 90%, 97%, 100% { - transform: translateY(5.27em); - } - - 80% { - transform: translateY(3.8em); - } - /*-15% */ - 95% { - transform: translateY(4.6em); - } - /* -7% */ - 99% { - transform: translateY(4.9em); - } - /* -3% */ -} - -@keyframes drop3 { - 75%, 90%, 97%, 100% { - transform: translateY(9.27em); - } - - 80% { - transform: translateY(7.2em); - } - /*-15% */ - 95% { - transform: translateY(8.3em); - } - /* -7% */ - 99% { - transform: translateY(8.8em); - } - /* -3% */ -} - -@keyframes drop4 { - 75%, 90%, 97%, 100% { - transform: translateY(13.27em); - } - - 80% { - transform: translateY(10.6em); - } - /*-15% */ - 95% { - transform: translateY(12em); - } - /* -7% */ - 99% { - transform: translateY(12.7em); - } - /* -3% */ -} - -@keyframes drop5 { - 75%, 90%, 97%, 100% { - transform: translateY(17.27em); - } - - 80% { - transform: translateY(14em); - } - /*-15% */ - 95% { - transform: translateY(15.7em); - } - /* -7% */ - 99% { - transform: translateY(16.5em); - } - /* -3% */ -} - -@keyframes drop6 { - 75%, 90%, 97%, 100% { - transform: translateY(21.27em); - } - - 80% { - transform: translateY(17.4em); - } - - 95% { - transform: translateY(19.4em); - } - - 99% { - transform: translateY(20.4em); - } -} diff --git a/5-blazor/0-start/Shared/NavMenu.razor b/5-blazor/0-start/Shared/NavMenu.razor deleted file mode 100644 index 24cc790..0000000 --- a/5-blazor/0-start/Shared/NavMenu.razor +++ /dev/null @@ -1,39 +0,0 @@ - - -
- -
- -@code { - private bool collapseNavMenu = true; - - private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; - - private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } -} diff --git a/5-blazor/0-start/Shared/NavMenu.razor.css b/5-blazor/0-start/Shared/NavMenu.razor.css deleted file mode 100644 index acc5f9f..0000000 --- a/5-blazor/0-start/Shared/NavMenu.razor.css +++ /dev/null @@ -1,62 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } -} diff --git a/5-blazor/0-start/Shared/SurveyPrompt.razor b/5-blazor/0-start/Shared/SurveyPrompt.razor deleted file mode 100644 index 962027f..0000000 --- a/5-blazor/0-start/Shared/SurveyPrompt.razor +++ /dev/null @@ -1,16 +0,0 @@ -
- - @Title - - - Please take our - brief survey - - and tell us what you think. -
- -@code { - // Demonstrates how a parent component can supply parameters - [Parameter] - public string? Title { get; set; } -} diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/FONT-LICENSE b/5-blazor/0-start/wwwroot/css/open-iconic/FONT-LICENSE deleted file mode 100644 index a1dc03f..0000000 --- a/5-blazor/0-start/wwwroot/css/open-iconic/FONT-LICENSE +++ /dev/null @@ -1,86 +0,0 @@ -SIL OPEN FONT LICENSE Version 1.1 - -Copyright (c) 2014 Waybury - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/ICON-LICENSE b/5-blazor/0-start/wwwroot/css/open-iconic/ICON-LICENSE deleted file mode 100644 index 2199f4a..0000000 --- a/5-blazor/0-start/wwwroot/css/open-iconic/ICON-LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Waybury - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/README.md b/5-blazor/0-start/wwwroot/css/open-iconic/README.md deleted file mode 100644 index 6b810e4..0000000 --- a/5-blazor/0-start/wwwroot/css/open-iconic/README.md +++ /dev/null @@ -1,114 +0,0 @@ -[Open Iconic v1.1.1](http://useiconic.com/open) -=========== - -### 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) - - - -## What's in Open Iconic? - -* 223 icons designed to be legible down to 8 pixels -* Super-light SVG files - 61.8 for the entire set -* SVG sprite—the modern replacement for icon fonts -* Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats -* Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats -* PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. - - -## Getting Started - -#### 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. - -### General Usage - -#### Using Open Iconic's SVGs - -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). - -``` -icon name -``` - -#### Using Open Iconic's SVG Sprite - -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. - -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.* - -``` - - - -``` - -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. - -``` -.icon { - width: 16px; - height: 16px; -} -``` - -Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. - -``` -.icon-account-login { - fill: #f00; -} -``` - -To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). - -#### Using Open Iconic's Icon Font... - - -##### …with Bootstrap - -You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` - - -``` - -``` - - -``` - -``` - -##### …with Foundation - -You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` - -``` - -``` - - -``` - -``` - -##### …on its own - -You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` - -``` - -``` - -``` - -``` - - -## License - -### Icons - -All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). - -### Fonts - -All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/5-blazor/0-start/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css deleted file mode 100644 index 4664f2e..0000000 --- a/5-blazor/0-start/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css +++ /dev/null @@ -1 +0,0 @@ -@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'} \ No newline at end of file diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.eot deleted file mode 100644 index f98177d..0000000 Binary files a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.eot and /dev/null differ diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.otf deleted file mode 100644 index f6bd684..0000000 Binary files a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.otf and /dev/null differ diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.svg deleted file mode 100644 index 32b2c4e..0000000 --- a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.svg +++ /dev/null @@ -1,543 +0,0 @@ - - - - - -Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 - By P.J. Onori -Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf deleted file mode 100644 index fab6048..0000000 Binary files a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf and /dev/null differ diff --git a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.woff deleted file mode 100644 index f930998..0000000 Binary files a/5-blazor/0-start/wwwroot/css/open-iconic/font/fonts/open-iconic.woff and /dev/null differ diff --git a/5-blazor/0-start/wwwroot/favicon.ico b/5-blazor/0-start/wwwroot/favicon.ico deleted file mode 100644 index 63e859b..0000000 Binary files a/5-blazor/0-start/wwwroot/favicon.ico and /dev/null differ diff --git a/5-blazor/0-start/wwwroot/icon-192.png b/5-blazor/0-start/wwwroot/icon-192.png deleted file mode 100644 index 166f56d..0000000 Binary files a/5-blazor/0-start/wwwroot/icon-192.png and /dev/null differ diff --git a/5-blazor/0-start/wwwroot/index.html b/5-blazor/0-start/wwwroot/index.html deleted file mode 100644 index efb5a49..0000000 --- a/5-blazor/0-start/wwwroot/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - ConnectFour - - - - - - - -
Loading...
- -
- An unhandled error has occurred. - Reload - 🗙 -
- - - - diff --git a/5-blazor/0-start/wwwroot/sample-data/weather.json b/5-blazor/0-start/wwwroot/sample-data/weather.json deleted file mode 100644 index 06463c0..0000000 --- a/5-blazor/0-start/wwwroot/sample-data/weather.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "date": "2018-05-06", - "temperatureC": 1, - "summary": "Freezing" - }, - { - "date": "2018-05-07", - "temperatureC": 14, - "summary": "Bracing" - }, - { - "date": "2018-05-08", - "temperatureC": -13, - "summary": "Freezing" - }, - { - "date": "2018-05-09", - "temperatureC": -16, - "summary": "Balmy" - }, - { - "date": "2018-05-10", - "temperatureC": -2, - "summary": "Chilly" - } -] diff --git a/5-blazor/1-complete/ConnectFour/App.razor b/5-blazor/1-complete/ConnectFour/App.razor deleted file mode 100644 index 6fd3ed1..0000000 --- a/5-blazor/1-complete/ConnectFour/App.razor +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
diff --git a/5-blazor/1-complete/ConnectFour/Components/App.razor b/5-blazor/1-complete/ConnectFour/Components/App.razor new file mode 100644 index 0000000..7827683 --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/App.razor @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/5-blazor/1-complete/ConnectFour/Components/Board.razor b/5-blazor/1-complete/ConnectFour/Components/Board.razor new file mode 100644 index 0000000..a38dac6 --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/Board.razor @@ -0,0 +1,96 @@ +@using System.Drawing +@inject GameState State + + + + + + + +
+ @winnerMessage +
+ @errorMessage + @CurrentTurn +
+ +
+
+ @for (var i = 0; i < 42; i++) + { + + + + } +
+ @for (var i = 0; i < 42; i++) + { + + } +
+ +@code { + private string[] pieces = new string[42]; + private string winnerMessage = string.Empty; + private string errorMessage = string.Empty; + + private string CurrentTurn => (winnerMessage == string.Empty) ? $"Player {State.PlayerTurn}'s Turn" : ""; + private string ResetStyle => (winnerMessage == string.Empty) ? "display: none;" : ""; + + [Parameter] + public Color BoardColor { get; set; } = ColorTranslator.FromHtml("yellow"); + + [Parameter] + public Color Player1Color { get; set; } = ColorTranslator.FromHtml("red"); + + [Parameter] + public Color Player2Color { get; set; } = ColorTranslator.FromHtml("blue"); + + protected override void OnInitialized() + { + State.ResetBoard(); + } + + private void PlayPiece(byte col) + { + errorMessage = string.Empty; + try + { + var player = State.PlayerTurn; + var turn = State.CurrentTurn; + var landingRow = State.PlayPiece(col); + pieces[turn] = $"player{player} col{col} drop{landingRow}"; + } + catch (ArgumentException ex) + { + errorMessage = ex.Message; + } + winnerMessage = State.CheckForWin() switch + { + GameState.WinState.Player1_Wins => "Player 1 Wins!", + GameState.WinState.Player2_Wins => "Player 2 Wins!", + GameState.WinState.Tie => "It's a tie!", + _ => "" + }; + } + + void ResetGame() + { + State.ResetBoard(); + winnerMessage = string.Empty; + errorMessage = string.Empty; + pieces = new string[42]; + } +} diff --git a/5-blazor/1-complete/ConnectFour/Components/Board.razor.css b/5-blazor/1-complete/ConnectFour/Components/Board.razor.css new file mode 100644 index 0000000..3938084 --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/Board.razor.css @@ -0,0 +1,270 @@ +div { + position: relative; +} + +nav { + top: 4em; + width: 30em; + display: inline-flex; + flex-direction: row; + margin-left: 10px; +} + +nav span { + width: 4em; + text-align: center; + cursor: pointer; + font-size: 1em; +} + +div.board { + margin-top: 1em; + flex-wrap: wrap; + width: 30em; + height: 24em; + overflow: hidden; + display: inline-flex; + flex-direction: row; + flex-wrap: wrap; + z-index: -5; + row-gap: 0; + pointer-events: none; + border-left: 10px solid var(--board-bg); +} + +span.container { + width: 4em; + height: 4em; + margin: 0; + padding: 4px; + overflow: hidden; + background-color: transparent; + position: relative; + z-index: -2; + pointer-events: none; +} + +.container span { + width: 3.5em; + height: 3.5em; + border-radius: 50%; + box-shadow: 0 0 0 3em var(--board-bg); + left: 0px; + position: absolute; + display: block; + z-index: 5; + pointer-events: none; +} + +.player1, .player2 { + width: 3.5em; + height: 3.5em; + border-radius: 50%; + left: 0px; + top: 0px; + position: absolute; + display: block; + z-index: -8; +} + +.player1 { + background-color: var(--player1); + animation-timing-function: cubic-bezier(.5, 0.05, 1, .5); + animation-iteration-count: 1; + animation-fill-mode: forwards; + box-shadow: 0 0 0 4px var(--player1); +} + +.player2 { + background-color: var(--player2); + animation-timing-function: cubic-bezier(.5, 0.05, 1, .5); + animation-iteration-count: 1; + animation-fill-mode: forwards; + box-shadow: 0 0 0 4px var(--player2); +} + +.col0 { + left: calc(0em + 9px); +} + +.col1 { + left: calc(4em + 9px); +} + +.col2 { + left: calc(8em + 9px); +} + +.col3 { + left: calc(12em + 9px); +} + +.col4 { + left: calc(16em + 9px); +} + +.col5 { + left: calc(20em + 9px); +} + +.col6 { + left: calc(24em + 9px); +} + +.drop1 { + animation-duration: 1s; + animation-name: drop1; +} + +.drop2 { + animation-duration: 1.5s; + animation-name: drop2; +} + +.drop3 { + animation-duration: 1.6s; + animation-name: drop3; +} + +.drop4 { + animation-duration: 1.7s; + animation-name: drop4; +} + +.drop5 { + animation-duration: 1.8s; + animation-name: drop5; +} + +.drop6 { + animation-duration: 1.9s; + animation-name: drop6; +} + +@keyframes drop1 { + 75%, 90%, 97%, 100% { + transform: translateY(1.27em); + } + + 80% { + transform: translateY(0.4em); + } + + /*-15% */ + 95% { + transform: translateY(0.8em); + } + + /* -7% */ + 99% { + transform: translateY(1.0em); + } + + /* -3% */ +} + +@keyframes drop2 { + 75%, 90%, 97%, 100% { + transform: translateY(5.27em); + } + + 80% { + transform: translateY(3.8em); + } + + /*-15% */ + 95% { + transform: translateY(4.6em); + } + + /* -7% */ + 99% { + transform: translateY(4.9em); + } + + /* -3% */ +} + +@keyframes drop3 { + 75%, 90%, 97%, 100% { + transform: translateY(9.27em); + } + + 80% { + transform: translateY(7.2em); + } + + /*-15% */ + 95% { + transform: translateY(8.3em); + } + + /* -7% */ + 99% { + transform: translateY(8.8em); + } + + /* -3% */ +} + +@keyframes drop4 { + 75%, 90%, 97%, 100% { + transform: translateY(13.27em); + } + + 80% { + transform: translateY(10.6em); + } + + /*-15% */ + 95% { + transform: translateY(12em); + } + + /* -7% */ + 99% { + transform: translateY(12.7em); + } + + /* -3% */ +} + +@keyframes drop5 { + 75%, 90%, 97%, 100% { + transform: translateY(17.27em); + } + + 80% { + transform: translateY(14em); + } + + /*-15% */ + 95% { + transform: translateY(15.7em); + } + + /* -7% */ + 99% { + transform: translateY(16.5em); + } + + /* -3% */ +} + +@keyframes drop6 { + 75%, 90%, 97%, 100% { + transform: translateY(21.27em); + } + + 80% { + transform: translateY(17.4em); + } + + 95% { + transform: translateY(19.4em); + } + + 99% { + transform: translateY(20.4em); + } +} \ No newline at end of file diff --git a/5-blazor/0-start/Shared/MainLayout.razor b/5-blazor/1-complete/ConnectFour/Components/Layout/MainLayout.razor similarity index 53% rename from 5-blazor/0-start/Shared/MainLayout.razor rename to 5-blazor/1-complete/ConnectFour/Components/Layout/MainLayout.razor index 839b8fe..5a24bb1 100644 --- a/5-blazor/0-start/Shared/MainLayout.razor +++ b/5-blazor/1-complete/ConnectFour/Components/Layout/MainLayout.razor @@ -7,7 +7,7 @@
- About + About
@@ -15,3 +15,9 @@
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/5-blazor/1-complete/ConnectFour/Shared/MainLayout.razor.css b/5-blazor/1-complete/ConnectFour/Components/Layout/MainLayout.razor.css similarity index 78% rename from 5-blazor/1-complete/ConnectFour/Shared/MainLayout.razor.css rename to 5-blazor/1-complete/ConnectFour/Components/Layout/MainLayout.razor.css index c865427..038baf1 100644 --- a/5-blazor/1-complete/ConnectFour/Shared/MainLayout.razor.css +++ b/5-blazor/1-complete/ConnectFour/Components/Layout/MainLayout.razor.css @@ -37,11 +37,7 @@ main { } @media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { + .top-row { justify-content: space-between; } @@ -79,3 +75,22 @@ main { padding-right: 1.5rem !important; } } + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/5-blazor/1-complete/ConnectFour/Components/Layout/NavMenu.razor b/5-blazor/1-complete/ConnectFour/Components/Layout/NavMenu.razor new file mode 100644 index 0000000..902677a --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/Layout/NavMenu.razor @@ -0,0 +1,18 @@ + + + + + + diff --git a/5-blazor/1-complete/ConnectFour/Components/Layout/NavMenu.razor.css b/5-blazor/1-complete/ConnectFour/Components/Layout/NavMenu.razor.css new file mode 100644 index 0000000..4e15395 --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/Layout/NavMenu.razor.css @@ -0,0 +1,105 @@ +.navbar-toggler { + appearance: none; + cursor: pointer; + width: 3.5rem; + height: 2.5rem; + color: white; + position: absolute; + top: 0.5rem; + right: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +} + +.navbar-toggler:checked { + background-color: rgba(255, 255, 255, 0.5); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep .nav-link { + color: #d7d7d7; + background: none; + border: none; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + width: 100%; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep .nav-link:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +.nav-scrollable { + display: none; +} + +.navbar-toggler:checked ~ .nav-scrollable { + display: block; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .nav-scrollable { + /* Never collapse the sidebar for wide screens */ + display: block; + + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/5-blazor/1-complete/ConnectFour/Components/Pages/Error.razor b/5-blazor/1-complete/ConnectFour/Components/Pages/Error.razor new file mode 100644 index 0000000..576cc2d --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/5-blazor/1-complete/ConnectFour/Components/Pages/Home.razor b/5-blazor/1-complete/ConnectFour/Components/Pages/Home.razor new file mode 100644 index 0000000..dd3400a --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/Pages/Home.razor @@ -0,0 +1,5 @@ +@page "/" + +Connect Four + + diff --git a/5-blazor/1-complete/ConnectFour/Components/Routes.razor b/5-blazor/1-complete/ConnectFour/Components/Routes.razor new file mode 100644 index 0000000..faa2a8c --- /dev/null +++ b/5-blazor/1-complete/ConnectFour/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/5-blazor/1-complete/ConnectFour/_Imports.razor b/5-blazor/1-complete/ConnectFour/Components/_Imports.razor similarity index 76% rename from 5-blazor/1-complete/ConnectFour/_Imports.razor rename to 5-blazor/1-complete/ConnectFour/Components/_Imports.razor index 95923a4..6538591 100644 --- a/5-blazor/1-complete/ConnectFour/_Imports.razor +++ b/5-blazor/1-complete/ConnectFour/Components/_Imports.razor @@ -3,8 +3,8 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop @using ConnectFour -@using ConnectFour.Shared +@using ConnectFour.Components diff --git a/5-blazor/1-complete/ConnectFour/ConnectFour.csproj b/5-blazor/1-complete/ConnectFour/ConnectFour.csproj index a200b2a..1b28a01 100644 --- a/5-blazor/1-complete/ConnectFour/ConnectFour.csproj +++ b/5-blazor/1-complete/ConnectFour/ConnectFour.csproj @@ -1,14 +1,9 @@ - + - net6.0 + net8.0 enable enable - - - - - diff --git a/5-blazor/0-start/Shared/GameState.cs b/5-blazor/1-complete/ConnectFour/GameState.cs similarity index 97% rename from 5-blazor/0-start/Shared/GameState.cs rename to 5-blazor/1-complete/ConnectFour/GameState.cs index 3703a3e..0a921f9 100644 --- a/5-blazor/0-start/Shared/GameState.cs +++ b/5-blazor/1-complete/ConnectFour/GameState.cs @@ -1,4 +1,4 @@ -namespace ConnectFour.Shared; +namespace ConnectFour; public class GameState { @@ -185,7 +185,7 @@ public class GameState private byte ConvertLandingSpotToRow(int landingSpot) { - return (byte)(Math.Floor(landingSpot / (decimal)7) + 1); + return (byte)(Math.Floor(landingSpot / (decimal)7) + 1); } diff --git a/5-blazor/1-complete/ConnectFour/Program.cs b/5-blazor/1-complete/ConnectFour/Program.cs index b5c419c..4c6e42a 100644 --- a/5-blazor/1-complete/ConnectFour/Program.cs +++ b/5-blazor/1-complete/ConnectFour/Program.cs @@ -1,12 +1,29 @@ -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using ConnectFour; +using ConnectFour.Components; -var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); +var builder = WebApplication.CreateBuilder(args); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -builder.Services.AddSingleton(); +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); +builder.Services.AddSingleton(); -await builder.Build().RunAsync(); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseStaticFiles(); +app.UseAntiforgery(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.Run(); diff --git a/5-blazor/1-complete/ConnectFour/Shared/Board.razor b/5-blazor/1-complete/ConnectFour/Shared/Board.razor deleted file mode 100644 index e1c600f..0000000 --- a/5-blazor/1-complete/ConnectFour/Shared/Board.razor +++ /dev/null @@ -1,115 +0,0 @@ -@inject GameState State -@using System.Drawing - - - - - - - -
- @WinnerMessage -
- @ErrorMessage - @CurrentTurn -
- -
- -
- - - @for (var i = 0; i < 42; i++) - { - - } - -
- - @for (var i = 0; i < 42; i++) - { - - } - - -
-@code { - - private string[] Pieces = new string[42]; - - private string WinnerMessage = string.Empty; - - private string ErrorMessage = string.Empty; - - private string CurrentTurn => (WinnerMessage == string.Empty) ? $"Player {State.PlayerTurn}'s Turn" : ""; - - private string ResetStyle => (WinnerMessage == string.Empty) ? "display: none;" : ""; - - [Parameter()] - public Color BoardColor { get; set; } - = ColorTranslator.FromHtml("yellow"); - - [Parameter()] - public Color Player1Color { get; set; } - = ColorTranslator.FromHtml("red"); - - [Parameter()] - public Color Player2Color { get; set; } - = ColorTranslator.FromHtml("blue"); - - protected override void OnInitialized() - { - - State.ResetBoard(); - - } - - private void PlayPiece(byte col) - { - - ErrorMessage = string.Empty; - - try - { - var landingRow = State.PlayPiece(col); - var cssClass = $"player{State.PlayerTurn} col{col} drop{landingRow}"; - Pieces[State.CurrentTurn - 1] = cssClass; - } - catch (ArgumentException ex) - { - ErrorMessage = ex.Message; - } - - WinnerMessage = State.CheckForWin() switch - { - GameState.WinState.Player1_Wins => "Player 1 Wins!", - GameState.WinState.Player2_Wins => "Player 2 Wins!", - GameState.WinState.Tie => "It's a tie!", - _ => "" - }; - - } - - void ResetGame() - { - State.ResetBoard(); - WinnerMessage = string.Empty; - ErrorMessage = string.Empty; - Pieces = new string[42]; - } - -} diff --git a/5-blazor/1-complete/ConnectFour/Shared/NavMenu.razor b/5-blazor/1-complete/ConnectFour/Shared/NavMenu.razor deleted file mode 100644 index 18315ba..0000000 --- a/5-blazor/1-complete/ConnectFour/Shared/NavMenu.razor +++ /dev/null @@ -1,29 +0,0 @@ - - -
- -
- -@code { - private bool collapseNavMenu = true; - - private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; - - private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } -} diff --git a/5-blazor/1-complete/ConnectFour/Shared/NavMenu.razor.css b/5-blazor/1-complete/ConnectFour/Shared/NavMenu.razor.css deleted file mode 100644 index acc5f9f..0000000 --- a/5-blazor/1-complete/ConnectFour/Shared/NavMenu.razor.css +++ /dev/null @@ -1,62 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } -} diff --git a/5-blazor/0-start/wwwroot/css/app.css b/5-blazor/1-complete/ConnectFour/wwwroot/app.css similarity index 80% rename from 5-blazor/0-start/wwwroot/css/app.css rename to 5-blazor/1-complete/ConnectFour/wwwroot/app.css index 9cd148f..b250c2f 100644 --- a/5-blazor/0-start/wwwroot/css/app.css +++ b/5-blazor/1-complete/ConnectFour/wwwroot/app.css @@ -1,15 +1,9 @@ -@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); - -html, body { +html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } -h1:focus { - outline: none; -} - a, .btn-link { - color: #0071c1; + color: #006bb7; } .btn-primary { @@ -18,41 +12,30 @@ a, .btn-link { border-color: #1861ac; } +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + .content { padding-top: 1.1rem; } +h1:focus { + outline: none; +} + .valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { - outline: 1px solid red; + outline: 1px solid #e50000; } .validation-message { - color: red; + color: #e50000; } -#blazor-error-ui { - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } - .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; padding: 1rem 1rem 1rem 3.7rem; @@ -62,3 +45,7 @@ a, .btn-link { .blazor-error-boundary::after { content: "An error has occurred." } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/bootstrap/bootstrap.min.css b/5-blazor/1-complete/ConnectFour/wwwroot/bootstrap/bootstrap.min.css similarity index 100% rename from 5-blazor/1-complete/ConnectFour/wwwroot/css/bootstrap/bootstrap.min.css rename to 5-blazor/1-complete/ConnectFour/wwwroot/bootstrap/bootstrap.min.css diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/bootstrap/bootstrap.min.css.map b/5-blazor/1-complete/ConnectFour/wwwroot/bootstrap/bootstrap.min.css.map similarity index 100% rename from 5-blazor/1-complete/ConnectFour/wwwroot/css/bootstrap/bootstrap.min.css.map rename to 5-blazor/1-complete/ConnectFour/wwwroot/bootstrap/bootstrap.min.css.map diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/FONT-LICENSE b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/FONT-LICENSE deleted file mode 100644 index a1dc03f..0000000 --- a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/FONT-LICENSE +++ /dev/null @@ -1,86 +0,0 @@ -SIL OPEN FONT LICENSE Version 1.1 - -Copyright (c) 2014 Waybury - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/ICON-LICENSE b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/ICON-LICENSE deleted file mode 100644 index 2199f4a..0000000 --- a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/ICON-LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Waybury - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/README.md b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/README.md deleted file mode 100644 index 6b810e4..0000000 --- a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/README.md +++ /dev/null @@ -1,114 +0,0 @@ -[Open Iconic v1.1.1](http://useiconic.com/open) -=========== - -### 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) - - - -## What's in Open Iconic? - -* 223 icons designed to be legible down to 8 pixels -* Super-light SVG files - 61.8 for the entire set -* SVG sprite—the modern replacement for icon fonts -* Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats -* Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats -* PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. - - -## Getting Started - -#### 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. - -### General Usage - -#### Using Open Iconic's SVGs - -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). - -``` -icon name -``` - -#### Using Open Iconic's SVG Sprite - -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. - -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.* - -``` - - - -``` - -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. - -``` -.icon { - width: 16px; - height: 16px; -} -``` - -Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. - -``` -.icon-account-login { - fill: #f00; -} -``` - -To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). - -#### Using Open Iconic's Icon Font... - - -##### …with Bootstrap - -You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` - - -``` - -``` - - -``` - -``` - -##### …with Foundation - -You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` - -``` - -``` - - -``` - -``` - -##### …on its own - -You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` - -``` - -``` - -``` - -``` - - -## License - -### Icons - -All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). - -### Fonts - -All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css deleted file mode 100644 index 4664f2e..0000000 --- a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css +++ /dev/null @@ -1 +0,0 @@ -@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'} \ No newline at end of file diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.eot deleted file mode 100644 index f98177d..0000000 Binary files a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.eot and /dev/null differ diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.otf deleted file mode 100644 index f6bd684..0000000 Binary files a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.otf and /dev/null differ diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.svg deleted file mode 100644 index 32b2c4e..0000000 --- a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.svg +++ /dev/null @@ -1,543 +0,0 @@ - - - - - -Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 - By P.J. Onori -Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf deleted file mode 100644 index fab6048..0000000 Binary files a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf and /dev/null differ diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.woff deleted file mode 100644 index f930998..0000000 Binary files a/5-blazor/1-complete/ConnectFour/wwwroot/css/open-iconic/font/fonts/open-iconic.woff and /dev/null differ diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/favicon.ico b/5-blazor/1-complete/ConnectFour/wwwroot/favicon.ico deleted file mode 100644 index 63e859b..0000000 Binary files a/5-blazor/1-complete/ConnectFour/wwwroot/favicon.ico and /dev/null differ diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/favicon.png b/5-blazor/1-complete/ConnectFour/wwwroot/favicon.png new file mode 100644 index 0000000..8422b59 Binary files /dev/null and b/5-blazor/1-complete/ConnectFour/wwwroot/favicon.png differ diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/icon-192.png b/5-blazor/1-complete/ConnectFour/wwwroot/icon-192.png deleted file mode 100644 index 166f56d..0000000 Binary files a/5-blazor/1-complete/ConnectFour/wwwroot/icon-192.png and /dev/null differ diff --git a/5-blazor/1-complete/ConnectFour/wwwroot/index.html b/5-blazor/1-complete/ConnectFour/wwwroot/index.html deleted file mode 100644 index efb5a49..0000000 --- a/5-blazor/1-complete/ConnectFour/wwwroot/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - ConnectFour - - - - - - - -
Loading...
- -
- An unhandled error has occurred. - Reload - 🗙 -
- - - - diff --git a/5-blazor/README.md b/5-blazor/README.md index e64dae7..1a85b17 100644 --- a/5-blazor/README.md +++ b/5-blazor/README.md @@ -1,508 +1,537 @@ # Introducing Blazor Web Applications -Today we're going to learn how to build a Blazor application by recreating the classic four-in-a-row game, Connect Four. +Today we're going to learn how to build a Blazor app by recreating the classic four-in-a-row game, Connect Four. -## What is Blazor +## What is Blazor? -Blazor is a framework for building web pages with HTML, CSS, and C#. We can define the layout and design of the website using standard HTML and CSS. The interactive components of the web pages can then be managed with C# code that runs on a server or in the browser using a web standard technology called WebAssembly. The formatting of HTML and CSS will build on what we learned in our previous sessions, using the Razor template language. -Blazor allows us to define our web pages using Razor and include other Razor files as components inside those pages. This means we can build and re-use parts of our application easily. +Blazor is a framework for building web pages with HTML, CSS, and C#. We can define the layout and design of the website using standard HTML and CSS. The interactive components of the web pages can then be managed with C# code that runs on a server or in the browser using a web standard technology called WebAssembly. Blazor allows us to define our web pages and components using Razor syntax, a convenient mixture of HTML and C#. You can easily reuse Blazor components inside other pages and components. This capability means we can build and reuse parts of our app easily. ## What is WebAssembly? -WebAssembly is a standard technology available in every modern browser that allows code to run, similar to JavaScript, in a browser. We can use tools to prepare our C# code for use in the browser as a web assembly application, and these tools are bundled into the .NET command-line application. +WebAssembly is a standard technology available in every modern browser that allows code to run, similar to JavaScript, in a browser. We can use tools to prepare our C# code for use in the browser as a WebAssembly app, and these tools are included with the .NET SDK. ## Structure of this repository -We're including all of the layout and game logic in this repository as well as a completed sample Blazor Connect Four application to compare your progress with. We'll walk through the initial construction of the application using the .NET command-line, and you can find an instance of that code with the CSS and game logic in the [0-start](0-start) folder of this repository. The completed state of the game can be found in the [1-complete](1-complete) folder. +We're including all of the layout and game logic in this repository as well as a completed sample Blazor Connect Four app to compare your progress with. We'll walk through the initial construction of the application using the .NET command-line, and you can find an instance of that code with the CSS and game logic in the [0-start](0-start) folder of this repository. The completed state of the game can be found in the [1-complete](1-complete) folder. -## What we're building +## What we're building This repository will walk you through Blazor and introduce the following concepts: -- Blazor component fundamentals -- How to get started with the Blazor WebAssembly project template -- How to construct and use a layout for a Blazor component -- How to react to user interactions +- Blazor component fundamentals +- How to get started with the Blazor Web App project template +- How to construct and use a layout for a Blazor component +- How to react to user interactions -We'll accomplish this by writing a classic four-in-a-row "Connect Four" game that runs in your browser using WebAssembly. In this game, 2 players alternate taking turns placing a gamepiece (typically a checker) in the top of the board. Game pieces fall to the lowest row of a column and the player that places 4 game pieces to make a line horizontally, vertically, or diagonally wins. +We accomplish the above goals by writing a classic four-in-a-row "Connect Four" game that runs in your browser. In this game, 2 players alternate taking turns placing a gamepiece (typically a checker) in the top of the board. Game pieces fall to the lowest row of a column and the player that places 4 game pieces to make a line horizontally, vertically, or diagonally wins. ## Create a new Blazor project -First, let's scaffold a new project for our game. With .NET 6 installed, we can start building our application at the command-line. +First, let's scaffold a new project for our game. **GitHub Codespaces Instructions** 1. Open a GitHub Codespace. To do this, simply select the green **Code** button. Then click the **+** to create a Codespace on the main branch. 2. Navigate to the project files in the [0-start folder](0-start) - + **Visual Studio Instructions** -1. Create a new blazor application in Visual Studio 2022 by choosing the "File - New... - Project" menu. +1. Create a new Blazor app in Visual Studio 2022 by choosing the **File > New > Project** menu. -2. Choose a "Blazor WebAssembly App" from the list of templates and name it "ConnectFour". Click next +1. Choose "Blazor Web App" from the list of templates and name it "ConnectFour". Select **Next**. -3. Choose the .NET 6 framework, Authentication type should be set to "None" and uncheck the "ASP.NET Core hosted" checkbox. +1. Choose .NET 8 for the framework version. The Authentication type should be set to **None**, Interactive render mode should be set to **Server**, and Interactivity location should be set to **Per page/component**. Leave all other options as the defaults. - This should create a _ConnectFour_ directory containing our application. + This action should create a ConnectFour directory containing our app. -4. Run the app by pressing F5 in Visual Studio 2022. This builds the app and hosts it on a random port between 5000 and 5300. HTTPS has a port selected for it in the range of 7000 to 7300. +1. Run the app by pressing F5 in Visual Studio 2022. - Your Output Window should report content similar to the following: + You should now see the Blazor app running in your browser: - ```output - Building... - info: Microsoft.Hosting.Lifetime[14] - Now listening on: https://localhost:7296 - info: Microsoft.Hosting.Lifetime[14] - Now listening on: http://localhost:5136 - info: Microsoft.Hosting.Lifetime[0] - Application started. Press Ctrl+C to shut down. - info: Microsoft.Hosting.Lifetime[0] - Hosting environment: Development - info: Microsoft.Hosting.Lifetime[0] - Content root path: C:\dev\ConnectFour - ``` + ![Screenshot from the new Blazor Template](img/1-NewTemplate.png) -5. Let's navigate to the address our web server announced it's serving the application at. You might be able to Ctrl+Click on the address in your terminal, or just copy the address into your browser. In the above sample, we would navigate to `http://localhost:5136`. You should be presented with the following welcome screen in your browser: +Congratulations! You've created your first Blazor app! - ![Screenshot from the new Blazor Template](img/1-NewTemplate.png) +## Create a board component - Congratulations! You've created your first Blazor application using the Blazor WebAssembly template. +Next, let's create a game board component to be used by players in our game. The component is defined using Razor syntax, which is a mix of HTML and C#. -## Create a Board component +1. Right-click on the *Components* folder in the Solution Explorer of Visual Studio. Choose **Add > Razor Component** from the context menu and name the file *Board.razor*. -Next, let's create a board component to be used by players in our game. The component is defined using Razor syntax, which is a mix of HTML and C#. + We'll use this component to hold everything needed for the game-board layout and managing interactions with it. The initial contents of this new component are an `h3` tag and a `@code` block indicating where C# code should be written: -1. Right-click on the **Shared** folder in the Solution Explorer of Visual Studio. Choose "Add - Razor Component" from the context menu and name the file `Board.razor`. Place this file in the **Shared** folder and this will allow the component to be referenced and used throughout the application. We will use this component to hold everything needed for the game-board layout and managing interactions with it. + ```razor +

Board

+ + @code { + + } + ``` - The initial contents of this new component are a simple H3 tag and a code block indicating where C# code should be written: +1. Prepare the `Home` page by opening the *Components/Pages/Home.razor* file and clearing out everything after the third line with the `PageTitle`` tag. - ```csharp -

Board

+ ```razor + @page "/" + + Home + ``` - @code { +1. Add our `Board` component to the `Home` page by adding a `` tag, which matches the filename of our new component. - } - ``` + ```razor + @page "/" + + Index + + ``` -1. Prepare our Index page by opening the `Pages/Index.razor` file. Clear out everything after the 3rd line with the PageTitle tags. + The *Home.razor* file is a component that can be navigated to from a web browser. It contains HTML, C#, and references to other Blazor components. We can identify this file as a page due to the presence of the `@page "/"` directive on the first line. This directive assigns the "/" route to the component and instructs Blazor to respond with the contents of this file when the default page at the "/" address is requested. - ```csharp - @page "/" +1. Run the app with F5 to see the changes. If the app is already running, tap the Hot Reload button next to the Run/Continue button to apply the changes to the running app. - Index - ``` + > TIP: Select the **Hot Reload on File Save** option from the Hot Reload menu to apply changes to the running app whenever you change a file. -1. We can add our `Board` component to the `Index` page by just adding a tag with the filename of our component to the `Index` page. + ![The initial Board component displayed on the Home page](img/2-Board-Step1.png) - ```csharp - @page "/" +Congratulations! You've built your first component and used it on a Blazor page. - Index +## Adding content and style to the game board - - ``` +Blazor components contain all of the HTML and markup needed to be rendered in a web browser. Let's start defining a game board with the seven columns and six rows. We'll add a little style to bring our board to life. - The `Index.razor` file is a **Page** that can be navigated to and contains HTML, C#, and references to other Blazor components. We can identify this file as a page due to the presence of the `@page "/"` directive on the first line. This instructs Blazor to respond with the contents of this file when the default page at the "/" address is requested. +1. In the *Board.razor* file, remove the HTML at the top and add the following content to define a board with 42 places for game pieces. - Let's run our application with F5 and in the hot-reload toolbar button activate the "Hot reload on file save" option. The hot-reload feature will patch and rebuild our application, and then refresh our browser as we add new features to the application. Hot reload allows you to observe the evolution of the game from a simple page to a full Connect Four board with interactive pieces. + We can use a C# `for` loop to generate the 42 board positions. The container `span` tag is picked up and repeated with its contents 42 times to represent our board. - ![The initial Board component displayed on the Index page](img/2-Board-Step1.png) + ```razor +
+
+ @for (var i = 0; i < 42; i++) + { + + + + } +
+
+ ````` - Congratulations! You've just built your first component and used it on a Blazor page. +When we save the board component, our app refreshes and it appears as an empty page, thanks to the Hot Reload functionality that rebuilds and launches the updated app. -## Adding layout and style to our board +> [!NOTE] +> You may be prompted by Visual Studio to restart your app as files change. Confirm that the app should be rebuilt on code edits, and the app will automatically restart and refresh the browser as you add features. -Blazor components contain all of the HTML and markup needed to be rendered in a web browser. Let's start defining a game board with the 7 columns and 6 rows. We'll then add a little style to bring our board to life. +### Styling the component -1. In the `Board.razor` file let's remove the HTML at the top and add the following content to define a board with 42 places to move. - - We can mix in some C# code in the form of a for loop to generate the 42 board positions. Just like razor pages and MVC views, we indicate the start of our C# code with an `@` symbol. Razor will continue interpreting C# code after the `@` until it identifies a tag that it will then render normally. In this case, the **span** tag will be picked up and repeated with its contents 42 times. +Let's add some style to the `Board` component by defining some colors for the frame of the board and the players above the first `div` tag in the *Board.razor* file: - ```csharp -
-
- @for (var i = 0; i < 42; i++) - { - - - - } -
-
- ``` +```razor + + + - It may seem strange to allocate 42 `span` elements inside of a `div`. When we save the board component, our application refreshes and it appears as an empty page. +
...
+``` - You may be prompted by Visual Studio to restart your app as files change. Confirm that the application should be rebuilt on code edits, and our application will automatically restart and refresh the browser for us as we add features. +These CSS variables `--board-bg`, `--player1: red`, `--player2: blue` will be picked up and used in the rest of our stylesheet for this component. -2. Let's add some style to this component. Blazor components can define content to be added to the HTML head of the page using a special **HeadContent** tag. Let's define some colors for the frame of the board and the players just above the first **div** tag of our `Board.razor` file: +Next, we'll add a completed stylesheet for the game to the `Board` component. - ```csharp - - - +1. Right-click in the Solution Explorer on the *Components* folder and create a new CSS file called *Board.razor.css*. -
- ... - ``` +1. Copy the following content into the new *Board.razor.css* file: - This **style** tag and contents will be delivered inside the **head** tag on the page. These CSS variables will be picked up and used in the rest of our stylesheet for this component. + ```css + div{position:relative}nav{top:4em;width:30em;display:inline-flex;flex-direction:row;margin-left:10px}nav span{width:4em;text-align:center;cursor:pointer;font-size:1em}div.board{margin-top:1em;flex-wrap:wrap;width:30em;height:24em;overflow:hidden;display:inline-flex;flex-direction:row;flex-wrap:wrap;z-index:-5;row-gap:0;pointer-events:none;border-left:10px solid var(--board-bg)}span.container{width:4em;height:4em;margin:0;padding:4px;overflow:hidden;background-color:transparent;position:relative;z-index:-2;pointer-events:none}.container span{width:3.5em;height:3.5em;border-radius:50%;box-shadow:0 0 0 3em var(--board-bg);left:0;position:absolute;display:block;z-index:5;pointer-events:none}.player1,.player2{width:3.5em;height:3.5em;border-radius:50%;left:0;top:0;position:absolute;display:block;z-index:-8}.player1{background-color:var(--player1);animation-timing-function:cubic-bezier(.5,.05,1,.5);animation-iteration-count:1;animation-fill-mode:forwards;box-shadow:0 0 0 4px var(--player1)}.player2{background-color:var(--player2);animation-timing-function:cubic-bezier(.5,.05,1,.5);animation-iteration-count:1;animation-fill-mode:forwards;box-shadow:0 0 0 4px var(--player2)}.col0{left:calc(0em + 9px)}.col1{left:calc(4em + 9px)}.col2{left:calc(8em + 9px)}.col3{left:calc(12em + 9px)}.col4{left:calc(16em + 9px)}.col5{left:calc(20em + 9px)}.col6{left:calc(24em + 9px)}.drop1{animation-duration:1s;animation-name:drop1}.drop2{animation-duration:1.5s;animation-name:drop2}.drop3{animation-duration:1.6s;animation-name:drop3}.drop4{animation-duration:1.7s;animation-name:drop4}.drop5{animation-duration:1.8s;animation-name:drop5}.drop6{animation-duration:1.9s;animation-name:drop6}@keyframes drop1{100%,75%,90%,97%{transform:translateY(1.27em)}80%{transform:translateY(.4em)}95%{transform:translateY(.8em)}99%{transform:translateY(1em)}}@keyframes drop2{100%,75%,90%,97%{transform:translateY(5.27em)}80%{transform:translateY(3.8em)}95%{transform:translateY(4.6em)}99%{transform:translateY(4.9em)}}@keyframes drop3{100%,75%,90%,97%{transform:translateY(9.27em)}80%{transform:translateY(7.2em)}95%{transform:translateY(8.3em)}99%{transform:translateY(8.8em)}}@keyframes drop4{100%,75%,90%,97%{transform:translateY(13.27em)}80%{transform:translateY(10.6em)}95%{transform:translateY(12em)}99%{transform:translateY(12.7em)}}@keyframes drop5{100%,75%,90%,97%{transform:translateY(17.27em)}80%{transform:translateY(14em)}95%{transform:translateY(15.7em)}99%{transform:translateY(16.5em)}}@keyframes drop6{100%,75%,90%,97%{transform:translateY(21.27em)}80%{transform:translateY(17.4em)}95%{transform:translateY(19.4em)}99%{transform:translateY(20.4em)}} + ``` -3. Next, we'll add a completed stylesheet for the game from the `0-start` folder in this repository. Right-click in the Solution Explorer on the **Shared** folder and create a new CSS file called `Board.razor.css`. Copy the following content into the new `Board.razor.css` file: + For convenience, you can also find this content in the [0-start/Shared/Board.razor.css](0-start/Shared/Board.razor.css) file in this repository. - ```css - div{position:relative}nav{top:4em;width:30em;display:inline-flex;flex-direction:row;margin-left:10px}nav span{width:4em;text-align:center;cursor:pointer;font-size:1em}div.board{margin-top:1em;flex-wrap:wrap;width:30em;height:24em;overflow:hidden;display:inline-flex;flex-direction:row;flex-wrap:wrap;z-index:-5;row-gap:0;pointer-events:none;border-left:10px solid var(--board-bg)}span.container{width:4em;height:4em;margin:0;padding:4px;overflow:hidden;background-color:transparent;position:relative;z-index:-2;pointer-events:none}.container span{width:3.5em;height:3.5em;border-radius:50%;box-shadow:0 0 0 3em var(--board-bg);left:0;position:absolute;display:block;z-index:5;pointer-events:none}.player1,.player2{width:3.5em;height:3.5em;border-radius:50%;left:0;top:0;position:absolute;display:block;z-index:-8}.player1{background-color:var(--player1);animation-timing-function:cubic-bezier(.5,.05,1,.5);animation-iteration-count:1;animation-fill-mode:forwards;box-shadow:0 0 0 4px var(--player1)}.player2{background-color:var(--player2);animation-timing-function:cubic-bezier(.5,.05,1,.5);animation-iteration-count:1;animation-fill-mode:forwards;box-shadow:0 0 0 4px var(--player2)}.col1{left:calc(0em + 9px)}.col2{left:calc(4em + 9px)}.col3{left:calc(8em + 9px)}.col4{left:calc(12em + 9px)}.col5{left:calc(16em + 9px)}.col6{left:calc(20em + 9px)}.col7{left:calc(24em + 9px)}.drop1{animation-duration:1s;animation-name:drop1}.drop2{animation-duration:1.5s;animation-name:drop2}.drop3{animation-duration:1.6s;animation-name:drop3}.drop4{animation-duration:1.7s;animation-name:drop4}.drop5{animation-duration:1.8s;animation-name:drop5}.drop6{animation-duration:1.9s;animation-name:drop6}@keyframes drop1{100%,75%,90%,97%{transform:translateY(1.27em)}80%{transform:translateY(.4em)}95%{transform:translateY(.8em)}99%{transform:translateY(1em)}}@keyframes drop2{100%,75%,90%,97%{transform:translateY(5.27em)}80%{transform:translateY(3.8em)}95%{transform:translateY(4.6em)}99%{transform:translateY(4.9em)}}@keyframes drop3{100%,75%,90%,97%{transform:translateY(9.27em)}80%{transform:translateY(7.2em)}95%{transform:translateY(8.3em)}99%{transform:translateY(8.8em)}}@keyframes drop4{100%,75%,90%,97%{transform:translateY(13.27em)}80%{transform:translateY(10.6em)}95%{transform:translateY(12em)}99%{transform:translateY(12.7em)}}@keyframes drop5{100%,75%,90%,97%{transform:translateY(17.27em)}80%{transform:translateY(14em)}95%{transform:translateY(15.7em)}99%{transform:translateY(16.5em)}}@keyframes drop6{100%,75%,90%,97%{transform:translateY(21.27em)}80%{transform:translateY(17.4em)}95%{transform:translateY(19.4em)}99%{transform:translateY(20.4em)}} - ``` + Blazor components and pages have a feature called CSS isolation that allows you to create style rules that will only be applied to the contents of that component or page. By creating a file with the same name as our component and adding the `.css` filename extension, Blazor will recognize this as the styles that should **ONLY** be applied to HTML content in the `Board` component. - For convenience, you can also find this content in the [0-start/Shared/Board.razor.css](0-start/Shared/Board.razor.css) file in this repository. + Here's some of the CSS used to format the board and "punch holes" for each of the spaces. There's more content available than displayed below in the CSS file for the game pieces and their animations on screen. - Blazor components and pages have a feature called CSS isolation that allows you to create style rules that will only be applied to the contents of that component or page. By creating a file with the same name as our component and adding the `.css` filename extension, Blazor will recognize this as the styles that should **ONLY** be applied to HTML content in the `Board.razor` template. + ```css + div.board { + margin-top: 1em; + flex-wrap: wrap; + width: 30em; + height: 24em; + overflow: hidden; + display: inline-flex; + flex-direction: row; + flex-wrap: wrap; + z-index: -5; + row-gap: 0; + pointer-events: none; + border-left: 10px solid var(--board-bg); + } + + span.container { + width: 4em; + height: 4em; + margin: 0; + padding: 4px; + overflow: hidden; + background-color: transparent; + position: relative; + z-index: -2; + pointer-events: none; + } + + .container span { + width: 3.5em; + height: 3.5em; + border-radius: 50%; + box-shadow: 0 0 0 3em var(--board-bg); + left: 0px; + position: absolute; + display: block; + z-index: 5; + pointer-events: none; + } + ``` - Here is some of the CSS used to format the board and 'punch holes' for each of the spaces. There is much more content than this in the CSS file, and we'll use that for the game pieces and their animations on screen. +The browser should update for you (if not, you can manually refresh the browser with F5), and you should be greeted with a proper yellow Connect Four board: - ```css - div.board { - margin-top: 1em; - flex-wrap: wrap; - width: 30em; - height: 24em; - overflow: hidden; - display: inline-flex; - flex-direction: row; - flex-wrap: wrap; - z-index: -5; - row-gap: 0; - pointer-events: none; - border-left: 10px solid var(--board-bg); - } +![The first appearance of a yellow Connect Four board on screen](img/2-Board-Step2.png) - span.container { - width: 4em; - height: 4em; - margin: 0; - padding: 4px; - overflow: hidden; - background-color: transparent; - position: relative; - z-index: -2; - pointer-events: none; - } +## Introducing game logic and controls - .container span { - width: 3.5em; - height: 3.5em; - border-radius: 50%; - box-shadow: 0 0 0 3em var(--board-bg); - left: 0px; - position: absolute; - display: block; - z-index: 5; - pointer-events: none; - } - ``` - - Your browser should be refreshed for you (if not you can manually refresh the browser with F5), and you should be greeted with a proper yellow Connect Four board: - - ![The first appearance of a yellow Connect Four board on screen](img/2-Board-Step2.png) - -## Introducing Game Logic and Controls - -The game logic for Connect Four is not too difficult to program. We need some code that will manage the state of the game and identify 4 consecutive game pieces played next to each other and announce the winner. To help keep this tutorial on-topic with teaching about Blazor, we are providing a class called `GameState.cs` that contains the logic for managing the game. +The game logic for Connect Four is not too difficult to program. We need some code that will manage the state of the game and identify 4 consecutive game pieces played next to each other and announce the winner. To help keep this tutorial on-topic with teaching about Blazor, we are providing a class called `GameState.cs` that contains the logic for managing the game. The [GameState.cs file is in this repository](1-complete/ConnectFour/Shared/GameState.cs) and you will copy it into your version of the game. -1. Copy the [GameState.cs file](1-complete/ConnectFour/Shared/GameState.cs) from this repository into your project in the `Shared` folder. +1. Copy the [GameState.cs file](1-complete/ConnectFour/Shared/GameState.cs) from this repository into the root of your project. -1. We need to make an instance of the `GameState` available to any component that requests it, and only 1 instance of `GameState` should be available in our application at a time. We will address this need by registering our GameState as a Singleton in the application. Open the `Program.cs` file at the root of the project and let's add this statement after the `builder.AddServices...` statement: +We need to make an instance of the `GameState` available to any component that requests it, and only one instance of `GameState` should be available in our app at a time. We'll address this need by registering our `GameState` as a singleton service in the app. - ```csharp - builder.Services.AddSingleton(); - ``` +1. Open the *Program.cs* file at the root of the project and add this statement to configure `GameState` as a singleton service in your app: - We can now **inject** an instance of our `GameState` into our `Board` component to work with. + ```csharp + builder.Services.AddSingleton(); + ``` -1. At the top of the `Board.razor` file, add the following directive to inject the current state of the game into the component: + We can now inject an instance of the `GameState` class into our `Board` component. - ```csharp - @inject ConnectFour.Shared.GameState State - ``` +1. Add the following `@inject` directive at the top of the *Board.razor* file to inject the current state of the game into the component: - We can now start connecting our Board component to the state of the game. + ```razor + @inject GameState State + ``` -1. Let's begin by resetting the state of the game when the **Board** component is first painted on screen. The event that handles this initialization of the component is called `OnInitialized`. Let's add some code to reset the state of the game when the component is initialized. Inside the `@code` block at the bottom of the `Board.razor` file, let's add this method, called an event handler: + We can now start connecting our `Board` component to the state of the game. - ```csharp - @code { - protected override void OnInitialized() - { - State.ResetBoard(); - } - } - ``` +## Reset state - When the board is first shown to a user, the state will be reset to the beginning of a game. +Let's begin by resetting the state of the game when the `Board` component is first painted on screen. We'll add some code to reset the state of the game when the component is initialized. -2. Next, let's allocate the possible 42 game pieces that could be played. This should be an array that is referenced by 42 HTML elements that will be managed as pieces are played on the board. We can move and place those pieces by assigning a set of CSS classes with column and row positions. +1. Add an `OnInitialized` method with a call to `ResetBoard`, inside the `@code` block at the bottom of the *Board.razor* file, like so: - Let's allocate those 42 spans just above the `@code` line, but inside the closing `
` element. We'll also define a private array to hold the CSS classes we will assign to the 42 game pieces: + ```razor + @code { + protected override void OnInitialized() + { + State.ResetBoard(); + } + } + ``` - ```csharp - @for (var i = 0; i < 42; i++) - { - - } + When the board is first shown to a user, the state is reset to the beginning of a game. - - - @code { +## Create game pieces - private string[] Pieces = new string[42]; - ``` +Next, let's allocate the possible 42 game pieces that could be played. We can represent the game pieces as an array referenced by 42 HTML elements on the board. We can move and place those pieces by assigning a set of CSS classes with column and row positions. - By default initialization of a string array, this will assign an empty string to the CSS class of each game piece span. This will prevent the game pieces from appearing on screen as they have no contents and no style applied to them. +1. Define an string array field in the code block to hold our game pieces: -1. Let's add a method to handle when a player places a **Piece** in a column. The **GameState** knows how to assign the correct row for the game piece, and will report back the row that it lands in. We can use this to assign some CSS classes for the player's color, the final location of the piece, and a CSS drop animation. + ```razor + private string[] pieces = new string[42]; + ``` - We'll call this method `PlayPiece` and accept an input parameter that specifies the column (1 indexed) the player has chosen. Add this code below the `Pieces` array we defined in the previous step. +1. Add code to the HTML section that creates 42 `span` tags, one for each game piece, in the same component: - ```csharp + ```razor + @for (var i = 0; i < 42; i++) + { + + } + ``` - private void PlayPiece(byte col) - { - var landingRow = State.PlayPiece(col); - var cssClass = $"player{State.PlayerTurn} col{col} drop{landingRow}"; - Pieces[State.CurrentTurn - 1] = cssClass; - } - ``` + Your full code should look like so: - We tell the game state to play a piece in the submitted column called `col` and capture the row reported by the game state. We can then define the 3 CSS classes to assign to the game piece to identify which player is currently acting, the column the piece was placed in, and the landing row. The last line of the method chooses the next element in the `Pieces` array and assigns these classes to that game piece. + ```razor +
+
...
+ @for (var i = 0; i < 42; i++) + { + + } +
+ @code { + private string[] Pieces = new string[42]; + + protected override void OnInitialized() + { + State.ResetBoard(); + } + } + ``` - If you look in the supplied `Board.razor.css` you'll find the CSS classes that match up to those assigned in line 2 of the method. + This assigns an empty string to the CSS class of each game piece span. An empty string for a CSS class prevents the game pieces from appearing on screen as no style is applied to them. - The resultant effect is that the game piece is placed in the column and animated to drop into the bottom-most row when this method is called. +## Handle game piece placement -1. We next need to place some controls that allow players to choose a column and call our new `PlayPiece` method. Let's add a row of HTML elements above the board that will call this method when they are clicked. +Let's add a method to handle when a player places a piece in a column. The `GameState` class knows how to assign the correct row for the game piece, and reports back the row that it lands in. We can use this information to assign CSS classes representing the player's color, the final location of the piece, and a CSS drop animation. - We could use characters to indicate "Play Here"... or we could be a little fancy and use an emote, as .NET, C#, and Blazor support using emote characters in our code. Let's use this character 🔽 to indicate that you can drop a piece in this column. +We call this method `PlayPiece`, and it accepts an input parameter that specifies the column the player has chosen. - Above the starting `
` tag, let's add a row of buttons: +1. Add this code below the `pieces` array we defined in the previous step. - ```csharp - - ``` + ```csharp + private void PlayPiece(byte col) + { + var player = State.PlayerTurn; + var turn = State.CurrentTurn; + var landingRow = State.PlayPiece(col); + pieces[turn] = $"player{player} col{col} drop{landingRow}"; + } + ``` - We can't use a for loop for this, as we are defining an `onclick` handler for these span elements that has an assigned column number. +Here's what the `PlayPiece` code does: - The `onclick` handler _LOOKS_ like a JavaScript handler that we're used to in HTML... but it's got an `@` character in front. This changes the language that will handle the **click** event on the `span` element to instead be .NET and C#. This allows us to write the method that calls `PlayPiece` in our C# code. +1. We tell the game state to play a piece in the submitted column called `col` and capture the row the piece landed in. +1. We can then define the three CSS classes to assign to the game piece to identify which player is currently acting, the column the piece was placed in, and the landing row. +1. The last line of the method assigns these classes to that game piece in the `pieces` array. -1. Let's review in the browser and it should look like this now: +If you look in the supplied *Board.razor.css*, you'll find the CSS classes matching column, row, and player turn. - ![Yellow Game Board with drop buttons at the top](img/2-Board-Step3.png) +The resultant effect is that the game piece is placed in the column and animated to drop into the bottom-most row when this method is called. - Even better... when we click one of the drop buttons at the top, this happens: +## Choosing a column - ![First piece dropped into board](img/2-board-drop.gif) +We next need to place some controls that allow players to choose a column and call our new `PlayPiece` method. We'll use the "🔽" character to indicate that you can drop a piece in this column. - That's great! We can now add pieces to the board. The `GameState` object is smart enough to pivot back and forth between the two playeres. Go ahead and click more drop buttons and watch the results. +1. Above the starting `
` tag, add a row of clickable buttons: -## Winning and Error Handling + ```html + + ``` -If you play with the game that you have configured at this point, you'll find that it will raise errors when you try to put too many pieces in the same column and it will stop when one player has won the game. + The `@onclick` attribute specifies an event handler for the click event. But to handle UI events, a Blazor component needs to be rendered using an *interactive render mode*. By default, Blazor components are rendered statically from the server. We can apply an interactive render mode to a component using the `@rendermode` attribute. -Let's add some error handling and indicators to our board to make the current state clear. +1. Update the `Board` component on the `Home` page to use the `InteractiveServer` render mode. -1. Let's add a simple status area above the board, and below the drop buttons. Insert the following markup after the closing `` element: + ```razor + + ``` - ```csharp - + The `InteractiveServer` render mode will handle UI events for your components from the server over a WebSocket connection with the browser. -
- @WinnerMessage -
- @ErrorMessage - @CurrentTurn -
- ``` +1. Run the app with these changes. It should look like this now: - This will allow us to place indicators for: - - Announcing a game winner - - A button that will allow us to restart the game - - Error messages + ![Yellow Game Board with drop buttons at the top](img/2-Board-Step3.png) + + Even better, when we select one of the drop buttons at the top, the following behavior can be observed: + + ![First piece dropped into board](img/2-board-drop.gif) + +Great progress! We can now add pieces to the board. The `GameState` object is smart enough to pivot back and forth between the two players. Go ahead and select more drop buttons and watch the results. + +## Winning and error handling + +If you play with the game that you've configured at this point, you'll find that it raises errors when you try to put too many pieces in the same column and when one player has won the game. + +Let's add some error handling and indicators to our board to make the current state clear. We'll add a status area above the board and below the drop buttons. + +1. Insert the following markup after the `nav` element: + + ```razor + + +
+ @winnerMessage +
+ @errorMessage + @CurrentTurn +
+ ``` + + This markup allows us to display indicators for: + + - Announcing a game winner + - A button that allows us to restart the game + - Error messages - The current player's turn - Let's fill in some logic to set these values + Let's fill in some logic to set these values. -1. We need to add fields in our code for these indicators. Add the following fields after the `Pieces` statement in code: +1. Add the following code after the pieces array: - ```csharp - private string[] Pieces = new string[42]; + ```csharp + private string[] pieces = new string[42]; + private string winnerMessage = string.Empty; + private string errorMessage = string.Empty; + + private string CurrentTurn => (winnerMessage == string.Empty) ? $"Player {State.PlayerTurn}'s Turn" : ""; + private string ResetStyle => (winnerMessage == string.Empty) ? "display: none;" : ""; + ``` - private string WinnerMessage = string.Empty; + - The `CurrentTurn` property is automatically calculated based on the state of the `winnerMessage` and the `PlayerTurn` property of the `GameState`. + - The `ResetStyle` is calculated based on contents of the `WinnerMessage`. If there's a `winnerMessage`, we make the reset button appear on screen. - private string ErrorMessage = string.Empty; +1. Let's handle the error message when a piece is played. Add a line to clear the error message and then wrap the code in the `PlayPiece` method with a `try...catch` block to set the `errorMessage` if an exception occurred: - private string CurrentTurn => (WinnerMessage == string.Empty) ? $"Player {State.PlayerTurn}'s Turn" : ""; + ```csharp + errorMessage = string.Empty; + try + { + var player = State.PlayerTurn; + var turn = State.CurrentTurn; + var landingRow = State.PlayPiece(col); + pieces[turn] = $"player{player} col{col} drop{landingRow}"; + } + catch (ArgumentException ex) + { + errorMessage = ex.Message; + } + ``` - private string ResetStyle => (WinnerMessage == string.Empty) ? "display: none;" : ""; - ``` + Our error handler indicator is simple and uses the Bootstrap CSS framework to display an error in danger mode. - The `CurrentTurn` field will be automatically calculated based on the state of the `WinnerMessage` and the `PlayerTurn` property of the `GameState`. + ![Game board with an error indicating the column is full](img/3-Board-ErrorHandler.png) - The `ResetStyle` will also be automatically calcluated based on contents of the `WinnerMessage`. If there is a `WinnerMessage` we will make the reset button appear on screen. +1. Next, let's add the `ResetGame` method that our button triggers to restart a game. Currently, the only way to restart a game is to refresh the page. This code allows us to stay on the same page. -1. Let's handle the error message when a piece is played. Add a line to clear the error message and then wrap the three lines of the `PlayPiece` method with a try...catch block to set the `ErrorMessage` if an exception occurred: + ```csharp + void ResetGame() + { + State.ResetBoard(); + winnerMessage = string.Empty; + errorMessage = string.Empty; + pieces = new string[42]; + } + ``` - ```csharp - ErrorMessage = string.Empty; + Now our `ResetGame` method has the following logic: - try - { - var landingRow = State.PlayPiece(col); - var cssClass = $"player{State.PlayerTurn} col{col} drop{landingRow}"; - Pieces[State.CurrentTurn - 1] = cssClass; - } - catch (ArgumentException ex) - { - ErrorMessage = ex.Message; - } - ``` + - Reset the state of the board. + - Hide our indicators. + - Reset the pieces array to an empty array of 42 strings. - Our error handler indicator is simple, and uses the [Bootstrap](https://getbootstrap.com) CSS framework to display an error in **danger** mode. + This update should allow us to play the game again, and now we see an indicator just above the board declaring the player's turn and eventually the completion of the game. - ![Game board with an error indicating the column is full](img/3-Board-ErrorHandler.png) + ![Game over with indicators](img/3-Board-Step1.png) -1. Next, let's add the `ResetGame` method that our button will trigger to restart a game. Currently, the only way to restart a game is to refresh the page... this will allow us to stay on the same page. + We're still left in a situation where we can't select the reset button. Let's add some logic in the `PlayPiece` method to detect the end of the game. - ```csharp - void ResetGame() - { - State.ResetBoard(); - WinnerMessage = string.Empty; - ErrorMessage = string.Empty; - Pieces = new string[42]; - } - ``` +1. Let's detect if there's a winner to the game by adding a switch expression after our `try...catch` block in `PlayPiece`. - We reset the state of the board, hide our indicators, and reset the `Pieces` array to an empty array of 42 strings. + ```csharp + winnerMessage = State.CheckForWin() switch + { + GameState.WinState.Player1_Wins => "Player 1 Wins!", + GameState.WinState.Player2_Wins => "Player 2 Wins!", + GameState.WinState.Tie => "It's a tie!", + _ => "" + }; + ``` - This update should allow us to play the game again, and now we will see an indicator just above the board declaring the player's turn and eventually the completion of the game. + The `CheckForWin` method returns an enum that reports which player, if any has won the game or if the game is a tie. This switch expression will set the `winnerMessage` field appropriately if a game over state has occurred. - ![Game over with indicators](img/3-Board-Step1.png) + Now when we play and reach a game-ending scenario, these indicators appear: - We're still left in a situation where we can't click the reset button. Let's add some logic in the `PlayPiece` method to detect the end of the game. + ![Game board announcing winner with reset button](img/3-Board-Step2.png) -1. Let's detect if there is a winner to the game by adding a switch expression after our try...catch block in `PlayPiece` +## Customizing the board with parameters - ```csharp - WinnerMessage = State.CheckForWin() switch - { - GameState.WinState.Player1_Wins => "Player 1 Wins!", - GameState.WinState.Player2_Wins => "Player 2 Wins!", - GameState.WinState.Tie => "It's a tie!", - _ => "" - }; - ``` +The game works, but maybe you don't like our default colors. In Blazor, we can define parameters on our components that allow us to pass in values that look like attributes on an HTML tag. - The `CheckForWin` method will return an enum that reports which player, if any has won the game or if the game is a tie. This switch expression will set the `WinnerMessage` field appropriately if a game over state has occurred. +Let's add some parameters for the colors on the board, and pass in some groovy colors from the `Home` page. - Now when we play and reach a game-ending scenario, these indicators appear: +Parameters in Blazor are properties on a component that have been decorated with the `Parameter` attribute. - ![Game board announcing winner with reset button](img/3-Board-Step2.png) +1. In *Board.razor*, let's define three properties for the board color, and each player's color. Before the `OnInitialized`` method, add these lines of code: -## Customizing the board with Parameters + ```csharp + [Parameter] + public Color BoardColor { get; set; } = ColorTranslator.FromHtml("yellow"); + + [Parameter] + public Color Player1Color { get; set; } = ColorTranslator.FromHtml("red"); + + [Parameter] + public Color Player2Color { get; set; } = ColorTranslator.FromHtml("blue"); + ``` -The game works... but maybe you don't like our default colors. In Blazor, we can define parameters on our components that will allow us to pass in values that look like attributes on an HTML tag. + We use the `Color` type to ensure that the values passed to our Board component are in-fact colors. -Let's add some parameters for the colors on the board, and pass in some groovy colors from the `Pages/Index.razor` page. +1. Add a `@using` directive to the top of the *Board.razor* file to indicate we're using content from the `System.Drawing` namespace. -Parameters in Blazor are properties in our component that have been decorated with the `Parameter` attribute. + ```razor + @using System.Drawing + ``` -1. In `Board.razor`, let's define 3 properties for the board color, and each player's color. Before the `OnInitialized` method, let's add these lines of code: +1. Use the parameters in the CSS block at the top of *Board.razor* to set the values of the CSS variables. - ```csharp - [Parameter()] - public Color BoardColor { get; set; } - = ColorTranslator.FromHtml("yellow"); + ```razor + + + + ``` - [Parameter()] - public Color Player1Color { get; set; } - = ColorTranslator.FromHtml("red"); + This change shouldn't have changed anything in the appearance of our game board. - [Parameter()] - public Color Player2Color { get; set; } - = ColorTranslator.FromHtml("blue"); - ``` +1. Let's head back to *Home.razor* and add some parameters to our `Board` tag and see how they change the game - We'll use the `Color` type to ensure that the values passed to our `Board` component are in-fact colors. Let's also add a `using` statement to the top of the `Board.razor` file to indicate we are using content from the `System.Drawing` namespace. + ```razor + + ``` - ```csharp - @using System.Drawing - ``` + Isn't that a cool looking board? -1. Let's now interpret the parameters and place their values in the CSS block at the top of `Board.razor` - - ```csharp - - - - ``` - - That shouldn't have changed anything in the appearance of our game board. - -1. Let's head back to `Pages/Index.razor` and add some parameters to our `` tag and see how they change the game - - ```csharp - - ``` - - That's a cool looking board now: - - ![Connect 4 board with green and purple pieces](img/4-Board.png) + ![Connect 4 board with green and purple pieces](img/4-Board.png) ## Summary -We've learned a lot about Blazor and built a neat little game. Here are just some of the skills we learned: +We've learned a lot about Blazor and built a neat little game. Here are some of the skills we learned: -- Created a component -- Added that component to our home page -- Used dependency injection to manage the state of a game +- Created a component +- Added that component to our home page +- Used dependency injection to manage the state of a game - Made the game interactive with event handlers to place pieces and reset the game - Wrote an error handler to report the state of the game - Added parameters to our component - + This is just a simple game, and there's so much more you could do with it. Looking for some challenges to improve it? Consider the following challenges: -- Remove the default layout and extra pages in the application to make it smaller +- Remove the default layout and extra pages in the app to make it smaller. - Improve the parameters to the `Board` component so that you can pass any valid CSS color value. -- Improve the indicators appearance with some CSS and HTML layout -- Introduce sound effects -- Add a visual indicator and prevent a drop button from being used when the column is full -- Add networking capabilities so that you can play a friend in their browser +- Improve the indicators appearance with some CSS and HTML layout. +- Introduce sound effects. +- Add a visual indicator and prevent a drop button from being used when the column is full. +- Add networking capabilities so that you can play a friend in their browser. - Insert the game into a .NET MAUI with Blazor application and play it on your phone or tablet. Happy coding and have fun! @@ -511,6 +540,6 @@ Happy coding and have fun! We're excited to support you on your learning journey! Check out the [.NET Community Page](https://dotnet.microsoft.com/platform/community) to find links to our blogs, YouTube, Twitter, and more. -# How'd it go? +## How'd it go? Please take this quick, [10 question survey](https://aka.ms/WebLearningSeries-git-survey) to give us your thoughts on this lesson & challenge! diff --git a/5-blazor/img/1-NewTemplate.png b/5-blazor/img/1-NewTemplate.png index 198c5af..0645cc5 100644 Binary files a/5-blazor/img/1-NewTemplate.png and b/5-blazor/img/1-NewTemplate.png differ