@jupyter/collaboration 4.3.0 → 4.4.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/collaboratorspanel.js +3 -1
- package/lib/components.js +23 -4
- package/lib/cursors.d.ts +42 -2
- package/lib/cursors.js +10 -1
- package/lib/menu.js +22 -5
- package/package.json +2 -2
- package/style/sidepanel.css +1 -0
|
@@ -51,9 +51,11 @@ export class CollaboratorsPanel extends Panel {
|
|
|
51
51
|
const state = this._awareness.getStates();
|
|
52
52
|
const collaboratorsMap = new Map();
|
|
53
53
|
state.forEach((value, key) => {
|
|
54
|
+
const currentIdentity = this._currentUser.identity;
|
|
54
55
|
if (this._currentUser.isReady &&
|
|
55
56
|
value.user &&
|
|
56
|
-
|
|
57
|
+
currentIdentity &&
|
|
58
|
+
value.user.username !== currentIdentity.username) {
|
|
57
59
|
const uniqueKey = `${value.user.username}-${value.current || 'no-current'}`;
|
|
58
60
|
if (!collaboratorsMap.has(uniqueKey)) {
|
|
59
61
|
collaboratorsMap.set(uniqueKey, value);
|
package/lib/components.js
CHANGED
|
@@ -8,19 +8,38 @@ import React, { useEffect, useState } from 'react';
|
|
|
8
8
|
* @returns The React component
|
|
9
9
|
*/
|
|
10
10
|
export function UserIconComponent(props) {
|
|
11
|
+
var _a;
|
|
11
12
|
const { userManager, onClick } = props;
|
|
12
|
-
const [user, setUser] = useState(userManager.identity)
|
|
13
|
+
const [user, setUser] = useState((_a = userManager.identity) !== null && _a !== void 0 ? _a : {
|
|
14
|
+
display_name: '',
|
|
15
|
+
color: '',
|
|
16
|
+
initials: '',
|
|
17
|
+
avatar_url: ''
|
|
18
|
+
});
|
|
19
|
+
const [imgError, setImgError] = useState(false);
|
|
13
20
|
useEffect(() => {
|
|
14
21
|
const updateUser = () => {
|
|
15
|
-
|
|
22
|
+
var _a;
|
|
23
|
+
setUser((_a = userManager.identity) !== null && _a !== void 0 ? _a : {
|
|
24
|
+
display_name: '',
|
|
25
|
+
color: '',
|
|
26
|
+
initials: '',
|
|
27
|
+
avatar_url: ''
|
|
28
|
+
});
|
|
16
29
|
};
|
|
17
30
|
userManager.userChanged.connect(updateUser);
|
|
18
31
|
return () => {
|
|
19
32
|
userManager.userChanged.disconnect(updateUser);
|
|
20
33
|
};
|
|
21
34
|
}, [userManager]);
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
const showImage = user.avatar_url && !imgError;
|
|
36
|
+
return (React.createElement("div", { title: user.display_name, className: "jp-UserInfo-Icon", style: !showImage ? { backgroundColor: user.color } : undefined, onClick: onClick }, showImage ? (React.createElement("img", { src: user.avatar_url, alt: user.display_name, onError: () => setImgError(true), style: {
|
|
37
|
+
width: '100%',
|
|
38
|
+
height: '100%',
|
|
39
|
+
borderRadius: '50%',
|
|
40
|
+
objectFit: 'cover',
|
|
41
|
+
cursor: 'pointer'
|
|
42
|
+
} })) : (React.createElement("span", null, user.initials))));
|
|
24
43
|
}
|
|
25
44
|
/**
|
|
26
45
|
* React widget for the user details.
|
package/lib/cursors.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Extension } from '@codemirror/state';
|
|
1
|
+
import { Extension, Facet } from '@codemirror/state';
|
|
2
|
+
import { User } from '@jupyterlab/services';
|
|
2
3
|
import { Awareness } from 'y-protocols/awareness';
|
|
3
|
-
import { Text } from 'yjs';
|
|
4
|
+
import { RelativePosition, Text } from 'yjs';
|
|
4
5
|
/**
|
|
5
6
|
* Yjs document objects
|
|
6
7
|
*/
|
|
@@ -14,6 +15,45 @@ export type EditorAwareness = {
|
|
|
14
15
|
*/
|
|
15
16
|
ytext: Text;
|
|
16
17
|
};
|
|
18
|
+
export interface ICursorState {
|
|
19
|
+
/**
|
|
20
|
+
* Cursor anchor
|
|
21
|
+
*/
|
|
22
|
+
anchor: RelativePosition;
|
|
23
|
+
/**
|
|
24
|
+
* Cursor head
|
|
25
|
+
*/
|
|
26
|
+
head: RelativePosition;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the cursor is an empty range or not.
|
|
29
|
+
*
|
|
30
|
+
* Default `true`
|
|
31
|
+
*/
|
|
32
|
+
empty?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the cursor is the primary one or not.
|
|
35
|
+
*
|
|
36
|
+
* Default `false`
|
|
37
|
+
*/
|
|
38
|
+
primary?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Awareness state definition
|
|
42
|
+
*/
|
|
43
|
+
export interface IAwarenessState extends Record<string, any> {
|
|
44
|
+
/**
|
|
45
|
+
* User identity
|
|
46
|
+
*/
|
|
47
|
+
user?: User.IIdentity;
|
|
48
|
+
/**
|
|
49
|
+
* User cursors
|
|
50
|
+
*/
|
|
51
|
+
cursors?: ICursorState[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Facet storing the Yjs document objects
|
|
55
|
+
*/
|
|
56
|
+
export declare const editorAwarenessFacet: Facet<EditorAwareness, EditorAwareness>;
|
|
17
57
|
/**
|
|
18
58
|
* CodeMirror extension to display remote users cursors
|
|
19
59
|
*
|
package/lib/cursors.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createAbsolutePositionFromRelativePosition, createRelativePositionFromJ
|
|
|
7
7
|
/**
|
|
8
8
|
* Facet storing the Yjs document objects
|
|
9
9
|
*/
|
|
10
|
-
const editorAwarenessFacet = Facet.define({
|
|
10
|
+
export const editorAwarenessFacet = Facet.define({
|
|
11
11
|
combine(configs) {
|
|
12
12
|
return configs[configs.length - 1];
|
|
13
13
|
}
|
|
@@ -77,6 +77,9 @@ const remoteCursorsLayer = layer({
|
|
|
77
77
|
markers(view) {
|
|
78
78
|
const { awareness, ytext } = view.state.facet(editorAwarenessFacet);
|
|
79
79
|
const ydoc = ytext.doc;
|
|
80
|
+
if (!ydoc) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
80
83
|
const cursors = [];
|
|
81
84
|
awareness.getStates().forEach((state, clientID) => {
|
|
82
85
|
var _a, _b, _c;
|
|
@@ -117,6 +120,9 @@ const userHover = hoverTooltip((view, pos) => {
|
|
|
117
120
|
var _a;
|
|
118
121
|
const { awareness, ytext } = view.state.facet(editorAwarenessFacet);
|
|
119
122
|
const ydoc = ytext.doc;
|
|
123
|
+
if (!ydoc) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
120
126
|
for (const [clientID, state] of awareness.getStates()) {
|
|
121
127
|
if (clientID === awareness.doc.clientID) {
|
|
122
128
|
continue;
|
|
@@ -160,6 +166,9 @@ const remoteSelectionLayer = layer({
|
|
|
160
166
|
markers(view) {
|
|
161
167
|
const { awareness, ytext } = view.state.facet(editorAwarenessFacet);
|
|
162
168
|
const ydoc = ytext.doc;
|
|
169
|
+
if (!ydoc) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
163
172
|
const cursors = [];
|
|
164
173
|
awareness.getStates().forEach((state, clientID) => {
|
|
165
174
|
var _a, _b, _c;
|
package/lib/menu.js
CHANGED
|
@@ -46,16 +46,33 @@ export class RendererUserMenu extends MenuBar.Renderer {
|
|
|
46
46
|
* @returns A virtual element representing the item label.
|
|
47
47
|
*/
|
|
48
48
|
_createUserIcon() {
|
|
49
|
-
|
|
49
|
+
const identity = this._user.identity;
|
|
50
|
+
if (this._user.isReady && (identity === null || identity === void 0 ? void 0 : identity.avatar_url)) {
|
|
50
51
|
return h.div({
|
|
51
52
|
className: 'lm-MenuBar-itemIcon jp-MenuBar-imageIcon'
|
|
52
|
-
}, h.img({
|
|
53
|
+
}, h.img({
|
|
54
|
+
src: identity.avatar_url,
|
|
55
|
+
onerror: (event) => {
|
|
56
|
+
const img = event.currentTarget;
|
|
57
|
+
const parent = img.parentElement;
|
|
58
|
+
if (parent) {
|
|
59
|
+
// Clear broken image and fallback to initials with colors
|
|
60
|
+
parent.textContent = '';
|
|
61
|
+
parent.style.backgroundColor = identity.color || '';
|
|
62
|
+
const span = document.createElement('span');
|
|
63
|
+
span.textContent = identity.initials;
|
|
64
|
+
parent.appendChild(span);
|
|
65
|
+
parent.classList.remove('jp-MenuBar-imageIcon');
|
|
66
|
+
parent.classList.add('jp-MenuBar-anonymousIcon');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}));
|
|
53
70
|
}
|
|
54
|
-
else if (this._user.isReady) {
|
|
71
|
+
else if (this._user.isReady && identity) {
|
|
55
72
|
return h.div({
|
|
56
73
|
className: 'lm-MenuBar-itemIcon jp-MenuBar-anonymousIcon',
|
|
57
|
-
style: { backgroundColor:
|
|
58
|
-
}, h.span({},
|
|
74
|
+
style: { backgroundColor: identity.color }
|
|
75
|
+
}, h.span({}, identity.initials));
|
|
59
76
|
}
|
|
60
77
|
else {
|
|
61
78
|
return h.div({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyter/collaboration",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0-beta.0",
|
|
4
4
|
"description": "JupyterLab - Real-Time Collaboration Widgets",
|
|
5
5
|
"homepage": "https://github.com/jupyterlab/jupyter-collaboration",
|
|
6
6
|
"bugs": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/react": "~18.3.1",
|
|
60
60
|
"rimraf": "^4.1.2",
|
|
61
|
-
"typescript": "~5.
|
|
61
|
+
"typescript": "~5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|