diff --git a/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.html b/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.html index 698a7ac6..16b291a1 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.html @@ -34,15 +34,15 @@ [icon]="'check-lg'" (buttonClick)="image.id !== currentImageId && modal.close({ type: 'ImageSelected', image: image })" /> } - {{ image.name }} + {{ image.name }} - {{ image.fileSize | fileSize: 'de' }} + {{ image.fileSize | fileSize: 'de' }}
- +
} @empty { diff --git a/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.scss b/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.scss index 38440487..50431e69 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.scss +++ b/src/Turnierplan.App/Client/src/app/portal/components/image-chooser/image-chooser.component.scss @@ -13,11 +13,15 @@ .image-tile { &:hover, &.hover-override { - border-color: #333 !important; - .hover-buttons { - background: rgb(255 255 255 / 85%); display: flex !important; + z-index: 2; + } + + .image-wrapper { + img { + opacity: 0.1; + } } } diff --git a/src/Turnierplan.App/Client/src/app/portal/components/image-widget/image-widget.component.html b/src/Turnierplan.App/Client/src/app/portal/components/image-widget/image-widget.component.html index 66543005..fd27a90c 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/image-widget/image-widget.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/image-widget/image-widget.component.html @@ -1,8 +1,8 @@
@if (currentImage) {
-
- +
+
} @else { diff --git a/src/Turnierplan.PdfRendering/Extensions/QuestPdfContainerExtensions.cs b/src/Turnierplan.PdfRendering/Extensions/QuestPdfContainerExtensions.cs index 935aff7a..e50e5583 100644 --- a/src/Turnierplan.PdfRendering/Extensions/QuestPdfContainerExtensions.cs +++ b/src/Turnierplan.PdfRendering/Extensions/QuestPdfContainerExtensions.cs @@ -9,39 +9,78 @@ namespace Turnierplan.PdfRendering.Extensions; internal static class QuestPdfContainerExtensions { - extension(IContainer container) + /// + /// Displays an image at the current position with the specified size. This method overwrites properties of the specified + /// so you should always call this method with a fresh container. Most importantly, the + /// method is called to allow for the desired positioning. + /// + public static void LogoImage(this IContainer container, Image image, IImageStorage imageStorage, float size, Unit sizeUnit, bool alignRight = false) { - public void Image(Image image, IImageStorage imageStorage) + if (alignRight) { - // Waiting for the task to complete is not ideal. However, attempting to use async - // inside the QuestPDF document structure is probably a much bigger nightmare... - var task = imageStorage.GetImageAsync(image); - task.Wait(); + float translateX; - var stream = task.Result; - container.Image(stream); + if (image.Height > image.Width) + { + var imageDisplayWidth = (float)image.Width / image.Height * size; + translateX = -imageDisplayWidth; + } + else + { + translateX = -size; + } - // Dispose the stream (at this point, QuestPDF has read the stream content into an internal buffer) - stream.Dispose(); + container = container.AlignRight().TranslateX(translateX, sizeUnit); } - /// - /// This code is taken from , last viewed on 2025-09-26 - /// - public void SkiaSharpSvgCanvas(Action drawOnCanvas) + float translateY; + + if (image.Width > image.Height) { - container.Svg(size => - { - using var stream = new MemoryStream(); + var imageDisplayHeight = (float)image.Height / image.Width * size; + translateY = 0.5f * (size - imageDisplayHeight); + } + else + { + translateY = 0.0f; + } - using (var canvas = SKSvgCanvas.Create(new SKRect(0, 0, size.Width, size.Height), stream)) - { - drawOnCanvas(canvas, size); - } + container.TranslateY(translateY, sizeUnit).Unconstrained().Width(size, sizeUnit).Height(size, sizeUnit).Image(image, imageStorage); + } - var svgData = stream.ToArray(); - return Encoding.UTF8.GetString(svgData); - }); - } + /// + /// Pulls the specified image from the configured image storage and creates a QuestPDF image with the retrieved image data. + /// + public static void Image(this IContainer container, Image image, IImageStorage imageStorage) + { + // Waiting for the task to complete is not ideal. However, attempting to use async + // inside the QuestPDF document structure is probably a much bigger nightmare... + var task = imageStorage.GetImageAsync(image); + task.Wait(); + + var stream = task.Result; + container.Image(stream); + + // Dispose the stream (at this point, QuestPDF has read the stream content into an internal buffer) + stream.Dispose(); + } + + /// + /// This code is taken from , last viewed on 2026-03-01 + /// + public static void SkiaSharpSvgCanvas(this IContainer container, Action drawOnCanvas) + { + container.Svg(size => + { + using var stream = new MemoryStream(); + + using (var canvas = SKSvgCanvas.Create(new SKRect(0, 0, size.Width, size.Height), stream)) + { + drawOnCanvas(canvas, size); + } + + var svgData = stream.ToArray(); + return Encoding.UTF8.GetString(svgData); + }); } } diff --git a/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs b/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs index 94587fb3..8c486b33 100644 --- a/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs +++ b/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs @@ -50,13 +50,13 @@ string GetSectionHeader(string key) if (tournament.PrimaryLogo is not null) { using var _ = DocumentRendererActivitySource.LoadRemoteImage(CurrentActivity, tournament.PrimaryLogo, nameof(tournament.PrimaryLogo)); - column.Item().Unconstrained().Width(3, Unit.Centimetre).Image(tournament.PrimaryLogo, imageStorage); + column.Item().LogoImage(tournament.PrimaryLogo, imageStorage, 3.0f, Unit.Centimetre); } if (tournament.SecondaryLogo is not null) { using var _ = DocumentRendererActivitySource.LoadRemoteImage(CurrentActivity, tournament.SecondaryLogo, nameof(tournament.SecondaryLogo)); - column.Item().AlignRight().Unconstrained().TranslateX(-3, Unit.Centimetre).Width(3, Unit.Centimetre).Image(tournament.SecondaryLogo, imageStorage); + column.Item().LogoImage(tournament.SecondaryLogo, imageStorage, 3.0f, Unit.Centimetre, alignRight: true); } var organizerName = string.IsNullOrWhiteSpace(configuration.OrganizerNameOverride) diff --git a/src/Turnierplan.PdfRendering/Renderer/ReceiptsRenderer.cs b/src/Turnierplan.PdfRendering/Renderer/ReceiptsRenderer.cs index 7c64f055..4d5e2236 100644 --- a/src/Turnierplan.PdfRendering/Renderer/ReceiptsRenderer.cs +++ b/src/Turnierplan.PdfRendering/Renderer/ReceiptsRenderer.cs @@ -37,13 +37,13 @@ protected override Document Render(Tournament tournament, ReceiptsDocumentConfig if (configuration.ShowPrimaryLogo && tournament.PrimaryLogo is not null) { using var _ = DocumentRendererActivitySource.LoadRemoteImage(CurrentActivity, tournament.PrimaryLogo, nameof(tournament.PrimaryLogo)); - column.Item().Unconstrained().Width(1.7f, Unit.Centimetre).Image(tournament.PrimaryLogo, imageStorage); + column.Item().LogoImage(tournament.PrimaryLogo, imageStorage, 1.7f, Unit.Centimetre); } if (configuration.ShowSecondaryLogo && tournament.SecondaryLogo is not null) { using var _ = DocumentRendererActivitySource.LoadRemoteImage(CurrentActivity, tournament.SecondaryLogo, nameof(tournament.SecondaryLogo)); - column.Item().AlignRight().Unconstrained().TranslateX(-1.7f, Unit.Centimetre).Width(1.7f, Unit.Centimetre).Image(tournament.SecondaryLogo, imageStorage); + column.Item().LogoImage(tournament.SecondaryLogo, imageStorage, 1.7f, Unit.Centimetre, alignRight: true); } column.Item().PaddingVertical(10).MinHeight(12, Unit.Millimetre).AlignMiddle().Column(headerColumn =>