Skip to content

Commit 1492678

Browse files
Implementing Collaborative Timeline Slider with Undo/Redo Functionality (#338)
* collaborative timeline slider added in the status bar dependency conflicts fixed timestamp order fork handler added connect fork read only notebook connected (fork) fork document in frontend fork in the frontend + apply updates added fork indication to the page added fork indication to the page distinction between fork and original document added to slider adjustement in fork handler file add css slider context menu option for opening file (fork) + timeline added notebook condition added file menu option to open timeline for notebook alternative: add file menu option to open right widget added timeline slider to status bar slider added to status bar status bar timeline slider with logic to switch between notebooks added error handling in the backend handler clean console add isActive to the registerStatusItem fix: internal server error fixed in updateTimelineforNotebook function fork cnd added to timeline status bar plugin undo manager refactoring fork logic for 1 notebook. fork logic implemented for multiple notebooks opened at the same time ydoc.share fork works with multiple notebooks + freezing notebook when sliding through timeline notebook id testing cad undo manager tests clearDoc method remove is timelineFork open rebase build error resolved using undo manager in the backend undo/redo steps added freeze document updated to the new version of pycrdt, fixed problem with undostack rebase restore version btn undo manager agnostic for different type of documents restoring version restoring version restore btn : fix style delete unused files delete unused files jupytercad jupyter cad jupytercad integration + rebase main conflicts conflicts fixed console error: empty response. icon visible only when data is not null moving fetch timeline in slider component: on click of the history button get format & content type from query params get format & content type from query params: fix updating contenttype and format when switching between documents remove sharedmodel from update content support for documents inside folders/subfolders. clean drive.ts move test files in folder delete unused dependency return comments deleted by accident fixes in jupyter-server-ydoc delete test documents add test-folder to gitignore styling restore button styling restore button pre commit hooks fixed pre commit issues fixed pre commit issues add license header to new files pre commit hooks python test: added jupytercad_core to CI/CD workflow python test debug python test debug collaborative timeline slider added in the status bar dependency conflicts fixed timestamp order fork handler added connect fork read only notebook connected (fork) fork document in frontend fork in the frontend + apply updates added fork indication to the page added fork indication to the page distinction between fork and original document added to slider adjustement in fork handler file add css slider context menu option for opening file (fork) + timeline added notebook condition added file menu option to open timeline for notebook alternative: add file menu option to open right widget added timeline slider to status bar slider added to status bar status bar timeline slider with logic to switch between notebooks added error handling in the backend handler clean console add isActive to the registerStatusItem fix: internal server error fixed in updateTimelineforNotebook function fork cnd added to timeline status bar plugin undo manager refactoring fork logic for 1 notebook. fork logic implemented for multiple notebooks opened at the same time ydoc.share fork works with multiple notebooks + freezing notebook when sliding through timeline notebook id testing cad undo manager tests clearDoc method remove is timelineFork open rebase build error resolved using undo manager in the backend undo/redo steps added freeze document updated to the new version of pycrdt, fixed problem with undostack rebase restore version btn undo manager agnostic for different type of documents restoring version restoring version restore btn : fix style delete unused files delete unused files jupytercad jupyter cad jupytercad integration + rebase main conflicts conflicts fixed console error: empty response. icon visible only when data is not null moving fetch timeline in slider component: on click of the history button get format & content type from query params get format & content type from query params: fix updating contenttype and format when switching between documents remove sharedmodel from update content support for documents inside folders/subfolders. clean drive.ts move test files in folder delete unused dependency return comments deleted by accident fixes in jupyter-server-ydoc delete test documents add test-folder to gitignore styling restore button styling restore button pre commit hooks fixed pre commit issues python test debug: test.yaml python test debug: test.yaml python test debug: test.yaml changed order of dependencies in test.yml removed jupytercad to test dependencies version removed jupytercad to test dependencies version pre commit changed the way document types are imported in the backend fixed yarn.lock after rebase * added code changes as requested. * fixed issue with opening timeline after closing notebook & generic type in handler * slider set to undo stack updates, improved fork handling * remove global variable keeping track of undo managers remove global variable keeping track of undo managers remove print * reconnect & refetching timeline after restore. * add loggin and error handling in handlers. * removed unused code in restore endpoint. * update pycrdt-websocket version requirement. * update pycrdt-websocket version requirement. * styling issues resolved. * remove widget attachement to body.
1 parent 2ca6b0f commit 1492678

File tree

16 files changed

+2354
-1334
lines changed

16 files changed

+2354
-1334
lines changed

packages/docprovider-extension/src/filebrowser.ts

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ import {
1010
JupyterFrontEndPlugin
1111
} from '@jupyterlab/application';
1212
import { Dialog, showDialog } from '@jupyterlab/apputils';
13-
import { IDocumentWidget } from '@jupyterlab/docregistry';
13+
import { DocumentWidget, IDocumentWidget } from '@jupyterlab/docregistry';
14+
import { Widget } from '@lumino/widgets';
1415
import {
1516
FileBrowser,
1617
IDefaultFileBrowser,
1718
IFileBrowserFactory
1819
} from '@jupyterlab/filebrowser';
20+
import { IStatusBar } from '@jupyterlab/statusbar';
21+
1922
import { IEditorTracker } from '@jupyterlab/fileeditor';
2023
import { ILogger, ILoggerRegistry } from '@jupyterlab/logconsole';
2124
import { INotebookTracker } from '@jupyterlab/notebook';
@@ -28,17 +31,21 @@ import { YFile, YNotebook } from '@jupyter/ydoc';
2831

2932
import {
3033
ICollaborativeDrive,
34+
IForkProvider,
3135
IGlobalAwareness,
36+
TimelineWidget,
3237
YDrive
3338
} from '@jupyter/docprovider';
3439
import { Awareness } from 'y-protocols/awareness';
40+
import { URLExt } from '@jupyterlab/coreutils';
3541

3642
/**
3743
* The command IDs used by the file browser plugin.
3844
*/
3945
namespace CommandIDs {
4046
export const openPath = 'filebrowser:open-path';
4147
}
48+
const DOCUMENT_TIMELINE_URL = 'api/collaboration/timeline';
4249

4350
/**
4451
* The default collaborative drive provider.
@@ -91,7 +98,7 @@ export const ynotebook: JupyterFrontEndPlugin<void> = {
9198
optional: [ISettingRegistry],
9299
activate: (
93100
app: JupyterFrontEnd,
94-
drive: ICollaborativeDrive,
101+
drive: YDrive,
95102
settingRegistry: ISettingRegistry | null
96103
): void => {
97104
let disableDocumentWideUndoRedo = true;
@@ -127,6 +134,93 @@ export const ynotebook: JupyterFrontEndPlugin<void> = {
127134
);
128135
}
129136
};
137+
/**
138+
* A plugin to add a timeline slider status item to the status bar.
139+
*/
140+
export const statusBarTimeline: JupyterFrontEndPlugin<void> = {
141+
id: '@jupyter/docprovider-extension:statusBarTimeline',
142+
description: 'Plugin to add a timeline slider to the status bar',
143+
autoStart: true,
144+
requires: [IStatusBar, ICollaborativeDrive],
145+
activate: async (
146+
app: JupyterFrontEnd,
147+
statusBar: IStatusBar,
148+
drive: ICollaborativeDrive
149+
): Promise<void> => {
150+
function isYDrive(drive: YDrive | ICollaborativeDrive): drive is YDrive {
151+
return 'getProviderForPath' in drive;
152+
}
153+
try {
154+
let sliderItem: Widget | null = null;
155+
let timelineWidget: TimelineWidget | null = null;
156+
157+
const updateTimelineForDocument = async (documentPath: string) => {
158+
if (drive && isYDrive(drive)) {
159+
// Dispose of the previous timelineWidget if it exists
160+
if (timelineWidget) {
161+
timelineWidget.dispose();
162+
timelineWidget = null;
163+
}
164+
165+
const provider = (await drive.getProviderForPath(
166+
documentPath
167+
)) as IForkProvider;
168+
const fullPath = URLExt.join(
169+
app.serviceManager.serverSettings.baseUrl,
170+
DOCUMENT_TIMELINE_URL,
171+
documentPath.split(':')[1]
172+
);
173+
174+
timelineWidget = new TimelineWidget(
175+
fullPath,
176+
provider,
177+
provider.contentType,
178+
provider.format
179+
);
180+
181+
const elt = document.getElementById('jp-slider-status-bar');
182+
if (elt && !timelineWidget.isAttached) {
183+
Widget.attach(timelineWidget, elt);
184+
}
185+
}
186+
};
187+
188+
if (app.shell.currentChanged) {
189+
app.shell.currentChanged.connect(async (_, args) => {
190+
const currentWidget = args.newValue as DocumentWidget;
191+
if (timelineWidget) {
192+
// Dispose of the timelineWidget when the document is closed
193+
timelineWidget.dispose();
194+
timelineWidget = null;
195+
}
196+
if (currentWidget && 'context' in currentWidget) {
197+
await updateTimelineForDocument(currentWidget.context.path);
198+
}
199+
});
200+
}
201+
202+
if (statusBar) {
203+
if (!sliderItem) {
204+
sliderItem = new Widget();
205+
sliderItem.addClass('jp-StatusBar-GroupItem');
206+
sliderItem.addClass('jp-mod-highlighted');
207+
sliderItem.id = 'jp-slider-status-bar';
208+
statusBar.registerStatusItem('jp-slider-status-bar', {
209+
item: sliderItem,
210+
align: 'left',
211+
rank: 4,
212+
isActive: () => {
213+
const currentWidget = app.shell.currentWidget;
214+
return !!currentWidget && 'context' in currentWidget;
215+
}
216+
});
217+
}
218+
}
219+
} catch (error) {
220+
console.error('Failed to activate statusBarTimeline plugin:', error);
221+
}
222+
}
223+
};
130224

131225
/**
132226
* The default file browser factory provider.
@@ -144,7 +238,7 @@ export const defaultFileBrowser: JupyterFrontEndPlugin<IDefaultFileBrowser> = {
144238
],
145239
activate: async (
146240
app: JupyterFrontEnd,
147-
drive: ICollaborativeDrive,
241+
drive: YDrive,
148242
fileBrowserFactory: IFileBrowserFactory,
149243
router: IRouter | null,
150244
tree: JupyterFrontEnd.ITreeResolver | null,
@@ -292,6 +386,7 @@ namespace Private {
292386
router.routed.disconnect(listener);
293387

294388
const paths = await tree?.paths;
389+
295390
if (paths?.file || paths?.browser) {
296391
// Restore the model without populating it.
297392
await browser.model.restore(browser.id, false);

packages/docprovider-extension/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
yfile,
1313
ynotebook,
1414
defaultFileBrowser,
15-
logger
15+
logger,
16+
statusBarTimeline
1617
} from './filebrowser';
1718
import { notebookCellExecutor } from './executor';
1819

@@ -25,7 +26,8 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
2526
ynotebook,
2627
defaultFileBrowser,
2728
logger,
28-
notebookCellExecutor
29+
notebookCellExecutor,
30+
statusBarTimeline
2931
];
3032

3133
export default plugins;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* -----------------------------------------------------------------------------
2+
| Copyright (c) Jupyter Development Team.
3+
| Distributed under the terms of the Modified BSD License.
4+
|----------------------------------------------------------------------------*/
5+
6+
import { ReactWidget } from '@jupyterlab/apputils';
7+
import { TimelineSliderComponent } from './component';
8+
import * as React from 'react';
9+
import { IForkProvider } from './ydrive';
10+
11+
export class TimelineWidget extends ReactWidget {
12+
private apiURL: string;
13+
private provider: IForkProvider;
14+
private contentType: string;
15+
private format: string;
16+
17+
constructor(
18+
apiURL: string,
19+
provider: IForkProvider,
20+
contentType: string,
21+
format: string
22+
) {
23+
super();
24+
this.apiURL = apiURL;
25+
this.provider = provider;
26+
this.contentType = contentType;
27+
this.format = format;
28+
this.addClass('jp-timelineSliderWrapper');
29+
}
30+
31+
render(): JSX.Element {
32+
return (
33+
<TimelineSliderComponent
34+
key={this.apiURL}
35+
apiURL={this.apiURL}
36+
provider={this.provider}
37+
contentType={this.contentType}
38+
format={this.format}
39+
/>
40+
);
41+
}
42+
updateContent(apiURL: string, provider: IForkProvider): void {
43+
this.apiURL = apiURL;
44+
this.provider = provider;
45+
this.contentType = this.provider.contentType;
46+
this.format = this.provider.format;
47+
48+
this.update();
49+
}
50+
}

0 commit comments

Comments
 (0)