@jupyterlab/docregistry
Version:
JupyterLab - Document Registry
519 lines • 14.5 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 { PathExt } from '@jupyterlab/coreutils';
import { nullTranslator } from '@jupyterlab/translation';
import { Signal } from '@lumino/signaling';
import { createReadonlyLabel } from './components';
/**
* The default implementation of a document model.
*/
export class DocumentModel extends CodeEditor.Model {
/**
* Construct a new document model.
*/
constructor(options = {}) {
var _a;
super({ sharedModel: options.sharedModel });
this._defaultLang = '';
this._dirty = false;
this._readOnly = false;
this._contentChanged = new Signal(this);
this._stateChanged = new Signal(this);
this._defaultLang = (_a = options.languagePreference) !== null && _a !== void 0 ? _a : '';
this._collaborationEnabled = !!options.collaborationEnabled;
this.sharedModel.changed.connect(this._onStateChanged, this);
}
/**
* 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.sharedModel.getSource();
}
/**
* Deserialize the model from a string.
*
* #### Notes
* Should emit a [contentChanged] signal.
*/
fromString(value) {
this.sharedModel.setSource(value);
}
/**
* Serialize the model to JSON.
*/
toJSON() {
return JSON.parse(this.sharedModel.getSource() || '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({
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 options - Model options.
*
* @returns A new document model.
*/
createNew(options = {}) {
const collaborative = options.collaborationEnabled && this.collaborative;
return new DocumentModel({
...options,
collaborationEnabled: collaborative
});
}
/**
* Get the preferred kernel language given a file path.
*/
preferredLanguage(path) {
return '';
}
}
/**
* 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._label = options.label || 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._autoStartDefault = !!options.autoStartDefault;
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;
}
/**
* A unique name identifying of the widget.
*/
get name() {
return this._name;
}
/**
* The label of the widget to display in dialogs.
* If not given, name is used instead.
*/
get label() {
return this._label;
}
/**
* 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;
}
/**
* Whether to automatically select the preferred kernel during a kernel start
*/
get autoStartDefault() {
return this._autoStartDefault;
}
set autoStartDefault(value) {
this._autoStartDefault = 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) {
var _a;
// Include the context ready promise in the widget reveal promise
options.reveal = Promise.all([options.reveal, options.context.ready]);
super(options);
this._trans = ((_a = options.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
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;
// Use localPath to avoid the drive name
const filename = this.context.localPath.split('/').pop() || this.context.localPath;
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) {
var _a;
if (args.name === 'dirty') {
this._handleDirtyState();
}
if (!this.context.model.dirty) {
if (!this.context.model.collaborative) {
if (!((_a = this.context.contentsModel) === null || _a === void 0 ? void 0 : _a.writable)) {
const readOnlyIndicator = createReadonlyLabel(this);
let roi = this.toolbar.insertBefore('kernelName', 'read-only-indicator', readOnlyIndicator);
if (!roi) {
this.toolbar.addItem('read-only-indicator', readOnlyIndicator);
}
}
}
}
}
/**
* 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