@jupyterlab/docmanager
Version:
JupyterLab - Document Manager
232 lines • 6.81 kB
JavaScript
/*
* Copyright (c) Jupyter Development Team.
* Distributed under the terms of the Modified BSD License.
*/
import { PageConfig } from '@jupyterlab/coreutils';
import { Debouncer } from '@lumino/polling';
import { Signal } from '@lumino/signaling';
/**
* Manager for recently opened and closed documents.
*/
export class RecentsManager {
constructor(options) {
this._recentsChanged = new Signal(this);
this._recents = {
opened: [],
closed: []
};
this._isDisposed = false;
this._maxRecentsLength = 10;
this._saveDebouncer = new Debouncer(this._save.bind(this), 500);
this._stateDB = options.stateDB;
this._contentsManager = options.contents;
this.updateRootDir();
this._loadRecents().catch(r => {
console.error(`Failed to load recent list from state:\n${r}`);
});
}
/**
* Whether the manager is disposed or not.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* List of recently opened items
*/
get recentlyOpened() {
const recents = this._recents.opened || [];
return recents.filter(r => r.root === this._serverRoot);
}
/**
* List of recently opened items
*/
get recentlyClosed() {
const recents = this._recents.closed || [];
return recents.filter(r => r.root === this._serverRoot);
}
/**
* Signal emitted when the recent list changes.
*/
get changed() {
return this._recentsChanged;
}
/**
* Maximal number of recent items to list.
*/
get maximalRecentsLength() {
return this._maxRecentsLength;
}
set maximalRecentsLength(value) {
this._maxRecentsLength = Math.round(Math.max(1, value));
let changed = false;
for (const type of ['opened', 'closed']) {
if (this._recents[type].length > this._maxRecentsLength) {
this._recents[type].length = this._maxRecentsLength;
changed = true;
}
}
if (changed) {
this._recentsChanged.emit(undefined);
}
}
/**
* Dispose recent manager resources
*/
dispose() {
if (this.isDisposed) {
return;
}
this._isDisposed = true;
Signal.clearData(this);
this._saveDebouncer.dispose();
}
/**
* Add a new path to the recent list.
*/
addRecent(document, event) {
const recent = {
...document,
root: this._serverRoot
};
const recents = this._recents[event];
// Check if it's already present; if so remove it
const existingIndex = recents.findIndex(r => r.path === document.path);
if (existingIndex >= 0) {
recents.splice(existingIndex, 1);
}
// Add to the front of the list
recents.unshift(recent);
this._setRecents(recents, event);
this._recentsChanged.emit(undefined);
}
/**
* Clear the recents list
*/
clearRecents() {
this._setRecents([], 'opened');
this._setRecents([], 'closed');
this._recentsChanged.emit(undefined);
}
/**
* Remove the document from recents list.
*/
removeRecent(document, event) {
this._removeRecent(document.path, [event]);
}
/**
* Check if the recent item is valid, remove if it from both lists if it is not.
*/
async validate(recent) {
const valid = await this._isValid(recent);
if (!valid) {
this._removeRecent(recent.path);
}
return valid;
}
/**
* Set server root dir.
*
* Note: protected to allow unit-testing.
*/
updateRootDir() {
this._serverRoot = PageConfig.getOption('serverRoot');
}
/**
* Remove a path from both lists (opened and closed).
*/
_removeRecent(path, lists = ['opened', 'closed']) {
let changed = false;
for (const type of lists) {
const recents = this._recents[type];
const newRecents = recents.filter(r => path !== r.path);
if (recents.length !== newRecents.length) {
this._setRecents(newRecents, type);
changed = true;
}
}
if (changed) {
this._recentsChanged.emit(undefined);
}
}
/**
* Check if the path of a given recent document exists.
*/
async _isValid(recent) {
var _a;
try {
await this._contentsManager.get(recent.path, { content: false });
}
catch (e) {
if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
return false;
}
}
return true;
}
/**
* Set the recent list
* @param recents The new recent list
*/
_setRecents(recents, type) {
this._recents[type] = recents
.slice(0, this.maximalRecentsLength)
.sort((a, b) => {
if (a.root === b.root) {
return 0;
}
else {
return a.root !== this._serverRoot ? 1 : -1;
}
});
this._saveDebouncer.invoke().catch(console.warn);
}
/**
* Load the recent items from the state.
*/
async _loadRecents() {
const recents = (await this._stateDB.fetch(Private.stateDBKey)) || {
opened: [],
closed: []
};
const allRecents = [...recents.opened, ...recents.closed];
const invalidPaths = new Set(await this._getInvalidPaths(allRecents));
for (const type of ['opened', 'closed']) {
this._setRecents(recents[type].filter(r => !invalidPaths.has(r.path)), type);
}
this._recentsChanged.emit(undefined);
}
/**
* Get the list of invalid path in recents.
*/
async _getInvalidPaths(recents) {
const invalidPathsOrNulls = await Promise.all(recents.map(async (r) => {
if (await this._isValid(r)) {
return null;
}
else {
return r.path;
}
}));
return invalidPathsOrNulls.filter(x => typeof x === 'string');
}
/**
* Save the recent items to the state.
*/
async _save() {
try {
await this._stateDB.save(Private.stateDBKey, this._recents);
}
catch (e) {
console.log('Saving recents failed', e);
}
}
}
var Private;
(function (Private) {
/**
* Key reserved in the state database.
*/
Private.stateDBKey = 'docmanager:recents';
})(Private || (Private = {}));
//# sourceMappingURL=recents.js.map