Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/docprovider/filebrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,9 @@ import {
ICollaborativeContentProvider,
IGlobalAwareness
} from '@jupyter/collaborative-drive';
import {
RtcContentProvider
} from './ydrive';
import { RtcContentProvider } from './ydrive';
import { Awareness } from 'y-protocols/awareness';


const TWO_SESSIONS_WARNING =
'The file %1 has been opened with two different views. ' +
'This is not supported. Please close this view; otherwise, ' +
Expand Down Expand Up @@ -132,7 +129,7 @@ export const ynotebook: JupyterFrontEndPlugin<void> = {
const enableDocWideUndo = settings?.get(
'experimentalEnableDocumentWideUndoRedo'
).composite as boolean;

// @ts-ignore
disableDocumentWideUndoRedo = !enableDocWideUndo ?? true;
};
Expand Down
2 changes: 1 addition & 1 deletion src/docprovider/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

export * from './filebrowser'
export * from './filebrowser';
2 changes: 1 addition & 1 deletion src/docprovider/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ export async function requestAPI<T = any>(
}

return data;
}
}
2 changes: 1 addition & 1 deletion src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function requestAPI<T>(
const settings = ServerConnection.makeSettings();
const requestUrl = URLExt.join(
settings.baseUrl,
'jupyter-rtc-core', // API Namespace
endPoint.startsWith('/') ? '' : 'jupyter-rtc-core', // API Namespace
endPoint
);

Expand Down
28 changes: 21 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,12 @@ import {
import {
AwarenessExecutionIndicator
} from './executionindicator';

import { IEditorServices } from '@jupyterlab/codeeditor';
import { requestAPI } from './handler';
import { YNotebookContentFactory } from './notebook';

import {
rtcContentProvider,
yfile,
ynotebook,
logger
} from './docprovider';
import { rtcContentProvider, yfile, ynotebook, logger } from './docprovider';

import { IStateDB, StateDB } from '@jupyterlab/statedb';
import { IGlobalAwareness } from '@jupyter/collaborative-drive';
Expand Down Expand Up @@ -284,6 +282,21 @@ export const kernelStatus: JupyterFrontEndPlugin<IKernelStatusModel> = {
};


/**
* The notebook cell factory provider.
*/
const factory: JupyterFrontEndPlugin<NotebookPanel.IContentFactory> = {
id: '@jupyter/rtc-core/notebook-extension:factory',
description: 'Provides the notebook cell factory.',
provides: NotebookPanel.IContentFactory,
requires: [IEditorServices],
autoStart: true,
activate: (app: JupyterFrontEnd, editorServices: IEditorServices) => {
const editorFactory = editorServices.factoryService.newInlineEditor;
return new YNotebookContentFactory({ editorFactory });
}
};

const plugins: JupyterFrontEndPlugin<unknown>[] = [
rtcContentProvider,
yfile,
Expand All @@ -292,7 +305,8 @@ const plugins: JupyterFrontEndPlugin<unknown>[] = [
rtcGlobalAwarenessPlugin,
plugin,
executionIndicator,
kernelStatus
kernelStatus,
factory
];

export default plugins;
Expand Down
222 changes: 222 additions & 0 deletions src/notebook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// @ts-nocheck
import {
Cell,
CodeCell,
CellModel,
CodeCellModel,
MarkdownCellModel,
RawCellModel
} from '@jupyterlab/cells';
import { NotebookPanel } from '@jupyterlab/notebook';
import { KernelMessage } from '@jupyterlab/services';
import {
CellChange,
createMutex,
ISharedCodeCell,
ISharedMarkdownCell,
ISharedRawCell,
YCodeCell
} from '@jupyter/ydoc';
import { IOutputAreaModel, OutputAreaModel } from '@jupyterlab/outputarea';
import { requestAPI } from './handler';
import { CellList } from '@jupyterlab/notebook';

import { ObservableList } from '@jupyterlab/observables';

const globalModelDBMutex = createMutex();


// @ts-ignore
CodeCellModel.prototype._onSharedModelChanged = function (
slot: ISharedCodeCell,
change: CellChange
) {
if (change.streamOutputChange) {
globalModelDBMutex(() => {
for (const streamOutputChange of change.streamOutputChange!) {
if ('delete' in streamOutputChange) {
// @ts-ignore
this._outputs.removeStreamOutput(streamOutputChange.delete!);
}
if ('insert' in streamOutputChange) {
// @ts-ignore
this._outputs.appendStreamOutput(
streamOutputChange.insert!.toString()
);
}
}
});
}

if (change.outputsChange) {
globalModelDBMutex(() => {
let retain = 0;
for (const outputsChange of change.outputsChange!) {
if ('retain' in outputsChange) {
retain += outputsChange.retain!;
}
if ('delete' in outputsChange) {
for (let i = 0; i < outputsChange.delete!; i++) {
// @ts-ignore
this._outputs.remove(retain);
}
}
if ('insert' in outputsChange) {
// Inserting an output always results in appending it.
for (const output of outputsChange.insert!) {
// For compatibility with older ydoc where a plain object,
// (rather than a Map instance) could be provided.
// In a future major release the use of Map will be required.
//@ts-ignore
if ('toJSON' in output) {
// @ts-ignore
const parsed = output.toJSON();
const metadata = parsed.metadata;
if (metadata && metadata.url) {
// fetch the real output
requestAPI(metadata.url).then(data => {
// @ts-ignore
this._outputs.add(data);
});
} else {
// @ts-ignore
this._outputs.add(parsed);
}
} else {
console.debug('output from doc: ', output);
// @ts-ignore
this._outputs.add(output);
}
}
}
}
});
}
if (change.executionCountChange) {
if (
change.executionCountChange.newValue &&
// @ts-ignore
(this.isDirty || !change.executionCountChange.oldValue)
) {
// @ts-ignore
this._setDirty(false);
}
// @ts-ignore
this.stateChanged.emit({
name: 'executionCount',
oldValue: change.executionCountChange.oldValue,
newValue: change.executionCountChange.newValue
});
}

if (change.executionStateChange) {
// @ts-ignore
this.stateChanged.emit({
name: 'executionState',
oldValue: change.executionStateChange.oldValue,
newValue: change.executionStateChange.newValue
});
}
// @ts-ignore
if (change.sourceChange && this.executionCount !== null) {
// @ts-ignore
this._setDirty(this._executedCode !== this.sharedModel.getSource().trim());
}
};

// @ts-ignore
CodeCellModel.prototype.onOutputsChange = function (
sender: IOutputAreaModel,
event: IOutputAreaModel.ChangedArgs
) {
console.debug('Inside onOutputsChange, called with event: ', event);
return
// @ts-ignore
const codeCell = this.sharedModel as YCodeCell;
globalModelDBMutex(() => {
if (event.type == 'remove') {
codeCell.updateOutputs(event.oldIndex, event.oldValues.length, []);
}
});
};

class RtcOutputAreaModel extends OutputAreaModel implements IOutputAreaModel{
/**
* Construct a new observable outputs instance.
*/
constructor(options: IOutputAreaModel.IOptions = {}) {
super({...options, values: []})
this._trusted = !!options.trusted;
this.contentFactory =
options.contentFactory || OutputAreaModel.defaultContentFactory;
this.list = new ObservableList<IOutputModel>();
if (options.values) {
// Create an array to store promises for each value
const valuePromises = options.values.map((value, originalIndex) => {
console.log("originalIndex: ", originalIndex, ", value: ", value);
// If value has a URL, fetch the data, otherwise just use the value directly
if (value.metadata?.url) {
return requestAPI(value.metadata.url)
.then(data => {
console.log("data from outputs service: " , data)
return {data, originalIndex}
})
.catch(error => {
console.error('Error fetching output:', error);
// If fetch fails, return original value to maintain order
return { data: null, originalIndex };
});
} else {
// For values without url, return immediately with original value
return Promise.resolve({ data: value, originalIndex });
}
});

// Wait for all promises to resolve and add values in original order
Promise.all(valuePromises)
.then(results => {
// Sort by original index to maintain order
results.sort((a, b) => a.originalIndex - b.originalIndex);

console.log("After fetching outputs...")
// Add each value in order
results.forEach((result) => {
console.log("originalIndex: ", result.originalIndex, ", data: ", result.data)
if(result.data && !this.isDisposed){
const index = this._add(result.data) - 1;
const item = this.list.get(index);
item.changed.connect(this._onGenericChange, this);
}
});

// Connect the list changed handler after all items are added
//this.list.changed.connect(this._onListChanged, this);
})/*
.catch(error => {
console.error('Error processing values:', error);
// If something goes wrong, fall back to original behavior
options.values.forEach(value => {
const index = this._add(value) - 1;
const item = this.list.get(index);
item.changed.connect(this._onGenericChange, this);
});
this.list.changed.connect(this._onListChanged, this);
});*/
} else {
// If no values, just connect the list changed handler
//this.list.changed.connect(this._onListChanged, this);
}

this.list.changed.connect(this._onListChanged, this);
}
}

CodeCellModel.ContentFactory.prototype.createOutputArea = function(options: IOutputAreaModel.IOptions): IOutputAreaModel {
return new RtcOutputAreaModel(options);
}

export class YNotebookContentFactory extends NotebookPanel.ContentFactory implements NotebookPanel.IContentFactory{
createCodeCell(options: CodeCell.IOptions): CodeCell {
return new CodeCell(options).initializeState();
}
}
Loading