From 819a859a4632c71bd3ee1297140af2bdc9e33af6 Mon Sep 17 00:00:00 2001 From: rabea-Al Date: Thu, 9 Oct 2025 03:09:30 +0800 Subject: [PATCH 1/5] Add "View details" dialog for long notifications --- src/helpers/notificationAugmentor.ts | 74 ++++++++++++++++++++++++++++ src/index.tsx | 5 +- style/base.css | 12 +++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/helpers/notificationAugmentor.ts diff --git a/src/helpers/notificationAugmentor.ts b/src/helpers/notificationAugmentor.ts new file mode 100644 index 00000000..8b3e32fa --- /dev/null +++ b/src/helpers/notificationAugmentor.ts @@ -0,0 +1,74 @@ +import { Notification, showDialog, Dialog } from '@jupyterlab/apputils'; +import { Widget } from '@lumino/widgets'; + +const MAX_VISIBLE_CHARS = 140; +const VIEW_DETAILS_LABEL = 'View details'; + +function toPlainText(value: unknown): string { + try { + return typeof value === 'string' ? value : JSON.stringify(value, null, 2); + } catch { + return String(value ?? ''); + } +} + +function createViewDetailsAction(fullMessage: string, dialogTitle = 'Details'): Notification.IAction { + return { + label: VIEW_DETAILS_LABEL, + caption: 'Show full message', + callback: async () => { + const dialogBody = new Widget(); + dialogBody.addClass('xircuits-notification-details'); + + const pre = document.createElement('pre'); + pre.textContent = fullMessage; + dialogBody.node.appendChild(pre); + + const copyButton = Dialog.createButton({ label: 'Copy' }); + const closeButton = Dialog.okButton({ label: 'Close' }); + + const result = await showDialog({ + title: dialogTitle, + body: dialogBody, + buttons: [copyButton, closeButton] + }); + + if (result.button.label === 'Copy') { + await navigator.clipboard.writeText(fullMessage); + } + } + }; +} + +function ensureViewDetailsAction( + messageText: string, + options: any = {}, + title?: string +): any { + if (messageText.length <= MAX_VISIBLE_CHARS) return options; + const actions = [...(options.actions ?? [])]; + if (!actions.some((a: any) => a?.label === VIEW_DETAILS_LABEL)) { + actions.push(createViewDetailsAction(messageText, title)); + } + return { ...options, actions }; +} + +export function augmentNotifications(): void { + const NotificationObj: any = Notification as any; + if (NotificationObj.__xircuitsAugmented) return; + + const wrap = (method: 'error' | 'warning' | 'info' | 'success') => { + const original = NotificationObj[method]?.bind(Notification); + if (!original) return; + + NotificationObj[method] = (...args: any[]) => { + const [rawMessage, rawOptions] = args; + const text = toPlainText(rawMessage); + const options = ensureViewDetailsAction(text, rawOptions); + return original(text, options); + }; + }; + + ['error', 'warning', 'info', 'success'].forEach(wrap); + NotificationObj.__xircuitsAugmented = true; +} diff --git a/src/index.tsx b/src/index.tsx index 1853ae69..0a208081 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -34,7 +34,7 @@ import { commandIDs } from "./commands/CommandIDs"; import { IEditorTracker } from '@jupyterlab/fileeditor'; import { IMainMenu } from '@jupyterlab/mainmenu'; import { handleInstall } from './context-menu/TrayContextMenu'; - +import { augmentNotifications } from './helpers/notificationAugmentor'; import { installComponentPreview } from './component_info_sidebar/previewHelper'; const FACTORY = 'Xircuits editor'; @@ -80,6 +80,9 @@ const xircuits: JupyterFrontEndPlugin = { console.log('Xircuits is activated!'); + // Add "View details" to long notifications + augmentNotifications(); + // Creating the widget factory to register it so the document manager knows about // our new DocumentWidget const widgetFactory = new XircuitsFactory({ diff --git a/style/base.css b/style/base.css index 4642ce49..d6f1dc90 100644 --- a/style/base.css +++ b/style/base.css @@ -158,6 +158,18 @@ body.light-mode jp-button[title="Toggle Light/Dark Mode"] .moon { visibility: body.light-mode jp-button[title="Toggle Light/Dark Mode"] .sun { visibility: visible; } +.xircuits-notification-details { + max-height: 60vh; + overflow: auto; + padding: 0.25rem; +} +.xircuits-notification-details pre { + white-space: pre-wrap; + word-break: break-word; + margin: 0; + font-family: var(--jp-code-font-family); + font-size: var(--jp-code-font-size); +} From c207cb77d839b21daa651ee2aae593ca5f2f4521 Mon Sep 17 00:00:00 2001 From: rabea-al Date: Thu, 9 Oct 2025 16:28:36 +0800 Subject: [PATCH 2/5] clean up library ID returned by resolveLibraryForNode --- src/helpers/notificationEffects.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/helpers/notificationEffects.ts b/src/helpers/notificationEffects.ts index e3592f95..491d29c3 100644 --- a/src/helpers/notificationEffects.ts +++ b/src/helpers/notificationEffects.ts @@ -92,9 +92,11 @@ export async function resolveLibraryForNode( const candidateId = pathToLibraryId(extras.path); if (!candidateId) return { libId: null, status: 'unknown' }; + const cleanLibId = normalizeLibraryName(candidateId.replace(/^xai_components[\/\\]/i, '')); + const idx = await loadLibraryIndex(); - const entry = idx.get(candidateId); - return computeStatusFromEntry(entry, candidateId); + const entry = idx.get(cleanLibId); + return computeStatusFromEntry(entry, cleanLibId); } export async function showInstallForRemoteLibrary(args: { From 9a353a4a9b7c5c8f941aec01ed0cf6c339f252b9 Mon Sep 17 00:00:00 2001 From: rabea-Al Date: Sun, 12 Oct 2025 17:51:04 +0800 Subject: [PATCH 3/5] Improve inline copy button with temporary "Copied" hint in details dialog --- src/helpers/notificationAugmentor.ts | 49 +++++++++++++++++++++++----- src/helpers/notificationEffects.ts | 4 +-- style/base.css | 30 +++++++++++++++++ 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/helpers/notificationAugmentor.ts b/src/helpers/notificationAugmentor.ts index 8b3e32fa..38fe129e 100644 --- a/src/helpers/notificationAugmentor.ts +++ b/src/helpers/notificationAugmentor.ts @@ -1,5 +1,6 @@ import { Notification, showDialog, Dialog } from '@jupyterlab/apputils'; import { Widget } from '@lumino/widgets'; +import { copyIcon } from '@jupyterlab/ui-components'; const MAX_VISIBLE_CHARS = 140; const VIEW_DETAILS_LABEL = 'View details'; @@ -17,25 +18,55 @@ function createViewDetailsAction(fullMessage: string, dialogTitle = 'Details'): label: VIEW_DETAILS_LABEL, caption: 'Show full message', callback: async () => { + const DURATION = 1200; + const dialogBody = new Widget(); dialogBody.addClass('xircuits-notification-details'); + const wrap = document.createElement('div'); + wrap.className = 'x-details-copyWrap'; + + const copyBtn = document.createElement('button'); + copyBtn.className = 'x-copy-icon-btn jp-Button jp-mod-minimal'; + copyBtn.type = 'button'; + copyBtn.title = 'Copy'; + copyBtn.setAttribute('aria-label', 'Copy'); + copyIcon.element({ container: copyBtn, height: '16px', width: '16px' }); + + wrap.appendChild(copyBtn); + const pre = document.createElement('pre'); + pre.className = 'x-details-pre'; pre.textContent = fullMessage; - dialogBody.node.appendChild(pre); - const copyButton = Dialog.createButton({ label: 'Copy' }); - const closeButton = Dialog.okButton({ label: 'Close' }); + dialogBody.node.append(wrap, pre); + + let timer: number | null = null; + copyBtn.addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(fullMessage); - const result = await showDialog({ + copyBtn.classList.add('is-copied'); + copyBtn.title = 'Copied'; + copyBtn.setAttribute('aria-label', 'Copied'); + + if (timer) clearTimeout(timer); + timer = window.setTimeout(() => { + copyBtn.classList.remove('is-copied'); + copyBtn.title = 'Copy'; + copyBtn.setAttribute('aria-label', 'Copy'); + timer = null; + }, DURATION); + } catch (err) { + console.error('Copy failed', err); + } + }); + + await showDialog({ title: dialogTitle, body: dialogBody, - buttons: [copyButton, closeButton] + buttons: [Dialog.okButton({ label: 'Close' })] }); - - if (result.button.label === 'Copy') { - await navigator.clipboard.writeText(fullMessage); - } } }; } diff --git a/src/helpers/notificationEffects.ts b/src/helpers/notificationEffects.ts index 99faca72..e96a623f 100644 --- a/src/helpers/notificationEffects.ts +++ b/src/helpers/notificationEffects.ts @@ -92,10 +92,10 @@ export async function resolveLibraryForNode( const candidateId = pathToLibraryId(extras.path); if (!candidateId) return { libId: null, status: 'unknown' }; - const cleanLibId = normalizeLibraryName(candidateId.replace(/^xai_components[\/\\]/i, '')); + const cleanLibId = candidateId.replace(/^xai_components[\/\\]/i, ''); const idx = await loadLibraryIndex(); - const entry = idx.get(cleanLibId); + const entry = idx.get(candidateId); return computeStatusFromEntry(entry, cleanLibId); } diff --git a/style/base.css b/style/base.css index d6f1dc90..338280db 100644 --- a/style/base.css +++ b/style/base.css @@ -172,4 +172,34 @@ body.light-mode jp-button[title="Toggle Light/Dark Mode"] .sun { visibility: v font-size: var(--jp-code-font-size); } +.xircuits-notification-details { position: relative; } + +.x-details-copyWrap { + position: absolute; + top: 6px; + right: 6px; +} + +.x-copy-icon-btn { position: relative; } + +.x-copy-icon-btn.is-copied::after { + content: "Copied"; + position: absolute; + top: 50%; + right: calc(100% + 8px); + transform: translateY(-50%); + white-space: nowrap; + pointer-events: none; + padding: 2px 6px; + background: var(--jp-layout-color2); + border: 1px solid var(--jp-border-color2); + border-radius: 4px; + font-size: var(--jp-ui-font-size1); + line-height: 1.4; + z-index: 1; +} + +.x-details-pre { margin-top: 28px; } + + From 9d15615e2ae6e147cce835be13036e1bc4cc1a98 Mon Sep 17 00:00:00 2001 From: rabea-Al Date: Mon, 13 Oct 2025 18:32:06 +0800 Subject: [PATCH 4/5] Fix copy button overlap --- style/base.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/style/base.css b/style/base.css index 338280db..45f55e7e 100644 --- a/style/base.css +++ b/style/base.css @@ -199,7 +199,9 @@ body.light-mode jp-button[title="Toggle Light/Dark Mode"] .sun { visibility: v z-index: 1; } -.x-details-pre { margin-top: 28px; } +.xircuits-notification-details .x-details-pre { + margin-top: 36px; +} From 41bc12f31535d116931fcbd16b5cf1981face11f Mon Sep 17 00:00:00 2001 From: rabea-Al Date: Tue, 14 Oct 2025 00:42:57 +0800 Subject: [PATCH 5/5] Revert "Improve inline copy button with temporary "Copied" hint in details dialog" This reverts commit 9a353a4a9b7c5c8f941aec01ed0cf6c339f252b9. --- src/helpers/notificationAugmentor.ts | 49 +++++----------------------- style/base.css | 36 +------------------- 2 files changed, 10 insertions(+), 75 deletions(-) diff --git a/src/helpers/notificationAugmentor.ts b/src/helpers/notificationAugmentor.ts index 38fe129e..8b3e32fa 100644 --- a/src/helpers/notificationAugmentor.ts +++ b/src/helpers/notificationAugmentor.ts @@ -1,6 +1,5 @@ import { Notification, showDialog, Dialog } from '@jupyterlab/apputils'; import { Widget } from '@lumino/widgets'; -import { copyIcon } from '@jupyterlab/ui-components'; const MAX_VISIBLE_CHARS = 140; const VIEW_DETAILS_LABEL = 'View details'; @@ -18,55 +17,25 @@ function createViewDetailsAction(fullMessage: string, dialogTitle = 'Details'): label: VIEW_DETAILS_LABEL, caption: 'Show full message', callback: async () => { - const DURATION = 1200; - const dialogBody = new Widget(); dialogBody.addClass('xircuits-notification-details'); - const wrap = document.createElement('div'); - wrap.className = 'x-details-copyWrap'; - - const copyBtn = document.createElement('button'); - copyBtn.className = 'x-copy-icon-btn jp-Button jp-mod-minimal'; - copyBtn.type = 'button'; - copyBtn.title = 'Copy'; - copyBtn.setAttribute('aria-label', 'Copy'); - copyIcon.element({ container: copyBtn, height: '16px', width: '16px' }); - - wrap.appendChild(copyBtn); - const pre = document.createElement('pre'); - pre.className = 'x-details-pre'; pre.textContent = fullMessage; + dialogBody.node.appendChild(pre); - dialogBody.node.append(wrap, pre); - - let timer: number | null = null; - copyBtn.addEventListener('click', async () => { - try { - await navigator.clipboard.writeText(fullMessage); + const copyButton = Dialog.createButton({ label: 'Copy' }); + const closeButton = Dialog.okButton({ label: 'Close' }); - copyBtn.classList.add('is-copied'); - copyBtn.title = 'Copied'; - copyBtn.setAttribute('aria-label', 'Copied'); - - if (timer) clearTimeout(timer); - timer = window.setTimeout(() => { - copyBtn.classList.remove('is-copied'); - copyBtn.title = 'Copy'; - copyBtn.setAttribute('aria-label', 'Copy'); - timer = null; - }, DURATION); - } catch (err) { - console.error('Copy failed', err); - } - }); - - await showDialog({ + const result = await showDialog({ title: dialogTitle, body: dialogBody, - buttons: [Dialog.okButton({ label: 'Close' })] + buttons: [copyButton, closeButton] }); + + if (result.button.label === 'Copy') { + await navigator.clipboard.writeText(fullMessage); + } } }; } diff --git a/style/base.css b/style/base.css index 45f55e7e..8bdd090b 100644 --- a/style/base.css +++ b/style/base.css @@ -170,38 +170,4 @@ body.light-mode jp-button[title="Toggle Light/Dark Mode"] .sun { visibility: v margin: 0; font-family: var(--jp-code-font-family); font-size: var(--jp-code-font-size); -} - -.xircuits-notification-details { position: relative; } - -.x-details-copyWrap { - position: absolute; - top: 6px; - right: 6px; -} - -.x-copy-icon-btn { position: relative; } - -.x-copy-icon-btn.is-copied::after { - content: "Copied"; - position: absolute; - top: 50%; - right: calc(100% + 8px); - transform: translateY(-50%); - white-space: nowrap; - pointer-events: none; - padding: 2px 6px; - background: var(--jp-layout-color2); - border: 1px solid var(--jp-border-color2); - border-radius: 4px; - font-size: var(--jp-ui-font-size1); - line-height: 1.4; - z-index: 1; -} - -.xircuits-notification-details .x-details-pre { - margin-top: 36px; -} - - - +} \ No newline at end of file