-
Notifications
You must be signed in to change notification settings - Fork 18
Introduced ContextualBalloon class for managing contextual balloons. #177
Changes from 5 commits
24b3784
66ed2ac
3c7fb73
20804ee
7b008e2
1958b63
0ff3a52
28ba5b2
67ee399
d4b1470
3de51b3
24e5c60
a23df06
782cb5c
c334894
dc1f532
58d7ce9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/** | ||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
/** | ||
* @module ui/contextualballoon | ||
*/ | ||
|
||
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; | ||
|
||
/** | ||
* Common contextual balloon of the Editor. | ||
* | ||
* This class reuses the same {module:ui/view~View} for each contextual balloon panel in the editor UI, makes | ||
* possible to add multiple panels to the same balloon (stored in the stack, last one in the stack is visible) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why calling it "panels"? They're simply views, aren't they? It's a tool to use a single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed. |
||
* and prevents of displaying more than one contextual balloon panel at the same time. | ||
*/ | ||
export default class ContextualBalloon { | ||
/** | ||
* Creates ContextualBalloon instance. | ||
* | ||
* @constructor | ||
*/ | ||
constructor() { | ||
/** | ||
* Stack of panels injected to the balloon. Last one in the stack is displayed | ||
* as content of {@link module:ui/contextualballoon~ContextualBalloon#view}. | ||
* | ||
* @private | ||
* @member {Map} #_stack | ||
*/ | ||
this._stack = new Map(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A good point :P |
||
|
||
/** | ||
* Balloon panel view. | ||
* | ||
* @member {module:ui:view~View} #view | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyway, any practical reason why not creating the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved it to the constructor. |
||
*/ | ||
} | ||
|
||
/** | ||
* Returns configuration of currently visible panel or `null` when there is no panel in the stack. | ||
* | ||
* @returns {module:ui/contextualballoon~Panel|null} | ||
*/ | ||
get visible() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
https://github.com/ckeditor/ckeditor5-design/wiki/Code-Style-Naming-Guidelines There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This getter returns visible view. It's not checking if given view is visible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... this returns not only the view but the whole configuration There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But maybe only the view should be returned. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... looks like there is no need to return the balloon position configuration because this position is used internally by ContextualBalloon. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
return this._stack.get( this.view.content.get( 0 ) ) || null; | ||
} | ||
|
||
/** | ||
* Returns `true` when panel of given view is in the stack otherwise returns `false`. | ||
* | ||
* @param {module:ui:view~View} view | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Invalid link. |
||
* @returns {Boolean} | ||
*/ | ||
isPanelInStack( view ) { | ||
return this._stack.has( view ); | ||
} | ||
|
||
/** | ||
* Adds panel to the stack and makes this panel visible. | ||
* | ||
* @param {module:ui/contextualballoon~Panel} panelData Configuration of the panel. | ||
*/ | ||
add( panelData ) { | ||
if ( this._stack.get( panelData.view ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I added |
||
/** | ||
* Trying to add configuration of the same panel more than once. | ||
* | ||
* @error contextualballoon-add-item-exist | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error id in docs does not match error id in |
||
*/ | ||
throw new CKEditorError( 'contextualballoon-add-panel-exist: Cannot add the same panel twice.' ); | ||
} | ||
|
||
// When adding panel to the not empty balloon. | ||
if ( this.visible ) { | ||
// Remove displayed content from the view. | ||
this.view.content.remove( this.visible.view ); | ||
} | ||
|
||
// Add new panel to the stack. | ||
this._stack.set( panelData.view, panelData ); | ||
// And display it. | ||
this._showPanel( panelData ); | ||
} | ||
|
||
/** | ||
* Removes panel of given {@link: module:ui/view~View} from the stack of panels. | ||
* If removed panel was visible then the panel before in the stack will be visible instead. | ||
* When there is no panel in the stack then balloon will hide. | ||
* | ||
* @param {module:ui/view~View} view View of panel which will be removed from the balloon. | ||
*/ | ||
remove( view ) { | ||
if ( !this._stack.get( view ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
/** | ||
* Trying to remove configuration of the panel not defined in the stack. | ||
* | ||
* @error contextualballoon-remove-panel-not-exist | ||
*/ | ||
throw new CKEditorError( 'contextualballoon-remove-panel-not-exist: Cannot remove configuration of not existing panel.' ); | ||
} | ||
|
||
// When visible panel is being removed. | ||
if ( this.visible.view === view ) { | ||
// We need to remove it from the view content. | ||
this.view.content.remove( view ); | ||
|
||
// And then remove from the stack. | ||
this._stack.delete( view ); | ||
|
||
// Next we need to check if there is other panel in stack to show. | ||
const lastPanel = Array.from( this._stack ).pop(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
|
||
// If it is. | ||
if ( lastPanel ) { | ||
// Just show it. | ||
this._showPanel( lastPanel[ 1 ] ); | ||
// Otherwise. | ||
} else { | ||
// Hide balloon panel. | ||
this.view.hide(); | ||
} | ||
// Otherwise. | ||
} else { | ||
// Just remove given panel from the stack. | ||
this._stack.delete( view ); | ||
} | ||
} | ||
|
||
/** | ||
* Updates position of balloon panel according to position data | ||
* of the first panel in the {#_stack}. | ||
*/ | ||
updatePosition() { | ||
this.view.attachTo( this._getBalloonPosition() ); | ||
} | ||
|
||
/** | ||
* Sets panel as a content of the balloon and attaches balloon using position options of the first panel. | ||
* | ||
* @private | ||
* @param {module:ui/contextualballoon~Panel} panelData Configuration of the panel. | ||
*/ | ||
_showPanel( panelData ) { | ||
this.view.content.add( panelData.view ); | ||
this.view.attachTo( this._getBalloonPosition() ); | ||
} | ||
|
||
/** | ||
* Returns position options of the first panel in the stack. | ||
* This helps to keep balloon in the same position when panels are changed. | ||
* | ||
* @private | ||
* @returns {module:utils/dom/position~Options} | ||
*/ | ||
_getBalloonPosition() { | ||
return Array.from( this._stack )[ 0 ][ 1 ].position; | ||
} | ||
} | ||
|
||
/** | ||
* An object describing configuration of single panel added to the balloon stack. | ||
* | ||
* @typedef {Object} module:ui/contextualballoon~Panel | ||
* | ||
* @property {module:ui/view~View} view Panel content view. | ||
* @property {module:utils/dom/position~Options} position Positioning options. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much work would it take to make it a generic utility? I suppose eventually we may end up with other UI components which should manage their children in a similar way. E.g. a dialog, where you edit some fields, then you go deeper to edit some details or internals, then you go back again, etc., without leaving the dialog. Each editing level would be a different child view of the same dialog. If this is a lot of thinking, designing and coding we may think about it as a follow–up, because it's not necessary to MVP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created follow-up #186