Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Improve context handling#4453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ian-r-rose merged 66 commits intojupyterlab:masterfromjasongrout:context
May 25, 2018
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
66 commits
Select commitHold shift + click to select a range
7a5bd00
WIP context cleanups
jasongroutApr 23, 2018
78df852
Handle paths having multiple contexts.
jasongroutApr 24, 2018
db40067
Some new docs about how JupyterLab deals with documents.
jasongroutApr 25, 2018
1e298d7
WIP
jasongroutApr 26, 2018
2e93f83
WIP
jasongroutApr 27, 2018
9c7c4a8
WIP
jasongroutApr 28, 2018
92d58d0
Revert back to original widget creation workflow, but insist we get b…
jasongroutMay 7, 2018
3816766
WIP for MimeDocument widget
jasongroutMay 9, 2018
6ede2da
WIP trying out an idea for a mime document renderer, pushing most log…
jasongroutMay 10, 2018
0ff19f6
WIP converting code to use IDocumentWidget
jasongroutMay 10, 2018
4aa4de6
Fix all compilation errors dealing with the move to IDocumentWidget.
jasongroutMay 10, 2018
58a7a4b
Fix lint errors
jasongroutMay 11, 2018
3fc6bde
WIP: Fix some tests, skip others while we are evaluating this approach.
jasongroutMay 11, 2018
1107f69
Create a specialized MimeDocument document widget type.
jasongroutMay 12, 2018
12dfc8e
Delay a document widget from being ready until the context is ready.
jasongroutMay 12, 2018
af12ced
Use the ABCWidgetFactory type parameter defaults where we can to simp…
jasongroutMay 12, 2018
55f8b32
Convert the notebook to use DocumentWidget
jasongroutMay 12, 2018
631d0e1
Move notebook content generation to the widget factory.
jasongroutMay 13, 2018
8b3b7e1
Remove image viewer toolbar-lookalike css.
jasongroutMay 13, 2018
db7fc2d
Move dirty state handling to the document widget.
jasongroutMay 13, 2018
c90b59b
Get rid of double toolbars in file editor and mime document.
jasongroutMay 13, 2018
c3496d1
Switch SingletonLayout workaround from panel layout to box layout for…
jasongroutMay 13, 2018
f7b03da
Adjust toolbar height to account for visible widgets.
jasongroutMay 14, 2018
78e85c3
Fix toolbar collapse status based on explicit hiding, rather than cum…
jasongroutMay 14, 2018
b60aaf5
Move csv viewer toolbar over to document widget toolbar.
jasongroutMay 15, 2018
1a6b58d
Delete now-redundant notebook toolbar styling.
jasongroutMay 15, 2018
11ffaaf
Revert part of 7a5bd00ce3df55b85c747d02a00e87c49e3288b4 dealing with …
jasongroutMay 15, 2018
cbb1b6a
Make a CSVDocumentWidget which creates default content and toolbar.
jasongroutMay 15, 2018
6554c24
Fix tests
jasongroutMay 15, 2018
b0a3234
Merge branch 'master' into context
jasongroutMay 15, 2018
5d9d5db
Fix issues with the merge.
jasongroutMay 16, 2018
7ecb3c8
Fix CSV viewer toolbar tests.
jasongroutMay 16, 2018
447ab04
Fix mimedocument tests.
jasongroutMay 16, 2018
ebac5f0
Allow MainAreaWidget ready promises actually be Promise<any> for conv…
jasongroutMay 16, 2018
ae8b29b
Add document widget tests.
jasongroutMay 16, 2018
087d1d9
Fix more tests.
jasongroutMay 16, 2018
0468ec2
WIP notebook tests.
jasongroutMay 17, 2018
ae892b4
More notebook test fixes.
jasongroutMay 17, 2018
ba2802f
Fix more tests, convert many to use async/await.
jasongroutMay 17, 2018
672e843
Add the notebook panel .notebook attribute to ease the transition.
jasongroutMay 17, 2018
57b5c07
Fix integrity failures.
jasongroutMay 17, 2018
99c6c45
Make MainAreaWidget promise resolve *after* the spinner is removed.
jasongroutMay 17, 2018
5a21d5a
When a code editor is created, clear the history after setting the in…
jasongroutMay 17, 2018
7b618ba
Clear the notebook model undo history when the model is set.
jasongroutMay 17, 2018
ec7d693
Move .notebook references to .content
jasongroutMay 17, 2018
d72fd54
Delete the .notebook attribute for notebook panels again.
jasongroutMay 18, 2018
ce1abda
Fix model initialization test.
jasongroutMay 18, 2018
5da4760
path generally will be a stricter criteria than factory name, so matc…
jasongroutMay 18, 2018
64212a6
Initialize a model after the context's first save/revert.
jasongroutMay 18, 2018
5027e9e
Merge remote-tracking branch 'origin/master' into context
jasongroutMay 20, 2018
f12be48
Just use the BoxLayout instead of trying SingletonLayout.
jasongroutMay 20, 2018
7bb239a
Remove comment monologue that is no longer necessary or accurate of t…
jasongroutMay 20, 2018
56e5766
Run notebook panel contentfactory tests.
jasongroutMay 20, 2018
427a67e
Tweak documentation
jasongroutMay 22, 2018
4efcad2
Use StackedLayout instead of BoxLayout for single children.
jasongroutMay 22, 2018
21040a0
Fix review comment issues.
jasongroutMay 23, 2018
bf94b1e
Document the choices around focusing content.
jasongroutMay 23, 2018
d031011
We don’t need the document opener spinner anymore now that the docume…
jasongroutMay 23, 2018
dba0b63
MainAreaWidget attribute name change: ready -> reveal
jasongroutMay 23, 2018
414fd43
Change the main area widget reveal promise to ‘revealed’
jasongroutMay 23, 2018
413f401
Fix the revealed logic for the notebook panel.
jasongroutMay 23, 2018
63c41c1
Clarify logic around `isRevealed` to mean “spinner is gone, either a …
jasongroutMay 23, 2018
3ab6372
Update docs per review comments.
jasongroutMay 24, 2018
6749792
Move signal construction to attribute definition, consistent with the…
jasongroutMay 24, 2018
693810c
Support making the content null.
jasongroutMay 24, 2018
ac907af
Change MainAreaWidget to using the spinner as just a DOM node rather …
jasongroutMay 25, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
WIP trying out an idea for a mime document renderer, pushing most log…
…ic into the factory.This uses basic parts in a composable way, but puts a lot of burden on the factory and externalizes a lot of the logic that maybe should be inside the document widget.
  • Loading branch information
@jasongrout
jasongrout committedMay 10, 2018
commit6ede2da5894cfcbaf5ec35003438cde55c871678
4 changes: 2 additions & 2 deletionspackages/application/src/mimerenderers.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@ import {
} from '@jupyterlab/apputils';

import {
MimeDocument, MimeDocumentFactory, DocumentRegistry
IDocumentWidget, MimeDocumentFactory, DocumentRegistry, MimeContent
} from '@jupyterlab/docregistry';

import {
Expand DownExpand Up@@ -104,7 +104,7 @@ function createRendermimePlugin(item: IRenderMime.IExtension): JupyterLabPlugin<

const factoryName = factory.name;
const namespace = `${factoryName}-renderer`;
const tracker = new InstanceTracker<MimeDocument>({ namespace });
const tracker = new InstanceTracker<IDocumentWidget<MimeContent>>({ namespace });

// Handle state restoration.
restorer.restore(tracker, {
Expand Down
45 changes: 32 additions & 13 deletionspackages/apputils/src/mainareawidget.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,20 @@ import {
* This widget is `closable` by default.
* This widget is automatically disposed when closed.
* This widget ensures its own focus when activated.
*
* You can pass in a ready promise - when the ready promise resolves, the content widget will be shown. In the case of a document widget, the ready promise passed in should probably be combined with the context ready promise? Or perhaps it's the responsibility of the one creating the document widget to hook the context up?
*
* I want to support a class that lets you just pass in a content widget and a ready promise, and I also want to support the use case of a subclass that creates the content widget.
*
* The case of creating the content widget probably doesn't need some of the things here, such as the automatic migration of the title attribute. So perhaps that *is* a separate case? When I create my own widget. Same thing about the toolbar - some widgets will want to create their own toolbar instead of pass in one.
*
* The other way to deal with this is to have the factory create the content widget and toolbar, and pass those into a mainareawidget. The document widget merely has the toolbar, content, and context accessors, and a ready promise/isReady function. The IDocumentWidget interface makes it so that we don't actually expose the constructor. So we actually *do* create the content widget and toolbar in the factory function.
*
* For the notebook factory function, MainAreaWidget takes the place of the notebook panel
*
* So what about the content widget that needs access to the context? Does the IDocumentWidget have a *content* that has a context attribute? That may make more sense than having the context on the overall panel?
*
* So how about this: Let's explore having the main widget like it is, and the factory providing a content that is context-aware
*/
export
class MainAreaWidget<T extends Widget = Widget> extends Widget {
Expand DownExpand Up@@ -67,11 +81,11 @@ class MainAreaWidget<T extends Widget = Widget> extends Widget {
this.title.changed.connect(this._updateContentTitle, this);
content.disposed.connect(() => this.dispose());

if (options.populated) {
if (options.ready) {
layout.addWidget(spinner);
this.populated = options.populated;
this.populated.then(() => {
this._isPopulated = true;
this._ready = options.ready;
this.ready.then(() => {
this._isReady = true;
const active = document.activeElement === spinner.node;
spinner.dispose();
layout.addWidget(content);
Expand All@@ -93,8 +107,8 @@ class MainAreaWidget<T extends Widget = Widget> extends Widget {
} else {
spinner.dispose();
layout.addWidget(content);
this._isPopulated = true;
this.populated = Promise.resolve(void 0);
this._isReady = true;
this._ready = Promise.resolve(void 0);
}
}

Expand All@@ -111,14 +125,16 @@ class MainAreaWidget<T extends Widget = Widget> extends Widget {
/**
* Whether the widget is fully populated.
*/
getisPopulated(): boolean {
return this._isPopulated;
getisReady(): boolean {
return this._isReady;
}

/**
* A promise that resolves when the widget is fully populated.
*/
readonly populated: Promise<void>;
get ready(): Promise<void> {
return this._ready;
}

/**
* Handle the DOM events for the widget.
Expand All@@ -135,7 +151,7 @@ class MainAreaWidget<T extends Widget = Widget> extends Widget {
case 'mouseup':
case 'mouseout':
let target = event.target as HTMLElement;
if (this._isPopulated &&
if (this._isReady &&
this.toolbar.node.contains(document.activeElement) &&
target.tagName !== 'SELECT') {
this._focusContent();
Expand DownExpand Up@@ -166,7 +182,7 @@ class MainAreaWidget<T extends Widget = Widget> extends Widget {
* Handle `'activate-request'` messages.
*/
protected onActivateRequest(msg: Message): void {
if (this._isPopulated) {
if (this._isReady) {
this._focusContent();
} else {
this._spinner.node.focus();
Expand DownExpand Up@@ -222,6 +238,7 @@ class MainAreaWidget<T extends Widget = Widget> extends Widget {
* Give focus to the content.
*/
private _focusContent(): void {
// TODO: perhaps we give the widget a chance to activate, and if that doesn't change the focus, *then* we focus the content node.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

still TODO?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think it's fine as is.

With the current order, we focus right away, but then post an asynchronous message to let the content widget do what it wants (which may include moving the focus somewhere else, of course). So the immediate result is that things are focused, but the content widget can override it.

With the other way, we'd really want tosend the message (not post it) to give the content a chance to focus whatever it wants synchronously, and then if it doesn't focus something inside the content, force the focus on the content node.

I like the current order better: (a) it ensures that the focus actually is on the content node when the function exits, and (b) it gives the content a chance to override it asynchronously.

I'm basically asking if we should switch the order of these two cases.

With the current order, callig _focusContent could actually not focus the content, since the content's activate request could actually focus something else. However, maybe that is okay, in that we don't want toforce having the content focused?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I didn't want to force synchronous focusing down the widget chain.

if (!this.content.node.contains(document.activeElement)) {
this.content.node.focus();
}
Expand All@@ -230,8 +247,10 @@ class MainAreaWidget<T extends Widget = Widget> extends Widget {
}

private _changeGuard = false;
private _isPopulated = false;
private _spinner = new Spinner();

private _isReady = false;
private _ready: Promise<void>;
}


Expand All@@ -258,6 +277,6 @@ namespace MainAreaWidget {
/**
* An optional promise for when the content is ready to be shown.
*/
populated?: Promise<void>;
ready?: Promise<void>;
}
}
10 changes: 10 additions & 0 deletionspackages/docregistry/src/default.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -400,6 +400,16 @@ abstract class ABCWidgetFactory<T extends IDocumentWidget, U extends DocumentReg
private _widgetCreated = new Signal<DocumentRegistry.IWidgetFactory<T, U>, T>(this);
}

/**
* How do we imagine most things using document widgets? Notebooks, images, etc., could be wanting to modify the toolbar, etc. So I imagine we'd have a notebook document widget inheriting from the document widget, rather than a main area widget with a notebook content widget. Or maybe most things have both a content widget and a document widget.
*
* Most of the time, the content widget will want direct access to the context. It also makes sense to have the context on the document widget so outsiders can use it, and the toolbar can use it?
*
*
* IDocumentWidget needs to just take a context and possibly a promise, I think, and provide a content, toolbar, context, and ready promise. So we should *not* extend the mainarea ioptions.
*
* For the implementation, perhaps we just make the BaseMainAreaWidget handle the focus bits, make a new MainAreaWidget class that lets you pass in the content widget, and have documentwidget class that handles the context part on top of the base main area widget class, and then perhaps a convenience document widget that lets you pass in the content widget, so people don't have to keep making their own subclasses?
*/

/**
* A document widget implementation.
Expand Down
129 changes: 70 additions & 59 deletionspackages/docregistry/src/mimedocument.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@
// Distributed under the terms of the Modified BSD License.

import {
showErrorMessage
showErrorMessage, MainAreaWidget
} from '@jupyterlab/apputils';

import {
Expand All@@ -22,7 +22,7 @@ import {
} from '@phosphor/messaging';

import {
BoxLayout, Widget
SingletonLayout, Widget
} from '@phosphor/widgets';

import {
Expand All@@ -35,29 +35,40 @@ import {


/**
* A widget for rendered mimetype document.
* Acontentwidget for a rendered mimetype document.
*/
export
classMimeDocument extendsDocumentWidget<IRenderMime.IRenderer> {
classMimeContent extendsWidget {
/**
* Construct a new widget.
*/
constructor(options:MimeDocument.IOptions) {
super(options);
this.addClass('jp-MimeDocument');
constructor(options:MimeContent.IOptions) {
super();
this.addClass('jp-MimeDocument'); // maybe move to the MainAreaWidget in the factory?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think this is fine here.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thanks.

this._mimeType = options.mimeType;
this._dataType = options.dataType || 'string';
this._context = options.context;
this._renderer = options.renderer;

Promise.all([this.context.ready, this.populated)
this.context.ready.then(() => {
if (this.isDisposed) {
return;
}
const layout = new SingletonLayout();
layout.widget = this._renderer;
this.layout = layout;

this._context.ready.then(() => {
return this._render();
}).then(() => {

// After rendering for the first time, send an activation request if we
// are currently focused.
if (this.node === document.activeElement) {
// We want to synchronously send (not post) the activate message, while
// we know this node still has focus.
MessageLoop.sendMessage(this._renderer, Widget.Msg.ActivateRequest);
}

// Throttle the rendering rate of the widget.
this._monitor = new ActivityMonitor({
signal: this.context.model.contentChanged,
signal: this._context.model.contentChanged,
timeout: options.renderTimeout
});
this._monitor.activityStopped.connect(this.update, this);
Expand DownExpand Up@@ -87,39 +98,33 @@ class MimeDocument extends DocumentWidget<IRenderMime.IRenderer> {
super.dispose();
}

/**
* Handle `'activate-request'` messages.
*/
protected onActivateRequest(msg: Message): void {
if (!this._hasRendered) {
this.node.focus();
return;
}
MessageLoop.sendMessage(this.content, Widget.Msg.ActivateRequest);
if (!this.node.contains(document.activeElement)) {
this.node.focus();
}
}

/**
* Handle an `update-request` message to the widget.
*/
protected onUpdateRequest(msg: Message): void {
if (this.context.isReady) {
if (this._context.isReady) {
this._render();
}
}

/**
* Render the mime content.
*/
private _render(): Promise<void> {
private async _render(): Promise<void> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

An aside: is TS capable of emitting theasync keyword automatically if it knows that a promise is being returned?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm not sure - TSnever emits the async keyword for us since we are compiling for es2015, and async/await is es2016. But I doubt it will automatically emit an async keyword if one is not there in the source.

if (this.isDisposed) {
return;
}

// Since rendering is async, we note render requests that happen while we
// actually are rendering for a future rendering.
if (this._isRendering) {
this._renderRequested = true;
return;
}

// Set up for this rendering pass.
this._renderRequested = false;
let context = this.context;
let context = this._context;
let model = context.model;
let data: JSONObject = {};
if (this._dataType === 'string') {
Expand All@@ -129,23 +134,21 @@ class MimeDocument extends DocumentWidget<IRenderMime.IRenderer> {
}
let mimeModel = new MimeModel({ data, callback: this._changeCallback });

this._isRendering = true;
return this.content.renderModel(mimeModel).then(() => {
// Handle the first render after an activation.
if (!this._hasRendered && this.node === document.activeElement) {
MessageLoop.sendMessage(this.content, Widget.Msg.ActivateRequest);
}
this._hasRendered = true;
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ooh, async/await error handling!

// Do the rendering asynchronously.
this._isRendering = true;
await this._renderer.renderModel(mimeModel);
this._isRendering = false;

// If there is an outstanding request to render, go ahead and render
if (this._renderRequested) {
return this._render();
}
}).catch(reason => {
}catch (error) {
// Dispose the document if rendering fails.
requestAnimationFrame(() => { this.dispose(); });

showErrorMessage(`Renderer Failure: ${context.path}`, reason);
});
requestAnimationFrame(() => { this._renderer.dispose(); });
showErrorMessage(`Renderer Failure: ${context.path}`, error);
}
}

/**
Expand All@@ -157,17 +160,18 @@ class MimeDocument extends DocumentWidget<IRenderMime.IRenderer> {
}
let data = options.data[this._mimeType];
if (typeof data === 'string') {
this.context.model.fromString(data);
this._context.model.fromString(data);
} else {
this.context.model.fromJSON(data);
this._context.model.fromJSON(data);
}
}

private _context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
private _renderer: IRenderMime.IRenderer;
private _monitor: ActivityMonitor<any, any> | null;
private _mimeType: string;
private _ready = new PromiseDelegate<void>();
private _dataType: 'string' | 'json';
private _hasRendered = false;
private _isRendering = false;
private _renderRequested = false;
}
Expand All@@ -177,16 +181,21 @@ class MimeDocument extends DocumentWidget<IRenderMime.IRenderer> {
* The namespace for MimeDocument class statics.
*/
export
namespaceMimeDocument {
namespaceMimeContent {
/**
* The options used to initialize a MimeDocument.
*/
export
interface IOptions extends DocumentWidget.IOptions<IRenderMime.IRenderer> {
/**
*The rendermime instance.
*Context
*/
rendermime: RenderMimeRegistry;
context: DocumentRegistry.IContext<DocumentRegistry.IModel>;

/**
* The renderer instance.
*/
renderer: IRenderMime.IRenderer;

/**
* The mime type.
Expand All@@ -210,7 +219,7 @@ namespace MimeDocument {
* An implementation of a widget factory for a rendered mimetype document.
*/
export
class MimeDocumentFactory extends ABCWidgetFactory<MimeDocument, DocumentRegistry.IModel> {
class MimeDocumentFactory extends ABCWidgetFactory<IDocumentWidget<MimeContent>, DocumentRegistry.IModel> {
/**
* Construct a new markdown widget factory.
*/
Expand All@@ -225,26 +234,28 @@ class MimeDocumentFactory extends ABCWidgetFactory<MimeDocument, DocumentRegistr
/**
* Create a new widget given a context.
*/
protected createNewWidget(context: DocumentRegistry.Context):MimeDocument {
let ft = this._fileType;
let mimeType = ft.mimeTypes.length ? ft.mimeTypes[0] : 'text/plain';
protected createNewWidget(context: DocumentRegistry.Context):MainAreaWidget {
const ft = this._fileType;
const mimeType = ft.mimeTypes.length ? ft.mimeTypes[0] : 'text/plain';


let rendermime = this._rendermime.clone({
const rendermime = this._rendermime.clone({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why did you move this out of the constructor forMimeContent? I don't see a problem with it, but don't quite understand why it was wrong before.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Good question. I think it was basically to simplify the mimecontent widget to not need a full rendermime, but just to take a single renderer. I think it's probably fine to put it back in if you think that's better. I suppose it makes it easier for the caller to not have this logic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think this is fine.

resolver: context.urlResolver
});
let content = rendermime.createRenderer(mimeType);
const renderer = rendermime.createRenderer(mimeType);

let widget = newMimeDocument({
const content = newMimeContent({
context,
content,
renderer,
mimeType,
renderTimeout: this._renderTimeout,
dataType: this._dataType,
});

widget.title.iconClass = ft.iconClass;
widget.title.iconLabel = ft.iconLabel;
content.title.iconClass = ft.iconClass;
content.title.iconLabel = ft.iconLabel;

const widget = new DocumentWidget({content});

return widget;
}

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp