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

Reply via email to