Trevor Parscal has uploaded a new change for review.
https://gerrit.wikimedia.org/r/93513
Change subject: [WIP] Context rewrite
......................................................................
[WIP] Context rewrite
Change-Id: I07c082e6ad60ddba059d58a9245c9343af437997
---
M modules/oojs-ui/OO.ui.Inspector.js
M modules/oojs-ui/OO.ui.Window.js
M modules/oojs-ui/OO.ui.WindowSet.js
M modules/ve/ui/actions/ve.ui.InspectorAction.js
M modules/ve/ui/styles/ve.ui.Context.css
M modules/ve/ui/ve.ui.Context.js
M modules/ve/ui/ve.ui.ToolFactory.js
7 files changed, 233 insertions(+), 198 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor
refs/changes/13/93513/1
diff --git a/modules/oojs-ui/OO.ui.Inspector.js
b/modules/oojs-ui/OO.ui.Inspector.js
index 7f60c84..ad48788 100644
--- a/modules/oojs-ui/OO.ui.Inspector.js
+++ b/modules/oojs-ui/OO.ui.Inspector.js
@@ -100,9 +100,7 @@
};
/**
- * Handle frame ready events.
- *
- * @method
+ * @inheritdoc
*/
OO.ui.Inspector.prototype.initialize = function () {
// Parent method
@@ -139,3 +137,13 @@
}
this.$body.append( this.$form );
};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.Inspector.prototype.show = function ( config ) {
+ this.fitHeightToContents();
+
+ // Parent method
+ OO.ui.Window.prototype.show.call( this, config );
+};
diff --git a/modules/oojs-ui/OO.ui.Window.js b/modules/oojs-ui/OO.ui.Window.js
index 5484e67..596f300 100644
--- a/modules/oojs-ui/OO.ui.Window.js
+++ b/modules/oojs-ui/OO.ui.Window.js
@@ -282,6 +282,7 @@
*
* @method
* @param {Object} [config] Window opening information
+ * @chainable
*/
OO.ui.Window.prototype.open = function ( config ) {
this.frame.run( OO.ui.bind( function () {
@@ -289,6 +290,8 @@
this.frame.$.focus();
this.enter();
}, this ) );
+
+ return this;
};
/**
@@ -296,11 +299,14 @@
*
* @method
* @param {Object} [config] Window closing information
+ * @chainable
*/
OO.ui.Window.prototype.close = function ( config ) {
this.exit();
this.frame.$content.find( ':focus' ).blur();
this.hide( config );
+
+ return this;
};
/**
diff --git a/modules/oojs-ui/OO.ui.WindowSet.js
b/modules/oojs-ui/OO.ui.WindowSet.js
index 2ce1cb6..6cfc99b 100644
--- a/modules/oojs-ui/OO.ui.WindowSet.js
+++ b/modules/oojs-ui/OO.ui.WindowSet.js
@@ -73,6 +73,10 @@
* @fires show
*/
OO.ui.WindowSet.prototype.onWindowShow = function ( win, config ) {
+ if ( this.currentWindow && win !== this.currentWindow ) {
+ this.currentWindow.close();
+ }
+ this.currentWindow = win;
this.emit( 'show', win, config );
};
@@ -85,6 +89,7 @@
* @fires hide
*/
OO.ui.WindowSet.prototype.onWindowHide = function ( win, config ) {
+ this.currentWindow = null;
this.emit( 'hide', win, config );
};
@@ -96,7 +101,6 @@
* @fires enter
*/
OO.ui.WindowSet.prototype.onWindowEnter = function ( win ) {
- this.currentWindow = win;
this.emit( 'enter', win );
};
@@ -108,7 +112,6 @@
* @fires exit
*/
OO.ui.WindowSet.prototype.onWindowExit = function ( win ) {
- this.currentWindow = null;
this.emit( 'exit', win );
};
@@ -133,9 +136,6 @@
if ( !this.factory.lookup( name ) ) {
throw new Error( 'Unknown window: ' + name );
- }
- if ( this.currentWindow ) {
- throw new Error( 'Cannot open another window while another one
is active' );
}
if ( !( name in this.windows ) ) {
win = this.windows[name] = this.factory.create( name, this, {
'$$': this.$$ } );
diff --git a/modules/ve/ui/actions/ve.ui.InspectorAction.js
b/modules/ve/ui/actions/ve.ui.InspectorAction.js
index bc53636..f7224bd 100644
--- a/modules/ve/ui/actions/ve.ui.InspectorAction.js
+++ b/modules/ve/ui/actions/ve.ui.InspectorAction.js
@@ -44,7 +44,7 @@
* @param {Object} [config] Configuration options for inspector setup
*/
ve.ui.InspectorAction.prototype.open = function ( name, config ) {
- this.surface.getContext().openInspector( name, config );
+ this.surface.getContext().getInspector( name ).open( config );
};
/* Registration */
diff --git a/modules/ve/ui/styles/ve.ui.Context.css
b/modules/ve/ui/styles/ve.ui.Context.css
index 50b6bc8..8b2c49a 100644
--- a/modules/ve/ui/styles/ve.ui.Context.css
+++ b/modules/ve/ui/styles/ve.ui.Context.css
@@ -7,28 +7,28 @@
.ve-ui-context,
.ve-ui-context-inspectors,
-.ve-ui-context-menu {
+.ve-ui-context-toolbar {
position: absolute;
}
-.ve-ui-context-menu .oo-ui-toolbar-bar {
+.ve-ui-context-toolbar .oo-ui-toolbar-bar {
white-space: nowrap;
border: none;
background: none;
}
-.ve-ui-context-menu .oo-ui-toolGroup {
+.ve-ui-context-toolbar .oo-ui-toolGroup {
border: none;
margin: 0;
}
-.ve-ui-context-menu .oo-ui-tool,
-.ve-ui-context-menu .oo-ui-tool:hover {
+.ve-ui-context-toolbar .oo-ui-tool,
+.ve-ui-context-toolbar .oo-ui-tool:hover {
border: none;
}
-.ve-ui-context-menu .oo-ui-tool:active,
-.ve-ui-context-menu .oo-ui-tool-active {
+.ve-ui-context-toolbar .oo-ui-tool:active,
+.ve-ui-context-toolbar .oo-ui-tool-active {
background-image: none;
}
@@ -46,7 +46,7 @@
margin-left: 0.5em;
}
-.ve-ui-context > .oo-ui-popupWidget:not(.oo-ui-popupWidget-tailed)
.oo-ui-context-menu {
+.ve-ui-context > .oo-ui-popupWidget:not(.oo-ui-popupWidget-tailed)
.oo-ui-context-toolbar {
right: 0;
}
diff --git a/modules/ve/ui/ve.ui.Context.js b/modules/ve/ui/ve.ui.Context.js
index e355b3d..373cf93 100644
--- a/modules/ve/ui/ve.ui.Context.js
+++ b/modules/ve/ui/ve.ui.Context.js
@@ -21,31 +21,37 @@
// Properties
this.surface = surface;
- this.inspectors = {};
this.visible = false;
this.showing = false;
this.hiding = false;
- this.openingInspector = false;
+ this.updating = false;
this.selecting = false;
this.relocating = false;
- this.embedded = false;
this.selection = null;
- this.toolbar = null;
+ // this.embedded = false;
this.afterModelSelectTimeout = null;
- this.popup = new OO.ui.PopupWidget( { '$$': this.$$, '$container':
this.surface.getView().$ } );
- this.$menu = this.$$( '<div>' );
- this.inspectors = new ve.ui.SurfaceWindowSet( surface,
ve.ui.inspectorFactory );
+ this.popup = new OO.ui.PopupWidget( {
+ '$$': this.$$,
+ '$container': this.surface.getView().$,
+ 'classes': [ 've-ui-context-popup' ]
+ } );
+ this.toolbar = new ve.ui.SurfaceToolbar( this.surface, {
+ '$$': this.$$,
+ 'classes': [ 've-ui-context-toolbar' ]
+ } );
+ this.inspectors = new ve.ui.SurfaceWindowSet( surface,
ve.ui.inspectorFactory, {
+ '$$': this.$$,
+ 'classes': [ 've-ui-context-inspectors' ]
+ } );
// Initialization
- this.$.addClass( 've-ui-context' ).append( this.popup.$ );
- this.inspectors.$.addClass( 've-ui-context-inspectors' );
- this.popup.$body.append(
- this.$menu.addClass( 've-ui-context-menu' ),
- this.inspectors.$.addClass( 've-ui-context-inspectors' )
- );
+ this.toolbar.initialize();
+ this.$
+ .addClass( 've-ui-context' )
+ .append( this.popup.$ );
+ this.popup.$body.append( this.toolbar.$, this.inspectors.$ );
// Events
- this.surface.getModel().connect( this, { 'select': 'onModelSelect' } );
this.surface.getView().connect( this, {
'selectionStart': 'onSelectionStart',
'selectionEnd': 'onSelectionEnd',
@@ -55,14 +61,14 @@
this.inspectors.connect( this, {
'show': 'onInspectorShow',
'enter': 'onInspectorEnter',
+ 'exit': 'onInspectorExit',
'hide': 'onInspectorHide'
} );
-
+ this.surface.getModel().connect( this, { 'select': 'onModelSelect' } );
this.$$( this.getElementWindow() ).on( {
'resize': ve.bind( this.update, this )
} );
- this.$.add( this.$menu )
- .on( 'mousedown', false );
+ this.$.on( 'mousedown', false );
};
/* Inheritance */
@@ -151,40 +157,50 @@
};
/**
- * Handle an inspector being setup.
+ * Handle an inspector being shown.
*
* @method
- * @param {ve.ui.SurfaceInspector} inspector Inspector that's been setup
+ * @param {ve.ui.SurfaceInspector} inspector Inspector that's been shown
* @param {Object} [config] Inspector showing information
*/
ve.ui.Context.prototype.onInspectorShow = function () {
- this.selection = this.surface.getModel().getSelection();
+ this.show();
};
/**
- * Handle an inspector being opened.
+ * Handle an inspector being entered.
*
* @method
- * @param {ve.ui.SurfaceInspector} inspector Inspector that's been opened
+ * @param {ve.ui.SurfaceInspector} inspector Inspector that's been entered
*/
ve.ui.Context.prototype.onInspectorEnter = function () {
- // Transition between menu and inspector
- this.show( true );
-};
-
-/**
- * Handle an inspector being closed.
- *
- * @method
- * @param {ve.ui.SurfaceInspector} inspector Inspector that's been opened
- * @param {Object} [config] Inspector hiding information
- */
-ve.ui.Context.prototype.onInspectorHide = function () {
+ this.selection = this.surface.getModel().getSelection();
this.update();
};
/**
- * Gets the surface the context is being used in.
+ * Handle an inspector being exited.
+ *
+ * @method
+ * @param {ve.ui.SurfaceInspector} inspector Inspector that's been exited
+ */
+ve.ui.Context.prototype.onInspectorExit = function () {
+ this.update();
+};
+
+/**
+ * Handle an inspector being hidden.
+ *
+ * @method
+ * @param {ve.ui.SurfaceInspector} inspector Inspector that's been hidden
+ * @param {Object} [config] Inspector hiding information
+ */
+ve.ui.Context.prototype.onInspectorHide = function () {
+ this.hide();
+};
+
+/**
+ * Get the surface the context is being used in.
*
* @method
* @returns {ve.ui.Surface} Surface of context
@@ -194,81 +210,153 @@
};
/**
+ * Get an inspector.
+ *
+ * @method
+ * @returns {ve.ui.SurfaceInspector|undefined} Matching inspector
+ */
+ve.ui.Context.prototype.getInspector = function ( name ) {
+ return this.inspectors.getWindow( name );
+};
+
+/**
* Destroy the context, removing all DOM elements.
*
* @method
- * @returns {ve.ui.Context} Context UserInterface
* @chainable
*/
ve.ui.Context.prototype.destroy = function () {
+ this.toolbar.destroy();
this.$.remove();
return this;
};
/**
- * Updates the context menu.
+ * Set the context mode.
*
* @method
- * @param {boolean} [transition=false] Use a smooth transition
- * @param {boolean} [repositionOnly=false] The context is only being moved so
don't fade in
+ * @param {string} [mode] Mode name, such as 'inspect' or 'toolbar', omit to
exit all modes and hide
+ * @param {Object} [config] Additional data for specific modes
+ * @param {ve.ui.SurfaceInspector} [config.inspector] Inspector to show, use
with 'inspect' mode
+ * @param {string[]} [config.tools] Names of tools to show, use with 'toolbar'
mode
* @chainable
*/
-ve.ui.Context.prototype.update = function ( transition, repositionOnly ) {
- var i, nodes, tools, tool,
- fragment = this.surface.getModel().getFragment( null, false ),
- selection = fragment.getRange(),
- inspector = this.inspectors.getCurrentWindow();
+ve.ui.Context.prototype.setMode = function ( mode, config ) {
+ var next,
+ current = this.inspectors.getCurrentWindow();
- if ( inspector && selection.equals( this.selection ) ) {
- // There's an inspector, and the selection hasn't changed,
update the position
- this.show( transition, repositionOnly );
- } else {
- // No inspector is open, or the selection has changed, show a
menu of available inspectors
- tools = ve.ui.toolFactory.getToolsForAnnotations(
fragment.getAnnotations() );
- nodes = fragment.getCoveredNodes();
- for ( i = 0; i < nodes.length; i++ ) {
- if ( nodes[i].range && nodes[i].range.isCollapsed() ) {
- nodes.splice( i, 1 );
- i--;
- }
+ // Turn things off
+ if ( mode !== 'inspect' ) {
+ if ( current ) {
+ current.close();
}
- if ( nodes.length === 1 ) {
- tool = ve.ui.toolFactory.getToolForNode( nodes[0].node
);
- if ( tool ) {
- tools.push( tool );
- }
- }
- if ( tools.length ) {
- // There's at least one inspectable annotation, build a
menu and show it
- this.$menu.empty();
- if ( this.toolbar ) {
- this.toolbar.destroy();
- }
- this.toolbar = new ve.ui.SurfaceToolbar( this.surface );
- this.toolbar.setup( [ { 'include' : tools } ] );
- this.$menu.append( this.toolbar.$ );
- this.show( transition, repositionOnly );
- this.toolbar.initialize();
- } else if ( this.visible ) {
- // Nothing to inspect
- this.hide();
- }
+ this.inspectors.$.hide();
+ }
+ if ( mode !== 'toolbar' ) {
+ this.toolbar.reset();
+ this.toolbar.$.hide();
}
- // Remember selection for next time
- this.selection = selection.clone();
+ // Turn things on
+ if ( mode === 'inspect' ) {
+ this.inspectors.$.show();
+ next = config.inspector;
+ if ( current !== next ) {
+ if ( current ) {
+ current.close();
+ }
+ next.open( config.config );
+ }
+ }
+ if ( mode === 'toolbar' ) {
+ this.toolbar.$.show();
+ this.toolbar.setup( [ { 'include' : config.tools } ] );
+ }
+
+ // Update visibility
+ if ( mode ) {
+ this.show();
+ this.reposition();
+ } else {
+ this.hide();
+ }
+};
+
+/**
+ * Shows the context.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.Context.prototype.show = function () {
+ if ( !this.showing && !this.visible ) {
+ this.showing = true;
+ this.$.show();
+ this.popup.show();
+ this.visible = true;
+ this.showing = false;
+ }
return this;
};
/**
- * Updates the position and size.
+ * Hides the context.
*
* @method
- * @param {boolean} [transition=false] Use a smooth transition
* @chainable
*/
-ve.ui.Context.prototype.updateDimensions = function ( transition ) {
+ve.ui.Context.prototype.hide = function () {
+ if ( !this.hiding && this.visible ) {
+ this.hiding = true;
+ this.popup.hide();
+ this.$.hide();
+ this.visible = false;
+ this.hiding = false;
+ }
+};
+
+/**
+ * Keep the mode in sync with the model.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.Context.prototype.update = function () {
+ var tools, fragment, inspector, selection;
+
+ if ( !this.updating ) {
+ this.updating = true;
+ fragment = this.surface.getModel().getFragment( null, false );
+ selection = fragment.getRange();
+ inspector = this.inspectors.getCurrentWindow();
+ if ( inspector && this.selection && this.selection.equals(
selection ) ) {
+ // Show current inspector
+ this.setMode( 'inspect', { 'inspector': inspector } );
+ } else {
+ tools = ve.ui.toolFactory.getToolsForFragment( fragment
);
+ if ( tools.length ) {
+ // Show toolbar for fragment
+ this.setMode( 'toolbar', { 'tools': tools } );
+ } else {
+ // Show nothing
+ this.setMode();
+ }
+ }
+ this.selection = selection.clone();
+ this.updating = false;
+ }
+
+ return this;
+};
+
+/**
+ * Reposition the context.
+ *
+ * @method
+ * @chainable
+ */
+ve.ui.Context.prototype.reposition = function () {
var $node, $container, focusableOffset, focusableWidth, nodePosition,
cursorPosition, position,
documentOffset, nodeOffset,
surface = this.surface.getView(),
@@ -277,7 +365,7 @@
surfaceOffset = surface.$.offset(),
rtl = this.surface.view.getDir() === 'rtl';
- $container = inspector ? this.inspectors.$ : this.$menu;
+ $container = inspector ? this.inspectors.$ : this.toolbar.$;
if ( focusedNode ) {
// We're on top of a node
$node = focusedNode.$focusable || focusedNode.$;
@@ -336,102 +424,7 @@
this.$.css( { 'left': position.x, 'top': position.y } );
- this.popup.display(
- $container.outerWidth( true ),
- $container.outerHeight( true ),
- transition
- );
+ this.popup.display( $container.outerWidth( true ),
$container.outerHeight( true ) );
- return this;
-};
-
-/**
- * Shows the context menu.
- *
- * @method
- * @param {boolean} [transition=false] Use a smooth transition
- * @param {boolean} [repositionOnly=false] The context is only being moved so
don't fade in
- * @chainable
- */
-ve.ui.Context.prototype.show = function ( transition, repositionOnly ) {
- var inspector = this.inspectors.getCurrentWindow(),
- focusedNode = this.surface.getView().getFocusedNode();
-
- if ( !this.showing ) {
- this.showing = true;
-
- this.$.show();
- this.popup.show();
-
- // Show either inspector or menu
- if ( inspector ) {
- this.$menu.hide();
- this.inspectors.$.show();
- if ( !repositionOnly ) {
- inspector.$.css( 'opacity', 0 );
- }
- // Update size and fade the inspector in after
animation is complete
- setTimeout( ve.bind( function () {
- inspector.fitHeightToContents();
- this.updateDimensions( transition );
- inspector.$.css( 'opacity', 1 );
- }, this ), 200 );
- } else {
- this.inspectors.$.hide();
- this.embedded = (
- focusedNode &&
- focusedNode.$focusable.outerHeight() >
this.$menu.outerHeight() * 2 &&
- focusedNode.$focusable.outerWidth() >
this.$menu.outerWidth() * 2
- );
- this.popup.useTail( !this.embedded );
- this.$menu.show();
- }
-
- this.updateDimensions( transition );
-
- this.visible = true;
- this.showing = false;
- }
-
- return this;
-};
-
-/**
- * Hides the context menu.
- *
- * @method
- * @chainable
- */
-ve.ui.Context.prototype.hide = function () {
- var inspector = this.inspectors.getCurrentWindow();
-
- // Guard against recursion: closing the inspector causes
onInspectorClose to call us again
- if ( !this.hiding ) {
- this.hiding = true;
- if ( inspector ) {
- inspector.close( { 'action': 'hide' } );
- }
- this.popup.hide();
- this.$.hide();
- this.visible = false;
- this.hiding = false;
- }
- return this;
-};
-
-/**
- * Opens a given inspector.
- *
- * @method
- * @param {string} name Symbolic name of inspector
- * @param {Object} [config] Configuration options to be sent to the inspector
class constructor
- * @chainable
- */
-ve.ui.Context.prototype.openInspector = function ( name, config ) {
- if ( !this.inspectors.currentWindow ) {
- this.openingInspector = true;
- this.inspectors.getWindow( name ).open( config );
- this.openingInspector = false;
- }
return this;
};
diff --git a/modules/ve/ui/ve.ui.ToolFactory.js
b/modules/ve/ui/ve.ui.ToolFactory.js
index f3d85d7..01cad48 100644
--- a/modules/ve/ui/ve.ui.ToolFactory.js
+++ b/modules/ve/ui/ve.ui.ToolFactory.js
@@ -25,7 +25,7 @@
/* Methods */
/**
- * Get a list of tools from a set of annotations.
+ * Get a list of tools that can be used with content containing a set of
annotations.
*
* The most specific tool will be chosen based on inheritance - mostly. The
order of being added
* also matters if the candidate classes aren't all in the same inheritance
chain, and since object
@@ -67,7 +67,7 @@
};
/**
- * Get a tool for a node.
+ * Get a tool that can be used with a node.
*
* The most specific tool will be chosen based on inheritance - mostly. The
order of being added
* also matters if the candidate classes aren't all in the same inheritance
chain, and since object
@@ -99,6 +99,34 @@
return candidateToolName;
};
+/**
+ * Get a list of tools that can be used with a fragment.
+ *
+ * @method
+ * @param {ve.dm.SurfaceFragment} fragment Fragment to show toolbar for
+ * @returns {string[]} Compatible tool names
+ */
+ve.ui.ToolFactory.prototype.getToolsForFragment = function ( fragment ) {
+ var i, len, tool,
+ tools = this.getToolsForAnnotations( fragment.getAnnotations()
),
+ nodes = fragment.getCoveredNodes();
+
+ for ( i = 0, len = nodes.length; i < len; i++ ) {
+ if ( nodes[i].range && nodes[i].range.isCollapsed() ) {
+ nodes.splice( i, 1 );
+ len--;
+ i--;
+ }
+ }
+ if ( nodes.length === 1 ) {
+ tool = this.getToolForNode( nodes[0].node );
+ if ( tool ) {
+ tools.push( tool );
+ }
+ }
+ return tools;
+};
+
/* Initialization */
ve.ui.toolFactory = new ve.ui.ToolFactory();
--
To view, visit https://gerrit.wikimedia.org/r/93513
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I07c082e6ad60ddba059d58a9245c9343af437997
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits