Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Introduced FakePanels for ContextualBalloon. #498

Merged
merged 15 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion src/panel/balloon/balloonpanelview.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,13 @@ export default class BalloonPanelView extends View {
fitInViewport: true
}, options );

const { top, left, name: position } = BalloonPanelView._getOptimalPosition( positionOptions );
const optimalPosition = BalloonPanelView._getOptimalPosition( positionOptions );

// Usually browsers make some problems with super accurate values like 104.345px
// so it is better to use int values.
const left = parseInt( optimalPosition.left );
const top = parseInt( optimalPosition.top );
const position = optimalPosition.name;

Object.assign( this, { top, left, position } );
}
Expand Down
169 changes: 151 additions & 18 deletions src/panel/balloon/contextualballoon.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ import View from '../../view';
import ButtonView from '../../button/buttonview';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
import toUnit from '@ckeditor/ckeditor5-utils/src/dom/tounit';
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';

import prevIcon from '../../../theme/icons/previous-arrow.svg';
import nextIcon from '../../../theme/icons/next-arrow.svg';

import '../../../theme/components/panel/balloonrotator.css';
import '../../../theme/components/panel/fakepanel.css';

const toPx = toUnit( 'px' );

/**
* Provides the common contextual balloon for the editor.
Expand Down Expand Up @@ -141,6 +146,14 @@ export default class ContextualBalloon extends Plugin {
* @type {module:ui/panel/balloon/contextualballoon~RotatorView}
*/
this._rotatorView = this._createRotatorView();

/**
* Displays fake panels under the balloon panel view when multiple stacks are added to the balloon.
*
* @private
* @type {module:ui/view~View}
*/
this._fakePanelsView = this._createFakePanelsView();
}

/**
Expand Down Expand Up @@ -259,7 +272,7 @@ export default class ContextualBalloon extends Plugin {
}

this.view.pin( this._getBalloonPosition() );
this._rotatorView.updateIsNarrow();
this._fakePanelsView.updatePosition();
}

/**
Expand Down Expand Up @@ -395,6 +408,24 @@ export default class ContextualBalloon extends Plugin {
return view;
}

/**
* @returns {module:ui/view~View}
*/
_createFakePanelsView() {
const view = new FakePanelsView( this.editor.locale, this.view );

view.bind( 'numberOfPanels' ).to( this, '_numberOfStacks', number => {
return number < 2 ? 0 : Math.min( number - 1, 2 );
} );

view.listenTo( this.view, 'change:top', () => view.updatePosition() );
view.listenTo( this.view, 'change:left', () => view.updatePosition() );

this.editor.ui.view.body.add( view );

return view;
}

/**
* Sets the view as a content of the balloon and attaches balloon using position
* options of the first view.
Expand All @@ -412,6 +443,7 @@ export default class ContextualBalloon extends Plugin {
this._rotatorView.showView( view );
this.visibleView = view;
this.view.pin( this._getBalloonPosition() );
this._fakePanelsView.updatePosition();
}

/**
Expand Down Expand Up @@ -460,13 +492,6 @@ class RotatorView extends View {
*/
this.set( 'isNavigationVisible', true );

/**
* Defines whether balloon should be marked as narrow or not.
*
* @member {Boolean} #isNarrow
*/
this.set( 'isNarrow', false );

/**
* Used for checking if view is focused or not.
*
Expand Down Expand Up @@ -521,8 +546,7 @@ class RotatorView extends View {

attributes: {
class: [
'ck-balloon-rotator__counter',
bind.to( 'isNarrow', value => value ? 'ck-hidden' : '' )
'ck-balloon-rotator__counter'
]
},

Expand Down Expand Up @@ -555,13 +579,6 @@ class RotatorView extends View {
this.focusTracker.add( this.element );
}

/**
* Checks if view width is narrow and updated {@link ~RotatorView#isNarrow} state.
*/
updateIsNarrow() {
this.isNarrow = this.element.clientWidth <= 200;
}

/**
* Shows given view.
*
Expand All @@ -570,7 +587,6 @@ class RotatorView extends View {
showView( view ) {
this.hideView();
this.content.add( view );
this.updateIsNarrow();
}

/**
Expand Down Expand Up @@ -600,3 +616,120 @@ class RotatorView extends View {
return view;
}
}

// Displays additional layers under the balloon when multiple stacks are added to the balloon.
//
// @private
// @extends module:ui/view~View
class FakePanelsView extends View {
// @inheritDoc
constructor( locale, balloonPanelView ) {
super( locale );

const bind = this.bindTemplate;

// Fake panels top offset.
//
// @observable
// @member {Number} #top
this.set( 'top', 0 );

// Fake panels left offset.
//
// @observable
// @member {Number} #left
this.set( 'left', 0 );

// Fake panels height.
//
// @observable
// @member {Number} #height
this.set( 'height', 0 );

// Fake panels width.
//
// @observable
// @member {Number} #width
this.set( 'width', 0 );

// Number of rendered fake panels.
//
// @observable
// @member {Number} #numberOfPanels
this.set( 'numberOfPanels', 0 );

// Collection of the child views which creates fake panel content.
//
// @readonly
// @type {module:ui/viewcollection~ViewCollection}
this.content = this.createCollection();

// Context.
//
// @private
// @type {module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
this._balloonPanelView = balloonPanelView;

this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck-fake-panel',
bind.to( 'numberOfPanels', number => number ? '' : 'ck-hidden' )
],
style: {
top: bind.to( 'top', toPx ),
left: bind.to( 'left', toPx ),
width: bind.to( 'width', toPx ),
height: bind.to( 'height', toPx )
}
},
children: this.content
} );

this.on( 'change:numberOfPanels', ( evt, name, next, prev ) => {
if ( next > prev ) {
this._addPanels( next - prev );
} else {
this._removePanels( prev - next );
}

this.updatePosition();
} );
}

// @private
// @param {Number} number
_addPanels( number ) {
while ( number-- ) {
const view = new View();

view.setTemplate( { tag: 'div' } );

this.content.add( view );
this.registerChild( view );
}
}

// @private
// @param {Number} number
_removePanels( number ) {
while ( number-- ) {
const view = this.content.last;

this.content.remove( view );
this.deregisterChild( view );
view.destroy();
}
}

// Updates coordinates of fake panels.
updatePosition() {
if ( this.numberOfPanels ) {
const { top, left } = this._balloonPanelView;
const { width, height } = new Rect( this._balloonPanelView.element );

Object.assign( this, { top, left, width, height } );
}
}
}
20 changes: 17 additions & 3 deletions tests/manual/contextualballoon/contextualballoon.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<p>Line of text, line of text, line of text, line of text.</p>
<p>Line of text, line of text, line of text, line of text.</p>
<p>Line of text, line of text, line of text, line of text.</p>
<p>Line of text, line of text, line of text, line of text.</p>
<p>Line of text, line of [select] text, line of text, line of text.</p>
<p>Line of text, line of text, line of text, line of text.</p>
<p>Line of text, line of text, line of text, line of text.</p>
<p>Line of text, line of text, line of text, line of text.</p>
Expand All @@ -21,11 +21,25 @@
max-width: 70%;
}

.highlight {
background: yellow;
.highlight.yellow {
background: hsla(55, 100%, 50%, 0.75);
}

.highlight.blue {
background: hsla(249, 100%, 50%, 0.75);
}

.highlight.pink {
background: hsla(313, 100%, 50%, 0.75);
}

.highlight.green {
background: hsla(114, 100%, 50%, 0.75);
}

.ck p.custom-view {
width: 300px;
text-align: center;
font-size: 14px;
padding: 10px 10px 9px;
}
Expand Down
22 changes: 13 additions & 9 deletions tests/manual/contextualballoon/contextualballoon.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ class CustomStackHighlight {
init() {
this.editor.conversion.for( 'editingDowncast' ).markerToHighlight( {
model: 'highlight',
view: () => {
return { classes: 'highlight' };
view: data => {
const color = data.markerName.split( ':' )[ 2 ];

return { classes: 'highlight ' + color };
}
} );

Expand All @@ -51,7 +53,7 @@ class CustomStackHighlight {

this.editor.plugins.get( ContextualBalloon ).add( {
view,
stackId: 'custom',
stackId: 'custom-' + marker.name.split( ':' )[ 1 ],
position: {
target: this._getMarkerDomElement( marker )
}
Expand All @@ -65,9 +67,9 @@ class CustomStackHighlight {

_getMarkerDomElement( marker ) {
const editing = this.editor.editing;
const viewElement = Array.from( editing.mapper.markerNameToElements( marker.name ).values() )[ 0 ];
const viewRange = editing.mapper.toViewRange( marker.getRange() );

return editing.view.domConverter.mapViewToDom( viewElement );
return editing.view.domConverter.viewRangeToDom( viewRange );
}
}

Expand All @@ -84,11 +86,13 @@ ClassicEditor
const root = editor.model.document.getRoot();

[
{ id: 1, start: [ 1, 5 ], end: [ 1, 26 ] },
{ id: 2, start: [ 5, 5 ], end: [ 5, 26 ] },
{ id: 3, start: [ 10, 5 ], end: [ 10, 26 ] }
{ id: 1, start: [ 1, 5 ], end: [ 1, 26 ], color: 'yellow' },
{ id: 2, start: [ 1, 2 ], end: [ 1, 33 ], color: 'green' },
{ id: 3, start: [ 5, 20 ], end: [ 5, 35 ], color: 'blue' },
{ id: 4, start: [ 5, 15 ], end: [ 5, 40 ], color: 'pink' },
{ id: 5, start: [ 5, 10 ], end: [ 5, 45 ], color: 'yellow' }
].forEach( data => {
writer.addMarker( `highlight:${ data.id }`, {
writer.addMarker( `highlight:${ data.id }:${ data.color }`, {
range: writer.createRange(
writer.createPositionFromPath( root, data.start ),
writer.createPositionFromPath( root, data.end )
Expand Down
10 changes: 9 additions & 1 deletion tests/manual/contextualballoon/contextualballoon.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@
## Multiple stacks

1. Select some highlighted text - "View in separate stack." should show up.
2. Switch stacks by clicking navigation buttons - you should switch between toolbar and custom view.
2. Switch stacks by clicking navigation buttons. You should switch between toolbar and custom views.

## Fake panels - min

1. Put the selection before the highlight, start moving selection by right arrow. You should see additional layer under the balloon only when at least 2 stacks are added to the balloon.

## Fake panels - max

1. Select text `[select]` (by non-collapsed selection) from the lower highlight. You should see `1 of 4` status of pagination but only 2 additional layers under the balloon should be visible.
13 changes: 13 additions & 0 deletions tests/panel/balloon/balloonpanelview.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ describe( 'BalloonPanelView', () => {
} ) );
} );

it( 'should parse optimal position offset to int', () => {
testUtils.sinon.stub( BalloonPanelView, '_getOptimalPosition' ).returns( {
top: 10.345,
left: 10.345,
name: 'position'
} );

view.attachTo( { target, limiter } );

expect( view.top ).to.equal( 10 );
expect( view.left ).to.equal( 10 );
} );

describe( 'limited by limiter element', () => {
beforeEach( () => {
// Mock limiter element dimensions.
Expand Down
Loading