diff --git a/packages/docprovider-extension/src/filebrowser.ts b/packages/docprovider-extension/src/filebrowser.ts index 64765319..bb139a29 100644 --- a/packages/docprovider-extension/src/filebrowser.ts +++ b/packages/docprovider-extension/src/filebrowser.ts @@ -10,7 +10,7 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application'; import { Dialog, showDialog } from '@jupyterlab/apputils'; -import { IDocumentWidget } from '@jupyterlab/docregistry'; +import { DocumentWidget, IDocumentWidget } from '@jupyterlab/docregistry'; import { Widget } from '@lumino/widgets'; import { FileBrowser, @@ -31,10 +31,13 @@ import { YFile, YNotebook } from '@jupyter/ydoc'; import { ICollaborativeDrive, + IForkProvider, IGlobalAwareness, + TimelineWidget, YDrive } from '@jupyter/docprovider'; import { Awareness } from 'y-protocols/awareness'; +import { URLExt } from '@jupyterlab/coreutils'; /** * The command IDs used by the file browser plugin. @@ -42,6 +45,7 @@ import { Awareness } from 'y-protocols/awareness'; namespace CommandIDs { export const openPath = 'filebrowser:open-path'; } +const DOCUMENT_TIMELINE_URL = 'api/collaboration/timeline'; /** * The default collaborative drive provider. @@ -137,45 +141,65 @@ export const statusBarTimeline: JupyterFrontEndPlugin = { id: '@jupyter/docprovider-extension:statusBarTimeline', description: 'Plugin to add a timeline slider to the status bar', autoStart: true, - requires: [IStatusBar], - optional: [ICollaborativeDrive], + requires: [IStatusBar, ICollaborativeDrive], activate: async ( app: JupyterFrontEnd, statusBar: IStatusBar, - drive: YDrive | null + drive: ICollaborativeDrive ): Promise => { + function isYDrive(drive: YDrive | ICollaborativeDrive): drive is YDrive { + return 'getProviderForPath' in drive; + } try { - if (!drive) { - console.warn('Collaborative drive not available'); - return; - } - let sliderItem: Widget | null = null; - const updateTimelineForDocument = async (document: any) => { - if (document && document.context) { - const { context } = document; - const documentPath = context.path; - const sharedModel = context.model.sharedModel; - document.node.id = sharedModel.ydoc.guid; - await drive.updateTimelineForNotebook(documentPath); + let timelineWidget: TimelineWidget | null = null; + + const updateTimelineForDocument = async (documentPath: string) => { + if (drive) { + if (isYDrive(drive)) { + const provider = (await drive.getProviderForPath( + documentPath + )) as IForkProvider; + const fullPath = URLExt.join( + app.serviceManager.serverSettings.baseUrl, + DOCUMENT_TIMELINE_URL, + documentPath.split(':')[1] + ); + + if (timelineWidget) { + timelineWidget.updateContent(fullPath, provider); + } else { + timelineWidget = new TimelineWidget( + fullPath, + provider, + provider.contentType, + provider.format + ); + } + const elt = document.getElementById('slider-status-bar'); + if (elt && !timelineWidget.isAttached) { + Widget.attach(timelineWidget, elt); + } else if (!timelineWidget.isAttached) { + Widget.attach(timelineWidget, document.body); + } + } } }; if (app.shell.currentChanged) { app.shell.currentChanged.connect(async (_, args) => { - const currentWidget = args.newValue; - + const currentWidget = args.newValue as DocumentWidget; if (currentWidget && 'context' in currentWidget) { - await updateTimelineForDocument(currentWidget); + await updateTimelineForDocument(currentWidget.context.path); } }); } + if (statusBar) { if (!sliderItem) { sliderItem = new Widget(); sliderItem.addClass('jp-StatusBar-GroupItem'); sliderItem.addClass('jp-mod-highlighted'); - sliderItem.id = 'slider-status-bar'; statusBar.registerStatusItem('slider-status-bar', { item: sliderItem, diff --git a/packages/docprovider/src/TimelineSlider.tsx b/packages/docprovider/src/TimelineSlider.tsx index 90c213fc..ac05f35f 100644 --- a/packages/docprovider/src/TimelineSlider.tsx +++ b/packages/docprovider/src/TimelineSlider.tsx @@ -6,17 +6,17 @@ import { ReactWidget } from '@jupyterlab/apputils'; import { TimelineSliderComponent } from './component'; import * as React from 'react'; -import { WebSocketProvider } from './yprovider'; +import { IForkProvider } from './ydrive'; export class TimelineWidget extends ReactWidget { private apiURL: string; - private provider: WebSocketProvider; + private provider: IForkProvider; private contentType: string; private format: string; constructor( apiURL: string, - provider: WebSocketProvider, + provider: IForkProvider, contentType: string, format: string ) { @@ -39,7 +39,7 @@ export class TimelineWidget extends ReactWidget { /> ); } - updateContent(apiURL: string, provider: WebSocketProvider): void { + updateContent(apiURL: string, provider: IForkProvider): void { this.apiURL = apiURL; this.provider = provider; this.contentType = this.provider.contentType; diff --git a/packages/docprovider/src/component.tsx b/packages/docprovider/src/component.tsx index e806b4ba..41f5a16e 100644 --- a/packages/docprovider/src/component.tsx +++ b/packages/docprovider/src/component.tsx @@ -5,14 +5,14 @@ import React, { useState, useRef } from 'react'; import '../style/slider.css'; -import { WebSocketProvider } from './yprovider'; import { requestDocFork, requestDocumentTimeline } from './requests'; import { historyIcon } from '@jupyterlab/ui-components'; import { Notification } from '@jupyterlab/apputils'; +import { IForkProvider } from './ydrive'; type Props = { apiURL: string; - provider: WebSocketProvider; + provider: IForkProvider; contentType: string; format: string; }; @@ -65,7 +65,7 @@ export const TimelineSliderComponent: React.FC = ({ console.error('Error fetching data:', error); } } - const handleClick = async () => { + const handleRestore = async () => { const response = await requestDocFork( `${session.format}:${session.type}:${session.fileId}`, 'undo', @@ -169,9 +169,9 @@ export const TimelineSliderComponent: React.FC = ({ {' '} {isBtn && ( -
+