This commit is contained in:
2024-03-10 21:42:32 +00:00
parent ef12e964d6
commit 4fa27ea626
9 changed files with 127 additions and 76 deletions

View File

@@ -15,4 +15,8 @@
<ProjectReference Include="..\BeauFindlay.Shared\BeauFindlay.Shared.csproj" /> <ProjectReference Include="..\BeauFindlay.Shared\BeauFindlay.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Components\AnchorNavigation\AnchorNavigation.razor" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,5 +1,5 @@
<button type="@Type" disabled="@IsLoading" <button type="@Type" disabled="@IsLoading"
class="border-0 ring-1 ring-inset ring-gray-300 bg-black px-3.5 py-2.5 text-sm font-semibold text-white shadow hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600 disabled:bg-gray-800"> class="border-0 ring-1 ring-inset ring-gray-300 bg-black px-3.5 py-2.5 text-sm font-semibold text-white shadow hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600 disabled:bg-gray-800 disabled:cursor-progress">
@if (IsLoading) @if (IsLoading)
{ {
<LoadingSpinner Size="LoadingSpinnerSize.Small"/> <LoadingSpinner Size="LoadingSpinnerSize.Small"/>

View File

@@ -5,7 +5,7 @@
<NavLink href="/contact" Match="NavLinkMatch.All" ActiveClass="border-l-2 border-r-2 px-2 rounded"> <NavLink href="/contact" Match="NavLinkMatch.All" ActiveClass="border-l-2 border-r-2 px-2 rounded">
Contact Contact
</NavLink> </NavLink>
<NavLink href="/this" Match="NavLinkMatch.All" ActiveClass="border-l-2 border-r-2 px-2 rounded"> <NavLink href="/about" Match="NavLinkMatch.All" ActiveClass="border-l-2 border-r-2 px-2 rounded">
This App This App
</NavLink> </NavLink>
</nav> </nav>

View File

@@ -0,0 +1,74 @@
@page "/About"
@inject IJSRuntime JSRuntime
@if (!hasPreviouslyRendered)
{
<h1 class="text-4xl">
<Typewriter Text="This app"/>
</h1>
}
else
{
<h1 class="text-4xl">This app<span class="blinking-cursor">|</span></h1>
}
<p class="py-4 text-xl">Below is a brief outline of how this app is made and what technologies are used. If you'd like to dive straight in then full project is available on my <a href="https://github.com/bdfin/my-portfolio" target="_blank" class="underline underline-offset-2">GitHub</a>.</p>
<section class="py-6" id="@FrontEndSection">
<h2 class="text-2xl">Front-end</h2>
<p class="py-4">I wanted to create a decent, modern client-side experience for this app and given my <em class="text-xs">(very...)</em> limited front-end expertise I decided to choose <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" target="_blank" class="underline underline-offset-2">.NET Blazor Webassembly</a>. Blazor is Microsoft's take on component-based SPAs (single page applications) and offers us back-end focussed devs a way of producing decent client experiences without needing to dive into another front-end specific technology.</p>
<p class="py-4">Blazor traditionally came in two flavours, server and webassembly with an additional third option (Blazor Web App) recently released with .NET 8 which is an amalgamation of both. <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-8.0#blazor-server" target="_blank" class="underline underline-offset-2">Blazor Server</a> initially generates content on the server and utilises web-sockets to communicate dynamic UI updates with the client without requiring a page load, whereas <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-8.0#blazor-webassembly" target="_blank" class="underline underline-offset-2">Blazor Webassembly</a> downloads the entire app to the client browser on first load alongside a light-weight .NET run-time to execute code directly on the browsers UI thread.</p>
<p class="py-4">As Blazor server requires a dedicated server to host the application, I chose Blazor webassembly to enable me to host this app for free using an <a href="https://azure.microsoft.com/en-gb/products/app-service/static" target="_blank" class="underline underline-offset-2">Azure Static Web App</a>. You can read more about this in the <a @onclick="() => ScrollToElementAsync(HostingSection)" class="underline underline-offset-2 cursor-pointer">hosting</a> section.</p>
</section>
<section class="py-6" id="@BackEndSection">
<h2 class="text-2xl pb-4">Back-end</h2>
<p class="my-4">As the </p>
</section>
<section class="py-6" id="@HostingSection">
<h2 class="text-2xl pb-4">Hosting</h2>
<p></p>
</section>
<AnchorNavigation />
@code {
private const string ComponentKey = "ComponentRendered_About";
private const string FrontEndSection = "front-end";
private const string BackEndSection = "back-end";
private const string HostingSection = "hosting";
private bool hasPreviouslyRendered;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var renderedBeforeAsString = await JSRuntime.InvokeAsync<string>("localStorage.getItem", ComponentKey);
var previousValue = hasPreviouslyRendered;
hasPreviouslyRendered = !string.IsNullOrEmpty(renderedBeforeAsString) && bool.Parse(renderedBeforeAsString);
if (!hasPreviouslyRendered)
{
await JSRuntime.InvokeVoidAsync("localStorage.setItem", ComponentKey, "true");
}
if (previousValue != hasPreviouslyRendered)
{
StateHasChanged();
}
}
}
private async Task ScrollToElementAsync(string elementId)
{
await JSRuntime.InvokeVoidAsync("scrollToElement", elementId);
}
}

View File

@@ -30,7 +30,7 @@
} }
else else
{ {
<h1 class="text-4xl">Hi, I'm Beau.</h1> <h1 class="text-4xl">Hi, I'm Beau.<span class="blinking-cursor">|</span></h1>
<p class="text-xl mt-4">I'm a UK-based software engineer and I love building cool stuff.</p> <p class="text-xl mt-4">I'm a UK-based software engineer and I love building cool stuff.</p>

View File

@@ -1,7 +0,0 @@
@page "/this"
<h3>ThisApp</h3>
@code {
}

View File

@@ -569,6 +569,11 @@ video {
margin-right: auto; margin-right: auto;
} }
.my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.ml-3 { .ml-3 {
margin-left: 0.75rem; margin-left: 0.75rem;
} }
@@ -675,6 +680,10 @@ video {
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
} }
.cursor-pointer {
cursor: pointer;
}
.grid-cols-1 { .grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(1, minmax(0, 1fr));
} }
@@ -716,18 +725,10 @@ video {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
.rounded-md {
border-radius: 0.375rem;
}
.border-0 { .border-0 {
border-width: 0px; border-width: 0px;
} }
.border {
border-width: 1px;
}
.border-l-2 { .border-l-2 {
border-left-width: 2px; border-left-width: 2px;
} }
@@ -741,16 +742,6 @@ video {
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); background-color: rgb(0 0 0 / var(--tw-bg-opacity));
} }
.bg-green-50 {
--tw-bg-opacity: 1;
background-color: rgb(240 253 244 / var(--tw-bg-opacity));
}
.bg-red-50 {
--tw-bg-opacity: 1;
background-color: rgb(254 242 242 / var(--tw-bg-opacity));
}
.fill-gray-600 { .fill-gray-600 {
fill: #4b5563; fill: #4b5563;
} }
@@ -759,11 +750,6 @@ video {
padding: 1rem; padding: 1rem;
} }
.px-10 {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
.px-2 { .px-2 {
padding-left: 0.5rem; padding-left: 0.5rem;
padding-right: 0.5rem; padding-right: 0.5rem;
@@ -809,19 +795,24 @@ video {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.py-6 {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.py-8 { .py-8 {
padding-top: 2rem; padding-top: 2rem;
padding-bottom: 2rem; padding-bottom: 2rem;
} }
.pt-10 {
padding-top: 2.5rem;
}
.pb-4 { .pb-4 {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.pt-10 {
padding-top: 2.5rem;
}
.text-center { .text-center {
text-align: center; text-align: center;
} }
@@ -886,24 +877,14 @@ video {
color: rgb(229 231 235 / var(--tw-text-opacity)); color: rgb(229 231 235 / var(--tw-text-opacity));
} }
.text-green-400 { .text-green-500 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(74 222 128 / var(--tw-text-opacity)); color: rgb(34 197 94 / var(--tw-text-opacity));
} }
.text-green-700 { .text-red-500 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(21 128 61 / var(--tw-text-opacity)); color: rgb(239 68 68 / var(--tw-text-opacity));
}
.text-green-800 {
--tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity));
}
.text-red-400 {
--tw-text-opacity: 1;
color: rgb(248 113 113 / var(--tw-text-opacity));
} }
.text-red-600 { .text-red-600 {
@@ -911,16 +892,6 @@ video {
color: rgb(220 38 38 / var(--tw-text-opacity)); color: rgb(220 38 38 / var(--tw-text-opacity));
} }
.text-red-700 {
--tw-text-opacity: 1;
color: rgb(185 28 28 / var(--tw-text-opacity));
}
.text-red-800 {
--tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity));
}
.text-slate-100 { .text-slate-100 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(241 245 249 / var(--tw-text-opacity)); color: rgb(241 245 249 / var(--tw-text-opacity));
@@ -941,25 +912,14 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
} }
.text-green-600 {
--tw-text-opacity: 1;
color: rgb(22 163 74 / var(--tw-text-opacity));
}
.text-green-500 {
--tw-text-opacity: 1;
color: rgb(34 197 94 / var(--tw-text-opacity));
}
.text-red-500 {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
}
.underline { .underline {
text-decoration-line: underline; text-decoration-line: underline;
} }
.underline-offset-2 {
text-underline-offset: 2px;
}
.antialiased { .antialiased {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@@ -971,6 +931,10 @@ video {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
} }
.outline {
outline-style: solid;
}
.ring-1 { .ring-1 {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@@ -1067,6 +1031,10 @@ video {
outline-color: #4b5563; outline-color: #4b5563;
} }
.disabled\:cursor-progress:disabled {
cursor: progress;
}
.disabled\:bg-gray-800:disabled { .disabled\:bg-gray-800:disabled {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity)); background-color: rgb(31 41 55 / var(--tw-bg-opacity));

View File

@@ -24,7 +24,9 @@
</p> </p>
</div> </div>
</div> </div>
<script src="js/smoothScroll.js"></script>
<script src="_framework/blazor.webassembly.js"></script> <script src="_framework/blazor.webassembly.js"></script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,10 @@
function scrollToElement(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}