8
0
Просмотр исходного кода

Created mockup of the LinkBalloonPanel.

Aleksander Nowodzinski 9 лет назад
Родитель
Сommit
eaf56c6c22

+ 33 - 0
packages/ckeditor5-link/src/ui/linkballoonpanel.js

@@ -0,0 +1,33 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import Controller from '../../ui/controller.js';
+
+/**
+ * The link balloon panel controller class.
+ *
+ * TODO: extends BalloonPanel
+ *
+ * @memberOf link.ui
+ * @extends ui.Controller
+ */
+export default class LinkBalloonPanel extends Controller {
+	/**
+	 * Creates an instance of {@link ui.dropdown.Dropdown} class.
+	 *
+	 * @param {ui.balloonPanel.BalloonPanelModel} model Model of this balloon panel.
+	 * @param {ui.View} view View of this balloon panel.
+	 */
+	constructor( model, view ) {
+		super( model, view );
+
+		view.model.bind( 'arrow', 'maxWidth', 'maxHeight' ).to( model );
+
+		view.model.set( 'top', 0 );
+		view.model.set( 'left', 0 );
+
+		view.model.set( 'isVisible', false );
+	}
+}

+ 230 - 0
packages/ckeditor5-link/src/ui/linkballoonpanelview.js

@@ -0,0 +1,230 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import View from '../../ui/view.js';
+import Template from '../../ui/template.js';
+
+const arrowLeftOffset = 30;
+const arrowTopOffset = 15;
+
+/**
+ * The balloon panel view class.
+ *
+ * See {@link ui.balloonPanel.BalloonPanel}.
+ *
+ * TODO: extends BalloonPanelView
+ *
+ * @memberOf link.ui
+ * @extends ui.View
+ */
+export default class LinkBalloonPanelView extends View {
+	/**
+	 * @inheritDoc
+	 */
+	constructor() {
+		super();
+
+		const bind = this.bind;
+
+		this.template = new Template( {
+			tag: 'div',
+
+			attributes: {
+				class: [
+					'ck-balloon-panel',
+					'ck-link-balloon-panel',
+					bind.to( 'arrow', ( value ) =>  `ck-balloon-panel_arrow_${ value }` ),
+					bind.if( 'isVisible', 'ck-balloon-panel_visible' ),
+				],
+
+				style: {
+					top: bind.to( 'top', ( value ) => `${ value }px` ),
+					left: bind.to( 'left', ( value ) => `${ value }px` ),
+					maxWidth: bind.to( 'maxWidth', ( value ) => `${ value }px` ),
+					maxHeight: bind.to( 'maxHeight', ( value ) => `${ value }px` ),
+				}
+			}
+		} );
+
+		this.register( 'main', el => el );
+	}
+
+	show() {
+		this.model.isVisible = true;
+	}
+
+	hide() {
+		this.model.isVisible = false;
+	}
+
+	getVisibleViewportRect( limiterElement ) {
+		const limiterRect = new AbsoluteDomRect( limiterElement, 'limiter' );
+		const windowScrollX = window.scrollX;
+		const windowScrollY = window.scrollY;
+		const bodyWidth = document.body.clientWidth;
+		const bodyHeight = document.body.clientHeight;
+
+		// 	[Viewport]
+		// 	+---------------------------------------+
+		// 	|                        [Limiter]      |
+		// 	|                        +----------------------+
+		// 	|                        |##############|       |
+		// 	|                        |##############|       |
+		// 	|                        |##############|       |
+		// 	|                        +-------^--------------+
+		// 	|                                |      |
+		// 	+--------------------------------|------+
+		//                                   |
+		//                                    \- [Visible Viewport Rect]
+		//
+		return new AbsoluteDomRect( {
+			top: Math.max( limiterRect.top, windowScrollY ),
+			left: Math.max( limiterRect.left, windowScrollX ),
+			right: Math.min( limiterRect.right, bodyWidth + windowScrollX ),
+			bottom: Math.min( limiterRect.bottom, bodyHeight + windowScrollY )
+		}, 'visibleViewportRect' );
+	}
+
+	attachTo( elementOrRange, limiterElement ) {
+		this.show();
+
+		const elementOrRangeRect = new AbsoluteDomRect( elementOrRange, 'elementOrRange' );
+		const panelRect = new AbsoluteDomRect( this.element );
+		const visibleViewportRect = this.getVisibleViewportRect( limiterElement );
+
+		// visibleViewportRect.paint();
+		// elementOrRangeRect.paint();
+
+		this.smartAttachTo( [
+			// [     ]
+			//    ^
+			// +--------------+
+			// |              |
+			// +--------------+
+			panelRect.clone( 'se' ).moveTo( {
+				top: elementOrRangeRect.bottom + arrowTopOffset,
+				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - arrowLeftOffset
+			} ),
+
+			//          [     ]
+			//             ^
+			// +--------------+
+			// |              |
+			// +--------------+
+			panelRect.clone( 'sw' ).moveTo( {
+				top: elementOrRangeRect.bottom + arrowTopOffset,
+				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - panelRect.width + arrowLeftOffset
+			} ),
+
+			// +--------------+
+			// |              |
+			// +--------------+
+			//    V
+			// [     ]
+			panelRect.clone( 'ne' ).moveTo( {
+				top: elementOrRangeRect.top - panelRect.height - arrowTopOffset,
+				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - arrowLeftOffset
+			} ),
+
+			// +--------------+
+			// |              |
+			// +--------------+
+			//             V
+			//          [     ]
+			panelRect.clone( 'nw' ).moveTo( {
+				top: elementOrRangeRect.top - panelRect.height - arrowTopOffset,
+				left: elementOrRangeRect.left + elementOrRangeRect.width / 2 - panelRect.width + arrowLeftOffset
+			} )
+		], visibleViewportRect );
+	}
+
+	smartAttachTo( rects, visibleViewportRect ) {
+		let maxIntersectRect;
+		let maxIntersectArea = -1;
+
+		for ( let rect of rects ) {
+			const intersectArea = rect.getIntersectArea( visibleViewportRect );
+			// rect.paint();
+
+			if ( intersectArea > maxIntersectArea ) {
+				maxIntersectRect = rect;
+				maxIntersectArea = intersectArea;
+			}
+		}
+
+		this.model.arrow = maxIntersectRect.name;
+		this.model.top = maxIntersectRect.top;
+		this.model.left = maxIntersectRect.left;
+	}
+}
+
+// AbsoluteDomRect always corresponds with what position: absolute would behave.
+class AbsoluteDomRect {
+	constructor( elementOrRangeOrRect, name ) {
+		if ( name ) {
+			this.name = name;
+		}
+
+		if ( elementOrRangeOrRect instanceof HTMLElement || elementOrRangeOrRect instanceof Range ) {
+			const elementRect = elementOrRangeOrRect.getBoundingClientRect();
+			const bodyRect = document.body.getBoundingClientRect();
+
+			this.top = elementRect.top - bodyRect.top;
+			this.right = elementRect.right - bodyRect.left;
+			this.bottom = elementRect.bottom - bodyRect.top;
+			this.left = elementRect.left - bodyRect.left;
+			this.width = elementRect.width;
+			this.height = elementRect.height;
+		} else {
+			Object.assign( this, elementOrRangeOrRect );
+
+			if ( typeof width == 'undefined' ) {
+				this.width = this.right - this.left;
+			}
+
+			if ( typeof height == 'undefined' ) {
+				this.height = this.bottom - this.top;
+			}
+		}
+	}
+
+	clone( newName ) {
+		return new AbsoluteDomRect( this, newName );
+	}
+
+	moveTo( { top, left } ) {
+		this.top = top;
+		this.right = left + this.width;
+		this.bottom = top + this.height;
+		this.left = left;
+
+		return this;
+	}
+
+	getIntersectArea( rect ) {
+		const hOverlap = Math.max( 0, Math.min( this.right, rect.right ) - Math.max( this.left, rect.left ) );
+		const vOverlap = Math.max( 0, Math.min( this.bottom, rect.bottom ) - Math.max( this.top, rect.top ) );
+
+		return hOverlap * vOverlap;
+	}
+
+	paint() {
+		this.painter = document.createElement( 'div' );
+		this.painter.style.backgroundColor = 'rgba(255,0,0,.05)';
+		this.painter.style.color = '#000';
+		this.painter.style.position = 'absolute';
+		this.painter.style.display = 'flex';
+		this.painter.style.justifyContent = 'center';
+		this.painter.style.alignItems = 'center';
+		this.painter.style.left = this.left + 'px';
+		this.painter.style.top = this.top + 'px';
+		this.painter.style.width = this.width + 'px';
+		this.painter.style.height = this.height + 'px';
+		this.painter.style.outline = '1px solid rgba(255,0,0,.3)';
+		this.painter.innerHTML = this.name;
+
+		document.body.appendChild( this.painter );
+	}
+}