ember-arcgis-page-layout
Version:
Page Layout infrastructure components
364 lines (331 loc) • 13.6 kB
JavaScript
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);
});
}
});