From e884ec020714c30cebe9787ee05bb20e23735740 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Thu, 23 Jan 2025 18:51:59 +0100 Subject: [PATCH] Added technology page --- src/Portfolio.Api/DatabaseContext.cs | 2 - src/Portfolio.Api/Program.cs | 2 +- src/Portfolio.Shared/Models/Language.cs | 20 ---- src/Portfolio.Shared/Models/Project.cs | 4 +- src/Portfolio.Shared/Models/Technology.cs | 18 +++- src/Portfolio.Web/Components/App.razor | 1 + .../Components/Components/ProjectView.razor | 5 +- .../Components/ProjectView.razor.css | 13 +++ .../Components/TechnologyView.razor | 40 +++++++ .../Components/TechnologyView.razor.css | 55 ++++++++++ .../Components/Layout/MainLayout.razor.css | 1 + .../Components/Layout/Navigation.razor.css | 1 - .../Components/Pages/ProjectsPage.razor | 6 +- .../Components/Pages/ProjectsPage.razor.css | 7 -- .../Components/Pages/TechnologiesPage.razor | 100 ++++++++++++++++++ .../Pages/TechnologiesPage.razor.css | 52 +++++++++ src/Portfolio.Web/wwwroot/app.css | 61 ++++++++++- src/Portfolio.Web/wwwroot/scroll-handler.js | 10 ++ 18 files changed, 359 insertions(+), 39 deletions(-) delete mode 100644 src/Portfolio.Shared/Models/Language.cs create mode 100644 src/Portfolio.Web/Components/Components/TechnologyView.razor create mode 100644 src/Portfolio.Web/Components/Components/TechnologyView.razor.css create mode 100644 src/Portfolio.Web/Components/Pages/TechnologiesPage.razor create mode 100644 src/Portfolio.Web/Components/Pages/TechnologiesPage.razor.css create mode 100644 src/Portfolio.Web/wwwroot/scroll-handler.js diff --git a/src/Portfolio.Api/DatabaseContext.cs b/src/Portfolio.Api/DatabaseContext.cs index aaa7b5c..5a5ccc2 100644 --- a/src/Portfolio.Api/DatabaseContext.cs +++ b/src/Portfolio.Api/DatabaseContext.cs @@ -7,8 +7,6 @@ public class DatabaseContext(DbContextOptions options) : DbCont public DbSet Projects { get; set; } - public DbSet Languages { get; set; } - public DbSet Technologies { get; set; } public DbSet Timeline { get; set; } diff --git a/src/Portfolio.Api/Program.cs b/src/Portfolio.Api/Program.cs index cdef3e7..aa10909 100644 --- a/src/Portfolio.Api/Program.cs +++ b/src/Portfolio.Api/Program.cs @@ -45,7 +45,7 @@ builder.Services.AddHopFrame(options => { .SetValue(langConfig, true); table.Property(p => p.Languages) - .FormatEach((l, _) => l.Label) + .FormatEach((l, _) => l.Name) .List(false); table.Property(p => p.Cover) diff --git a/src/Portfolio.Shared/Models/Language.cs b/src/Portfolio.Shared/Models/Language.cs deleted file mode 100644 index f6ee861..0000000 --- a/src/Portfolio.Shared/Models/Language.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Portfolio.Shared.Models; - -public sealed class Language { - - [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; init; } - - [MaxLength(255)] - public required string Label { get; set; } - - [MaxLength(255)] - public required string Identifier { get; set; } - - [MaxLength(255)] - public string? Suffix { get; set; } - -} \ No newline at end of file diff --git a/src/Portfolio.Shared/Models/Project.cs b/src/Portfolio.Shared/Models/Project.cs index fab23ce..682ef03 100644 --- a/src/Portfolio.Shared/Models/Project.cs +++ b/src/Portfolio.Shared/Models/Project.cs @@ -26,8 +26,8 @@ public sealed class Project { public ProjectStatus Status { get; set; } = ProjectStatus.Finished; - [ForeignKey("languages")] - public List Languages { get; init; } = new(); + [ForeignKey("technologies")] + public List Languages { get; init; } = new(); public DateTime Created { get; init; } = DateTime.Now; diff --git a/src/Portfolio.Shared/Models/Technology.cs b/src/Portfolio.Shared/Models/Technology.cs index edd44dd..f6eefae 100644 --- a/src/Portfolio.Shared/Models/Technology.cs +++ b/src/Portfolio.Shared/Models/Technology.cs @@ -10,13 +10,27 @@ public sealed class Technology { [MaxLength(255)] public required string Name { get; set; } + + [MaxLength(255)] + public required string Identifier { get; set; } + + [MaxLength(255)] + public string? Suffix { get; set; } public TechnologyLevel Level { get; set; } = TechnologyLevel.Beginner; + public TechnologyType Type { get; set; } = TechnologyType.Language; + } -public enum TechnologyLevel : byte { +public enum TechnologyLevel { Beginner = 0, Intermediate = 1, Professional = 2 -} \ No newline at end of file +} + +public enum TechnologyType { + Language = 0, + Framework = 1, + Additional = 2 +} diff --git a/src/Portfolio.Web/Components/App.razor b/src/Portfolio.Web/Components/App.razor index 46936de..5202003 100644 --- a/src/Portfolio.Web/Components/App.razor +++ b/src/Portfolio.Web/Components/App.razor @@ -7,6 +7,7 @@ + diff --git a/src/Portfolio.Web/Components/Components/ProjectView.razor b/src/Portfolio.Web/Components/Components/ProjectView.razor index b0693f8..200f895 100644 --- a/src/Portfolio.Web/Components/Components/ProjectView.razor +++ b/src/Portfolio.Web/Components/Components/ProjectView.razor @@ -1,6 +1,6 @@ @using Portfolio.Shared.Models -
+
project-cover

@Project.Name

@@ -32,6 +32,9 @@ [Parameter] public required Project Project { get; set; } + [Parameter] + public required int Index { get; set; } + private string GetStatusName() { return Project.Status switch { ProjectStatus.Finished => "Fertig", diff --git a/src/Portfolio.Web/Components/Components/ProjectView.razor.css b/src/Portfolio.Web/Components/Components/ProjectView.razor.css index 74bfc65..4e112c5 100644 --- a/src/Portfolio.Web/Components/Components/ProjectView.razor.css +++ b/src/Portfolio.Web/Components/Components/ProjectView.razor.css @@ -6,6 +6,19 @@ border-radius: 30px; overflow: hidden; box-shadow: 0 0 40px -10px var(--primary); + opacity: 0; + animation: animate-in 200ms forwards; + animation-delay: calc(var(--index) * 200ms); +} + +@keyframes animate-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } } .shell { diff --git a/src/Portfolio.Web/Components/Components/TechnologyView.razor b/src/Portfolio.Web/Components/Components/TechnologyView.razor new file mode 100644 index 0000000..e2bc846 --- /dev/null +++ b/src/Portfolio.Web/Components/Components/TechnologyView.razor @@ -0,0 +1,40 @@ +@rendermode InteractiveServer +@using Portfolio.Shared.Models + +
+
+

@Technology.Name

+ @GetTechnologyLevelName() +
+
+
+ +@inject IJSRuntime Runtime + +@code { + + [Parameter] + public required Technology Technology { get; set; } + + private ElementReference _element; + + public string GetTechnologyLevelName() { + switch (Technology.Level) { + case TechnologyLevel.Beginner: + return "Anfänger"; + case TechnologyLevel.Intermediate: + return "Erweitert"; + case TechnologyLevel.Professional: + return "Fortgeschritten"; + + default: + return "Normal"; + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) return; + await Runtime.InvokeVoidAsync("observeElement", _element); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Web/Components/Components/TechnologyView.razor.css b/src/Portfolio.Web/Components/Components/TechnologyView.razor.css new file mode 100644 index 0000000..620ca54 --- /dev/null +++ b/src/Portfolio.Web/Components/Components/TechnologyView.razor.css @@ -0,0 +1,55 @@ +.technology { + margin-bottom: 2rem; + + .tech-header { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + + .tech-name { + margin: 0; + font-size: 20px; + font-weight: normal; + } + + .tech-level { + align-self: flex-end; + color: var(--desc-color); + } + } + + .tech-progress { + height: 10px; + border-radius: 5px; + background: var(--gradient); + box-shadow: 0 3px 10px 1px var(--primary-muted); + transition: width 200ms ease-out; + width: 0; + + &.level-1 { + --width: 33%; + } + + &.level-2 { + --width: 66%; + } + + &.level-3 { + --width: 100%; + } + + &.in-view { + animation: slider-in 500ms forwards ease-out; + } + } +} + +@keyframes slider-in { + from { + width: 0; + } + + to { + width: var(--width); + } +} diff --git a/src/Portfolio.Web/Components/Layout/MainLayout.razor.css b/src/Portfolio.Web/Components/Layout/MainLayout.razor.css index 9e6e6ae..1eed6fb 100644 --- a/src/Portfolio.Web/Components/Layout/MainLayout.razor.css +++ b/src/Portfolio.Web/Components/Layout/MainLayout.razor.css @@ -9,6 +9,7 @@ main { width: 100%; position: sticky; top: 0; + z-index: 100; } .content { diff --git a/src/Portfolio.Web/Components/Layout/Navigation.razor.css b/src/Portfolio.Web/Components/Layout/Navigation.razor.css index 413a245..407a864 100644 --- a/src/Portfolio.Web/Components/Layout/Navigation.razor.css +++ b/src/Portfolio.Web/Components/Layout/Navigation.razor.css @@ -6,7 +6,6 @@ background-color: var(--background); border-bottom: 1px solid var(--border-color); padding-inline: 0.5rem; - z-index: 100; } .links { diff --git a/src/Portfolio.Web/Components/Pages/ProjectsPage.razor b/src/Portfolio.Web/Components/Pages/ProjectsPage.razor index 30dd18f..d5247a5 100644 --- a/src/Portfolio.Web/Components/Pages/ProjectsPage.razor +++ b/src/Portfolio.Web/Components/Pages/ProjectsPage.razor @@ -3,11 +3,13 @@ @using Portfolio.Shared.Services @using Portfolio.Web.Components.Components +Projekte +

Alle Projekte

- @foreach (var project in _projects) { - + @foreach (var (index, project) in _projects.Index()) { + }
diff --git a/src/Portfolio.Web/Components/Pages/ProjectsPage.razor.css b/src/Portfolio.Web/Components/Pages/ProjectsPage.razor.css index f4aaa5e..c00986a 100644 --- a/src/Portfolio.Web/Components/Pages/ProjectsPage.razor.css +++ b/src/Portfolio.Web/Components/Pages/ProjectsPage.razor.css @@ -4,10 +4,3 @@ gap: 100px; justify-content: space-evenly; } - -h2 { - margin-top: 3rem; - margin-bottom: 5rem; - font-size: 2rem; - font-weight: 600; -} diff --git a/src/Portfolio.Web/Components/Pages/TechnologiesPage.razor b/src/Portfolio.Web/Components/Pages/TechnologiesPage.razor new file mode 100644 index 0000000..ac9259a --- /dev/null +++ b/src/Portfolio.Web/Components/Pages/TechnologiesPage.razor @@ -0,0 +1,100 @@ +@page "/technologies" +@rendermode InteractiveServer + +@using System.Collections.Immutable +@using Portfolio.Shared.Models +@using Portfolio.Shared.Services +@using Portfolio.Web.Components.Components + +Technologien + +
+
+
+
+
+
+

Technologien in Projekten

+
+
+
+
+ +
+

Programmiersprachen

+
+ @foreach (var tech in _technologies.Where(t => t.Type == TechnologyType.Language)) { + + } +
+
+ +
+

Frameworks

+
+ @foreach (var tech in _technologies.Where(t => t.Type == TechnologyType.Framework)) { + + } +
+
+ +
+

Zusätzliche Fähigkeiten

+
+ @foreach (var skill in _technologies.Where(t => t.Type == TechnologyType.Additional)) { +
+
+

@skill.Name

+
+ } +
+
+ + + + +@inherits CancellableComponent +@inject ITechnologyRepository TechnologyRepository +@inject IProjectRepository ProjectRepository +@inject IJSRuntime Runtime + +@code { + + private IEnumerable _technologies = []; + + protected override async Task OnInitializedAsync() { + _technologies = await TechnologyRepository.GetTechnologies(TokenSource.Token); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (firstRender) { + var projects = await ProjectRepository.GetProjects(TokenSource.Token); + + var data = projects + .SelectMany(p => p.Languages) + .CountBy(l => l.Name) + .ToList(); + + await Runtime.InvokeVoidAsync("displayChart", + data.Select(c => c.Key), + data.Select(c => c.Value)); + } + } +} \ No newline at end of file diff --git a/src/Portfolio.Web/Components/Pages/TechnologiesPage.razor.css b/src/Portfolio.Web/Components/Pages/TechnologiesPage.razor.css new file mode 100644 index 0000000..90d647c --- /dev/null +++ b/src/Portfolio.Web/Components/Pages/TechnologiesPage.razor.css @@ -0,0 +1,52 @@ +#tech-projects { + margin-top: 50px; + position: relative; + + .chart { + display: flex; + margin-top: 30px; + + .chart-container { + width: 500px; + height: 500px; + } + } + + .artwork { + top: 40px; + } +} + +.home-section h2 { + margin-block: 5rem 2rem; +} + +#additional { + #skills-wrapper { + display: flex; + justify-content: space-evenly; + gap: 20px; + margin-top: 30px; + flex-wrap: wrap; + + .skill { + display: flex; + gap: 10px; + align-items: center; + + .dot { + width: 15px; + height: 15px; + background: var(--gradient); + box-shadow: 0 3px 10px 1px var(--primary-muted); + border-radius: 50%; + } + + h3 { + font-weight: normal; + margin: 0; + font-size: 20px; + } + } + } +} diff --git a/src/Portfolio.Web/wwwroot/app.css b/src/Portfolio.Web/wwwroot/app.css index e9aa8a2..2df0872 100644 --- a/src/Portfolio.Web/wwwroot/app.css +++ b/src/Portfolio.Web/wwwroot/app.css @@ -1,6 +1,7 @@ :root { - --primary: #8e5bd2; + --primary: rgb(142, 91, 210); --secondary: #11a8bd; + --primary-muted: rgba(142, 91, 210, 0.4); --gradient: linear-gradient(90deg, var(--primary), var(--secondary)); --gradient-angled: linear-gradient(135deg, var(--primary), var(--secondary)); @@ -30,3 +31,61 @@ html, body { padding: 0; height: 100%; } + +h2 { + margin-top: 3rem; + margin-bottom: 5rem; + font-size: 2rem; + font-weight: 600; +} + +.artwork { + position: absolute; + left: 55%; + top: 19vh; + z-index: 0; + + .circle { + position: absolute; + aspect-ratio: 1 / 1; + z-index: -1; + background: var(--gradient-angled); + border-radius: 50%; + + &:after { + content: ''; + position: absolute; + inset: 1px; + background-color: var(--background); + border-radius: 50%; + } + } + + .big-circle { + left: 0; + top: 0; + width: 400px; + } + + .small-circle { + top: 100px; + left: 350px; + width: 150px; + + &:after { + content: none; + } + } + + .image { + top: -50px; + left: 170px; + width: 250px; + + &:after { + background-image: url("/favicon.ico"); + background-size: 112%; + background-position: -15px -15px; + } + } +} diff --git a/src/Portfolio.Web/wwwroot/scroll-handler.js b/src/Portfolio.Web/wwwroot/scroll-handler.js new file mode 100644 index 0000000..027fc33 --- /dev/null +++ b/src/Portfolio.Web/wwwroot/scroll-handler.js @@ -0,0 +1,10 @@ +const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + entry.target.classList.add("in-view"); + }); +}); + +function observeElement(element) { + observer?.observe(element); +}