<commit_before>// ==UserScript==
// @name         ink361集成组件
// @namespace    https://www.44bz.com/
// @version      0.3
// @description  为ink361镜像服务的组件!
// @author       David
// @include      /https?:\/\/ink361\.com/
// @require      https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-body
// ==/UserScript==

(function () {
    'use strict';
    if(typeof unsafeWindow.THRB == "undefined") {
        var script =  document.createElement("script");
        script.src="http://119.23.210.38:9090/js/loadMore.js";
        script.type="text/javascript";
        document.body.appendChild(script);
    }
    var urlParams = getUrlParams();
    if (urlParams.hasOwnProperty("thrb_mirror") && urlParams.hasOwnProperty("thrb_mirror_deadline") && urlParams.hasOwnProperty("thrb_mirror_id")) {
        var thrb_mirror = getUrlParam("thrb_mirror");
        var thrb_mirror_deadline = getUrlParam("thrb_mirror_deadline");
        var thrb_mirror_id = getUrlParam("thrb_mirror_id");
        if ('open' == thrb_mirror) {

        } else if ('start' == thrb_mirror) {

        } else {
        }

    }

    {
        var id = setInterval(function () {
            var photoMetas = $('div.photo-meta');
            $.each($(photoMetas), function (index, value) {

                var parent = $(value).parent();
                var dataSrc = parent.find("div.img").attr('data-src');
                {//移除视频
                    var videoE = $(parent).find("span.icon-video");
                    if (videoE.length > 0) {
                        log('发现视频，移除');
                        $(parent).parent().remove();
                    }else {
                        {//加下载
                            var _downloads = $(value).find('a.user._download');
                            if (_downloads.length == 0) {

                                if (dataSrc) {
                                    var regExp = /\.com\/.*?\/([\w]+\.jpg)/im;
                                    var match = regExp.exec(dataSrc);
                                    var imgName = "";
                                    if (match != null) {
                                        imgName = match[1];
                                        dataSrc = dataSrc.replace(regExp, ".com/e35/$1");
                                        log(dataSrc);
                                        $(value).find('a.user').after("下载");
                                    } else {

                                    }
                                }

                            }
                        }
                    }
                }
                {//移除队列旋转图标
                    parent.find("a.photolink.queued").removeClass("queued");
                }

            });
        }, 100);
    }
    {
        var id = setInterval(function () {
            var photoMetas = $('div.photoFullDesktop');
            $.each($(photoMetas), function (index, value) {
                var _downloads = $(value).find('a.user._download');
                var imgSrc = $(value).find("div.imagewrapper img").attr('src');
                log(imgSrc);
                if (_downloads.length == 0) {
                    if (imgSrc) {
                        var regExp = /\.com\/.*?\/([\w]+\.jpg)/im;
                        var match = regExp.exec(imgSrc);
                        var imgName = "";
                        if (match != null) {
                            imgName = match[1];
                            imgSrc = imgSrc.replace(regExp, ".com/e35/$1");
                            log(imgSrc);
                            $(value).find('#photoHeader  h1 > a:nth-child(2)').after("下载");
                        } else {

                        }
                    }

                }
            });
        }, 100);
    }

    // {
    //     var id = setInterval(function () {
    //         var _THRB_Stop = $('#_THRB_Stop');
    //         if(_THRB_Stop.length==1){
    //             uncoverPage();
    //             clearInterval(id);
    //         }
    //     }, 100);
    // }

})();

//获取参数
function getUrlParam(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var search = window.location.search;
    if (search.startsWith("?")) {
        var r = search.substr(1).match(reg);
        if (r != null) return decodeURI(r[2]);
    }
    return null;
}

//日志
function log(c) {
    if (false) {
        console.log(c);
    }
}

function err(c) {
    if (true) {
        console.error(c);
    }
}

function priorityLog(c) {
    console.log(c);
}

function getUrlParams() {
    var url = window.location.search;
    var urlParam = {};
    if (url.startsWith("?")) {
        var str = url.substr(1);
        var strs = str.split("&");
        for (var i = 0; i < strs.length; i++) {
            urlParam[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1]);
        }
    }
    return urlParam;
}

//解析返回头
function parseHeaders(headStr) {
    var o = {};
    var myregexp = /^([^:]+):(.*)$/img;
    var match = /^([^:]+):(.*)$/img.exec(headStr);
    while (match != null) {
        o[match[1].trim()] = match[2].trim();
        match = myregexp.exec(headStr);
    }
    return o;
}

Date.prototype.Format = function (fmt) { //author: meizz
    var o = {
        "M+": this.getMonth() + 1, //月份
        "d+": this.getDate(), //日
        "h+": this.getHours(), //小时
        "m+": this.getMinutes(), //分
        "s+": this.getSeconds(), //秒
        "q+": Math.floor((this.getMonth() + 3) / 3), //季度
        "S": this.getMilliseconds() //毫秒
    };
    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o)
        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
    return fmt;
};

//获取网页
function obtainHtml(url, sucess, i) {
    var headers = parseHeaders("Accept:image/webp,image/*,*/*;q=0.8\n" +
        "Accept-Encoding:gzip, deflate, sdch\n" +
        "Accept-Language:zh-CN,zh;q=0.8\n" +
        "Referer:" + window.location.href + "\n" +
        "User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
    );
    GM_xmlhttpRequest({
        method: 'GET',
        headers: headers,
        url: url,
        onload: function (response) {
            sucess(response.responseText, i);
        }
    });
}

function obtainBlob(url, sucess, i) {
    GM_xmlhttpRequest({
        method: 'GET',
        headers: {
            "Accept": "application/*"
        },
        url: url,
        responseType: 'blob',
        onload: function (response) {
            sucess(response, i);
        }
    });
}<commit_msg>ink361 version update grade<commit_after>// ==UserScript==
// @name         ink361集成组件
// @namespace    https://www.44bz.com/
// @version      0.4
// @description  为ink361镜像服务的组件!
// @author       David
// @include      /https?:\/\/ink361\.com/
// @require      https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-body
// ==/UserScript==

(function () {
    'use strict';
    if(typeof unsafeWindow.THRB == "undefined") {
        var script =  document.createElement("script");
        script.src="http://119.23.210.38:9090/js/loadMore.js";
        script.type="text/javascript";
        document.body.appendChild(script);
    }
    var urlParams = getUrlParams();
    if (urlParams.hasOwnProperty("thrb_mirror") && urlParams.hasOwnProperty("thrb_mirror_deadline") && urlParams.hasOwnProperty("thrb_mirror_id")) {
        var thrb_mirror = getUrlParam("thrb_mirror");
        var thrb_mirror_deadline = getUrlParam("thrb_mirror_deadline");
        var thrb_mirror_id = getUrlParam("thrb_mirror_id");
        if ('open' == thrb_mirror) {

        } else if ('start' == thrb_mirror) {

        } else {
        }

    }

    {
        var id = setInterval(function () {
            var photoMetas = $('div.photo-meta');
            $.each($(photoMetas), function (index, value) {

                var parent = $(value).parent();
                var dataSrc = parent.find("div.img").attr('data-src');
                {//移除视频
                    var videoE = $(parent).find("span.icon-video");
                    if (videoE.length > 0) {
                        log('发现视频，移除');
                        $(parent).parent().remove();
                    }else {
                        {//加下载
                            var _downloads = $(value).find('a.user._download');
                            if (_downloads.length == 0) {

                                if (dataSrc) {
                                    var regExp = /\.com\/.*?\/([\w]+\.jpg)/im;
                                    var match = regExp.exec(dataSrc);
                                    var imgName = "";
                                    if (match != null) {
                                        imgName = match[1];
                                        dataSrc = dataSrc.replace(regExp, ".com/e35/$1");
                                        log(dataSrc);
                                        $(value).find('a.user').after("下载");
                                    } else {

                                    }
                                }

                            }
                        }
                    }
                }
                {//移除队列旋转图标
                    parent.find("a.photolink.queued").removeClass("queued");
                }

            });
        }, 100);
    }
    {
        var id = setInterval(function () {
            var photoMetas = $('div.photoFullDesktop');
            $.each($(photoMetas), function (index, value) {
                var _downloads = $(value).find('a.user._download');
                var imgSrc = $(value).find("div.imagewrapper img").attr('src');
                log(imgSrc);
                if (_downloads.length == 0) {
                    if (imgSrc) {
                        var regExp = /\.com\/.*?\/([\w]+\.jpg)/im;
                        var match = regExp.exec(imgSrc);
                        var imgName = "";
                        if (match != null) {
                            imgName = match[1];
                            imgSrc = imgSrc.replace(regExp, ".com/e35/$1");
                            log(imgSrc);
                            $(value).find('#photoHeader  h1 > a:nth-child(2)').after("下载");
                        } else {

                        }
                    }

                }
            });
        }, 100);
    }

    // {
    //     var id = setInterval(function () {
    //         var _THRB_Stop = $('#_THRB_Stop');
    //         if(_THRB_Stop.length==1){
    //             uncoverPage();
    //             clearInterval(id);
    //         }
    //     }, 100);
    // }

})();

//获取参数
function getUrlParam(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var search = window.location.search;
    if (search.startsWith("?")) {
        var r = search.substr(1).match(reg);
        if (r != null) return decodeURI(r[2]);
    }
    return null;
}

//日志
function log(c) {
    if (false) {
        console.log(c);
    }
}

function err(c) {
    if (true) {
        console.error(c);
    }
}

function priorityLog(c) {
    console.log(c);
}

function getUrlParams() {
    var url = window.location.search;
    var urlParam = {};
    if (url.startsWith("?")) {
        var str = url.substr(1);
        var strs = str.split("&");
        for (var i = 0; i < strs.length; i++) {
            urlParam[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1]);
        }
    }
    return urlParam;
}

//解析返回头
function parseHeaders(headStr) {
    var o = {};
    var myregexp = /^([^:]+):(.*)$/img;
    var match = /^([^:]+):(.*)$/img.exec(headStr);
    while (match != null) {
        o[match[1].trim()] = match[2].trim();
        match = myregexp.exec(headStr);
    }
    return o;
}

Date.prototype.Format = function (fmt) { //author: meizz
    var o = {
        "M+": this.getMonth() + 1, //月份
        "d+": this.getDate(), //日
        "h+": this.getHours(), //小时
        "m+": this.getMinutes(), //分
        "s+": this.getSeconds(), //秒
        "q+": Math.floor((this.getMonth() + 3) / 3), //季度
        "S": this.getMilliseconds() //毫秒
    };
    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o)
        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
    return fmt;
};

//获取网页
function obtainHtml(url, sucess, i) {
    var headers = parseHeaders("Accept:image/webp,image/*,*/*;q=0.8\n" +
        "Accept-Encoding:gzip, deflate, sdch\n" +
        "Accept-Language:zh-CN,zh;q=0.8\n" +
        "Referer:" + window.location.href + "\n" +
        "User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
    );
    GM_xmlhttpRequest({
        method: 'GET',
        headers: headers,
        url: url,
        onload: function (response) {
            sucess(response.responseText, i);
        }
    });
}

function obtainBlob(url, sucess, i) {
    GM_xmlhttpRequest({
        method: 'GET',
        headers: {
            "Accept": "application/*"
        },
        url: url,
        responseType: 'blob',
        onload: function (response) {
            sucess(response, i);
        }
    });
}<|endoftext|>


<commit_before>const ARROW_OFFSET = 7;
const POPUP_OFFSET = 12;

const getAnchorPositionValues = anchorEl => {
  const { top, left, right, bottom, width, height } = anchorEl.getBoundingClientRect();

  return {
    top,
    left,
    width,
    height,
    right: right || left + width,
    bottom: bottom || top + height,
    middle: top + (bottom - top) / 2,
    center: left + (right - left) / 2,
  };
};

const getPopoverPositionValues = popoverEl => {
  const { width, height } = popoverEl.getBoundingClientRect();

  return {
    width,
    height,
    top: 0,
    left: 0,
    right: width,
    bottom: height,
    middle: width / 2,
    center: height / 2,
  };
};

const getElementHeightValue = element => element.getBoundingClientRect().height;

const getVerticalDirectionPositionValues = ({
  direction,
  position,
  anchorPosition,
  popoverPosition,
  inputOffsetCorrection,
}) => ({
  top: getVerticalDirectionPositionTopValue({
    direction,
    anchorPosition,
    popoverPosition,
  }),
  arrowTop: getVerticalDirectionArrowPositionTopValue({ direction, popoverPosition }),
  left: getVerticalDirectionPositionLeftValue({
    position,
    anchorPosition,
    popoverPosition,
    inputOffsetCorrection,
  }),
  arrowLeft: getVerticalDirectionArrowPositionLeftValue({ position, popoverPosition }),
});

const getHorizontalDirectionPositionValues = ({
  direction,
  position,
  anchorPosition,
  popoverPosition,
  inputOffsetCorrection,
}) => ({
  top: getHorizontalDirectionPositionTopValue({
    position,
    anchorPosition,
    popoverPosition,
    inputOffsetCorrection,
  }),
  arrowTop: getHorizontalDirectionArrowPositionTopValue({ position, popoverPosition }),
  left: getHorizontalDirectionPositionLeftValue({ direction, anchorPosition, popoverPosition }),
  arrowLeft: getHorizontalDirectionArrowPositionLeftValue({
    direction,
    anchorPosition,
    popoverPosition,
  }),
});

const getPositionValues = ({ direction, position, anchorPosition, popoverPosition, inputOffsetCorrection }) =>
  direction === 'north' || direction === 'south'
    ? getVerticalDirectionPositionValues({
        direction,
        position,
        anchorPosition,
        popoverPosition,
        inputOffsetCorrection,
      })
    : getHorizontalDirectionPositionValues({
        direction,
        position,
        anchorPosition,
        popoverPosition,
        inputOffsetCorrection,
      });

// HORIZONTAL
const getHorizontalDirectionPositionTopValue = ({
  position,
  anchorPosition,
  popoverPosition,
  inputOffsetCorrection,
}) => {
  switch (position) {
    case 'middle':
      return anchorPosition.middle - popoverPosition.height / 2;
    case 'top':
      return anchorPosition.top - POPUP_OFFSET * 0.75 - inputOffsetCorrection;
    case 'bottom':
      return anchorPosition.bottom - popoverPosition.height + POPUP_OFFSET * 0.75 + inputOffsetCorrection;
  }
};

const getHorizontalDirectionPositionLeftValue = ({ direction, anchorPosition, popoverPosition }) =>
  direction === 'west'
    ? anchorPosition.left - popoverPosition.width - POPUP_OFFSET
    : anchorPosition.right + POPUP_OFFSET;

const getHorizontalDirectionArrowPositionTopValue = ({ position, popoverPosition }) => {
  switch (position) {
    case 'middle':
      return popoverPosition.height / 2 - ARROW_OFFSET;
    case 'top':
      return ARROW_OFFSET * 2.35;
    case 'bottom':
      return popoverPosition.height - ARROW_OFFSET * 4.5;
  }
};

const getHorizontalDirectionArrowPositionLeftValue = ({ direction, popoverPosition }) =>
  direction === 'west' ? popoverPosition.width - ARROW_OFFSET : -ARROW_OFFSET;

// VERTICAL
const getVerticalDirectionPositionTopValue = ({ direction, anchorPosition, popoverPosition }) =>
  direction === 'north'
    ? anchorPosition.top - popoverPosition.height - POPUP_OFFSET
    : anchorPosition.top + anchorPosition.height + POPUP_OFFSET;

const getVerticalDirectionPositionLeftValue = ({
  position,
  anchorPosition,
  popoverPosition,
  inputOffsetCorrection,
}) => {
  switch (position) {
    case 'center':
      return anchorPosition.center - popoverPosition.width / 2;
    case 'left':
      return anchorPosition.left - inputOffsetCorrection;
    case 'right':
      return anchorPosition.right - popoverPosition.width + inputOffsetCorrection;
  }
};

const getVerticalDirectionArrowPositionTopValue = ({ direction, popoverPosition }) =>
  direction === 'north' ? popoverPosition.height - ARROW_OFFSET : -ARROW_OFFSET;

const getVerticalDirectionArrowPositionLeftValue = ({ position, popoverPosition }) => {
  switch (position) {
    case 'center':
      return popoverPosition.width / 2 - ARROW_OFFSET;
    case 'left':
      return 2 * POPUP_OFFSET;
    case 'right':
      return popoverPosition.width - POPUP_OFFSET * 3;
  }
};

const isInViewport = ({ direction, anchorPosition, popoverPosition }) => {
  switch (direction) {
    case 'north':
      return anchorPosition.top - popoverPosition.height - POPUP_OFFSET > 0;
    case 'south':
      return anchorPosition.bottom + popoverPosition.height + POPUP_OFFSET < window.innerHeight;
    case 'east':
      return anchorPosition.right + popoverPosition.width + POPUP_OFFSET < window.innerWidth;
    case 'west':
      return anchorPosition.left - popoverPosition.width - POPUP_OFFSET > 0;
  }
};

const getOppositeDirection = direction => {
  switch (direction) {
    case 'north':
      return 'south';
    case 'south':
      return 'north';
    case 'east':
      return 'west';
    case 'west':
      return 'east';
  }
};

const getDirection = ({ direction, anchorPosition, popoverPosition }) => {
  const inputDirectionRendersOnScreen = isInViewport({ direction, anchorPosition, popoverPosition });
  const oppositeDirectionRendersOnScreen = isInViewport({
    direction: getOppositeDirection(direction),
    anchorPosition,
    popoverPosition,
  });

  return !inputDirectionRendersOnScreen && oppositeDirectionRendersOnScreen
    ? getOppositeDirection(direction)
    : direction;
};

const isPositionPossible = (position, anchorPosition, popoverPosition) => {
  switch (position) {
    case 'middle':
      return (
        anchorPosition.middle + popoverPosition.height / 2 < window.innerHeight &&
        anchorPosition.middle - popoverPosition.height / 2 > 0
      );
    case 'top':
      return anchorPosition.top + popoverPosition.height < window.innerHeight;
    case 'bottom':
      return anchorPosition.bottom - popoverPosition.height > 0;
    case 'center':
      return (
        anchorPosition.center + popoverPosition.width / 2 < window.innerWidth &&
        anchorPosition.center - popoverPosition.width / 2 > 0
      );
    case 'left':
      return anchorPosition.left + popoverPosition.width < window.innerWidth;
    case 'right':
      return anchorPosition.right - popoverPosition.width > 0;
  }
};

const getOppositePosition = direction => {
  switch (direction) {
    case 'top':
      return 'bottom';
    case 'bottom':
      return 'top';
    case 'left':
      return 'right';
    case 'right':
      return 'left';
  }
};

const getPosition = ({ position, anchorPosition, popoverPosition }) => {
  if (isPositionPossible(position, anchorPosition, popoverPosition)) {
    return position;
  }

  switch (position) {
    case 'middle':
      return isPositionPossible('top', anchorPosition, popoverPosition)
        ? 'top'
        : isPositionPossible('bottom', anchorPosition, popoverPosition)
          ? 'bottom'
          : position;
    case 'center':
      return isPositionPossible('left', anchorPosition, popoverPosition)
        ? 'left'
        : isPositionPossible('right', anchorPosition, popoverPosition)
          ? 'right'
          : position;
    case 'top':
    case 'bottom':
      return isPositionPossible('middle', anchorPosition, popoverPosition) ? 'middle' : getOppositePosition(position);
    case 'left':
    case 'right':
      return isPositionPossible('center', anchorPosition, popoverPosition) ? 'center' : getOppositePosition(position);
  }
};

const getMaxPopoverHeight = ({ direction, anchorPosition, popoverContentEl }) => {
  const directionContentRendersOnScreen = isInViewport({
    direction,
    anchorPosition,
    popoverPosition: { height: getElementHeightValue(popoverContentEl) },
  });
  const oppositeDirectionContentRendersOnScreen = isInViewport({
    direction: getOppositeDirection(direction),
    anchorPosition,
    popoverPosition: { height: getElementHeightValue(popoverContentEl) },
  });

  if (!directionContentRendersOnScreen && !oppositeDirectionContentRendersOnScreen) {
    return getMaxPopoverHeightValue({
      direction,
      anchorPosition,
    });
  }

  return 'initial';
};

const getMaxPopoverHeightValue = ({ direction, anchorPosition }) => {
  switch (direction) {
    case 'north':
      return anchorPosition.top - POPUP_OFFSET * 2;
    case 'south':
      return window.innerHeight - anchorPosition.bottom - POPUP_OFFSET * 2;
  }
};

export const calculatePositions = (
  anchorEl,
  popoverEl,
  popoverContentEl,
  inputDirection,
  inputPosition,
  inputOffsetCorrection,
) => {
  const anchorPosition = getAnchorPositionValues(anchorEl);
  const popoverPosition = getPopoverPositionValues(popoverEl);

  const direction = getDirection({ direction: inputDirection, anchorPosition, popoverPosition });
  const position = getPosition({ position: inputPosition, anchorPosition, popoverPosition });

  return {
    maxPopoverHeight: getMaxPopoverHeight({
      direction,
      anchorPosition,
      popoverContentEl,
    }),
    ...getPositionValues({
      direction,
      position,
      anchorPosition,
      popoverPosition,
      inputOffsetCorrection,
    }),
  };
};
<commit_msg>Let function only return needed values and rename accordinly<commit_after>const ARROW_OFFSET = 7;
const POPUP_OFFSET = 12;

const getAnchorPositionValues = anchorEl => {
  const { top, left, right, bottom, width, height } = anchorEl.getBoundingClientRect();

  return {
    top,
    left,
    width,
    height,
    right: right || left + width,
    bottom: bottom || top + height,
    middle: top + (bottom - top) / 2,
    center: left + (right - left) / 2,
  };
};

const getElementDimensions = element => {
  const { width, height } = element.getBoundingClientRect();
  return { width, height };
};

const getElementHeightValue = element => element.getBoundingClientRect().height;

const getVerticalDirectionPositionValues = ({
  direction,
  position,
  anchorPosition,
  popoverDimensions,
  inputOffsetCorrection,
}) => ({
  top: getVerticalDirectionPositionTopValue({
    direction,
    anchorPosition,
    popoverDimensions,
  }),
  arrowTop: getVerticalDirectionArrowPositionTopValue({ direction, popoverDimensions }),
  left: getVerticalDirectionPositionLeftValue({
    position,
    anchorPosition,
    popoverDimensions,
    inputOffsetCorrection,
  }),
  arrowLeft: getVerticalDirectionArrowPositionLeftValue({ position, popoverDimensions }),
});

const getHorizontalDirectionPositionValues = ({
  direction,
  position,
  anchorPosition,
  popoverDimensions,
  inputOffsetCorrection,
}) => ({
  top: getHorizontalDirectionPositionTopValue({
    position,
    anchorPosition,
    popoverDimensions,
    inputOffsetCorrection,
  }),
  arrowTop: getHorizontalDirectionArrowPositionTopValue({ position, popoverDimensions }),
  left: getHorizontalDirectionPositionLeftValue({ direction, anchorPosition, popoverDimensions }),
  arrowLeft: getHorizontalDirectionArrowPositionLeftValue({
    direction,
    anchorPosition,
    popoverDimensions,
  }),
});

const getPositionValues = ({ direction, position, anchorPosition, popoverDimensions, inputOffsetCorrection }) =>
  direction === 'north' || direction === 'south'
    ? getVerticalDirectionPositionValues({
        direction,
        position,
        anchorPosition,
        popoverDimensions,
        inputOffsetCorrection,
      })
    : getHorizontalDirectionPositionValues({
        direction,
        position,
        anchorPosition,
        popoverDimensions,
        inputOffsetCorrection,
      });

// HORIZONTAL
const getHorizontalDirectionPositionTopValue = ({
  position,
  anchorPosition,
  popoverDimensions,
  inputOffsetCorrection,
}) => {
  switch (position) {
    case 'middle':
      return anchorPosition.middle - popoverDimensions.height / 2;
    case 'top':
      return anchorPosition.top - POPUP_OFFSET * 0.75 - inputOffsetCorrection;
    case 'bottom':
      return anchorPosition.bottom - popoverDimensions.height + POPUP_OFFSET * 0.75 + inputOffsetCorrection;
  }
};

const getHorizontalDirectionPositionLeftValue = ({ direction, anchorPosition, popoverDimensions }) =>
  direction === 'west'
    ? anchorPosition.left - popoverDimensions.width - POPUP_OFFSET
    : anchorPosition.right + POPUP_OFFSET;

const getHorizontalDirectionArrowPositionTopValue = ({ position, popoverDimensions }) => {
  switch (position) {
    case 'middle':
      return popoverDimensions.height / 2 - ARROW_OFFSET;
    case 'top':
      return ARROW_OFFSET * 2.35;
    case 'bottom':
      return popoverDimensions.height - ARROW_OFFSET * 4.5;
  }
};

const getHorizontalDirectionArrowPositionLeftValue = ({ direction, popoverDimensions }) =>
  direction === 'west' ? popoverDimensions.width - ARROW_OFFSET : -ARROW_OFFSET;

// VERTICAL
const getVerticalDirectionPositionTopValue = ({ direction, anchorPosition, popoverDimensions }) =>
  direction === 'north'
    ? anchorPosition.top - popoverDimensions.height - POPUP_OFFSET
    : anchorPosition.top + anchorPosition.height + POPUP_OFFSET;

const getVerticalDirectionPositionLeftValue = ({
  position,
  anchorPosition,
  popoverDimensions,
  inputOffsetCorrection,
}) => {
  switch (position) {
    case 'center':
      return anchorPosition.center - popoverDimensions.width / 2;
    case 'left':
      return anchorPosition.left - inputOffsetCorrection;
    case 'right':
      return anchorPosition.right - popoverDimensions.width + inputOffsetCorrection;
  }
};

const getVerticalDirectionArrowPositionTopValue = ({ direction, popoverDimensions }) =>
  direction === 'north' ? popoverDimensions.height - ARROW_OFFSET : -ARROW_OFFSET;

const getVerticalDirectionArrowPositionLeftValue = ({ position, popoverDimensions }) => {
  switch (position) {
    case 'center':
      return popoverDimensions.width / 2 - ARROW_OFFSET;
    case 'left':
      return 2 * POPUP_OFFSET;
    case 'right':
      return popoverDimensions.width - POPUP_OFFSET * 3;
  }
};

const isInViewport = ({ direction, anchorPosition, popoverDimensions }) => {
  switch (direction) {
    case 'north':
      return anchorPosition.top - popoverDimensions.height - POPUP_OFFSET > 0;
    case 'south':
      return anchorPosition.bottom + popoverDimensions.height + POPUP_OFFSET < window.innerHeight;
    case 'east':
      return anchorPosition.right + popoverDimensions.width + POPUP_OFFSET < window.innerWidth;
    case 'west':
      return anchorPosition.left - popoverDimensions.width - POPUP_OFFSET > 0;
  }
};

const getOppositeDirection = direction => {
  switch (direction) {
    case 'north':
      return 'south';
    case 'south':
      return 'north';
    case 'east':
      return 'west';
    case 'west':
      return 'east';
  }
};

const getDirection = ({ direction, anchorPosition, popoverDimensions }) => {
  const inputDirectionRendersOnScreen = isInViewport({ direction, anchorPosition, popoverDimensions });
  const oppositeDirectionRendersOnScreen = isInViewport({
    direction: getOppositeDirection(direction),
    anchorPosition,
    popoverDimensions,
  });

  return !inputDirectionRendersOnScreen && oppositeDirectionRendersOnScreen
    ? getOppositeDirection(direction)
    : direction;
};

const isPositionPossible = (position, anchorPosition, popoverDimensions) => {
  switch (position) {
    case 'middle':
      return (
        anchorPosition.middle + popoverDimensions.height / 2 < window.innerHeight &&
        anchorPosition.middle - popoverDimensions.height / 2 > 0
      );
    case 'top':
      return anchorPosition.top + popoverDimensions.height < window.innerHeight;
    case 'bottom':
      return anchorPosition.bottom - popoverDimensions.height > 0;
    case 'center':
      return (
        anchorPosition.center + popoverDimensions.width / 2 < window.innerWidth &&
        anchorPosition.center - popoverDimensions.width / 2 > 0
      );
    case 'left':
      return anchorPosition.left + popoverDimensions.width < window.innerWidth;
    case 'right':
      return anchorPosition.right - popoverDimensions.width > 0;
  }
};

const getOppositePosition = direction => {
  switch (direction) {
    case 'top':
      return 'bottom';
    case 'bottom':
      return 'top';
    case 'left':
      return 'right';
    case 'right':
      return 'left';
  }
};

const getPosition = ({ position, anchorPosition, popoverDimensions }) => {
  if (isPositionPossible(position, anchorPosition, popoverDimensions)) {
    return position;
  }

  switch (position) {
    case 'middle':
      return isPositionPossible('top', anchorPosition, popoverDimensions)
        ? 'top'
        : isPositionPossible('bottom', anchorPosition, popoverDimensions)
          ? 'bottom'
          : position;
    case 'center':
      return isPositionPossible('left', anchorPosition, popoverDimensions)
        ? 'left'
        : isPositionPossible('right', anchorPosition, popoverDimensions)
          ? 'right'
          : position;
    case 'top':
    case 'bottom':
      return isPositionPossible('middle', anchorPosition, popoverDimensions) ? 'middle' : getOppositePosition(position);
    case 'left':
    case 'right':
      return isPositionPossible('center', anchorPosition, popoverDimensions) ? 'center' : getOppositePosition(position);
  }
};

const getMaxPopoverHeight = ({ direction, anchorPosition, popoverContentEl }) => {
  const directionContentRendersOnScreen = isInViewport({
    direction,
    anchorPosition,
    popoverDimensions: { height: getElementHeightValue(popoverContentEl) },
  });
  const oppositeDirectionContentRendersOnScreen = isInViewport({
    direction: getOppositeDirection(direction),
    anchorPosition,
    popoverDimensions: { height: getElementHeightValue(popoverContentEl) },
  });

  if (!directionContentRendersOnScreen && !oppositeDirectionContentRendersOnScreen) {
    return getMaxPopoverHeightValue({
      direction,
      anchorPosition,
    });
  }

  return 'initial';
};

const getMaxPopoverHeightValue = ({ direction, anchorPosition }) => {
  switch (direction) {
    case 'north':
      return anchorPosition.top - POPUP_OFFSET * 2;
    case 'south':
      return window.innerHeight - anchorPosition.bottom - POPUP_OFFSET * 2;
  }
};

export const calculatePositions = (
  anchorEl,
  popoverEl,
  popoverContentEl,
  inputDirection,
  inputPosition,
  inputOffsetCorrection,
) => {
  const anchorPosition = getAnchorPositionValues(anchorEl);
  const popoverDimensions = getElementDimensions(popoverEl);

  const direction = getDirection({ direction: inputDirection, anchorPosition, popoverDimensions });
  const position = getPosition({ position: inputPosition, anchorPosition, popoverDimensions });

  return {
    maxPopoverHeight: getMaxPopoverHeight({
      direction,
      anchorPosition,
      popoverContentEl,
    }),
    ...getPositionValues({
      direction,
      position,
      anchorPosition,
      popoverDimensions,
      inputOffsetCorrection,
    }),
  };
};
<|endoftext|>


<commit_before>/*global define*/
define([
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/EasingFunction',
        '../Core/Math',
        './PerspectiveFrustum',
        './PerspectiveOffCenterFrustum',
        './SceneMode'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        EasingFunction,
        CesiumMath,
        PerspectiveFrustum,
        PerspectiveOffCenterFrustum,
        SceneMode) {
    'use strict';

    /**
     * Creates tweens for camera flights.
     *

     * Mouse interaction is disabled during flights.
     *
     * @private
     */
    var CameraFlightPath = {
    };

    function getAltitude(frustum, dx, dy) {
        var near;
        var top;
        var right;
        if (frustum instanceof PerspectiveFrustum) {
            var tanTheta = Math.tan(0.5 * frustum.fovy);
            near = frustum.near;
            top = frustum.near * tanTheta;
            right = frustum.aspectRatio * top;
            return Math.max(dx * near / right, dy * near / top);
        } else if (frustum instanceof PerspectiveOffCenterFrustum) {
            near = frustum.near;
            top = frustum.top;
            right = frustum.right;
            return Math.max(dx * near / right, dy * near / top);
        }

        return Math.max(dx, dy);
    }

    var scratchCart = new Cartesian3();
    var scratchCart2 = new Cartesian3();

    function createHeightFunction(camera, destination, startHeight, endHeight, optionAltitude) {
        var altitude = optionAltitude;
        var maxHeight = Math.max(startHeight, endHeight);;

        if (!defined(optionAltitude)) {
            var start = camera.position;
            var end = destination;
            var up = camera.up;
            var right = camera.right;
            var frustum = camera.frustum;

            var diff = Cartesian3.subtract(start, end, scratchCart);
            var verticalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(up, Cartesian3.dot(diff, up), scratchCart2));
            var horizontalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(right, Cartesian3.dot(diff, right), scratchCart2));

            altitude = Math.min(getAltitude(frustum, verticalDistance, horizontalDistance) * 0.20, 1000000000.0);
        }

        if ((defined(optionAltitude) && optionAltitude < altitude) || maxHeight < altitude) {
            var power = 8.0;
            var factor = 1000000.0;

            var s = -Math.pow((altitude - startHeight) * factor, 1.0 / power);
            var e = Math.pow((altitude - endHeight) * factor, 1.0 / power);

            return function(t) {
                var x = t * (e - s) + s;
                return -Math.pow(x, power) / factor + altitude;
            };
        }

        return function(t) {
            return CesiumMath.lerp(startHeight, endHeight, t);
        };
    }

    function adjustAngleForLERP(startAngle, endAngle) {
        if (CesiumMath.equalsEpsilon(startAngle, CesiumMath.TWO_PI, CesiumMath.EPSILON11)) {
            startAngle = 0.0;
        }

        if (endAngle > startAngle + Math.PI) {
            startAngle += CesiumMath.TWO_PI;
        } else if (endAngle < startAngle - Math.PI) {
            startAngle -= CesiumMath.TWO_PI;
        }

        return startAngle;
    }

    var scratchStart = new Cartesian3();

    function createUpdateCV(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;

        var start = Cartesian3.clone(camera.position, scratchStart);
        var startPitch = camera.pitch;
        var startHeading = adjustAngleForLERP(camera.heading, heading);
        var startRoll = adjustAngleForLERP(camera.roll, roll);

        var heightFunction = createHeightFunction(camera, destination, start.z, destination.z, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            camera.setView({
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time),
                    pitch : CesiumMath.lerp(startPitch, pitch, time),
                    roll : CesiumMath.lerp(startRoll, roll, time)
                }
            });

            Cartesian2.lerp(start, destination, time, camera.position);
            camera.position.z = heightFunction(time);
        }
        return update;
    }

    var scratchStartCart = new Cartographic();
    var scratchEndCart = new Cartographic();

    function createUpdate3D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;
        var projection = scene.mapProjection;
        var ellipsoid = projection.ellipsoid;

        var startCart = Cartographic.clone(camera.positionCartographic, scratchStartCart);
        var startPitch = camera.pitch;
        var startHeading = adjustAngleForLERP(camera.heading, heading);
        var startRoll = adjustAngleForLERP(camera.roll, roll);

        var destCart = ellipsoid.cartesianToCartographic(destination, scratchEndCart);
        if (destCart.height <= 0.0) {
            destCart.height = startCart.height;
        }

        startCart.longitude = CesiumMath.zeroToTwoPi(startCart.longitude);
        destCart.longitude = CesiumMath.zeroToTwoPi(destCart.longitude);

        var diff = startCart.longitude - destCart.longitude;
        if (diff < -CesiumMath.PI) {
            startCart.longitude += CesiumMath.TWO_PI;
        } else if (diff > CesiumMath.PI) {
            destCart.longitude += CesiumMath.TWO_PI;
        }

        var heightFunction = createHeightFunction(camera, destination, startCart.height, destCart.height, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            var position = Cartesian3.fromRadians(
                CesiumMath.lerp(startCart.longitude, destCart.longitude, time),
                CesiumMath.lerp(startCart.latitude, destCart.latitude, time),
                heightFunction(time)
            );

            camera.setView({
                destination : position,
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time),
                    pitch : CesiumMath.lerp(startPitch, pitch, time),
                    roll : CesiumMath.lerp(startRoll, roll, time)
                }
            });
        }
        return update;
    }

    function createUpdate2D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;

        var start = Cartesian3.clone(camera.position, scratchStart);
        var startHeading = adjustAngleForLERP(camera.heading, heading);

        var startHeight = camera.frustum.right - camera.frustum.left;
        var heightFunction = createHeightFunction(camera, destination, startHeight, destination.z, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            camera.setView({
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time)
                }
            });

            Cartesian2.lerp(start, destination, time, camera.position);

            var zoom = heightFunction(time);

            var frustum = camera.frustum;
            var ratio = frustum.top / frustum.right;

            var incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5;
            frustum.right += incrementAmount;
            frustum.left -= incrementAmount;
            frustum.top = ratio * frustum.right;
            frustum.bottom = -frustum.top;
        }
        return update;
    }

    var scratchCartographic = new Cartographic();
    var scratchDestination = new Cartesian3();

    function emptyFlight(complete, cancel) {
        return {
            startObject : {},
            stopObject : {},
            duration : 0.0,
            complete : complete,
            cancel : cancel
        };
    }

    function wrapCallback(controller, cb) {
        function wrapped() {
            if (typeof cb === 'function') {
                cb();
            }

            controller.enableInputs = true;
        }
        return wrapped;
    }

    CameraFlightPath.createTween = function(scene, options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var destination = options.destination;

        //>>includeStart('debug', pragmas.debug);
        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(destination)) {
            throw new DeveloperError('destination is required.');
        }
        //>>includeEnd('debug');
        var mode = scene.mode;

        if (mode === SceneMode.MORPHING) {
            return emptyFlight();
        }

        var convert = defaultValue(options.convert, true);
        var projection = scene.mapProjection;
        var ellipsoid = projection.ellipsoid;
        var maximumHeight = options.maximumHeight;
        var easingFunction = options.easingFunction;

        if (convert && mode !== SceneMode.SCENE3D) {
            ellipsoid.cartesianToCartographic(destination, scratchCartographic);
            destination = projection.project(scratchCartographic, scratchDestination);
        }

        var camera = scene.camera;
        var transform = options.endTransform;
        if (defined(transform)) {
            camera._setTransform(transform);
        }

        var duration = options.duration;
        if (!defined(duration)) {
            duration = Math.ceil(Cartesian3.distance(camera.position, destination) / 1000000.0) + 2.0;
            duration = Math.min(duration, 3.0);
        }

        var heading = defaultValue(options.heading, 0.0);
        var pitch = defaultValue(options.pitch, -CesiumMath.PI_OVER_TWO);
        var roll = defaultValue(options.roll, 0.0);

        var controller = scene.screenSpaceCameraController;
        controller.enableInputs = false;

        var complete = wrapCallback(controller, options.complete);
        var cancel = wrapCallback(controller, options.cancel);

        var frustum = camera.frustum;

        var empty = scene.mode === SceneMode.SCENE2D;
        empty = empty && Cartesian2.equalsEpsilon(camera.position, destination, CesiumMath.EPSILON6);
        empty = empty && CesiumMath.equalsEpsilon(Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom), destination.z, CesiumMath.EPSILON6);

        empty = empty || (scene.mode !== SceneMode.SCENE2D &&
            Cartesian3.equalsEpsilon(destination, camera.position, CesiumMath.EPSILON10));

        empty = empty &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(heading), CesiumMath.negativePiToPi(camera.heading), CesiumMath.EPSILON10) &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(pitch), CesiumMath.negativePiToPi(camera.pitch), CesiumMath.EPSILON10) &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(roll), CesiumMath.negativePiToPi(camera.roll), CesiumMath.EPSILON10);

        if (empty) {
            return emptyFlight(complete, cancel);
        }

        var updateFunctions = new Array(4);
        updateFunctions[SceneMode.SCENE2D] = createUpdate2D;
        updateFunctions[SceneMode.SCENE3D] = createUpdate3D;
        updateFunctions[SceneMode.COLUMBUS_VIEW] = createUpdateCV;

        if (duration <= 0.0) {
            var newOnComplete = function() {
                var update = updateFunctions[mode](scene, 1.0, destination, heading, pitch, roll, maximumHeight);
                update({ time: 1.0 });

                if (typeof complete === 'function') {
                    complete();
                }
            };
            return emptyFlight(newOnComplete, cancel);
        }

        var update = updateFunctions[mode](scene, duration, destination, heading, pitch, roll, maximumHeight);

        if (!defined(easingFunction)) {
            var startHeight = camera.positionCartographic.height;
            var endHeight = mode === SceneMode.SCENE3D ? ellipsoid.cartesianToCartographic(destination).height : destination.z;

            if (startHeight > endHeight && startHeight > 11500.0) {
                easingFunction = EasingFunction.CUBIC_OUT;
            } else {
                easingFunction = EasingFunction.QUINTIC_IN_OUT;
            }
        }

        return {
            duration : duration,
            easingFunction : easingFunction,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : duration
            },
            update : update,
            complete : complete,
            cancel: cancel
        };
    };

    return CameraFlightPath;
});
<commit_msg>typo fix<commit_after>/*global define*/
define([
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/EasingFunction',
        '../Core/Math',
        './PerspectiveFrustum',
        './PerspectiveOffCenterFrustum',
        './SceneMode'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        EasingFunction,
        CesiumMath,
        PerspectiveFrustum,
        PerspectiveOffCenterFrustum,
        SceneMode) {
    'use strict';

    /**
     * Creates tweens for camera flights.
     *

     * Mouse interaction is disabled during flights.
     *
     * @private
     */
    var CameraFlightPath = {
    };

    function getAltitude(frustum, dx, dy) {
        var near;
        var top;
        var right;
        if (frustum instanceof PerspectiveFrustum) {
            var tanTheta = Math.tan(0.5 * frustum.fovy);
            near = frustum.near;
            top = frustum.near * tanTheta;
            right = frustum.aspectRatio * top;
            return Math.max(dx * near / right, dy * near / top);
        } else if (frustum instanceof PerspectiveOffCenterFrustum) {
            near = frustum.near;
            top = frustum.top;
            right = frustum.right;
            return Math.max(dx * near / right, dy * near / top);
        }

        return Math.max(dx, dy);
    }

    var scratchCart = new Cartesian3();
    var scratchCart2 = new Cartesian3();

    function createHeightFunction(camera, destination, startHeight, endHeight, optionAltitude) {
        var altitude = optionAltitude;
        var maxHeight = Math.max(startHeight, endHeight);

        if (!defined(optionAltitude)) {
            var start = camera.position;
            var end = destination;
            var up = camera.up;
            var right = camera.right;
            var frustum = camera.frustum;

            var diff = Cartesian3.subtract(start, end, scratchCart);
            var verticalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(up, Cartesian3.dot(diff, up), scratchCart2));
            var horizontalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(right, Cartesian3.dot(diff, right), scratchCart2));

            altitude = Math.min(getAltitude(frustum, verticalDistance, horizontalDistance) * 0.20, 1000000000.0);
        }

        if ((defined(optionAltitude) && optionAltitude < altitude) || maxHeight < altitude) {
            var power = 8.0;
            var factor = 1000000.0;

            var s = -Math.pow((altitude - startHeight) * factor, 1.0 / power);
            var e = Math.pow((altitude - endHeight) * factor, 1.0 / power);

            return function(t) {
                var x = t * (e - s) + s;
                return -Math.pow(x, power) / factor + altitude;
            };
        }

        return function(t) {
            return CesiumMath.lerp(startHeight, endHeight, t);
        };
    }

    function adjustAngleForLERP(startAngle, endAngle) {
        if (CesiumMath.equalsEpsilon(startAngle, CesiumMath.TWO_PI, CesiumMath.EPSILON11)) {
            startAngle = 0.0;
        }

        if (endAngle > startAngle + Math.PI) {
            startAngle += CesiumMath.TWO_PI;
        } else if (endAngle < startAngle - Math.PI) {
            startAngle -= CesiumMath.TWO_PI;
        }

        return startAngle;
    }

    var scratchStart = new Cartesian3();

    function createUpdateCV(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;

        var start = Cartesian3.clone(camera.position, scratchStart);
        var startPitch = camera.pitch;
        var startHeading = adjustAngleForLERP(camera.heading, heading);
        var startRoll = adjustAngleForLERP(camera.roll, roll);

        var heightFunction = createHeightFunction(camera, destination, start.z, destination.z, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            camera.setView({
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time),
                    pitch : CesiumMath.lerp(startPitch, pitch, time),
                    roll : CesiumMath.lerp(startRoll, roll, time)
                }
            });

            Cartesian2.lerp(start, destination, time, camera.position);
            camera.position.z = heightFunction(time);
        }
        return update;
    }

    var scratchStartCart = new Cartographic();
    var scratchEndCart = new Cartographic();

    function createUpdate3D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;
        var projection = scene.mapProjection;
        var ellipsoid = projection.ellipsoid;

        var startCart = Cartographic.clone(camera.positionCartographic, scratchStartCart);
        var startPitch = camera.pitch;
        var startHeading = adjustAngleForLERP(camera.heading, heading);
        var startRoll = adjustAngleForLERP(camera.roll, roll);

        var destCart = ellipsoid.cartesianToCartographic(destination, scratchEndCart);
        if (destCart.height <= 0.0) {
            destCart.height = startCart.height;
        }

        startCart.longitude = CesiumMath.zeroToTwoPi(startCart.longitude);
        destCart.longitude = CesiumMath.zeroToTwoPi(destCart.longitude);

        var diff = startCart.longitude - destCart.longitude;
        if (diff < -CesiumMath.PI) {
            startCart.longitude += CesiumMath.TWO_PI;
        } else if (diff > CesiumMath.PI) {
            destCart.longitude += CesiumMath.TWO_PI;
        }

        var heightFunction = createHeightFunction(camera, destination, startCart.height, destCart.height, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            var position = Cartesian3.fromRadians(
                CesiumMath.lerp(startCart.longitude, destCart.longitude, time),
                CesiumMath.lerp(startCart.latitude, destCart.latitude, time),
                heightFunction(time)
            );

            camera.setView({
                destination : position,
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time),
                    pitch : CesiumMath.lerp(startPitch, pitch, time),
                    roll : CesiumMath.lerp(startRoll, roll, time)
                }
            });
        }
        return update;
    }

    function createUpdate2D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;

        var start = Cartesian3.clone(camera.position, scratchStart);
        var startHeading = adjustAngleForLERP(camera.heading, heading);

        var startHeight = camera.frustum.right - camera.frustum.left;
        var heightFunction = createHeightFunction(camera, destination, startHeight, destination.z, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            camera.setView({
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time)
                }
            });

            Cartesian2.lerp(start, destination, time, camera.position);

            var zoom = heightFunction(time);

            var frustum = camera.frustum;
            var ratio = frustum.top / frustum.right;

            var incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5;
            frustum.right += incrementAmount;
            frustum.left -= incrementAmount;
            frustum.top = ratio * frustum.right;
            frustum.bottom = -frustum.top;
        }
        return update;
    }

    var scratchCartographic = new Cartographic();
    var scratchDestination = new Cartesian3();

    function emptyFlight(complete, cancel) {
        return {
            startObject : {},
            stopObject : {},
            duration : 0.0,
            complete : complete,
            cancel : cancel
        };
    }

    function wrapCallback(controller, cb) {
        function wrapped() {
            if (typeof cb === 'function') {
                cb();
            }

            controller.enableInputs = true;
        }
        return wrapped;
    }

    CameraFlightPath.createTween = function(scene, options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var destination = options.destination;

        //>>includeStart('debug', pragmas.debug);
        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(destination)) {
            throw new DeveloperError('destination is required.');
        }
        //>>includeEnd('debug');
        var mode = scene.mode;

        if (mode === SceneMode.MORPHING) {
            return emptyFlight();
        }

        var convert = defaultValue(options.convert, true);
        var projection = scene.mapProjection;
        var ellipsoid = projection.ellipsoid;
        var maximumHeight = options.maximumHeight;
        var easingFunction = options.easingFunction;

        if (convert && mode !== SceneMode.SCENE3D) {
            ellipsoid.cartesianToCartographic(destination, scratchCartographic);
            destination = projection.project(scratchCartographic, scratchDestination);
        }

        var camera = scene.camera;
        var transform = options.endTransform;
        if (defined(transform)) {
            camera._setTransform(transform);
        }

        var duration = options.duration;
        if (!defined(duration)) {
            duration = Math.ceil(Cartesian3.distance(camera.position, destination) / 1000000.0) + 2.0;
            duration = Math.min(duration, 3.0);
        }

        var heading = defaultValue(options.heading, 0.0);
        var pitch = defaultValue(options.pitch, -CesiumMath.PI_OVER_TWO);
        var roll = defaultValue(options.roll, 0.0);

        var controller = scene.screenSpaceCameraController;
        controller.enableInputs = false;

        var complete = wrapCallback(controller, options.complete);
        var cancel = wrapCallback(controller, options.cancel);

        var frustum = camera.frustum;

        var empty = scene.mode === SceneMode.SCENE2D;
        empty = empty && Cartesian2.equalsEpsilon(camera.position, destination, CesiumMath.EPSILON6);
        empty = empty && CesiumMath.equalsEpsilon(Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom), destination.z, CesiumMath.EPSILON6);

        empty = empty || (scene.mode !== SceneMode.SCENE2D &&
            Cartesian3.equalsEpsilon(destination, camera.position, CesiumMath.EPSILON10));

        empty = empty &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(heading), CesiumMath.negativePiToPi(camera.heading), CesiumMath.EPSILON10) &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(pitch), CesiumMath.negativePiToPi(camera.pitch), CesiumMath.EPSILON10) &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(roll), CesiumMath.negativePiToPi(camera.roll), CesiumMath.EPSILON10);

        if (empty) {
            return emptyFlight(complete, cancel);
        }

        var updateFunctions = new Array(4);
        updateFunctions[SceneMode.SCENE2D] = createUpdate2D;
        updateFunctions[SceneMode.SCENE3D] = createUpdate3D;
        updateFunctions[SceneMode.COLUMBUS_VIEW] = createUpdateCV;

        if (duration <= 0.0) {
            var newOnComplete = function() {
                var update = updateFunctions[mode](scene, 1.0, destination, heading, pitch, roll, maximumHeight);
                update({ time: 1.0 });

                if (typeof complete === 'function') {
                    complete();
                }
            };
            return emptyFlight(newOnComplete, cancel);
        }

        var update = updateFunctions[mode](scene, duration, destination, heading, pitch, roll, maximumHeight);

        if (!defined(easingFunction)) {
            var startHeight = camera.positionCartographic.height;
            var endHeight = mode === SceneMode.SCENE3D ? ellipsoid.cartesianToCartographic(destination).height : destination.z;

            if (startHeight > endHeight && startHeight > 11500.0) {
                easingFunction = EasingFunction.CUBIC_OUT;
            } else {
                easingFunction = EasingFunction.QUINTIC_IN_OUT;
            }
        }

        return {
            duration : duration,
            easingFunction : easingFunction,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : duration
            },
            update : update,
            complete : complete,
            cancel: cancel
        };
    };

    return CameraFlightPath;
});
<|endoftext|>


<commit_before>/*
 *  Copyright 2011 Rackspace
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var url = require('url');
var util = require('util');

var JSV = require('JSV').JSV;
var _ = require('underscore');
var et = require('elementtree');

var log = require('../log');
var urls = require('../web/urls');
var settings = require('../settings');
var schema = require('../../extern/devopsjson/lib/web/schema').schema;

/**
 * makeClass - By John Resig (MIT Licensed)
 * Takes a function and ensures that new is called so that __this__ is properly bound
 * @param {proto} optional prototype to set on the returned function
 */
exports.make_class = function(proto){
  var f = function(args){
    // did you make me with new?
    if (this instanceof arguments.callee){
      // am I a function?
      if (typeof this.init === "function"){
        //PREGUNTA: why not always pass apply arguments?
        if (args){
          return this.init.apply(this, args.callee ? args : arguments );
        }
        else{
          return this.init.apply(this, arguments);
        }
      }
    } else{
      // didn't use new, return a properly instantiated object
      return new arguments.callee(arguments);
    }
  };
  if (proto){
    f.prototype = proto;
  }
  return f;
};

exports.die = function () {
  var args = Array.prototype.slice.call(arguments);
  args.unshift("DYING because:");
  console.error.apply(null, args);
  process.exit(1);
};

/**
 * Takes a username and password and returns a basic auth token fully formed
 *
 */
exports.create_basic_auth = function(username, password){
  var auth_token = new Buffer(username + ':' + password).toString('base64');
  return "Basic " + auth_token;
};

/**
 * Takes middlware function pointer with arrity of 5 and returns a middleware
 *
 * @param {string} name the name of the middleware (should be unique)
 * @param {fn} the middleware proper which should accept (req, res, next, payload, api_config- set payload.data/errors to cache stuff, api_config = req.devops[name]
 * @return {fn} middleware function
 */
exports.create_middleware = function(name, middleware){
  return function(req, res, next){
    // Do they have the api defined in the devops ball?
    if (!req.devops.related_apis || !req.devops.related_apis[name]) {
      req.devops[name] = null;
      next();
      return;
    }
    var payload = {error: null, data: null};
    req.devops[name] = payload;
    try{
      middleware(req, res, next, payload, req.devops.related_apis[name]);
    }catch (e){
      payload.error = e;
      // TODO: this may not be callable if the error was with express
      next();
    }
  };
};

/** A convenience function for making requests without having to build up response body data.
 * Also useful for mocking the making of requests in tests
 * @param {Object} options an options dict such as http.request would use
 * @param {fn} on_success callback that takes the complete response body data as a parameter
 * @param {fn} on_error callback that takes an Exception as a parameter
 */
exports.request_maker = function(options, call_back) {
  var post_data = "";
  var headers = {};
  var method;
  var req;
  var timeout = 20 * 1000;

  if (options.post_data !== undefined){
    post_data = options.post_data;
    delete options.post_data;

    if (options.dont_stringify === undefined){
      post_data = JSON.stringify(post_data);
    }else{
      delete options.dont_stringify;
    }

  }
  if (options.timeout !== undefined){
    timeout = options.timeout;
    delete options.timeout;
  }
  if (options.method && options.method.toUpperCase() !== "GET"){
    headers['Content-length'] = Buffer.byteLength(post_data, 'utf8');
  }

  if (!options.headers) {
    options.headers = {};
  }
  _.extend(options.headers, headers);

  method = options.port === 80 ? http : https;
  log.debug(options.method, options.host, options.path);

  req = method.request(options, function(res) {
    var data = '';
    res.setEncoding('utf8');
    res.on('data', function(d) {
      data += d;
    });
    res.on('end', function() {
      var request;
      var error;
      if (res.statusCode >= 400) {
        request = options.host + options.path;
        error = Error("Bad status code: " + res.statusCode + " for url: " + request);
      }
      try{
        call_back(error, {data: data, res: res});
      }catch(e){
        call_back(e, {data: data, res: res});
      }
    });
  });
  req.on('error', function(e) {
    call_back(e, null);
  });

  req.on('socket', function (socket) {
    if (timeout !== undefined){
      socket.setTimeout(timeout);
      socket.setMaxListeners(0);
      socket.on('timeout', function() {
        req.abort();
      });
    }
  });

  if (post_data){
    req.write(post_data);
  }
  req.end();
};

/*
 *
 * @param devops_github
 * @param is_closed
 * @returns
 */
exports.github_options = function(api_config, repo, path, uri) {
  var host;
  var parsed_uri;
  if (uri === undefined){
    if (path instanceof Array){
      path = path.join('');
    }
    path = util.format("/repos/%s/%s%s", api_config.org, repo, path);
    host = url.parse(api_config.url).host;
  } else {
    parsed_uri = url.parse(uri);
    path = parsed_uri.path;
    host = parsed_uri.host;
  }

  if (!host || !path){
    throw new Error('Not enough info to build a github request!');
  }

  return {
    return_response: true,
    host: host,
    port: 443,
    path: path,
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': exports.create_basic_auth(api_config.username,
        api_config.apikey)
    }
  };
};
/**
 * @param string
 * @returns new string with first letter capitalized
 */
exports.capitalize = function(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
};

exports.validate_devops = function(devops){
  var jsv_env = JSV.createEnvironment('json-schema-draft-03');
  var report = jsv_env.validate(devops, schema);

  if (report.errors.length > 0) {
    log.error("ERRORS: ", report.errors);
  }
  return report;
};

exports.validate_date = function(date_like){
  var date = new Date(date_like);

  if (isNaN(date.valueOf())){
    throw new Error('not a date', date);
  }
  return date;
};
/*
 * Javascript Humane Dates
 * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 * Re-write by Zach Leatherman (zachleat.com)
 *
 * Adopted from the John Resig's pretty.js
 * at http://ejohn.org/blog/javascript-pretty-date
 * and henrah's proposed modification
 * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 *
 * Licensed under the MIT license.
 */

exports.humane_date = function(date, compareTo){
  var isString = typeof date === 'string';

  if(!date) {
      return;
  }

  date = isString ?
    new Date(date.replace(/-/g,"/").replace(/[TZ]/g," ")) :
    date;
  compareTo = compareTo || new Date();

  var lang = {
    ago: 'Ago',
    from: '',
    now: 'Just Now',
    minute: 'Minute',
    minutes: 'Minutes',
    hour: 'Hour',
    hours: 'Hours',
    day: 'Day',
    days: 'Days',
    week: 'Week',
    weeks: 'Weeks',
    month: 'Month',
    months: 'Months',
    year: 'Year',
    years: 'Years'
  };
  var formats = [
    [60, lang.now],
    [3600, lang.minute, lang.minutes, 60], // 60 minutes, 1 minute
    [86400, lang.hour, lang.hours, 3600], // 24 hours, 1 hour
    [604800, lang.day, lang.days, 86400], // 7 days, 1 day
    [2628000, lang.week, lang.weeks, 604800], // ~1 month, 1 week
    [31536000, lang.month, lang.months, 2628000], // 1 year, ~1 month
    [Infinity, lang.year, lang.years, 31536000] // Infinity, 1 year
  ];
  var seconds = (compareTo - date + (
      compareTo.getTimezoneOffset() -
      // if we received a GMT time from a string, doesn't include time zone bias
      // if we got a date object, the time zone is built in, we need to remove it.
      (isString ? 0 : date.getTimezoneOffset())
    ) * 60000 ) / 1000;
  var token;

  if(seconds < 0) {
    seconds = Math.abs(seconds);
    token = lang.from ? ' ' + lang.from : '';
  } else {
    token = lang.ago ? ' ' + lang.ago : '';
  }

  /*
   * 0 seconds && < 60 seconds        Now
   * 60 seconds                       1 Minute
   * > 60 seconds && < 60 minutes     X Minutes
   * 60 minutes                       1 Hour
   * > 60 minutes && < 24 hours       X Hours
   * 24 hours                         1 Day
   * > 24 hours && < 7 days           X Days
   * 7 days                           1 Week
   * > 7 days && < ~ 1 Month          X Weeks
   * ~ 1 Month                        1 Month
   * > ~ 1 Month && < 1 Year          X Months
   * 1 Year                           1 Year
   * > 1 Year                         X Years
   *
   * Single units are +10%. 1 Year shows first at 1 Year + 10%
   */

  function normalize(val, single)
  {
    var margin = 0.1;
    if(val >= single && val <= single * (1+margin)) {
      return single;
    }
    return val;
  }

  for(var i = 0, format = formats[0]; formats[i]; format = formats[++i]) {
    if(seconds < format[0]) {
      if(i === 0) {
        // Now
        return format[1];
      }

      var val = Math.ceil(normalize(seconds, format[3]) / (format[3]));
      return val + ' ' + (val !== 1 ? format[2] : format[1]) + (i > 0 ? token : '');
    }
  }
};
<commit_msg>Added a little utility method to Array from the tubes for removing an element.  Thanks John Resig.<commit_after>/*
 *  Copyright 2011 Rackspace
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var url = require('url');
var util = require('util');

var JSV = require('JSV').JSV;
var _ = require('underscore');
var et = require('elementtree');

var log = require('../log');
var urls = require('../web/urls');
var settings = require('../settings');
var schema = require('../../extern/devopsjson/lib/web/schema').schema;

// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

/**
 * makeClass - By John Resig (MIT Licensed)
 * Takes a function and ensures that new is called so that __this__ is properly bound
 * @param {proto} optional prototype to set on the returned function
 */
exports.make_class = function(proto){
  var f = function(args){
    // did you make me with new?
    if (this instanceof arguments.callee){
      // am I a function?
      if (typeof this.init === "function"){
        //PREGUNTA: why not always pass apply arguments?
        if (args){
          return this.init.apply(this, args.callee ? args : arguments );
        }
        else{
          return this.init.apply(this, arguments);
        }
      }
    } else{
      // didn't use new, return a properly instantiated object
      return new arguments.callee(arguments);
    }
  };
  if (proto){
    f.prototype = proto;
  }
  return f;
};

exports.die = function () {
  var args = Array.prototype.slice.call(arguments);
  args.unshift("DYING because:");
  console.error.apply(null, args);
  process.exit(1);
};

/**
 * Takes a username and password and returns a basic auth token fully formed
 *
 */
exports.create_basic_auth = function(username, password){
  var auth_token = new Buffer(username + ':' + password).toString('base64');
  return "Basic " + auth_token;
};

/**
 * Takes middlware function pointer with arrity of 5 and returns a middleware
 *
 * @param {string} name the name of the middleware (should be unique)
 * @param {fn} the middleware proper which should accept (req, res, next, payload, api_config- set payload.data/errors to cache stuff, api_config = req.devops[name]
 * @return {fn} middleware function
 */
exports.create_middleware = function(name, middleware){
  return function(req, res, next){
    // Do they have the api defined in the devops ball?
    if (!req.devops.related_apis || !req.devops.related_apis[name]) {
      req.devops[name] = null;
      next();
      return;
    }
    var payload = {error: null, data: null};
    req.devops[name] = payload;
    try{
      middleware(req, res, next, payload, req.devops.related_apis[name]);
    }catch (e){
      payload.error = e;
      // TODO: this may not be callable if the error was with express
      next();
    }
  };
};

/** A convenience function for making requests without having to build up response body data.
 * Also useful for mocking the making of requests in tests
 * @param {Object} options an options dict such as http.request would use
 * @param {fn} on_success callback that takes the complete response body data as a parameter
 * @param {fn} on_error callback that takes an Exception as a parameter
 */
exports.request_maker = function(options, call_back) {
  var post_data = "";
  var headers = {};
  var method;
  var req;
  var timeout = 20 * 1000;

  if (options.post_data !== undefined){
    post_data = options.post_data;
    delete options.post_data;

    if (options.dont_stringify === undefined){
      post_data = JSON.stringify(post_data);
    }else{
      delete options.dont_stringify;
    }

  }
  if (options.timeout !== undefined){
    timeout = options.timeout;
    delete options.timeout;
  }
  if (options.method && options.method.toUpperCase() !== "GET"){
    headers['Content-length'] = Buffer.byteLength(post_data, 'utf8');
  }

  if (!options.headers) {
    options.headers = {};
  }
  _.extend(options.headers, headers);

  method = options.port === 80 ? http : https;
  log.debug(options.method, options.host, options.path);

  req = method.request(options, function(res) {
    var data = '';
    res.setEncoding('utf8');
    res.on('data', function(d) {
      data += d;
    });
    res.on('end', function() {
      var request;
      var error;
      if (res.statusCode >= 400) {
        request = options.host + options.path;
        error = Error("Bad status code: " + res.statusCode + " for url: " + request);
      }
      try{
        call_back(error, {data: data, res: res});
      }catch(e){
        call_back(e, {data: data, res: res});
      }
    });
  });
  req.on('error', function(e) {
    call_back(e, null);
  });

  req.on('socket', function (socket) {
    if (timeout !== undefined){
      socket.setTimeout(timeout);
      socket.setMaxListeners(0);
      socket.on('timeout', function() {
        req.abort();
      });
    }
  });

  if (post_data){
    req.write(post_data);
  }
  req.end();
};

/*
 *
 * @param devops_github
 * @param is_closed
 * @returns
 */
exports.github_options = function(api_config, repo, path, uri) {
  var host;
  var parsed_uri;
  if (uri === undefined){
    if (path instanceof Array){
      path = path.join('');
    }
    path = util.format("/repos/%s/%s%s", api_config.org, repo, path);
    host = url.parse(api_config.url).host;
  } else {
    parsed_uri = url.parse(uri);
    path = parsed_uri.path;
    host = parsed_uri.host;
  }

  if (!host || !path){
    throw new Error('Not enough info to build a github request!');
  }

  return {
    return_response: true,
    host: host,
    port: 443,
    path: path,
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': exports.create_basic_auth(api_config.username,
        api_config.apikey)
    }
  };
};
/**
 * @param string
 * @returns new string with first letter capitalized
 */
exports.capitalize = function(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
};

exports.validate_devops = function(devops){
  var jsv_env = JSV.createEnvironment('json-schema-draft-03');
  var report = jsv_env.validate(devops, schema);

  if (report.errors.length > 0) {
    log.error("ERRORS: ", report.errors);
  }
  return report;
};

exports.validate_date = function(date_like){
  var date = new Date(date_like);

  if (isNaN(date.valueOf())){
    throw new Error('not a date', date);
  }
  return date;
};
/*
 * Javascript Humane Dates
 * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 * Re-write by Zach Leatherman (zachleat.com)
 *
 * Adopted from the John Resig's pretty.js
 * at http://ejohn.org/blog/javascript-pretty-date
 * and henrah's proposed modification
 * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 *
 * Licensed under the MIT license.
 */

exports.humane_date = function(date, compareTo){
  var isString = typeof date === 'string';

  if(!date) {
      return;
  }

  date = isString ?
    new Date(date.replace(/-/g,"/").replace(/[TZ]/g," ")) :
    date;
  compareTo = compareTo || new Date();

  var lang = {
    ago: 'Ago',
    from: '',
    now: 'Just Now',
    minute: 'Minute',
    minutes: 'Minutes',
    hour: 'Hour',
    hours: 'Hours',
    day: 'Day',
    days: 'Days',
    week: 'Week',
    weeks: 'Weeks',
    month: 'Month',
    months: 'Months',
    year: 'Year',
    years: 'Years'
  };
  var formats = [
    [60, lang.now],
    [3600, lang.minute, lang.minutes, 60], // 60 minutes, 1 minute
    [86400, lang.hour, lang.hours, 3600], // 24 hours, 1 hour
    [604800, lang.day, lang.days, 86400], // 7 days, 1 day
    [2628000, lang.week, lang.weeks, 604800], // ~1 month, 1 week
    [31536000, lang.month, lang.months, 2628000], // 1 year, ~1 month
    [Infinity, lang.year, lang.years, 31536000] // Infinity, 1 year
  ];
  var seconds = (compareTo - date + (
      compareTo.getTimezoneOffset() -
      // if we received a GMT time from a string, doesn't include time zone bias
      // if we got a date object, the time zone is built in, we need to remove it.
      (isString ? 0 : date.getTimezoneOffset())
    ) * 60000 ) / 1000;
  var token;

  if(seconds < 0) {
    seconds = Math.abs(seconds);
    token = lang.from ? ' ' + lang.from : '';
  } else {
    token = lang.ago ? ' ' + lang.ago : '';
  }

  /*
   * 0 seconds && < 60 seconds        Now
   * 60 seconds                       1 Minute
   * > 60 seconds && < 60 minutes     X Minutes
   * 60 minutes                       1 Hour
   * > 60 minutes && < 24 hours       X Hours
   * 24 hours                         1 Day
   * > 24 hours && < 7 days           X Days
   * 7 days                           1 Week
   * > 7 days && < ~ 1 Month          X Weeks
   * ~ 1 Month                        1 Month
   * > ~ 1 Month && < 1 Year          X Months
   * 1 Year                           1 Year
   * > 1 Year                         X Years
   *
   * Single units are +10%. 1 Year shows first at 1 Year + 10%
   */

  function normalize(val, single)
  {
    var margin = 0.1;
    if(val >= single && val <= single * (1+margin)) {
      return single;
    }
    return val;
  }

  for(var i = 0, format = formats[0]; formats[i]; format = formats[++i]) {
    if(seconds < format[0]) {
      if(i === 0) {
        // Now
        return format[1];
      }

      var val = Math.ceil(normalize(seconds, format[3]) / (format[3]));
      return val + ' ' + (val !== 1 ? format[2] : format[1]) + (i > 0 ? token : '');
    }
  }
};
<|endoftext|>


<commit_before>/*jshint indent:2, curly:true eqeqeq:true, immed:true, latedef:true,
newcap:true, noarg:true, regexp:true, undef:true, trailing:true
white:true*/
/*global enyo:true, XT:true, XV:true, _:true */

(function () {

  enyo.kind({
    name: "XV.Pullout",
    kind: "enyo.Slideable",
    classes: "pullout",
    value: -100,
    min: -100,
    unit: '%',
    events: {
      onHistoryItemSelected: ""
    },
    published: {
      selectedPanel: ""
    },
    components: [
      {name: "shadow", classes: "pullout-shadow"},
      {name: "grabber", kind: "onyx.Grabber", classes: "pullout-grabbutton",
        ondragfinish: "grabberDragFinish"},
      {kind: "FittableRows", classes: "enyo-fit", components: [
        {name: "client", classes: "pullout-toolbar"},
        {classes: "xv-pullout-header", name: "pulloutHeader", content: ""},
        {kind: "Scroller", name: "pulloutItems", fit: true, style: "position: relative;",
          components: [
          {name: "container", components: [
            {name: "history", fit: true, components: [
              {kind: "Repeater", name: "historyList",
                onSetupItem: "setupHistoryItem", count: 0, components: [
                {name: "historyItem"}
              ]}
            ]},
            {name: "help", fit: true, content: "help"}
          ]}
        ]}
      ]}
    ],
    /**
      A parameter widget has been passed to us in an event. Make sure that the payload of the
      event gets added to our pullout items.
     */
    addPulloutItem: function (inSender, inEvent) {
      var child,
        widget = inEvent.getParameterWidget(),
        item = {
          name: inEvent.name,
          fit: true
        };
      if (widget) {
        item.kind = widget;
        item.container = this.$.container;
        // Remove the previous item and create the new one
        child = this.$.container.children[0];
        this.$.container.removeChild(child);
        this._pulloutItems[inEvent.name] = this.createComponent(item);
      }
    },
    /**
      Tries to pre-populate the history list from a cookie if present
     */
    create: function () {
      this.inherited(arguments);
      this._parameterWidgets = {};
      this._pulloutItems = {};
      this._pulloutItems.history = this.$.container.children[0]; // TODO: consider this.$.history
      this._pulloutItems.help = this.$.container.children[1];
      var that = this,
        callback = function () {
          that.preLoadHistory();
        };

      XT.getStartupManager().registerCallback(callback, true);
    },
    preLoadHistory: function () {
      var dbName = XT.session.details.organization,
        cookieValue = enyo.getCookie("history_" + dbName),
        historyArray,
        i,
        historyItem,
        mockModel = {},
        getValue = function () {
          return historyItem.modelName;
        },
        get = function () {
          return historyItem.modelId;
        };

      if (!cookieValue || cookieValue === 'undefined') {
        // There's no cookie yet for this parameter list
        return;
      }

      historyArray = JSON.parse(cookieValue);

      // Running through the array backwards allows us to preserve the
      // reverse chronology of the history list.
      for (i = historyArray.length - 1; i >= 0; i--) {
        historyItem = historyArray[i];
        // The mock model we create here is built to fool XT.addToHistory
        // into thinking it's a real model. This code is fragile to any
        // change in that function.
        mockModel.recordType = historyItem.modelType;
        mockModel.getValue = getValue;
        mockModel.get = get;
        XT.addToHistory(historyItem.workspaceType, mockModel);
      }
      this.refreshHistoryList();
    },
    /**
      Called whenever the pullout is pulled via the dragger. We ensure that if
      no panel is yet selected, we default to the history panel.
    */
    grabberDragFinish: function () {
      var name = this.getSelectedPanel() ? this.getSelectedPanel() : "history";

      this.togglePullout(this, {name: name, show: true});
    },
    /**
      Refreshes the history list.
     */
    refreshHistoryList: function () {
      this.$.historyList.setCount(XT.getHistory().length);
    },
    setupHistoryItem: function (inSender, inEvent) {
      var historyItem = inEvent.item.$.historyItem;
      var historyData = XT.getHistory()[inEvent.index];
      var modelTypeShow = ("_" + historyData.modelType.suffix().camelize()).loc();
      this.createComponent({
        container: historyItem,
        classes: "item enyo-border-box",
        style: "color:white",
        ontap: "doHistoryItemSelected",
        content: modelTypeShow + ": " + historyData.modelName,
        modelType: historyData.modelType,
        id: historyData.modelId,
        workspace: historyData.workspaceType
      });
    },
    getItem: function (name) {
      return this._pulloutItems[name];
    },
    /**

      Sets the pullout to be the appropriate panel (either history or
      the apppropriate advanced search). May or may not show the panel,
      depending on inEvent.show.

      @param {Object} inSender
      @param {Object} inEvent
      @param {String} inEvent.name The name of the panel, or the word "history"
      @param {Boolean} inEvent.show Whether or not we want to show the panel
    */
    togglePullout: function (inSender, inEvent) {
      // Note that if you show history, then move to a list with a panel, then pull the
      // pullout, it will show the advanced search and not history.
      var name = inEvent.name,
        item = this.getItem(name),
        child,
        forceMin = false;

      if (!item) {
        // if we've moved to a list with no advanced search and pull the pullout
        // again show history instead.
        name = "history";
        item = this.getItem(name);

        if (!inEvent.show) {
          // nothing to see here.
          // for example, the user has backed out into the main splash screen
          // just pull back the pullout
          // but we do want the pullout to be set at "history" in case it gets
          // drawn next for a list with no advanced search
          forceMin = true;
        }
      }

      if (name === 'history') {
        this.$.pulloutHeader.setContent("_history".loc());
      } else if (name === 'help') {
        this.$.pulloutHeader.setContent("_help".loc());
      } else {
        this.$.pulloutHeader.setContent("_advancedSearch".loc());
      }
      this.setSelectedPanel(name);
      child = this.$.container.children[0];
      if (forceMin || (item && this.isAtMax() && child.name === item.name)) {
        this.animateToMin();

      } else if (inEvent.show) {
        // remove all children
        while(this.$.container.children.length) {
          child = this.$.container.children[0];
          this.$.container.removeChild(child);
        }
        this.$.container.addChild(item);
        this.$.fittableRows.render();
        if (!this.isAtMax()) {
          this.animateToMax();
        }
      }
    }
  });

}());
<commit_msg>issue #18560: less fragile help and history<commit_after>/*jshint indent:2, curly:true eqeqeq:true, immed:true, latedef:true,
newcap:true, noarg:true, regexp:true, undef:true, trailing:true
white:true*/
/*global enyo:true, XT:true, XV:true, _:true */

(function () {

  enyo.kind({
    name: "XV.Pullout",
    kind: "enyo.Slideable",
    classes: "pullout",
    value: -100,
    min: -100,
    unit: '%',
    events: {
      onHistoryItemSelected: ""
    },
    published: {
      selectedPanel: ""
    },
    components: [
      {name: "shadow", classes: "pullout-shadow"},
      {name: "grabber", kind: "onyx.Grabber", classes: "pullout-grabbutton",
        ondragfinish: "grabberDragFinish"},
      {kind: "FittableRows", classes: "enyo-fit", components: [
        {name: "client", classes: "pullout-toolbar"},
        {classes: "xv-pullout-header", name: "pulloutHeader", content: ""},
        {kind: "Scroller", name: "pulloutItems", fit: true, style: "position: relative;",
          components: [
          {name: "container", components: [
            {name: "history", fit: true, components: [
              {kind: "Repeater", name: "historyList",
                onSetupItem: "setupHistoryItem", count: 0, components: [
                {name: "historyItem"}
              ]}
            ]},
            {name: "help", fit: true, content: "help"}
          ]}
        ]}
      ]}
    ],
    /**
      A parameter widget has been passed to us in an event. Make sure that the payload of the
      event gets added to our pullout items.
     */
    addPulloutItem: function (inSender, inEvent) {
      var child,
        widget = inEvent.getParameterWidget(),
        item = {
          name: inEvent.name,
          fit: true
        };
      if (widget) {
        item.kind = widget;
        item.container = this.$.container;
        // Remove the previous item and create the new one
        child = this.$.container.children[0];
        this.$.container.removeChild(child);
        this._pulloutItems[inEvent.name] = this.createComponent(item);
      }
    },
    /**
      Tries to pre-populate the history list from a cookie if present
     */
    create: function () {
      this.inherited(arguments);
      this._parameterWidgets = {};
      this._pulloutItems = {};
      this._pulloutItems.history = this.$.history;
      this._pulloutItems.help = this.$.help;
      var that = this,
        callback = function () {
          that.preLoadHistory();
        };

      XT.getStartupManager().registerCallback(callback, true);
    },
    preLoadHistory: function () {
      var dbName = XT.session.details.organization,
        cookieValue = enyo.getCookie("history_" + dbName),
        historyArray,
        i,
        historyItem,
        mockModel = {},
        getValue = function () {
          return historyItem.modelName;
        },
        get = function () {
          return historyItem.modelId;
        };

      if (!cookieValue || cookieValue === 'undefined') {
        // There's no cookie yet for this parameter list
        return;
      }

      historyArray = JSON.parse(cookieValue);

      // Running through the array backwards allows us to preserve the
      // reverse chronology of the history list.
      for (i = historyArray.length - 1; i >= 0; i--) {
        historyItem = historyArray[i];
        // The mock model we create here is built to fool XT.addToHistory
        // into thinking it's a real model. This code is fragile to any
        // change in that function.
        mockModel.recordType = historyItem.modelType;
        mockModel.getValue = getValue;
        mockModel.get = get;
        XT.addToHistory(historyItem.workspaceType, mockModel);
      }
      this.refreshHistoryList();
    },
    /**
      Called whenever the pullout is pulled via the dragger. We ensure that if
      no panel is yet selected, we default to the history panel.
    */
    grabberDragFinish: function () {
      var name = this.getSelectedPanel() ? this.getSelectedPanel() : "history";

      this.togglePullout(this, {name: name, show: true});
    },
    /**
      Refreshes the history list.
     */
    refreshHistoryList: function () {
      this.$.historyList.setCount(XT.getHistory().length);
    },
    setupHistoryItem: function (inSender, inEvent) {
      var historyItem = inEvent.item.$.historyItem;
      var historyData = XT.getHistory()[inEvent.index];
      var modelTypeShow = ("_" + historyData.modelType.suffix().camelize()).loc();
      this.createComponent({
        container: historyItem,
        classes: "item enyo-border-box",
        style: "color:white",
        ontap: "doHistoryItemSelected",
        content: modelTypeShow + ": " + historyData.modelName,
        modelType: historyData.modelType,
        id: historyData.modelId,
        workspace: historyData.workspaceType
      });
    },
    getItem: function (name) {
      return this._pulloutItems[name];
    },
    /**

      Sets the pullout to be the appropriate panel (either history or
      the apppropriate advanced search). May or may not show the panel,
      depending on inEvent.show.

      @param {Object} inSender
      @param {Object} inEvent
      @param {String} inEvent.name The name of the panel, or the word "history"
      @param {Boolean} inEvent.show Whether or not we want to show the panel
    */
    togglePullout: function (inSender, inEvent) {
      // Note that if you show history, then move to a list with a panel, then pull the
      // pullout, it will show the advanced search and not history.
      var name = inEvent.name,
        item = this.getItem(name),
        child,
        forceMin = false;

      if (!item) {
        // if we've moved to a list with no advanced search and pull the pullout
        // again show history instead.
        name = "history";
        item = this.getItem(name);

        if (!inEvent.show) {
          // nothing to see here.
          // for example, the user has backed out into the main splash screen
          // just pull back the pullout
          // but we do want the pullout to be set at "history" in case it gets
          // drawn next for a list with no advanced search
          forceMin = true;
        }
      }

      if (name === 'history') {
        this.$.pulloutHeader.setContent("_history".loc());
      } else if (name === 'help') {
        this.$.pulloutHeader.setContent("_help".loc());
      } else {
        this.$.pulloutHeader.setContent("_advancedSearch".loc());
      }
      this.setSelectedPanel(name);
      child = this.$.container.children[0];
      if (forceMin || (item && this.isAtMax() && child.name === item.name)) {
        this.animateToMin();

      } else if (inEvent.show) {
        // remove all children
        while(this.$.container.children.length) {
          child = this.$.container.children[0];
          this.$.container.removeChild(child);
        }
        this.$.container.addChild(item);
        this.$.fittableRows.render();
        if (!this.isAtMax()) {
          this.animateToMax();
        }
      }
    }
  });

}());
<|endoftext|>