Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds annotations #172

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
71 changes: 71 additions & 0 deletions javascript/src/annotations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* -----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/

import type { JSONValue } from '@lumino/coreutils';
import type { ISignal } from '@lumino/signaling';

/**
* Generic annotation change
*/
export type AnnotationsChange<T> = {
oldValue?: T;
newValue?: T;
};

/**
* Annotation interface.
*/
export interface IAnnotation {
sender: string;
pos: JSONValue;
content: JSONValue;
}

/**
* Annotations interface.
* This interface must be implemented by the shared documents that want
* to include annotations.
*/
export interface IAnnotations<T extends IAnnotation> {
/**
* The annotation changed signal.
*/
readonly annotationChanged: ISignal<this, AnnotationsChange<T>>;

/**
* Return an iterator that yields every annotation key.
*/
readonly annotations: Array<string>;

/**
* Get the value for an annotation
*
* @param key Key to get
*/
getAnnotation(key: string): T | undefined;

/**
* Set the value of an annotation
*
* @param key Key to set
* @param value New value
*/
setAnnotation(key: string, value: T): void;

/**
* Update the value of an existing annotation
*
* @param key Key to update
* @param value New value
*/
updateAnnotation(key: string, value: T): void;

/**
* Delete an annotation
*
* @param key Key to delete
*/
deleteAnnotation(key: string): void;
}
18 changes: 11 additions & 7 deletions javascript/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import type {
import type { IObservableDisposable } from '@lumino/disposable';
import type { ISignal } from '@lumino/signaling';

import type { IAnnotation, IAnnotations } from './annotations.js';

/**
* Changes on Sequence-like data are expressed as Quill-inspired deltas.
*
Expand Down Expand Up @@ -95,6 +97,11 @@ export interface ISharedDocument extends ISharedBase {
*/
readonly state: JSONObject;

/**
* The changed signal.
*/
readonly changed: ISignal<this, DocumentChange>;

/**
* Get the value for a state attribute
*
Expand All @@ -109,11 +116,6 @@ export interface ISharedDocument extends ISharedBase {
* @param value New attribute value
*/
setState(key: string, value: JSONValue): void;

/**
* The changed signal.
*/
readonly changed: ISignal<this, DocumentChange>;
}

/**
Expand Down Expand Up @@ -399,8 +401,10 @@ export namespace SharedCell {
* Implements an API for nbformat.IBaseCell.
*/
export interface ISharedBaseCell<
Metadata extends nbformat.IBaseCellMetadata = nbformat.IBaseCellMetadata
> extends ISharedText {
Metadata extends nbformat.IBaseCellMetadata = nbformat.IBaseCellMetadata,
Annotation extends IAnnotation = IAnnotation
> extends ISharedText,
IAnnotations<Annotation> {
/**
* The type of the cell.
*/
Expand Down
2 changes: 2 additions & 0 deletions javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
export * from './api.js';
export * from './utils.js';

export * from './annotations.js';

export * from './ytext.js';
export * from './ydocument.js';
export * from './yfile.js';
Expand Down
91 changes: 88 additions & 3 deletions javascript/src/ycell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import type * as nbformat from '@jupyterlab/nbformat';
import { JSONExt, JSONObject, PartialJSONValue, UUID } from '@lumino/coreutils';
import { ISignal, Signal } from '@lumino/signaling';

import { Awareness } from 'y-protocols/awareness';
import * as Y from 'yjs';

import type {
CellChange,
IMapChange,
Expand All @@ -18,6 +20,8 @@ import type {
ISharedRawCell,
SharedCell
} from './api.js';
import type { AnnotationsChange, IAnnotation } from './annotations.js';

import { IYText } from './ytext.js';
import { YNotebook } from './ynotebook.js';

Expand Down Expand Up @@ -128,7 +132,7 @@ export const createStandaloneCell = (cell: SharedCell.Cell): YCellType =>
createCell(cell);

export class YBaseCell<Metadata extends nbformat.IBaseCellMetadata>
implements ISharedBaseCell<Metadata>, IYText
implements ISharedBaseCell<Metadata, IAnnotation>, IYText
{
/**
* Create a new YCell that works standalone. It cannot be
Expand Down Expand Up @@ -227,6 +231,21 @@ export class YBaseCell<Metadata extends nbformat.IBaseCellMetadata>
return this._isDisposed;
}

/**
* The annotation changed signal.
*/
get annotationChanged(): ISignal<this, AnnotationsChange<IAnnotation>> {
return this._annotationChanged;
}

/**
* Return an iterator that yields every annotation key.
*/
get annotations(): Array<string> {
const annotation = this._ymetadata.get('annotations');
return Object.keys(annotation);
}

/**
* Whether the cell is standalone or not.
*
Expand Down Expand Up @@ -370,6 +389,66 @@ export class YBaseCell<Metadata extends nbformat.IBaseCellMetadata>
Signal.clearData(this);
}

/**
* Get the value for an annotation
*
* @param key Key to get
*/
getAnnotation(key: string): IAnnotation | undefined {
const annotations = this._ymetadata.get('annotations');
if (annotations && key in annotations) {
return JSONExt.deepCopy(annotations[key]);
} else {
return undefined;
}
}

/**
* Set the value of an annotation
*
* @param key Key to set
* @param value New value
*/
setAnnotation(key: string, value: IAnnotation): void {
const clone = JSONExt.deepCopy(value as any);
const annotations = this._ymetadata.get('annotations') ?? {};
annotations[key] = clone;
this._ymetadata.set('annotations', annotations);
}

/**
* Update the value of an existing annotation
*
* @param key Key to update
* @param value New value
*/
updateAnnotation(key: string, value: Partial<IAnnotation>): void {
const annotations = this._ymetadata.get('annotations');
if (!annotations || !(key in annotations)) {
return;
}

const annotation = annotations[key];
const clone = JSONExt.deepCopy(value as any);
for (const [key, value] of Object.entries(clone)) {
annotation[key] = value;
}
this._ymetadata.set('annotations', { ...annotations, key: annotation });
}

/**
* Delete an annotation
*
* @param key Key to delete
*/
deleteAnnotation(key: string): void {
const annotations = this._ymetadata.get('annotations');
if (annotations && key in annotations) {
delete annotations[key];
this._ymetadata.set('annotations', annotations);
}
}

/**
* Get cell id.
*
Expand Down Expand Up @@ -661,18 +740,24 @@ export class YBaseCell<Metadata extends nbformat.IBaseCellMetadata>
};

protected _metadataChanged = new Signal<this, IMapChange>(this);

/**
* The notebook that this cell belongs to.
*/
protected _notebook: YNotebook | null = null;

private _awareness: Awareness | null;
private _changed = new Signal<this, CellChange>(this);
private _disposed = new Signal<this, void>(this);
private _isDisposed = false;
private _prevSourceLength: number;
private _undoManager: Y.UndoManager | null = null;
private _ymetadata: Y.Map<any>;
private _ysource: Y.Text;

private _disposed = new Signal<this, void>(this);
private _changed = new Signal<this, CellChange>(this);
private _annotationChanged = new Signal<this, AnnotationsChange<IAnnotation>>(
this
);
}

/**
Expand Down