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

Internal: next implementation of the resizer. This was required to acquire access to model item of resized element (here an image).

Most of the logic has been moved to the ResizeContext type. Now the idea is to create a ResizeManager that will include zero or more contexts during the runtime and will manage (only one) active context during the resize.
Marek Lewandowski 6 лет назад
Родитель
Сommit
c4cd264468

+ 192 - 0
packages/ckeditor5-widget/src/resizecontext.js

@@ -0,0 +1,192 @@
+import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
+import dragHandlerIcon from '../theme/icons/drag-handler.svg';
+
+const HEIGHT_ATTRIBUTE_NAME = 'height';
+
+/**
+ * Returns coordinates of top-left corner of a element, relative to the document's top-left corner.
+ *
+ * @param {HTMLElement} element
+ * @returns {Object} return
+ * @returns {Number} return.x
+ * @returns {Number} return.y
+ */
+function getAbsolutePosition( element ) {
+	const nativeRectangle = element.getBoundingClientRect();
+
+	return {
+		x: nativeRectangle.left + element.ownerDocument.defaultView.scrollX,
+		y: nativeRectangle.top + element.ownerDocument.defaultView.scrollY
+	};
+}
+
+export default class ResizeContext {
+	constructor() {
+		// HTMLElement???
+		this.resizeHost = null;
+		// view/UiElement
+		this.resizeWrapperElement = null;
+		// view/Element
+		this.widgetWrapperElement = null;
+		// HTMLElement|null - might be uninitialized
+		this.domResizeWrapper = null;
+		this.domResizeShadow = null;
+
+		// @todo: ---- options below seems like a little outside of a scope of a single context ----
+		// Size before resizing.
+		this.initialSize = {
+			x: 0,
+			y: 0
+		};
+
+		// Position of a clicked resize handler in x and y axes.
+		this.direction = {
+			y: 'top',
+			x: 'left'
+		};
+
+		// Reference point of resizer where the dragging started. It is used to measure the distance to user cursor
+		// traveled, thus how much the image should be enlarged.
+		// This information is only known after DOM was rendered, so it will be updated later.
+		this.referenceCoordinates = {
+			y: 0,
+			x: 0
+		};
+	}
+
+	/**
+	 *
+	 * @param {module:engine/view/element~Element} widgetElement Widget's wrapper.
+	 * @param {module:engine/view/downcastwriter~DowncastWriter} writer
+	 */
+	attach( widgetElement, writer ) {
+		const that = this;
+
+		this.widgetWrapperElement = widgetElement;
+
+		this.resizeWrapperElement = writer.createUIElement( 'div', {
+			class: 'ck ck-widget__resizer-wrapper'
+		}, function( domDocument ) {
+			const domElement = this.toDomElement( domDocument );
+
+			that.domResizeShadow = that._appendShadowElement( domDocument, domElement );
+			that._appendResizers( domElement );
+
+			that.domResizeWrapper = domElement;
+
+			return domElement;
+		} );
+
+		// Append resizer wrapper to the widget's wrapper.
+		writer.insert( writer.createPositionAt( widgetElement, widgetElement.childCount ), this.resizeWrapperElement );
+		writer.addClass( [ 'ck-widget_with-resizer' ], widgetElement );
+	}
+
+	/**
+	 *
+	 * @param {HTMLElement} domResizeHandler Handler used to calculate reference point.
+	 */
+	begin( domResizeHandler ) {
+		this.domResizeShadow.classList.add( 'ck-widget__resizer-shadow-active' );
+
+		this.referenceCoordinates = getAbsolutePosition( domResizeHandler );
+	}
+
+	commit( editor ) {
+		const modelEntry = this._getModel( editor, this.widgetWrapperElement );
+		const newHeight = this.domResizeShadow.clientHeight;
+
+		this._dismissShadow();
+
+		editor.model.change( writer => {
+			writer.setAttribute( HEIGHT_ATTRIBUTE_NAME, newHeight, modelEntry );
+		} );
+	}
+
+	cancel() {
+		this._dismissShadow();
+	}
+
+	destroy() {
+		this.cancel();
+
+		this.domResizeShadow = null;
+		this.wrapper = null;
+	}
+
+	updateSize( domEventData ) {
+		const currentCoordinates = this._extractCoordinates( domEventData );
+		const yDistance = this.referenceCoordinates.y - currentCoordinates.y;
+
+		// For top, left handler:
+		// yDistance > 0 - element is enlarged
+		// yDistance < 0 - element is shrinked
+
+		if ( yDistance > 0 ) {
+			// console.log( 'enlarging' );
+			this.domResizeShadow.style.top = ( yDistance * -1 ) + 'px';
+		} else {
+			// console.log( 'shrinking' );
+			this.domResizeShadow.style.top = ( yDistance * -1 ) + 'px';
+		}
+	}
+
+	redraw() {
+		if ( this.domResizeWrapper ) {
+			const resizingHost = this.domResizeWrapper.parentElement.querySelector( 'img' );
+
+			this.domResizeWrapper.style.left = resizingHost.offsetLeft + 'px';
+			this.domResizeWrapper.style.right = resizingHost.offsetLeft + 'px';
+		}
+	}
+
+	_appendShadowElement( domDocument, domElement ) {
+		const shadowElement = domDocument.createElement( 'div' );
+		shadowElement.setAttribute( 'class', 'ck ck-widget__resizer-shadow' );
+		domElement.appendChild( shadowElement );
+
+		return shadowElement;
+	}
+
+	_appendResizers( domElement ) {
+		const resizerPositions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
+
+		for ( const currentPosition of resizerPositions ) {
+			// Use the IconView from the UI library.
+			const icon = new IconView();
+			icon.set( 'content', dragHandlerIcon );
+			icon.extendTemplate( {
+				attributes: {
+					'class': `ck-widget__resizer ck-widget__resizer-${ currentPosition }`
+				}
+			} );
+
+			// Make sure icon#element is rendered before passing to appendChild().
+			icon.render();
+
+			domElement.appendChild( icon.element );
+		}
+	}
+
+	_dismissShadow() {
+		this.domResizeShadow.classList.remove( 'ck-widget__resizer-shadow-active' );
+		this.domResizeShadow.removeAttribute( 'style' );
+	}
+
+	/**
+	 *
+	 * @param {module:@ckeditor/ckeditor5-core/src/editor/editor~Editor} editor
+	 * @param {module:@ckeditor/ckeditor5-engine/src/view/element~Element} widgetWrapperElement
+	 * @returns {module:@ckeditor/ckeditor5-engine/src/model/element~Element|undefined}
+	 */
+	_getModel( editor, widgetWrapperElement ) {
+		return editor.editing.mapper.toModelElement( widgetWrapperElement );
+	}
+
+	_extractCoordinates( event ) {
+		return {
+			x: event.domEvent.pageX,
+			y: event.domEvent.pageY
+		};
+	}
+}

+ 40 - 111
packages/ckeditor5-widget/src/widgetresizer.js

@@ -7,104 +7,11 @@
  * @module widget/widget
  */
 
-import WidgetResizeFeature from './widgetresizefeature';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import MouseObserver from './view/mouseobserver';
 import MouseMoveObserver from './view/mousemoveobserver';
-
 import getAncestors from '@ckeditor/ckeditor5-utils/src/dom/getancestors';
-
-/**
- * Returns coordinates of top-left corner of a element, relative to the document's top-left corner.
- *
- * @param {HTMLElement} element
- * @returns {Object} return
- * @returns {Number} return.x
- * @returns {Number} return.y
- */
-function getAbsolutePosition( element ) {
-	const nativeRectangle = element.getBoundingClientRect();
-
-	return {
-		x: nativeRectangle.left + element.ownerDocument.defaultView.scrollX,
-		y: nativeRectangle.top + element.ownerDocument.defaultView.scrollY
-	};
-}
-
-class ResizeContext {
-	constructor( handler ) {
-		const widgetWrapper = getAncestors( handler ).filter(
-			element => element.classList.contains( 'ck-widget_with-resizer' )
-		)[ 0 ];
-
-		this.widgetWrapper = widgetWrapper;
-		this.shadowWrapper = widgetWrapper.querySelector( '.ck-widget__resizer-shadow' );
-
-		// Reference edge (corner) that should be used to calculate resize difference.
-		this.referenceCoordinates = getAbsolutePosition( handler );
-
-		// Initial height of resizing host / resized element.
-		// @todo: hardcoded img support
-		const resizingHost = widgetWrapper.querySelector( 'img' );
-		this.initialHeight = resizingHost.height;
-
-		// Position of a clicked resize handler in x and y axes.
-		this.direction = {
-			y: 'top',
-			x: 'left'
-		};
-	}
-
-	initialize() {
-		this.shadowWrapper.classList.add( 'ck-widget__resizer-shadow-active' );
-	}
-
-	destroy() {
-		this.shadowWrapper.classList.remove( 'ck-widget__resizer-shadow-active' );
-		this.shadowWrapper.removeAttribute( 'style' );
-
-		this.shadowWrapper = null;
-		this.wrapper = null;
-	}
-
-	updateSize( domEventData ) {
-		const currentCoordinates = this._extractCoordinates( domEventData );
-		const yDistance = this.referenceCoordinates.y - currentCoordinates.y;
-
-		// For top, left handler:
-		// yDistance > 0 - element is enlarged
-		// yDistance < 0 - element is shrinked
-
-		if ( yDistance > 0 ) {
-			// console.log( 'enlarging' );
-			this.shadowWrapper.style.top = ( yDistance * -1 ) + 'px';
-		} else {
-			// console.log( 'shrinking' );
-			this.shadowWrapper.style.top = ( yDistance * -1 ) + 'px';
-		}
-	}
-
-	// Accepts the proposed resize intent.
-	commit() {
-	}
-
-	/**
-	 *
-	 * @param {module:@ckeditor/ckeditor5-core/src/editor/editor~Editor} editor
-	 * @param {module:@ckeditor/ckeditor5-engine/src/view/element~Element} widgetWrapperElement
-	 * @returns {module:@ckeditor/ckeditor5-engine/src/model/element~Element|undefined}
-	 */
-	_getModel( editor, widgetWrapperElement ) {
-		return editor.editing.mapper.toModelElement( widgetWrapperElement );
-	}
-
-	_extractCoordinates( event ) {
-		return {
-			x: event.domEvent.pageX,
-			y: event.domEvent.pageY
-		};
-	}
-}
+import ResizeContext2 from './resizecontext';
 
 /**
  * The base class for widget features. This type provides a common API for reusable features of widgets.
@@ -118,6 +25,9 @@ export default class WidgetResizer extends Plugin {
 	}
 
 	init() {
+		this.contexts = [];
+		this.activeContext = null;
+
 		const view = this.editor.editing.view;
 		const viewDocument = view.document;
 
@@ -131,58 +41,77 @@ export default class WidgetResizer extends Plugin {
 		this._observers.mouseMove.disable();
 
 		let isActive = false;
-		let resizeContext = null;
 
 		// Mouse move observer is only needed when the mouse button is pressed.
 		// this.listenTo( viewDocument, 'mousemove', () => console.log( 'move' ) );
 		this.listenTo( viewDocument, 'mousemove', ( event, domEventData ) => {
-			if ( resizeContext ) {
-				resizeContext.updateSize( domEventData );
+			if ( this.activeContext ) {
+				this.activeContext.updateSize( domEventData );
 			}
 		} );
 
 		this.listenTo( viewDocument, 'mousedown', ( event, domEventData ) => {
 			const target = domEventData.domTarget;
 
-			const resizeHandler = isResizeWrapper( target ) || getAncestors( target ).filter( isResizeWrapper )[ 0 ];
+			const resizeHandler = isResizeHandler( target ) || getAncestors( target ).filter( isResizeHandler )[ 0 ];
 
 			if ( resizeHandler ) {
 				isActive = true;
 				this._observers.mouseMove.enable();
 
-				resizeContext = new ResizeContext( resizeHandler );
-				resizeContext.initialize( domEventData );
+				this.activeContext = this._getContextByHandler( resizeHandler );
+
+				if ( this.activeContext ) {
+					this.activeContext.begin( resizeHandler );
+				}
 			}
 		} );
 
 		const finishResizing = () => {
-			// @todo listen also for mouse up outside of the editable.
 			if ( isActive ) {
 				isActive = false;
 				this._observers.mouseMove.disable();
 
-				resizeContext.commit();
-				resizeContext.destroy();
-				resizeContext = null;
+				if ( this.activeContext ) {
+					this.activeContext.commit( this.editor );
+				}
+
+				this.activeContext = null;
 			}
 		};
 
-		// @todo: it should listen on the entire window.
+		// @todo: it should listen on the entire window, as it should also catch events outside of the editable.
 		this.listenTo( viewDocument, 'mouseup', finishResizing );
 
-		function isResizeWrapper( element ) {
+		function isResizeHandler( element ) {
 			return element.classList && element.classList.contains( 'ck-widget__resizer' );
 		}
 	}
 
 	apply( widgetElement, writer ) {
-		// @todo inline the logic
-		const ret = new WidgetResizeFeature();
+		const context = new ResizeContext2();
+		context.attach( widgetElement, writer );
 
-		const renderRefresh = ret.apply( widgetElement, writer );
+		this.editor.editing.view.once( 'render', () => context.redraw() );
 
-		this.editor.editing.view.once( 'render', renderRefresh );
+		this.contexts.push( context );
+	}
+
+	/**
+	 * Returns a resize context associated with given `domResizeWrapper`.
+	 *
+	 * @param {HTMLElement} domResizeWrapper
+	 */
+	_getContextByWrapper( domResizeWrapper ) {
+		for ( const context of this.contexts ) {
+			if ( domResizeWrapper.isSameNode( context.domResizeWrapper ) ) {
+				return context;
+			}
+		}
+	}
 
-		return ret;
+	_getContextByHandler( domResizeHandler ) {
+		return this._getContextByWrapper( getAncestors( domResizeHandler )
+			.filter( element => element.classList.contains( 'ck-widget__resizer-wrapper' ) )[ 0 ] );
 	}
 }