Skip to content

Commit

Permalink
added code changes as requested.
Browse files Browse the repository at this point in the history
  • Loading branch information
Meriem-BenIsmail committed Aug 26, 2024
1 parent 05df889 commit 4fbab40
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 120 deletions.
64 changes: 44 additions & 20 deletions packages/docprovider-extension/src/filebrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -31,17 +31,21 @@ 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.
*/
namespace CommandIDs {
export const openPath = 'filebrowser:open-path';
}
const DOCUMENT_TIMELINE_URL = 'api/collaboration/timeline';

/**
* The default collaborative drive provider.
Expand Down Expand Up @@ -137,45 +141,65 @@ export const statusBarTimeline: JupyterFrontEndPlugin<void> = {
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<void> => {
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,
Expand Down
8 changes: 4 additions & 4 deletions packages/docprovider/src/TimelineSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand All @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions packages/docprovider/src/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -65,7 +65,7 @@ export const TimelineSliderComponent: React.FC<Props> = ({
console.error('Error fetching data:', error);
}
}
const handleClick = async () => {
const handleRestore = async () => {
const response = await requestDocFork(
`${session.format}:${session.type}:${session.fileId}`,
'undo',
Expand Down Expand Up @@ -169,9 +169,9 @@ export const TimelineSliderComponent: React.FC<Props> = ({
</strong>{' '}
</div>
{isBtn && (
<div className="restore-btn">
<div className="restore-btn-container">
<button
onClick={handleClick}
onClick={handleRestore}
className="jp-ToolbarButtonComponent restore-btn"
style={{ background: '#1976d2' }}
>
Expand Down
91 changes: 22 additions & 69 deletions packages/docprovider/src/ydrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { TranslationBundle } from '@jupyterlab/translation';
import { Contents, Drive, User } from '@jupyterlab/services';
import { ISignal, Signal } from '@lumino/signaling';

import { Widget } from '@lumino/widgets';
import { DocumentChange, ISharedDocument, YDocument } from '@jupyter/ydoc';

import { WebSocketProvider } from './yprovider';
Expand All @@ -16,7 +15,6 @@ import {
SharedDocumentFactory
} from './tokens';
import { Awareness } from 'y-protocols/awareness';
import { TimelineWidget } from './TimelineSlider';

const DISABLE_RTC =
PageConfig.getOption('disableRTC') === 'true' ? true : false;
Expand All @@ -25,7 +23,16 @@ const DISABLE_RTC =
* The url for the default drive service.
*/
const DOCUMENT_PROVIDER_URL = 'api/collaboration/room';
const DOCUMENT_TIMELINE_URL = 'api/collaboration/timeline';

export interface IForkProvider {
connectToFork: (
action: 'undo' | 'redo',
mode: string,
steps: number
) => Promise<any>;
contentType: string;
format: string;
}

/**
* A Collaborative implementation for an `IDrive`, talking to the
Expand Down Expand Up @@ -60,57 +67,27 @@ export class YDrive extends Drive implements ICollaborativeDrive {
*/
readonly sharedModelFactory: ISharedModelFactory;

disposeTimelineWidget(): void {
if (this._timelineWidget) {
this._timelineWidget.dispose();
this._timelineWidget = null;
async getProviderForPath(path: string): Promise<IForkProvider> {
let key = '';
if (path.split('.')[1] === 'ipynb') {
key = `json:notebook:${path.split(':')[1]}`;
} else if (path.split('.')[1] === 'jcad') {
key = `text:jcad:${path.split(':')[1]}`;
} else {
key = `text:file:${path.split(':')[1]}`;
}
}

setTimelineWidget(widget: TimelineWidget) {
this._timelineWidget = widget;
}

getTimelineWidget(): TimelineWidget | null {
return this._timelineWidget || null;
}

async updateTimelineForNotebook(notebookPath: string): Promise<void> {
try {
if (notebookPath.split(':')[1]) {
const fullPath = URLExt.join(
this.serverSettings.baseUrl,
DOCUMENT_TIMELINE_URL,
notebookPath.split(':')[1]
);
if (this._timelineWidget) {
let key = '';
if (notebookPath.split('.')[1] === 'ipynb') {
key = `json:notebook:${notebookPath.split(':')[1]}`;
} else if (notebookPath.split('.')[1] === 'jcad') {
key = `text:jcad:${notebookPath.split(':')[1]}`;
} else {
key = `text:file:${notebookPath.split(':')[1]}`;
}

const provider = this._providers.get(key);
if (provider) {
this._timelineWidget.updateContent(fullPath, provider);
}
} else {
console.warn('Timeline widget is not initialized or no data fetched');
}
}
} catch (error: any) {
console.error('An unexpected error occurred:', error);
const provider = this._providers.get(key);
if (!provider) {
throw new Error(`No provider found for path: ${path}`);
}
return provider;
}

/**
* Dispose of the resources held by the manager.
*/
dispose(): void {
this.disposeTimelineWidget();
if (this.isDisposed) {
return;
}
Expand Down Expand Up @@ -270,29 +247,6 @@ export class YDrive extends Drive implements ICollaborativeDrive {
}
this._globalAwareness?.setLocalStateField('documents', documents);
});

const notebookPath = URLExt.join(
this.serverSettings.baseUrl,
DOCUMENT_TIMELINE_URL,
options.path
);
if (!this._timelineWidget) {
this._timelineWidget = new TimelineWidget(
notebookPath,
provider,
options.contentType,
options.format
);
} else {
this.updateTimelineForNotebook(options.path);
this._timelineWidget.update();
}
const elt = document.getElementById('slider-status-bar');
if (elt && !this._timelineWidget.isAttached) {
Widget.attach(this._timelineWidget, elt);
} else if (!this._timelineWidget.isAttached) {
Widget.attach(this._timelineWidget, document.body);
}
} catch (error) {
// Falling back to the contents API if opening the websocket failed
// This may happen if the shared document is not a YDocument.
Expand All @@ -307,7 +261,6 @@ export class YDrive extends Drive implements ICollaborativeDrive {
private _providers: Map<string, WebSocketProvider>;
private _globalAwareness: Awareness | null;
private _ydriveFileChanged = new Signal<this, Contents.IChangedArgs>(this);
private _timelineWidget: TimelineWidget | null = null;
}

/**
Expand Down
12 changes: 2 additions & 10 deletions packages/docprovider/src/yprovider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Awareness } from 'y-protocols/awareness';
import { WebsocketProvider as YWebsocketProvider } from 'y-websocket';

import { requestDocFork, requestDocSession } from './requests';
import { IForkProvider } from './ydrive';

/**
* An interface for a document provider.
Expand All @@ -35,7 +36,7 @@ export interface IDocumentProvider extends IDisposable {
*
*/

export class WebSocketProvider implements IDocumentProvider {
export class WebSocketProvider implements IDocumentProvider, IForkProvider {
/**
* Construct a new WebSocketProvider
*
Expand Down Expand Up @@ -98,15 +99,6 @@ export class WebSocketProvider implements IDocumentProvider {
this._disconnect();
Signal.clearData(this);
}
get sharedModel(): YDocument<DocumentChange> {
return this._sharedModel;
}
setPath(path: string) {
this._path = path;
}
setSharedModel(sharedModel: YDocument<DocumentChange>) {
this._sharedModel = sharedModel;
}

private async _connect(): Promise<void> {
const session = await requestDocSession(
Expand Down
15 changes: 5 additions & 10 deletions packages/docprovider/style/slider.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,19 @@
display: flex;
}

.jp-Notebook.frozen {
pointer-events: none;
opacity: 0.5;
}

.timestamp-display {
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
}

.restore-btn button {
cursor: pointer;
color: #fff;
width: 100%;
.restore-btn-container {
width: 192px;
}

.restore-btn {
width: 192px;
cursor: pointer;
color: #fff;
width: 100%;
}
3 changes: 1 addition & 2 deletions projects/jupyter-server-ydoc/jupyter_server_ydoc/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,8 @@ def initialize(
self.ywebsocket_server = ywebsocket_server

async def get(self, path: str) -> None:

file_id_manager = self.settings["file_id_manager"]
file_id = file_id_manager.get_id("/".join(self.request.path.split("/")[4:]))
file_id = file_id_manager.get_id(path)

# get format and content_type
format = str(self.request.query_arguments.get("format")[0].decode("utf-8"))
Expand Down

0 comments on commit 4fbab40

Please sign in to comment.