From 49219aad6c0c14b5155fa27ac5b2e4a3cf52a2d6 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:17:43 +0100 Subject: [PATCH 01/10] Log Viewer: Refactor log types chart to use Lit repeat directive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Import and use repeat directive for better performance - Add _logLevelKeys state property to track log level keys - Update setLogLevelCount() to populate _logLevelKeys - Replace .map() with repeat() in render method for legend and donut slices - Update willUpdate to observe both filter and response changes - Resolves TODO comment about using repeat directive 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../log-viewer-log-types-chart.element.ts | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts index c314fa23e6c2..fa2372aeafe2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts @@ -1,5 +1,5 @@ import { UMB_APP_LOG_VIEWER_CONTEXT } from '../../../logviewer-workspace.context-token.js'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { LogLevelCountsReponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { consumeContext } from '@umbraco-cms/backoffice/context-api'; @@ -27,8 +27,11 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { @state() private _logLevelCountFilter: string[] = []; + @state() + private _logLevelKeys: string[] = []; + protected override willUpdate(_changedProperties: Map): void { - if (_changedProperties.has('_logLevelCountFilter')) { + if (_changedProperties.has('_logLevelCountFilter') || _changedProperties.has('_logLevelCountResponse')) { this.setLogLevelCount(); } } @@ -43,9 +46,15 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { } setLogLevelCount() { - this._logLevelCount = this._logLevelCountResponse - ? Object.entries(this._logLevelCountResponse).filter(([level]) => !this._logLevelCountFilter.includes(level)) - : []; + if (this._logLevelCountResponse) { + this._logLevelKeys = Object.keys(this._logLevelCountResponse); + this._logLevelCount = Object.entries(this._logLevelCountResponse).filter( + ([level]) => !this._logLevelCountFilter.includes(level), + ); + } else { + this._logLevelKeys = []; + this._logLevelCount = []; + } } #observeStuff() { @@ -55,43 +64,42 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { }); } - // TODO: Stop using this complex code in render methods, instead changes to _logLevelCount should trigger a state prop containing the keys. And then try to make use of the repeat LIT method: override render() { return html`
    - ${this._logLevelCountResponse - ? Object.keys(this._logLevelCountResponse).map( - (level) => - html`
  • - -
  • `, - ) - : ''} + ${repeat( + this._logLevelKeys, + (level) => level, + (level) => + html`
  • + +
  • `, + )}
- - ${this._logLevelCountResponse - ? this._logLevelCount.map( - ([level, number]) => - html` `, - ) - : ''} + + ${repeat( + this._logLevelCount, + ([level]) => level, + ([level, number]) => + html``, + )}
From 4de030a95b77f08a127fd4c3976009ced201f657 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:18:54 +0100 Subject: [PATCH 02/10] Donut Chart: Add inline numbers and fix tooltip positioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add showInlineNumbers property to optionally display numbers inside slices - Implement #getTextPosition() method to calculate text position at slice center - Render SVG text elements when showInlineNumbers is enabled - Fix tooltip positioning to appear near cursor (changed from x-10, y-70 to x+10, y+10) - Recalculate container bounds on each mouse move to handle window resize - Add pointer-events: none to tooltip to prevent mouse interference - Add CSS styling for slice numbers (user-select: none) - Enable inline numbers by default in log viewer log types chart 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../donut-chart/donut-chart.element.ts | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts index 095b60c80edc..c4b73a577d1b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts @@ -72,6 +72,13 @@ export class UmbDonutChartElement extends LitElement { @property({ type: Boolean }) hideDetailBox = false; + /** + * Shows numbers inside each slice of the donut chart + * @memberof UmbDonutChartElement + */ + @property({ type: Boolean, attribute: 'show-inline-numbers' }) + showInlineNumbers = false; + @queryAssignedElements({ selector: 'umb-donut-slice' }) private _slices!: UmbDonutSliceElement[]; @@ -180,11 +187,28 @@ export class UmbDonutChartElement extends LitElement { return [coordX, coordY].join(' '); } + #getTextPosition(circle: CircleWithCommands): { x: number; y: number } { + // Calculate the middle angle of the slice + const startAngle = -circle.offset; + const sliceDegrees = UmbDonutChartElement.percentToDegrees(circle.percent); + const middleAngle = startAngle + sliceDegrees / 2; + + // Position the text at the middle of the donut ring + const textRadius = this.radius - this.borderSize / 2; + const angleRad = (middleAngle * Math.PI) / 180; + const x = Math.cos(angleRad) * textRadius + this.svgSize / 2; + const y = -Math.sin(angleRad) * textRadius + this.svgSize / 2; + + return { x, y }; + } + #calculateDetailsBoxPosition = (event: MouseEvent) => { + // Recalculate bounds on each mouse move to handle window resize + this.#containerBounds = this._container.getBoundingClientRect(); const x = this.#containerBounds ? event.clientX - this.#containerBounds?.left : 0; const y = this.#containerBounds ? event.clientY - this.#containerBounds?.top : 0; - this._posX = x - 10; - this._posY = y - 70; + this._posX = x + 10; + this._posY = y + 10; }; #setDetailsBoxData(event: MouseEvent) { @@ -251,7 +275,21 @@ export class UmbDonutChartElement extends LitElement { role="listitem" d="${circle.commands}" transform="rotate(${circle.offset} ${this._viewBox / 2} ${this._viewBox / 2})"> - `, + + ${ + this.showInlineNumbers + ? svg`${circle.number}` + : '' + }`, )} `; @@ -311,6 +349,7 @@ export class UmbDonutChartElement extends LitElement { transform: translate3d(var(--pos-x), var(--pos-y), 0); transition: transform 0.2s cubic-bezier(0.02, 1.23, 0.79, 1.08); transition: opacity 150ms linear; + pointer-events: none; } #details-box.show { @@ -328,6 +367,10 @@ export class UmbDonutChartElement extends LitElement { display: flex; align-items: center; } + + .slice-number { + user-select: none; + } `, ]; } From a471c726442b520bd0a74341f92e7f1847ac0bd5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:24:59 +0100 Subject: [PATCH 03/10] Donut Chart: Add clickable slices and visible description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add href property to donut-slice element for clickable slices - Wrap SVG paths in tags when href is provided - Update Circle interface to include href property - Add showDescription property to optionally display description text - Render description as visible text below the chart - Add CSS styling for description text - Update log-types-chart to build search URLs with log level and date range - Observe dateRange from context to build accurate search URLs - Enable clickable slices and visible description in log-types-chart Now clicking on a donut slice navigates to the search view filtered by that log level and the current date range. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../donut-chart/donut-chart.element.ts | 28 +++++++++++++++++-- .../donut-chart/donut-slice.element.ts | 7 +++++ .../log-viewer-log-types-chart.element.ts | 27 +++++++++++++++++- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts index c4b73a577d1b..03d98eadabca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts @@ -19,6 +19,7 @@ interface Circle { percent: number; kind: string; number: number; + href: string; } interface CircleWithCommands extends Circle { @@ -79,6 +80,13 @@ export class UmbDonutChartElement extends LitElement { @property({ type: Boolean, attribute: 'show-inline-numbers' }) showInlineNumbers = false; + /** + * Shows the description text below the chart + * @memberof UmbDonutChartElement + */ + @property({ type: Boolean, attribute: 'show-description' }) + showDescription = false; + @queryAssignedElements({ selector: 'umb-donut-slice' }) private _slices!: UmbDonutSliceElement[]; @@ -148,6 +156,7 @@ export class UmbDonutChartElement extends LitElement { color: slice.color, name: slice.name, kind: slice.kind, + href: slice.href, }; }), ); @@ -256,10 +265,10 @@ export class UmbDonutChartElement extends LitElement { ${this.description} ${this._circles.map( - (circle, i) => svg` + (circle, i) => { + const content = svg` ${circle.number}` : '' - }`, + }`; + + return circle.href + ? svg`${content}` + : content; + }, )} `; @@ -305,6 +319,7 @@ export class UmbDonutChartElement extends LitElement { ${this._detailAmount} ${this._detailKind} + ${this.showDescription && this.description ? html`

${this.description}

` : ''} `; } @@ -371,6 +386,13 @@ export class UmbDonutChartElement extends LitElement { .slice-number { user-select: none; } + + .description { + text-align: center; + font-size: var(--uui-type-small-size); + color: var(--uui-color-text-alt); + margin: var(--uui-size-space-2) 0 0 0; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-slice.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-slice.element.ts index 36a2de1f8b98..960ba0775323 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-slice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-slice.element.ts @@ -32,6 +32,13 @@ export class UmbDonutSliceElement extends LitElement { @property() kind = ''; + /** + * Optional href to make the slice clickable + * @memberof UmbDonutSliceElement + */ + @property() + href = ''; + override willUpdate() { this.dispatchEvent(new CustomEvent('slice-update', { composed: true, bubbles: true })); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts index fa2372aeafe2..e64ab98596af 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts @@ -18,6 +18,9 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { return this.#logViewerContext; } + @state() + private _dateRange = { startDate: '', endDate: '' }; + @state() private _logLevelCountResponse: LogLevelCountsReponseModel | null = null; @@ -62,6 +65,24 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { this._logLevelCountResponse = logLevel ?? null; this.setLogLevelCount(); }); + + this.observe(this._logViewerContext?.dateRange, (dateRange) => { + if (dateRange) { + this._dateRange = dateRange; + } + }); + } + + #buildSearchUrl(level: string): string { + const params = new URLSearchParams(); + params.set('loglevels', level); + if (this._dateRange.startDate) { + params.set('startDate', this._dateRange.startDate); + } + if (this._dateRange.endDate) { + params.set('endDate', this._dateRange.endDate); + } + return `view/search/?${params.toString()}`; } override render() { @@ -89,7 +110,10 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { )} - + ${repeat( this._logLevelCount, ([level]) => level, @@ -98,6 +122,7 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { .name=${level} .amount=${number} .kind=${'messages'} + .href=${this.#buildSearchUrl(level)} .color="${`var(--umb-log-viewer-${level.toLowerCase()}-color)`}">`, )} From a2eec64bb501a48fe562a9dd00b839dfc6dd79e9 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:26:24 +0100 Subject: [PATCH 04/10] fix: uses whole link --- .../overview/components/log-viewer-log-types-chart.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts index e64ab98596af..05a386a772b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts @@ -82,7 +82,7 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { if (this._dateRange.endDate) { params.set('endDate', this._dateRange.endDate); } - return `view/search/?${params.toString()}`; + return `section/settings/workspace/logviewer/view/search/?${params.toString()}`; } override render() { From f27ebb52daa89f732976155b3f5bdb4a16ee85b1 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:28:00 +0100 Subject: [PATCH 05/10] Log Viewer: Fix log types chart layout for larger screens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add media query to switch from column to row layout on screens wider than 768px. This ensures the legend and donut chart are displayed side by side on desktop resolutions instead of stacked vertically. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/log-viewer-log-types-chart.element.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts index 05a386a772b3..2a913ad7652f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts @@ -141,6 +141,13 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { justify-content: space-between; } + @media (min-width: 768px) { + #log-types-container { + flex-direction: row; + align-items: flex-start; + } + } + button { all: unset; display: flex; From f6c66ec655087c7c96e540d57cc3b0cb4406a56f Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:44:35 +0100 Subject: [PATCH 06/10] chore: improves mock function --- .../src/mocks/data/log-viewer.data.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts index 96872fa203a9..d45cf85c6a18 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts @@ -43,15 +43,15 @@ class UmbLogViewerMessagesData extends UmbMockDBBase { return this.data.slice(skip, take); } - getLevelCount() { - const levels = this.data.map((log) => log.level?.toLowerCase() ?? 'unknown'); - const counts = {}; - levels.forEach((level: string) => { - //eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - counts[level ?? 'unknown'] = (counts[level] || 0) + 1; - }); - return counts; + getLevelCount(): Record { + return this.data.reduce( + (counts, log) => { + const level = log.level ?? 'unknown'; + counts[level] = (counts[level] || 0) + 1; + return counts; + }, + {} as Record, + ); } } From 5b4633513819b18fdd78452f9cb16d9cf791007a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:46:05 +0100 Subject: [PATCH 07/10] chore: formatting --- .../donut-chart/donut-chart.element.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts index 03d98eadabca..39a5f7e86982 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts @@ -264,9 +264,8 @@ export class UmbDonutChartElement extends LitElement { ${this.description} - ${this._circles.map( - (circle, i) => { - const content = svg` + ${this._circles.map((circle, i) => { + const content = svg` ${content}` - : content; - }, - )} + return circle.href ? svg`${content}` : content; + })} `; } @@ -387,12 +383,12 @@ export class UmbDonutChartElement extends LitElement { user-select: none; } - .description { - text-align: center; - font-size: var(--uui-type-small-size); - color: var(--uui-color-text-alt); - margin: var(--uui-size-space-2) 0 0 0; - } + .description { + text-align: center; + font-size: var(--uui-type-small-size); + color: var(--uui-color-text-alt); + margin: var(--uui-size-space-2) 0 0 0; + } `, ]; } From 52d36ca94a2666abf6c2b9dcf92d373fc20fc1c2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:57:20 +0100 Subject: [PATCH 08/10] fix: ensures the donut chart works responsively --- .../donut-chart/donut-chart.element.ts | 8 +- .../log-viewer-log-types-chart.element.ts | 74 +++++++++++++------ 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts index 39a5f7e86982..9118c316a7e2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/components/donut-chart/donut-chart.element.ts @@ -307,7 +307,9 @@ export class UmbDonutChartElement extends LitElement { override render() { return html`
- ${this.#renderCircles()} + + ${this.#renderCircles()} +
@@ -341,7 +343,9 @@ export class UmbDonutChartElement extends LitElement { #container { position: relative; - width: 200px; + width: 100%; + max-width: 200px; + aspect-ratio: 1; } #details-box { diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts index 2a913ad7652f..64b93e27733d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts @@ -88,7 +88,21 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { override render() { return html` +

In the chosen date range you have this number of log message of type:

+ + ${repeat( + this._logLevelCount, + ([level]) => level, + ([level, number]) => + html``, + )} +
    ${repeat( @@ -110,22 +124,6 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { )}
- - ${repeat( - this._logLevelCount, - ([level]) => level, - ([level, number]) => - html``, - )} -
`; @@ -133,18 +131,48 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { static override styles = [ css` + uui-box { + container-type: inline-size; + } + + #description { + text-align: center; + font-size: var(--uui-type-small-size); + color: var(--uui-color-text-alt); + margin: 0 0 var(--uui-size-space-4) 0; + } + #log-types-container { - display: flex; + display: grid; gap: var(--uui-size-space-4); - flex-direction: column-reverse; - align-items: center; - justify-content: space-between; + grid-template-columns: 1fr; + place-items: center; } - @media (min-width: 768px) { + umb-donut-chart { + width: 100%; + max-width: 200px; + } + + #legend { + width: 100%; + display: flex; + justify-content: center; + } + + @container (min-width: 312px) { #log-types-container { - flex-direction: row; - align-items: flex-start; + grid-template-columns: auto 1fr; + place-items: start; + } + + umb-donut-chart { + max-width: 200px; + } + + #legend { + width: auto; + justify-content: flex-start; } } From 72d77152aaa700308fcdaac84508a8020ac79841 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:32:40 +0100 Subject: [PATCH 09/10] feat: adds support for SVGAElement in the router --- .../core/router/router-slot/util/anchor.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/util/anchor.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/util/anchor.ts index b0fff6303cd8..d61dfe07acf2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/util/anchor.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/router-slot/util/anchor.ts @@ -1,5 +1,5 @@ /** - * Hook up a click listener to the window that, for all anchor tags + * Hook up a click listener to the window that, for all anchor tags (HTML or SVG) * that has a relative HREF, uses the history API instead. */ export function ensureAnchorHistory() { @@ -10,37 +10,50 @@ export function ensureAnchorHistory() { if ((isWindows && e.ctrlKey) || (!isWindows && e.metaKey)) return; // Find the target by using the composed path to get the element through the shadow boundaries. + // Support both HTML anchor tags and SVG anchor tags const $anchor = (('composedPath' in e) as any) - ? e.composedPath().find(($elem) => $elem instanceof HTMLAnchorElement) + ? e.composedPath().find(($elem) => $elem instanceof HTMLAnchorElement || $elem instanceof SVGAElement) : e.target; - // Abort if the event is not about the anchor tag - if ($anchor == null || !($anchor instanceof HTMLAnchorElement)) { + // Abort if the event is not about an anchor tag (HTML or SVG) + if ($anchor == null || !($anchor instanceof HTMLAnchorElement || $anchor instanceof SVGAElement)) { return; } // Get the HREF value from the anchor tag - const href = $anchor.href; + // SVGAElement.href returns SVGAnimatedString, so we need to access .baseVal + const href = $anchor instanceof SVGAElement ? $anchor.href.baseVal : $anchor.href; + const target = $anchor instanceof SVGAElement ? $anchor.target.baseVal : $anchor.target; + + // For SVG anchors, we need to construct a full URL to extract pathname, search, and hash + // For HTML anchors, these properties are directly available + let fullUrl: URL; + try { + // Use the current document location as the base to resolve relative URLs + // This respects the tag and works the same as HTML anchors + // Note: This may resolve into an external URL, but we validate that later + fullUrl = new URL(href, document.location.origin); + } catch { + // Invalid URL, skip + return; + } // Only handle the anchor tag if the follow holds true: // - The HREF is relative to the origin of the current location. // - The target is targeting the current frame. // - The anchor doesn't have the attribute [data-router-slot]="disabled" if ( - !href.startsWith(location.origin) || - ($anchor.target !== '' && $anchor.target !== '_self') || + fullUrl.origin !== location.origin || + (target !== '' && target !== '_self') || $anchor.dataset['routerSlot'] === 'disabled' ) { return; } - // Remove the origin from the start of the HREF to get the path - const path = $anchor.pathname + $anchor.search + $anchor.hash; - // Prevent the default behavior e.preventDefault(); // Change the history! - history.pushState(null, '', path); + history.pushState(null, '', fullUrl); }); } From fb4e8e4a9f2429596394390ffa7b4dfd083b24ef Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:04:41 +0100 Subject: [PATCH 10/10] adds key for description --- src/Umbraco.Web.UI.Client/src/assets/lang/en.ts | 1 + .../components/log-viewer-log-types-chart.element.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 839dfc9e7ec8..e3622fa8adfa 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -2500,6 +2500,7 @@ export default { pollingEvery10: 'Polling every 10s', pollingEvery20: 'Polling every 20s', pollingEvery30: 'Polling every 30s', + logTypesChartDescription: 'In the chosen date range, you have this number of log messages grouped by type:', }, clipboard: { labelForCopyAllEntries: 'Copy %0%', diff --git a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts index 64b93e27733d..201a3f1964bd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/log-viewer/workspace/views/overview/components/log-viewer-log-types-chart.element.ts @@ -88,7 +88,11 @@ export class UmbLogViewerLogTypesChartElement extends UmbLitElement { override render() { return html` -

In the chosen date range you have this number of log message of type:

+

+ + In the chosen date range, you have this number of log messages grouped by type: + +

${repeat(