<commit_before>/**
 * list_tree_view.js
 * @flow weak
 */

import _ from "lodash";
import React from "react";
import { connect } from "react-redux";
import { Button, Dropdown, Input, Menu } from "antd";
import Window from "libs/window";
import TreesTabItemView from "oxalis/view/right-menu/trees_tab_item_view";
import InputComponent from "oxalis/view/components/input_component";
import { updateUserSettingAction } from "oxalis/model/actions/settings_actions";
import { getActiveTree } from "oxalis/model/accessors/skeletontracing_accessor";
import {
  setTreeNameAction,
  createTreeAction,
  deleteTreeAction,
  shuffleTreeColorAction,
  selectNextTreeAction,
  toggleAllTreesAction,
  toggleInactiveTreesAction,
} from "oxalis/model/actions/skeletontracing_actions";
import type { Dispatch } from "redux";
import Store from "oxalis/store";
import type { OxalisState } from "oxalis/store";

const ButtonGroup = Button.Group;
const InputGroup = Input.Group;

class TreesTabView extends React.Component {
  handleChangeTreeName = evt => {
    this.props.onChangeTreeName(evt.target.value);
  };

  deleteTree = () => {
    if (Window.confirm("Do you really want to delete the whole tree?")) {
      this.props.onDeleteTree();
    }
  };

  shuffleTreeColor = () => {
    getActiveTree(this.props.skeletonTracing).map(activeTree =>
      this.props.onShuffleTreeColor(activeTree.treeId),
    );
  };

  shuffleAllTreeColors = () => {
    for (const tree of _.values(this.props.skeletonTracing.trees)) {
      this.props.onShuffleTreeColor(tree.treeId);
    }
  };

  toggleAllTrees() {
    Store.dispatch(toggleAllTreesAction());
  }

  toggleInactiveTrees() {
    Store.dispatch(toggleInactiveTreesAction());
  }

  getTreesComponents() {
    const orderAttribute = this.props.userConfiguration.sortTreesByName ? "name" : "timestamp";

    return _.orderBy(this.props.skeletonTracing.trees, [orderAttribute], ["asc"]).map(tree =>
      <TreesTabItemView
        key={tree.treeId}
        tree={tree}
        activeTreeId={this.props.skeletonTracing.activeTreeId}
      />,
    );
  }

  handleDropdownClick = ({ key }) => {
    const shouldSortTreesByName = key === "sortByName";
    this.props.onSortTree(shouldSortTreesByName);
  };

  getSettingsDropdown() {
    const activeMenuKey = this.props.userConfiguration.sortTreesByName
      ? "sortByName"
      : "sortByTime";

    return (
      <Menu defaultSelectedKeys={[activeMenuKey]} onSelect={this.handleDropdownClick}>
        <Menu.Item key="sortByName">by name</Menu.Item>
        <Menu.Item key="sortByTime">by creation time</Menu.Item>
      </Menu>
    );
  }

  render() {
    const activeTreeName = getActiveTree(this.props.skeletonTracing)
      .map(activeTree => activeTree.name)
      .getOrElse("");

    return (

        <ButtonGroup>
          <Button onClick={this.props.onCreateTree} title="Create Tree">
             Create
          </Button>
          <Button onClick={this.deleteTree} title="Delete Tree">
             Delete
          </Button>
          <Button onClick={this.shuffleTreeColor} title="Change Tree Color">
             Change Color
          </Button>
          <Button onClick={this.shuffleAllTreeColors} title="Shuffle all Tree Colors">
             Shuffle all Colors
          </Button>
          <Button onClick={this.toggleAllTrees} title="Toggle Visibility of all Trees">
             Toggle all
          </Button>
          <Button onClick={this.toggleInactiveTrees} title="Toggle Visibility of inactive Trees">
             Toggle inactive
          </Button>
        </ButtonGroup>
        <InputGroup compact>
          <Button onClick={this.props.onSelectNextTreeBackward}>

          </Button>
          <InputComponent
            onChange={this.handleChangeTreeName}
            value={activeTreeName}
            style={{ width: "60%" }}
          />
          <Button onClick={this.props.onSelectNextTreeForward}>

          </Button>
          <Dropdown overlay={this.getSettingsDropdown()}>
            <Button title="Sort">

            </Button>
          </Dropdown>
        </InputGroup>

          {this.getTreesComponents()}

    );
  }
}

const mapStateToProps = (state: OxalisState) => ({
  skeletonTracing: state.tracing,
  userConfiguration: state.userConfiguration,
});

const mapDispatchToProps = (dispatch: Dispatch<*>) => ({
  onShuffleTreeColor(treeId) {
    dispatch(shuffleTreeColorAction(treeId));
  },
  onSortTree(shouldSortTreesByName) {
    dispatch(updateUserSettingAction("sortTreesByName", shouldSortTreesByName));
  },
  onSelectNextTreeForward() {
    dispatch(selectNextTreeAction(true));
  },
  onSelectNextTreeBackward() {
    dispatch(selectNextTreeAction(false));
  },
  onCreateTree() {
    dispatch(createTreeAction());
  },
  onDeleteTree() {
    dispatch(deleteTreeAction());
  },
  onChangeTreeName(name) {
    dispatch(setTreeNameAction(name));
  },
});

export default connect(mapStateToProps, mapDispatchToProps)(TreesTabView);
<commit_msg>use title case for button labels<commit_after>/**
 * list_tree_view.js
 * @flow weak
 */

import _ from "lodash";
import React from "react";
import { connect } from "react-redux";
import { Button, Dropdown, Input, Menu } from "antd";
import Window from "libs/window";
import TreesTabItemView from "oxalis/view/right-menu/trees_tab_item_view";
import InputComponent from "oxalis/view/components/input_component";
import { updateUserSettingAction } from "oxalis/model/actions/settings_actions";
import { getActiveTree } from "oxalis/model/accessors/skeletontracing_accessor";
import {
  setTreeNameAction,
  createTreeAction,
  deleteTreeAction,
  shuffleTreeColorAction,
  selectNextTreeAction,
  toggleAllTreesAction,
  toggleInactiveTreesAction,
} from "oxalis/model/actions/skeletontracing_actions";
import type { Dispatch } from "redux";
import Store from "oxalis/store";
import type { OxalisState } from "oxalis/store";

const ButtonGroup = Button.Group;
const InputGroup = Input.Group;

class TreesTabView extends React.Component {
  handleChangeTreeName = evt => {
    this.props.onChangeTreeName(evt.target.value);
  };

  deleteTree = () => {
    if (Window.confirm("Do you really want to delete the whole tree?")) {
      this.props.onDeleteTree();
    }
  };

  shuffleTreeColor = () => {
    getActiveTree(this.props.skeletonTracing).map(activeTree =>
      this.props.onShuffleTreeColor(activeTree.treeId),
    );
  };

  shuffleAllTreeColors = () => {
    for (const tree of _.values(this.props.skeletonTracing.trees)) {
      this.props.onShuffleTreeColor(tree.treeId);
    }
  };

  toggleAllTrees() {
    Store.dispatch(toggleAllTreesAction());
  }

  toggleInactiveTrees() {
    Store.dispatch(toggleInactiveTreesAction());
  }

  getTreesComponents() {
    const orderAttribute = this.props.userConfiguration.sortTreesByName ? "name" : "timestamp";

    return _.orderBy(this.props.skeletonTracing.trees, [orderAttribute], ["asc"]).map(tree =>
      <TreesTabItemView
        key={tree.treeId}
        tree={tree}
        activeTreeId={this.props.skeletonTracing.activeTreeId}
      />,
    );
  }

  handleDropdownClick = ({ key }) => {
    const shouldSortTreesByName = key === "sortByName";
    this.props.onSortTree(shouldSortTreesByName);
  };

  getSettingsDropdown() {
    const activeMenuKey = this.props.userConfiguration.sortTreesByName
      ? "sortByName"
      : "sortByTime";

    return (
      <Menu defaultSelectedKeys={[activeMenuKey]} onSelect={this.handleDropdownClick}>
        <Menu.Item key="sortByName">by name</Menu.Item>
        <Menu.Item key="sortByTime">by creation time</Menu.Item>
      </Menu>
    );
  }

  render() {
    const activeTreeName = getActiveTree(this.props.skeletonTracing)
      .map(activeTree => activeTree.name)
      .getOrElse("");

    return (

        <ButtonGroup>
          <Button onClick={this.props.onCreateTree} title="Create Tree">
             Create
          </Button>
          <Button onClick={this.deleteTree} title="Delete Tree">
             Delete
          </Button>
          <Button onClick={this.shuffleTreeColor} title="Change Tree Color">
             Change Color
          </Button>
          <Button onClick={this.shuffleAllTreeColors} title="Shuffle All Tree Colors">
             Shuffle All Colors
          </Button>
          <Button onClick={this.toggleAllTrees} title="Toggle Visibility of All Trees">
             Toggle All
          </Button>
          <Button onClick={this.toggleInactiveTrees} title="Toggle Visibility of Inactive Trees">
             Toggle Inactive
          </Button>
        </ButtonGroup>
        <InputGroup compact>
          <Button onClick={this.props.onSelectNextTreeBackward}>

          </Button>
          <InputComponent
            onChange={this.handleChangeTreeName}
            value={activeTreeName}
            style={{ width: "60%" }}
          />
          <Button onClick={this.props.onSelectNextTreeForward}>

          </Button>
          <Dropdown overlay={this.getSettingsDropdown()}>
            <Button title="Sort">

            </Button>
          </Dropdown>
        </InputGroup>

          {this.getTreesComponents()}

    );
  }
}

const mapStateToProps = (state: OxalisState) => ({
  skeletonTracing: state.tracing,
  userConfiguration: state.userConfiguration,
});

const mapDispatchToProps = (dispatch: Dispatch<*>) => ({
  onShuffleTreeColor(treeId) {
    dispatch(shuffleTreeColorAction(treeId));
  },
  onSortTree(shouldSortTreesByName) {
    dispatch(updateUserSettingAction("sortTreesByName", shouldSortTreesByName));
  },
  onSelectNextTreeForward() {
    dispatch(selectNextTreeAction(true));
  },
  onSelectNextTreeBackward() {
    dispatch(selectNextTreeAction(false));
  },
  onCreateTree() {
    dispatch(createTreeAction());
  },
  onDeleteTree() {
    dispatch(deleteTreeAction());
  },
  onChangeTreeName(name) {
    dispatch(setTreeNameAction(name));
  },
});

export default connect(mapStateToProps, mapDispatchToProps)(TreesTabView);
<|endoftext|>


<commit_before>//----------------------------------------------------------------------------
//  Copyright (C) 2008-2011  The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// Kernel Status widget
//============================================================================

var IPython = (function (IPython) {

    var utils = IPython.utils;

    var KernelStatusWidget = function (selector) {
        this.selector = selector;
        if (this.selector !== undefined) {
            this.element = $(selector);
            this.style();
        }
    };

    KernelStatusWidget.prototype.style = function () {
        this.element.addClass('ui-widget');
        this.element.attr('title', "The kernel execution status." +
        " If 'Busy', the kernel is currently running code." +
        " If 'Idle', it is available for execution.");
    };

    KernelStatusWidget.prototype.status_busy = function () {
        this.element.removeClass("status_idle");
        this.element.removeClass("status_restarting");
        this.element.addClass("status_busy");
        this.element.text("Busy");
    };

    KernelStatusWidget.prototype.status_idle = function () {
        this.element.removeClass("status_busy");
        this.element.removeClass("status_restarting");
        this.element.addClass("status_idle");
        this.element.text("Idle");
    };

    KernelStatusWidget.prototype.status_restarting = function () {
        this.element.removeClass("status_busy");
        this.element.removeClass("status_idle");
        this.element.addClass("status_restarting");
        this.element.text("Restarting");
    };

    IPython.KernelStatusWidget = KernelStatusWidget;

    return IPython;

}(IPython));

<commit_msg>write busy in notebook window title<commit_after>//----------------------------------------------------------------------------
//  Copyright (C) 2008-2011  The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// Kernel Status widget
//============================================================================

var IPython = (function (IPython) {

    var utils = IPython.utils;

    var KernelStatusWidget = function (selector) {
        this.selector = selector;
        if (this.selector !== undefined) {
            this.element = $(selector);
            this.style();
        }
    };

    KernelStatusWidget.prototype.style = function () {
        this.element.addClass('ui-widget');
        this.element.attr('title', "The kernel execution status." +
        " If 'Busy', the kernel is currently running code." +
        " If 'Idle', it is available for execution.");
    };

    KernelStatusWidget.prototype.status_busy = function () {
        this.element.removeClass("status_idle");
        this.element.removeClass("status_restarting");
        this.element.addClass("status_busy");
        window.document.title='(Busy) '+window.document.title;
        this.element.text("Busy");
    };

    KernelStatusWidget.prototype.status_idle = function () {
        this.element.removeClass("status_busy");
        this.element.removeClass("status_restarting");
        this.element.addClass("status_idle");
        IPython.save_widget.set_document_title();
        this.element.text("Idle");
    };

    KernelStatusWidget.prototype.status_restarting = function () {
        this.element.removeClass("status_busy");
        this.element.removeClass("status_idle");
        this.element.addClass("status_restarting");
        this.element.text("Restarting");
    };

    IPython.KernelStatusWidget = KernelStatusWidget;

    return IPython;

}(IPython));

<|endoftext|>


<commit_before>var React   = require('react/addons'),
    scroll  = require('scroll'),
    Beacon  = require('./Beacon'),
    Tooltip = require('./Tooltip');

var joyride = {
    browser: undefined,
    initialized: false,
    mixin: false,
    steps: [],

    getBrowser: function () {
        // Return cached result if avalible, else get result then cache it.
        if (this.browser) {
            return this.browser;
        }

        var isOpera = Boolean(window.opera) || navigator.userAgent.indexOf(' OPR/') >= 0;
        // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
        var isFirefox = typeof InstallTrigger !== 'undefined';// Firefox 1.0+
        var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
        // At least Safari 3+: "[object HTMLElementConstructor]"
        var isChrome = Boolean(window.chrome) && !isOpera;// Chrome 1+
        var isIE = /*@cc_on!@*/false || Boolean(document.documentMode); // At least IE6

        return (this.browser =
            isOpera ? 'opera' :
                isFirefox ? 'firefox' :
                    isSafari ? 'safari' :
                        isChrome ? 'chrome' :
                            isIE ? 'ie' :
                                '');
    },

    getElementDimensions: function (el) {
        // Get the DOM Node if you pass in a string
        el = (typeof el === 'string') ? document.querySelector(el) : el;

        var styles = window.getComputedStyle(el),
            height = el.clientHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10),
            width  = el.clientWidth + parseInt(styles.marginLeft, 10) + parseInt(styles.marginRight, 10);

        return {
            height: height,
            width: width
        };
    },

    keyboardNavigation: function (e) {
        var intKey = (window.Event) ? e.which : e.keyCode,
            hasSteps;

        if (this.state.joyrideShowTooltip) {
            if ([32, 38, 40].indexOf(intKey) > -1) {
                e.preventDefault();
            }

            if (intKey === 27) {
                joyride.toggleTooltip(false, this.state.joyrideCurrentIndex + 1);
            }
            else if ([13, 32].indexOf(intKey) > -1) {
                hasSteps = Boolean(joyride.steps[this.state.joyrideCurrentIndex + 1]);
                joyride.toggleTooltip(hasSteps, this.state.joyrideCurrentIndex + 1);
            }
        }
    },
    preventWindowOverflow: function (value, axis, elWidth, elHeight) {
        var winWidth  = window.innerWidth,
            docHeight = document.body.offsetHeight,
            newValue  = value;

        if (axis === 'x') {
            if (value + elWidth >= winWidth) {
                newValue = winWidth - elWidth - 15;
            }
            else if (value < 0) {
                newValue = 15;
            }
        }
        else if (axis === 'y') {
            if (value + elHeight >= docHeight) {
                newValue = docHeight - elHeight - 15;
            }
            else if (value < 0) {
                newValue = 15;
            }
        }

        return newValue;
    }
};

var Mixin = {
    getInitialState: function () {
        return {
            joyrideCurrentIndex: 0,
            joyrideKeyboardNavigation: true,
            joyrideLocale: {
                back: 'Back',
                close: 'Close',
                last: 'Last',
                next: 'Next'
            },
            joyrideOverridePosition: false,
            joyridePlay: false,
            joyrideScrollToSteps: true,
            joyrideScrollOffset: 20,
            joyrideShowBackButton: true,
            joyrideShowOverlay: true,
            joyrideShowStepsProgress: false,
            joyrideShowTooltip: false,
            joyrideTooltipOffset: 15,
            joyrideType: 'guided',
            joyrideCompleteCallback: undefined,
            joyrideStepCallback: undefined,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        };
    },

    componentDidMount: function () {
        var state = this.state;

        this._target = document.createElement('div');
        this._target.className = 'joyride';
        document.body.appendChild(this._target);

        if (state.joyridePlay && joyride.steps[state.joyrideCurrentIndex]) {
            this._renderLayer();
        }

        joyride.mixin = this;
        window.addEventListener('resize', this.joyrideCalcPlacement);

        if (this.state.joyrideKeyboardNavigation) {
            document.body.addEventListener('keydown', joyride.keyboardNavigation.bind(this));
        }
    },

    componentWillUnmount: function () {
        this._unrenderLayer();
        document.body.removeChild(this._target);

        window.removeEventListener('resize', this.joyrideCalcPlacement);
        if (this.state.joyrideKeyboardNavigation) {
            document.body.removeEventListener('keydown', joyride.keyboardNavigation.bind(this));
        }
    },

    componentDidUpdate: function (prevProps, prevState) {
        var state = this.state,
            opt   = {
                shouldRun: state.joyridePlay && (state.joyridePlay !== prevState.joyridePlay || state.joyrideCurrentIndex !== prevState.joyrideCurrentIndex),
                hasStep: Boolean(joyride.steps[state.joyrideCurrentIndex]),
                hasSteps: joyride.steps.length > 0,
                newX: state.joyrideXPos !== prevState.joyrideXPos,
                newY: state.joyrideYPos !== prevState.joyrideYPos,
                toggleTooltip: state.joyrideShowTooltip !== prevState.joyrideShowTooltip
            };

        if ((opt.shouldRun || opt.toggleTooltip || opt.newX || opt.newY) && opt.hasStep) {
            this._renderLayer();
        }
        else if (opt.shouldRun && opt.hasSteps && !opt.hasStep) {
            if (typeof state.joyrideCompleteCallback === 'function') {
                state.joyrideCompleteCallback(joyride.steps);
            }
            this._unrenderLayer();
        }
    },

    /**
     * Starts the tour
     * @param {boolean} [autorun]- Starts with the first tooltip opened
     */
    joyrideStart: function (autorun) {
        autorun = autorun || false;

        this.setState({
            joyrideShowTooltip: autorun,
            joyridePlay: true
        });
    },

    /**
     * Add Steps
     * @param {object|array} steps - Steps to add to the tour
     * @param {boolean} [start] - Starts the tour right away
     */
    joyrideAddSteps: function (steps, start) {
        var tmpSteps = [],
            el;

        start = start || this.state.joyridePlay;

        if (Array.isArray(steps)) {
            steps.forEach(function (s) {
                if (s instanceof Object) {
                    tmpSteps.push(s);
                }
            });
        }
        else {
            tmpSteps = [steps];
        }

        tmpSteps.forEach(function (s) {
            el = document.querySelector(s.selector);
            s.position = s.position || 'top';

            if (el && el.offsetParent) {
                joyride.steps.push(s);
            }
        });

        this.setState(
            React.addons.update(this.state, {
                joyridePlay: { $set: start }
            })
        );
    },

    /**
     * Retrieve the current progress of your tour
     * @returns {{index: (number|*), percentageComplete: number, step: (object|null)}}
     */
    joyrideGetProgress: function () {
        return {
            index: this.state.joyrideCurrentIndex,
            percentageComplete: parseFloat(((this.state.joyrideCurrentIndex / joyride.steps.length) * 100).toFixed(2).replace('.00', '')),
            step: joyride.steps[this.state.joyrideCurrentIndex]
        };
    },

    _onClickBeacon: function (e) {
        e.preventDefault();

        this.setState({
            joyrideShowTooltip: true,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        });
    },

    _onClickTooltip: function (e) {
        e.preventDefault();
        e.stopPropagation();

        var state    = this.state,
            type     = e.currentTarget.getAttribute('data-type'),
            newIndex = state.joyrideCurrentIndex + (type === 'back' ? -1 : 1);

        if (e.target === e.currentTarget) {
            if (typeof state.joyrideStepCallback === 'function') {
                state.joyrideStepCallback(joyride.steps[state.joyrideCurrentIndex]);
            }

            this.setState({
                joyrideShowTooltip: state.joyrideType === 'guided' && type !== 'close' && Boolean(joyride.steps[newIndex]),
                joyrideCurrentIndex: newIndex,
                joyrideXPos: -1000,
                joyrideYPos: -1000
            });
        }
    },

    _calculatePlacement: function () {
        var state     = this.state,
            step      = joyride.steps[state.joyrideCurrentIndex],
            component = document.querySelector((state.joyrideShowTooltip ? '.joyride-tooltip' : '.joyride-beacon')),
            position,
            body,
            target,
            placement = {
                x: -1000,
                y: -1000
            };

        if (step && (!/animate/.test(component.className) || state.joyrideXPos < 0)) {
            position = step.position;
            body = document.body.getBoundingClientRect();
            target = document.querySelector(step.selector).getBoundingClientRect();
            component = joyride.getElementDimensions((state.joyrideShowTooltip ? '.joyride-tooltip' : '.joyride-beacon'));

            // Change the step position in the tooltip won't fit in the window
            if (/^left/.test(position) && target.left - (component.width + state.joyrideTooltipOffset) < 0) {
                position = 'top';
            }
            else if (/^right/.test(position) && target.left + target.width + (component.width + state.joyrideTooltipOffset) > body.width) {
                position = 'bottom';
            }

            // Calculate x position
            if (/^left/.test(position)) {
                placement.x = target.left - (state.joyrideShowTooltip ? component.width + state.joyrideTooltipOffset : component.width / 2);
            }
            else if (/^right/.test(position)) {
                placement.x = target.left + target.width - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.width / 2);
            }
            else {
                placement.x = target.left + target.width / 2 - component.width / 2;
            }

            // Calculate y position
            if (/^top/.test(position)) {
                placement.y = (target.top - body.top) - (state.joyrideShowTooltip ? component.height + state.joyrideTooltipOffset : component.height / 2);
            }
            else if (/^bottom/.test(position)) {
                placement.y = (target.top - body.top) + target.height - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.height / 2);
            }
            else {
                placement.y = (target.top - body.top) + target.height / 2 - component.height / 2 + (state.joyrideShowTooltip ? state.joyrideTooltipOffset : 0);
            }

            if (/^bottom|^top/.test(position)) {
                if (/left/.test(position)) {
                    placement.x = target.left - (state.joyrideShowTooltip ? state.joyrideTooltipOffset : component.width / 2);
                }
                else if (/right/.test(position)) {
                    placement.x = target.left + target.width - (state.joyrideShowTooltip ? component.width - state.joyrideTooltipOffset : component.width / 2);
                }
            }

            this.setState({
                joyrideXPos: joyride.preventWindowOverflow(Math.ceil(placement.x), 'x', component.width, component.height),
                joyrideYPos: joyride.preventWindowOverflow(Math.ceil(placement.y), 'y', component.width, component.height),
                joyrideOverridePosition: step.position !== position ? position : false
            });
        }
    },

    _getScrollTop: function () {
        var step      = joyride.steps[this.state.joyrideCurrentIndex],
            position  = this.state.joyrideOverridePosition || step.position,
            target    = document.querySelector(step.selector),
            targetTop = target.getBoundingClientRect().top + document.body.scrollTop,
            scrollTop = 0;

        if (/^top/.test(position)) {
            scrollTop = Math.floor(this.state.joyrideYPos - this.state.joyrideScrollOffset);
        }
        else if (/^bottom|^left|^right/.test(position)) {
            scrollTop = Math.floor(targetTop - this.state.joyrideScrollOffset);
        }

        return scrollTop;
    },

    _scrollToNextStep: function (cb) {
        if (joyride.steps[this.state.joyrideCurrentIndex] && this.state.joyrideScrollToSteps) {
            scroll.top(document.body, this._getScrollTop(), cb);
        }
    },

    _renderLayer: function () {
        var component = this._renderCurrentStep();
        if (!joyride.initialized) {
            joyride.initialized = true;
            React.renderToString(component);
        }

        React.render(component, this._target, function () {
            this._calculatePlacement();
            this._scrollToNextStep();
        }.bind(this));
    },

    _unrenderLayer: function () {
        React.unmountComponentAtNode(this._target);
    },

    _renderCurrentStep: function () {
        var state       = this.state,
            component,
            currentStep = joyride.steps[state.joyrideCurrentIndex],
            buttons     = {
                primary: state.joyrideLocale.close
            },
            target      = currentStep && currentStep.selector ? document.querySelector(currentStep.selector) : null,
            cssPosition = target ? target.style.position : null;

        if (target) {
            if (state.joyrideShowTooltip) {
                if (state.joyrideType === 'guided') {
                    buttons.primary = state.joyrideLocale.last;

                    if (Boolean(joyride.steps[state.joyrideCurrentIndex + 1])) {
                        buttons.primary = state.joyrideLocale.next;

                        if (state.joyrideShowStepsProgress) {
                            buttons.primary += ' ' + (state.joyrideCurrentIndex + 1) + '/' + joyride.steps.length;
                        }
                    }

                    if (state.joyrideShowBackButton && state.joyrideCurrentIndex > 0) {
                        buttons.secondary = state.joyrideLocale.back;
                    }
                }

                component = React.createElement(Tooltip, {
                    animate: state.joyrideXPos > -1,
                    browser: joyride.getBrowser(),
                    buttons: buttons,
                    cssPosition: cssPosition,
                    overridePosition: state.joyrideOverridePosition,
                    showOverlay: state.joyrideShowOverlay,
                    step: currentStep,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    onClick: this._onClickTooltip
                });
            }
            else {
                component = React.createElement(Beacon, {
                    cssPosition: cssPosition,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    handleClick: this._onClickBeacon
                });
            }
        }

        return component;
    }
};

module.exports = Mixin;
<commit_msg>rename mixin shared functions<commit_after>var React   = require('react/addons'),
    scroll  = require('scroll'),
    Beacon  = require('./Beacon'),
    Tooltip = require('./Tooltip');

var joyride = {
    browser: undefined,
    initialized: false,
    mixin: false,
    steps: [],

    getBrowser: function () {
        // Return cached result if avalible, else get result then cache it.
        if (this.browser) {
            return this.browser;
        }

        var isOpera = Boolean(window.opera) || navigator.userAgent.indexOf(' OPR/') >= 0;
        // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
        var isFirefox = typeof InstallTrigger !== 'undefined';// Firefox 1.0+
        var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
        // At least Safari 3+: "[object HTMLElementConstructor]"
        var isChrome = Boolean(window.chrome) && !isOpera;// Chrome 1+
        var isIE = /*@cc_on!@*/false || Boolean(document.documentMode); // At least IE6

        return (this.browser =
            isOpera ? 'opera' :
                isFirefox ? 'firefox' :
                    isSafari ? 'safari' :
                        isChrome ? 'chrome' :
                            isIE ? 'ie' :
                                '');
    },

    getElementDimensions: function (el) {
        // Get the DOM Node if you pass in a string
        el = (typeof el === 'string') ? document.querySelector(el) : el;

        var styles = window.getComputedStyle(el),
            height = el.clientHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10),
            width  = el.clientWidth + parseInt(styles.marginLeft, 10) + parseInt(styles.marginRight, 10);

        return {
            height: height,
            width: width
        };
    },

    keyboardNavigation: function (e) {
        var intKey = (window.Event) ? e.which : e.keyCode,
            hasSteps;

        if (this.state.joyrideShowTooltip) {
            if ([32, 38, 40].indexOf(intKey) > -1) {
                e.preventDefault();
            }

            if (intKey === 27) {
                joyride.toggleTooltip(false, this.state.joyrideCurrentIndex + 1);
            }
            else if ([13, 32].indexOf(intKey) > -1) {
                hasSteps = Boolean(joyride.steps[this.state.joyrideCurrentIndex + 1]);
                joyride.toggleTooltip(hasSteps, this.state.joyrideCurrentIndex + 1);
            }
        }
    },
    preventWindowOverflow: function (value, axis, elWidth, elHeight) {
        var winWidth  = window.innerWidth,
            docHeight = document.body.offsetHeight,
            newValue  = value;

        if (axis === 'x') {
            if (value + elWidth >= winWidth) {
                newValue = winWidth - elWidth - 15;
            }
            else if (value < 0) {
                newValue = 15;
            }
        }
        else if (axis === 'y') {
            if (value + elHeight >= docHeight) {
                newValue = docHeight - elHeight - 15;
            }
            else if (value < 0) {
                newValue = 15;
            }
        }

        return newValue;
    }
};

var Mixin = {
    getInitialState: function () {
        return {
            joyrideCurrentIndex: 0,
            joyrideKeyboardNavigation: true,
            joyrideLocale: {
                back: 'Back',
                close: 'Close',
                last: 'Last',
                next: 'Next'
            },
            joyrideOverridePosition: false,
            joyridePlay: false,
            joyrideScrollToSteps: true,
            joyrideScrollOffset: 20,
            joyrideShowBackButton: true,
            joyrideShowOverlay: true,
            joyrideShowStepsProgress: false,
            joyrideShowTooltip: false,
            joyrideTooltipOffset: 15,
            joyrideType: 'guided',
            joyrideCompleteCallback: undefined,
            joyrideStepCallback: undefined,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        };
    },

    componentDidMount: function () {
        var state = this.state;

        this._target = document.createElement('div');
        this._target.className = 'joyride';
        document.body.appendChild(this._target);

        if (state.joyridePlay && joyride.steps[state.joyrideCurrentIndex]) {
            this.joyrideRenderLayer();
        }

        joyride.mixin = this;
        window.addEventListener('resize', this.joyrideCalcPlacement);

        if (this.state.joyrideKeyboardNavigation) {
            document.body.addEventListener('keydown', joyride.keyboardNavigation.bind(this));
        }
    },

    componentWillUnmount: function () {
        this.joyrideUnrenderLayer();
        document.body.removeChild(this._target);

        window.removeEventListener('resize', this.joyrideCalcPlacement);
        if (this.state.joyrideKeyboardNavigation) {
            document.body.removeEventListener('keydown', joyride.keyboardNavigation.bind(this));
        }
    },

    componentDidUpdate: function (prevProps, prevState) {
        var state = this.state,
            opt   = {
                shouldRun: state.joyridePlay && (state.joyridePlay !== prevState.joyridePlay || state.joyrideCurrentIndex !== prevState.joyrideCurrentIndex),
                hasStep: Boolean(joyride.steps[state.joyrideCurrentIndex]),
                hasSteps: joyride.steps.length > 0,
                newX: state.joyrideXPos !== prevState.joyrideXPos,
                newY: state.joyrideYPos !== prevState.joyrideYPos,
                toggleTooltip: state.joyrideShowTooltip !== prevState.joyrideShowTooltip
            };

        if ((opt.shouldRun || opt.toggleTooltip || opt.newX || opt.newY) && opt.hasStep) {
            this.joyrideRenderLayer();
        }
        else if (opt.shouldRun && opt.hasSteps && !opt.hasStep) {
            if (typeof state.joyrideCompleteCallback === 'function') {
                state.joyrideCompleteCallback(joyride.steps);
            }
            this.joyrideUnrenderLayer();
        }
    },

    /**
     * Starts the tour
     * @param {boolean} [autorun]- Starts with the first tooltip opened
     */
    joyrideStart: function (autorun) {
        autorun = autorun || false;

        this.setState({
            joyrideShowTooltip: autorun,
            joyridePlay: true
        });
    },

    /**
     * Add Steps
     * @param {object|array} steps - Steps to add to the tour
     * @param {boolean} [start] - Starts the tour right away
     */
    joyrideAddSteps: function (steps, start) {
        var tmpSteps = [],
            el;

        start = start || this.state.joyridePlay;

        if (Array.isArray(steps)) {
            steps.forEach(function (s) {
                if (s instanceof Object) {
                    tmpSteps.push(s);
                }
            });
        }
        else {
            tmpSteps = [steps];
        }

        tmpSteps.forEach(function (s) {
            el = document.querySelector(s.selector);
            s.position = s.position || 'top';

            if (el && el.offsetParent) {
                joyride.steps.push(s);
            }
        });

        this.setState(
            React.addons.update(this.state, {
                joyridePlay: { $set: start }
            })
        );
    },

    /**
     * Retrieve the current progress of your tour
     * @returns {{index: (number|*), percentageComplete: number, step: (object|null)}}
     */
    joyrideGetProgress: function () {
        return {
            index: this.state.joyrideCurrentIndex,
            percentageComplete: parseFloat(((this.state.joyrideCurrentIndex / joyride.steps.length) * 100).toFixed(2).replace('.00', '')),
            step: joyride.steps[this.state.joyrideCurrentIndex]
        };
    },

    _onClickBeacon: function (e) {
        e.preventDefault();

        this.setState({
            joyrideShowTooltip: true,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        });
    },

    _onClickTooltip: function (e) {
        e.preventDefault();
        e.stopPropagation();

        var state    = this.state,
            type     = e.currentTarget.getAttribute('data-type'),
            newIndex = state.joyrideCurrentIndex + (type === 'back' ? -1 : 1);

        if (e.target === e.currentTarget) {
            if (typeof state.joyrideStepCallback === 'function') {
                state.joyrideStepCallback(joyride.steps[state.joyrideCurrentIndex]);
            }

            this.setState({
                joyrideShowTooltip: state.joyrideType === 'guided' && type !== 'close' && Boolean(joyride.steps[newIndex]),
                joyrideCurrentIndex: newIndex,
                joyrideXPos: -1000,
                joyrideYPos: -1000
            });
        }
    },

    joyrideCalcPlacement: function () {
        var state     = this.state,
            step      = joyride.steps[state.joyrideCurrentIndex],
            component = document.querySelector((state.joyrideShowTooltip ? '.joyride-tooltip' : '.joyride-beacon')),
            position,
            body,
            target,
            placement = {
                x: -1000,
                y: -1000
            };

        if (step && (!/animate/.test(component.className) || state.joyrideXPos < 0)) {
            position = step.position;
            body = document.body.getBoundingClientRect();
            target = document.querySelector(step.selector).getBoundingClientRect();
            component = joyride.getElementDimensions((state.joyrideShowTooltip ? '.joyride-tooltip' : '.joyride-beacon'));

            // Change the step position in the tooltip won't fit in the window
            if (/^left/.test(position) && target.left - (component.width + state.joyrideTooltipOffset) < 0) {
                position = 'top';
            }
            else if (/^right/.test(position) && target.left + target.width + (component.width + state.joyrideTooltipOffset) > body.width) {
                position = 'bottom';
            }

            // Calculate x position
            if (/^left/.test(position)) {
                placement.x = target.left - (state.joyrideShowTooltip ? component.width + state.joyrideTooltipOffset : component.width / 2);
            }
            else if (/^right/.test(position)) {
                placement.x = target.left + target.width - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.width / 2);
            }
            else {
                placement.x = target.left + target.width / 2 - component.width / 2;
            }

            // Calculate y position
            if (/^top/.test(position)) {
                placement.y = (target.top - body.top) - (state.joyrideShowTooltip ? component.height + state.joyrideTooltipOffset : component.height / 2);
            }
            else if (/^bottom/.test(position)) {
                placement.y = (target.top - body.top) + target.height - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.height / 2);
            }
            else {
                placement.y = (target.top - body.top) + target.height / 2 - component.height / 2 + (state.joyrideShowTooltip ? state.joyrideTooltipOffset : 0);
            }

            if (/^bottom|^top/.test(position)) {
                if (/left/.test(position)) {
                    placement.x = target.left - (state.joyrideShowTooltip ? state.joyrideTooltipOffset : component.width / 2);
                }
                else if (/right/.test(position)) {
                    placement.x = target.left + target.width - (state.joyrideShowTooltip ? component.width - state.joyrideTooltipOffset : component.width / 2);
                }
            }

            this.setState({
                joyrideXPos: joyride.preventWindowOverflow(Math.ceil(placement.x), 'x', component.width, component.height),
                joyrideYPos: joyride.preventWindowOverflow(Math.ceil(placement.y), 'y', component.width, component.height),
                joyrideOverridePosition: step.position !== position ? position : false
            });
        }
    },

    _getScrollTop: function () {
        var step      = joyride.steps[this.state.joyrideCurrentIndex],
            position  = this.state.joyrideOverridePosition || step.position,
            target    = document.querySelector(step.selector),
            targetTop = target.getBoundingClientRect().top + document.body.scrollTop,
            scrollTop = 0;

        if (/^top/.test(position)) {
            scrollTop = Math.floor(this.state.joyrideYPos - this.state.joyrideScrollOffset);
        }
        else if (/^bottom|^left|^right/.test(position)) {
            scrollTop = Math.floor(targetTop - this.state.joyrideScrollOffset);
        }

        return scrollTop;
    },

    _scrollToNextStep: function (cb) {
        if (joyride.steps[this.state.joyrideCurrentIndex] && this.state.joyrideScrollToSteps) {
            scroll.top(document.body, this._getScrollTop(), cb);
        }
    },

    joyrideRenderLayer: function () {
        var component = this.joyrideRenderStep();
        if (!joyride.initialized) {
            joyride.initialized = true;
            React.renderToString(component);
        }

        React.render(component, this._target, function () {
            this._scrollToNextStep();
            this.joyrideCalcPlacement();
        }.bind(this));
    },

    joyrideUnrenderLayer: function () {
        React.unmountComponentAtNode(this._target);
    },

    joyrideRenderStep: function () {
        var state       = this.state,
            component,
            currentStep = joyride.steps[state.joyrideCurrentIndex],
            buttons     = {
                primary: state.joyrideLocale.close
            },
            target      = currentStep && currentStep.selector ? document.querySelector(currentStep.selector) : null,
            cssPosition = target ? target.style.position : null;

        if (target) {
            if (state.joyrideShowTooltip) {
                if (state.joyrideType === 'guided') {
                    buttons.primary = state.joyrideLocale.last;

                    if (Boolean(joyride.steps[state.joyrideCurrentIndex + 1])) {
                        buttons.primary = state.joyrideLocale.next;

                        if (state.joyrideShowStepsProgress) {
                            buttons.primary += ' ' + (state.joyrideCurrentIndex + 1) + '/' + joyride.steps.length;
                        }
                    }

                    if (state.joyrideShowBackButton && state.joyrideCurrentIndex > 0) {
                        buttons.secondary = state.joyrideLocale.back;
                    }
                }

                component = React.createElement(Tooltip, {
                    animate: state.joyrideXPos > -1,
                    browser: joyride.getBrowser(),
                    buttons: buttons,
                    cssPosition: cssPosition,
                    overridePosition: state.joyrideOverridePosition,
                    showOverlay: state.joyrideShowOverlay,
                    step: currentStep,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    onClick: this._onClickTooltip
                });
            }
            else {
                component = React.createElement(Beacon, {
                    cssPosition: cssPosition,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    handleClick: this._onClickBeacon
                });
            }
        }

        return component;
    }
};

module.exports = Mixin;
<|endoftext|>


<commit_before>/*jslint browser: true, on: true, eqeqeq: true, newcap: true, immed: true */

/*
	TODOs:

	Fo sho:
		- page controls
		- chrome frame / ExplorerCanvas / non canvas version?
		- show loading graphic & then fade in new page if user is ahead of the last loaded page
		- check for html5 feature support where used: diveintohtml5.org/everything.html or www.modernizr.com
		- remember position (use localStorage/cookie/sql)
		- really need to speed up enhancements, try to use webworkers

	Nice 2 have:
		- offline access
		- thumbnail browser
		- page turn animation? (http://www.cynergysystems.com/blogs/page/rickbarraza?entry=the_secret_behind_the_page)
*/

/**
 * Merge two arrays. Any properties in b will replace the same properties in
 * a. New properties from b will be added to a.
 *
 * @param a {Object}
 * @param b {Object}
 */
function merge(a, b) {

	var prop;

	if (typeof b === "undefined") { b = {}; }

	for (prop in a) {
		if (a.hasOwnProperty(prop)) {
			if (prop in b) { continue; }
			b[prop] = a[prop];
		}
	}

	return b;
}

function ComicBook(id, srcs, opts) {

	var canvas_id = id;		// canvas element id
	this.srcs = srcs;	// array of image srcs for pages

	var defaults = {
		displayMode: "double",	// single / double
		zoomMode: "fitWidth",	// manual / fitWidth
		manga: false,			// true / false
		enhance: {}
	};

	var options = merge(defaults, opts); // options array for internal use

	var no_pages = srcs.length;
	var pages = [];		// array of preloaded Image objects
	var canvas;			// the HTML5 canvas object
	var context;		// the 2d drawing context

	var buffer = 4;		// image preload buffer level
	var pointer = 0;	// the current page
	var loaded = 0;		// the amount of images that have been loaded so far

	var scale = 1;		// page zoom scale, 1 = 100%

	/**
	 * Figure out the cursor position relative to the canvas.
	 *
	 * Thanks to: Mark Pilgrim & http://diveintohtml5.org/canvas.html
	 */
	function getCursorPosition(e) {

		var x; // horizontal cursor position

		// check if page relative positions exist, if not figure them out
		if (e.pageX) {
			x = e.pageX;
		} else {
			x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
		}

		// make the position relative to the canvas
		x -= canvas.offsetLeft;

		// check if the user clicked on the left or right side
		return (x <= canvas.width / 2) ? 'left' : 'right';
	}

	/**
	 * Setup the canvas element for use throughout the class.
	 *
	 * @see #ComicBook.prototype.draw
	 * @see #ComicBook.prototype.enhance
	 */
	function init() {

		// setup canvas
		canvas = document.getElementById(canvas_id);
		context = canvas.getContext("2d");

		// add page controls
		canvas.addEventListener("click", ComicBook.prototype.navigation, false);
	}

	/*
	 * @param {String} id The canvas ID to draw the comic on.
	 * @param {Object} srcs An array of all the comic page srcs, in order
	 * @see #preload
	 */
	ComicBook.prototype.draw = function () {

		init();

		// preload images if needed
		if (pages.length !== no_pages) { this.preload(this.srcs); }
		else { this.drawPage(); }
	};

	/*
	 * Zoom the canvas
	 *
	 * @param new_scale {Number} Scale the canvas to this ratio
	 */
	ComicBook.prototype.zoom = function (new_scale) {
		options.zoomMode = "manual";
		scale = new_scale;
		if (typeof pages[pointer] === "object") { this.drawPage(); }
	};

	/**
	 * Preload all images, draw the page only after a given number have been loaded.
	 *
	 * @param srcs {Object} srcs
	 * @see #drawPage
	 */
	ComicBook.prototype.preload = function (srcs) {

		if (no_pages < buffer) { buffer = no_pages; } // don't get stuck if the buffer level is higher than the number of pages

		var i = 0; // the current page counter for this method

		// show load status panel
		if ($("#status").length === 0) {
			$(canvas).after('

');
		}

		// I am using recursion instead of a forEach loop so that the next image is
		// only loaded when the previous one has completely finished
		function preload(i) {

			var page = new Image();

			$("#status p").text("loading page " + (i + 1) + " of " + no_pages);

			page.src = srcs[i];

			page.onload = function () {

				// console.info("loaded: " + srcs[i]);

				pages[i] = this;
				loaded++;

				// there are still more pages to load, do it
				if (loaded < no_pages) {
					i++;
					preload(i);
				}

				// start rendering the comic when the buffer level has been reached
				if (loaded === buffer) { ComicBook.prototype.drawPage(); }
				if (loaded === no_pages) { $("#status").fadeOut(150).remove(); }
			};
		}

		// manually trigger the first load
		if (i === 0) { preload(i); }
	};

	/**
	 * Draw the current page in the canvas
	 *
	 * TODO: break this down into drawSinglePage() & drawDoublePage()?
	 * TODO: if the current browser doesn't have canvas support, use img tags
	 */
	ComicBook.prototype.drawPage = function() {

		var zoom_scale;
		var offsetW = 0, offsetH = 0;

		var page = pages[pointer];
		var page2 = pages[pointer + 1];

		if (typeof page !== "object") { throw "invalid page type '"+ typeof page +"'"; }

		var width = page.width;

		// reset the canvas to stop duplicate pages showing
		canvas.width = 0;
		canvas.height = 0;

		if (options.displayMode === "double") {

			// for double page spreads, factor in the width of both pages
			if (typeof page2 === "object") { width += page2.width; }

			// if this is the last page and there is no page2, still keep the canvas wide
			else { width += width; }
		}

		// update the page scale if a non manual mode has been chosen
		switch(options.zoomMode) {

			case "manual":
				document.body.style.overflowX = "auto";
				zoom_scale = (options.displayMode === "double") ? scale * 2 : scale;
				break;

			case "fitWidth":
				document.body.style.overflowX = "hidden";
				zoom_scale =  (window.innerWidth > width) ? ((window.innerWidth - width) / window.innerWidth) + 1 // scale up if the window is wider than the page
					  : window.innerWidth / width; // scale down if the window is narrower than the page
				break;

			default:throw "invalid zoomMode";
		}

		var canvas_width  = page.width * zoom_scale;
		var canvas_height = page.height * zoom_scale;

		var page_width = (options.zoomMode === "manual") ? page.width * scale : canvas_width;
		var page_height = (options.zoomMode === "manual") ? page.height * scale : canvas_height;

		canvas_height = page_height;

		// make sure the canvas is always at least full screen, even if the page is more narrow than the screen
		canvas.width = (canvas_width < window.innerWidth) ? window.innerWidth : canvas_width;
		canvas.height = (canvas_height < window.innerHeight) ? window.innerHeight : canvas_height;

		// disable scrollbars if not needed
		document.body.style.overflowX = (canvas_width < window.innerWidth) ? "hidden" : "auto";
		document.body.style.overflowY = (canvas_height < window.innerHeight) ? "hidden" : "auto";

		// work out a horizontal position that will keep the pages always centred
		if (canvas_width < window.innerWidth && options.zoomMode === "manual") {
			offsetW = (window.innerWidth - page_width) / 2;
			if (options.displayMode === "double") { offsetW = offsetW - page_width / 2; }
		}

		// work out a vertical position that will keep the pages always centred
		if (canvas_height < window.innerHeight && options.zoomMode === "manual") {
			offsetH = (window.innerHeight - page_height) / 2;
		}

		// in manga double page mode reverse the page(s)
		if (options.manga && options.displayMode === "double") {
			var tmpPage = page;
			var tmpPage2 = page2; // FIXME: check this exists before using
			page = tmpPage2;
			page2 = tmpPage;
		}

		// draw the page(s)
		context.drawImage(page, offsetW, offsetH, page_width, page_height);
		if (options.displayMode === "double" && typeof page2 === "object") { context.drawImage(page2, page_width + offsetW, offsetH, page_width, page_height); }

		// apply any image enhancements previously defined
		$.each(options.enhance, function(action, options) {
			ComicBook.prototype.enhance[action](options);
		});
	};

	/**
	 * Increment the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawNextPage = function () {
		if (pointer + 1 < pages.length) {
			pointer += (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Decrement the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawPrevPage = function () {
		if (pointer > 0) {
			pointer -= (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Apply image enhancements to the canvas.
	 *
	 * Powered by the awesome Pixastic: http://www.pixastic.com/
	 *
	 * TODO: reset & apply all image enhancements each time before applying new one
	 *
	 * TODO: abstract this into an "Enhance" object, separate from ComicBook?
	 */
	ComicBook.prototype.enhance = {

		/**
		 * Reset enhancements.
		 * This can reset a specific enhancement if the method name is passed, or
		 * it will reset all.
		 *
		 * @param method {string} the specific enhancement to reset
		 */
		reset: function (method) {
			if (!method) {
				options.enhance = {};
			} else {
				delete options.enhance[method];
			}
			ComicBook.prototype.drawPage();
		},

		/**
		 * Adjust brightness / contrast
		 *
		 * options
		 *    brightness (int) -150 to 150
		 *    contrast: (float) -1 to infinity
		 *
		 * @param {Object} options
		 */
		brightness: function (params, reset) {

			if (reset !== false) { this.reset("brightness"); }

			// merge user options with defaults
			var opts = merge({ brightness: 0, contrast: 0 }, params);

			// remember options for later
			options.enhance.brightness = opts;

			// run the enhancement
			Pixastic.process(canvas, "brightness", {
				brightness: opts.brightness,
				contrast: opts.contrast,
				legacy: true
			});

			init();
		},

		/**
		 * Force black and white
		 */
		desaturate: function () {

			options.enhance.desaturate = {};

			Pixastic.process(canvas, "desaturate", { average : false });

			init();
		},

		/**
		 * Sharpen
		 *
		 * options:
		 *   amount: number (-1 to infinity)
		 *
		 * @param {Object} options
		 */
		sharpen: function (params) {

			var opts = merge({ amount: 0 }, params);

			options.enhance.sharpen = opts;

			Pixastic.process(canvas, "sharpen", {
				amount: opts.amount
			});

			init();
		}
	};

	ComicBook.prototype.navigation = function (e) {

		if (e.type === "click") {

			var side = getCursorPosition(e);

			window.scroll(0,0); // make sure the top of the page is in view

			// western style (left to right)
			if (!options.manga) {
				if (side === "left") { ComicBook.prototype.drawPrevPage(); }
				if (side === "right") { ComicBook.prototype.drawNextPage(); }
			}
			// manga style (right to left)
			else {
				if (side === "left") { ComicBook.prototype.drawNextPage(); }
				if (side === "right") { ComicBook.prototype.drawPrevPage(); }
			}
		}
	};
}
<commit_msg>minor preload tweak, removed useless parameter<commit_after>/*jslint browser: true, on: true, eqeqeq: true, newcap: true, immed: true */

/*
	TODOs:

	Fo sho:
		- page controls
		- chrome frame / ExplorerCanvas / non canvas version?
		- show loading graphic & then fade in new page if user is ahead of the last loaded page
		- check for html5 feature support where used: diveintohtml5.org/everything.html or www.modernizr.com
		- remember position (use localStorage/cookie/sql)
		- really need to speed up enhancements, try to use webworkers

	Nice 2 have:
		- smart preload, start 5 before pointer and load from there, on navigation update preload position
		- offline access
		- thumbnail browser
		- page turn animation? (http://www.cynergysystems.com/blogs/page/rickbarraza?entry=the_secret_behind_the_page)
*/

/**
 * Merge two arrays. Any properties in b will replace the same properties in
 * a. New properties from b will be added to a.
 *
 * @param a {Object}
 * @param b {Object}
 */
function merge(a, b) {

	var prop;

	if (typeof b === "undefined") { b = {}; }

	for (prop in a) {
		if (a.hasOwnProperty(prop)) {
			if (prop in b) { continue; }
			b[prop] = a[prop];
		}
	}

	return b;
}

function ComicBook(id, srcs, opts) {

	var canvas_id = id;		// canvas element id
	this.srcs = srcs;	// array of image srcs for pages

	var defaults = {
		displayMode: "double",	// single / double
		zoomMode: "fitWidth",	// manual / fitWidth
		manga: false,			// true / false
		enhance: {}
	};

	var options = merge(defaults, opts); // options array for internal use

	var no_pages = srcs.length;
	var pages = [];		// array of preloaded Image objects
	var canvas;			// the HTML5 canvas object
	var context;		// the 2d drawing context

	var buffer = 4;		// image preload buffer level

	var loaded = 0;		// the amount of images that have been loaded so far

	var scale = 1;		// page zoom scale, 1 = 100%

	// the current page, can pass a default as a url hash
	var pointer = (parseInt(window.location.hash.replace('#',''),10) - 1) || 0;

	/**
	 * Figure out the cursor position relative to the canvas.
	 *
	 * Thanks to: Mark Pilgrim & http://diveintohtml5.org/canvas.html
	 */
	function getCursorPosition(e) {

		var x; // horizontal cursor position

		// check if page relative positions exist, if not figure them out
		if (e.pageX) {
			x = e.pageX;
		} else {
			x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
		}

		// make the position relative to the canvas
		x -= canvas.offsetLeft;

		// check if the user clicked on the left or right side
		return (x <= canvas.width / 2) ? 'left' : 'right';
	}

	/**
	 * Setup the canvas element for use throughout the class.
	 *
	 * @see #ComicBook.prototype.draw
	 * @see #ComicBook.prototype.enhance
	 */
	function init() {
		// setup canvas
		canvas = document.getElementById(canvas_id);
		context = canvas.getContext("2d");

		// add page controls
		canvas.addEventListener("click", ComicBook.prototype.navigation, false);
	}

	/*
	 * @see #preload
	 */
	ComicBook.prototype.draw = function () {

		init();

		// preload images if needed
		if (pages.length !== no_pages) { this.preload(); }
		else { this.drawPage(); }
	};

	/*
	 * Zoom the canvas
	 *
	 * @param new_scale {Number} Scale the canvas to this ratio
	 */
	ComicBook.prototype.zoom = function (new_scale) {
		options.zoomMode = "manual";
		scale = new_scale;
		if (typeof pages[pointer] === "object") { this.drawPage(); }
	};

	/**
	 * Preload all images, draw the page only after a given number have been loaded.
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.preload = function () {

		var srcs = this.srcs;

		if (no_pages < buffer) { buffer = no_pages; } // don't get stuck if the buffer level is higher than the number of pages

		var i = 0; // the current page counter for this method

		// show load status panel
		if ($("#status").length === 0) {
			$(canvas).after('

');
		}

		// I am using recursion instead of a forEach loop so that the next image is
		// only loaded when the previous one has completely finished
		function preload(i) {

			var page = new Image();

			$("#status p").text("loading page " + (i + 1) + " of " + no_pages);

			page.src = srcs[i];

			page.onload = function () {

				// console.info("loaded: " + srcs[i]);

				pages[i] = this;
				loaded++;

				// there are still more pages to load, do it
				if (loaded < no_pages) {
					i++;
					preload(i);
				}

				// start rendering the comic when the buffer level has been reached
				if (loaded === buffer) { ComicBook.prototype.drawPage(); }
				if (loaded === no_pages) { $("#status").fadeOut(150).remove(); }
			};
		}

		// manually trigger the first load
		if (i === 0) { preload(i); }
	};

	/**
	 * Draw the current page in the canvas
	 *
	 * TODO: break this down into drawSinglePage() & drawDoublePage()?
	 * TODO: if the current browser doesn't have canvas support, use img tags
	 */
	ComicBook.prototype.drawPage = function() {

		var zoom_scale;
		var offsetW = 0, offsetH = 0;
		var page = pages[pointer];
		var page2 = pages[pointer + 1];

		if (typeof page !== "object") { throw "invalid page type '"+ typeof page +"'"; }

		var width = page.width;

		// reset the canvas to stop duplicate pages showing
		canvas.width = 0;
		canvas.height = 0;

		if (options.displayMode === "double") {

			// for double page spreads, factor in the width of both pages
			if (typeof page2 === "object") { width += page2.width; }

			// if this is the last page and there is no page2, still keep the canvas wide
			else { width += width; }
		}

		// update the page scale if a non manual mode has been chosen
		switch(options.zoomMode) {

			case "manual":
				document.body.style.overflowX = "auto";
				zoom_scale = (options.displayMode === "double") ? scale * 2 : scale;
				break;

			case "fitWidth":
				document.body.style.overflowX = "hidden";
				zoom_scale =  (window.innerWidth > width) ? ((window.innerWidth - width) / window.innerWidth) + 1 // scale up if the window is wider than the page
					  : window.innerWidth / width; // scale down if the window is narrower than the page
				break;

			default:throw "invalid zoomMode";
		}

		var canvas_width  = page.width * zoom_scale;
		var canvas_height = page.height * zoom_scale;

		var page_width = (options.zoomMode === "manual") ? page.width * scale : canvas_width;
		var page_height = (options.zoomMode === "manual") ? page.height * scale : canvas_height;

		canvas_height = page_height;

		// make sure the canvas is always at least full screen, even if the page is more narrow than the screen
		canvas.width = (canvas_width < window.innerWidth) ? window.innerWidth : canvas_width;
		canvas.height = (canvas_height < window.innerHeight) ? window.innerHeight : canvas_height;

		// disable scrollbars if not needed
		document.body.style.overflowX = (canvas_width < window.innerWidth) ? "hidden" : "auto";
		document.body.style.overflowY = (canvas_height < window.innerHeight) ? "hidden" : "auto";

		// work out a horizontal position that will keep the pages always centred
		if (canvas_width < window.innerWidth && options.zoomMode === "manual") {
			offsetW = (window.innerWidth - page_width) / 2;
			if (options.displayMode === "double") { offsetW = offsetW - page_width / 2; }
		}

		// work out a vertical position that will keep the pages always centred
		if (canvas_height < window.innerHeight && options.zoomMode === "manual") {
			offsetH = (window.innerHeight - page_height) / 2;
		}

		// in manga double page mode reverse the page(s)
		if (options.manga && options.displayMode === "double") {
			var tmpPage = page;
			var tmpPage2 = page2; // FIXME: check this exists before using
			page = tmpPage2;
			page2 = tmpPage;
		}

		// draw the page(s)
		context.drawImage(page, offsetW, offsetH, page_width, page_height);
		if (options.displayMode === "double" && typeof page2 === "object") { context.drawImage(page2, page_width + offsetW, offsetH, page_width, page_height); }

		window.location.hash = pointer + 1;

		// apply any image enhancements previously defined
		$.each(options.enhance, function(action, options) {
			ComicBook.prototype.enhance[action](options);
		});
	};

	/**
	 * Increment the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawNextPage = function () {
		if (pointer + 1 < pages.length) {
			pointer += (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Decrement the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawPrevPage = function () {
		if (pointer > 0) {
			pointer -= (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Apply image enhancements to the canvas.
	 *
	 * Powered by the awesome Pixastic: http://www.pixastic.com/
	 *
	 * TODO: reset & apply all image enhancements each time before applying new one
	 *
	 * TODO: abstract this into an "Enhance" object, separate from ComicBook?
	 */
	ComicBook.prototype.enhance = {

		/**
		 * Reset enhancements.
		 * This can reset a specific enhancement if the method name is passed, or
		 * it will reset all.
		 *
		 * @param method {string} the specific enhancement to reset
		 */
		reset: function (method) {
			if (!method) {
				options.enhance = {};
			} else {
				delete options.enhance[method];
			}
			ComicBook.prototype.drawPage();
		},

		/**
		 * Adjust brightness / contrast
		 *
		 * options
		 *    brightness (int) -150 to 150
		 *    contrast: (float) -1 to infinity
		 *
		 * @param {Object} options
		 */
		brightness: function (params, reset) {

			if (reset !== false) { this.reset("brightness"); }

			// merge user options with defaults
			var opts = merge({ brightness: 0, contrast: 0 }, params);

			// remember options for later
			options.enhance.brightness = opts;

			// run the enhancement
			Pixastic.process(canvas, "brightness", {
				brightness: opts.brightness,
				contrast: opts.contrast,
				legacy: true
			});

			init();
		},

		/**
		 * Force black and white
		 */
		desaturate: function () {

			options.enhance.desaturate = {};

			Pixastic.process(canvas, "desaturate", { average : false });

			init();
		},

		/**
		 * Sharpen
		 *
		 * options:
		 *   amount: number (-1 to infinity)
		 *
		 * @param {Object} options
		 */
		sharpen: function (params) {

			var opts = merge({ amount: 0 }, params);

			options.enhance.sharpen = opts;

			Pixastic.process(canvas, "sharpen", {
				amount: opts.amount
			});

			init();
		}
	};

	ComicBook.prototype.navigation = function (e) {

		if (e.type === "click") {

			var side = getCursorPosition(e);

			window.scroll(0,0); // make sure the top of the page is in view

			// western style (left to right)
			if (!options.manga) {
				if (side === "left") { ComicBook.prototype.drawPrevPage(); }
				if (side === "right") { ComicBook.prototype.drawNextPage(); }
			}
			// manga style (right to left)
			else {
				if (side === "left") { ComicBook.prototype.drawNextPage(); }
				if (side === "right") { ComicBook.prototype.drawPrevPage(); }
			}
		}
	};
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const Listr = require('listr');
const execa = require('execa');
const chalk = require('chalk');
const del = require('del');
const UpdaterRenderer = require('listr-update-renderer');
const moment = require('moment');
const argv = require('minimist')(process.argv.slice(2), {
    string: ['appMode'],
    boolean: ['lint', 'i18n'],
    default: {
        website: false,
        lint: true,
        fromCi: false,
        i18n: true,
        localize: false,
        appMode: 'bundle',
        remote: false,
        forceFetch: false,
        silentMessage: false,
        'default-branch': 'master'
    }
});

// Compat mode WebClient
const ENV_FILE = fs.existsSync('.env') ? '.env' : 'env/.env';
require('dotenv').config({ path: ENV_FILE });

const { debug, success, error, about, warn } = require('./lib/helpers/log')('proton-bundler');
const coucou = require('./lib/helpers/coucou');
const { bash, script } = require('./lib/helpers/cli');
const {
    customBundler: { tasks: customTasks, config: customConfig },
    getCustomHooks
} = require('./lib/custom');
const { pull, push, getConfig, generateChangelog } = require('./lib/git');
const askDeploy = require('./lib/ask');
const buildRemote = require('./lib/buildRemote');

const PKG = require(path.join(process.cwd(), 'package.json'));

const getTasks = (branch, { isCI, flowType = 'single', forceI18n, appMode, runI18n, isRemoteBuild }) => {
    const { EXTERNAL_FILES = ['.htaccess'] } = customConfig;
    const { hookPreTasks, hookPostTasks, hookPostTaskClone, hookPostTaskBuild, customConfigSetup } = getCustomHooks(
        customTasks({
            branch,
            isCI,
            flowType,
            appMode,
            forceI18n,
            runI18n,
            isRemoteBuild
        })
    );

    const configTasks = customConfigSetup.length
        ? customConfigSetup
        : [
              {
                  title: 'Setup app config',
                  enabled: () => !isCI,
                  task() {
                      return bash('npx proton-pack', process.argv.slice(2));
                  }
              }
          ];

    const list = [
        {
            title: 'Check if the deploy branch exists',
            skip() {
                if (!(process.env.QA_BRANCHES || '').length) {
                    return 'If you do not have QA_BRANCHES inside your .env you cannot deploy to something new';
                }

                if (`deploy-${process.env.QA_BRANCH}` === branch) {
                    return '🤖 No need to check for this branch, it exists';
                }

                const branches = process.env.QA_BRANCHES.split(',').join('|');
                // Do not try to deploy on QA or cobalt
                if (new RegExp(`deploy-(cobalt|${branches})`).test(branch)) {
                    return '✋ You shall not deploy to QA';
                }
            },
            enabled: () => !/dev|beta|prod/.test(branch),
            async task() {
                // For the CI to force SSH
                if (process.env.GIT_REMOTE_URL_CI && argv.fromCi) {
                    await bash(`git remote set-url origin ${process.env.GIT_REMOTE_URL_CI}`);
                }
                return script('createNewDeployBranch.sh', ['--check', branch.replace('deploy-', '')], 'inherit');
            }
        },
        ...hookPreTasks,
        {
            title: 'Save dependencies if we need',
            enabled: () => !isRemoteBuild && !isCI && /dev|beta|alpha/.test(branch),
            task() {
                return script('updatePackageLock.sh', [argv['default-branch']]);
            }
        },
        {
            title: 'Clear previous dist',
            async task() {
                await del(['dist', 'distCurrent', 'distback'], { dryRun: false });
                isCI && execa('mkdir dist');
            }
        },
        {
            title: 'Lint sources',
            enabled: () => argv.lint !== false && !isRemoteBuild,
            task: () => execa('npm', ['run', 'lint'])
        },
        ...configTasks,
        {
            title: 'Extract git env for the bundle',
            enabled: () => !isCI,
            async task(ctx) {
                const { commit, branch, tag } = await getConfig();
                ctx.originCommit = commit;
                ctx.originBranch = branch;
                ctx.tag = tag;
                debug(ctx, 'git env bundle');
            }
        },
        {
            title: `Pull dist branch ${branch}`,
            enabled: () => !isCI,
            task: () => pull(branch, argv.forceFetch, argv.fromCi)
        },
        ...hookPostTaskClone,
        {
            title: 'Copy some files',
            task() {
                const rule = EXTERNAL_FILES.length > 1 ? `{${EXTERNAL_FILES.join(',')}}` : EXTERNAL_FILES.join(',');
                return bash(`cp src/${rule} dist/`);
            }
        },
        {
            title: 'Upgrade translations inside the app',
            enabled: () => forceI18n || (runI18n && !isCI && /prod|beta/.test(branch)),
            task() {
                return execa('npm', ['run', 'i18n:getlatest']);
            }
        },
        {
            title: 'Build the application',
            task() {
                const args = process.argv.slice(2);
                if (appMode === 'standalone') {
                    return execa('npm', ['run', 'build:standalone', '--', ...args]);
                }

                return execa('npm', ['run', 'build', '--', ...args]);
            }
        },
        ...hookPostTaskBuild,
        {
            title: 'Generate the version info',
            task(ctx) {
                const { tag = `v${PKG.version}`, originCommit } = ctx || {};
                const fileName = path.join('dist', 'assets/version.json');
                return script('createVersionJSON.sh', [originCommit, tag, fileName]);
            }
        },
        {
            title: `Push dist to ${branch}`,
            enabled: () => !isCI,
            task: (ctx) => push(branch, ctx)
        },
        {
            title: 'Update crowdin with latest translations',
            enabled: () => runI18n && !isCI && /prod|beta/.test(branch),
            task() {
                return execa('npm', ['run', 'i18n:upgrade']);
            }
        },
        ...hookPostTasks,
        {
            title: 'Inform us about new translations available',
            enabled: () => runI18n && !isCI && /prod|beta/.test(branch),
            task() {
                const [, type] = branch.match(/deploy-(\w+)/) || [];
                return execa('npx', ['proton-i18n', 'coucou', type]);
            }
        }
    ];
    return list;
};

/**
 * Get the API dest when we deploy.
 * You can use the custom config from proton.bundler.js to get it
 * @return {Promise<String>}
 */
async function getAPIUrl() {
    if (customConfig.apiUrl) {
        return customConfig.apiUrl;
    }

    const args = process.argv.slice(2);
    const { stdout } = await bash('npx proton-pack print-config', args);
    debug(stdout, 'print-config output');
    const [, url] = stdout.match(/apiUrl": "(.+)"(,*?)/);
    return url;
}

async function main() {
    /*
        If we build from the remote repository we need to:
            - clone the repository inside /tmp
            - install dependencies
            - run the deploy command again from this directory
        So let's put an end to the current deploy.
     */
    if (argv.remote) {
        await buildRemote(PKG);
        const args = process.argv.slice(2).filter((key) => !/--remote/.test(key));
        return script('builder.sh', [PKG.name, ...args], 'inherit'); // inherit for the colors ;)
    }

    // Custom local deploy for the CI
    const isCI = process.env.NODE_ENV_DIST === 'ci';
    const branch = argv.branch;
    const flowType = argv.flow;
    const runI18n = argv.i18n;
    const forceI18n = argv.localize;
    const appMode = argv.appMode;
    const isRemoteBuild = argv.source === 'remote';

    debug({ customConfig, argv }, 'configuration deploy');

    if (!branch && !isCI) {
        throw new Error('You must define a branch name. --branch=XXX');
    }

    const apiUrl = await getAPIUrl();

    process.env.NODE_ENV_BRANCH = branch;
    process.env.NODE_ENV_API = apiUrl;

    about({
        ...(!isCI && { branch }),
        apiUrl,
        appMode,
        isRemoteBuild,
        SENTRY: process.env.NODE_ENV_SENTRY
    });

    const start = moment(Date.now());
    const listTasks = getTasks(branch, { isCI, flowType, forceI18n, appMode, runI18n, isRemoteBuild });
    const tasks = new Listr(listTasks, {
        renderer: UpdaterRenderer,
        collapse: false
    });

    await tasks.run();

    const now = moment(Date.now());
    const total = now.diff(start, 'seconds');
    const time = total > 60 ? moment.utc(total * 1000).format('mm:ss') : `${total}s`;

    !isCI && success('App deployment done', { time });
    isCI && success(`Build CI app to the directory: ${chalk.bold('dist')}`, { time });

    if (!isCI && !argv.silentMessage) {
        return askDeploy(branch, PKG, argv);
    }
}

if (argv._.includes('hosts')) {
    return script('createNewDeployBranch.sh', process.argv.slice(3)).then(({ stdout }) => console.log(stdout));
}

if (argv._.includes('log-commits')) {
    const parseEnv = ({ branch, website }) => {
        if (website && /deploy-(a|b)$/.test(branch)) {
            return 'deploy-prod';
        }
        return branch;
    };

    debug(argv, 'arguments');
    const branchName = parseEnv(argv);
    return askDeploy(branchName, PKG, argv);
}

if (argv._.includes('changelog')) {
    const { branch } = argv;
    const url = argv.url || (PKG.bugs || {}).url;
    debug({ argv }, 'arguments');

    if (!['dev', 'v4', process.env.QA_BRANCH].includes(branch)) {
        return warn(`No changelog available for the branch ${branch}`); // not available
    }

    if (!url) {
        return warn('No URL found for the issues');
    }

    return generateChangelog(branch, url).then((data) => {
        if (data) {
            coucou.send(data, { env: branch, mode: 'changelog' }, PKG);
        }
    });
}

main().catch(error);
<commit_msg>Add restriction to old and tor<commit_after>#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const Listr = require('listr');
const execa = require('execa');
const chalk = require('chalk');
const del = require('del');
const UpdaterRenderer = require('listr-update-renderer');
const moment = require('moment');
const argv = require('minimist')(process.argv.slice(2), {
    string: ['appMode'],
    boolean: ['lint', 'i18n'],
    default: {
        website: false,
        lint: true,
        fromCi: false,
        i18n: true,
        localize: false,
        appMode: 'bundle',
        remote: false,
        forceFetch: false,
        silentMessage: false,
        'default-branch': 'master'
    }
});

// Compat mode WebClient
const ENV_FILE = fs.existsSync('.env') ? '.env' : 'env/.env';
require('dotenv').config({ path: ENV_FILE });

const { debug, success, error, about, warn } = require('./lib/helpers/log')('proton-bundler');
const coucou = require('./lib/helpers/coucou');
const { bash, script } = require('./lib/helpers/cli');
const {
    customBundler: { tasks: customTasks, config: customConfig },
    getCustomHooks
} = require('./lib/custom');
const { pull, push, getConfig, generateChangelog } = require('./lib/git');
const askDeploy = require('./lib/ask');
const buildRemote = require('./lib/buildRemote');

const PKG = require(path.join(process.cwd(), 'package.json'));

const getTasks = (branch, { isCI, flowType = 'single', forceI18n, appMode, runI18n, isRemoteBuild }) => {
    const { EXTERNAL_FILES = ['.htaccess'] } = customConfig;
    const { hookPreTasks, hookPostTasks, hookPostTaskClone, hookPostTaskBuild, customConfigSetup } = getCustomHooks(
        customTasks({
            branch,
            isCI,
            flowType,
            appMode,
            forceI18n,
            runI18n,
            isRemoteBuild
        })
    );

    const configTasks = customConfigSetup.length
        ? customConfigSetup
        : [
              {
                  title: 'Setup app config',
                  enabled: () => !isCI,
                  task() {
                      return bash('npx proton-pack', process.argv.slice(2));
                  }
              }
          ];

    const list = [
        {
            title: 'Check if the deploy branch exists',
            skip() {
                if (!(process.env.QA_BRANCHES || '').length) {
                    return 'If you do not have QA_BRANCHES inside your .env you cannot deploy to something new';
                }

                if (`deploy-${process.env.QA_BRANCH}` === branch) {
                    return '🤖 No need to check for this branch, it exists';
                }

                const branches = process.env.QA_BRANCHES.split(',').join('|');
                // Do not try to deploy on QA or cobalt
                if (new RegExp(`deploy-(cobalt|${branches})`).test(branch)) {
                    return '✋ You shall not deploy to QA';
                }
            },
            enabled: () => !/dev|beta|prod|tor|old/.test(branch),
            async task() {
                // For the CI to force SSH
                if (process.env.GIT_REMOTE_URL_CI && argv.fromCi) {
                    await bash(`git remote set-url origin ${process.env.GIT_REMOTE_URL_CI}`);
                }
                return script('createNewDeployBranch.sh', ['--check', branch.replace('deploy-', '')], 'inherit');
            }
        },
        ...hookPreTasks,
        {
            title: 'Save dependencies if we need',
            enabled: () => !isRemoteBuild && !isCI && /dev|beta|alpha/.test(branch),
            task() {
                return script('updatePackageLock.sh', [argv['default-branch']]);
            }
        },
        {
            title: 'Clear previous dist',
            async task() {
                await del(['dist', 'distCurrent', 'distback'], { dryRun: false });
                isCI && execa('mkdir dist');
            }
        },
        {
            title: 'Lint sources',
            enabled: () => argv.lint !== false && !isRemoteBuild,
            task: () => execa('npm', ['run', 'lint'])
        },
        ...configTasks,
        {
            title: 'Extract git env for the bundle',
            enabled: () => !isCI,
            async task(ctx) {
                const { commit, branch, tag } = await getConfig();
                ctx.originCommit = commit;
                ctx.originBranch = branch;
                ctx.tag = tag;
                debug(ctx, 'git env bundle');
            }
        },
        {
            title: `Pull dist branch ${branch}`,
            enabled: () => !isCI,
            task: () => pull(branch, argv.forceFetch, argv.fromCi)
        },
        ...hookPostTaskClone,
        {
            title: 'Copy some files',
            task() {
                const rule = EXTERNAL_FILES.length > 1 ? `{${EXTERNAL_FILES.join(',')}}` : EXTERNAL_FILES.join(',');
                return bash(`cp src/${rule} dist/`);
            }
        },
        {
            title: 'Upgrade translations inside the app',
            enabled: () => forceI18n || (runI18n && !isCI && /prod|beta/.test(branch)),
            task() {
                return execa('npm', ['run', 'i18n:getlatest']);
            }
        },
        {
            title: 'Build the application',
            task() {
                const args = process.argv.slice(2);
                if (appMode === 'standalone') {
                    return execa('npm', ['run', 'build:standalone', '--', ...args]);
                }

                return execa('npm', ['run', 'build', '--', ...args]);
            }
        },
        ...hookPostTaskBuild,
        {
            title: 'Generate the version info',
            task(ctx) {
                const { tag = `v${PKG.version}`, originCommit } = ctx || {};
                const fileName = path.join('dist', 'assets/version.json');
                return script('createVersionJSON.sh', [originCommit, tag, fileName]);
            }
        },
        {
            title: `Push dist to ${branch}`,
            enabled: () => !isCI,
            task: (ctx) => push(branch, ctx)
        },
        {
            title: 'Update crowdin with latest translations',
            enabled: () => runI18n && !isCI && /prod|beta/.test(branch),
            task() {
                return execa('npm', ['run', 'i18n:upgrade']);
            }
        },
        ...hookPostTasks,
        {
            title: 'Inform us about new translations available',
            enabled: () => runI18n && !isCI && /prod|beta/.test(branch),
            task() {
                const [, type] = branch.match(/deploy-(\w+)/) || [];
                return execa('npx', ['proton-i18n', 'coucou', type]);
            }
        }
    ];
    return list;
};

/**
 * Get the API dest when we deploy.
 * You can use the custom config from proton.bundler.js to get it
 * @return {Promise<String>}
 */
async function getAPIUrl() {
    if (customConfig.apiUrl) {
        return customConfig.apiUrl;
    }

    const args = process.argv.slice(2);
    const { stdout } = await bash('npx proton-pack print-config', args);
    debug(stdout, 'print-config output');
    const [, url] = stdout.match(/apiUrl": "(.+)"(,*?)/);
    return url;
}

async function main() {
    /*
        If we build from the remote repository we need to:
            - clone the repository inside /tmp
            - install dependencies
            - run the deploy command again from this directory
        So let's put an end to the current deploy.
     */
    if (argv.remote) {
        await buildRemote(PKG);
        const args = process.argv.slice(2).filter((key) => !/--remote/.test(key));
        return script('builder.sh', [PKG.name, ...args], 'inherit'); // inherit for the colors ;)
    }

    // Custom local deploy for the CI
    const isCI = process.env.NODE_ENV_DIST === 'ci';
    const branch = argv.branch;
    const flowType = argv.flow;
    const runI18n = argv.i18n;
    const forceI18n = argv.localize;
    const appMode = argv.appMode;
    const isRemoteBuild = argv.source === 'remote';

    debug({ customConfig, argv }, 'configuration deploy');

    if (!branch && !isCI) {
        throw new Error('You must define a branch name. --branch=XXX');
    }

    const apiUrl = await getAPIUrl();

    process.env.NODE_ENV_BRANCH = branch;
    process.env.NODE_ENV_API = apiUrl;

    about({
        ...(!isCI && { branch }),
        apiUrl,
        appMode,
        isRemoteBuild,
        SENTRY: process.env.NODE_ENV_SENTRY
    });

    const start = moment(Date.now());
    const listTasks = getTasks(branch, { isCI, flowType, forceI18n, appMode, runI18n, isRemoteBuild });
    const tasks = new Listr(listTasks, {
        renderer: UpdaterRenderer,
        collapse: false
    });

    await tasks.run();

    const now = moment(Date.now());
    const total = now.diff(start, 'seconds');
    const time = total > 60 ? moment.utc(total * 1000).format('mm:ss') : `${total}s`;

    !isCI && success('App deployment done', { time });
    isCI && success(`Build CI app to the directory: ${chalk.bold('dist')}`, { time });

    if (!isCI && !argv.silentMessage) {
        return askDeploy(branch, PKG, argv);
    }
}

if (argv._.includes('hosts')) {
    return script('createNewDeployBranch.sh', process.argv.slice(3)).then(({ stdout }) => console.log(stdout));
}

if (argv._.includes('log-commits')) {
    const parseEnv = ({ branch, website }) => {
        if (website && /deploy-(a|b)$/.test(branch)) {
            return 'deploy-prod';
        }
        return branch;
    };

    debug(argv, 'arguments');
    const branchName = parseEnv(argv);
    return askDeploy(branchName, PKG, argv);
}

if (argv._.includes('changelog')) {
    const { branch } = argv;
    const url = argv.url || (PKG.bugs || {}).url;
    debug({ argv }, 'arguments');

    if (!['dev', 'v4', process.env.QA_BRANCH].includes(branch)) {
        return warn(`No changelog available for the branch ${branch}`); // not available
    }

    if (!url) {
        return warn('No URL found for the issues');
    }

    return generateChangelog(branch, url).then((data) => {
        if (data) {
            coucou.send(data, { env: branch, mode: 'changelog' }, PKG);
        }
    });
}

main().catch(error);
<|endoftext|>


<commit_before>/**
 * The Home view is the view one sees at /keystone. It shows a list of all lists,
 * grouped by their section.
 */

import React from 'react';
import { Container, Spinner } from 'elemental';
import { connect } from 'react-redux';

import Lists from './components/Lists';
import Section from './components/Section';
import AlertMessages from '../../shared/AlertMessages';
import {
	loadCounts,
} from './actions';

var HomeView = React.createClass({
	displayName: 'HomeView',
	// When everything is rendered, start loading the item counts of the lists
	// from the API
	componentDidMount () {
		this.props.dispatch(loadCounts());
	},
	getSpinner () {
		if (this.props.counts && Object.keys(this.props.counts).length === 0
			&& (this.props.error || this.props.loading)) {
			return (
				<Spinner />
			);
		}
		return null;
	},
	render () {
		const spinner = this.getSpinner();
		return (
			<Container data-screen-id="home">

{Keystone.brand}

					{(this.props.error) && (
						<AlertMessages
							alerts={{ error: { error:
								"There is a problem with the network, we're trying to reconnect...",
							} }}
						/>
					)}
					{/* Render flat nav */}
					{Keystone.nav.flat ? (
						<Lists
							counts={this.props.counts}
							lists={Keystone.lists}
							spinner={spinner}
						/>
					) : (

							{/* Render nav with sections */}
							{Keystone.nav.sections.map((navSection) => {
								return (

										<Lists
											counts={this.props.counts}
											lists={navSection.lists}
											spinner={spinner}
										/>

								);
							})}
							{/* Render orphaned lists */}
							{Keystone.orphanedLists.length && (

									<Lists
										counts={this.props.counts}
										lists={Keystone.orphanedLists}
										spinner={spinner}
									/>

							)}

					)}

			</Container>
		);
	},
});

export {
	HomeView,
};

export default connect((state) => ({
	counts: state.home.counts,
	loading: state.home.loading,
	error: state.home.error,
}))(HomeView);
<commit_msg>Fixing orphanedLists output in React<commit_after>/**
 * The Home view is the view one sees at /keystone. It shows a list of all lists,
 * grouped by their section.
 */

import React from 'react';
import { Container, Spinner } from 'elemental';
import { connect } from 'react-redux';

import Lists from './components/Lists';
import Section from './components/Section';
import AlertMessages from '../../shared/AlertMessages';
import {
	loadCounts,
} from './actions';

var HomeView = React.createClass({
	displayName: 'HomeView',
	// When everything is rendered, start loading the item counts of the lists
	// from the API
	componentDidMount () {
		this.props.dispatch(loadCounts());
	},
	getSpinner () {
		if (this.props.counts && Object.keys(this.props.counts).length === 0
			&& (this.props.error || this.props.loading)) {
			return (
				<Spinner />
			);
		}
		return null;
	},
	render () {
		const spinner = this.getSpinner();
		return (
			<Container data-screen-id="home">

{Keystone.brand}

					{(this.props.error) && (
						<AlertMessages
							alerts={{ error: { error:
								"There is a problem with the network, we're trying to reconnect...",
							} }}
						/>
					)}
					{/* Render flat nav */}
					{Keystone.nav.flat ? (
						<Lists
							counts={this.props.counts}
							lists={Keystone.lists}
							spinner={spinner}
						/>
					) : (

							{/* Render nav with sections */}
							{Keystone.nav.sections.map((navSection) => {
								return (

										<Lists
											counts={this.props.counts}
											lists={navSection.lists}
											spinner={spinner}
										/>

								);
							})}
							{/* Render orphaned lists */}
							{Keystone.orphanedLists.length ? (

									<Lists
										counts={this.props.counts}
										lists={Keystone.orphanedLists}
										spinner={spinner}
									/>

							) : null}

					)}

			</Container>
		);
	},
});

export {
	HomeView,
};

export default connect((state) => ({
	counts: state.home.counts,
	loading: state.home.loading,
	error: state.home.error,
}))(HomeView);
<|endoftext|>