UNPKG

8.57 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { Printing, showErrorMessage } from '@jupyterlab/apputils';
4import { IEditorMimeTypeService } from '@jupyterlab/codeeditor';
5import { ActivityMonitor } from '@jupyterlab/coreutils';
6import { MimeModel } from '@jupyterlab/rendermime';
7import { nullTranslator } from '@jupyterlab/translation';
8import { JSONExt, PromiseDelegate } from '@lumino/coreutils';
9import { MessageLoop } from '@lumino/messaging';
10import { StackedLayout, Widget } from '@lumino/widgets';
11import { ABCWidgetFactory, DocumentWidget } from './default';
12/**
13 * A content widget for a rendered mimetype document.
14 */
15export class MimeContent extends Widget {
16 /**
17 * Construct a new widget.
18 */
19 constructor(options) {
20 super();
21 /**
22 * A bound change callback.
23 */
24 this._changeCallback = (options) => {
25 if (!options.data || !options.data[this.mimeType]) {
26 return;
27 }
28 const data = options.data[this.mimeType];
29 if (typeof data === 'string') {
30 if (data !== this._context.model.toString()) {
31 this._context.model.fromString(data);
32 }
33 }
34 else if (data !== null &&
35 data !== undefined &&
36 !JSONExt.deepEqual(data, this._context.model.toJSON())) {
37 this._context.model.fromJSON(data);
38 }
39 };
40 this._fragment = '';
41 this._ready = new PromiseDelegate();
42 this._isRendering = false;
43 this._renderRequested = false;
44 this.addClass('jp-MimeDocument');
45 this.translator = options.translator || nullTranslator;
46 this._trans = this.translator.load('jupyterlab');
47 this.mimeType = options.mimeType;
48 this._dataType = options.dataType || 'string';
49 this._context = options.context;
50 this.renderer = options.renderer;
51 const layout = (this.layout = new StackedLayout());
52 layout.addWidget(this.renderer);
53 this._context.ready
54 .then(() => {
55 return this._render();
56 })
57 .then(() => {
58 // After rendering for the first time, send an activation request if we
59 // are currently focused.
60 if (this.node === document.activeElement) {
61 // We want to synchronously send (not post) the activate message, while
62 // we know this node still has focus.
63 MessageLoop.sendMessage(this.renderer, Widget.Msg.ActivateRequest);
64 }
65 // Throttle the rendering rate of the widget.
66 this._monitor = new ActivityMonitor({
67 signal: this._context.model.contentChanged,
68 timeout: options.renderTimeout
69 });
70 this._monitor.activityStopped.connect(this.update, this);
71 this._ready.resolve(undefined);
72 })
73 .catch(reason => {
74 // Dispose the document if rendering fails.
75 requestAnimationFrame(() => {
76 this.dispose();
77 });
78 void showErrorMessage(this._trans.__('Renderer Failure: %1', this._context.path), reason);
79 });
80 }
81 /**
82 * Print method. Deferred to the renderer.
83 */
84 [Printing.symbol]() {
85 return Printing.getPrintFunction(this.renderer);
86 }
87 /**
88 * A promise that resolves when the widget is ready.
89 */
90 get ready() {
91 return this._ready.promise;
92 }
93 /**
94 * Set URI fragment identifier.
95 */
96 setFragment(fragment) {
97 this._fragment = fragment;
98 this.update();
99 }
100 /**
101 * Dispose of the resources held by the widget.
102 */
103 dispose() {
104 if (this.isDisposed) {
105 return;
106 }
107 if (this._monitor) {
108 this._monitor.dispose();
109 }
110 this._monitor = null;
111 super.dispose();
112 }
113 /**
114 * Handle an `update-request` message to the widget.
115 */
116 onUpdateRequest(msg) {
117 if (this._context.isReady) {
118 void this._render();
119 this._fragment = '';
120 }
121 }
122 /**
123 * Render the mime content.
124 */
125 async _render() {
126 if (this.isDisposed) {
127 return;
128 }
129 // Since rendering is async, we note render requests that happen while we
130 // actually are rendering for a future rendering.
131 if (this._isRendering) {
132 this._renderRequested = true;
133 return;
134 }
135 // Set up for this rendering pass.
136 this._renderRequested = false;
137 const context = this._context;
138 const model = context.model;
139 const data = {};
140 if (this._dataType === 'string') {
141 data[this.mimeType] = model.toString();
142 }
143 else {
144 data[this.mimeType] = model.toJSON();
145 }
146 const mimeModel = new MimeModel({
147 data,
148 callback: this._changeCallback,
149 metadata: { fragment: this._fragment }
150 });
151 try {
152 // Do the rendering asynchronously.
153 this._isRendering = true;
154 await this.renderer.renderModel(mimeModel);
155 this._isRendering = false;
156 // If there is an outstanding request to render, go ahead and render
157 if (this._renderRequested) {
158 return this._render();
159 }
160 }
161 catch (reason) {
162 // Dispose the document if rendering fails.
163 requestAnimationFrame(() => {
164 this.dispose();
165 });
166 void showErrorMessage(this._trans.__('Renderer Failure: %1', context.path), reason);
167 }
168 }
169}
170/**
171 * A document widget for mime content.
172 */
173export class MimeDocument extends DocumentWidget {
174 setFragment(fragment) {
175 this.content.setFragment(fragment);
176 }
177}
178/**
179 * An implementation of a widget factory for a rendered mimetype document.
180 */
181export class MimeDocumentFactory extends ABCWidgetFactory {
182 /**
183 * Construct a new mimetype widget factory.
184 */
185 constructor(options) {
186 super(Private.createRegistryOptions(options));
187 this._rendermime = options.rendermime;
188 this._renderTimeout = options.renderTimeout || 1000;
189 this._dataType = options.dataType || 'string';
190 this._fileType = options.primaryFileType;
191 this._factory = options.factory;
192 }
193 /**
194 * Create a new widget given a context.
195 */
196 createNewWidget(context) {
197 var _a, _b;
198 const ft = this._fileType;
199 const mimeType = (ft === null || ft === void 0 ? void 0 : ft.mimeTypes.length)
200 ? ft.mimeTypes[0]
201 : IEditorMimeTypeService.defaultMimeType;
202 const rendermime = this._rendermime.clone({
203 resolver: context.urlResolver
204 });
205 let renderer;
206 if (this._factory && this._factory.mimeTypes.includes(mimeType)) {
207 renderer = this._factory.createRenderer({
208 mimeType,
209 resolver: rendermime.resolver,
210 sanitizer: rendermime.sanitizer,
211 linkHandler: rendermime.linkHandler,
212 latexTypesetter: rendermime.latexTypesetter,
213 markdownParser: rendermime.markdownParser
214 });
215 }
216 else {
217 renderer = rendermime.createRenderer(mimeType);
218 }
219 const content = new MimeContent({
220 context,
221 renderer,
222 mimeType,
223 renderTimeout: this._renderTimeout,
224 dataType: this._dataType
225 });
226 content.title.icon = ft === null || ft === void 0 ? void 0 : ft.icon;
227 content.title.iconClass = (_a = ft === null || ft === void 0 ? void 0 : ft.iconClass) !== null && _a !== void 0 ? _a : '';
228 content.title.iconLabel = (_b = ft === null || ft === void 0 ? void 0 : ft.iconLabel) !== null && _b !== void 0 ? _b : '';
229 const widget = new MimeDocument({ content, context });
230 return widget;
231 }
232}
233/**
234 * The namespace for the module implementation details.
235 */
236var Private;
237(function (Private) {
238 /**
239 * Create the document registry options.
240 */
241 function createRegistryOptions(options) {
242 return {
243 ...options,
244 readOnly: true
245 };
246 }
247 Private.createRegistryOptions = createRegistryOptions;
248})(Private || (Private = {}));
249//# sourceMappingURL=mimedocument.js.map
\No newline at end of file