-
-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Provide a widget that can be used in a collaborative document to display current users * Automatic application of license header * Move the widget to collaboration package * Add style and allow empty model (for notebook panel for example) * Send the document model to the iconRenderer, and avoid displaying cursor on Anonymous icons * Improve the default icon renderer - allow the default renderer to receive additional classes - rename it for consistency - avoid sending non related props to div element * CSS * lint --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
47305e4
commit 22a505b
Showing
9 changed files
with
235 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
/* | ||
* Copyright (c) Jupyter Development Team. | ||
* Distributed under the terms of the Modified BSD License. | ||
*/ | ||
|
||
import { DocumentRegistry } from '@jupyterlab/docregistry'; | ||
import { User } from '@jupyterlab/services'; | ||
import { classes, ReactWidget } from '@jupyterlab/ui-components'; | ||
import * as React from 'react'; | ||
|
||
const USERS_ITEM_CLASS = 'jp-toolbar-users-item'; | ||
|
||
/** | ||
* The namespace for the UsersItem component. | ||
*/ | ||
export namespace UsersItem { | ||
/** | ||
* Properties of the component. | ||
*/ | ||
export interface IProps { | ||
/** | ||
* The model of the document. | ||
*/ | ||
model: DocumentRegistry.IModel | null; | ||
|
||
/** | ||
* A function to display the user icons, optional. | ||
* This function will overwrite the default one, and can be used to handle event on | ||
* icons. | ||
*/ | ||
iconRenderer?: (props: UsersItem.IIconRendererProps) => JSX.Element; | ||
} | ||
|
||
/** | ||
* The state of the component. | ||
*/ | ||
export interface IState { | ||
/** | ||
* The user list. | ||
*/ | ||
usersList: IUserData[]; | ||
} | ||
|
||
/** | ||
* Properties send to the iconRenderer function. | ||
*/ | ||
export interface IIconRendererProps | ||
extends React.HTMLAttributes<HTMLElement> { | ||
/** | ||
* The user. | ||
*/ | ||
user: IUserData; | ||
|
||
/** | ||
* The document's model. | ||
*/ | ||
model?: DocumentRegistry.IModel; | ||
} | ||
|
||
/** | ||
* The user data type. | ||
*/ | ||
export type IUserData = { | ||
/** | ||
* User id (the client id of the awareness). | ||
*/ | ||
userId: number; | ||
/** | ||
* User data. | ||
*/ | ||
userData: User.IIdentity; | ||
}; | ||
} | ||
|
||
/** | ||
* A component displaying the collaborative users of a document. | ||
*/ | ||
export class UsersItem extends React.Component< | ||
UsersItem.IProps, | ||
UsersItem.IState | ||
> { | ||
constructor(props: UsersItem.IProps) { | ||
super(props); | ||
this._model = props.model; | ||
this._iconRenderer = props.iconRenderer ?? null; | ||
this.state = { usersList: [] }; | ||
} | ||
|
||
/** | ||
* Static method to create a widget. | ||
*/ | ||
static createWidget(options: UsersItem.IProps): ReactWidget { | ||
return ReactWidget.create(<UsersItem {...options} />); | ||
} | ||
|
||
componentDidMount(): void { | ||
this._model?.sharedModel.awareness.on('change', this._awarenessChange); | ||
this._awarenessChange(); | ||
} | ||
|
||
/** | ||
* Filter out the duplicated users, which can happen temporary on reload. | ||
*/ | ||
private filterDuplicated( | ||
usersList: UsersItem.IUserData[] | ||
): UsersItem.IUserData[] { | ||
const newList: UsersItem.IUserData[] = []; | ||
const selected = new Set<string>(); | ||
for (const element of usersList) { | ||
if ( | ||
element?.userData?.username && | ||
!selected.has(element.userData.username) | ||
) { | ||
selected.add(element.userData.username); | ||
newList.push(element); | ||
} | ||
} | ||
return newList; | ||
} | ||
|
||
render(): React.ReactNode { | ||
const IconRenderer = this._iconRenderer ?? DefaultIconRenderer; | ||
return ( | ||
<div className={USERS_ITEM_CLASS}> | ||
{this.filterDuplicated(this.state.usersList).map(user => { | ||
if ( | ||
this._model && | ||
user.userId !== this._model.sharedModel.awareness.clientID | ||
) { | ||
return IconRenderer({ user, model: this._model }); | ||
} | ||
})} | ||
</div> | ||
); | ||
} | ||
|
||
/** | ||
* Triggered when a change occurs in the document awareness, to build again the users list. | ||
*/ | ||
private _awarenessChange = () => { | ||
const clients = this._model?.sharedModel.awareness.getStates() as Map< | ||
number, | ||
User.IIdentity | ||
>; | ||
|
||
const users: UsersItem.IUserData[] = []; | ||
if (clients) { | ||
clients.forEach((val, key) => { | ||
if (val.user) { | ||
users.push({ userId: key, userData: val.user as User.IIdentity }); | ||
} | ||
}); | ||
} | ||
this.setState(old => ({ ...old, usersList: users })); | ||
}; | ||
|
||
private _model: DocumentRegistry.IModel | null; | ||
private _iconRenderer: | ||
| ((props: UsersItem.IIconRendererProps) => JSX.Element) | ||
| null; | ||
} | ||
|
||
/** | ||
* Default renderer for the user icon. | ||
*/ | ||
export function DefaultIconRenderer( | ||
props: UsersItem.IIconRendererProps | ||
): JSX.Element { | ||
let el: JSX.Element; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const { user, model, ...htmlProps } = props; | ||
|
||
const iconClasses = classes('lm-MenuBar-itemIcon', props.className || ''); | ||
if (user.userData.avatar_url) { | ||
el = ( | ||
<div | ||
{...htmlProps} | ||
key={user.userId} | ||
title={user.userData.display_name} | ||
className={classes(iconClasses, 'jp-MenuBar-imageIcon')} | ||
> | ||
<img src={user.userData.avatar_url} alt="" /> | ||
</div> | ||
); | ||
} else { | ||
el = ( | ||
<div | ||
{...htmlProps} | ||
key={user.userId} | ||
title={user.userData.display_name} | ||
className={classes(iconClasses, 'jp-MenuBar-anonymousIcon')} | ||
style={{ backgroundColor: user.userData.color }} | ||
> | ||
<span>{user.userData.initials}</span> | ||
</div> | ||
); | ||
} | ||
|
||
return el; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* ----------------------------------------------------------------------------- | ||
| Copyright (c) Jupyter Development Team. | ||
| Distributed under the terms of the Modified BSD License. | ||
|---------------------------------------------------------------------------- */ | ||
|
||
.jp-toolbar-users-item { | ||
flex-grow: 1; | ||
display: flex; | ||
flex-direction: row; | ||
} | ||
|
||
.jp-toolbar-users-item .jp-MenuBar-anonymousIcon, | ||
.jp-toolbar-users-item .jp-MenuBar-imageIcon { | ||
position: relative; | ||
left: 0; | ||
height: 22px; | ||
width: 22px; | ||
box-sizing: border-box; | ||
cursor: default; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters