@jupyterlab/notebook
Version:
JupyterLab - Notebook
281 lines • 11 kB
JavaScript
/*
* Copyright (c) Jupyter Development Team.
* Distributed under the terms of the Modified BSD License.
*/
import { DOMUtils } from '@jupyterlab/apputils';
import { showPopup, TextItem } from '@jupyterlab/statusbar';
import { nullTranslator } from '@jupyterlab/translation';
import { classes, lineFormIcon, ReactWidget, VDomModel, VDomRenderer } from '@jupyterlab/ui-components';
import React from 'react';
/**
* A component for rendering a "go-to-cell" form.
*/
class CellNumberFormComponent extends React.Component {
/**
* Construct a new CellNumberFormComponent.
*/
constructor(props) {
super(props);
/**
* Handle a change to the value in the input field.
*/
this._handleChange = (event) => {
this.setState({ value: event.currentTarget.value });
};
/**
* Handle submission of the input field.
*/
this._handleSubmit = (event) => {
var _a, _b;
event.preventDefault();
const value = parseInt((_b = (_a = this._textInput) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', 10);
if (!isNaN(value) &&
isFinite(value) &&
1 <= value &&
value <= this.props.maxCell) {
this.props.handleSubmit(value);
}
return false;
};
/**
* Handle focusing of the input field.
*/
this._handleFocus = () => {
this.setState({ hasFocus: true });
};
/**
* Handle blurring of the input field.
*/
this._handleBlur = () => {
this.setState({ hasFocus: false });
};
this._textInput = null;
const translator = props.translator || nullTranslator;
this._trans = translator.load('jupyterlab');
this.state = {
value: '',
hasFocus: false,
textInputId: DOMUtils.createDomID() + '-cell-number-input'
};
}
/**
* Focus the element on mount.
*/
componentDidMount() {
var _a;
(_a = this._textInput) === null || _a === void 0 ? void 0 : _a.focus();
}
/**
* Render the CellNumberFormComponent.
*/
render() {
return (React.createElement("div", { className: "jp-lineFormSearch" },
React.createElement("form", { name: "cellNumberForm", onSubmit: this._handleSubmit, noValidate: true },
React.createElement("div", { className: classes('jp-lineFormWrapper', 'lm-lineForm-wrapper', this.state.hasFocus ? 'jp-lineFormWrapperFocusWithin' : undefined) },
React.createElement("input", { type: "number", id: this.state.textInputId, className: "jp-lineFormInput", min: 1, max: this.props.maxCell, onChange: this._handleChange, onFocus: this._handleFocus, onBlur: this._handleBlur, value: this.state.value, ref: input => {
this._textInput = input;
} }),
React.createElement("div", { className: "jp-baseLineForm jp-lineFormButtonContainer" },
React.createElement(lineFormIcon.react, { className: "jp-baseLineForm jp-lineFormButtonIcon", elementPosition: "center" }),
React.createElement("input", { type: "submit", className: "jp-baseLineForm jp-lineFormButton", value: "" }))),
React.createElement("label", { className: "jp-lineFormCaption", htmlFor: this.state.textInputId }, this._trans.__('Go to cell number between 1 and %1', this.props.maxCell)))));
}
}
/**
* A pure functional component for rendering a notebook cell counter.
*/
function CellCounterComponent(props) {
const translator = props.translator || nullTranslator;
const trans = translator.load('jupyterlab');
const source = props.selectionStart > 0 && props.selectionStart !== props.selectionEnd
? trans.__('%1:%2/%3', props.selectionStart, props.selectionEnd, props.totalCells)
: trans.__('Cell %1/%2', props.activeCell, props.totalCells);
const keydownHandler = (event) => {
if (event.key === 'Enter' ||
event.key === 'Spacebar' ||
event.key === ' ') {
event.preventDefault();
event.stopPropagation();
props.handleClick();
}
};
return (React.createElement(TextItem, { role: "button", "aria-haspopup": true, onClick: props.handleClick, source: source, title: trans.__('Go to cell…'), tabIndex: 0, onKeyDown: keydownHandler }));
}
/**
* A widget implementing a notebook cell counter status item.
*/
export class CellCounterStatus extends VDomRenderer {
/**
* Construct a new CellCounterStatus status item.
*/
constructor(options = {}) {
super(new CellCounterStatus.Model());
this._popup = null;
this.addClass('jp-mod-highlighted');
this._translator = options.translator || nullTranslator;
}
/**
* Render the status item.
*/
render() {
if (this.model === null) {
return null;
}
return (React.createElement(CellCounterComponent, { activeCell: this.model.activeCell, selectionStart: this.model.selectionStart, selectionEnd: this.model.selectionEnd, totalCells: this.model.totalCells, translator: this._translator, handleClick: () => this._handleClick() }));
}
/**
* A click handler for the widget.
*/
_handleClick() {
if (this.model.totalCells < 1) {
return;
}
if (this._popup) {
this._popup.dispose();
}
const body = ReactWidget.create(React.createElement(CellNumberFormComponent, { handleSubmit: value => this._handleSubmit(value), maxCell: this.model.totalCells, translator: this._translator }));
this._popup = showPopup({
body,
anchor: this,
align: 'right'
});
}
/**
* Handle submission for the widget.
*/
_handleSubmit(value) {
var _a;
const notebook = this.model.notebook;
if (!notebook) {
return;
}
const cellIndex = value - 1;
notebook.activeCellIndex = cellIndex;
notebook.deselectAll();
void notebook.scrollToItem(cellIndex).catch(reason => {
console.error('Go to cell', reason);
});
(_a = this._popup) === null || _a === void 0 ? void 0 : _a.dispose();
notebook.activate();
}
}
/**
* A namespace for CellCounterStatus statics.
*/
(function (CellCounterStatus) {
/**
* A VDom model for a status item tracking active and total notebook cells.
*/
class Model extends VDomModel {
constructor() {
super(...arguments);
this._activeCell = 0;
this._selectionStart = 0;
this._selectionEnd = 0;
this._totalCells = 0;
this._notebook = null;
}
/**
* The notebook tracked by this model.
*/
get notebook() {
return this._notebook;
}
set notebook(notebook) {
const oldNotebook = this._notebook;
if (oldNotebook) {
oldNotebook.activeCellChanged.disconnect(this._onChanged, this);
oldNotebook.modelContentChanged.disconnect(this._onChanged, this);
oldNotebook.selectionChanged.disconnect(this._onChanged, this);
}
const oldState = this._getAllState();
this._notebook = notebook;
if (!this._notebook) {
this._activeCell = 0;
this._selectionStart = 0;
this._selectionEnd = 0;
this._totalCells = 0;
}
else {
this._notebook.activeCellChanged.connect(this._onChanged, this);
this._notebook.modelContentChanged.connect(this._onChanged, this);
this._notebook.selectionChanged.connect(this._onChanged, this);
this._updateStateFromNotebook(this._notebook);
}
this._triggerChange(oldState, this._getAllState());
}
/**
* The current active cell index shown to users (1-based).
*/
get activeCell() {
return this._activeCell;
}
/**
* The first selected cell index shown to users (1-based).
*/
get selectionStart() {
return this._selectionStart;
}
/**
* The last selected cell index shown to users (1-based).
*/
get selectionEnd() {
return this._selectionEnd;
}
/**
* The total number of cells.
*/
get totalCells() {
return this._totalCells;
}
/**
* React to notebook changes by refreshing the tracked state.
*/
_onChanged(notebook) {
const oldState = this._getAllState();
this._updateStateFromNotebook(notebook);
this._triggerChange(oldState, this._getAllState());
}
_updateStateFromNotebook(notebook) {
const activeCellIndex = notebook.activeCellIndex;
this._activeCell = activeCellIndex >= 0 ? activeCellIndex + 1 : 0;
this._totalCells = notebook.widgets.length;
let selectionStart = this._activeCell;
let selectionEnd = this._activeCell;
let seenSelection = false;
notebook.widgets.forEach((cell, index) => {
if (!notebook.isSelectedOrActive(cell)) {
return;
}
const oneBasedIndex = index + 1;
if (!seenSelection) {
selectionStart = oneBasedIndex;
selectionEnd = oneBasedIndex;
seenSelection = true;
return;
}
selectionEnd = oneBasedIndex;
});
this._selectionStart = seenSelection ? selectionStart : 0;
this._selectionEnd = seenSelection ? selectionEnd : 0;
}
_getAllState() {
return {
activeCell: this._activeCell,
selectionStart: this._selectionStart,
selectionEnd: this._selectionEnd,
totalCells: this._totalCells
};
}
_triggerChange(oldState, newState) {
if (oldState.activeCell !== newState.activeCell ||
oldState.selectionStart !== newState.selectionStart ||
oldState.selectionEnd !== newState.selectionEnd ||
oldState.totalCells !== newState.totalCells) {
this.stateChanged.emit(void 0);
}
}
}
CellCounterStatus.Model = Model;
})(CellCounterStatus || (CellCounterStatus = {}));
//# sourceMappingURL=cellcounterstatus.js.map