UNPKG

@jupyterlab/docregistry

Version:
486 lines 13.6 kB
// 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