@jupyterlab/docregistry
Version:
JupyterLab - Document Registry
486 lines • 13.6 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { MainAreaWidget, setToolbar } from '@jupyterlab/apputils';
import { CodeEditor } from '@jupyterlab/codeeditor';
import { Mode } from '@jupyterlab/codemirror';
import { PathExt } from '@jupyterlab/coreutils';
import * as models from '@jupyter/ydoc';
import { nullTranslator } from '@jupyterlab/translation';
import { Signal } from '@lumino/signaling';
/**
* The default implementation of a document model.
*/
export class DocumentModel extends CodeEditor.Model {
/**
* Construct a new document model.
*/
constructor(languagePreference, modelDB, collaborationEnabled) {
super({ modelDB });
this._defaultLang = '';
this._dirty = false;
this._readOnly = false;
this._contentChanged = new Signal(this);
this._stateChanged = new Signal(this);
this._defaultLang = languagePreference || '';
const filemodel = new models.YFile();
this.switchSharedModel(filemodel, true);
this.value.changed.connect(this.triggerContentChange, this);
this.sharedModel.changed.connect(this._onStateChanged, this);
this._collaborationEnabled = !!collaborationEnabled;
}
/**
* A signal emitted when the document content changes.
*/
get contentChanged() {
return this._contentChanged;
}
/**
* A signal emitted when the document state changes.
*/
get stateChanged() {
return this._stateChanged;
}
/**
* The dirty state of the document.
*/
get dirty() {
return this._dirty;
}
set dirty(newValue) {
const oldValue = this._dirty;
if (newValue === oldValue) {
return;
}
this._dirty = newValue;
this.triggerStateChange({
name: 'dirty',
oldValue,
newValue
});
}
/**
* The read only state of the document.
*/
get readOnly() {
return this._readOnly;
}
set readOnly(newValue) {
if (newValue === this._readOnly) {
return;
}
const oldValue = this._readOnly;
this._readOnly = newValue;
this.triggerStateChange({ name: 'readOnly', oldValue, newValue });
}
/**
* The default kernel name of the document.
*
* #### Notes
* This is a read-only property.
*/
get defaultKernelName() {
return '';
}
/**
* The default kernel language of the document.
*
* #### Notes
* This is a read-only property.
*/
get defaultKernelLanguage() {
return this._defaultLang;
}
/**
* Whether the model is collaborative or not.
*/
get collaborative() {
return this._collaborationEnabled;
}
/**
* Serialize the model to a string.
*/
toString() {
return this.value.text;
}
/**
* Deserialize the model from a string.
*
* #### Notes
* Should emit a [contentChanged] signal.
*/
fromString(value) {
this.value.text = value;
}
/**
* Serialize the model to JSON.
*/
toJSON() {
return JSON.parse(this.value.text || 'null');
}
/**
* Deserialize the model from JSON.
*
* #### Notes
* Should emit a [contentChanged] signal.
*/
fromJSON(value) {
this.fromString(JSON.stringify(value));
}
/**
* Initialize the model with its current state.
*/
initialize() {
return;
}
/**
* Trigger a state change signal.
*/
triggerStateChange(args) {
this._stateChanged.emit(args);
}
/**
* Trigger a content changed signal.
*/
triggerContentChange() {
this._contentChanged.emit(void 0);
this.dirty = true;
}
_onStateChanged(sender, changes) {
if (changes.sourceChange) {
this.triggerContentChange();
}
if (changes.stateChange) {
changes.stateChange.forEach(value => {
if (value.name === 'dirty') {
// Setting `dirty` will trigger the state change.
// We always set `dirty` because the shared model state
// and the local attribute are synchronized one way shared model -> _dirty
this.dirty = value.newValue;
}
else if (value.oldValue !== value.newValue) {
this.triggerStateChange(Object.assign({ newValue: undefined, oldValue: undefined }, value));
}
});
}
}
}
/**
* An implementation of a model factory for text files.
*/
export class TextModelFactory {
/**
* Instantiates a TextModelFactory.
*/
constructor(collaborative) {
this._isDisposed = false;
this._collaborative = collaborative !== null && collaborative !== void 0 ? collaborative : true;
}
/**
* The name of the model type.
*
* #### Notes
* This is a read-only property.
*/
get name() {
return 'text';
}
/**
* The type of the file.
*
* #### Notes
* This is a read-only property.
*/
get contentType() {
return 'file';
}
/**
* The format of the file.
*
* This is a read-only property.
*/
get fileFormat() {
return 'text';
}
/**
* Whether the model is collaborative or not.
*/
get collaborative() {
return this._collaborative;
}
/**
* Get whether the model factory has been disposed.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* Dispose of the resources held by the model factory.
*/
dispose() {
this._isDisposed = true;
}
/**
* Create a new model.
*
* @param languagePreference - An optional kernel language preference.
* @param modelDB - An optional model storage.
* @param isInitialized - Whether the model is initialized or not.
* @param collaborationEnabled - Whether collaboration is enabled at the application level or not (default `false`).
*
* @returns A new document model.
*/
createNew(languagePreference, modelDB, isInitialized, collaborationEnabled) {
const collaborative = collaborationEnabled && this.collaborative;
return new DocumentModel(languagePreference, modelDB, collaborative);
}
/**
* Get the preferred kernel language given a file path.
*/
preferredLanguage(path) {
const mode = Mode.findByFileName(path);
return mode && mode.mode;
}
}
/**
* An implementation of a model factory for base64 files.
*/
export class Base64ModelFactory extends TextModelFactory {
/**
* The name of the model type.
*
* #### Notes
* This is a read-only property.
*/
get name() {
return 'base64';
}
/**
* The type of the file.
*
* #### Notes
* This is a read-only property.
*/
get contentType() {
return 'file';
}
/**
* The format of the file.
*
* This is a read-only property.
*/
get fileFormat() {
return 'base64';
}
}
/**
* The default implementation of a widget factory.
*/
export class ABCWidgetFactory {
/**
* Construct a new `ABCWidgetFactory`.
*/
constructor(options) {
this._isDisposed = false;
this._widgetCreated = new Signal(this);
this._translator = options.translator || nullTranslator;
this._name = options.name;
this._readOnly = options.readOnly === undefined ? false : options.readOnly;
this._defaultFor = options.defaultFor ? options.defaultFor.slice() : [];
this._defaultRendered = (options.defaultRendered || []).slice();
this._fileTypes = options.fileTypes.slice();
this._modelName = options.modelName || 'text';
this._preferKernel = !!options.preferKernel;
this._canStartKernel = !!options.canStartKernel;
this._shutdownOnClose = !!options.shutdownOnClose;
this._toolbarFactory = options.toolbarFactory;
}
/**
* A signal emitted when a widget is created.
*/
get widgetCreated() {
return this._widgetCreated;
}
/**
* Get whether the model factory has been disposed.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* Dispose of the resources used by the document manager.
*/
dispose() {
if (this.isDisposed) {
return;
}
this._isDisposed = true;
Signal.clearData(this);
}
/**
* Whether the widget factory is read only.
*/
get readOnly() {
return this._readOnly;
}
/**
* The name of the widget to display in dialogs.
*/
get name() {
return this._name;
}
/**
* The file types the widget can view.
*/
get fileTypes() {
return this._fileTypes.slice();
}
/**
* The registered name of the model type used to create the widgets.
*/
get modelName() {
return this._modelName;
}
/**
* The file types for which the factory should be the default.
*/
get defaultFor() {
return this._defaultFor.slice();
}
/**
* The file types for which the factory should be the default for
* rendering a document model, if different from editing.
*/
get defaultRendered() {
return this._defaultRendered.slice();
}
/**
* Whether the widgets prefer having a kernel started.
*/
get preferKernel() {
return this._preferKernel;
}
/**
* Whether the widgets can start a kernel when opened.
*/
get canStartKernel() {
return this._canStartKernel;
}
/**
* The application language translator.
*/
get translator() {
return this._translator;
}
/**
* Whether the kernel should be shutdown when the widget is closed.
*/
get shutdownOnClose() {
return this._shutdownOnClose;
}
set shutdownOnClose(value) {
this._shutdownOnClose = value;
}
/**
* Create a new widget given a document model and a context.
*
* #### Notes
* It should emit the [widgetCreated] signal with the new widget.
*/
createNew(context, source) {
var _a;
// Create the new widget
const widget = this.createNewWidget(context, source);
// Add toolbar
setToolbar(widget, (_a = this._toolbarFactory) !== null && _a !== void 0 ? _a : this.defaultToolbarFactory.bind(this));
// Emit widget created signal
this._widgetCreated.emit(widget);
return widget;
}
/**
* Default factory for toolbar items to be added after the widget is created.
*/
defaultToolbarFactory(widget) {
return [];
}
}
/**
* The class name added to a dirty widget.
*/
const DIRTY_CLASS = 'jp-mod-dirty';
/**
* A document widget implementation.
*/
export class DocumentWidget extends MainAreaWidget {
constructor(options) {
// Include the context ready promise in the widget reveal promise
options.reveal = Promise.all([options.reveal, options.context.ready]);
super(options);
this.context = options.context;
// Handle context path changes
this.context.pathChanged.connect(this._onPathChanged, this);
this._onPathChanged(this.context, this.context.path);
// Listen for changes in the dirty state.
this.context.model.stateChanged.connect(this._onModelStateChanged, this);
void this.context.ready.then(() => {
this._handleDirtyState();
});
// listen for changes to the title object
this.title.changed.connect(this._onTitleChanged, this);
}
/**
* Set URI fragment identifier.
*/
setFragment(fragment) {
/* no-op */
}
/**
* Handle a title change.
*/
async _onTitleChanged(_sender) {
const validNameExp = /[\/\\:]/;
const name = this.title.label;
const filename = this.context.path.split('/').pop();
if (name === filename) {
return;
}
if (name.length > 0 && !validNameExp.test(name)) {
const oldPath = this.context.path;
await this.context.rename(name);
if (this.context.path !== oldPath) {
// Rename succeeded
return;
}
}
// Reset title if name is invalid or rename fails
this.title.label = filename;
}
/**
* Handle a path change.
*/
_onPathChanged(sender, path) {
this.title.label = PathExt.basename(sender.localPath);
// The document is not untitled any more.
this.isUntitled = false;
}
/**
* Handle a change to the context model state.
*/
_onModelStateChanged(sender, args) {
if (args.name === 'dirty') {
this._handleDirtyState();
}
}
/**
* Handle the dirty state of the context model.
*/
_handleDirtyState() {
if (this.context.model.dirty &&
!this.title.className.includes(DIRTY_CLASS)) {
this.title.className += ` ${DIRTY_CLASS}`;
}
else {
this.title.className = this.title.className.replace(DIRTY_CLASS, '');
}
}
}
//# sourceMappingURL=default.js.map