From 99b25264b0f369a7a04dbd66d496c30d42519bfd Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 17 Jun 2026 11:42:57 +0200 Subject: [PATCH] Move the timeline status bar slider to the jupyter-collaboration-ui package --- packages/collaboration-extension/package.json | 2 + packages/collaboration-extension/src/index.ts | 2 + .../collaboration-extension/src/timeline.ts | 124 ++++++++++++++++++ packages/collaboration/package.json | 1 + .../src/TimelineSlider.tsx | 2 +- .../src/component.tsx | 5 +- packages/collaboration/src/index.ts | 1 + packages/collaboration/style/base.css | 1 + .../style/slider.css | 0 .../docprovider-extension/src/filebrowser.ts | 115 +--------------- packages/docprovider-extension/src/index.ts | 9 +- packages/docprovider/src/index.ts | 1 - packages/docprovider/style/base.css | 2 - yarn.lock | 3 + 14 files changed, 139 insertions(+), 129 deletions(-) create mode 100644 packages/collaboration-extension/src/timeline.ts rename packages/{docprovider => collaboration}/src/TimelineSlider.tsx (97%) rename packages/{docprovider => collaboration}/src/component.tsx (98%) rename packages/{docprovider => collaboration}/style/slider.css (100%) diff --git a/packages/collaboration-extension/package.json b/packages/collaboration-extension/package.json index 53d3bea2..1f473214 100644 --- a/packages/collaboration-extension/package.json +++ b/packages/collaboration-extension/package.json @@ -61,8 +61,10 @@ "@jupyterlab/apputils": "^4.7.0-beta.1", "@jupyterlab/codemirror": "^4.6.0-beta.1", "@jupyterlab/coreutils": "^6.6.0-beta.1", + "@jupyterlab/docregistry": "^4.6.0-beta.1", "@jupyterlab/services": "^7.6.0-beta.1", "@jupyterlab/statedb": "^4.6.0-beta.1", + "@jupyterlab/statusbar": "^4.6.0-beta.1", "@jupyterlab/translation": "^4.6.0-beta.1", "@jupyterlab/ui-components": "^4.6.0-beta.1", "@lumino/widgets": "^2.8.0", diff --git a/packages/collaboration-extension/src/index.ts b/packages/collaboration-extension/src/index.ts index 196b75c0..c9fec075 100644 --- a/packages/collaboration-extension/src/index.ts +++ b/packages/collaboration-extension/src/index.ts @@ -15,6 +15,7 @@ import { userEditorCursors } from './collaboration'; import { sharedLink } from './sharedlink'; +import { statusBarTimeline } from './timeline'; /** * Export the plugins as default. @@ -25,6 +26,7 @@ const plugins: JupyterFrontEndPlugin[] = [ rtcGlobalAwarenessPlugin, rtcPanelPlugin, sharedLink, + statusBarTimeline, userEditorCursors ]; diff --git a/packages/collaboration-extension/src/timeline.ts b/packages/collaboration-extension/src/timeline.ts new file mode 100644 index 00000000..33bed5fd --- /dev/null +++ b/packages/collaboration-extension/src/timeline.ts @@ -0,0 +1,124 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; +import { URLExt } from '@jupyterlab/coreutils'; +import { DocumentWidget } from '@jupyterlab/docregistry'; +import { IStatusBar } from '@jupyterlab/statusbar'; +import { Widget } from '@lumino/widgets'; + +import { TimelineWidget } from '@jupyter/collaboration'; +import { ICollaborativeContentProvider } from '@jupyter/collaborative-drive'; +import { IForkProvider } from '@jupyter/docprovider'; + +const DOCUMENT_TIMELINE_URL = 'api/collaboration/timeline'; + +/** + * A plugin to add a timeline slider status item to the status bar. + */ +export const statusBarTimeline: JupyterFrontEndPlugin = { + id: '@jupyter/collaboration-extension:statusBarTimeline', + description: 'Plugin to add a timeline slider to the status bar', + autoStart: true, + requires: [IStatusBar, ICollaborativeContentProvider], + activate: async ( + app: JupyterFrontEnd, + statusBar: IStatusBar, + contentProvider: ICollaborativeContentProvider + ): Promise => { + try { + let sliderItem: Widget | null = null; + let timelineWidget: TimelineWidget | null = null; + + const updateTimelineForDocument = async ( + documentPath: string, + documentId: string + ) => { + if (!documentId) { + return; + } + // Dispose of the previous timelineWidget if it exists + if (timelineWidget) { + timelineWidget.dispose(); + timelineWidget = null; + } + + const [format, type] = documentId.split(':'); + const provider = contentProvider.providers.get( + `${format}:${type}:${documentPath}` + ); + if (!provider) { + // this can happen for documents which are not provisioned with RTC + return; + } + + const forkProvider = provider as unknown as IForkProvider; + + const fullPath = URLExt.join( + app.serviceManager.serverSettings.baseUrl, + DOCUMENT_TIMELINE_URL, + documentPath + ); + + timelineWidget = new TimelineWidget( + fullPath, + forkProvider, + forkProvider.contentType, + forkProvider.format, + DOCUMENT_TIMELINE_URL, + app.serviceManager.serverSettings + ); + + const elt = document.getElementById('jp-slider-status-bar'); + if (elt && !timelineWidget.isAttached) { + Widget.attach(timelineWidget, elt); + } + }; + + if (app.shell.currentChanged) { + app.shell.currentChanged.connect(async (_, args) => { + const currentWidget = args.newValue as DocumentWidget; + if (timelineWidget) { + // Dispose of the timelineWidget when the document is closed + timelineWidget.dispose(); + timelineWidget = null; + } + if (currentWidget && 'context' in currentWidget) { + await currentWidget.context.ready; + await updateTimelineForDocument( + currentWidget.context.path, + currentWidget.context.model.sharedModel.getState( + 'document_id' + ) as string + ); + } + }); + } + + if (statusBar) { + if (!sliderItem) { + sliderItem = new Widget(); + sliderItem.addClass('jp-StatusBar-GroupItem'); + sliderItem.addClass('jp-mod-highlighted'); + sliderItem.id = 'jp-slider-status-bar'; + statusBar.registerStatusItem('jp-slider-status-bar', { + item: sliderItem, + align: 'left', + rank: 4, + isActive: () => { + const currentWidget = app.shell + .currentWidget as DocumentWidget | null; + + return currentWidget?.context?.model?.collaborative || false; + } + }); + } + } + } catch (error) { + console.error('Failed to activate statusBarTimeline plugin:', error); + } + } +}; diff --git a/packages/collaboration/package.json b/packages/collaboration/package.json index 99c81252..89c24348 100644 --- a/packages/collaboration/package.json +++ b/packages/collaboration/package.json @@ -41,6 +41,7 @@ "dependencies": { "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.7.0", + "@jupyter/docprovider": "^5.0.0-alpha.0", "@jupyterlab/apputils": "^4.7.0-beta.1", "@jupyterlab/coreutils": "^6.6.0-beta.1", "@jupyterlab/docregistry": "^4.6.0-beta.1", diff --git a/packages/docprovider/src/TimelineSlider.tsx b/packages/collaboration/src/TimelineSlider.tsx similarity index 97% rename from packages/docprovider/src/TimelineSlider.tsx rename to packages/collaboration/src/TimelineSlider.tsx index 2a76f6ef..ad837387 100644 --- a/packages/docprovider/src/TimelineSlider.tsx +++ b/packages/collaboration/src/TimelineSlider.tsx @@ -6,7 +6,7 @@ import { ReactWidget } from '@jupyterlab/apputils'; import { TimelineSliderComponent } from './component'; import * as React from 'react'; -import { IForkProvider } from './ydrive'; +import { IForkProvider } from '@jupyter/docprovider'; import { ServerConnection } from '@jupyterlab/services'; export class TimelineWidget extends ReactWidget { diff --git a/packages/docprovider/src/component.tsx b/packages/collaboration/src/component.tsx similarity index 98% rename from packages/docprovider/src/component.tsx rename to packages/collaboration/src/component.tsx index 85588a33..c0ce56c1 100644 --- a/packages/docprovider/src/component.tsx +++ b/packages/collaboration/src/component.tsx @@ -4,15 +4,14 @@ |----------------------------------------------------------------------------*/ import React, { useState, useRef } from 'react'; -import '../style/slider.css'; import { + IForkProvider, requestUndoRedo, requestDocSession, requestDocumentTimeline -} from './requests'; +} from '@jupyter/docprovider'; import { historyIcon } from '@jupyterlab/ui-components'; import { Notification } from '@jupyterlab/apputils'; -import { IForkProvider } from './ydrive'; import { ServerConnection } from '@jupyterlab/services'; type Props = { diff --git a/packages/collaboration/src/index.ts b/packages/collaboration/src/index.ts index 8b8209ca..c2f3fb59 100644 --- a/packages/collaboration/src/index.ts +++ b/packages/collaboration/src/index.ts @@ -10,5 +10,6 @@ export * from './collaboratorspanel'; export * from './cursors'; export * from './menu'; export * from './sharedlink'; +export * from './TimelineSlider'; export * from './userinfopanel'; export * from './users-item'; diff --git a/packages/collaboration/style/base.css b/packages/collaboration/style/base.css index 24340285..0820dbbd 100644 --- a/packages/collaboration/style/base.css +++ b/packages/collaboration/style/base.css @@ -7,6 +7,7 @@ @import url('./sidepanel.css'); @import url('./users-item.css'); @import url('./sharedlink.css'); +@import url('./slider.css'); .jp-shared-link-body { user-select: none; diff --git a/packages/docprovider/style/slider.css b/packages/collaboration/style/slider.css similarity index 100% rename from packages/docprovider/style/slider.css rename to packages/collaboration/style/slider.css diff --git a/packages/docprovider-extension/src/filebrowser.ts b/packages/docprovider-extension/src/filebrowser.ts index 408a3d43..7305416c 100644 --- a/packages/docprovider-extension/src/filebrowser.ts +++ b/packages/docprovider-extension/src/filebrowser.ts @@ -8,10 +8,8 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application'; import { Dialog, showDialog } from '@jupyterlab/apputils'; -import { DocumentWidget, IDocumentWidget } from '@jupyterlab/docregistry'; -import { Widget } from '@lumino/widgets'; +import { IDocumentWidget } from '@jupyterlab/docregistry'; -import { IStatusBar } from '@jupyterlab/statusbar'; import { ContentsManager } from '@jupyterlab/services'; import { IDocumentManager } from '@jupyterlab/docmanager'; @@ -36,15 +34,10 @@ import { IGlobalAwareness } from '@jupyter/collaborative-drive'; import { - IForkProvider, - TimelineWidget, RtcContentProvider, IDocumentProviderFactory } from '@jupyter/docprovider'; import { Awareness } from 'y-protocols/awareness'; -import { URLExt } from '@jupyterlab/coreutils'; - -const DOCUMENT_TIMELINE_URL = 'api/collaboration/timeline'; const TWO_SESSIONS_WARNING = 'The file %1 has been opened with two different views. ' + @@ -169,112 +162,6 @@ export const ynotebook: JupyterFrontEndPlugin = { notebookFactory.contentProviderId = 'rtc'; } }; -/** - * A plugin to add a timeline slider status item to the status bar. - */ -export const statusBarTimeline: JupyterFrontEndPlugin = { - id: '@jupyter/docprovider-extension:statusBarTimeline', - description: 'Plugin to add a timeline slider to the status bar', - autoStart: true, - requires: [IStatusBar, ICollaborativeContentProvider], - activate: async ( - app: JupyterFrontEnd, - statusBar: IStatusBar, - contentProvider: ICollaborativeContentProvider - ): Promise => { - try { - let sliderItem: Widget | null = null; - let timelineWidget: TimelineWidget | null = null; - - const updateTimelineForDocument = async ( - documentPath: string, - documentId: string - ) => { - if (!documentId) { - return; - } - // Dispose of the previous timelineWidget if it exists - if (timelineWidget) { - timelineWidget.dispose(); - timelineWidget = null; - } - - const [format, type] = documentId.split(':'); - const provider = contentProvider.providers.get( - `${format}:${type}:${documentPath}` - ); - if (!provider) { - // this can happen for documents which are not provisioned with RTC - return; - } - - const forkProvider = provider as unknown as IForkProvider; - - const fullPath = URLExt.join( - app.serviceManager.serverSettings.baseUrl, - DOCUMENT_TIMELINE_URL, - documentPath - ); - - timelineWidget = new TimelineWidget( - fullPath, - forkProvider, - forkProvider.contentType, - forkProvider.format, - DOCUMENT_TIMELINE_URL, - app.serviceManager.serverSettings - ); - - const elt = document.getElementById('jp-slider-status-bar'); - if (elt && !timelineWidget.isAttached) { - Widget.attach(timelineWidget, elt); - } - }; - - if (app.shell.currentChanged) { - app.shell.currentChanged.connect(async (_, args) => { - const currentWidget = args.newValue as DocumentWidget; - if (timelineWidget) { - // Dispose of the timelineWidget when the document is closed - timelineWidget.dispose(); - timelineWidget = null; - } - if (currentWidget && 'context' in currentWidget) { - await currentWidget.context.ready; - await updateTimelineForDocument( - currentWidget.context.path, - currentWidget.context.model.sharedModel.getState( - 'document_id' - ) as string - ); - } - }); - } - - if (statusBar) { - if (!sliderItem) { - sliderItem = new Widget(); - sliderItem.addClass('jp-StatusBar-GroupItem'); - sliderItem.addClass('jp-mod-highlighted'); - sliderItem.id = 'jp-slider-status-bar'; - statusBar.registerStatusItem('jp-slider-status-bar', { - item: sliderItem, - align: 'left', - rank: 4, - isActive: () => { - const currentWidget = app.shell - .currentWidget as DocumentWidget | null; - - return currentWidget?.context?.model?.collaborative || false; - } - }); - } - } - } catch (error) { - console.error('Failed to activate statusBarTimeline plugin:', error); - } - } -}; /** * The default collaborative drive provider. diff --git a/packages/docprovider-extension/src/index.ts b/packages/docprovider-extension/src/index.ts index bd5dad3a..115dc38a 100644 --- a/packages/docprovider-extension/src/index.ts +++ b/packages/docprovider-extension/src/index.ts @@ -7,13 +7,7 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application'; -import { - rtcContentProvider, - yfile, - ynotebook, - logger, - statusBarTimeline -} from './filebrowser'; +import { rtcContentProvider, yfile, ynotebook, logger } from './filebrowser'; import { notebookCellExecutor } from './executor'; import { forkManagerPlugin } from './forkManager'; import { @@ -30,7 +24,6 @@ const plugins: JupyterFrontEndPlugin[] = [ ynotebook, logger, notebookCellExecutor, - statusBarTimeline, forkManagerPlugin, documentProviderFactoryPlugin, awarenessProviderFactoryPlugin diff --git a/packages/docprovider/src/index.ts b/packages/docprovider/src/index.ts index 178b7984..460a954f 100644 --- a/packages/docprovider/src/index.ts +++ b/packages/docprovider/src/index.ts @@ -12,6 +12,5 @@ export * from './notebookCellExecutor'; export * from './requests'; export * from './ydrive'; export * from './yprovider'; -export * from './TimelineSlider'; export * from './tokens'; export * from './forkManager'; diff --git a/packages/docprovider/style/base.css b/packages/docprovider/style/base.css index e77d3daf..ea4e246a 100644 --- a/packages/docprovider/style/base.css +++ b/packages/docprovider/style/base.css @@ -2,5 +2,3 @@ | Copyright (c) Jupyter Development Team. | Distributed under the terms of the Modified BSD License. |---------------------------------------------------------------------------- */ - -@import url('./slider.css'); diff --git a/yarn.lock b/yarn.lock index 306c0f4d..deabcf0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2072,8 +2072,10 @@ __metadata: "@jupyterlab/builder": ^4.6.0-alpha.5 "@jupyterlab/codemirror": ^4.6.0-beta.1 "@jupyterlab/coreutils": ^6.6.0-beta.1 + "@jupyterlab/docregistry": ^4.6.0-beta.1 "@jupyterlab/services": ^7.6.0-beta.1 "@jupyterlab/statedb": ^4.6.0-beta.1 + "@jupyterlab/statusbar": ^4.6.0-beta.1 "@jupyterlab/translation": ^4.6.0-beta.1 "@jupyterlab/ui-components": ^4.6.0-beta.1 "@lumino/widgets": ^2.8.0 @@ -2093,6 +2095,7 @@ __metadata: dependencies: "@codemirror/state": ^6.2.0 "@codemirror/view": ^6.7.0 + "@jupyter/docprovider": ^5.0.0-alpha.0 "@jupyterlab/apputils": ^4.7.0-beta.1 "@jupyterlab/coreutils": ^6.6.0-beta.1 "@jupyterlab/docregistry": ^4.6.0-beta.1