From 71c7e963274f468ab7fda0f54baa3f19401bc4d2 Mon Sep 17 00:00:00 2001
From: ROpdebee <15186467+ROpdebee@users.noreply.github.com>
Date: Thu, 28 Jul 2022 15:36:11 +0200
Subject: [PATCH] fix(ecau): wrap long URLs in logging banners
Discogs URLs tend to overflow the logging banner. Firefox performs
sub-optimal line breaking (it breaks in the middle of the URL), but
Chrome performs no line breaking at all.
To fix this, we'll insert word-break hints in front of all forward
slashes, which should convince the browsers to perform proper line
breaking. However, since it's still possible that an enormous URL
without forward slashes will appear, we're also setting a CSS
property to allow all browsers to do arbitrary word breaking in case
it's really necessary.
---
src/lib/logging/guiSink.scss | 2 ++
src/lib/logging/guiSink.tsx | 12 ++++++++++--
src/lib/util/array.ts | 23 +++++++++++++++++++++++
3 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/src/lib/logging/guiSink.scss b/src/lib/logging/guiSink.scss
index 17338bb1c..1f200635f 100644
--- a/src/lib/logging/guiSink.scss
+++ b/src/lib/logging/guiSink.scss
@@ -10,6 +10,8 @@
border: 1px solid;
border-radius: 4px;
font-weight: 500;
+ // Ensure decent wrapping across all browsers.
+ overflow-wrap: break-word;
// Colours come from bootstrap.
&.error {
diff --git a/src/lib/logging/guiSink.tsx b/src/lib/logging/guiSink.tsx
index 6fb9898ef..bc1368337 100644
--- a/src/lib/logging/guiSink.tsx
+++ b/src/lib/logging/guiSink.tsx
@@ -1,3 +1,4 @@
+import { insertBetween } from '@lib/util/array';
import { insertStylesheet } from '@lib/util/css';
import type { LoggingSink } from './sink';
@@ -20,8 +21,15 @@ export class GuiSink implements LoggingSink {
private createMessage(className: string, message: string, exception?: unknown): HTMLSpanElement {
const extraMessage = exception instanceof Error ? `: ${exception.message}` : '';
const content = message + extraMessage;
-
- return {content};
+ // Insert word-break hints before all forward slashes so that URLs are
+ // more likely to be split in natural places. In case it's still too
+ // long, CSS properties will break even further, but without these,
+ // the long URLs tend to be put on their own line.
+ // Need to use a factory for insertBetween, otherwise the same
+ // element will be reused and it'll only be placed at the last slash.
+ const children = insertBetween(content.split(/(?=\/)/), () => );
+
+ return {children};
}
private addMessage(el: HTMLSpanElement): void {
diff --git a/src/lib/util/array.ts b/src/lib/util/array.ts
index a224947ff..4b4609fc0 100644
--- a/src/lib/util/array.ts
+++ b/src/lib/util/array.ts
@@ -44,3 +44,26 @@ export function collatedSort(array: string[]): string[] {
export function enumerate(array: T[]): Array<[T, number]> {
return array.map((el, idx) => [el, idx]);
}
+
+function isFactory(maybeFactory: T2 | (() => T2)): maybeFactory is () => T2 {
+ return typeof maybeFactory === 'function';
+}
+
+/**
+ * Create an array wherein a given element is inserted between every two
+ * consecutive elements of the original array.
+ *
+ * Example:
+ * insertBetween([1,2,3], 0) // => [1, 0, 2, 0, 3]
+ * insertBetween([1], 0) // => [1]
+ *
+ * @param {readonly T1[]} arr The original array.
+ * @param {T2} newElement The element to insert, or a factory creating these elements.
+ * @return {(Array)} Resulting array.
+ */
+export function insertBetween(arr: readonly T1[], newElement: T2 | (() => T2)): Array {
+ return [
+ ...arr.slice(0, 1),
+ ...arr.slice(1).flatMap((elmt) => [isFactory(newElement) ? newElement() : newElement, elmt]),
+ ];
+}