<commit_before>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import DropdownButton from './Buttons/DropdownButton';
import EditTitleForm from './Texts/EditTitleForm';
import ConfirmModal from './Modals/ConfirmModal';
import { editVideoTitle, deleteVideo } from 'redux/actions/VideoActions';
import { connect } from 'react-redux';
import UsernameText from './Texts/UsernameText';
import { cleanString } from 'helpers/stringHelpers';
import Link from 'components/Link';
import FullTextReveal from 'components/FullTextReveal';
import { textIsOverflown } from 'helpers';
import ErrorBoundary from 'components/Wrappers/ErrorBoundary';
import VideoThumbImage from 'components/VideoThumbImage';
import Icon from 'components/Icon';
import { Color } from 'constants/css';
import { css } from 'emotion';
import { charLimit } from 'constants/defaultValues';

class VideoThumb extends Component {
  static propTypes = {
    arrayIndex: PropTypes.number,
    clickSafe: PropTypes.bool,
    deletable: PropTypes.bool,
    deleteVideo: PropTypes.func.isRequired,
    editable: PropTypes.bool,
    editVideoTitle: PropTypes.func,
    lastVideoId: PropTypes.number,
    style: PropTypes.object,
    to: PropTypes.string.isRequired,
    user: PropTypes.object.isRequired,
    video: PropTypes.shape({
      byUser: PropTypes.bool,
      content: PropTypes.string.isRequired,
      id: PropTypes.number.isRequired,
      isStarred: PropTypes.number,
      numLikes: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      title: PropTypes.string.isRequired
    }).isRequired
  };

  state = {
    onEdit: false,
    confirmModalShown: false,
    onTitleHover: false
  };

  render() {
    const { onEdit, confirmModalShown, onTitleHover } = this.state;
    const { deletable, editable, video, style, to, user } = this.props;
    const menuProps = [];
    if (editable) {
      menuProps.push({
        label: 'Edit',
        onClick: this.onEditTitle
      });
    }
    if (deletable || editable) {
      menuProps.push({
        label: 'Remove',
        onClick: this.onDeleteClick
      });
    }
    return (
      <ErrorBoundary style={style}>

          {(deletable || editable) && (
            <DropdownButton
              style={{
                position: 'absolute',
                zIndex: '1'
              }}
              direction="left"
              snow
              noBorderRadius
              menuProps={menuProps}
            />
          )}

            <Link to={`/${to}`} onClickAsync={this.onLinkClick}>
              <VideoThumbImage
                height="65%"
                videoId={video.id}
                isStarred={!!video.isStarred}
                src={`https://img.youtube.com/vi/${
                  video.content
                }/mqdefault.jpg`}
              />
            </Link>

            {onEdit ? (

                <EditTitleForm
                  autoFocus
                  maxLength={charLimit.video.title}
                  title={video.title}
                  onEditSubmit={this.onEditedTitleSubmit}
                  onClickOutSide={this.onEditTitleCancel}
                />

            ) : (

 {
                    this.thumbLabel = ref;
                  }}
                  style={{
                    whiteSpace: 'nowrap',
                    textOverflow: 'ellipsis',
                    overflow: 'hidden',
                    lineHeight: 'normal'
                  }}
                >
                  <a
                    style={{
                      color: video.byUser ? Color.orange() : Color.blue()
                    }}
                    href={`/${to}`}
                    onClick={this.onLinkClick}
                    onMouseOver={this.onMouseOver}
                    onMouseLeave={() => this.setState({ onTitleHover: false })}
                  >
                    {cleanString(video.title)}

                <FullTextReveal
                  show={onTitleHover}
                  text={cleanString(video.title)}
                />

            )}

              {!onEdit && (

                  Added by <UsernameText user={user} />

              )}
              {video.numLikes > 0 && (

                  <Icon icon="thumbs-up" />
                   ×
                  {video.numLikes}

              )}

        {confirmModalShown && (
          <ConfirmModal
            title="Remove Video"
            onHide={this.onHideModal}
            onConfirm={this.onDeleteConfirm}
          />
        )}
      </ErrorBoundary>
    );
  }

  onLinkClick = () => {
    const { clickSafe } = this.props;
    return Promise.resolve(clickSafe);
  };

  onEditTitle = () => {
    this.setState({ onEdit: true });
  };

  onEditedTitleSubmit = async title => {
    const { video, editVideoTitle } = this.props;
    const videoId = video.id;
    await editVideoTitle({ title, videoId });
    this.setState({ onEdit: false });
  };

  onEditTitleCancel = () => {
    this.setState({ onEdit: false });
  };

  onDeleteClick = () => {
    this.setState({ confirmModalShown: true });
  };

  onDeleteConfirm = () => {
    const { deleteVideo, video, arrayIndex, lastVideoId } = this.props;
    const videoId = video.id;
    deleteVideo({ videoId, arrayIndex, lastVideoId });
  };

  onHideModal = () => {
    this.setState({ confirmModalShown: false });
  };

  onMouseOver = () => {
    if (textIsOverflown(this.thumbLabel)) {
      this.setState({ onTitleHover: true });
    }
  };
}

export default connect(
  state => ({ userId: state.UserReducer.userId }),
  {
    editVideoTitle,
    deleteVideo
  }
)(VideoThumb);
<commit_msg>minor bug fix<commit_after>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import DropdownButton from './Buttons/DropdownButton';
import EditTitleForm from './Texts/EditTitleForm';
import ConfirmModal from './Modals/ConfirmModal';
import { editVideoTitle, deleteVideo } from 'redux/actions/VideoActions';
import { connect } from 'react-redux';
import UsernameText from './Texts/UsernameText';
import { cleanString } from 'helpers/stringHelpers';
import Link from 'components/Link';
import FullTextReveal from 'components/FullTextReveal';
import { textIsOverflown } from 'helpers';
import ErrorBoundary from 'components/Wrappers/ErrorBoundary';
import VideoThumbImage from 'components/VideoThumbImage';
import Icon from 'components/Icon';
import { Color } from 'constants/css';
import { css } from 'emotion';
import { charLimit } from 'constants/defaultValues';

class VideoThumb extends Component {
  static propTypes = {
    arrayIndex: PropTypes.number,
    clickSafe: PropTypes.bool,
    deletable: PropTypes.bool,
    deleteVideo: PropTypes.func.isRequired,
    editable: PropTypes.bool,
    editVideoTitle: PropTypes.func,
    lastVideoId: PropTypes.number,
    style: PropTypes.object,
    to: PropTypes.string.isRequired,
    user: PropTypes.object.isRequired,
    video: PropTypes.shape({
      byUser: PropTypes.number,
      content: PropTypes.string.isRequired,
      id: PropTypes.number.isRequired,
      isStarred: PropTypes.number,
      likes: PropTypes.array,
      title: PropTypes.string.isRequired
    }).isRequired
  };

  state = {
    onEdit: false,
    confirmModalShown: false,
    onTitleHover: false
  };

  render() {
    const { onEdit, confirmModalShown, onTitleHover } = this.state;
    const { deletable, editable, video, style, to, user } = this.props;
    const menuProps = [];
    if (editable) {
      menuProps.push({
        label: 'Edit',
        onClick: this.onEditTitle
      });
    }
    if (deletable || editable) {
      menuProps.push({
        label: 'Remove',
        onClick: this.onDeleteClick
      });
    }
    return (
      <ErrorBoundary style={style}>

          {(deletable || editable) && (
            <DropdownButton
              style={{
                position: 'absolute',
                zIndex: '1'
              }}
              direction="left"
              snow
              noBorderRadius
              menuProps={menuProps}
            />
          )}

            <Link to={`/${to}`} onClickAsync={this.onLinkClick}>
              <VideoThumbImage
                height="65%"
                videoId={video.id}
                isStarred={!!video.isStarred}
                src={`https://img.youtube.com/vi/${
                  video.content
                }/mqdefault.jpg`}
              />
            </Link>

            {onEdit ? (

                <EditTitleForm
                  autoFocus
                  maxLength={charLimit.video.title}
                  title={video.title}
                  onEditSubmit={this.onEditedTitleSubmit}
                  onClickOutSide={this.onEditTitleCancel}
                />

            ) : (

 {
                    this.thumbLabel = ref;
                  }}
                  style={{
                    whiteSpace: 'nowrap',
                    textOverflow: 'ellipsis',
                    overflow: 'hidden',
                    lineHeight: 'normal'
                  }}
                >
                  <a
                    style={{
                      color: video.byUser ? Color.orange() : Color.blue()
                    }}
                    href={`/${to}`}
                    onClick={this.onLinkClick}
                    onMouseOver={this.onMouseOver}
                    onMouseLeave={() => this.setState({ onTitleHover: false })}
                  >
                    {cleanString(video.title)}

                <FullTextReveal
                  show={onTitleHover}
                  text={cleanString(video.title)}
                />

            )}

              {!onEdit && (

                  Added by <UsernameText user={user} />

              )}
              {video.likes?.length > 0 && (

                  <Icon icon="thumbs-up" />
                   ×
                  {video.likes.length}

              )}

        {confirmModalShown && (
          <ConfirmModal
            title="Remove Video"
            onHide={this.onHideModal}
            onConfirm={this.onDeleteConfirm}
          />
        )}
      </ErrorBoundary>
    );
  }

  onLinkClick = () => {
    const { clickSafe } = this.props;
    return Promise.resolve(clickSafe);
  };

  onEditTitle = () => {
    this.setState({ onEdit: true });
  };

  onEditedTitleSubmit = async title => {
    const { video, editVideoTitle } = this.props;
    const videoId = video.id;
    await editVideoTitle({ title, videoId });
    this.setState({ onEdit: false });
  };

  onEditTitleCancel = () => {
    this.setState({ onEdit: false });
  };

  onDeleteClick = () => {
    this.setState({ confirmModalShown: true });
  };

  onDeleteConfirm = () => {
    const { deleteVideo, video, arrayIndex, lastVideoId } = this.props;
    const videoId = video.id;
    deleteVideo({ videoId, arrayIndex, lastVideoId });
  };

  onHideModal = () => {
    this.setState({ confirmModalShown: false });
  };

  onMouseOver = () => {
    if (textIsOverflown(this.thumbLabel)) {
      this.setState({ onTitleHover: true });
    }
  };
}

export default connect(
  state => ({ userId: state.UserReducer.userId }),
  {
    editVideoTitle,
    deleteVideo
  }
)(VideoThumb);
<|endoftext|>


<commit_before>import _ from 'underscore';
import $ from 'jquery';
import {LayoutView} from 'backbone.marionette';
import {Model} from 'backbone';
import PlaylistsList from './Playlists';
import {localStorage} from '../../../utils';
import app from '../../../application';
import {props} from '../../decorators';

class Playlists extends LayoutView {

    @props({
        className: 'layout-playlists',

        template: require('../templates/playlists.ejs'),

        regions: {
            list: '.js-playlists'
        },

        behaviors: {
            BtnToTop: {},
            Search: {
                container: '.js-search-container',
                filterCheckboxBehavior: true,
                tags: false
            },
            Loader: {}
        },

        events: {
            'click @ui.link': '_onClickLink'
        },

        ui: {
            link: '.js-link',
            loader: '.js-loader'
        },

        model: new Model({
            search: '',
            filterByRBTV: true,
            filterByLP: true,
            _loading: false
        })
    })

    /** @returns {{search: String, rbtv: String|null, lp: String|null}} */
    get channelFilter() {
        return {
            search: this.model.get('search'),
            rbtv: this.model.get('filterByRBTV'),
            lp: this.model.get('filterByLP')
        };
    }

    startLoading() {
        this.model.set('_loading', true);
    }

    stopLoading() {
        this.model.set('_loading', false);
    }

    modelEvents() {
        return {
            'change:search': () => {
                this.renderCollection(
                    _.extend(this.channelFilter, { resetResults: true })
                );
            },

            'change:filterByRBTV change:filterByLP': () => {
                this.renderCollection(
                    _.extend(this.channelFilter, { resetResults: true })
                );
            }
        };
    }

    /**
     * Lifecycle methods
     */

    initialize() {
        this.renderCollection = _.debounce(this.renderCollection, 150, {leading: false});
    }

    onRender() {
        this.getRegion('list').show(
            new PlaylistsList({
                collection: this.collection
            })
        );

        this.stickit();
    }

    onDestroy() {
        this._killScroll();
    }

    /**
     * Public methods
     */

    /**
     * @param {Boolean|Object} filter
     */
    renderCollection(filter = false) {
        console.log('renderCollection()');

        if (filter === true) {
            filter = localStorage.get('playlists.filter');

            if (filter) {
                this.model.set({
                    search: filter.search,
                    filterByRBTV: filter.rbtv,
                    filterByLP: filter.lp
                });
            }
        }

        if (!filter) {
            filter = this.channelFilter;
        }

        // Cache filter
        localStorage.set('playlists.filter', _.omit(filter, 'increaseResults', 'resetResults'));

        this.stopLoading();

        // Search collection
        this.collection.search(filter);

        this._initScroll();
    }

    /**
     * Private methods
     */

    _initScroll() {
        this._killScroll();

        $(window).on('scroll.playlists', this._onScroll.bind(this));
    }

    _killScroll() {
        $(window).off('scroll.playlists');
    }

    /**
     * Event handler
     */

    _onClickLink(e) {
        const $link = $(e.currentTarget);
        const route   = $link.attr('href').substr(1);

        app.navigate(route);

        e.preventDefault();
    }

    _onScroll() {
        const maxY = $(document).height() - window.innerHeight - 600;
        const y    = window.scrollY;

        if (y >= maxY) {
            this._killScroll();

            this.renderCollection(
                _.extend(this.channelFilter, { increaseResults: true })
            );
        }
    }
}

export default Playlists;
<commit_msg>Removed console.log<commit_after>import _ from 'underscore';
import $ from 'jquery';
import {LayoutView} from 'backbone.marionette';
import {Model} from 'backbone';
import PlaylistsList from './Playlists';
import {localStorage} from '../../../utils';
import app from '../../../application';
import {props} from '../../decorators';

class Playlists extends LayoutView {

    @props({
        className: 'layout-playlists',

        template: require('../templates/playlists.ejs'),

        regions: {
            list: '.js-playlists'
        },

        behaviors: {
            BtnToTop: {},
            Search: {
                container: '.js-search-container',
                filterCheckboxBehavior: true,
                tags: false
            },
            Loader: {}
        },

        events: {
            'click @ui.link': '_onClickLink'
        },

        ui: {
            link: '.js-link',
            loader: '.js-loader'
        },

        model: new Model({
            search: '',
            filterByRBTV: true,
            filterByLP: true,
            _loading: false
        })
    })

    /** @returns {{search: String, rbtv: String|null, lp: String|null}} */
    get channelFilter() {
        return {
            search: this.model.get('search'),
            rbtv: this.model.get('filterByRBTV'),
            lp: this.model.get('filterByLP')
        };
    }

    startLoading() {
        this.model.set('_loading', true);
    }

    stopLoading() {
        this.model.set('_loading', false);
    }

    modelEvents() {
        return {
            'change:search': () => {
                this.renderCollection(
                    _.extend(this.channelFilter, { resetResults: true })
                );
            },

            'change:filterByRBTV change:filterByLP': () => {
                this.renderCollection(
                    _.extend(this.channelFilter, { resetResults: true })
                );
            }
        };
    }

    /**
     * Lifecycle methods
     */

    initialize() {
        this.renderCollection = _.debounce(this.renderCollection, 150, {leading: false});
    }

    onRender() {
        this.getRegion('list').show(
            new PlaylistsList({
                collection: this.collection
            })
        );

        this.stickit();
    }

    onDestroy() {
        this._killScroll();
    }

    /**
     * Public methods
     */

    /**
     * @param {Boolean|Object} filter
     */
    renderCollection(filter = false) {
        if (filter === true) {
            filter = localStorage.get('playlists.filter');

            if (filter) {
                this.model.set({
                    search: filter.search,
                    filterByRBTV: filter.rbtv,
                    filterByLP: filter.lp
                });
            }
        }

        if (!filter) {
            filter = this.channelFilter;
        }

        // Cache filter
        localStorage.set('playlists.filter', _.omit(filter, 'increaseResults', 'resetResults'));

        this.stopLoading();

        // Search collection
        this.collection.search(filter);

        this._initScroll();
    }

    /**
     * Private methods
     */

    _initScroll() {
        this._killScroll();

        $(window).on('scroll.playlists', this._onScroll.bind(this));
    }

    _killScroll() {
        $(window).off('scroll.playlists');
    }

    /**
     * Event handler
     */

    _onClickLink(e) {
        const $link = $(e.currentTarget);
        const route   = $link.attr('href').substr(1);

        app.navigate(route);

        e.preventDefault();
    }

    _onScroll() {
        const maxY = $(document).height() - window.innerHeight - 600;
        const y    = window.scrollY;

        if (y >= maxY) {
            this._killScroll();

            this.renderCollection(
                _.extend(this.channelFilter, { increaseResults: true })
            );
        }
    }
}

export default Playlists;
<|endoftext|>


<commit_before>'use strict';

module.exports = (function() {

  /**
   * Creates a new scheduled task.
   *
   * @param {Task} task - task to schedule.
   * @param {boolean} immediateStart - whether to start the task immediately.
   */
  function ScheduledTask(task, immediateStart) {
    this.task = function () {
      var date = new Date()
      this.tick = setTimeout(this.task.bind(this), 1000 - date.getMilliseconds());
      task.update(date);
    };

    this.tick = null;

    if (immediateStart !== false) {
      this.start();
    }
  }

  /**
   * Starts updating the task.
   *
   * @returns {ScheduledTask} instance of this task.
   */
  ScheduledTask.prototype.start = function() {
    if (this.task && !this.tick) {
      this.tick = setTimeout(this.task.bind(this), 1000);
    }

    return this;
  };

  /**
   * Stops updating the task.
   *
   * @returns {ScheduledTask} instance of this task.
   */
  ScheduledTask.prototype.stop = function() {
    if (this.tick) {
      clearTimeout(this.tick);
      this.tick = null;
    }

    return this;
  };

  /**
   * Destoys the scheduled task.
   */
  ScheduledTask.prototype.destroy = function() {
    this.stop();

    this.task = null;
  };

  return ScheduledTask;
}());
<commit_msg>fix linter<commit_after>'use strict';

module.exports = (function() {

  /**
   * Creates a new scheduled task.
   *
   * @param {Task} task - task to schedule.
   * @param {boolean} immediateStart - whether to start the task immediately.
   */
  function ScheduledTask(task, immediateStart) {
    this.task = function () {
      var date = new Date();
      this.tick = setTimeout(this.task.bind(this),
        1000 - date.getMilliseconds());
      task.update(date);
    };

    this.tick = null;

    if (immediateStart !== false) {
      this.start();
    }
  }

  /**
   * Starts updating the task.
   *
   * @returns {ScheduledTask} instance of this task.
   */
  ScheduledTask.prototype.start = function() {
    if (this.task && !this.tick) {
      this.tick = setTimeout(this.task.bind(this), 1000);
    }

    return this;
  };

  /**
   * Stops updating the task.
   *
   * @returns {ScheduledTask} instance of this task.
   */
  ScheduledTask.prototype.stop = function() {
    if (this.tick) {
      clearTimeout(this.tick);
      this.tick = null;
    }

    return this;
  };

  /**
   * Destoys the scheduled task.
   */
  ScheduledTask.prototype.destroy = function() {
    this.stop();

    this.task = null;
  };

  return ScheduledTask;
}());
<|endoftext|>


<commit_before>import { select } from "d3-selection";
import { max as d3max, extent as d3extent } from "d3-array";
import { scaleSqrt } from "d3-scale";

import { mouse as d3mouse } from "d3-selection";

import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
  forceX
} from "d3-force";

import { format as formatBytes } from "bytes";

import { createTooltip, createMouseleave, createMouseover } from "./tooltip";

const createMousemove = (tooltipNode, container) => d => {
  const [x, y] = d3mouse(container);
  const nodePath = d.id;

  const str = `${nodePath}<br/><b>${formatBytes(d.value || d.size)}</b>`;

  tooltipNode
    .html(str)
    .style("left", x + 30 + "px")
    .style("top", y + "px");
};

import "./style/style-network.scss";

function color(group) {
  if (group === 0) {
    return "#db7100";
  } else {
    return "#599e59";
  }
}

const WIDTH = 1000;
const HEIGHT = 1000;

const chartsContainer = document.querySelector("#charts");

for (const { id, root: data } of window.nodesData) {
  const wrapper = document.createElement("div");
  wrapper.innerHTML = `

${id}

      `;
  const chartNode = wrapper.querySelector(".chart");
  chartsContainer.appendChild(chartNode);

  const tooltip = createTooltip(select(chartNode));

  const nodes = Object.entries(data.nodes).map(([id, mod]) => ({ id, ...mod }));
  const index = new Map(nodes.map(d => [d.id, d]));
  const links = data.links.map(d =>
    Object.assign(Object.create(d), {
      source: index.get(d.source),
      target: index.get(d.target),
      value: 1
    })
  );

  const maxLines = d3max(nodes, d => d.size);
  const size = scaleSqrt()
    .domain([1, maxLines])
    .range([5, 30]);

  const svg = select(chartNode)
    .append("svg")
    .attr("viewBox", [0, 0, WIDTH, HEIGHT]);

  const simulation = forceSimulation()
    .force(
      "link",
      forceLink()
        .id(d => d.id)
        .strength(1)
    )
    .force("collide", forceCollide().radius(d => size(d.size) + 1))
    .force("forceX", forceX(HEIGHT / 2).strength(0.05))
    .force("charge", forceManyBody().strength(-10))
    .force("center", forceCenter(WIDTH / 2, HEIGHT / 2));

  simulation.nodes(nodes);
  simulation.force("link").links(links);
  simulation.stop();

  for (let i = 0; i < 150; i++) simulation.tick();

  const xExtent = d3extent(nodes, d => d.x);
  const yExtent = d3extent(nodes, d => d.y);

  const xRange = xExtent[1] - xExtent[0];
  const yRange = yExtent[1] - yExtent[0];

  if (yRange > xRange) {
    nodes.forEach(d => {
      const y = parseFloat(d.y);
      d.y = parseFloat(d.x);
      d.x = y;
    });
  }

  svg
    .append("g")
    .attr("stroke", "#999")
    .attr("stroke-opacity", 0.6)
    .selectAll("line")
    .data(links)
    .join("line")
    .attr("stroke-width", 1)
    // set correct positions
    .attr("x1", d => d.source.x)
    .attr("y1", d => d.source.y)
    .attr("x2", d => d.target.x)
    .attr("y2", d => d.target.y);

  svg
    .append("g")
    .attr("stroke", "#fff")
    .attr("stroke-width", 1.5)
    .selectAll("circle")
    .data(nodes)
    .join("circle")
    .attr("r", d => size(d.size))
    .attr("fill", d => color(d.group))
    .attr("cx", d => d.x)
    .attr("cy", d => d.y)
    .on("mouseover", createMouseover(tooltip, chartNode))
    .on("mousemove", createMousemove(tooltip, chartNode))
    .on("mouseleave", createMouseleave(tooltip, chartNode));
}
<commit_msg>Change color a bit for network graph<commit_after>import { select } from "d3-selection";
import { max as d3max, extent as d3extent } from "d3-array";
import { scaleSqrt } from "d3-scale";

import { mouse as d3mouse } from "d3-selection";

import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
  forceX
} from "d3-force";

import { format as formatBytes } from "bytes";

import { createTooltip, createMouseleave, createMouseover } from "./tooltip";

const createMousemove = (tooltipNode, container) => d => {
  const [x, y] = d3mouse(container);
  const nodePath = d.id;

  const str = `${nodePath}<br/><b>${formatBytes(d.value || d.size)}</b>`;

  tooltipNode
    .html(str)
    .style("left", x + 30 + "px")
    .style("top", y + "px");
};

import "./style/style-network.scss";

function color(group) {
  if (group === 0) {
    return "#487ea4";
  } else {
    return "#599e59";
  }
}

const WIDTH = 1000;
const HEIGHT = 1000;

const chartsContainer = document.querySelector("#charts");

for (const { id, root: data } of window.nodesData) {
  const wrapper = document.createElement("div");
  wrapper.innerHTML = `

${id}

      `;
  const chartNode = wrapper.querySelector(".chart");
  chartsContainer.appendChild(chartNode);

  const tooltip = createTooltip(select(chartNode));

  const nodes = Object.entries(data.nodes).map(([id, mod]) => ({ id, ...mod }));
  const index = new Map(nodes.map(d => [d.id, d]));
  const links = data.links.map(d =>
    Object.assign(Object.create(d), {
      source: index.get(d.source),
      target: index.get(d.target),
      value: 1
    })
  );

  const maxLines = d3max(nodes, d => d.size);
  const size = scaleSqrt()
    .domain([1, maxLines])
    .range([5, 30]);

  const svg = select(chartNode)
    .append("svg")
    .attr("viewBox", [0, 0, WIDTH, HEIGHT]);

  const simulation = forceSimulation()
    .force(
      "link",
      forceLink()
        .id(d => d.id)
        .strength(1)
    )
    .force("collide", forceCollide().radius(d => size(d.size) + 1))
    .force("forceX", forceX(HEIGHT / 2).strength(0.05))
    .force("charge", forceManyBody().strength(-10))
    .force("center", forceCenter(WIDTH / 2, HEIGHT / 2));

  simulation.nodes(nodes);
  simulation.force("link").links(links);
  simulation.stop();

  for (let i = 0; i < 150; i++) simulation.tick();

  const xExtent = d3extent(nodes, d => d.x);
  const yExtent = d3extent(nodes, d => d.y);

  const xRange = xExtent[1] - xExtent[0];
  const yRange = yExtent[1] - yExtent[0];

  if (yRange > xRange) {
    nodes.forEach(d => {
      const y = parseFloat(d.y);
      d.y = parseFloat(d.x);
      d.x = y;
    });
  }

  svg
    .append("g")
    .attr("stroke", "#999")
    .attr("stroke-opacity", 0.6)
    .selectAll("line")
    .data(links)
    .join("line")
    .attr("stroke-width", 1)
    // set correct positions
    .attr("x1", d => d.source.x)
    .attr("y1", d => d.source.y)
    .attr("x2", d => d.target.x)
    .attr("y2", d => d.target.y);

  svg
    .append("g")
    .attr("stroke", "#fff")
    .attr("stroke-width", 1.5)
    .selectAll("circle")
    .data(nodes)
    .join("circle")
    .attr("r", d => size(d.size))
    .attr("fill", d => color(d.group))
    .attr("cx", d => d.x)
    .attr("cy", d => d.y)
    .on("mouseover", createMouseover(tooltip, chartNode))
    .on("mousemove", createMousemove(tooltip, chartNode))
    .on("mouseleave", createMouseleave(tooltip, chartNode));
}
<|endoftext|>


<commit_before>// combox.js
// ==================================================
// Author: lute
// Last modified: 2015-02-24
// ==================================================

+function($) {
  'use strict';

  var Combox = function(element, options) {
    this.$element = $(element)
    this.$container = null
    this.$optionContainer = null
    this.$backdrop = null
    this.options = options
    this.menuOpen = false
  }

  Combox.DEFAULTS = {
    placeholder   : '请选择',
    clazz         : '',
    columns       : 3,
    optionIconSize: 48
  }

  Combox.prototype.init = function() {
    this.$element.css({ display: 'none', opacity: 0 })

    var container = '
' +
                      '' +
                        '' + this.options.placeholder + '' +
                        '' +
                      '' +
                      '

' +
                    '
'

    this.$container = $(container).insertAfter(this.$element)
    this.$optionContainer = this.$container.find('.option-container')

    // construct option sudoku
    var $lis = this.$element.find('li')
    var rowCount = Math.ceil($lis.length / this.options.columns)
    var makeup = rowCount * this.options.columns - $lis.length

    for(var i = 1; i <= rowCount; i++) {
      $('
').appendTo(this.$optionContainer)

      var row = $lis.slice((i - 1) * this.options.columns, i * this.options.columns)
      for(var j = 0; j < row.length; j++) {
        var $option = $(row[j]).children()
        $(this.constructOption($option)).appendTo(this.$container.find('.combox-option-row').last())
      }
    }

    for(var k = 0; k < makeup; k++) {
      $(this.constructOption()).appendTo(this.$container.find('.combox-option-row').last())
      if(k === 0) this.$container.find('.combox-option').last().append('')
    }

    var that = this
    this.$container.on('tap', '[data-role="currentOption"]', $.proxy(this.toggle, this))
    this.$container.on('tap', '[data-role="comboxOption"]', function() {
      var currentIndex = $('.combox-option').index($(this))
      var $currentTarget = $(that.$element.find('li')[currentIndex]).children()
      var e = $.Event('fancy:combox:choose', { relatedTarget: $currentTarget[0] })

      // close menu
      that.closeMenu()
      // switch combox chosen text
      that.$container.find('.combox-chosen').text($(this).text())
      // tigger menu chosen event
      that.$element.trigger(e)
    })
  }

  Combox.prototype.constructOption = function(option) {
    var anchor = ''
    if(option) {
      var icon = option.data('icon') ?
        '' : ''
      var text = option.text()
      var disabled = option.data('disabled')

      if(disabled) anchor = '' +
                              icon + text + ''
      else anchor = '' + icon + text + ''

    } else {
      anchor = ''
    }
    return anchor
  }

  Combox.prototype.toggle = function() {
    this.menuOpen ? this.closeMenu() : this.showMenu()
  }

  Combox.prototype.showMenu = function() {
    var that = this
    var e = $.Event('fancy:combox:open')
    this.$element.trigger(e)

    if(this.menuOpen || e.isDefaultPrevented()) return
    this.menuOpen = true

    this.$container.addClass('combox-menu-open')

    this.backdrop(function() {
      var transition = $.support.transition && that.$optionContainer.hasClass('fade')
      that.$optionContainer.show()

      // calculate sudoku cell width
      var width = that.$optionContainer[0].offsetWidth / that.options.columns
      that.$container.find('.combox-option').each(function() {
        $(this).css('width', width + 'px')
      })

      if(transition) that.$optionContainer[0].offsetWidth  // force reflow
      that.$optionContainer.addClass('in')

      var e = $.Event("fancy:combox:opend")
      transition ?
        that.$optionContainer.one($.support.transition.end, function() {
          that.$element.trigger(e)
        }).emulateTransitionEnd(150) : that.$optionContainer.trigger(e)
    })
  }

  Combox.prototype.closeMenu = function(e) {
    if(e) e.preventDefault()

    e = $.Event('fancy:combox:close')
    this.$element.trigger(e)

    if(!this.menuOpen || e.isDefaultPrevented()) return

    this.menuOpen = false
    this.$container.removeClass('combox-menu-open')
    this.$optionContainer.removeClass('in')

    $.support.transition && this.$optionContainer.hasClass('fade') ?
      this.$optionContainer.one($.support.transition.end, $.proxy(this.hideMenu, this))
                           .emulateTransitionEnd(150) :
      this.hideMenu()
  }

  Combox.prototype.hideMenu = function() {
    var that = this
    this.$optionContainer.hide()
    this.backdrop(function() {
      that.$element.trigger('fancy:combox:closed')
    })
  }

  Combox.prototype.backdrop = function(callback) {
    var that = this
    var animate = this.$container.find('.option-container').hasClass('fade') ? 'fade' : ''
    if(this.menuOpen) {
      var doAnimate = $.support.transition && animate
      this.$backdrop = $('
')
        .appendTo($(document.body))
      this.$backdrop.one('tap', $.proxy(this.closeMenu, this))

      if(doAnimate) this.$backdrop[0].offsetWidth  // force reflow
      this.$backdrop.addClass('in')

      if(!callback) return

      doAnimate ?
        this.$backdrop
          .one($.support.transition.end, callback)
          .emulateTransitionEnd(150) :
        callback()
    } else if(!this.menuOpen && this.$backdrop) {
      this.$backdrop.removeClass('in')
      var callbackRemove = function() {
        that.removeBackdrop()
        callback && callback()
      }

      $.support.transition && this.$backdrop.hasClass('fade') ?
        this.$backdrop
          .one($.support.transition.end, callbackRemove)
          .emulateTransitionEnd(150) :
        callbackRemove()
    } else if(callback) {
      callback()
    }
  }

  Combox.prototype.removeBackdrop = function() {
    this.$backdrop && this.$backdrop.remove()
    this.$backdrop = null
  }

  function Plugin(option) {
    return this.each(function() {
      var $this = $(this)
      var options = $.extend({}, Combox.DEFAULTS, $this.data(), typeof option == 'object' && option)
      var data = $this.data('fancy.combox')

      if(!data) $this.data('fancy.combox', (data = new Combox(this, options)))
      data.init()
    })
  }

  var old = $.fn.combox

  $.fn.combox = Plugin
  $.fn.combox.Constructor = Combox

  $.fn.combox.noConflict = function() {
    $.fn.combox = old
    return this
  }

}(window.Zepto);
<commit_msg>change double quote to single quote in combox.js<commit_after>// combox.js
// ==================================================
// Author: lute
// Last modified: 2015-02-24
// ==================================================

+function($) {
  'use strict';

  var Combox = function(element, options) {
    this.$element = $(element)
    this.$container = null
    this.$optionContainer = null
    this.$backdrop = null
    this.options = options
    this.menuOpen = false
  }

  Combox.DEFAULTS = {
    placeholder   : '请选择',
    clazz         : '',
    columns       : 3,
    optionIconSize: 48
  }

  Combox.prototype.init = function() {
    this.$element.css({ display: 'none', opacity: 0 })

    var container = '
' +
                      '' +
                        '' + this.options.placeholder + '' +
                        '' +
                      '' +
                      '

' +
                    '
'

    this.$container = $(container).insertAfter(this.$element)
    this.$optionContainer = this.$container.find('.option-container')

    // construct option sudoku
    var $lis = this.$element.find('li')
    var rowCount = Math.ceil($lis.length / this.options.columns)
    var makeup = rowCount * this.options.columns - $lis.length

    for(var i = 1; i <= rowCount; i++) {
      $('
').appendTo(this.$optionContainer)

      var row = $lis.slice((i - 1) * this.options.columns, i * this.options.columns)
      for(var j = 0; j < row.length; j++) {
        var $option = $(row[j]).children()
        $(this.constructOption($option)).appendTo(this.$container.find('.combox-option-row').last())
      }
    }

    for(var k = 0; k < makeup; k++) {
      $(this.constructOption()).appendTo(this.$container.find('.combox-option-row').last())
      if(k === 0) this.$container.find('.combox-option').last().append('')
    }

    var that = this
    this.$container.on('tap', '[data-role="currentOption"]', $.proxy(this.toggle, this))
    this.$container.on('tap', '[data-role="comboxOption"]', function() {
      var currentIndex = $('.combox-option').index($(this))
      var $currentTarget = $(that.$element.find('li')[currentIndex]).children()
      var e = $.Event('fancy:combox:choose', { relatedTarget: $currentTarget[0] })

      // close menu
      that.closeMenu()
      // switch combox chosen text
      that.$container.find('.combox-chosen').text($(this).text())
      // tigger menu chosen event
      that.$element.trigger(e)
    })
  }

  Combox.prototype.constructOption = function(option) {
    var anchor = ''
    if(option) {
      var icon = option.data('icon') ?
        '' : ''
      var text = option.text()
      var disabled = option.data('disabled')

      if(disabled) anchor = '' +
                              icon + text + ''
      else anchor = '' + icon + text + ''

    } else {
      anchor = ''
    }
    return anchor
  }

  Combox.prototype.toggle = function() {
    this.menuOpen ? this.closeMenu() : this.showMenu()
  }

  Combox.prototype.showMenu = function() {
    var that = this
    var e = $.Event('fancy:combox:open')
    this.$element.trigger(e)

    if(this.menuOpen || e.isDefaultPrevented()) return
    this.menuOpen = true

    this.$container.addClass('combox-menu-open')

    this.backdrop(function() {
      var transition = $.support.transition && that.$optionContainer.hasClass('fade')
      that.$optionContainer.show()

      // calculate sudoku cell width
      var width = that.$optionContainer[0].offsetWidth / that.options.columns
      that.$container.find('.combox-option').each(function() {
        $(this).css('width', width + 'px')
      })

      if(transition) that.$optionContainer[0].offsetWidth  // force reflow
      that.$optionContainer.addClass('in')

      var e = $.Event('fancy:combox:opend')
      transition ?
        that.$optionContainer.one($.support.transition.end, function() {
          that.$element.trigger(e)
        }).emulateTransitionEnd(150) : that.$optionContainer.trigger(e)
    })
  }

  Combox.prototype.closeMenu = function(e) {
    if(e) e.preventDefault()

    e = $.Event('fancy:combox:close')
    this.$element.trigger(e)

    if(!this.menuOpen || e.isDefaultPrevented()) return

    this.menuOpen = false
    this.$container.removeClass('combox-menu-open')
    this.$optionContainer.removeClass('in')

    $.support.transition && this.$optionContainer.hasClass('fade') ?
      this.$optionContainer.one($.support.transition.end, $.proxy(this.hideMenu, this))
                           .emulateTransitionEnd(150) :
      this.hideMenu()
  }

  Combox.prototype.hideMenu = function() {
    var that = this
    this.$optionContainer.hide()
    this.backdrop(function() {
      that.$element.trigger('fancy:combox:closed')
    })
  }

  Combox.prototype.backdrop = function(callback) {
    var that = this
    var animate = this.$container.find('.option-container').hasClass('fade') ? 'fade' : ''
    if(this.menuOpen) {
      var doAnimate = $.support.transition && animate
      this.$backdrop = $('
')
        .appendTo($(document.body))
      this.$backdrop.one('tap', $.proxy(this.closeMenu, this))

      if(doAnimate) this.$backdrop[0].offsetWidth  // force reflow
      this.$backdrop.addClass('in')

      if(!callback) return

      doAnimate ?
        this.$backdrop
          .one($.support.transition.end, callback)
          .emulateTransitionEnd(150) :
        callback()
    } else if(!this.menuOpen && this.$backdrop) {
      this.$backdrop.removeClass('in')
      var callbackRemove = function() {
        that.removeBackdrop()
        callback && callback()
      }

      $.support.transition && this.$backdrop.hasClass('fade') ?
        this.$backdrop
          .one($.support.transition.end, callbackRemove)
          .emulateTransitionEnd(150) :
        callbackRemove()
    } else if(callback) {
      callback()
    }
  }

  Combox.prototype.removeBackdrop = function() {
    this.$backdrop && this.$backdrop.remove()
    this.$backdrop = null
  }

  function Plugin(option) {
    return this.each(function() {
      var $this = $(this)
      var options = $.extend({}, Combox.DEFAULTS, $this.data(), typeof option == 'object' && option)
      var data = $this.data('fancy.combox')

      if(!data) $this.data('fancy.combox', (data = new Combox(this, options)))
      data.init()
    })
  }

  var old = $.fn.combox

  $.fn.combox = Plugin
  $.fn.combox.Constructor = Combox

  $.fn.combox.noConflict = function() {
    $.fn.combox = old
    return this
  }

}(window.Zepto);
<|endoftext|>


<commit_before>/*jslint indent: 2, unparam: true*/
/*global document: false, MutationObserver: false, chrome: false, window: false*/
"use strict";

function $(s, elem) {
  elem = elem || document;
  return elem.querySelector(s);
}

function createTag(name, className, innerHTML) {
  var tag = document.createElement(name);
  tag.className = className;

  if (innerHTML) {
    tag.innerHTML = innerHTML;
  }

  return tag;
}

function createLink(className, tagName, linkHref) {
  var link;

  // Param defaults
  tagName  = tagName  || 'a';
  linkHref = linkHref || '#';
  link     = createTag(tagName, className);

  if (tagName === 'a') {
    link.href = linkHref;
  }

  link.appendChild(document.createTextNode('Start timer'));
  return link;
}

function invokeIfFunction(trial) {
  if (trial instanceof Function) {
    return trial();
  }
  return trial;
}

function getFullPageHeight() {
  var body = document.body,
    html = document.documentElement;

  return Math.max(body.scrollHeight, body.offsetHeight,
                         html.clientHeight, html.scrollHeight, html.offsetHeight);
}

var togglbutton = {
  isStarted: false,
  element: null,
  serviceName: '',
  mousedownTrigger: null,
  projectBlurTrigger: null,
  taskBlurTrigger: null,
  tagsVisible: false,
  hasTasks: false,
  currentDescription: "",
  fullPageHeight: getFullPageHeight(),
  fullVersion: "TogglButton",
  render: function (selector, opts, renderer) {
    chrome.extension.sendMessage({type: 'activate'}, function (response) {
      if (response.success) {
        togglbutton.fullVersion = response.version;
        if (opts.observe) {
          var observer = new MutationObserver(function (mutations) {
            togglbutton.renderTo(selector, renderer);
          });
          observer.observe(document, {childList: true, subtree: true});
        }
        togglbutton.renderTo(selector, renderer);
      }
    });
  },

  renderTo: function (selector, renderer) {
    var i, len, elems = document.querySelectorAll(selector);
    for (i = 0, len = elems.length; i < len; i += 1) {
      elems[i].classList.add('toggl');
    }
    for (i = 0, len = elems.length; i < len; i += 1) {
      renderer(elems[i]);
    }
  },

  topPosition: function (rect, editFormWidth, editFormHeight) {
    var left = (rect.left - 10),
      top = (rect.top + document.body.scrollTop - 10);

    if (left + editFormWidth > window.innerWidth) {
      left = window.innerWidth - 10 - editFormWidth;
    }
    if (top + editFormHeight > togglbutton.fullPageHeight) {
      top = window.innerHeight + document.body.scrollTop - 10 - editFormHeight;
    }
    return {left: left, top: top};
  },

  getSelectedTags: function () {
    var tags = [],
      tag,
      i,
      s = document.getElementById("toggl-button-tag");
    for (i = 0; i < s.options.length; i += 1) {
      if (s.options[i].selected === true) {
        tag = s.options[i].innerHTML;
        tags.push(tag);
      }
    }
    return tags;
  },

  delegateTaskClick: function (e) {
    // Ignore this click if it caused the last task blur.
    if (togglbutton.taskBlurTrigger === e.target) {
      togglbutton.taskBlurTrigger = null;
      return;
    }

    var dropdown = document.getElementById('toggl-button-task'),
      event = document.createEvent('MouseEvents');
    event.initMouseEvent('mousedown', true, true, window);
    dropdown.dispatchEvent(event);
  },

  resetTasks: function () {
    $("#toggl-button-task-placeholder").removeEventListener('click', togglbutton.delegateTaskClick);
    $("#toggl-button-task-placeholder > div").innerHTML = "Add task";
    $("#toggl-button-task").innerHTML = "";
  },

  addEditForm: function (response) {
    togglbutton.hasTasks = response.hasTasks;
    if (response === null || !response.showPostPopup) {
      return;
    }

    var pid = (!!response.entry.pid) ? response.entry.pid : 0,
      projectSelect,
      handler,
      position,
      editFormHeight = 350,
      editFormWidth = 240,
      submitForm,
      taskSelect,
      updateTags,
      closeTagsList,
      elemRect,
      div = document.createElement('div'),
      editForm;

    elemRect = togglbutton.element.getBoundingClientRect();
    editForm = $("#toggl-button-edit-form");
    position = togglbutton.topPosition(elemRect, editFormWidth, editFormHeight);

    if (editForm !== null) {
      togglbutton.fetchTasks(pid, editForm);
      $("#toggl-button-description").value = response.entry.description;
      $("#toggl-button-project").value = pid;
      projectSelect = document.getElementById("toggl-button-project");
      $("#toggl-button-project-placeholder > div").innerHTML = (pid === 0) ? "Add project" : projectSelect.options[projectSelect.selectedIndex].text;
      togglbutton.resetTasks();
      $("#toggl-button-tag-placeholder > div", editForm).innerHTML = "Add tags";
      $("#toggl-button-tag").value = "";
      editForm.style.left = position.left + "px";
      editForm.style.top = position.top + "px";
      editForm.style.display = "block";
      return;
    }

    div.innerHTML = response.html.replace("{service}", togglbutton.serviceName);
    editForm = div.firstChild;
    editForm.style.left = position.left + "px";
    editForm.style.top = position.top + "px";
    if (togglbutton.serviceName === "basecamp") {
      editForm.style.position = "fixed";
    }
    document.body.appendChild(editForm);
    togglbutton.fetchTasks(pid, editForm);

    handler = function (e) {
      if (!/toggl-button/.test(e.target.className) && !/toggl-button/.test(e.target.parentElement.className)) {
        closeTagsList(true);
        editForm.style.display = "none";
        this.removeEventListener("click", handler);
      }
    };

    submitForm = function (that) {
      var taskButton = $("#toggl-button-task"),
        selectedProject = $("#toggl-button-project"),
        request = {
          type: "update",
          description: $("#toggl-button-description").value,
          pid: selectedProject.value,
          projectName: selectedProject.options[selectedProject.selectedIndex].text,
          tags: togglbutton.getSelectedTags(),
          tid: (taskButton && taskButton.value) ? taskButton.value : null
        };
      chrome.extension.sendMessage(request);
      closeTagsList(true);
      editForm.style.display = "none";
    };

    updateTags = function () {
      var tags = togglbutton.getSelectedTags();
      if (tags.length) {
        tags = tags.join(',');
      } else {
        tags = "Add tags";
      }
      $("#toggl-button-tag-placeholder > div", editForm).innerHTML = tags;
    };

    closeTagsList = function (close) {
      var dropdown = document.getElementById('toggl-button-tag');
      if (close) {
        dropdown.style.display = "none";
        togglbutton.tagsVisible = false;
        return;
      }
      if (togglbutton.tagsVisible) {
        dropdown.style.display = "none";
        updateTags();
      } else {
        dropdown.style.display = "block";
      }
      togglbutton.tagsVisible = !togglbutton.tagsVisible;
    };

    $("#toggl-button-description", editForm).value = response.entry.description;
    $("#toggl-button-project", editForm).value = pid;
    projectSelect = $("#toggl-button-project", editForm);
    $("#toggl-button-project-placeholder > div", editForm).innerHTML = (pid === 0) ? "Add project" : projectSelect.options[projectSelect.selectedIndex].text;
    $("#toggl-button-hide", editForm).addEventListener('click', function (e) {
      closeTagsList(true);
      editForm.style.display = "none";
    });

    $("#toggl-button-update", editForm).addEventListener('click', function (e) {
      submitForm(this);
    });

    $("form", editForm).addEventListener('submit', function (e) {
      submitForm(this);
    });

    $(".toggl-button", editForm).addEventListener('click', function (e) {
      var link;
      e.preventDefault();
      link = togglbutton.element;
      link.classList.remove('active');
      link.style.color = '';
      if (!link.classList.contains("min")) {
        link.innerHTML = 'Start timer';
      }
      chrome.extension.sendMessage({type: 'stop'}, togglbutton.addEditForm);
      closeTagsList(true);
      editForm.style.display = "none";
      return false;
    });
    document.addEventListener('mousedown', function (e) {
      togglbutton.mousedownTrigger = e.target;
    });
    document.addEventListener('mouseup', function (e) {
      togglbutton.mousedownTrigger = null;
    });

    $("#toggl-button-project-placeholder", editForm).addEventListener('click', function (e) {
      // Ignore this click if it caused the last project blur.
      if (togglbutton.projectBlurTrigger === e.target) {
        togglbutton.projectBlurTrigger = null;
        return;
      }

      var dropdown = document.getElementById('toggl-button-project'),
        event = document.createEvent('MouseEvents');
      event.initMouseEvent('mousedown', true, true, window);
      dropdown.dispatchEvent(event);
    });

    $("#toggl-button-tag-placeholder", editForm).addEventListener('click', function (e) {
      closeTagsList(false);
    });

    projectSelect.addEventListener('change', function (e) {
      var projectId = this.value;
      $("#toggl-button-project-placeholder > div", editForm).innerHTML = (projectId === "0") ? "Add project" : this.options[this.selectedIndex].text;
      // Force blur.
      togglbutton.projectBlurTrigger = null;
      projectSelect.blur();

      togglbutton.fetchTasks(projectId, editForm);
    });
    projectSelect.addEventListener('click', function () {
      // Catch click in case user selects an already-selected item - force blur.
      togglbutton.projectBlurTrigger = null;
      projectSelect.blur();
    });
    projectSelect.addEventListener('blur', function () {
      togglbutton.projectBlurTrigger = togglbutton.mousedownTrigger;
    });

    taskSelect = $("#toggl-button-task", editForm);
    taskSelect.addEventListener('change', function (e) {
      $("#toggl-button-task-placeholder > div", editForm).innerHTML = (taskSelect.value === "0") ? "Add task" : taskSelect.options[taskSelect.selectedIndex].text;

      // Force blur.
      togglbutton.taskBlurTrigger = null;
      taskSelect.blur();
    });
    taskSelect.addEventListener('click', function () {
      // Catch click in case user selects an already-selected item - force blur.
      togglbutton.taskBlurTrigger = null;
      projectSelect.blur();
    });
    taskSelect.addEventListener('blur', function (e) {
      togglbutton.taskBlurTrigger = togglbutton.mousedownTrigger;
    });

    document.addEventListener("click", handler);
  },

  fetchTasks: function (projectId, editForm) {
    var tasksRow = document.getElementById("toggl-button-tasks-row");
    togglbutton.resetTasks();
    if (!togglbutton.hasTasks || projectId === 0) {
      tasksRow.style.display = "none";
      return;
    }
    // If tasks are available, populate the task dropdown.
    chrome.extension.sendMessage({type: 'getTasksHtml', projectId: projectId}, function (response) {
      if (response && response.success && response.html) {
        $('#toggl-button-task').innerHTML = response.html;
        $("#toggl-button-task-placeholder", editForm).addEventListener('click', togglbutton.delegateTaskClick);
        tasksRow.style.display = "block";
      } else {
        tasksRow.style.display = "none";
      }
    });
  },

  createTimerLink: function (params) {
    var link = createLink('toggl-button');
    togglbutton.currentDescription = invokeIfFunction(params.description);
    function activate() {
      link.classList.add('active');
      link.style.color = '#1ab351';
      if (params.buttonType !== 'minimal') {
        link.innerHTML = 'Stop timer';
      }
    }

    function deactivate() {
      link.classList.remove('active');
      link.style.color = '';
      if (params.buttonType !== 'minimal') {
        link.innerHTML = 'Start timer';
      }
    }

    link.classList.add(params.className);
    togglbutton.serviceName = params.className;

    if (params.buttonType === 'minimal') {
      link.classList.add('min');
      link.removeChild(link.firstChild);
    }

    link.addEventListener('click', function (e) {
      var opts;
      e.preventDefault();

      if (link.classList.contains('active')) {
        deactivate();
        opts = {type: 'stop'};
      } else {
        activate();
        opts = {
          type: 'timeEntry',
          respond: true,
          projectId: invokeIfFunction(params.projectId),
          description: invokeIfFunction(params.description),
          tags: invokeIfFunction(params.tags),
          projectName: invokeIfFunction(params.projectName),
          createdWith: togglbutton.fullVersion
        };
      }
      togglbutton.element = e.target;
      chrome.extension.sendMessage(opts, togglbutton.addEditForm);

      return false;
    });

    // new button created - set state
    chrome.extension.sendMessage({type: 'currentEntry'}, function (response) {
      var description, currentEntry;
      if (response.success) {
        currentEntry = response.currentEntry;
        description = invokeIfFunction(params.description);
        if (description === currentEntry.description) {
          activate(link);
        }
      }
    });

    return link;
  },

  // If "entry" is passed, make button active; otherwise inactive.
  updateTimerLink: function (entry) {
    var linkText = '',
      color = '',
      link = $(".toggl-button"),
      minimal;
    if (link === null) {
      return;
    }
    minimal = link.classList.contains("min");

    if (!entry || togglbutton.currentDescription !== entry.description) {
      link.classList.remove('active');
      if (!minimal) {
        linkText = 'Start timer';
      }
    } else {
      link.classList.add('active');
      color = '#1ab351';
      if (!minimal) {
        linkText = 'Stop timer';
      }
    }
    link.style.color = color;
    link.innerHTML = linkText;
  },

  newMessage: function (request, sender, sendResponse) {
    if (request.type === 'stop-entry') {
      togglbutton.updateTimerLink();
    } else if (request.type === 'sync') {
      if ($("#toggl-button-edit-form") !== null) {
        $("#toggl-button-edit-form").remove();
      }
    }
  }
};

chrome.extension.onMessage.addListener(togglbutton.newMessage);
window.addEventListener('focus', function (e) {
  // update button state
  chrome.extension.sendMessage({type: 'currentEntry'}, function (response) {
    togglbutton.updateTimerLink(response.currentEntry);
  });
});<commit_msg>Fixed post start popup position in basecamp next<commit_after>/*jslint indent: 2, unparam: true*/
/*global document: false, MutationObserver: false, chrome: false, window: false*/
"use strict";

function $(s, elem) {
  elem = elem || document;
  return elem.querySelector(s);
}

function createTag(name, className, innerHTML) {
  var tag = document.createElement(name);
  tag.className = className;

  if (innerHTML) {
    tag.innerHTML = innerHTML;
  }

  return tag;
}

function createLink(className, tagName, linkHref) {
  var link;

  // Param defaults
  tagName  = tagName  || 'a';
  linkHref = linkHref || '#';
  link     = createTag(tagName, className);

  if (tagName === 'a') {
    link.href = linkHref;
  }

  link.appendChild(document.createTextNode('Start timer'));
  return link;
}

function invokeIfFunction(trial) {
  if (trial instanceof Function) {
    return trial();
  }
  return trial;
}

function getFullPageHeight() {
  var body = document.body,
    html = document.documentElement;

  return Math.max(body.scrollHeight, body.offsetHeight,
                         html.clientHeight, html.scrollHeight, html.offsetHeight);
}

var togglbutton = {
  isStarted: false,
  element: null,
  serviceName: '',
  mousedownTrigger: null,
  projectBlurTrigger: null,
  taskBlurTrigger: null,
  tagsVisible: false,
  hasTasks: false,
  currentDescription: "",
  fullPageHeight: getFullPageHeight(),
  fullVersion: "TogglButton",
  render: function (selector, opts, renderer) {
    chrome.extension.sendMessage({type: 'activate'}, function (response) {
      if (response.success) {
        togglbutton.fullVersion = response.version;
        if (opts.observe) {
          var observer = new MutationObserver(function (mutations) {
            togglbutton.renderTo(selector, renderer);
          });
          observer.observe(document, {childList: true, subtree: true});
        }
        togglbutton.renderTo(selector, renderer);
      }
    });
  },

  renderTo: function (selector, renderer) {
    var i, len, elems = document.querySelectorAll(selector);
    for (i = 0, len = elems.length; i < len; i += 1) {
      elems[i].classList.add('toggl');
    }
    for (i = 0, len = elems.length; i < len; i += 1) {
      renderer(elems[i]);
    }
  },

  topPosition: function (rect, editFormWidth, editFormHeight) {
    var left = (rect.left - 10),
      top = (rect.top + document.body.scrollTop - 10);

    if (left + editFormWidth > window.innerWidth) {
      left = window.innerWidth - 10 - editFormWidth;
    }
    if (top + editFormHeight > togglbutton.fullPageHeight) {
      top = window.innerHeight + document.body.scrollTop - 10 - editFormHeight;
    }
    return {left: left, top: top};
  },

  getSelectedTags: function () {
    var tags = [],
      tag,
      i,
      s = document.getElementById("toggl-button-tag");
    for (i = 0; i < s.options.length; i += 1) {
      if (s.options[i].selected === true) {
        tag = s.options[i].innerHTML;
        tags.push(tag);
      }
    }
    return tags;
  },

  delegateTaskClick: function (e) {
    // Ignore this click if it caused the last task blur.
    if (togglbutton.taskBlurTrigger === e.target) {
      togglbutton.taskBlurTrigger = null;
      return;
    }

    var dropdown = document.getElementById('toggl-button-task'),
      event = document.createEvent('MouseEvents');
    event.initMouseEvent('mousedown', true, true, window);
    dropdown.dispatchEvent(event);
  },

  resetTasks: function () {
    $("#toggl-button-task-placeholder").removeEventListener('click', togglbutton.delegateTaskClick);
    $("#toggl-button-task-placeholder > div").innerHTML = "Add task";
    $("#toggl-button-task").innerHTML = "";
  },

  addEditForm: function (response) {
    togglbutton.hasTasks = response.hasTasks;
    if (response === null || !response.showPostPopup) {
      return;
    }

    var pid = (!!response.entry.pid) ? response.entry.pid : 0,
      projectSelect,
      handler,
      position,
      editFormHeight = 350,
      editFormWidth = 240,
      submitForm,
      taskSelect,
      updateTags,
      closeTagsList,
      elemRect,
      div = document.createElement('div'),
      editForm;

    elemRect = togglbutton.element.getBoundingClientRect();
    editForm = $("#toggl-button-edit-form");
    position = togglbutton.topPosition(elemRect, editFormWidth, editFormHeight);

    if (editForm !== null) {
      togglbutton.fetchTasks(pid, editForm);
      $("#toggl-button-description").value = response.entry.description;
      $("#toggl-button-project").value = pid;
      projectSelect = document.getElementById("toggl-button-project");
      $("#toggl-button-project-placeholder > div").innerHTML = (pid === 0) ? "Add project" : projectSelect.options[projectSelect.selectedIndex].text;
      togglbutton.resetTasks();
      $("#toggl-button-tag-placeholder > div", editForm).innerHTML = "Add tags";
      $("#toggl-button-tag").value = "";
      editForm.style.left = position.left + "px";
      editForm.style.top = position.top + "px";
      editForm.style.display = "block";
      return;
    }

    div.innerHTML = response.html.replace("{service}", togglbutton.serviceName);
    editForm = div.firstChild;
    editForm.style.left = position.left + "px";
    editForm.style.top = position.top + "px";
    document.body.appendChild(editForm);
    togglbutton.fetchTasks(pid, editForm);

    handler = function (e) {
      if (!/toggl-button/.test(e.target.className) && !/toggl-button/.test(e.target.parentElement.className)) {
        closeTagsList(true);
        editForm.style.display = "none";
        this.removeEventListener("click", handler);
      }
    };

    submitForm = function (that) {
      var taskButton = $("#toggl-button-task"),
        selectedProject = $("#toggl-button-project"),
        request = {
          type: "update",
          description: $("#toggl-button-description").value,
          pid: selectedProject.value,
          projectName: selectedProject.options[selectedProject.selectedIndex].text,
          tags: togglbutton.getSelectedTags(),
          tid: (taskButton && taskButton.value) ? taskButton.value : null
        };
      chrome.extension.sendMessage(request);
      closeTagsList(true);
      editForm.style.display = "none";
    };

    updateTags = function () {
      var tags = togglbutton.getSelectedTags();
      if (tags.length) {
        tags = tags.join(',');
      } else {
        tags = "Add tags";
      }
      $("#toggl-button-tag-placeholder > div", editForm).innerHTML = tags;
    };

    closeTagsList = function (close) {
      var dropdown = document.getElementById('toggl-button-tag');
      if (close) {
        dropdown.style.display = "none";
        togglbutton.tagsVisible = false;
        return;
      }
      if (togglbutton.tagsVisible) {
        dropdown.style.display = "none";
        updateTags();
      } else {
        dropdown.style.display = "block";
      }
      togglbutton.tagsVisible = !togglbutton.tagsVisible;
    };

    $("#toggl-button-description", editForm).value = response.entry.description;
    $("#toggl-button-project", editForm).value = pid;
    projectSelect = $("#toggl-button-project", editForm);
    $("#toggl-button-project-placeholder > div", editForm).innerHTML = (pid === 0) ? "Add project" : projectSelect.options[projectSelect.selectedIndex].text;
    $("#toggl-button-hide", editForm).addEventListener('click', function (e) {
      closeTagsList(true);
      editForm.style.display = "none";
    });

    $("#toggl-button-update", editForm).addEventListener('click', function (e) {
      submitForm(this);
    });

    $("form", editForm).addEventListener('submit', function (e) {
      submitForm(this);
    });

    $(".toggl-button", editForm).addEventListener('click', function (e) {
      var link;
      e.preventDefault();
      link = togglbutton.element;
      link.classList.remove('active');
      link.style.color = '';
      if (!link.classList.contains("min")) {
        link.innerHTML = 'Start timer';
      }
      chrome.extension.sendMessage({type: 'stop'}, togglbutton.addEditForm);
      closeTagsList(true);
      editForm.style.display = "none";
      return false;
    });
    document.addEventListener('mousedown', function (e) {
      togglbutton.mousedownTrigger = e.target;
    });
    document.addEventListener('mouseup', function (e) {
      togglbutton.mousedownTrigger = null;
    });

    $("#toggl-button-project-placeholder", editForm).addEventListener('click', function (e) {
      // Ignore this click if it caused the last project blur.
      if (togglbutton.projectBlurTrigger === e.target) {
        togglbutton.projectBlurTrigger = null;
        return;
      }

      var dropdown = document.getElementById('toggl-button-project'),
        event = document.createEvent('MouseEvents');
      event.initMouseEvent('mousedown', true, true, window);
      dropdown.dispatchEvent(event);
    });

    $("#toggl-button-tag-placeholder", editForm).addEventListener('click', function (e) {
      closeTagsList(false);
    });

    projectSelect.addEventListener('change', function (e) {
      var projectId = this.value;
      $("#toggl-button-project-placeholder > div", editForm).innerHTML = (projectId === "0") ? "Add project" : this.options[this.selectedIndex].text;
      // Force blur.
      togglbutton.projectBlurTrigger = null;
      projectSelect.blur();

      togglbutton.fetchTasks(projectId, editForm);
    });
    projectSelect.addEventListener('click', function () {
      // Catch click in case user selects an already-selected item - force blur.
      togglbutton.projectBlurTrigger = null;
      projectSelect.blur();
    });
    projectSelect.addEventListener('blur', function () {
      togglbutton.projectBlurTrigger = togglbutton.mousedownTrigger;
    });

    taskSelect = $("#toggl-button-task", editForm);
    taskSelect.addEventListener('change', function (e) {
      $("#toggl-button-task-placeholder > div", editForm).innerHTML = (taskSelect.value === "0") ? "Add task" : taskSelect.options[taskSelect.selectedIndex].text;

      // Force blur.
      togglbutton.taskBlurTrigger = null;
      taskSelect.blur();
    });
    taskSelect.addEventListener('click', function () {
      // Catch click in case user selects an already-selected item - force blur.
      togglbutton.taskBlurTrigger = null;
      projectSelect.blur();
    });
    taskSelect.addEventListener('blur', function (e) {
      togglbutton.taskBlurTrigger = togglbutton.mousedownTrigger;
    });

    document.addEventListener("click", handler);
  },

  fetchTasks: function (projectId, editForm) {
    var tasksRow = document.getElementById("toggl-button-tasks-row");
    togglbutton.resetTasks();
    if (!togglbutton.hasTasks || projectId === 0) {
      tasksRow.style.display = "none";
      return;
    }
    // If tasks are available, populate the task dropdown.
    chrome.extension.sendMessage({type: 'getTasksHtml', projectId: projectId}, function (response) {
      if (response && response.success && response.html) {
        $('#toggl-button-task').innerHTML = response.html;
        $("#toggl-button-task-placeholder", editForm).addEventListener('click', togglbutton.delegateTaskClick);
        tasksRow.style.display = "block";
      } else {
        tasksRow.style.display = "none";
      }
    });
  },

  createTimerLink: function (params) {
    var link = createLink('toggl-button');
    togglbutton.currentDescription = invokeIfFunction(params.description);
    function activate() {
      link.classList.add('active');
      link.style.color = '#1ab351';
      if (params.buttonType !== 'minimal') {
        link.innerHTML = 'Stop timer';
      }
    }

    function deactivate() {
      link.classList.remove('active');
      link.style.color = '';
      if (params.buttonType !== 'minimal') {
        link.innerHTML = 'Start timer';
      }
    }

    link.classList.add(params.className);
    togglbutton.serviceName = params.className;

    if (params.buttonType === 'minimal') {
      link.classList.add('min');
      link.removeChild(link.firstChild);
    }

    link.addEventListener('click', function (e) {
      var opts;
      e.preventDefault();

      if (link.classList.contains('active')) {
        deactivate();
        opts = {type: 'stop'};
      } else {
        activate();
        opts = {
          type: 'timeEntry',
          respond: true,
          projectId: invokeIfFunction(params.projectId),
          description: invokeIfFunction(params.description),
          tags: invokeIfFunction(params.tags),
          projectName: invokeIfFunction(params.projectName),
          createdWith: togglbutton.fullVersion
        };
      }
      togglbutton.element = e.target;
      chrome.extension.sendMessage(opts, togglbutton.addEditForm);

      return false;
    });

    // new button created - set state
    chrome.extension.sendMessage({type: 'currentEntry'}, function (response) {
      var description, currentEntry;
      if (response.success) {
        currentEntry = response.currentEntry;
        description = invokeIfFunction(params.description);
        if (description === currentEntry.description) {
          activate(link);
        }
      }
    });

    return link;
  },

  // If "entry" is passed, make button active; otherwise inactive.
  updateTimerLink: function (entry) {
    var linkText = '',
      color = '',
      link = $(".toggl-button"),
      minimal;
    if (link === null) {
      return;
    }
    minimal = link.classList.contains("min");

    if (!entry || togglbutton.currentDescription !== entry.description) {
      link.classList.remove('active');
      if (!minimal) {
        linkText = 'Start timer';
      }
    } else {
      link.classList.add('active');
      color = '#1ab351';
      if (!minimal) {
        linkText = 'Stop timer';
      }
    }
    link.style.color = color;
    link.innerHTML = linkText;
  },

  newMessage: function (request, sender, sendResponse) {
    if (request.type === 'stop-entry') {
      togglbutton.updateTimerLink();
    } else if (request.type === 'sync') {
      if ($("#toggl-button-edit-form") !== null) {
        $("#toggl-button-edit-form").remove();
      }
    }
  }
};

chrome.extension.onMessage.addListener(togglbutton.newMessage);
window.addEventListener('focus', function (e) {
  // update button state
  chrome.extension.sendMessage({type: 'currentEntry'}, function (response) {
    togglbutton.updateTimerLink(response.currentEntry);
  });
});<|endoftext|>


<commit_before>// Beautiful player using POO

// Work In Progress

class Player {
  constructor(params = {
    basePath,
    parent,
    id,
    className,
    link,
    width,
    height,
    controls,
    autoplay,
    loop
  }) {

    this.parent = document.querySelector(params.parent)
    this.link = params.basePath + params.link
    this.width = params.width
    this.height = params.height
    this.autoplay = params.autoplay
    this.loop = params.loop
    this.id = params.id
    this.className = params.className

    this.playerContainer = document.createElement('div')

    this.playerContainer.className = 'playerContainer'
    this.playerContainer.tabIndex = 0
    this.playerContainer.style.width = this.width + 'px'
    this.playerContainer.style.height = 'auto'
    this.parent.appendChild(this.playerContainer)

    this.player = document.createElement('video')

    this.player.src = this.link
    this.player.id = this.id
    this.player.className = this.className
    this.player.autoplay = this.autoplay
    this.player.controls = false
    this.player.loop = this.loop

    this.playerContainer.appendChild(this.player)

    // Check if controls are required
    if (params.controls) {
      this.createControllers()
    } else {
      console.log('To enable controllers turn controls paramaters to true')
    }
  }

  createControllers() {

    let body = document.querySelector('body')

    let player = this.player
    let playerContainer = this.playerContainer
    let parent = this.parent

    // Create control bar
    let controlBar = document.createElement('div')
    controlBar.className = 'controlBar'
    playerContainer.appendChild(controlBar)

    let timer = null

    // Create play/pause button
    let playPauseButton = document.createElement('div')
    playPauseButton.className = 'playPauseButton'
    controlBar.appendChild(playPauseButton)

    // Create volumeContainer
    let volumeContainer = document.createElement('div')
    volumeContainer.className = 'volumeContainer'
    controlBar.appendChild(volumeContainer)

    let volumeTimer = null

    // Create Mute Button
    let muteButton = document.createElement('div')
    muteButton.className = 'muteButton'
    volumeContainer.appendChild(muteButton)

    // Create Volume Slider
    let volumeController = document.createElement('div')
    volumeController.className = 'volumeController'
    volumeContainer.appendChild(volumeController)

    // Create Volume Bar inside Volume Slider
    let volumeBar = document.createElement('div')
    volumeBar.className = 'volumeBar'
    volumeController.appendChild(volumeBar)

    let activeVolume = false

    // Create currentTimer
    let currentTimer = '0:00'

    // Create durationTimer
    let durationTimer = '0:00'

    // Create timerComplete
    let timerComplete = document.createElement('div')
    timerComplete.className = 'timerComplete'
    timerComplete.innerHTML = currentTimer + '\xa0 /\xa0' + durationTimer

    controlBar.appendChild(timerComplete)

    // Create Timeline
    let timeline = document.createElement('div')
    timeline.className = 'timeline'
    controlBar.appendChild(timeline)
    let activeTimeline = false

    // Create vars for lerp
    let position = 0
    let amount = 0.1

    // Create TimelineLoadingBar
    let timelineLoadingBar = document.createElement('div')
    timelineLoadingBar.className = 'timelineLoadingBar'
    timeline.appendChild(timelineLoadingBar)

    // Create Timeline Bar
    let timelineBar = document.createElement('div')
    timelineBar.className = 'timelineBar'
    timeline.appendChild(timelineBar)

    // Create fullscreenButton
    let fullscreenButton = document.createElement('div')
    fullscreenButton.className = 'fullscreenButton'
    controlBar.appendChild(fullscreenButton)

    let fullscreenMode = false

    // Check at start

    // Check Video Status to display the right button
    if (player.autoplay) {
      playPauseButton.classList.add('isPlaying')
    } else {
      playPauseButton.classList.remove('isPlaying')
    }

    // Set volume at start
    player.volume = 0
    volumeBar.style.transform = 'scaleX(' + player.volume + ')'

    // Events

    // PlayPause Events
    let playPauseEvents = [player, playPauseButton]
    playPauseEvents.forEach(function(e) {
      e.addEventListener('click', function() {
        if (player.paused) {
          player.play()
          playPauseButton.classList.add('isPlaying')
        } else {
          player.pause()
          playPauseButton.classList.remove('isPlaying')
        }
      })
    })

    parent.addEventListener('keydown', function(e) {
      if (e.keyCode === 32) {
        if (player.paused) {
          player.play()
          playPauseButton.classList.add('isPlaying')
        } else {
          player.pause()
          playPauseButton.classList.remove('isPlaying')
        }
      }
    })

    // Volume Events
    volumeContainer.addEventListener('mouseover', function() {
      volumeController.classList.add('volumeControllerHovered')
    })

    volumeContainer.addEventListener('mouseleave', function() {
      volumeTimer = setTimeout(mouseOut, 2000)
    })

    function mouseOut() {
      volumeController.classList.remove('volumeControllerHovered')
      clearTimeout(volumeTimer)
    }

    muteButton.addEventListener('click', function() {
      let volumeSaved = 0
      if (!muteButton.classList.contains('isMuted')) {
        this.volumeSaved = player.volume
        player.volume = 0
        muteButton.classList.toggle('isMuted')
      } else {
        player.volume = this.volumeSaved
        muteButton.classList.toggle('isMuted')
      }
    })

    volumeController.addEventListener('mousedown', function(event) {
      activeVolume = true
      muteButton.classList.remove('isMuted')
      if (0.1 <= event.offsetX / volumeController.offsetWidth && event.offsetX / volumeController.offsetWidth <= 0.9) {
        player.volume = event.offsetX / volumeController.offsetWidth
      } else if (event.offsetX / volumeController.offsetWidth < 0.1) {
        player.volume = 0
        muteButton.classList.add('isMuted')
      } else {
        player.volume = 1
      }
      volumeBar.style.transform = 'scaleX(' + player.volume + ')'
    })

    volumeController.addEventListener('mousemove', function(event) {
      if (activeVolume) {
        muteButton.classList.remove('isMuted')
        if (0.1 <= event.offsetX / volumeController.offsetWidth && event.offsetX / volumeController.offsetWidth <= 0.9) {
          player.volume = event.offsetX / volumeController.offsetWidth
        } else if (event.offsetX / volumeController.offsetWidth < 0.1) {
          player.volume = 0
          muteButton.classList.add('isMuted')
        } else {
          player.volume = 1
        }
        volumeBar.style.transform = 'scaleX(' + player.volume + ')'
      }
    })

    window.addEventListener('mouseup', function() {
      activeVolume = false
    })

    // Timeline Events
    setInterval(function barProgression() {
      // Display bar progression
      let timelineBarProgression = player.currentTime / player.duration

      if (position === 0) {
        timelineBar.style.transform = 'scaleX(' + timelineBarProgression + ')'
        position += player.currentTime
      } else {
        position += (player.currentTime - position) * amount
        timelineBarProgression = position / player.duration
        timelineBar.style.transform = 'scaleX(' + timelineBarProgression + ')'
      }

      // Display on timeline what is already loaded
      timelineLoadingBar.style.transform = 'scaleX(' + player.buffered.end(0) / player.duration + ')'

    }, 16)

    player.addEventListener('timeupdate', function() {

      // Display time formated
      let durationTimeFormated = new Date(null)
      durationTimeFormated.setSeconds(Math.floor(player.duration))
      let ds = durationTimeFormated.getSeconds()
      let dm = durationTimeFormated.getMinutes()
      let dh = durationTimeFormated.getUTCHours()

      let currentTimeFormated = new Date(null)
      currentTimeFormated.setSeconds(Math.floor(player.currentTime))

      let cs = currentTimeFormated.getSeconds()
      let cm = currentTimeFormated.getMinutes()
      let ch = currentTimeFormated.getUTCHours()

      // Checks for currentTime display
      if (cs >= 0 && cs < 10)  {
        cs = '0' + cs
      }
      if (ch > 0 && (cm === 0 && cm < 10)) {
        cm = '0' + cm
      }

      // Checks for durationTime display
      if (ds >= 0 && ds < 10) {
        ds = '0' + ds
      }
      if (dh > 0 && (dm === 0 && dm < 10)) {
        dm = '0' + cm
      }

      //
      if (dh === 0 || ch === 0) {
        currentTimer = cm + ':' + cs
        durationTimer = dm + ':' + ds
      } else {
        currentTimer = ch + ':' + cm + ':' + cs
        durationTimer = dh + ':' + dm + ':' + ds
      }

      timerComplete.innerHTML = currentTimer + '\xa0/\xa0' + durationTimer

    })

    timeline.addEventListener('mousedown', function(event) {
      player.pause()
      timelineBar.classList.add('isTransiting')
      activeTimeline = true
      let requestedPosition = event.offsetX / timeline.offsetWidth
      player.currentTime = player.duration * requestedPosition
      timelineBar.style.transform = 'scaleX(' + requestedPosition + ')'
      position = player.currentTime
    })

    timeline.addEventListener('mousemove', function(event) {
      if (activeTimeline) {
        let requestedPosition = event.offsetX / timeline.offsetWidth
        player.currentTime = player.duration * requestedPosition
        timelineBar.style.transform = 'scaleX(' + requestedPosition + ')'
        position = player.currentTime
      }
    })

    window.addEventListener('mouseup', function() {
      if (activeTimeline) {
        timelineBar.classList.remove('isTransiting')
        player.play()
        playPauseButton.classList.add('isPlaying')
        activeTimeline = false
      }
    })

    // Fullscreen
    fullscreenButton.addEventListener('click', function(e) {
      if (!fullscreenMode) {
        if (playerContainer.requestFullscreen) {
          playerContainer.requestFullscreen()
        } else if (playerContainer.mozRequestFullScreen) {
          playerContainer.mozRequestFullScreen()
        } else if (playerContainer.webkitRequestFullscreen) {
          playerContainer.webkitRequestFullscreen()
        }
        fullscreenMode = true
      } else {
        if (document.cancelFullScreen) {
          document.cancelFullScreen()
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen()
        } else if (document.webkitCancelFullScreen) {
          document.webkitCancelFullScreen()
        }
        fullscreenMode = false
      }
    })

    // Fullscreen on double click
    player.addEventListener('dblclick', function(e) {
      if (!fullscreenMode) {
        if (playerContainer.requestFullscreen) {
          playerContainer.requestFullscreen()
        } else if (playerContainer.mozRequestFullScreen) {
          playerContainer.mozRequestFullScreen()
        } else if (playerContainer.webkitRequestFullscreen) {
          playerContainer.webkitRequestFullscreen()
        }
        fullscreenMode = true
      } else {
        if (document.cancelFullScreen) {
          document.cancelFullScreen()
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen()
        } else if (document.webkitCancelFullScreen) {
          document.webkitCancelFullScreen()
        }
        fullscreenMode = false
      }
    })

    let isFullscreen = false

    const fullscreenEvents = ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "msfullscreenchange"]
    fullscreenEvents.forEach(function(event) {
      document.addEventListener(event, function() {
        isFullscreen = !isFullscreen
        if (!isFullscreen) {
          fullscreenMode = false
        }
      })
    })

    // Display ControlBar correctly
    player.addEventListener('mousemove', function() {
      controlBar.classList.add('controlBarIsActive')
      body.classList.remove('cursorIsRemoved')
      clearTimeout(timer)
      timer = setTimeout(mouseStopped, 2000)
    })

    player.addEventListener('mouseleave', function() {
      controlBar.classList.remove('controlBarIsActive')
      clearTimeout(timer)
    })

    controlBar.addEventListener('mouseover', function() {
      clearTimeout(timer)
      controlBar.classList.add('controlBarIsActive')
      body.classList.remove('cursorIsRemoved')
    })

    function mouseStopped() {
      controlBar.classList.remove('controlBarIsActive')
      body.classList.add('cursorIsRemoved')
    }

    // Volume using scroll

    // let lastScrollTop = window.pageYOffset;

    // player.addEventListener('scroll', function(event) {
    //   var st = window.pageYOffset || document.documentElement.scrollTop;
    //   if (st > lastScrollTop){
    //     console.log(down);
    //       // downscroll code
    //   } else {
    //     console.log(up);
    //      // upscroll code
    //   }

    //   lastScrollTop = st;
    // })
  }
}

// Creating new Player
let customPlayer = new Player({
  basePath: './src/videos/',
  parent: '.container',
  id: '1',
  className: 'customPlayer',
  link: 'video.mp4',
  width: 800,
  height: 'auto',
  controls: true,
  autoplay: true,
  loop: false
})<commit_msg>Fix animation<commit_after>// Beautiful player using POO

// Work In Progress

class Player {
  constructor(params = {
    basePath,
    parent,
    id,
    className,
    link,
    width,
    height,
    controls,
    autoplay,
    loop
  }) {

    this.parent = document.querySelector(params.parent)
    this.link = params.basePath + params.link
    this.width = params.width
    this.height = params.height
    this.autoplay = params.autoplay
    this.loop = params.loop
    this.id = params.id
    this.className = params.className

    this.playerContainer = document.createElement('div')

    this.playerContainer.className = 'playerContainer'
    this.playerContainer.tabIndex = 0
    this.playerContainer.style.width = this.width + 'px'
    this.playerContainer.style.height = 'auto'
    this.parent.appendChild(this.playerContainer)

    this.player = document.createElement('video')

    this.player.src = this.link
    this.player.id = this.id
    this.player.className = this.className
    this.player.autoplay = this.autoplay
    this.player.controls = false
    this.player.loop = this.loop

    this.playerContainer.appendChild(this.player)

    // Check if controls are required
    if (params.controls) {
      this.createControllers()
    } else {
      console.log('To enable controllers turn controls paramaters to true')
    }
  }

  createControllers() {

    let body = document.querySelector('body')

    let player = this.player
    let playerContainer = this.playerContainer
    let parent = this.parent

    // Create control bar
    let controlBar = document.createElement('div')
    controlBar.className = 'controlBar'
    playerContainer.appendChild(controlBar)

    let timer = null

    // Create play/pause button
    let playPauseButton = document.createElement('div')
    playPauseButton.className = 'playPauseButton'
    controlBar.appendChild(playPauseButton)

    // Create volumeContainer
    let volumeContainer = document.createElement('div')
    volumeContainer.className = 'volumeContainer'
    controlBar.appendChild(volumeContainer)

    let volumeTimer = null

    // Create Mute Button
    let muteButton = document.createElement('div')
    muteButton.className = 'muteButton'
    volumeContainer.appendChild(muteButton)

    // Create Volume Slider
    let volumeController = document.createElement('div')
    volumeController.className = 'volumeController'
    volumeContainer.appendChild(volumeController)

    // Create Volume Bar inside Volume Slider
    let volumeBar = document.createElement('div')
    volumeBar.className = 'volumeBar'
    volumeController.appendChild(volumeBar)

    let activeVolume = false

    // Create currentTimer
    let currentTimer = '0:00'

    // Create durationTimer
    let durationTimer = '0:00'

    // Create timerComplete
    let timerComplete = document.createElement('div')
    timerComplete.className = 'timerComplete'
    timerComplete.innerHTML = currentTimer + '\xa0 /\xa0' + durationTimer

    controlBar.appendChild(timerComplete)

    // Create Timeline
    let timeline = document.createElement('div')
    timeline.className = 'timeline'
    controlBar.appendChild(timeline)
    let activeTimeline = false

    // Create vars for lerp
    let position = 0
    let amount = 0.1

    // Create TimelineLoadingBar
    let timelineLoadingBar = document.createElement('div')
    timelineLoadingBar.className = 'timelineLoadingBar'
    timeline.appendChild(timelineLoadingBar)

    // Create Timeline Bar
    let timelineBar = document.createElement('div')
    timelineBar.className = 'timelineBar'
    timeline.appendChild(timelineBar)

    // Create fullscreenButton
    let fullscreenButton = document.createElement('div')
    fullscreenButton.className = 'fullscreenButton'
    controlBar.appendChild(fullscreenButton)

    let fullscreenMode = false

    // Check at start

    // Check Video Status to display the right button
    if (player.autoplay) {
      playPauseButton.classList.add('isPlaying')
    } else {
      playPauseButton.classList.remove('isPlaying')
    }

    // Set volume at start
    player.volume = 0
    volumeBar.style.transform = 'scaleX(' + player.volume + ')'

    // Events

    // PlayPause Events
    let playPauseEvents = [player, playPauseButton]
    playPauseEvents.forEach(function(e) {
      e.addEventListener('click', function() {
        if (player.paused) {
          player.play()
          playPauseButton.classList.add('isPlaying')
        } else {
          player.pause()
          playPauseButton.classList.remove('isPlaying')
        }
      })
    })

    parent.addEventListener('keydown', function(e) {
      if (e.keyCode === 32) {
        if (player.paused) {
          player.play()
          playPauseButton.classList.add('isPlaying')
        } else {
          player.pause()
          playPauseButton.classList.remove('isPlaying')
        }
      }
    })

    // Volume Events
    volumeContainer.addEventListener('mouseover', function() {
      clearTimeout(volumeTimer)
      volumeController.classList.add('volumeControllerHovered')
    })

    volumeContainer.addEventListener('mouseleave', function() {
      volumeTimer = setTimeout(mouseOut, 2000)
    })

    function mouseOut() {
      volumeController.classList.remove('volumeControllerHovered')
      clearTimeout(volumeTimer)
    }

    muteButton.addEventListener('click', function() {
      let volumeSaved = 0
      if (!muteButton.classList.contains('isMuted')) {
        this.volumeSaved = player.volume
        player.volume = 0
        muteButton.classList.toggle('isMuted')
      } else {
        player.volume = this.volumeSaved
        muteButton.classList.toggle('isMuted')
      }
    })

    volumeController.addEventListener('mousedown', function(event) {
      activeVolume = true
      muteButton.classList.remove('isMuted')
      if (0.1 <= event.offsetX / volumeController.offsetWidth && event.offsetX / volumeController.offsetWidth <= 0.9) {
        player.volume = event.offsetX / volumeController.offsetWidth
      } else if (event.offsetX / volumeController.offsetWidth < 0.1) {
        player.volume = 0
        muteButton.classList.add('isMuted')
      } else {
        player.volume = 1
      }
      volumeBar.style.transform = 'scaleX(' + player.volume + ')'
    })

    volumeController.addEventListener('mousemove', function(event) {
      if (activeVolume) {
        muteButton.classList.remove('isMuted')
        if (0.1 <= event.offsetX / volumeController.offsetWidth && event.offsetX / volumeController.offsetWidth <= 0.9) {
          player.volume = event.offsetX / volumeController.offsetWidth
        } else if (event.offsetX / volumeController.offsetWidth < 0.1) {
          player.volume = 0
          muteButton.classList.add('isMuted')
        } else {
          player.volume = 1
        }
        volumeBar.style.transform = 'scaleX(' + player.volume + ')'
      }
    })

    window.addEventListener('mouseup', function() {
      activeVolume = false
    })

    // Timeline Events
    setInterval(function barProgression() {
      // Display bar progression
      let timelineBarProgression = player.currentTime / player.duration

      if (position === 0) {
        timelineBar.style.transform = 'scaleX(' + timelineBarProgression + ')'
        position += player.currentTime
      } else {
        position += (player.currentTime - position) * amount
        timelineBarProgression = position / player.duration
        timelineBar.style.transform = 'scaleX(' + timelineBarProgression + ')'
      }

      // Display on timeline what is already loaded
      timelineLoadingBar.style.transform = 'scaleX(' + player.buffered.end(0) / player.duration + ')'

    }, 16)

    player.addEventListener('timeupdate', function() {

      // Display time formated
      let durationTimeFormated = new Date(null)
      durationTimeFormated.setSeconds(Math.floor(player.duration))
      let ds = durationTimeFormated.getSeconds()
      let dm = durationTimeFormated.getMinutes()
      let dh = durationTimeFormated.getUTCHours()

      let currentTimeFormated = new Date(null)
      currentTimeFormated.setSeconds(Math.floor(player.currentTime))

      let cs = currentTimeFormated.getSeconds()
      let cm = currentTimeFormated.getMinutes()
      let ch = currentTimeFormated.getUTCHours()

      // Checks for currentTime display
      if (cs >= 0 && cs < 10)  {
        cs = '0' + cs
      }
      if (ch > 0 && (cm === 0 && cm < 10)) {
        cm = '0' + cm
      }

      // Checks for durationTime display
      if (ds >= 0 && ds < 10) {
        ds = '0' + ds
      }
      if (dh > 0 && (dm === 0 && dm < 10)) {
        dm = '0' + cm
      }

      //
      if (dh === 0 || ch === 0) {
        currentTimer = cm + ':' + cs
        durationTimer = dm + ':' + ds
      } else {
        currentTimer = ch + ':' + cm + ':' + cs
        durationTimer = dh + ':' + dm + ':' + ds
      }

      timerComplete.innerHTML = currentTimer + '\xa0/\xa0' + durationTimer

    })

    timeline.addEventListener('mousedown', function(event) {
      player.pause()
      timelineBar.classList.add('isTransiting')
      activeTimeline = true
      let requestedPosition = event.offsetX / timeline.offsetWidth
      player.currentTime = player.duration * requestedPosition
      timelineBar.style.transform = 'scaleX(' + requestedPosition + ')'
      position = player.currentTime
    })

    timeline.addEventListener('mousemove', function(event) {
      if (activeTimeline) {
        let requestedPosition = event.offsetX / timeline.offsetWidth
        player.currentTime = player.duration * requestedPosition
        timelineBar.style.transform = 'scaleX(' + requestedPosition + ')'
        position = player.currentTime
      }
    })

    window.addEventListener('mouseup', function() {
      if (activeTimeline) {
        timelineBar.classList.remove('isTransiting')
        player.play()
        playPauseButton.classList.add('isPlaying')
        activeTimeline = false
      }
    })

    // Fullscreen
    fullscreenButton.addEventListener('click', function(e) {
      if (!fullscreenMode) {
        if (playerContainer.requestFullscreen) {
          playerContainer.requestFullscreen()
        } else if (playerContainer.mozRequestFullScreen) {
          playerContainer.mozRequestFullScreen()
        } else if (playerContainer.webkitRequestFullscreen) {
          playerContainer.webkitRequestFullscreen()
        }
        fullscreenMode = true
      } else {
        if (document.cancelFullScreen) {
          document.cancelFullScreen()
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen()
        } else if (document.webkitCancelFullScreen) {
          document.webkitCancelFullScreen()
        }
        fullscreenMode = false
      }
    })

    // Fullscreen on double click
    player.addEventListener('dblclick', function(e) {
      if (!fullscreenMode) {
        if (playerContainer.requestFullscreen) {
          playerContainer.requestFullscreen()
        } else if (playerContainer.mozRequestFullScreen) {
          playerContainer.mozRequestFullScreen()
        } else if (playerContainer.webkitRequestFullscreen) {
          playerContainer.webkitRequestFullscreen()
        }
        fullscreenMode = true
      } else {
        if (document.cancelFullScreen) {
          document.cancelFullScreen()
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen()
        } else if (document.webkitCancelFullScreen) {
          document.webkitCancelFullScreen()
        }
        fullscreenMode = false
      }
    })

    let isFullscreen = false

    const fullscreenEvents = ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "msfullscreenchange"]
    fullscreenEvents.forEach(function(event) {
      document.addEventListener(event, function() {
        isFullscreen = !isFullscreen
        if (!isFullscreen) {
          fullscreenMode = false
        }
      })
    })

    // Display ControlBar correctly
    player.addEventListener('mousemove', function() {
      controlBar.classList.add('controlBarIsActive')
      body.classList.remove('cursorIsRemoved')
      clearTimeout(timer)
      timer = setTimeout(mouseStopped, 2000)
    })

    player.addEventListener('mouseleave', function() {
      controlBar.classList.remove('controlBarIsActive')
      clearTimeout(timer)
    })

    controlBar.addEventListener('mouseover', function() {
      clearTimeout(timer)
      controlBar.classList.add('controlBarIsActive')
      body.classList.remove('cursorIsRemoved')
    })

    function mouseStopped() {
      controlBar.classList.remove('controlBarIsActive')
      body.classList.add('cursorIsRemoved')
    }

    // Volume using scroll

    // let lastScrollTop = window.pageYOffset;

    // player.addEventListener('scroll', function(event) {
    //   var st = window.pageYOffset || document.documentElement.scrollTop;
    //   if (st > lastScrollTop){
    //     console.log(down);
    //       // downscroll code
    //   } else {
    //     console.log(up);
    //      // upscroll code
    //   }

    //   lastScrollTop = st;
    // })
  }
}

// Creating new Player
let customPlayer = new Player({
  basePath: './src/videos/',
  parent: '.container',
  id: '1',
  className: 'customPlayer',
  link: 'video.mp4',
  width: 800,
  height: 'auto',
  controls: true,
  autoplay: true,
  loop: false
})<|endoftext|>