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]), + ]; +}