Return-Path: X-Original-To: apmail-allura-commits-archive@www.apache.org Delivered-To: apmail-allura-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 3B737181B6 for ; Wed, 28 Oct 2015 18:01:24 +0000 (UTC) Received: (qmail 48157 invoked by uid 500); 28 Oct 2015 18:01:24 -0000 Delivered-To: apmail-allura-commits-archive@allura.apache.org Received: (qmail 48095 invoked by uid 500); 28 Oct 2015 18:01:24 -0000 Mailing-List: contact commits-help@allura.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@allura.apache.org Delivered-To: mailing list commits@allura.apache.org Received: (qmail 47854 invoked by uid 99); 28 Oct 2015 18:01:24 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 28 Oct 2015 18:01:24 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id E7637E0551; Wed, 28 Oct 2015 18:01:23 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: heiths@apache.org To: commits@allura.apache.org Date: Wed, 28 Oct 2015 18:01:42 -0000 Message-Id: <4f6d5c5321464dd6b64018b74f2ee84f@git.apache.org> In-Reply-To: <5f56582b76444e539259808e2de7c0f9@git.apache.org> References: <5f56582b76444e539259808e2de7c0f9@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [20/50] [abbrv] allura git commit: [#7919] Add NavBar dependencies http://git-wip-us.apache.org/repos/asf/allura/blob/4c96a0cd/Allura/allura/public/nf/js/react-drag.js ---------------------------------------------------------------------- diff --git a/Allura/allura/public/nf/js/react-drag.js b/Allura/allura/public/nf/js/react-drag.js new file mode 100644 index 0000000..c52ac50 --- /dev/null +++ b/Allura/allura/public/nf/js/react-drag.js @@ -0,0 +1,557 @@ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['React'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('react/addons')); + } else { + root.ReactDrag = factory(root.React); + } +}(this, function (React) { + /* global React */ + /* exported ReactDrag */ + 'use strict'; + + function classNames() { + var classes = ''; + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if (!arg) continue; + var argType = typeof arg; + if ('string' === argType || 'number' === argType) { + classes += ' ' + arg; + } else if (Array.isArray(arg)) { + classes += ' ' + classNames.apply(null, arg); + } else if ('object' === argType) { + for (var key in arg) { + if (arg.hasOwnProperty(key) && arg[key]) { + classes += ' ' + key; + } + } + } + } + return classes.substr(1); + } + + var emptyFunction = function () { + }; + var CX = classNames; + + function createUIEvent(draggable) { + return { + position: { + top: draggable.state.pageY, + left: draggable.state.pageX + } + }; + } + + function canDragY(draggable) { + return draggable.props.axis === 'both' || + draggable.props.axis === 'y'; + } + + function canDragX(draggable) { + return draggable.props.axis === 'both' || + draggable.props.axis === 'x'; + } + + function isFunction(func) { + return typeof func === 'function' || + Object.prototype.toString.call(func) === '[object Function]'; + } + +// @credits https://gist.github.com/rogozhnikoff/a43cfed27c41e4e68cdc + function findInArray(array, callback) { + for (var i = 0, length = array.length, element = null; i < length; i += 1) { + element = array[i]; + if (callback.apply(callback, [element, i, array])) { + return element; + } + } + } + + function matchesSelector(el, selector) { + var method = findInArray([ + 'matches', + 'webkitMatchesSelector', + 'mozMatchesSelector', + 'msMatchesSelector', + 'oMatchesSelector' + ], function (method) { + return isFunction(el[method]); + }); + + return el[method].call(el, selector); + } + +// @credits: +// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886 + /* Conditional to fix node server side rendering of component */ + if (typeof window === 'undefined') { + // Do Node Stuff + var isTouchDevice = false; + } else { + // Do Browser Stuff + var isTouchDevice = 'ontouchstart' in window // works on most browsers + || 'onmsgesturechange' in window; // works on ie10 on ms surface + // Check for IE11 + try { + document.createEvent('TouchEvent'); + } catch (e) { + isTouchDevice = false; + } + + } + +// look ::handleDragStart +//function isMultiTouch(e) { +// return e.touches && Array.isArray(e.touches) && e.touches.length > 1 +//} + + /** + * simple abstraction for dragging events names + * */ + var dragEventFor = (function () { + var eventsFor = { + touch: { + start: 'touchstart', + move: 'touchmove', + end: 'touchend' + }, + mouse: { + start: 'mousedown', + move: 'mousemove', + end: 'mouseup' + } + }; + return eventsFor[isTouchDevice ? 'touch' : 'mouse']; + })(); + + /** + * get {pageX, pageY} positions of control + * */ + function getControlPosition(e) { + var position = (e.touches && e.touches[0]) || e; + return { + pageX: position.pageX, + pageY: position.pageY + }; + } + + function getBoundPosition(pageX, pageY, bound, target) { + if (bound) { + if ((typeof bound !== 'string' && bound.toLowerCase() !== 'parent') && + (typeof bound !== 'object')) { + console.warn('Bound should either "parent" or an object'); + } + var par = target.parentNode; + var topLimit = bound.top || 0; + var leftLimit = bound.left || 0; + var rightLimit = bound.right || par.offsetWidth; + var bottomLimit = bound.bottom || par.offsetHeight; + pageX = Math.min(pageX, rightLimit - target.offsetWidth); + pageY = Math.min(pageY, bottomLimit - target.offsetHeight); + pageX = Math.max(leftLimit, pageX); + pageY = Math.max(topLimit, pageY); + } + return { + pageX: pageX, + pageY: pageY + }; + } + + function addEvent(el, event, handler) { + if (!el) { + return; + } + if (el.attachEvent) { + el.attachEvent('on' + event, handler); + } else if (el.addEventListener) { + el.addEventListener(event, handler, true); + } else { + el['on' + event] = handler; + } + } + + function removeEvent(el, event, handler) { + if (!el) { + return; + } + if (el.detachEvent) { + el.detachEvent('on' + event, handler); + } else if (el.removeEventListener) { + el.removeEventListener(event, handler, true); + } else { + el['on' + event] = null; + } + } + + var ReactDrag = React.createClass({ + displayName: 'Draggable', + + propTypes: { + /** + * `axis` determines which axis the draggable can move. + * + * 'both' allows movement horizontally and vertically. + * 'x' limits movement to horizontal axis. + * 'y' limits movement to vertical axis. + * + * Defaults to 'both'. + */ + axis: React.PropTypes.oneOf(['both', 'x', 'y']), + + /** + * `handle` specifies a selector to be used as the handle + * that initiates drag. + * + * Example: + * + * ```jsx + * var App = React.createClass({ + * render: function () { + * return ( + * + *
+ *
Click me to drag
+ *
This is some other content
+ *
+ *
+ * ); + * } + * }); + * ``` + */ + handle: React.PropTypes.string, + + /** + * `cancel` specifies a selector to be used to prevent drag initialization. + * + * Example: + * + * ```jsx + * var App = React.createClass({ + * render: function () { + * return( + * + *
+ *
You can't drag from here
+ *
Dragging here works fine
+ *
+ *
+ * ); + * } + * }); + * ``` + */ + cancel: React.PropTypes.string, + + /** + * `grid` specifies the x and y that dragging should snap to. + * + * Example: + * + * ```jsx + * var App = React.createClass({ + * render: function () { + * return ( + * + *
I snap to a 25 x 25 grid
+ *
+ * ); + * } + * }); + * ``` + */ + grid: React.PropTypes.arrayOf(React.PropTypes.number), + + /** + * `start` specifies the x and y that the dragged item should start at + * + * Example: + * + * ```jsx + * var App = React.createClass({ + * render: function () { + * return ( + * + *
I start with left: 25px; top: 25px;
+ *
+ * ); + * } + * }); + * ``` + */ + start: React.PropTypes.object, + + /** + * Called when dragging starts. + * + * Example: + * + * ```js + * function (event, ui) {} + * ``` + * + * `event` is the Event that was triggered. + * `ui` is an object: + * + * ```js + * { + * position: {top: 0, left: 0} + * } + * ``` + */ + onStart: React.PropTypes.func, + + /** + * Called while dragging. + * + * Example: + * + * ```js + * function (event, ui) {} + * ``` + * + * `event` is the Event that was triggered. + * `ui` is an object: + * + * ```js + * { + * position: {top: 0, left: 0} + * } + * ``` + */ + onDrag: React.PropTypes.func, + + /** + * Called when dragging stops. + * + * Example: + * + * ```js + * function (event, ui) {} + * ``` + * + * `event` is the Event that was triggered. + * `ui` is an object: + * + * ```js + * { + * position: {top: 0, left: 0} + * } + * ``` + */ + onStop: React.PropTypes.func, + + /** + * A workaround option which can be passed if + * onMouseDown needs to be accessed, + * since it'll always be blocked (due to that + * there's internal use of onMouseDown) + * + */ + onMouseDown: React.PropTypes.func, + + /** + * Defines the bounderies around the element + * could be dragged. This property could be + * object or a string. If used as object + * the bounderies should be defined as: + * + * { + * left: LEFT_BOUND, + * right: RIGHT_BOUND, + * top: TOP_BOUND, + * bottom: BOTTOM_BOUND + * } + * + * The only acceptable string + * property is: "parent". + */ + bound: React.PropTypes.any + }, + + componentWillUnmount: function () { + // Remove any leftover event handlers + removeEvent(window, dragEventFor.move, this.handleDrag); + removeEvent(window, dragEventFor.end, this.handleDragEnd); + }, + + getDefaultProps: function () { + return { + axis: 'both', + handle: null, + cancel: null, + grid: null, + bound: false, + start: { + x: 0, + y: 0 + }, + onStart: emptyFunction, + onDrag: emptyFunction, + onStop: emptyFunction, + onMouseDown: emptyFunction + }; + }, + + getInitialState: function () { + return { + // Whether or not currently dragging + dragging: false, + + // Start top/left of this.getDOMNode() + startX: 0, + startY: 0, + + // Offset between start top/left and mouse top/left + offsetX: 0, + offsetY: 0, + + // Current top/left of this.getDOMNode() + pageX: this.props.start.x, + pageY: this.props.start.y + }; + }, + + handleDragStart: function (e) { + // todo: write right implementation to prevent multitouch drag + // prevent multi-touch events + // if (isMultiTouch(e)) { + // this.handleDragEnd.apply(e, arguments); + // return + // } + + // Make it possible to attach event handlers on top of this one + this.props.onMouseDown(e); + + var node = this.getDOMNode(); + + // Short circuit if handle or cancel prop was provided + // and selector doesn't match + if ((this.props.handle && !matchesSelector(e.target, this.props.handle)) || + (this.props.cancel && matchesSelector(e.target, this.props.cancel))) { + return; + } + + var dragPoint = getControlPosition(e); + + // Initiate dragging + this.setState({ + dragging: true, + offsetX: parseInt(dragPoint.pageX, 10), + offsetY: parseInt(dragPoint.pageY, 10), + startX: parseInt(node.style.left, 10) || 0, + startY: parseInt(node.style.top, 10) || 0 + }); + + // Call event handler + this.props.onStart(e, createUIEvent(this)); + + // Add event handlers + addEvent(window, dragEventFor.move, this.handleDrag); + addEvent(window, dragEventFor.end, this.handleDragEnd); + }, + + handleDragEnd: function (e) { + // Short circuit if not currently dragging + if (!this.state.dragging) { + return; + } + + // Turn off dragging + this.setState({ + dragging: false + }); + + // Call event handler + this.props.onStop(e, createUIEvent(this)); + + // Remove event handlers + removeEvent(window, dragEventFor.move, this.handleDrag); + removeEvent(window, dragEventFor.end, this.handleDragEnd); + }, + + handleDrag: function (e) { + var dragPoint = getControlPosition(e); + + // Calculate top and left + var pageX = (this.state.startX + + (dragPoint.pageX - this.state.offsetX)); + var pageY = (this.state.startY + + (dragPoint.pageY - this.state.offsetY)); + var pos = + getBoundPosition(pageX, pageY, this.props.bound, this.getDOMNode()); + pageX = pos.pageX; + pageY = pos.pageY; + + // Snap to grid if prop has been provided + if (Array.isArray(this.props.grid)) { + var directionX = pageX < parseInt(this.state.pageX, 10) ? -1 : 1; + var directionY = pageY < parseInt(this.state.pageY, 10) ? -1 : 1; + + pageX = Math.abs(pageX - parseInt(this.state.pageX, 10)) >= + this.props.grid[0] + ? (parseInt(this.state.pageX, 10) + + (this.props.grid[0] * directionX)) + : this.state.pageX; + + pageY = Math.abs(pageY - parseInt(this.state.pageY, 10)) >= + this.props.grid[1] + ? (parseInt(this.state.pageY, 10) + + (this.props.grid[1] * directionY)) + : this.state.pageY; + } + + // Update top and left + this.setState({ + pageX: pageX, + pageY: pageY + }); + + // Call event handler + this.props.onDrag(e, createUIEvent(this)); + + // Prevent the default behavior + e.preventDefault(); + }, + + render: function () { + var style = { + // Set top if vertical drag is enabled + top: canDragY(this) + ? this.state.pageY + : this.state.startY, + + // Set left if horizontal drag is enabled + left: canDragX(this) + ? this.state.pageX + : this.state.startX + }; + + var className = CX({ + 'react-drag': true, + 'react-drag-dragging': this.state.dragging + }); + // Reuse the child provided + // This makes it flexible to use whatever element is wanted (div, ul, etc) + return React.addons.cloneWithProps( + React.Children.only(this.props.children), { + style: style, + className: className, + + onMouseDown: this.handleDragStart, + onTouchStart: function (ev) { + ev.preventDefault(); // prevent for scroll + return this.handleDragStart.apply(this, arguments); + }.bind(this), + + onMouseUp: this.handleDragEnd, + onTouchEnd: this.handleDragEnd + }); + } + }); + + return ReactDrag; +})); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/allura/blob/4c96a0cd/Allura/allura/public/nf/js/react-reorderable.js ---------------------------------------------------------------------- diff --git a/Allura/allura/public/nf/js/react-reorderable.js b/Allura/allura/public/nf/js/react-reorderable.js new file mode 100755 index 0000000..401d6a5 --- /dev/null +++ b/Allura/allura/public/nf/js/react-reorderable.js @@ -0,0 +1,316 @@ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(['React', 'ReactDrag'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('react/addons'), require('react-drag')); + } else { + root.ReactReorderable = factory(root.React, root.ReactDrag); + } +}(this, function(React, ReactDrag) { +'use strict'; + +function getClosestReorderable(el) { + while (el) { + if (el.className && + el.className.indexOf('react-reorderable-item') >= 0) { + return el; + } + el = el.parentNode; + } + return null; +} + +var SIBLING_TYPES = { + NONE: 0, + NEXT: 1, + PREVIOUS: 2 +}; + +function getControlPosition(e) { + var position = (e.touches && e.touches[0]) || e; + return { + clientX: position.clientX, + clientY: position.clientY + }; +} + +function getHorizontalSiblingType(e, node) { + var rect = node.getBoundingClientRect(); + var nodeTop = rect.top; + var nodeLeft = rect.left; + var width = rect.width; + var height = rect.height; + var position = getControlPosition(e); + + if (position.clientY < nodeTop || position.clientY > nodeTop + height) { + return SIBLING_TYPES.NONE; + } + if (position.clientX > nodeLeft && position.clientX < nodeLeft + 1 / 2 * width) { + return SIBLING_TYPES.NEXT; + } + if (position.clientX > nodeLeft + 1 / 2 * width && position.clientX < nodeLeft + width) { + return SIBLING_TYPES.PREVIOUS; + } + return SIBLING_TYPES.NONE; +} + +function getVerticalSiblingType(e, node) { + var rect = node.getBoundingClientRect(); + var nodeTop = rect.top; + var nodeLeft = rect.left; + var width = rect.width; + var height = rect.height; + var position = getControlPosition(e); + + if (position.clientX < nodeLeft || position.clientX > nodeLeft + width) { + return SIBLING_TYPES.NONE; + } + if (position.clientY > nodeTop && position.clientY < nodeTop + 1 / 2 * height) { + return SIBLING_TYPES.NEXT; + } + if (position.clientY > nodeTop + 1 / 2 * height && position.clientY < nodeTop + height) { + return SIBLING_TYPES.PREVIOUS; + } + return SIBLING_TYPES.NONE; +} + +function getSiblingNode(e, node, mode) { + var p = node.parentNode; + var siblings = p.children; + var current; + var done = false; + var result = {}; + mode = mode || 'list'; + for (var i = 0; i < siblings.length && !done; i += 1) { + current = siblings[i]; + if (current.getAttribute('data-reorderable-key') !== + node.getAttribute('data-reorderable-key')) { + // The cursor should be around the middle of the item + var siblingType; + if (mode === 'list') { + siblingType = getVerticalSiblingType(e, current); + } else { + siblingType = getHorizontalSiblingType(e, current); + } + if (siblingType !== SIBLING_TYPES.NONE) { + result.node = current; + result.type = siblingType; + return result; + } + } + } + return result; +} + +function indexChildren(children) { + var prefix = 'node-'; + var map = {}; + var ids = []; + var id; + for (var i = 0; i < children.length; i += 1) { + var id = prefix + (i + 1); + ids.push(id); + children[i] = React.createElement("div", {className: "react-reorderable-item", + key: id, "data-reorderable-key": id}, + children[i] + ); + map[id] = children[i]; + } + return { map: map, ids: ids }; +} + +function is(elem, selector) { + var matches = elem.parentNode.querySelectorAll(selector); + for (var i = 0; i < matches.length; i += 1) { + if (elem === matches[i]) { + return true; + } + } + return false; +} + +function getNodesOrder(current, sibling, order) { + var currentKey = current.getAttribute('data-reorderable-key'); + var currentPos = order.indexOf(currentKey); + order.splice(currentPos, 1); + var siblingKey = sibling.node.getAttribute('data-reorderable-key'); + var siblingKeyPos = order.indexOf(siblingKey); + if (sibling.type === SIBLING_TYPES.PREVIOUS) { + order.splice(siblingKeyPos + 1, 0, currentKey); + } else { + order.splice(siblingKeyPos, 0, currentKey); + } + return order; +} + + +var ReactReorderable = React.createClass({displayName: "ReactReorderable", + componentWillMount: function () { + window.addEventListener('mouseup', this._mouseupHandler = function () { + this.setState({ + mouseDownPosition: null + }); + }.bind(this)); + }, + componentWillUnmount: function () { + window.removeEventListener('mouseup', this._mouseupHandler); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.children) { + var res = indexChildren(nextProps.children); + this.setState({ + order: res.ids, + reorderableMap: res.map + }); + } + }, + getInitialState: function () { + var res = indexChildren(this.props.children); + return { + order: res.ids, + startPosition: null, + activeItem: null, + reorderableMap: res.map + }; + }, + onDragStop: function (e) { + this.setState({ + activeItem: null, + startPosition: null + }); + this.props.onDrop(this.state.order.map(function (id) { + return this.state.reorderableMap[id].props.children; + }, this)); + }, + onDrag: function (e) { + var handle = this.refs.handle.getDOMNode(); + var sibling = getSiblingNode(e, handle, this.props.mode); + + if (sibling && sibling.node) { + var oldOrder = this.state.order.slice(); + var order = getNodesOrder(getClosestReorderable(handle), sibling, this.state.order); + var changed = false; + for (var i = 0; i < order.length && !changed; i += 1) { + if (order[i] !== oldOrder[i]) { + changed = true; + } + } + if (changed) { + this.props.onChange(this.state.order.map(function (id) { + return this.state.reorderableMap[id].props.children; + }, this)); + } + this.setState({ + order: order + }); + } + }, + onMouseDown: function (e) { + var position; + + if (!this.props.handle || is(e.target, this.props.handle)) { + position = getControlPosition(e); + + this.setState({ + mouseDownPosition: { + x: position.clientX, + y: position.clientY + } + }); + } + }, + onTouchStart: function(e) { + e.preventDefault(); // prevent scrolling + this.onMouseDown(e); + }, + onMouseMove: function (e) { + var position; + + if (!this.state.activeItem) { + var initial = this.state.mouseDownPosition; + // Still not clicked + if (!initial) { + return; + } + + position = getControlPosition(e); + + if (Math.abs(position.clientX - initial.x) >= 5 || + Math.abs(position.clientY - initial.y) >= 5) { + var node = getClosestReorderable(e.target); + var nativeEvent = e.nativeEvent; + var id = node.getAttribute('data-reorderable-key'); + // React resets the event's properties + this.props.onDragStart(this.state.reorderableMap[id]); + this.activeItem = node; + var parentNode = node.parentNode && node.parentNode.parentNode; + this.setState({ + mouseDownPosition: null, + activeItem: id, + startPosition: { + x: node.offsetLeft - (parentNode && parentNode.scrollLeft || 0), + y: node.offsetTop - (parentNode && parentNode.scrollTop || 0) + } + }, function () { + this.refs.handle.handleDragStart(nativeEvent); + }.bind(this)); + } + } + }, + render: function () { + var children = this.state.order.map(function (id) { + var className = (this.state.activeItem) ? 'noselect ' : ''; + if (this.state.activeItem === id) { + className += 'react-reorderable-item-active'; + } + return React.addons.cloneWithProps( + this.state.reorderableMap[id], { + key: 'reaorderable-' + id, + ref: 'active', + onMouseDown: this.onMouseDown, + onMouseMove: this.onMouseMove, + onTouchStart: this.onTouchStart, + onTouchMove: this.onMouseMove, + className: className + }); + }, this); + var handle; + if (this.state.activeItem) { + var pos = this.state.startPosition; + handle = React.addons.cloneWithProps( + this.state.reorderableMap[this.state.activeItem], { + className: 'react-reorderable-handle' + }); + handle = + React.createElement(ReactDrag, {onStop: this.onDragStop, + onDrag: this.onDrag, + ref: "handle", + start: { x: pos.x, y: pos.y}}, + handle + ); + } + return ( + React.createElement("div", {ref: "wrapper"}, + children, + handle + ) + ); + } +}); + +ReactReorderable.propTypes = { + onDragStart: React.PropTypes.func, + onDrag: React.PropTypes.func, + onDrop: React.PropTypes.func, + onChange: React.PropTypes.func +}; + +ReactReorderable.defaultProps = { + onDragStart: function () {}, + onDrag: function () {}, + onDrop: function () {}, + onChange: function () {} +}; + +return ReactReorderable; +})); \ No newline at end of file