From 5e3fa90f8a924842636d0ba5cf84300eca180fd8 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:16:48 +0200 Subject: [PATCH 1/3] [OGUI-1906] Add copy button and hover-to-persist behaviour Notifications now pause auto-hide while hovered, so users have time to read longer or normal message at their leisure. Added a copy-to-clipboard button inside the notification, flush with the right edge, that copies the message and displays a new "Text copied to clipboard" notification when clicked. --- Framework/Frontend/css/src/bootstrap.css | 6 ++-- Framework/Frontend/js/src/Notification.js | 34 +++++++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Framework/Frontend/css/src/bootstrap.css b/Framework/Frontend/css/src/bootstrap.css index c5415d655..f7adb68f5 100644 --- a/Framework/Frontend/css/src/bootstrap.css +++ b/Framework/Frontend/css/src/bootstrap.css @@ -359,8 +359,10 @@ label { display: block; margin-bottom: 0.5em; } .notification { position: absolute; top: 2em; width: 100%; text-align: center; cursor: pointer; pointer-events: none; } .notification-content { transition: opacity 0.8s cubic-bezier(0.18, 0.89, 0.34, 1.11), filter 0.8s cubic-bezier(0.18, 0.89, 0.34, 1.11); } -.notification-open { display: inline; opacity: 1; filter: blur(0); pointer-events: all; } -.notification-close { display: inline; opacity: 0; filter: blur(5em); pointer-events: none; } +.notification-open { display: inline-flex; opacity: 1; filter: blur(0); pointer-events: all; } +.notification-close { display: inline-flex; opacity: 0; filter: blur(5em); pointer-events: none; } + /* border radius none on the left keep on right */ +.notification .btn { border-radius: 0 0.25em 0.25em 0; } /* Basic table */ diff --git a/Framework/Frontend/js/src/Notification.js b/Framework/Frontend/js/src/Notification.js index b70ce520e..3ec97bcef 100644 --- a/Framework/Frontend/js/src/Notification.js +++ b/Framework/Frontend/js/src/Notification.js @@ -15,6 +15,9 @@ import Observable from './Observable.js'; import { h } from './renderer.js'; import switchCase from './switchCase.js'; +import { iconClipboard } from './icons.js'; + +const COPY_CONFIRMATION = 'Text copied to clipboard'; /** * Container of notification with time management @@ -49,6 +52,8 @@ export class Notification extends Observable { this.type = 'primary'; this.state = 'hidden'; // Shown, hidden this.timerId = 0; // Timer to auto-hide notification + this.duration = 5000; // Original duration of the current notification + this.hovered = false; // Whether the notification is hovered } /** @@ -76,11 +81,14 @@ export class Notification extends Observable { this.message = message; this.type = type; this.state = 'shown'; + this.duration = duration; // Auto-hide after duration if (duration !== Infinity) { this.timerId = setTimeout(() => { - this.hide(); + if (!this.hovered) { + this.hide(); + } }, duration); } @@ -124,13 +132,33 @@ export class Notification extends Observable { */ export const notification = (notificationInstance) => h('.notification.text-no-select.level4.text-light', { -}, h('span.notification-content.br2.p2.shadow-level4', { +}, h('div.notification-content.br2.shadow-level4', { // ClassName: notificationInstance.message && (notificationInstance.state === 'shown' ? 'notification-open' : 'notification-close'), onclick: () => notificationInstance.hide(), + onmouseenter: () => { + notificationInstance.hovered = true; + }, + onmouseleave: () => { + notificationInstance.hovered = false; + // If this is not present then will show again when clicked close because of mouseLeave event + if (notificationInstance.state === 'shown') { + notificationInstance.show(notificationInstance.message, notificationInstance.type, notificationInstance.duration); + } + }, className: `${switchCase(notificationInstance.type, { primary: 'white bg-primary', success: 'white bg-success', warning: 'white bg-warning', danger: 'white bg-danger', })} ${notificationInstance.state === 'shown' ? 'notification-open' : 'notification-close'}`, -}, notificationInstance.message)); +}, [ + h('div.mh2.pv2', notificationInstance.message), + notificationInstance.message !== COPY_CONFIRMATION && h(`button.btn.btn-${notificationInstance.type}.br0`, { + title: 'Copy to clipboard', + onclick: (e) => { + e.stopPropagation(); + navigator.clipboard.writeText(notificationInstance.message) + .then(() => notificationInstance.show(COPY_CONFIRMATION, notificationInstance.type, 1500)); + }, + }, iconClipboard()), +])); From 71f96db98ff6b45b23f054a4528dc59164ca389d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:22:10 +0200 Subject: [PATCH 2/3] [OGUI-1906] Move onClick hide to more specific div --- Framework/Frontend/js/src/Notification.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Framework/Frontend/js/src/Notification.js b/Framework/Frontend/js/src/Notification.js index 3ec97bcef..fd573ec4a 100644 --- a/Framework/Frontend/js/src/Notification.js +++ b/Framework/Frontend/js/src/Notification.js @@ -134,7 +134,6 @@ export const notification = (notificationInstance) => h('.notification.text-no-s }, h('div.notification-content.br2.shadow-level4', { // ClassName: notificationInstance.message && (notificationInstance.state === 'shown' ? 'notification-open' : 'notification-close'), - onclick: () => notificationInstance.hide(), onmouseenter: () => { notificationInstance.hovered = true; }, @@ -152,7 +151,7 @@ export const notification = (notificationInstance) => h('.notification.text-no-s danger: 'white bg-danger', })} ${notificationInstance.state === 'shown' ? 'notification-open' : 'notification-close'}`, }, [ - h('div.mh2.pv2', notificationInstance.message), + h('div.mh2.pv2', { onclick: () => notificationInstance.hide() }, notificationInstance.message), notificationInstance.message !== COPY_CONFIRMATION && h(`button.btn.btn-${notificationInstance.type}.br0`, { title: 'Copy to clipboard', onclick: (e) => { From 9ec26bbfa3a9d230465825770af13cd4519d467f Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:25:39 +0200 Subject: [PATCH 3/3] [OGUI-1906] Add better CSS selector --- Framework/Frontend/css/src/bootstrap.css | 3 +-- Framework/Frontend/js/src/Notification.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Framework/Frontend/css/src/bootstrap.css b/Framework/Frontend/css/src/bootstrap.css index f7adb68f5..a5b256474 100644 --- a/Framework/Frontend/css/src/bootstrap.css +++ b/Framework/Frontend/css/src/bootstrap.css @@ -361,8 +361,7 @@ label { display: block; margin-bottom: 0.5em; } .notification-content { transition: opacity 0.8s cubic-bezier(0.18, 0.89, 0.34, 1.11), filter 0.8s cubic-bezier(0.18, 0.89, 0.34, 1.11); } .notification-open { display: inline-flex; opacity: 1; filter: blur(0); pointer-events: all; } .notification-close { display: inline-flex; opacity: 0; filter: blur(5em); pointer-events: none; } - /* border radius none on the left keep on right */ -.notification .btn { border-radius: 0 0.25em 0.25em 0; } +.notification-copy-btn { border-radius: 0 0.25em 0.25em 0; } /* Basic table */ diff --git a/Framework/Frontend/js/src/Notification.js b/Framework/Frontend/js/src/Notification.js index fd573ec4a..ca92d1e04 100644 --- a/Framework/Frontend/js/src/Notification.js +++ b/Framework/Frontend/js/src/Notification.js @@ -152,7 +152,7 @@ export const notification = (notificationInstance) => h('.notification.text-no-s })} ${notificationInstance.state === 'shown' ? 'notification-open' : 'notification-close'}`, }, [ h('div.mh2.pv2', { onclick: () => notificationInstance.hide() }, notificationInstance.message), - notificationInstance.message !== COPY_CONFIRMATION && h(`button.btn.btn-${notificationInstance.type}.br0`, { + notificationInstance.message !== COPY_CONFIRMATION && h(`button.btn.btn-${notificationInstance.type}.notification-copy-btn`, { title: 'Copy to clipboard', onclick: (e) => { e.stopPropagation();