From bc0aa13c132848f940b46331486c5d79104a491e Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Tue, 22 Apr 2025 13:58:13 +0200 Subject: [PATCH 1/8] Now opens editor and viewer --- src/index.ts | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 013accf..05e946a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -98,6 +98,13 @@ const extension: JupyterFrontEndPlugin = { tracker.save(widget); }); tracker.add(widget); + + // Open editor alongside viewer + commands.execute('docmanager:open', { + path: widget.context.path, + factory: 'Editor' + }); + }); // Register widget and model factories @@ -121,20 +128,23 @@ const extension: JupyterFrontEndPlugin = { icon: urdf_icon, iconClass: 'jp-URDFIcon', caption: 'Create a new URDF', - execute: () => { + execute: async () => { const cwd = browserFactory.model.path; - commands - .execute('docmanager:new-untitled', { - path: cwd, - type: 'file', - ext: '.urdf' - }) - .then(model => - commands.execute('docmanager:open', { - path: model.path, - factory: FACTORY - }) - ); + const model = await commands.execute('docmanager:new-untitled', { + path: cwd, + type: 'file', + ext: '.urdf' + }); + + await commands.execute('docmanager:open', { + path: model.path, + factory: FACTORY + }); + + await commands.execute('docmanager:open', { + path: model.path, + factory: 'Editor' + }); } }); From ad6cf83fb32eaa89357407c6c6840d56c659a7c8 Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Wed, 23 Apr 2025 17:17:42 +0200 Subject: [PATCH 2/8] Fix lint check --- src/index.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 05e946a..5b4e1bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,10 +101,9 @@ const extension: JupyterFrontEndPlugin = { // Open editor alongside viewer commands.execute('docmanager:open', { - path: widget.context.path, - factory: 'Editor' + path: widget.context.path, + factory: 'Editor' }); - }); // Register widget and model factories @@ -135,12 +134,12 @@ const extension: JupyterFrontEndPlugin = { type: 'file', ext: '.urdf' }); - + await commands.execute('docmanager:open', { path: model.path, factory: FACTORY }); - + await commands.execute('docmanager:open', { path: model.path, factory: 'Editor' From ecfc6af477fd1ebc65b6b7aa36c9e4fcf750ab93 Mon Sep 17 00:00:00 2001 From: Yahiewi <125813966+Yahiewi@users.noreply.github.com> Date: Thu, 24 Apr 2025 12:45:27 +0200 Subject: [PATCH 3/8] Fixed Color Picker issue (#82) * Fixed Color Picker issue * Fix lint check --- style/base.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/style/base.css b/style/base.css index 78527b6..6626fd3 100644 --- a/style/base.css +++ b/style/base.css @@ -128,3 +128,9 @@ max-height: 50vh; overflow: scroll; } + +/* Expand hover area to reach color pickers */ +.urdf-gui .cr.color .c:hover { + padding: 15px 0; + margin: -15px 0; +} From 77d57c4ac5ca0348718a229d74acadab66397b8b Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Mon, 28 Apr 2025 12:18:11 +0200 Subject: [PATCH 4/8] Add split screen --- src/index.ts | 92 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5b4e1bd..aa7fcc6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,54 +62,81 @@ const extension: JupyterFrontEndPlugin = { launcher: ILauncher, languageRegistry: IEditorLanguageRegistry ) => { - console.log('JupyterLab extension URDF is activated!'); - const { commands } = app; + const { commands, shell } = app; - // Tracker - const namespace = 'jupyterlab-urdf'; - const tracker = new WidgetTracker({ namespace }); + // --- track whether we've already done the split, and the two anchor IDs --- + let splitDone = false; + let leftEditorRefId: string | null = null; + let rightViewerRefId: string | null = null; - // State restoration: reopen document if it was open previously + const tracker = new WidgetTracker({ + namespace: 'jupyterlab-urdf' + }); if (restorer) { restorer.restore(tracker, { command: 'docmanager:open', - args: widget => ({ path: widget.context.path, factory: FACTORY }), - name: widget => { - console.debug('[Restorer]: Re-opening', widget.context.path); - return widget.context.path; - } + args: w => ({ path: w.context.path, factory: FACTORY }), + name: w => w.context.path }); } - // Create widget factory so that manager knows about widget + // Function to check if any URDF widgets are currently open + const checkAndResetSplitState = () => { + if (tracker.size === 0) { + // No URDF widgets left, reset split state + splitDone = false; + leftEditorRefId = null; + rightViewerRefId = null; + } + }; + const widgetFactory = new URDFWidgetFactory({ name: FACTORY, fileTypes: ['urdf'], defaultFor: ['urdf'] }); - // Add widget to tracker when created - widgetFactory.widgetCreated.connect((sender, widget) => { + widgetFactory.widgetCreated.connect(async (sender, widget) => { widget.title.icon = urdf_icon; widget.title.iconClass = 'jp-URDFIcon'; - - // Notify instance tracker if restore data needs to be updated - widget.context.pathChanged.connect(() => { - tracker.save(widget); - }); + widget.context.pathChanged.connect(() => tracker.save(widget)); tracker.add(widget); - // Open editor alongside viewer - commands.execute('docmanager:open', { - path: widget.context.path, - factory: 'Editor' + // Add dispose listener to reset split state when all widgets are closed + widget.disposed.connect(() => { + checkAndResetSplitState(); }); + + if (!splitDone) { + // First file: split out the editor to the left of this viewer + const editor = await commands.execute('docmanager:open', { + path: widget.context.path, + factory: 'Editor', + options: { mode: 'split-left', ref: widget.id } + }); + splitDone = true; + leftEditorRefId = editor.id; + rightViewerRefId = widget.id; + } else { + // Subsequent viewers → tab them into the _right_ panel + if (rightViewerRefId) { + shell.add(widget, 'main', { + mode: 'tab-after', + ref: rightViewerRefId + }); + } + // And open each new editor as a tab in the _left_ panel + if (leftEditorRefId) { + await commands.execute('docmanager:open', { + path: widget.context.path, + factory: 'Editor', + options: { mode: 'tab-after', ref: leftEditorRefId } + }); + } + } }); - // Register widget and model factories app.docRegistry.addWidgetFactory(widgetFactory); - - // Register file type app.docRegistry.addFileType({ name: 'urdf', displayName: 'URDF', @@ -121,29 +148,22 @@ const extension: JupyterFrontEndPlugin = { icon: urdf_icon }); - // Add command for creating new urdf (file) + // new‐file command now just fires the viewer; widgetCreated handles the rest commands.addCommand('urdf:create-new', { label: 'Create new URDF', icon: urdf_icon, - iconClass: 'jp-URDFIcon', caption: 'Create a new URDF', execute: async () => { const cwd = browserFactory.model.path; - const model = await commands.execute('docmanager:new-untitled', { + const { path } = await commands.execute('docmanager:new-untitled', { path: cwd, type: 'file', ext: '.urdf' }); - await commands.execute('docmanager:open', { - path: model.path, + path, factory: FACTORY }); - - await commands.execute('docmanager:open', { - path: model.path, - factory: 'Editor' - }); } }); From fa6c3ff1601daa1b7dd055ea6483434ff388906e Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Tue, 29 Apr 2025 10:21:52 +0200 Subject: [PATCH 5/8] Refactor code --- src/index.ts | 77 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/index.ts b/src/index.ts index aa7fcc6..34a906a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,53 +62,59 @@ const extension: JupyterFrontEndPlugin = { launcher: ILauncher, languageRegistry: IEditorLanguageRegistry ) => { + console.log('JupyterLab extension URDF is activated!'); const { commands, shell } = app; - // --- track whether we've already done the split, and the two anchor IDs --- + // Tracker + const namespace = 'jupyterlab-urdf'; + const tracker = new WidgetTracker({ namespace }); + + // Track split state let splitDone = false; let leftEditorRefId: string | null = null; let rightViewerRefId: string | null = null; - const tracker = new WidgetTracker({ - namespace: 'jupyterlab-urdf' - }); + // State restoration: reopen document if it was open previously if (restorer) { restorer.restore(tracker, { command: 'docmanager:open', - args: w => ({ path: w.context.path, factory: FACTORY }), - name: w => w.context.path + args: widget => ({ path: widget.context.path, factory: FACTORY }), + name: widget => { + console.debug('[Restorer]: Re-opening', widget.context.path); + return widget.context.path; + } }); } - // Function to check if any URDF widgets are currently open - const checkAndResetSplitState = () => { - if (tracker.size === 0) { - // No URDF widgets left, reset split state - splitDone = false; - leftEditorRefId = null; - rightViewerRefId = null; - } - }; - + // Create widget factory so that manager knows about widget const widgetFactory = new URDFWidgetFactory({ name: FACTORY, fileTypes: ['urdf'], defaultFor: ['urdf'] }); + // Add widget to tracker when created widgetFactory.widgetCreated.connect(async (sender, widget) => { widget.title.icon = urdf_icon; widget.title.iconClass = 'jp-URDFIcon'; - widget.context.pathChanged.connect(() => tracker.save(widget)); + + // Notify instance tracker if restore data needs to be updated + widget.context.pathChanged.connect(() => { + tracker.save(widget); + }); tracker.add(widget); - // Add dispose listener to reset split state when all widgets are closed + // Reset split state when all widgets are closed widget.disposed.connect(() => { - checkAndResetSplitState(); + if (tracker.size === 0) { + splitDone = false; + leftEditorRefId = null; + rightViewerRefId = null; + } }); + // Split layout on first open, then tab into panels if (!splitDone) { - // First file: split out the editor to the left of this viewer const editor = await commands.execute('docmanager:open', { path: widget.context.path, factory: 'Editor', @@ -118,14 +124,12 @@ const extension: JupyterFrontEndPlugin = { leftEditorRefId = editor.id; rightViewerRefId = widget.id; } else { - // Subsequent viewers → tab them into the _right_ panel if (rightViewerRefId) { shell.add(widget, 'main', { mode: 'tab-after', ref: rightViewerRefId }); } - // And open each new editor as a tab in the _left_ panel if (leftEditorRefId) { await commands.execute('docmanager:open', { path: widget.context.path, @@ -136,7 +140,10 @@ const extension: JupyterFrontEndPlugin = { } }); + // Register widget and model factories app.docRegistry.addWidgetFactory(widgetFactory); + + // Register file type app.docRegistry.addFileType({ name: 'urdf', displayName: 'URDF', @@ -148,22 +155,26 @@ const extension: JupyterFrontEndPlugin = { icon: urdf_icon }); - // new‐file command now just fires the viewer; widgetCreated handles the rest + // Add command for creating new urdf (file) commands.addCommand('urdf:create-new', { label: 'Create new URDF', icon: urdf_icon, + iconClass: 'jp-URDFIcon', caption: 'Create a new URDF', - execute: async () => { + execute: () => { const cwd = browserFactory.model.path; - const { path } = await commands.execute('docmanager:new-untitled', { - path: cwd, - type: 'file', - ext: '.urdf' - }); - await commands.execute('docmanager:open', { - path, - factory: FACTORY - }); + commands + .execute('docmanager:new-untitled', { + path: cwd, + type: 'file', + ext: '.urdf' + }) + .then(model => + commands.execute('docmanager:open', { + path: model.path, + factory: FACTORY + }) + ); } }); From dab1c49b74d95a44648e6d2a6e20993f19eb4c60 Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Tue, 29 Apr 2025 13:53:02 +0200 Subject: [PATCH 6/8] Add persistent layout for split panels --- src/index.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 34a906a..68ed219 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,9 +70,13 @@ const extension: JupyterFrontEndPlugin = { const tracker = new WidgetTracker({ namespace }); // Track split state - let splitDone = false; - let leftEditorRefId: string | null = null; - let rightViewerRefId: string | null = null; + const splitDoneKey = 'jupyterlab-urdf:splitDone'; + const leftEditorRefKey = 'jupyterlab-urdf:leftEditorRefId'; + const rightViewerRefKey = 'jupyterlab-urdf:rightViewerRefId'; + let splitDone = localStorage.getItem(splitDoneKey) === 'true'; + let leftEditorRefId: string | null = localStorage.getItem(leftEditorRefKey); + let rightViewerRefId: string | null = + localStorage.getItem(rightViewerRefKey); // State restoration: reopen document if it was open previously if (restorer) { @@ -108,8 +112,11 @@ const extension: JupyterFrontEndPlugin = { widget.disposed.connect(() => { if (tracker.size === 0) { splitDone = false; + localStorage.setItem(splitDoneKey, 'false'); leftEditorRefId = null; rightViewerRefId = null; + localStorage.removeItem(leftEditorRefKey); + localStorage.removeItem(rightViewerRefKey); } }); @@ -121,8 +128,11 @@ const extension: JupyterFrontEndPlugin = { options: { mode: 'split-left', ref: widget.id } }); splitDone = true; + localStorage.setItem(splitDoneKey, 'true'); leftEditorRefId = editor.id; rightViewerRefId = widget.id; + localStorage.setItem(leftEditorRefKey, leftEditorRefId as string); + localStorage.setItem(rightViewerRefKey, rightViewerRefId as string); } else { if (rightViewerRefId) { shell.add(widget, 'main', { From 2d6b69171d96b851eb494dd1c8ac7375784ee130 Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Wed, 30 Apr 2025 11:59:47 +0200 Subject: [PATCH 7/8] Refactor split panel code --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 68ed219..3e62fce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,7 +69,7 @@ const extension: JupyterFrontEndPlugin = { const namespace = 'jupyterlab-urdf'; const tracker = new WidgetTracker({ namespace }); - // Track split state + // Track and persist split panel state const splitDoneKey = 'jupyterlab-urdf:splitDone'; const leftEditorRefKey = 'jupyterlab-urdf:leftEditorRefId'; const rightViewerRefKey = 'jupyterlab-urdf:rightViewerRefId'; From a32a335fba4b04ebf2cd9ecb1a0f4c71677db688 Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Wed, 7 May 2025 12:05:19 +0200 Subject: [PATCH 8/8] Improve split state tracking and refactor code --- src/index.ts | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3e62fce..872f4a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -78,6 +78,16 @@ const extension: JupyterFrontEndPlugin = { let rightViewerRefId: string | null = localStorage.getItem(rightViewerRefKey); + // Reset our “splitDone” flags & remove saved refs + function resetSplitState() { + splitDone = false; + localStorage.setItem(splitDoneKey, 'false'); + leftEditorRefId = null; + rightViewerRefId = null; + localStorage.removeItem(leftEditorRefKey); + localStorage.removeItem(rightViewerRefKey); + } + // State restoration: reopen document if it was open previously if (restorer) { restorer.restore(tracker, { @@ -111,15 +121,21 @@ const extension: JupyterFrontEndPlugin = { // Reset split state when all widgets are closed widget.disposed.connect(() => { if (tracker.size === 0) { - splitDone = false; - localStorage.setItem(splitDoneKey, 'false'); - leftEditorRefId = null; - rightViewerRefId = null; - localStorage.removeItem(leftEditorRefKey); - localStorage.removeItem(rightViewerRefKey); + resetSplitState(); } }); + // Detect stale editor-ref + if (splitDone && leftEditorRefId) { + // look through all widgets in the main area + const allMain = [...shell.widgets('main')]; + const stillHasEditor = allMain.some(w => w.id === leftEditorRefId); + if (!stillHasEditor) { + // the editor tab was closed ⇒ reset split state + resetSplitState(); + } + } + // Split layout on first open, then tab into panels if (!splitDone) { const editor = await commands.execute('docmanager:open', {