diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..24a8e87 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/src/Portfolio.Api/Program.cs b/src/Portfolio.Api/Program.cs index aa10909..40be3c1 100644 --- a/src/Portfolio.Api/Program.cs +++ b/src/Portfolio.Api/Program.cs @@ -58,6 +58,22 @@ builder.Services.AddHopFrame(options => { table.Property(p => p.SourceCode) .List(false); }); + + context.Table(table => { + table.Property(a => a.AboutMe) + .List(false) + .IsTextArea(true); + + table.Property(a => a.Future) + .List(false) + .IsTextArea(true); + }); + + context.Table(table => { + table.Property(t => t.Description) + .IsTextArea(true) + .List(false); + }); }); }); builder.Services.AddRazorComponents() diff --git a/src/Portfolio.Shared/Models/TimelineEntry.cs b/src/Portfolio.Shared/Models/TimelineEntry.cs index 4c25ca1..a5b554b 100644 --- a/src/Portfolio.Shared/Models/TimelineEntry.cs +++ b/src/Portfolio.Shared/Models/TimelineEntry.cs @@ -8,7 +8,7 @@ public sealed class TimelineEntry { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; init; } - public DateOnly Date { get; init; } + public DateOnly Date { get; init; } = DateOnly.FromDateTime(DateTime.Now); [MaxLength(1000)] public required string Description { get; set; } diff --git a/src/Portfolio.Web/Components/Components/TimestampView.razor b/src/Portfolio.Web/Components/Components/TimestampView.razor new file mode 100644 index 0000000..1817a85 --- /dev/null +++ b/src/Portfolio.Web/Components/Components/TimestampView.razor @@ -0,0 +1,15 @@ +@using Portfolio.Shared.Models +
+

@Entry.Date.Year

+ @Entry.Description +
+ +@code { + + [Parameter] + public required TimelineEntry Entry { get; set; } + + [Parameter] + public required int Index { get; set; } + +} \ No newline at end of file diff --git a/src/Portfolio.Web/Components/Components/TimestampView.razor.css b/src/Portfolio.Web/Components/Components/TimestampView.razor.css new file mode 100644 index 0000000..600e3b9 --- /dev/null +++ b/src/Portfolio.Web/Components/Components/TimestampView.razor.css @@ -0,0 +1,92 @@ +.timestamp { + flex: 1 1 0; + display: flex; + flex-direction: column; + gap: 50px; + position: relative; + opacity: 0; + animation: fade-in 200ms forwards calc(var(--index) * 200ms) ease-out; + + h2 { + font-size: 20px; + margin: 0; + font-weight: normal; + } + + span { + box-sizing: border-box; + color: var(--desc-color); + font-size: 14px; + padding-right: 10px; + } + + &:after { + content: ''; + width: 15px; + height: 15px; + background: var(--gradient-angled); + border-radius: 50%; + box-shadow: 0 3px 10px 1px var(--primary-muted); + + position: absolute; + top: 45px; + } + + &:before { + content: ''; + width: 0; + height: 3px; + background-color: #FFF; + top: 51px; + position: absolute; + display: var(--show-bar, block); + animation: timestamp-in 500ms forwards calc((var(--index) + 1) * 200ms) ease-in-out; + } +} + +@media screen and (max-width: 1200px) { + .timestamp { + gap: 15px; + padding-left: 30px; + box-sizing: border-box; + animation: none !important; + opacity: 1; + + span { + margin-bottom: 50px; + } + + &:after { + top: 5px; + left: -5px; + } + + &:before { + top: 5px; + left: 1px; + width: 3px; + height: 100%; + animation: none !important; + } + } +} + +@keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes timestamp-in { + from { + width: 0; + } + + to { + width: 100%; + } +} diff --git a/src/Portfolio.Web/Components/Pages/AboutPage.razor b/src/Portfolio.Web/Components/Pages/AboutPage.razor new file mode 100644 index 0000000..9169184 --- /dev/null +++ b/src/Portfolio.Web/Components/Pages/AboutPage.razor @@ -0,0 +1,61 @@ +@page "/about" + +@using Portfolio.Shared.Models +@using Portfolio.Shared.Services +@using Portfolio.Web.Components.Components + +Über mich + +
+
+

Über mich

+ @foreach (var paragraph in _about.AboutMe.Split("\n\n")) { +

@paragraph

+ } +
+
+

Zukünftige Projekte

+ @foreach (var paragraph in _about.Future.Split("\n\n")) { +

@paragraph

+ } +
+
+ +
+

Programmiererfahrung

+
+ @foreach (var (index, entry) in _experience.Index()) { + + } +
+
+ +
+

Karriere

+
+ @foreach (var (index, entry) in _carrier.Index()) { + + } +
+
+ +@inherits CancellableComponent +@inject IAboutRepository AboutRepository +@inject ITimelineRepository TimelineRepository + +@code { + + private IEnumerable _experience = []; + private IEnumerable _carrier = []; + private About _about = new() { + AboutMe = string.Empty, + Future = string.Empty + }; + + protected override async Task OnInitializedAsync() { + _about = await AboutRepository.GetAbout(TokenSource.Token); + _experience = await TimelineRepository.GetTimeline(TimelineEntryType.Experience, TokenSource.Token); + _carrier = await TimelineRepository.GetTimeline(TimelineEntryType.Carrier, TokenSource.Token); + } + +} \ No newline at end of file diff --git a/src/Portfolio.Web/Components/Pages/AboutPage.razor.css b/src/Portfolio.Web/Components/Pages/AboutPage.razor.css new file mode 100644 index 0000000..01d7c42 --- /dev/null +++ b/src/Portfolio.Web/Components/Pages/AboutPage.razor.css @@ -0,0 +1,37 @@ +#about { + display: grid; + gap: 20px; + grid-template-columns: repeat(2, 1fr); +} + +.title { + margin-block: 5rem 2rem; +} + +.timeline ::deep .timestamp:last-of-type { + --show-bar: none; +} + +.timeline { + display: flex; + margin-top: 30px; +} + +@media screen and (max-width: 1200px) { + #about { + grid-template-columns: unset; + grid-template-rows: repeat(2, max-content); + } + + .timeline { + flex-direction: column-reverse; + } + + .timeline ::deep .timestamp:last-of-type { + --show-bar: block; + } + + .timeline ::deep .timestamp:first-of-type { + --show-bar: none; + } +}