Skip to content

Commit

Permalink
Allow undoing changes when connecting, add handler for deleting fork
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Apr 2, 2024
1 parent ea5303e commit a63043a
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 9 deletions.
9 changes: 8 additions & 1 deletion jupyter_collaboration/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pycrdt_websocket.ystore import BaseYStore
from traitlets import Bool, Float, Type

from .handlers import DocForkHandler, DocMergeHandler, DocSessionHandler, YDocWebSocketHandler
from .handlers import DocForkHandler, DocDeleteHandler, DocMergeHandler, DocSessionHandler, YDocWebSocketHandler
from .loaders import FileLoaderMapping
from .stores import SQLiteYStore
from .utils import AWARENESS_EVENTS_SCHEMA_PATH, EVENTS_SCHEMA_PATH
Expand Down Expand Up @@ -123,6 +123,13 @@ def initialize_handlers(self):
"ywebsocket_server": self.ywebsocket_server,
}
),
(
r"/api/collaboration/delete_room",
DocDeleteHandler,
{
"ywebsocket_server": self.ywebsocket_server,
}
),
]
)

Expand Down
44 changes: 42 additions & 2 deletions jupyter_collaboration/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,13 +471,53 @@ async def put(self):
if idx in root_state:
del root_state[idx]
else:
self.set_status(404)
raise RuntimeError(f"Could not find root document fork with ID: {fork_roomid}")
fork_room = await self._websocket_server.get_room(fork_roomid)
fork_ydoc = fork_room.ydoc
update = fork_ydoc.get_update()
root_ydoc.apply_update(update)
fork_update = fork_ydoc.get_update()
root_ydoc.apply_update(fork_update)
root_room.fork_ydocs.remove(fork_ydoc)
fork_state = fork_ydoc.get("state", type=Map)
fork_state["merge"] = fork_roomid
#self._websocket_server.delete_room(name=fork_roomid)
self.set_status(200)


class DocDeleteHandler(APIHandler):
"""
Jupyter Server's handler to delete a document.
"""

auth_resource = "contents"

def initialize(
self,
ywebsocket_server: JupyterWebsocketServer,
) -> None:
self._websocket_server = ywebsocket_server

@web.authenticated
@authorized
async def delete(self):
"""
Deletes a forked document.
"""
model = self.get_json_body()
fork_roomid = model["fork_roomid"]
root_room = await self._websocket_server.get_room(model["root_roomid"])
root_ydoc = root_room.ydoc
idx = f"fork_{fork_roomid}"
root_state = root_ydoc.get("state", type=Map)
if idx in root_state:
del root_state[idx]
else:
self.set_status(404)
raise RuntimeError(f"Could not find root document fork with ID: {fork_roomid}")
fork_room = await self._websocket_server.get_room(fork_roomid)
fork_ydoc = fork_room.ydoc
root_room.fork_ydocs.remove(fork_ydoc)
fork_state = fork_ydoc.get("state", type=Map)
fork_state["delete"] = fork_roomid
#self._websocket_server.delete_room(name=fork_roomid)
self.set_status(200)
8 changes: 5 additions & 3 deletions packages/collaboration-extension/src/collaboration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
EditorExtensionRegistry,
IEditorExtensionRegistry
} from '@jupyterlab/codemirror';
import { requestDocMerge, WebSocketAwarenessProvider } from '@jupyter/docprovider';
import { requestDocDelete, requestDocMerge, WebSocketAwarenessProvider } from '@jupyter/docprovider';
import {
SidePanel,
usersIcon,
Expand Down Expand Up @@ -310,6 +310,7 @@ export class EditingModeExtension implements DocumentRegistry.IWidgetExtension<N
reviewCommands.addCommand('discard', {
label: 'Discard',
execute: () => {
requestDocDelete(sharedModel.currentRoomId, sharedModel.rootRoomId);
}
});

Expand All @@ -322,7 +323,7 @@ export class EditingModeExtension implements DocumentRegistry.IWidgetExtension<N
if (changes.stateChange) {
changes.stateChange.forEach(value => {
const forkPrefix = 'fork_';
if (value.name === 'merge') {
if (value.name === 'merge' || value.name === 'delete') {
// FIXME: a client who is not connected to the fork should not see this update
if (sharedModel.currentRoomId === value.newValue) {
editingMenu.title.label = 'Editing';
Expand All @@ -331,7 +332,8 @@ export class EditingModeExtension implements DocumentRegistry.IWidgetExtension<N
delete suggestions[value.newValue];
suggestionMenu.removeItem(item);
reviewMenu.clearItems();
sharedModel.provider.connect(sharedModel.rootRoomId);
const merge = value.name === 'merge';
sharedModel.provider.connect(sharedModel.rootRoomId, merge);
open_dialog('Editing', this._trans);
}
}
Expand Down
40 changes: 40 additions & 0 deletions packages/docprovider/src/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ServerConnection, Contents } from '@jupyterlab/services';
const DOC_SESSION_URL = 'api/collaboration/session';
const DOC_FORK_URL = 'api/collaboration/fork_room';
const DOC_MERGE_URL = 'api/collaboration/merge_room';
const DOC_DELETE_URL = 'api/collaboration/delete_room';

/**
* Document session model
Expand Down Expand Up @@ -150,3 +151,42 @@ export async function requestDocMerge(

return data;
}


export async function requestDocDelete(
forkRoomid: string,
rootRoomid: string,
): Promise<any> {
const settings = ServerConnection.makeSettings();
const url = URLExt.join(
settings.baseUrl,
DOC_DELETE_URL,
);
const body = {
method: 'DELETE',
body: JSON.stringify({ fork_roomid: forkRoomid, root_roomid: rootRoomid })
};

let response: Response;
try {
response = await ServerConnection.makeRequest(url, body, settings);
} catch (error) {
throw new ServerConnection.NetworkError(error as Error);
}

let data: any = await response.text();

if (data.length > 0) {
try {
data = JSON.parse(data);
} catch (error) {
console.log('Not a JSON response body.', response);
}
}

if (!response.ok) {
throw new ServerConnection.ResponseError(response, data.message || data);
}

return data;
}
18 changes: 17 additions & 1 deletion packages/docprovider/src/yprovider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,25 @@ export class WebSocketProvider implements IDocumentProvider {
return forkId;
}

connect(roomId: string) {
connect(roomId: string, merge?: boolean) {
this._sharedModel.currentRoomId = roomId;
this._yWebsocketProvider?.disconnect();
if (roomId === this._sharedModel.rootRoomId) {
// connecting to the root
// don't bring our changes there if not merging
if (merge !== true) {
while (this._sharedModel.undoManager.canUndo()) {
this._sharedModel.undoManager.undo();
}
}
this._sharedModel.undoManager.clear();
}
else {
// connecting to a fork
// keep track of changes so that we can undo them when connecting back to root
this._sharedModel.undoManager.clear();
}

this._yWebsocketProvider = new YWebsocketProvider(
this._serverUrl,
roomId,
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2156,7 +2156,7 @@ __metadata:

"@jupyter/ydoc@file:.yalc/@jupyter/ydoc::locator=%40jupyter%2Freal-time-collaboration%40workspace%3A.":
version: 2.0.1
resolution: "@jupyter/ydoc@file:.yalc/@jupyter/ydoc#.yalc/@jupyter/ydoc::hash=e50509&locator=%40jupyter%2Freal-time-collaboration%40workspace%3A."
resolution: "@jupyter/ydoc@file:.yalc/@jupyter/ydoc#.yalc/@jupyter/ydoc::hash=045bce&locator=%40jupyter%2Freal-time-collaboration%40workspace%3A."
dependencies:
"@jupyterlab/application": ^4.0.0
"@jupyterlab/nbformat": ^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0
Expand All @@ -2165,7 +2165,7 @@ __metadata:
"@lumino/signaling": ^1.10.0 || ^2.0.0
y-protocols: ^1.0.5
yjs: ^13.5.40
checksum: c54e335aebc1f0b28241fe0031d5f47513a7a90621b5cca10e6aec3a965adea96390bd8e2c51191e448a9c276ca284ff563eb4870844233798e79a03560b1648
checksum: aed2b93d1f9d447e7ad1be8699dc3a31968f4cfd4772a1ed1330308eb08ba8d14973df24bae4e49bd93f416f646b84edb44c567487403e7d0bdb665e6ca0e29f
languageName: node
linkType: hard

Expand Down

0 comments on commit a63043a

Please sign in to comment.