UNPKG

ember-arcgis-page-layout

Version:

Page Layout infrastructure components

364 lines (331 loc) 13.6 kB
import Ember from 'ember'; export default Ember.Service.extend(Ember.Evented, { isDragging: false, cardComponents: [], rowComponents: [], sectionComponents: [], // Used when a dragging UX is in progress. It serves as a communication hub. draggingProperties: null, draggingInProgress: Ember.computed.notEmpty('draggingProperties'), // Used by dock components to indicate which one is active. Others should disable mouse activities // until this property is null again. activeEventsHandler: null, actionInProgress: Ember.computed.notEmpty('activeEventsHandler'), /** * Delegated mouse event handlers */ setMouseHandlers (moveHandler, upHandler) { Ember.$('html').on('mousemove', moveHandler); Ember.$('html').on('mouseup', upHandler); }, clearMouseHandlers (moveHandler, upHandler) { Ember.$('html').off('mousemove', moveHandler); Ember.$('html').off('mouseup', upHandler); }, /** * Reset the state of the service */ reset () { this.set('cardComponents', []); this.set('rowComponents', []); this.set('sectionComponents', []); this.set('layoutEditor', null); this.set('draggingProperties', null); this.set('activeEventsHandler', null); }, /** * Centralized means to watch mouse movement and ensure * the intent is to DRAG vs just a sloppy click */ checkDrag (event, component) { if (this.get('disableEvents')) { return; } let startPosition = { left: event.originalEvent.clientX + window.pageXOffset, top: event.originalEvent.clientY + window.pageYOffset }; // set a local var so it's accessible inside the closures below let lc = this; // Check if the user is actually dragging the element function mousemove (evt) { let newPosition = { left: evt.originalEvent.clientX + window.pageXOffset, top: evt.originalEvent.clientY + window.pageYOffset }; if (Math.abs(startPosition.left - newPosition.left) < 2 && Math.abs(startPosition.top - newPosition.top) < 2) { startPosition = newPosition; } else { lc.startDragging({ component: component, mousePosition: newPosition }); // inform the component that it has started to be dragged... if (component.onDragStart) { component.onDragStart(); } off(); } return; } function off () { lc.clearMouseHandlers(mousemove, mouseup); } function mouseup () { lc.set('activeEventsHandler', null); off(); } // kick it off lc.setMouseHandlers(mousemove, mouseup); return true; }, mouseMove (event) { if (this.get('draggingProperties')) { let position = { left: event.originalEvent.clientX + window.pageXOffset, top: event.originalEvent.clientY + window.pageYOffset }; // tried to throttle this, but ends up being janky this.updateDragging(position); } else { // console.log('mouseMove but draggingProperties is null'); } }, mouseUp (/* event */) { // if we have draggingProperties... we may need to take some action if (this.get('draggingProperties')) { let dp = this.get('draggingProperties'); this.handleDrop(dp); Ember.$('body').toggleClass('disable-user-select'); } }, startDragging (draggingProperties) { // disable-user-select on body Ember.$('body').toggleClass('disable-user-select'); // set the drag properties this.setProperties({ draggingProperties: Ember.Object.create(draggingProperties) }); // TODO: refactor so we don't need to do this arbitrary copying of properties this.set('draggingProperties.dragType', this.get('draggingProperties.component.dragType')); this.set('draggingProperties.dragAction', this.get('draggingProperties.component.dragAction')); // clear any current mouseHandlers // this.clearMouseHandlers(); // setup our own mouse handlers this.setMouseHandlers(Ember.run.bind(this, this.mouseMove), Ember.run.bind(this, this.mouseUp)); // update our dragging this.updateDragging(); // inform the page-layout-editor that a drag has started... this.get('layoutEditor').dragStarted(); }, /** * Generic fn to locate components in the various collections * based on a position */ getComponentAtPosition (type, position) { // console.log('Position: ' + position.top + ' ' + position.left); let collection = this.get(type + 'Components'); let found = collection.find(function (itm) { let cp = itm.get('componentPosition'); return position.left >= cp.left && position.left <= (cp.left + cp.width) && position.top >= cp.top && position.top <= (cp.top + cp.height); }); return found; }, /** * All the mechanics of interactions when dragging are managed in here */ updateDragging: function (position) { let draggingProperties = this.get('draggingProperties'); if (!draggingProperties && !position) { return; } if (!position) { position = draggingProperties.get('mousePosition'); } else { draggingProperties.set('mousePosition', position); } // hide any controls that may be visible this.trigger('showControls', null); this.trigger('showSectionControls', null); // --------------------- // HIT TESTS // --------------------- // We intersect the mouse position against the cards, rows and sections // CARDS let cardFound = this.getComponentAtPosition('card', position); if (cardFound !== draggingProperties.get('component')) { // dont show drop-targets if we are dragging over the start card... if (this.get('draggingProperties.dragType') === 'card') { this.trigger('showCardDropTargets', cardFound); } this.set('draggingProperties.targetCard', cardFound); } else { this.trigger('showCardDropTargets', null); } // ROWS let rowFound = this.getComponentAtPosition('row', position); if (rowFound) { // Ember.debug('Got hit for row : ' + rowFound.get('elementId')); if (this.get('draggingProperties.dragType') === 'card') { this.trigger('showRowDropTargets', rowFound); } this.set('draggingProperties.targetRow', rowFound); } else { this.trigger('showRowDropTargets', null); } // SECTIONS let sectionFound = this.getComponentAtPosition('section', position); if (sectionFound) { let dragType = this.get('draggingProperties.dragType'); // only show the section targets if we are dragging a section if (dragType === 'section') { this.trigger('showRowDropTargets', sectionFound); } else if (dragType === 'card' && !sectionFound.get('hasRows')) { this.trigger('showEmptySectionDropTarget', sectionFound); } this.set('draggingProperties.targetSection', sectionFound); } else { this.trigger('showSectionDropTargets', null); } // --------------------- }, /** * Handle Dropping */ handleDrop (draggingProperties) { this.trigger('dragEnd', draggingProperties); if (!draggingProperties.dockingTarget) { Ember.debug('No current drop target - do nothing'); // hide the drop targets this.trigger('showCardDropTargets', null); this.trigger('showRowDropTargets', null); this.trigger('showEmptySectionDropTarget', null); // if we are dragging a card, tell it to un-highlight itself if (draggingProperties.component.get('dragType') === 'card') { draggingProperties.component.set('highlight', false); } // nuke the draggingProperties this.set('draggingProperties', null); // turn off our main mousehanderls this.clearMouseHandlers(Ember.run.bind(this, this.mouseMove), Ember.run.bind(this, this.mouseUp)); return; } Ember.debug('----------------------------'); Ember.debug(' DROP EVENT:'); if (draggingProperties.targetCard) { Ember.debug(' TARGET CARD:' + draggingProperties.targetCard.get('elementId')); } else { Ember.debug(' TARGET CARD: null'); } if (draggingProperties.targetRow) { Ember.debug(' TARGET ROW :' + draggingProperties.targetRow.get('elementId')); } else { Ember.debug(' TARGET ROW: null'); } Ember.debug(' TARGET SECTION :' + draggingProperties.targetSection.get('elementId')); Ember.debug(' ----------------------------'); Ember.debug(' DOCKING TARGET:' + draggingProperties.dockingTarget); Ember.debug(' DROP TARGET TYPE:' + draggingProperties.dropTargetType); Ember.debug(' DRAGGED TYPE:' + draggingProperties.component.get('dragType')); Ember.debug(' DRAGGED ACTION:' + draggingProperties.component.get('dragAction')); Ember.debug('----------------------------'); // hide the drop targets this.trigger('showCardDropTargets', null); this.trigger('showRowDropTargets', null); this.trigger('showEmptySectionDropTarget', null); // expand out of the properties into vars we can reason about let dragType = draggingProperties.component.get('dragType'); let dragAction = draggingProperties.component.get('dragAction'); let dropTargetType = draggingProperties.dropTargetType; let targetCard = draggingProperties.targetCard; let targetRow = draggingProperties.targetRow; let targetSection = draggingProperties.targetSection; let dockingTarget = draggingProperties.dockingTarget; // we have to clone on add or move so we don't get // nested refs to the same object let clone; // if we have a card... if (dragType === 'card') { // if ACTION is ADD if (dragAction === 'add') { clone = Ember.copy(draggingProperties.component.get('model.defaults'), true); // and we are DROPPING on a CARD-TARGET... if (dropTargetType === 'card') { targetRow.insertCard(clone, targetCard.get('model'), dockingTarget); } // and we are DROPPING on a ROW-TARGET if (dropTargetType === 'row') { targetSection.insertCardIntoNewRow(clone, targetRow.get('model'), dockingTarget); } // and we are DROPPING on an empty section if (dropTargetType === 'section') { targetSection.insertCardIntoNewRow(clone, null, dockingTarget); } } // if we're moving and we have a valid drop target if (dragAction === 'move' && dropTargetType) { if (targetRow.get('model.cards').length === 1 && targetRow.get('model.cards').includes(draggingProperties.component.get('model'))) { // if the card we are dragging is already in the row we are dragging to // and it is the only one, we do nothing } else { // if we are moving, lets make a clone of the model, then remove it from where it was clone = Ember.copy(draggingProperties.component.get('model'), true); // remove it from it's old row, and resize the remaining cards... // brute-force it because this will not be a large collection let sections = this.get('sectionComponents'); sections.forEach(function (sectionComponent) { let sectionModel = sectionComponent.get('model'); sectionModel.rows.forEach(function (rowModel) { // find the row that contained the card... if (rowModel.cards.indexOf(draggingProperties.component.get('model')) > -1) { targetSection.removeCard(draggingProperties.component.get('model'), rowModel); } }); }); // and we are DROPPING on a CARD-TARGET... if (dropTargetType === 'card') { targetRow.insertCard(clone, targetCard.get('model'), dockingTarget); } // and we are DROPPING on a ROW-TARGET if (dropTargetType === 'row') { targetSection.insertCardIntoNewRow(clone, targetRow.get('model'), dockingTarget); } // and we are DROPPING on a ROW-TARGET if (dropTargetType === 'section') { targetSection.insertCardIntoNewRow(clone, null, dockingTarget); } } } }// dragType===card // Sections are managed at the layoutEditor level // if we have a section... if (dragType === 'section') { if (dragAction === 'add') { clone = Ember.copy(draggingProperties.component.get('model.defaults'), true); this.get('layoutEditor').insertSection(clone, targetSection.get('model'), dockingTarget); } if (dragAction === 'move') { // make a clone clone = Ember.copy(draggingProperties.component.get('model'), true); this.set('layoutEditor.model.sections', this.get('layoutEditor.model.sections').without(draggingProperties.component.get('model'))); // now inject the clone at the correct location this.get('layoutEditor').insertSection(clone, targetSection.get('model'), dockingTarget); } } // we updated the model, gotsta let'em know this.trigger('modelChanged'); // nuke the draggingProperties this.set('draggingProperties', null); // turn off our main mousehanderls this.clearMouseHandlers(Ember.run.bind(this, this.mouseMove), Ember.run.bind(this, this.mouseUp)); }, /** * Clear the .isFooter property for all sections */ clearFooters () { let sections = this.get('sectionComponents'); sections.forEach(function (sectionComponent) { sectionComponent.set('model.isFooter', false); }); } });