<commit_before>'use strict';

class GlobalSvg {
    DONE = 0;
    _inited = false;
    REMOVE = 1;
    RETURN = 2;
    scale = 1;

    createDom() {
        if (this.inited) {
            return;
        }

        //document attached element not removed by angular
        $('#globalSvgSurface').remove();
        $('#globalSvg').remove();

        const body = $('body');
        this._container = Entry.Dom('div', {
            classes: ['globalSvgSurface', 'entryRemove'],
            id: 'globalSvgSurface',
            parent: body,
        });

        this.svgDom = Entry.Dom(
            $(
                '<svg id="globalSvg" width="1" height="1"' +
                    'version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>'
            ),
            { parent: this._container }
        );

        this.svg = Entry.SVG('globalSvg');
        this.svgPoint = this.svg.createSVGPoint();
        this.left = 0;
        this.top = 0;
        this._inited = true;
    }

    setView(view, mode) {
        if (view == this._view) {
            return;
        }
        const data = view.block || view;
        if (data.isReadOnly() || !view.movable) {
            return;
        }
        this._view = view;
        this._mode = mode;

        const fromBlockMenu = view.dragInstance && view.dragInstance.isNew;
        const backPackMode = !fromBlockMenu && Entry.playground.backPack.isShow;
        if (mode !== Entry.Workspace.MODE_VIMBOARD && !backPackMode) {
            view.set({ visible: false });
        }

        this.draw();
        this.show();
        this.align();
        this.position();
        return true;
    }

    getView() {
        return this._view;
    }

    setComment(view, mode) {
        if (view == this._view || view.readOnly || !view.movable) {
            return;
        }

        view._path.attr({
            id: `${view.id}C`,
            stroke: 'red',
        });

        view._titlePath.attr({
            id: `${view.id}T`,
        });

        view._textPath.attr({
            href: `#${view.id}C`,
        });

        view._titleTextPath.attr({
            href: `#${view.id}T`,
        });
        this._view = view;
        this._mode = mode;
        this.originalX = view.x;
        this.originalY = view.y;
        this.draw();
        this.show();
        this.align();
        this.commentPosition();
    }

    draw() {
        const blockView = this._view;
        if (this._svg) {
            this.remove();
        }
        const isVimMode = this._mode == Entry.Workspace.MODE_VIMBOARD;
        const bBox = blockView.svgGroup.getBBox();
        const { width, height } = bBox;
        this.svgDom.attr({
            width: `${Math.round(bBox.width + 4)}px`,
            height: `${Math.round(bBox.height + 4)}px`,
        });
        this.xScaleDiff = (width * (this.scale - 1)) / (this.scale * 2);
        this.yscaleDiff = (height * (this.scale - 1)) / (this.scale * 2);

        this.svgGroup = Entry.SVG.createElement(blockView.svgGroup.cloneNode(true), { opacity: 1 });
        this.svg.appendChild(this.svgGroup);
        if (blockView.svgCommentGroup) {
            this.svgCommentGroup = Entry.SVG.createElement(
                blockView.svgCommentGroup.cloneNode(true),
                {
                    opacity: 1,
                }
            );
            this.svg.appendChild(this.svgCommentGroup);
        }
        //TODO selectAll function replace
        if (isVimMode) {
            const $svg = $(this.svgGroup);
            const $svgComment = $(this.svgCommentGroup);

            $svg.find('g').css({ filter: 'none' });
            $svgComment.find('g').css({ filter: 'none' });

            $svg.find('path, rect, polygon').velocity(
                {
                    opacity: 0,
                },
                {
                    duration: 500,
                }
            );
            $svgComment.find('path, rect, polygon').velocity(
                {
                    opacity: 0,
                },
                {
                    duration: 500,
                }
            );

            $svg.find('text').velocity(
                {
                    fill: '#000000',
                },
                {
                    duration: 530,
                }
            );
            $svgComment.find('text').velocity(
                {
                    fill: '#000000',
                },
                {
                    duration: 530,
                }
            );
        }
    }

    remove() {
        if (!this.svgGroup) {
            return;
        }
        this.svgGroup.remove();
        delete this.svgGroup;
        if (this.svgCommentGroup) {
            this.svgCommentGroup.remove();
            delete this.svgCommentGroup;
        }
        delete this._view;
        delete this._offsetX;
        delete this._offsetY;
        delete this._startX;
        delete this._startY;
        this.hide();
    }

    align() {
        let offsetX = 0;
        let offsetY = 0;
        if (this._view.getSkeleton) {
            offsetX = this._view.getSkeleton().box(this._view).offsetX || 0;
            offsetY = this._view.getSkeleton().box(this._view).offsetY || 0;
        }
        offsetX *= -1;
        offsetX += 1;
        offsetY *= -1;
        offsetY += 1;
        this._offsetX = offsetX;
        this._offsetY = offsetY;
        const transform = `translate(${offsetX}, ${offsetY})`;
        this.svgGroup.attr({ transform });
        if (this.svgCommentGroup) {
            this.svgCommentGroup.attr({ transform });
        }
    }

    show() {
        this.isShow = true;
        this._container.removeClass('entryRemove');
    }

    hide() {
        this.isShow = false;
        this._container.addClass('entryRemove');
    }

    position(value) {
        const blockView = this._view;
        if (!blockView) {
            return;
        }
        const pos = blockView.getAbsoluteCoordinate();
        const offset = blockView.getBoard().offset();
        if (value) {
            this.left += value.left;
            this.top += value.top;
        } else {
            this.left = pos.scaleX + (offset.left / this.scale - this._offsetX);
            this.top = pos.scaleY + (offset.top / this.scale - this._offsetY);
        }
        this._applyDomPos(this.left, this.top);
    }

    commentPosition({ startX = 0, startY = 0 } = {}) {
        const view = this._view;
        if (!view) {
            return;
        }
        const pos = view.getAbsoluteCoordinate();
        const offset = view.board.offset();
        this.left = pos.scaleX + (offset.left / this.scale - this._offsetX) - this.originalX;
        this.top = pos.scaleY + (offset.top / this.scale - this._offsetY) - this.originalY;
        const [line] = this.svgGroup.getElementsByTagName('line');
        if (line) {
            line.setAttribute('x1', startX / this.scale - this.left + view.parentWidth);
            line.setAttribute('y1', startY / this.scale - this.top + view.parentHeight / 2);
        }
        this._applyDomPos(this.left, this.top);
    }

    adjust(adjustX, adjustY) {
        const left = this.left + (adjustX || 0);
        const top = this.top + (adjustY || 0);
        if (left === this.left && top === this.top) {
            return;
        }

        this.left = left;
        this.top = top;
        this._applyDomPos(this.left, this.top);
    }

    _applyDomPos(left, top) {
        this.svgDom.css({
            transform: `scale(${this.scale}) translate3d(${left + this.xScaleDiff}px,${top +
                this.yscaleDiff}px, 0px)`,
        });
    }

    terminateDrag(blockView) {
        const mousePos = Entry.mouseCoordinate;
        const board = blockView.getBoard();
        const blockMenu = board.workspace.blockMenu;
        const bLeft = blockMenu.offset().left;
        const bTop = blockMenu.offset().top;
        const bWidth = blockMenu.visible ? blockMenu.blockMenuWrapper.width() : 0;
        if (mousePos.y > board.offset().top - 20 && mousePos.x > bLeft + bWidth) {
            return this.DONE;
        } else if (mousePos.y > bTop && mousePos.x > bLeft && blockMenu.visible) {
            if (blockView.block && !blockView.block.isDeletable()) {
                return this.RETURN;
            } else {
                return this.REMOVE;
            }
        } else {
            return this.RETURN;
        }
    }

    addControl(...args) {
        this.onMouseDown(...args);
    }

    onMouseDown(e) {
        this._startY = e.pageY;
        const that = this;
        e.stopPropagation();
        e.preventDefault();
        const $doc = $(document);
        $doc.bind('mousemove.block', onMouseMove);
        $doc.bind('mouseup.block', onMouseUp);
        $doc.bind('touchmove.block', onMouseMove);
        $doc.bind('touchend.block', onMouseUp);
        this._startX = e.pageX;
        this._startY = e.pageY;

        function onMouseMove(e) {
            const newX = e.pageX;
            const newY = e.pageY;
            const dX = newX - that._startX;
            const dY = newY - that._startY;
            const newLeft = that.left + dX;
            const newTop = that.top + dY;
            that._applyDomPos(newLeft, newTop);
            that._startX = newX;
            that._startY = newY;
            that.left = newLeft;
            that.top = newTop;
        }

        function onMouseUp(e) {
            $(document).unbind('.block');
        }
    }

    setScale(scale = 1) {
        this.scale = scale;
    }

    getRelativePoint(matrix) {
        return this.svgPoint.matrixTransform(matrix);
    }
}

Entry.GlobalSvg = new GlobalSvg();
<commit_msg>백팩 모드에서 블록 배율 적용<commit_after>'use strict';

class GlobalSvg {
    DONE = 0;
    _inited = false;
    REMOVE = 1;
    RETURN = 2;
    scale = 1;

    createDom() {
        if (this.inited) {
            return;
        }

        //document attached element not removed by angular
        $('#globalSvgSurface').remove();
        $('#globalSvg').remove();

        const body = $('body');
        this._container = Entry.Dom('div', {
            classes: ['globalSvgSurface', 'entryRemove'],
            id: 'globalSvgSurface',
            parent: body,
        });

        this.svgDom = Entry.Dom(
            $(
                '<svg id="globalSvg" width="1" height="1"' +
                    'version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>'
            ),
            { parent: this._container }
        );

        this.svg = Entry.SVG('globalSvg');
        this.svgPoint = this.svg.createSVGPoint();
        this.left = 0;
        this.top = 0;
        this._inited = true;
    }

    setView(view, mode) {
        if (view == this._view) {
            return;
        }
        const data = view.block || view;
        if (data.isReadOnly() || !view.movable) {
            return;
        }
        this._view = view;
        this._mode = mode;

        const fromBlockMenu = view.dragInstance && view.dragInstance.isNew;
        const backPackMode = !fromBlockMenu && Entry.playground.backPack.isShow;
        if (mode !== Entry.Workspace.MODE_VIMBOARD && !backPackMode) {
            view.set({ visible: false });
        }

        this.draw();
        this.show();
        this.align();
        this.position();
        return true;
    }

    getView() {
        return this._view;
    }

    setComment(view, mode) {
        if (view == this._view || view.readOnly || !view.movable) {
            return;
        }

        view._path.attr({
            id: `${view.id}C`,
            stroke: 'red',
        });

        view._titlePath.attr({
            id: `${view.id}T`,
        });

        view._textPath.attr({
            href: `#${view.id}C`,
        });

        view._titleTextPath.attr({
            href: `#${view.id}T`,
        });
        this._view = view;
        this._mode = mode;
        this.originalX = view.x;
        this.originalY = view.y;
        this.draw();
        this.show();
        this.align();
        this.commentPosition();
    }

    draw() {
        const blockView = this._view;
        if (this._svg) {
            this.remove();
        }
        const isVimMode = this._mode == Entry.Workspace.MODE_VIMBOARD;
        const bBox = blockView.svgGroup.getBBox();
        const { width, height } = bBox;
        this.svgDom.attr({
            width: `${Math.round(bBox.width + 4)}px`,
            height: `${Math.round(bBox.height + 4)}px`,
        });
        this.xScaleDiff = (width * (this.scale - 1)) / (this.scale * 2);
        this.yscaleDiff = (height * (this.scale - 1)) / (this.scale * 2);

        this.svgGroup = Entry.SVG.createElement(blockView.svgGroup.cloneNode(true), { opacity: 1 });
        this.svg.appendChild(this.svgGroup);
        if (blockView.svgCommentGroup) {
            this.svgCommentGroup = Entry.SVG.createElement(
                blockView.svgCommentGroup.cloneNode(true),
                {
                    opacity: 1,
                }
            );
            this.svg.appendChild(this.svgCommentGroup);
        }
        //TODO selectAll function replace
        if (isVimMode) {
            const $svg = $(this.svgGroup);
            const $svgComment = $(this.svgCommentGroup);

            $svg.find('g').css({ filter: 'none' });
            $svgComment.find('g').css({ filter: 'none' });

            $svg.find('path, rect, polygon').velocity(
                {
                    opacity: 0,
                },
                {
                    duration: 500,
                }
            );
            $svgComment.find('path, rect, polygon').velocity(
                {
                    opacity: 0,
                },
                {
                    duration: 500,
                }
            );

            $svg.find('text').velocity(
                {
                    fill: '#000000',
                },
                {
                    duration: 530,
                }
            );
            $svgComment.find('text').velocity(
                {
                    fill: '#000000',
                },
                {
                    duration: 530,
                }
            );
        }
    }

    remove() {
        if (!this.svgGroup) {
            return;
        }
        this.svgGroup.remove();
        delete this.svgGroup;
        if (this.svgCommentGroup) {
            this.svgCommentGroup.remove();
            delete this.svgCommentGroup;
        }
        delete this._view;
        delete this._offsetX;
        delete this._offsetY;
        delete this._startX;
        delete this._startY;
        this.hide();
    }

    align() {
        let offsetX = 0;
        let offsetY = 0;
        if (this._view.getSkeleton) {
            offsetX = this._view.getSkeleton().box(this._view).offsetX || 0;
            offsetY = this._view.getSkeleton().box(this._view).offsetY || 0;
        }
        offsetX *= -1;
        offsetX += 1;
        offsetY *= -1;
        offsetY += 1;
        this._offsetX = offsetX;
        this._offsetY = offsetY;
        const transform = `translate(${offsetX}, ${offsetY})`;
        this.svgGroup.attr({ transform });
        if (this.svgCommentGroup) {
            this.svgCommentGroup.attr({ transform });
        }
    }

    show() {
        this.isShow = true;
        this._container.removeClass('entryRemove');
    }

    hide() {
        this.isShow = false;
        this._container.addClass('entryRemove');
    }

    position(value) {
        const blockView = this._view;
        if (!blockView) {
            return;
        }
        const pos = blockView.getAbsoluteCoordinate();
        const offset = blockView.getBoard().offset();
        if (value) {
            this.left += value.left / this.scale;
            this.top += value.top / this.scale;
        } else {
            this.left = pos.scaleX + (offset.left / this.scale - this._offsetX);
            this.top = pos.scaleY + (offset.top / this.scale - this._offsetY);
        }
        this._applyDomPos(this.left, this.top);
    }

    commentPosition({ startX = 0, startY = 0 } = {}) {
        const view = this._view;
        if (!view) {
            return;
        }
        const pos = view.getAbsoluteCoordinate();
        const offset = view.board.offset();
        this.left = pos.scaleX + (offset.left / this.scale - this._offsetX) - this.originalX;
        this.top = pos.scaleY + (offset.top / this.scale - this._offsetY) - this.originalY;
        const [line] = this.svgGroup.getElementsByTagName('line');
        if (line) {
            line.setAttribute('x1', startX / this.scale - this.left + view.parentWidth);
            line.setAttribute('y1', startY / this.scale - this.top + view.parentHeight / 2);
        }
        this._applyDomPos(this.left, this.top);
    }

    adjust(adjustX, adjustY) {
        const left = this.left + (adjustX || 0);
        const top = this.top + (adjustY || 0);
        if (left === this.left && top === this.top) {
            return;
        }

        this.left = left;
        this.top = top;
        this._applyDomPos(this.left, this.top);
    }

    _applyDomPos(left, top) {
        this.svgDom.css({
            transform: `scale(${this.scale}) translate3d(${left + this.xScaleDiff}px,${top +
                this.yscaleDiff}px, 0px)`,
        });
    }

    terminateDrag(blockView) {
        const mousePos = Entry.mouseCoordinate;
        const board = blockView.getBoard();
        const blockMenu = board.workspace.blockMenu;
        const bLeft = blockMenu.offset().left;
        const bTop = blockMenu.offset().top;
        const bWidth = blockMenu.visible ? blockMenu.blockMenuWrapper.width() : 0;
        if (mousePos.y > board.offset().top - 20 && mousePos.x > bLeft + bWidth) {
            return this.DONE;
        } else if (mousePos.y > bTop && mousePos.x > bLeft && blockMenu.visible) {
            if (blockView.block && !blockView.block.isDeletable()) {
                return this.RETURN;
            } else {
                return this.REMOVE;
            }
        } else {
            return this.RETURN;
        }
    }

    addControl(...args) {
        this.onMouseDown(...args);
    }

    onMouseDown(e) {
        this._startY = e.pageY;
        const that = this;
        e.stopPropagation();
        e.preventDefault();
        const $doc = $(document);
        $doc.bind('mousemove.block', onMouseMove);
        $doc.bind('mouseup.block', onMouseUp);
        $doc.bind('touchmove.block', onMouseMove);
        $doc.bind('touchend.block', onMouseUp);
        this._startX = e.pageX;
        this._startY = e.pageY;

        function onMouseMove(e) {
            const newX = e.pageX;
            const newY = e.pageY;
            const dX = newX - that._startX;
            const dY = newY - that._startY;
            const newLeft = that.left + dX;
            const newTop = that.top + dY;
            that._applyDomPos(newLeft, newTop);
            that._startX = newX;
            that._startY = newY;
            that.left = newLeft;
            that.top = newTop;
        }

        function onMouseUp(e) {
            $(document).unbind('.block');
        }
    }

    setScale(scale = 1) {
        this.scale = scale;
    }

    getRelativePoint(matrix) {
        return this.svgPoint.matrixTransform(matrix);
    }
}

Entry.GlobalSvg = new GlobalSvg();
<|endoftext|>


<commit_before>// TODO: consider putting it in a webjar instead
require(['jquery', '$xwiki.getSkinFile("uicomponents/widgets/tree.min.js", true)'], function ($) {
  'use strict';

  /**
   * Class that represent a decoded URL.
   * TODO: make it more generic and put it in some library.
   */
  var DecodedURL = function (path, params) {

    /**
     * Fields
     */
    var self    = this;
    self.path   = path;
    self.params = params;

    /**
     * Parse an URL and returns an object containing the path and the parameters decoded from the query string.
     * Hashes (#) are not supported.
     */
    self.initFromURL = function (url) {
      self.params = {};
      var queryStringLocator = url.indexOf('?');
      if (queryStringLocator == -1) {
        self.path = url;
      } else {
        self.path = url.substring(0, queryStringLocator);
        var queryString = url.substring(queryStringLocator + 1);
        // Parse the query string
        var parts = queryString.split('&');
        for (var i = 0; i < parts.length; ++i) {
          var temp  = parts[i].split('=');
          var name  = decodeURIComponent(temp[0]);
          var value = decodeURIComponent(temp[1]);
          if (self.params[name] !== undefined) {
            if (Array.isArray(self.params[name])) {
              self.params[name].push(value);
            } else {
              var oldValue = self.params[name];
              self.params[name] = [oldValue, value];
            }
          } else {
            self.params[name] = value;
          }
        }
      }
    };

    /**
     * Return a serialized version of the url
     */
    self.serialize = function () {
      var url = self.path;
      var separator = '?';
      for (var param in self.params) {
        var value = self.params[param];
        if (param != '' && value !== undefined) {
          if (Array.isArray(value)) {
            for (var i = 0; i < value.length; ++i) {
              url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value[i]);
              separator = '&';
            }
          } else if (typeof value === 'string') {
            url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value);
            separator = '&';
          }
        }
      }
      return url;
    };

    /**
     * Return a clone of the object
     */
    self.clone = function () {
      var clone = new DecodedURL();
      clone.path = self.path;
      clone.params = [];
      for (var param in self.params) {
        if (Array.isArray(self.params[param])) {
          clone.params[param] = [];
          for (var i = 0; i < self.params[param].length; ++i) {
            clone.params[param].push(self.params[param][i]);
          }
        } else if (typeof self.params[param] === 'string') {
          clone.params[param] = self.params[param];
        }
      }
      return clone;
    }
  };

  /**
   * Work with the Export Tree
   */
  require(['tree'], function () {
    $(document).ready(function () {

      /**
       * Save the default url
       */
      $('#exportModal a.btn').each(function() {
        var button = $(this);
        var url = new DecodedURL();
        url.initFromURL(button.attr('href'));
        button.data('url', url);
      });

      /**
       * Initialize the tree.
       * Note: we need to wait the 'loaded.jstree' event otherwise we have synchronization issues.
       */
      var tree = $('#exportModal .xtree');
      tree.on('loaded.jstree', function () {

        // Change the tree behaviour
        var treeReference = $.jstree.reference(tree);
        treeReference.settings.checkbox.cascade        = 'down';
        treeReference.settings.checkbox.three_state    = false ;
        treeReference.settings.contextmenu.select_node = false ;

        // Check all by default
        treeReference.check_all();

        /**
         * Modifying the context menu
         */
        treeReference.settings.contextmenu.items = function (node) {
          var items = {};

          items.select_children = {
            label: "$escapetool.javascript($services.localization.render('core.exporter.selectChildren'))",
            action: function () {
              for (var i = 0; i < node.children.length; ++i) {
                var child = node.children[i];
                treeReference.check_node(child);
              }
            },
            _disabled: !node.state.opened
          };
          items.unselect_children = {
            label: "$escapetool.javascript($services.localization.render('core.exporter.unselectChildren'))",
            action: function () {
              for (var i = 0; i < node.children.length; ++i) {
                var child = node.children[i];
                treeReference.uncheck_node(child);
              }
            },
            _disabled: !node.state.opened
          };

          return items;
        };

        /**
         * Handle 'select all' button
         */
        $('#exportModal .tree_select_all').click(function() {
          treeReference.check_all();
          return false;
        });

        /**
         * Handle 'select none' button
         */
        $('#exportModal .tree_select_none').click(function() {
          treeReference.uncheck_all();
          return false;
        });

        /**
         * Update the URL of the export buttons according to the tree
         */
         $('#exportModal #exportModelOtherCollapse a.btn').click(function(event) {
          var button = $(this);
          var url = button.data('url').clone();
          var checkedNodes = treeReference.get_checked(true);
          if (url.params === undefined) {
            url.params = {'pages': []};
          }

          url.params.pages = [];
          for (var i = 0; i < checkedNodes.length; ++i) {
            var node = checkedNodes[i];
            var pageReference = XWiki.Model.resolve(node.data.id, XWiki.EntityType.DOCUMENT);
            url.params.pages.push(XWiki.Model.serialize(pageReference));
            // In case the page coudl have children
            if (pageReference.getName() == 'WebHome') {
              // The node could be checked but not loaded
              if (node.state.loaded == false) {
                // In that case, we need to add all children using a wildcard.
                var spaceReference = new XWiki.EntityReference('%', XWiki.EntityType.DOCUMENT, pageReference.parent);
                url.params.pages.push(XWiki.Model.serialize(spaceReference));
              }
              // Special behaviour for the XAR export
              if (url.params.format == 'xar') {
                // Also add the WebPreferences
                var webPreferencesReference = new XWiki.EntityReference('WebPreferences', XWiki.EntityType.DOCUMENT,
                  pageReference.parent);
                url.params.pages.push(XWiki.Model.serialize(webPreferencesReference));
              }
            }
          }
          button.attr('href', url.serialize());
        });
      });
    });

  });
});
<commit_msg>XWIKI-12544: Allow selection of children in the Export dialog * Don't depend on uicomponents/widgets/tree.min.js because it wasn't meant for this (and it's not an actual module). Instead, depend on the require-config.min.js that brings the configuration for the 'tree' module (which we ask for a couple of lines below)<commit_after>// TODO: consider putting it in a webjar instead
require([
  'jquery',
  "$!services.webjars.url('org.xwiki.platform:xwiki-platform-tree-webjar', 'require-config.min.js', {'evaluate': true})"
], function ($) {
  'use strict';

  /**
   * Class that represent a decoded URL.
   * TODO: make it more generic and put it in some library.
   */
  var DecodedURL = function (path, params) {

    /**
     * Fields
     */
    var self    = this;
    self.path   = path;
    self.params = params;

    /**
     * Parse an URL and returns an object containing the path and the parameters decoded from the query string.
     * Hashes (#) are not supported.
     */
    self.initFromURL = function (url) {
      self.params = {};
      var queryStringLocator = url.indexOf('?');
      if (queryStringLocator == -1) {
        self.path = url;
      } else {
        self.path = url.substring(0, queryStringLocator);
        var queryString = url.substring(queryStringLocator + 1);
        // Parse the query string
        var parts = queryString.split('&');
        for (var i = 0; i < parts.length; ++i) {
          var temp  = parts[i].split('=');
          var name  = decodeURIComponent(temp[0]);
          var value = decodeURIComponent(temp[1]);
          if (self.params[name] !== undefined) {
            if (Array.isArray(self.params[name])) {
              self.params[name].push(value);
            } else {
              var oldValue = self.params[name];
              self.params[name] = [oldValue, value];
            }
          } else {
            self.params[name] = value;
          }
        }
      }
    };

    /**
     * Return a serialized version of the url
     */
    self.serialize = function () {
      var url = self.path;
      var separator = '?';
      for (var param in self.params) {
        var value = self.params[param];
        if (param != '' && value !== undefined) {
          if (Array.isArray(value)) {
            for (var i = 0; i < value.length; ++i) {
              url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value[i]);
              separator = '&';
            }
          } else if (typeof value === 'string') {
            url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value);
            separator = '&';
          }
        }
      }
      return url;
    };

    /**
     * Return a clone of the object
     */
    self.clone = function () {
      var clone = new DecodedURL();
      clone.path = self.path;
      clone.params = [];
      for (var param in self.params) {
        if (Array.isArray(self.params[param])) {
          clone.params[param] = [];
          for (var i = 0; i < self.params[param].length; ++i) {
            clone.params[param].push(self.params[param][i]);
          }
        } else if (typeof self.params[param] === 'string') {
          clone.params[param] = self.params[param];
        }
      }
      return clone;
    }
  };

  /**
   * Work with the Export Tree
   */
  require(['tree'], function () {
    $(document).ready(function () {

      /**
       * Save the default url
       */
      $('#exportModal a.btn').each(function() {
        var button = $(this);
        var url = new DecodedURL();
        url.initFromURL(button.attr('href'));
        button.data('url', url);
      });

      /**
       * Initialize the tree.
       * Note: we need to wait the 'loaded.jstree' event otherwise we have synchronization issues.
       */
      var tree = $('#exportModal .xtree');
      tree.on('loaded.jstree', function () {

        // Change the tree behaviour
        var treeReference = $.jstree.reference(tree);
        treeReference.settings.checkbox.cascade        = 'down';
        treeReference.settings.checkbox.three_state    = false ;
        treeReference.settings.contextmenu.select_node = false ;

        // Check all by default
        treeReference.check_all();

        /**
         * Modifying the context menu
         */
        treeReference.settings.contextmenu.items = function (node) {
          var items = {};

          items.select_children = {
            label: "$escapetool.javascript($services.localization.render('core.exporter.selectChildren'))",
            action: function () {
              for (var i = 0; i < node.children.length; ++i) {
                var child = node.children[i];
                treeReference.check_node(child);
              }
            },
            _disabled: !node.state.opened
          };
          items.unselect_children = {
            label: "$escapetool.javascript($services.localization.render('core.exporter.unselectChildren'))",
            action: function () {
              for (var i = 0; i < node.children.length; ++i) {
                var child = node.children[i];
                treeReference.uncheck_node(child);
              }
            },
            _disabled: !node.state.opened
          };

          return items;
        };

        /**
         * Handle 'select all' button
         */
        $('#exportModal .tree_select_all').click(function() {
          treeReference.check_all();
          return false;
        });

        /**
         * Handle 'select none' button
         */
        $('#exportModal .tree_select_none').click(function() {
          treeReference.uncheck_all();
          return false;
        });

        /**
         * Update the URL of the export buttons according to the tree
         */
         $('#exportModal #exportModelOtherCollapse a.btn').click(function(event) {
          var button = $(this);
          var url = button.data('url').clone();
          var checkedNodes = treeReference.get_checked(true);
          if (url.params === undefined) {
            url.params = {'pages': []};
          }

          url.params.pages = [];
          for (var i = 0; i < checkedNodes.length; ++i) {
            var node = checkedNodes[i];
            var pageReference = XWiki.Model.resolve(node.data.id, XWiki.EntityType.DOCUMENT);
            url.params.pages.push(XWiki.Model.serialize(pageReference));
            // In case the page coudl have children
            if (pageReference.getName() == 'WebHome') {
              // The node could be checked but not loaded
              if (node.state.loaded == false) {
                // In that case, we need to add all children using a wildcard.
                var spaceReference = new XWiki.EntityReference('%', XWiki.EntityType.DOCUMENT, pageReference.parent);
                url.params.pages.push(XWiki.Model.serialize(spaceReference));
              }
              // Special behaviour for the XAR export
              if (url.params.format == 'xar') {
                // Also add the WebPreferences
                var webPreferencesReference = new XWiki.EntityReference('WebPreferences', XWiki.EntityType.DOCUMENT,
                  pageReference.parent);
                url.params.pages.push(XWiki.Model.serialize(webPreferencesReference));
              }
            }
          }
          button.attr('href', url.serialize());
        });
      });
    });

  });
});
<|endoftext|>


<commit_before>// TODO: consider putting it in a webjar instead
require(['jquery', '$xwiki.getSkinFile('uicomponents/widgets/tree.min.js', true)'], function ($) {
  'use strict';

  /**
   * Class that represent a decoded URL.
   * TODO: make it more generic and put it in some library.
   */
  var DecodedURL = function (path, params) {

    /**
     * Fields
     */
    var self    = this;
    self.path   = path;
    self.params = params;

    /**
     * Parse an URL and returns an object containing the path and the parameters decoded from the query string.
     * Hashes (#) are not supported.
     */
    self.initFromURL = function (url) {
      self.params = {};
      var queryStringLocator = url.indexOf('?');
      if (queryStringLocator == -1) {
        self.path = url;
      } else {
        self.path = url.substring(0, queryStringLocator);
        var queryString = url.substring(queryStringLocator + 1);
        // Parse the query string
        var parts = queryString.split('&');
        for (var i = 0; i < parts.length; ++i) {
          var temp  = parts[i].split('=');
          var name  = decodeURIComponent(temp[0]);
          var value = decodeURIComponent(temp[1]);
          if (self.params[name] !== undefined) {
            if (Array.isArray(self.params[name])) {
              self.params[name].push(value);
            } else {
              var oldValue = self.params[name];
              self.params[name] = [oldValue, value];
            }
          } else {
            self.params[name] = value;
          }
        }
      }
    };

    /**
     * Return a serialized version of the url
     */
    self.serialize = function () {
      var url = self.path;
      var separator = '?';
      for (var param in self.params) {
        var value = self.params[param];
        if (param != '' && value !== undefined) {
          if (Array.isArray(value)) {
            for (var i = 0; i < value.length; ++i) {
              url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value[i]);
              separator = '&';
            }
          } else if (typeof value === 'string') {
            url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value);
            separator = '&';
          }
        }
      }
      return url;
    };

    /**
     * Return a clone of the object
     */
    self.clone = function () {
      var clone = new DecodedURL();
      clone.path = self.path;
      clone.params = [];
      for (var param in self.params) {
        if (Array.isArray(self.params[param])) {
          clone.params[param] = [];
          for (var i = 0; i < self.params[param].length; ++i) {
            clone.params[param].push(self.params[param][i]);
          }
        } else if (typeof self.params[param] === 'string') {
          clone.params[param] = self.params[param];
        }
      }
      return clone;
    }
  };

  /**
   * Work with the Export Tree
   */
  require(['tree'], function () {
    $(document).ready(function () {

      /**
       * Save the default url
       */
      $('#exportModal a.btn').each(function() {
        var button = $(this);
        var url = new DecodedURL();
        url.initFromURL(button.attr('href'));
        button.data('url', url);
      });

      /**
       * Change the tree behaviour
       */
      var tree          = $('#exportModal .xtree');
      var treeReference = $.jstree.reference(tree);
      treeReference.settings.checkbox.cascade     = 'down';
      treeReference.settings.checkbox.three_state = false ;
      treeReference.settings.contextmenu.select_node = false;

      /**
       * Check all on loading
       */
      tree.on('loaded.jstree', function () {
        treeReference.check_all();
      });

      /**
       * Modifying the context menu
       */
      treeReference.settings.contextmenu.items = function (node) {
        var items = {};

        items.select_children = {
          label: '$escapetool.javascript($services.localization.render('core.exporter.selectChildren'))',
          action: function () {
            for (var i = 0; i < node.children.length; ++i) {
              var child = node.children[i];
              treeReference.check_node(child);
            }
          },
          _disabled: !node.state.opened
        };
        items.unselect_children = {
          label: '$escapetool.javascript($services.localization.render('core.exporter.unselectChildren'))',
          action: function () {
            for (var i = 0; i < node.children.length; ++i) {
              var child = node.children[i];
              treeReference.uncheck_node(child);
            }
          },
          _disabled: !node.state.opened
        };

        return items;
      };

      /**
       * Handle 'select all' button
       */
      $('#exportModal .tree_select_all').click(function() {
        treeReference.check_all();
        return false;
      });

      /**
       * Handle 'select none' button
       */
      $('#exportModal .tree_select_none').click(function() {
        treeReference.uncheck_all();
        return false;
      });

      /**
       * Update the URL of the export buttons according to the tree
       */
       $('#exportModal #exportModelOtherCollapse a.btn').click(function(event) {
        var button = $(this);
        var url = button.data('url').clone();
        var checkedNodes = treeReference.get_checked(true);
        if (url.params === undefined) {
          url.params = {'pages': []};
        }

        url.params.pages = [];
        for (var i = 0; i < checkedNodes.length; ++i) {
          var node = checkedNodes[i];
          var pageReference = XWiki.Model.resolve(node.data.id, XWiki.EntityType.DOCUMENT);
          url.params.pages.push(XWiki.Model.serialize(pageReference));
          // In case the page coudl have children
          if (pageReference.getName() == 'WebHome') {
            // The node could be checked but not loaded
            if (node.state.loaded == false) {
              // In that case, we need to add all children using a wildcard.
              var spaceReference = new XWiki.EntityReference('%', XWiki.EntityType.DOCUMENT, pageReference.parent);
              url.params.pages.push(XWiki.Model.serialize(spaceReference));
            }
            // Special behaviour for the XAR export
            if (url.params.format == 'xar') {
              // Also add the WebPreferences
              var webPreferencesReference = new XWiki.EntityReference('WebPreferences', XWiki.EntityType.DOCUMENT,
                pageReference.parent);
              url.params.pages.push(XWiki.Model.serialize(webPreferencesReference));
            }
          }
        }
        button.attr('href', url.serialize());
      });
    });

  });
});
<commit_msg>XWIKI-12544: Allow selection of children in the Export dialog.<commit_after>// TODO: consider putting it in a webjar instead
require(['jquery', '$xwiki.getSkinFile("uicomponents/widgets/tree.min.js", true)'], function ($) {
  'use strict';

  /**
   * Class that represent a decoded URL.
   * TODO: make it more generic and put it in some library.
   */
  var DecodedURL = function (path, params) {

    /**
     * Fields
     */
    var self    = this;
    self.path   = path;
    self.params = params;

    /**
     * Parse an URL and returns an object containing the path and the parameters decoded from the query string.
     * Hashes (#) are not supported.
     */
    self.initFromURL = function (url) {
      self.params = {};
      var queryStringLocator = url.indexOf('?');
      if (queryStringLocator == -1) {
        self.path = url;
      } else {
        self.path = url.substring(0, queryStringLocator);
        var queryString = url.substring(queryStringLocator + 1);
        // Parse the query string
        var parts = queryString.split('&');
        for (var i = 0; i < parts.length; ++i) {
          var temp  = parts[i].split('=');
          var name  = decodeURIComponent(temp[0]);
          var value = decodeURIComponent(temp[1]);
          if (self.params[name] !== undefined) {
            if (Array.isArray(self.params[name])) {
              self.params[name].push(value);
            } else {
              var oldValue = self.params[name];
              self.params[name] = [oldValue, value];
            }
          } else {
            self.params[name] = value;
          }
        }
      }
    };

    /**
     * Return a serialized version of the url
     */
    self.serialize = function () {
      var url = self.path;
      var separator = '?';
      for (var param in self.params) {
        var value = self.params[param];
        if (param != '' && value !== undefined) {
          if (Array.isArray(value)) {
            for (var i = 0; i < value.length; ++i) {
              url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value[i]);
              separator = '&';
            }
          } else if (typeof value === 'string') {
            url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value);
            separator = '&';
          }
        }
      }
      return url;
    };

    /**
     * Return a clone of the object
     */
    self.clone = function () {
      var clone = new DecodedURL();
      clone.path = self.path;
      clone.params = [];
      for (var param in self.params) {
        if (Array.isArray(self.params[param])) {
          clone.params[param] = [];
          for (var i = 0; i < self.params[param].length; ++i) {
            clone.params[param].push(self.params[param][i]);
          }
        } else if (typeof self.params[param] === 'string') {
          clone.params[param] = self.params[param];
        }
      }
      return clone;
    }
  };

  /**
   * Work with the Export Tree
   */
  require(['tree'], function () {
    $(document).ready(function () {

      /**
       * Save the default url
       */
      $('#exportModal a.btn').each(function() {
        var button = $(this);
        var url = new DecodedURL();
        url.initFromURL(button.attr('href'));
        button.data('url', url);
      });

      /**
       * Change the tree behaviour
       */
      var tree          = $('#exportModal .xtree');
      var treeReference = $.jstree.reference(tree);
      treeReference.settings.checkbox.cascade     = 'down';
      treeReference.settings.checkbox.three_state = false ;
      treeReference.settings.contextmenu.select_node = false;

      /**
       * Check all on loading
       */
      tree.on('loaded.jstree', function () {
        treeReference.check_all();
      });

      /**
       * Modifying the context menu
       */
      treeReference.settings.contextmenu.items = function (node) {
        var items = {};

        items.select_children = {
          label: "$escapetool.javascript($services.localization.render('core.exporter.selectChildren'))",
          action: function () {
            for (var i = 0; i < node.children.length; ++i) {
              var child = node.children[i];
              treeReference.check_node(child);
            }
          },
          _disabled: !node.state.opened
        };
        items.unselect_children = {
          label: "$escapetool.javascript($services.localization.render('core.exporter.unselectChildren'))",
          action: function () {
            for (var i = 0; i < node.children.length; ++i) {
              var child = node.children[i];
              treeReference.uncheck_node(child);
            }
          },
          _disabled: !node.state.opened
        };

        return items;
      };

      /**
       * Handle 'select all' button
       */
      $('#exportModal .tree_select_all').click(function() {
        treeReference.check_all();
        return false;
      });

      /**
       * Handle 'select none' button
       */
      $('#exportModal .tree_select_none').click(function() {
        treeReference.uncheck_all();
        return false;
      });

      /**
       * Update the URL of the export buttons according to the tree
       */
       $('#exportModal #exportModelOtherCollapse a.btn').click(function(event) {
        var button = $(this);
        var url = button.data('url').clone();
        var checkedNodes = treeReference.get_checked(true);
        if (url.params === undefined) {
          url.params = {'pages': []};
        }

        url.params.pages = [];
        for (var i = 0; i < checkedNodes.length; ++i) {
          var node = checkedNodes[i];
          var pageReference = XWiki.Model.resolve(node.data.id, XWiki.EntityType.DOCUMENT);
          url.params.pages.push(XWiki.Model.serialize(pageReference));
          // In case the page coudl have children
          if (pageReference.getName() == 'WebHome') {
            // The node could be checked but not loaded
            if (node.state.loaded == false) {
              // In that case, we need to add all children using a wildcard.
              var spaceReference = new XWiki.EntityReference('%', XWiki.EntityType.DOCUMENT, pageReference.parent);
              url.params.pages.push(XWiki.Model.serialize(spaceReference));
            }
            // Special behaviour for the XAR export
            if (url.params.format == 'xar') {
              // Also add the WebPreferences
              var webPreferencesReference = new XWiki.EntityReference('WebPreferences', XWiki.EntityType.DOCUMENT,
                pageReference.parent);
              url.params.pages.push(XWiki.Model.serialize(webPreferencesReference));
            }
          }
        }
        button.attr('href', url.serialize());
      });
    });

  });
});
<|endoftext|>


<commit_before>import * as R from 'ramda';
import merge from 'ramda/es/merge';

function findMaxLength(array) {
    let maxLength = 0;
    R.forEach(row => { if (row instanceof Array && row.length > maxLength) {
        maxLength = row.length;
    }}, array);
    return maxLength;
}

function getGroupedColumnIndices(column, columns, headerRowIndex, mergeDuplicateHeaders) {
    const columnIndex = columns.findIndex(col => col.id === column.id);

    if (!column.name || (Array.isArray(column.name) && column.name.length < headerRowIndex) || !mergeDuplicateHeaders) {
        return { groupIndexFirst: columnIndex, groupIndexLast: columnIndex };
    }
    let lastColumnIndex = columnIndex;

    for (let i = columnIndex; i < columns.length; ++i) {
        const c = columns[i];

        if (c.name && Array.isArray(c.name) && c.name.length > headerRowIndex && c.name[headerRowIndex] === column.name[headerRowIndex]) {
            lastColumnIndex = i;
        } else {
            break;
        }
    }

    return { groupIndexFirst: columnIndex, groupIndexLast: lastColumnIndex };
}

export function deleteColumn(column, columns, headerRowIndex, data) {
    const {groupIndexFirst, groupIndexLast} = getGroupedColumnIndices(
        column, columns, headerRowIndex
    );
    const rejectedColumnIds = R.slice(
        groupIndexFirst,
        groupIndexLast + 1,
        R.pluck('id', columns)
    );
    return {
        columns: R.remove(
            groupIndexFirst,
            1 + groupIndexLast - groupIndexFirst,
            columns
        ),
        data: R.map(R.omit(rejectedColumnIds), data),
        // NOTE - We're just clearing these so that there aren't any
        // inconsistencies. In an ideal world, we would probably only
        // update them if they contained one of the columns that we're
        // trying to delete
        ...clearSelection
    };
}

export const clearSelection = {
    active_cell: undefined,
    start_cell: undefined,
    end_cell: undefined,
    selected_cells: []
};

export function changeColumnHeader(column, columns, headerRowIndex, mergeDuplicateHeaders, newColumnName){
    let cloneColumn = Object.assign({}, column);
    if ((typeof column.name === 'string') || (column.name instanceof String)) {
            const columnHeaders = columns.map(col => col.name);
            const maxLength = findMaxLength(columnHeaders);
            const newColumnNames = Array(maxLength).fill(column.name);
            cloneColumn = Object.assign({}, column, {name: newColumnNames});
    }
    const transformedColumn = columns.map(col => {
        if (((typeof column.name === 'string') || (column.name instanceof String)) && col.id === column.id) {
            return cloneColumn;
        } else {
            return col;
        }
    })
    const { groupIndexFirst, groupIndexLast } = getGroupedColumnIndices(
        column, columns, headerRowIndex, mergeDuplicateHeaders
    );
    let newColumns = R.clone(transformedColumn);
    R.range(groupIndexFirst, groupIndexLast + 1).map(i => {
        let namePath;
        if (R.type(transformedColumn[i].name) === 'Array') {
            namePath = [i, 'name', headerRowIndex];
        }
        newColumns = R.set(R.lensPath(namePath), newColumnName, newColumns);
    });
    return { columns: newColumns} ;
}

export function editColumnName(column, columns, headerRowIndex, mergeDuplicateHeaders) {
    const newColumnName = window.prompt('Enter a new column name');
    return changeColumnHeader(column, columns, headerRowIndex, mergeDuplicateHeaders, newColumnName);
}
<commit_msg>linting<commit_after>import * as R from 'ramda';
import merge from 'ramda/es/merge';

function findMaxLength(array) {
    let maxLength = 0;
    R.forEach(row => { if (row instanceof Array && row.length > maxLength) {
        maxLength = row.length;
    }}, array);
    return maxLength;
}

function getGroupedColumnIndices(column, columns, headerRowIndex, mergeDuplicateHeaders) {
    const columnIndex = columns.findIndex(col => col.id === column.id);

    if (!column.name || (Array.isArray(column.name) && column.name.length < headerRowIndex) || !mergeDuplicateHeaders) {
        return { groupIndexFirst: columnIndex, groupIndexLast: columnIndex };
    }
    let lastColumnIndex = columnIndex;

    for (let i = columnIndex; i < columns.length; ++i) {
        const c = columns[i];

        if (c.name && Array.isArray(c.name) && c.name.length > headerRowIndex && c.name[headerRowIndex] === column.name[headerRowIndex]) {
            lastColumnIndex = i;
        } else {
            break;
        }
    }

    return { groupIndexFirst: columnIndex, groupIndexLast: lastColumnIndex };
}

export function deleteColumn(column, columns, headerRowIndex, data) {
    const {groupIndexFirst, groupIndexLast} = getGroupedColumnIndices(
        column, columns, headerRowIndex
    );
    const rejectedColumnIds = R.slice(
        groupIndexFirst,
        groupIndexLast + 1,
        R.pluck('id', columns)
    );
    return {
        columns: R.remove(
            groupIndexFirst,
            1 + groupIndexLast - groupIndexFirst,
            columns
        ),
        data: R.map(R.omit(rejectedColumnIds), data),
        // NOTE - We're just clearing these so that there aren't any
        // inconsistencies. In an ideal world, we would probably only
        // update them if they contained one of the columns that we're
        // trying to delete
        ...clearSelection
    };
}

export const clearSelection = {
    active_cell: undefined,
    start_cell: undefined,
    end_cell: undefined,
    selected_cells: []
};

export function changeColumnHeader(column, columns, headerRowIndex, mergeDuplicateHeaders, newColumnName) {
    let cloneColumn = Object.assign({}, column);
    if ((typeof column.name === 'string') || (column.name instanceof String)) {
            const columnHeaders = columns.map(col => col.name);
            const maxLength = findMaxLength(columnHeaders);
            const newColumnNames = Array(maxLength).fill(column.name);
            cloneColumn = Object.assign({}, column, {name: newColumnNames});
    }
    const transformedColumn = columns.map(col => {
        if (((typeof column.name === 'string') || (column.name instanceof String)) && col.id === column.id) {
            return cloneColumn;
        } else {
            return col;
        }
    });
    const { groupIndexFirst, groupIndexLast } = getGroupedColumnIndices(
        column, columns, headerRowIndex, mergeDuplicateHeaders
    );
    let newColumns = R.clone(transformedColumn);
    R.range(groupIndexFirst, groupIndexLast + 1).map(i => {
        let namePath;
        if (R.type(transformedColumn[i].name) === 'Array') {
            namePath = [i, 'name', headerRowIndex];
        }
        newColumns = R.set(R.lensPath(namePath), newColumnName, newColumns);
    });
    return { columns: newColumns} ;
}

export function editColumnName(column, columns, headerRowIndex, mergeDuplicateHeaders) {
    const newColumnName = window.prompt('Enter a new column name');
    return changeColumnHeader(column, columns, headerRowIndex, mergeDuplicateHeaders, newColumnName);
}
<|endoftext|>


<commit_before>goog.provide('ol.format.WFSDescribeFeatureType');

goog.require('ol');
goog.require('ol.format.XML');
goog.require('ol.xml');

/**
 * @classdesc
 * Format for reading WFS DescribeFeatureType data.
 *
 * @constructor
 * @extends {ol.format.XML}
 * @api
 */
ol.format.WFSDescribeFeatureType = function() {

  ol.format.XML.call(this);

};
ol.inherits(ol.format.WFSDescribeFeatureType, ol.format.XML);

/**
 * Read a WFS DescribeFeatureType document.
 *
 * @function
 * @param {Document|Node|string} source The XML source.
 * @return {Object} An object representing the WFS DescribeFeatureType.
 * @api
 */
ol.format.WFSDescribeFeatureType.prototype.read;

/**
 * @inheritDoc
 */
ol.format.WFSDescribeFeatureType.prototype.readFromDocument = function(doc) {
  for (let n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readFromNode(n);
    }
  }
  return null;
};

/**
 * @inheritDoc
 */
ol.format.WFSDescribeFeatureType.prototype.readFromNode = function(node) {
  let result = {};
  result = ol.xml.pushParseAndPop(
    result,
    ol.format.WFSDescribeFeatureType.PARSERS_,
    node,
    []
  );
  return result;
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object} Attributes.
 */
ol.format.WFSDescribeFeatureType.readElement_ = function(node, objectStack) {
  const attributes = {};
  for (let i = 0, len = node.attributes.length; i < len; i++) {
    const attribute = node.attributes.item(i);
    attributes[attribute.name] = attribute.value;
  }
  if (objectStack.length === 1) {
    // remove namespace from type
    attributes['type'] = attributes['type'].split(':').pop();
  }
  return attributes;
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readComplexType_ = function(node, objectStack) {
  const name = node.getAttribute('name');
  const object = ol.xml.pushParseAndPop(
    {'name': name},
    ol.format.WFSDescribeFeatureType.COMPLEX_TYPE_PARSERS_,
    node, objectStack
  );
  // flatten
  object['complexContent'] =
    object['complexContent']['extension']['sequence']['element'];
  return object;
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readComplexContent_ = function(
  node, objectStack
) {
  return ol.xml.pushParseAndPop(
    {},
    ol.format.WFSDescribeFeatureType.COMPLEX_CONTENT_PARSERS_,
    node,
    objectStack
  );
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readExtension_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop(
    {},
    ol.format.WFSDescribeFeatureType.EXTENSION_PARSERS_,
    node,
    objectStack
  );
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readSequence_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop(
    {},
    ol.format.WFSDescribeFeatureType.SEQUENCE_PARSERS_,
    node,
    objectStack
  );
};

/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_ = [
  null,
  'http://www.w3.org/2001/XMLSchema'
];

/**
 * @const
 * @type {!Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'element': ol.xml.makeObjectPropertyPusher(
        ol.format.WFSDescribeFeatureType.readElement_
      ),
      'complexType': ol.xml.makeObjectPropertyPusher(
        ol.format.WFSDescribeFeatureType.readComplexType_
      )
    });

/**
 * @const
 * @type {!Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.COMPLEX_TYPE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'complexContent': ol.xml.makeObjectPropertySetter(
        ol.format.WFSDescribeFeatureType.readComplexContent_
      )
    });

/**
 * @const
 * @type {!Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.COMPLEX_CONTENT_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'extension': ol.xml.makeObjectPropertySetter(
        ol.format.WFSDescribeFeatureType.readExtension_
      )
    });

/**
 * @const
 * @type {!Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.EXTENSION_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'sequence': ol.xml.makeObjectPropertySetter(
        ol.format.WFSDescribeFeatureType.readSequence_
      )
    });

/**
 * @const
 * @type {!Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.SEQUENCE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'element': ol.xml.makeObjectPropertyPusher(
        ol.format.WFSDescribeFeatureType.readElement_
      )
    });
<commit_msg>Fix WFSDescribeFeatureType parsers type<commit_after>goog.provide('ol.format.WFSDescribeFeatureType');

goog.require('ol');
goog.require('ol.format.XML');
goog.require('ol.xml');

/**
 * @classdesc
 * Format for reading WFS DescribeFeatureType data.
 *
 * @constructor
 * @extends {ol.format.XML}
 * @api
 */
ol.format.WFSDescribeFeatureType = function() {

  ol.format.XML.call(this);

};
ol.inherits(ol.format.WFSDescribeFeatureType, ol.format.XML);

/**
 * Read a WFS DescribeFeatureType document.
 *
 * @function
 * @param {Document|Node|string} source The XML source.
 * @return {Object} An object representing the WFS DescribeFeatureType.
 * @api
 */
ol.format.WFSDescribeFeatureType.prototype.read;

/**
 * @inheritDoc
 */
ol.format.WFSDescribeFeatureType.prototype.readFromDocument = function(doc) {
  for (let n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readFromNode(n);
    }
  }
  return null;
};

/**
 * @inheritDoc
 */
ol.format.WFSDescribeFeatureType.prototype.readFromNode = function(node) {
  let result = {};
  result = ol.xml.pushParseAndPop(
    result,
    ol.format.WFSDescribeFeatureType.PARSERS_,
    node,
    []
  );
  return result;
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object} Attributes.
 */
ol.format.WFSDescribeFeatureType.readElement_ = function(node, objectStack) {
  const attributes = {};
  for (let i = 0, len = node.attributes.length; i < len; i++) {
    const attribute = node.attributes.item(i);
    attributes[attribute.name] = attribute.value;
  }
  if (objectStack.length === 1) {
    // remove namespace from type
    attributes['type'] = attributes['type'].split(':').pop();
  }
  return attributes;
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readComplexType_ = function(node, objectStack) {
  const name = node.getAttribute('name');
  const object = ol.xml.pushParseAndPop(
    {'name': name},
    ol.format.WFSDescribeFeatureType.COMPLEX_TYPE_PARSERS_,
    node, objectStack
  );
  // flatten
  object['complexContent'] =
    object['complexContent']['extension']['sequence']['element'];
  return object;
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readComplexContent_ = function(
  node, objectStack
) {
  return ol.xml.pushParseAndPop(
    {},
    ol.format.WFSDescribeFeatureType.COMPLEX_CONTENT_PARSERS_,
    node,
    objectStack
  );
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readExtension_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop(
    {},
    ol.format.WFSDescribeFeatureType.EXTENSION_PARSERS_,
    node,
    objectStack
  );
};

/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {T} Object.
 * @template T
 */
ol.format.WFSDescribeFeatureType.readSequence_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop(
    {},
    ol.format.WFSDescribeFeatureType.SEQUENCE_PARSERS_,
    node,
    objectStack
  );
};

/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_ = [
  null,
  'http://www.w3.org/2001/XMLSchema'
];

/**
 * @const
 * @type {Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'element': ol.xml.makeObjectPropertyPusher(
        ol.format.WFSDescribeFeatureType.readElement_
      ),
      'complexType': ol.xml.makeObjectPropertyPusher(
        ol.format.WFSDescribeFeatureType.readComplexType_
      )
    });

/**
 * @const
 * @type {Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.COMPLEX_TYPE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'complexContent': ol.xml.makeObjectPropertySetter(
        ol.format.WFSDescribeFeatureType.readComplexContent_
      )
    });

/**
 * @const
 * @type {Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.COMPLEX_CONTENT_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'extension': ol.xml.makeObjectPropertySetter(
        ol.format.WFSDescribeFeatureType.readExtension_
      )
    });

/**
 * @const
 * @type {Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.EXTENSION_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'sequence': ol.xml.makeObjectPropertySetter(
        ol.format.WFSDescribeFeatureType.readSequence_
      )
    });

/**
 * @const
 * @type {Object.<string, !Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFSDescribeFeatureType.SEQUENCE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WFSDescribeFeatureType.NAMESPACE_URIS_, {
      'element': ol.xml.makeObjectPropertyPusher(
        ol.format.WFSDescribeFeatureType.readElement_
      )
    });
<|endoftext|>


<commit_before>var config = require('./config');
var viewport = require('../client/viewport');
var messages = require('../client/messages');
var random = require('./random');

// Stubs out a fish, giving it a random id, position, and movement parameters
var Fish = function(options) {
  this.options = options;

  this.id = Math.floor(random.between(0, 100000));

  // the x and y position
  this.x = random.between(0, config.WORLD_MAX_X);
  this.y = random.between(0, config.WORLD_MAX_Y);

  // the direction the fish is moving, could be left or right
  this.vx = config.FISH_RESTING_SPEED * random.between(0.9, 1.1);
  if(random.maybe(0.5)) {
    this.vx *= -1;
  }

  // make the fish drift a little upwards
  this.vy = random.between(-3.0, 3.0);

  // the size of the fish
  this.scale = Math.floor(Math.random() * (5 - 2) + 2 ) / 5;

  // the speed at which the fish drifts normally
  this.individualRestingSpeed = Math.abs(this.vx);

  // a flag to keep track of the speed a fish should have after it makes a turn around
  this.speedAfterTurn = 0;

  // the direction the fish is turning. Used during the process of a random turn
  this.turnDirection = 0;

  // The depth at which the fish generally likes to hang out
  this.preferredDepth = this.y;

  // whether or not it's in "feeding mode"
  this.feeding = false;

  // whether or not it's in "startled mode"
  this.startled = false;

  // the previous transition state
  this.transitioned = false;

  // whether or not it's transitioning to a new viewport
  this.transitioning = false;
};

// The list of fields that we want to share between the server and the client
Fish.serializableFields = [
  'id',
  'x',
  'y',
  'vx',
  'vy',
  'scale',
  'individualRestingSpeed',
  'speedAfterTurn',
  'turnDirection',
  'preferredDepth',
  'feeding',
  'startled'
];

Fish.prototype.update = function() {
  var dt = 1 / 60;

  this.isTransitioning();

  // basic motion
  this.x += this.vx * dt;
  this.y += this.vy * dt;

  if (this.transitioning) {
    // Do not drift or turn the fish if it is transitioning to a new screen
    this.vy = 0;
  } else {
    // Movement quirks
    //this.doTurn();
    //this.doMiniStartle();

    // Every once and a while turn drift in a different direction vertically
    if(random.maybe(0.01)) {
      this.vy = random.between(-3.0, 3.0);
    }

    // However, if we've gone too far vertically make the fish move back towards it's preferred depth
    if(Math.abs(this.preferredDepth - this.y) > 40 && !this.feeding) {
      this.vy = Math.sign(this.preferredDepth - this.y) * random.between(10, 15);
    }
  }

  this.checkCollision();

};

// Calculate if the current fish is ready to transition to another client screen
Fish.prototype.isTransitioning = function() {
  var xrange = this.x % config.WINDOW_DEFAULT_WIDTH;
  var xdir = Math.sign(this.vx);

  this.transitioned = this.transitioning;
  this.transitioning = ((xrange >= 97 && xdir === 1) || (xrange <=3 && xdir === -1)) && viewport.containsFish(this);
  if (!this.transitioned && this.transitioning) {
    var newFishScreenIndex = null;
    if(xdir === -1) {
      newFishScreenIndex = (viewport.screenIndex() + config.VIEWPORT_COUNT - 1) % config.VIEWPORT_COUNT;
    } else {
      newFishScreenIndex = (viewport.screenIndex() + 1) % config.VIEWPORT_COUNT;
    }

    console.log('Pushing', this.id, 'from', viewport.screenIndex(), 'to', newFishScreenIndex);
    console.log('  x=', this.x, 'y=', this.y, 'vx=', this.vx);

    // Update position through a pub-sub event
    messages.publish(newFishScreenIndex, {
      type: 'clientPosition',
      fish: this.serialize()
    });
  }
};

// Handles startling logic. Every so often, the fish will get scared and move faster to get away!
Fish.prototype.doMiniStartle = function() {
  // If we're not already startled, we might just become startled
  if(!this.startled && random.maybe(0.001)) {
    this.startled = true;
    this.vx = config.FISH_STARTLE_SPEED * random.between(0.9, 1.1);
  }

  // if we're still above our resting speed, slowly reduce it until we're back at our normal speed
  if(Math.abs(this.vx) > this.individualRestingSpeed) {
    this.vx *= 0.99;
  }

  // If we lowered our speed enough, switch out of started mode and get ready to be startled again!
  if(Math.abs(this.vx) < this.individualRestingSpeed) {
    this.startled = false;
    this.vx = Math.sign(this.vx) * this.individualRestingSpeed;
  }
};

// handles random turn logic
Fish.prototype.doTurn = function() {
  // If we're not currently turning, we might just start turning. In this case we always turn to the opposite
  // direction that we're currently going
  if(this.turnDirection === 0 && random.maybe(0.001)) {
    this.turnDirection = -Math.sign(this.vx);
    this.speedAfterTurn = -this.vx;
  }

  // if we're going left, lower vx into the negatives. If right, raise vx to the positives
  if(this.turnDirection === -1 && this.vx > this.speedAfterTurn) {
    this.vx -= 1;
  } else if(this.turnDirection === 1 && this.vx < this.speedAfterTurn) {
    this.vx += 1;
  }

  // if we've reached our target speed after the turn, get out of turn mode
  if(this.turnDirection === -1 && this.vx <= this.speedAfterTurn) {
    this.turnDirection = 0;
  } else if(this.turnDirection === 1 && this.vx >= this.speedAfterTurn) {
    this.turnDirection = 0;
  }
};

Fish.prototype.checkCollision = function() {
  // Wall collision
  if(this.x < -5) {
    this.x = config.WORLD_MAX_X + 4;
  } else if(this.x > config.WORLD_MAX_X + 5) {
    this.x = -4;
  }

  if(this.y < 0) {
    this.y = 0;
    this.vy = Math.abs(this.vy);
  } else if(this.y > config.WORLD_MAX_Y) {
    this.y = config.WORLD_MAX_Y;
    this.vy = -Math.abs(this.vy);
  }
};

// Given an array of feed points, finds the closest feed point and then directs the fish to it
Fish.prototype.approachFeedPoints = function(feedPoints) {
  var closestIndex = -1;
  var closestXDistance = Infinity;
  for(var i = 0; i < feedPoints.length; i++) {
    var distance = Math.abs(feedPoints[i].x - this.x);
    if(distance < closestXDistance) {
      closestXDistance = distance;
      closestIndex = i;
    }
  }

  // if there was no closest fish, nothing to do
  if(closestIndex === -1) {
    this.feeding = false;
    return;
  }

  // if we're pretty close, don't attempt to turn so the fish overshoots a little, making it look more realistic
  if(this.y < config.FISH_FOOD_Y_OVERSHOOT || closestXDistance < config.FISH_FOOD_X_OVERSHOOT) {
    this.feeding = false;
    return;
  }

  // if we got this far, we found a feeding point. Figure out the angle we should use to get to the food and set the
  // velocity to go there
  this.feeding = true;
  var closestFeedPointX = feedPoints[closestIndex].x;
  var approachAngle = Math.atan2(-this.y, closestFeedPointX - this.x);
  var velocity = config.FISH_FOOD_APPROACH_SPEED;
  this.vx = velocity * Math.cos(approachAngle);
  this.vy = velocity * Math.sin(approachAngle);
};

// handy serialization method for socket communication
Fish.prototype.serialize = function() {
  var serialized = {};
  Fish.serializableFields.forEach(function(fieldName) {
    serialized[fieldName] = this[fieldName];
  }.bind(this));
  return serialized;
};

// takes raw server data and updates this fish with the data. Importantly, we ignore position/velocity stuff because
// we want dead reckoning to smooth that out for us
Fish.prototype.deserializeExtraFields = function(serverData) {
  Fish.serializableFields.forEach(function(fieldName) {
    if(fieldName == 'x' || fieldName == 'y' || fieldName == 'vx' || fieldName == 'vy') {
      return
    }

    this[fieldName] = serverData[fieldName];
  }.bind(this));
};

module.exports = Fish;
<commit_msg>Making sure that IDs are indeed shared across clients<commit_after>var config = require('./config');
var viewport = require('../client/viewport');
var messages = require('../client/messages');
var random = require('./random');

var GLOBAL_ID = 1;

// Stubs out a fish, giving it a random id, position, and movement parameters
var Fish = function(options) {
  this.options = options;

  this.id = GLOBAL_ID++;

  // the x and y position
  this.x = random.between(0, config.WORLD_MAX_X);
  this.y = random.between(0, config.WORLD_MAX_Y);

  // the direction the fish is moving, could be left or right
  this.vx = config.FISH_RESTING_SPEED * random.between(0.9, 1.1);
  if(random.maybe(0.5)) {
    this.vx *= -1;
  }

  // make the fish drift a little upwards
  this.vy = random.between(-3.0, 3.0);

  // the size of the fish
  this.scale = Math.floor(Math.random() * (5 - 2) + 2 ) / 5;

  // the speed at which the fish drifts normally
  this.individualRestingSpeed = Math.abs(this.vx);

  // a flag to keep track of the speed a fish should have after it makes a turn around
  this.speedAfterTurn = 0;

  // the direction the fish is turning. Used during the process of a random turn
  this.turnDirection = 0;

  // The depth at which the fish generally likes to hang out
  this.preferredDepth = this.y;

  // whether or not it's in "feeding mode"
  this.feeding = false;

  // whether or not it's in "startled mode"
  this.startled = false;

  // the previous transition state
  this.transitioned = false;

  // whether or not it's transitioning to a new viewport
  this.transitioning = false;
};

// The list of fields that we want to share between the server and the client
Fish.serializableFields = [
  'id',
  'x',
  'y',
  'vx',
  'vy',
  'scale',
  'individualRestingSpeed',
  'speedAfterTurn',
  'turnDirection',
  'preferredDepth',
  'feeding',
  'startled'
];

Fish.prototype.update = function() {
  var dt = 1 / 60;

  this.isTransitioning();

  // basic motion
  this.x += this.vx * dt;
  this.y += this.vy * dt;

  if (this.transitioning) {
    // Do not drift or turn the fish if it is transitioning to a new screen
    this.vy = 0;
  } else {
    // Movement quirks
    //this.doTurn();
    //this.doMiniStartle();

    // Every once and a while turn drift in a different direction vertically
    if(random.maybe(0.01)) {
      this.vy = random.between(-3.0, 3.0);
    }

    // However, if we've gone too far vertically make the fish move back towards it's preferred depth
    if(Math.abs(this.preferredDepth - this.y) > 40 && !this.feeding) {
      this.vy = Math.sign(this.preferredDepth - this.y) * random.between(10, 15);
    }
  }

  this.checkCollision();

};

// Calculate if the current fish is ready to transition to another client screen
Fish.prototype.isTransitioning = function() {
  var xrange = this.x % config.WINDOW_DEFAULT_WIDTH;
  var xdir = Math.sign(this.vx);

  this.transitioned = this.transitioning;
  this.transitioning = ((xrange >= 97 && xdir === 1) || (xrange <=3 && xdir === -1)) && viewport.containsFish(this);
  if (!this.transitioned && this.transitioning) {
    var newFishScreenIndex = null;
    if(xdir === -1) {
      newFishScreenIndex = (viewport.screenIndex() + config.VIEWPORT_COUNT - 1) % config.VIEWPORT_COUNT;
    } else {
      newFishScreenIndex = (viewport.screenIndex() + 1) % config.VIEWPORT_COUNT;
    }

    console.log('Pushing', this.id, 'from', viewport.screenIndex(), 'to', newFishScreenIndex);
    console.log('  x=', this.x, 'y=', this.y, 'vx=', this.vx);

    // Update position through a pub-sub event
    messages.publish(newFishScreenIndex, {
      type: 'clientPosition',
      fish: this.serialize()
    });
  }
};

// Handles startling logic. Every so often, the fish will get scared and move faster to get away!
Fish.prototype.doMiniStartle = function() {
  // If we're not already startled, we might just become startled
  if(!this.startled && random.maybe(0.001)) {
    this.startled = true;
    this.vx = config.FISH_STARTLE_SPEED * random.between(0.9, 1.1);
  }

  // if we're still above our resting speed, slowly reduce it until we're back at our normal speed
  if(Math.abs(this.vx) > this.individualRestingSpeed) {
    this.vx *= 0.99;
  }

  // If we lowered our speed enough, switch out of started mode and get ready to be startled again!
  if(Math.abs(this.vx) < this.individualRestingSpeed) {
    this.startled = false;
    this.vx = Math.sign(this.vx) * this.individualRestingSpeed;
  }
};

// handles random turn logic
Fish.prototype.doTurn = function() {
  // If we're not currently turning, we might just start turning. In this case we always turn to the opposite
  // direction that we're currently going
  if(this.turnDirection === 0 && random.maybe(0.001)) {
    this.turnDirection = -Math.sign(this.vx);
    this.speedAfterTurn = -this.vx;
  }

  // if we're going left, lower vx into the negatives. If right, raise vx to the positives
  if(this.turnDirection === -1 && this.vx > this.speedAfterTurn) {
    this.vx -= 1;
  } else if(this.turnDirection === 1 && this.vx < this.speedAfterTurn) {
    this.vx += 1;
  }

  // if we've reached our target speed after the turn, get out of turn mode
  if(this.turnDirection === -1 && this.vx <= this.speedAfterTurn) {
    this.turnDirection = 0;
  } else if(this.turnDirection === 1 && this.vx >= this.speedAfterTurn) {
    this.turnDirection = 0;
  }
};

Fish.prototype.checkCollision = function() {
  // Wall collision
  if(this.x < -5) {
    this.x = config.WORLD_MAX_X + 4;
  } else if(this.x > config.WORLD_MAX_X + 5) {
    this.x = -4;
  }

  if(this.y < 0) {
    this.y = 0;
    this.vy = Math.abs(this.vy);
  } else if(this.y > config.WORLD_MAX_Y) {
    this.y = config.WORLD_MAX_Y;
    this.vy = -Math.abs(this.vy);
  }
};

// Given an array of feed points, finds the closest feed point and then directs the fish to it
Fish.prototype.approachFeedPoints = function(feedPoints) {
  var closestIndex = -1;
  var closestXDistance = Infinity;
  for(var i = 0; i < feedPoints.length; i++) {
    var distance = Math.abs(feedPoints[i].x - this.x);
    if(distance < closestXDistance) {
      closestXDistance = distance;
      closestIndex = i;
    }
  }

  // if there was no closest fish, nothing to do
  if(closestIndex === -1) {
    this.feeding = false;
    return;
  }

  // if we're pretty close, don't attempt to turn so the fish overshoots a little, making it look more realistic
  if(this.y < config.FISH_FOOD_Y_OVERSHOOT || closestXDistance < config.FISH_FOOD_X_OVERSHOOT) {
    this.feeding = false;
    return;
  }

  // if we got this far, we found a feeding point. Figure out the angle we should use to get to the food and set the
  // velocity to go there
  this.feeding = true;
  var closestFeedPointX = feedPoints[closestIndex].x;
  var approachAngle = Math.atan2(-this.y, closestFeedPointX - this.x);
  var velocity = config.FISH_FOOD_APPROACH_SPEED;
  this.vx = velocity * Math.cos(approachAngle);
  this.vy = velocity * Math.sin(approachAngle);
};

// handy serialization method for socket communication
Fish.prototype.serialize = function() {
  var serialized = {};
  Fish.serializableFields.forEach(function(fieldName) {
    serialized[fieldName] = this[fieldName];
  }.bind(this));
  return serialized;
};

// takes raw server data and updates this fish with the data. Importantly, we ignore position/velocity stuff because
// we want dead reckoning to smooth that out for us
Fish.prototype.deserializeExtraFields = function(serverData) {
  Fish.serializableFields.forEach(function(fieldName) {
    if(fieldName == 'x' || fieldName == 'y' || fieldName == 'vx' || fieldName == 'vy') {
      return
    }

    this[fieldName] = serverData[fieldName];
  }.bind(this));
};

module.exports = Fish;
<|endoftext|>


<commit_before>'use strict'

import {TimerView, updateTimerView} from './timerView.js'

import {toggleSettingsMenu, getInputValue, tickingIsDesired} from '../input/settingsMenu.js'

import {updateProgressCircle} from './progressCircle.js'

import Icon from '../static/icon.png'

export let timerActive = false
let timerID = 0
const milliseconds = 1000

export const Timer = ({
startTime = new Date().getTime(),
pomodoro = getInputValue('pomodoroInput'),
shortBreak = getInputValue('shortBreakInput'),
longBreak = getInputValue('longBreakInput'),
pomodoroCount = 0,
currentActivity = 'pomodoro',
endTime = startTime + (pomodoro * milliseconds),
timeLeft = pomodoro
} = {}) => ({
  startTime,
  pomodoro,
  shortBreak,
  longBreak,
  pomodoroCount,
  currentActivity,
  endTime,
  timeLeft
})

export function beginTimer (timer = Timer(), display = TimerView()) {
  if (tickingIsDesired()) {
    let timerTick = document.getElementById('tick')
    timerTick.play()
  }

  timerActive = true
  timerID = setTimeout(checkTimer, 1000, timer, display)
  toggleSettingsMenu('hide')
  updateProgressCircle(0, 0)
  updateTimerView(display)
}

export function endTimer () {
  timerActive = false
  clearTimeout(timerID)
  toggleSettingsMenu('show')
  updateTimerView()
  updateProgressCircle(0, 0)

  let timerTick = document.getElementById('tick')
  timerTick.pause()
}

function checkTimer (timerToCheck, display) {
  let currentTime = new Date().getTime()
  timerToCheck.timeLeft--
  updateTimerView(TimerView({
    title: display.title,
    time: timerToCheck.timeLeft
  }))

  let totalTime = (timerToCheck.endTime - timerToCheck.startTime) / 1000
  updateProgressCircle(totalTime, (totalTime - timerToCheck.timeLeft))

  if (currentTime >= timerToCheck.endTime) {
    finishTimer(timerToCheck)
  } else {
    timerID = setTimeout(checkTimer, 1000, timerToCheck, display)
  }
}

function finishTimer (previousTimer) {
  let newStartTime = new Date().getTime()

  if (document.getElementById('alarm') !== null) {
    let alarm = document.getElementById('alarm')
    alarm.play()
  }
  if (previousTimer.currentActivity === 'pomodoro' && previousTimer.pomodoroCount < 3) {
    let newEndtime = newStartTime + (previousTimer.shortBreak * milliseconds)

    notify('You finished, Take a short break!', 'Short Break Started')
    beginTimer(Timer({
      pomodoro: previousTimer.pomodoro,
      shortBreak: previousTimer.shortBreak,
      longBreak: previousTimer.longBreak,
      pomodoroCount: previousTimer.pomodoroCount + 1,
      currentActivity: 'shortBreak',
      endTime: newEndtime,
      timeLeft: previousTimer.shortBreak
    }), TimerView({
      title: 'Short Break',
      time: previousTimer.shortBreak
    }))
  } else if (previousTimer.currentActivity === 'pomodoro' && previousTimer.pomodoroCount >= 3) {
    let newEndtime = newStartTime + (previousTimer.longBreak * milliseconds)

    notify('Four in a row! Take a long one, dude.', 'Long Break Started')
    beginTimer(Timer({
      pomodoro: previousTimer.pomodoro,
      shortBreak: previousTimer.shortBreak,
      longBreak: previousTimer.longBreak,
      pomodoroCount: 0,
      currentActivity: 'longBreak',
      endTime: newEndtime,
      timeLeft: previousTimer.longBreak
    }), TimerView({
      title: 'Long Break',
      time: previousTimer.longBreak
    }))
  } else {
    let newEndtime = newStartTime + (previousTimer.pomodoro * milliseconds)

    notify('Recharged a bit? Good! Pick something new and go get \'em!', 'Pomodoro Started')
    beginTimer(Timer({
      pomodoro: previousTimer.pomodoro,
      shortBreak: previousTimer.shortBreak,
      longBreak: previousTimer.longBreak,
      pomodoroCount: previousTimer.pomodoroCount,
      endTime: newEndtime,
      timeLeft: previousTimer.pomodoro
    }), TimerView({
      title: 'Pomodoro',
      time: previousTimer.pomodoro
    }))
  }
}

function notify (theBody, theTitle) {
  let options = {
    body: theBody,
    icon: Icon
  }
  let n = new Notification(theTitle, options)
  // close the notification for systems which don't close it automatically
  setTimeout(n.close.bind(n), 7000)

  n.onclick = function (event) {
    if (document.getElementById('alarm') !== null) {
      let alarm = document.getElementById('alarm')
      alarm.pause()
    }
  }
}
<commit_msg>fixed tickingIsDesired import in timer.js<commit_after>'use strict'

import {TimerView, updateTimerView} from './timerView.js'

import {toggleSettingsMenu, getInputValue} from '../input/settingsMenu.js'

import {tickingIsDesired} from '../input/tickToggle'

import {updateProgressCircle} from './progressCircle.js'

import Icon from '../static/icon.png'

export let timerActive = false
let timerID = 0
const milliseconds = 1000

export const Timer = ({
startTime = new Date().getTime(),
pomodoro = getInputValue('pomodoroInput'),
shortBreak = getInputValue('shortBreakInput'),
longBreak = getInputValue('longBreakInput'),
pomodoroCount = 0,
currentActivity = 'pomodoro',
endTime = startTime + (pomodoro * milliseconds),
timeLeft = pomodoro
} = {}) => ({
  startTime,
  pomodoro,
  shortBreak,
  longBreak,
  pomodoroCount,
  currentActivity,
  endTime,
  timeLeft
})

export function beginTimer (timer = Timer(), display = TimerView()) {
  if (tickingIsDesired()) {
    let timerTick = document.getElementById('tick')
    timerTick.play()
  }

  timerActive = true
  timerID = setTimeout(checkTimer, 1000, timer, display)
  toggleSettingsMenu('hide')
  updateProgressCircle(0, 0)
  updateTimerView(display)
}

export function endTimer () {
  timerActive = false
  clearTimeout(timerID)
  toggleSettingsMenu('show')
  updateTimerView()
  updateProgressCircle(0, 0)

  let timerTick = document.getElementById('tick')
  timerTick.pause()
}

function checkTimer (timerToCheck, display) {
  let currentTime = new Date().getTime()
  timerToCheck.timeLeft--
  updateTimerView(TimerView({
    title: display.title,
    time: timerToCheck.timeLeft
  }))

  let totalTime = (timerToCheck.endTime - timerToCheck.startTime) / 1000
  updateProgressCircle(totalTime, (totalTime - timerToCheck.timeLeft))

  if (currentTime >= timerToCheck.endTime) {
    finishTimer(timerToCheck)
  } else {
    timerID = setTimeout(checkTimer, 1000, timerToCheck, display)
  }
}

function finishTimer (previousTimer) {
  let newStartTime = new Date().getTime()

  if (document.getElementById('alarm') !== null) {
    let alarm = document.getElementById('alarm')
    alarm.play()
  }
  if (previousTimer.currentActivity === 'pomodoro' && previousTimer.pomodoroCount < 3) {
    let newEndtime = newStartTime + (previousTimer.shortBreak * milliseconds)

    notify('You finished, Take a short break!', 'Short Break Started')
    beginTimer(Timer({
      pomodoro: previousTimer.pomodoro,
      shortBreak: previousTimer.shortBreak,
      longBreak: previousTimer.longBreak,
      pomodoroCount: previousTimer.pomodoroCount + 1,
      currentActivity: 'shortBreak',
      endTime: newEndtime,
      timeLeft: previousTimer.shortBreak
    }), TimerView({
      title: 'Short Break',
      time: previousTimer.shortBreak
    }))
  } else if (previousTimer.currentActivity === 'pomodoro' && previousTimer.pomodoroCount >= 3) {
    let newEndtime = newStartTime + (previousTimer.longBreak * milliseconds)

    notify('Four in a row! Take a long one, dude.', 'Long Break Started')
    beginTimer(Timer({
      pomodoro: previousTimer.pomodoro,
      shortBreak: previousTimer.shortBreak,
      longBreak: previousTimer.longBreak,
      pomodoroCount: 0,
      currentActivity: 'longBreak',
      endTime: newEndtime,
      timeLeft: previousTimer.longBreak
    }), TimerView({
      title: 'Long Break',
      time: previousTimer.longBreak
    }))
  } else {
    let newEndtime = newStartTime + (previousTimer.pomodoro * milliseconds)

    notify('Recharged a bit? Good! Pick something new and go get \'em!', 'Pomodoro Started')
    beginTimer(Timer({
      pomodoro: previousTimer.pomodoro,
      shortBreak: previousTimer.shortBreak,
      longBreak: previousTimer.longBreak,
      pomodoroCount: previousTimer.pomodoroCount,
      endTime: newEndtime,
      timeLeft: previousTimer.pomodoro
    }), TimerView({
      title: 'Pomodoro',
      time: previousTimer.pomodoro
    }))
  }
}

function notify (theBody, theTitle) {
  let options = {
    body: theBody,
    icon: Icon
  }
  let n = new Notification(theTitle, options)
  // close the notification for systems which don't close it automatically
  setTimeout(n.close.bind(n), 7000)

  n.onclick = function (event) {
    if (document.getElementById('alarm') !== null) {
      let alarm = document.getElementById('alarm')
      alarm.pause()
    }
  }
}
<|endoftext|>


<commit_before>var getRandomPerspective,
    getPerspective,
    postSubmission;
function WriteCtrl($scope, $routeParams, $http, $interval, localStorageService) {
  $scope.perspective;
  $scope.submission = {
    'text' : undefined
  };

  function renderPerspective(response) {
    $scope.perspective = response.data;
    console.log($scope.perspective);
  }

  $scope.get_random_perspective = function() {
    return $http.get("/api/perspective/random").then(renderPerspective);
  }

  $scope.get_perspective = function(id) {
    return $http.get("/api/perspective/" + id).then(renderPerspective);
  }

  $scope.post_submission = function(submission) {
    submission.perspective_id = $scope.perspective.id;
    console.log("Submission:", submission);
    $http.post("/api/submission", submission).then(function(response) {
      console.log(response);
    });
  }

  $scope.latest_round;
  $scope.get_latest_round = function() {
    return $http.get("/api/round/latest").then(function(response) {
      $scope.latest_round = response.data;
    });
  }

  $scope.elapsed_time;
  $scope.update_elapsed_time = function() {
    $scope.elapsed_time = moment($scope.latest_round.start_time).fromNow();
  }

  angular.element(document).ready(function () {
    var perspective_id = $routeParams.perspectiveId;
    if (perspective_id) {
      $scope.get_perspective(perspective_id)
        .then(loadSavedText);
    } else {
      $scope.get_latest_round().then(function(response) {
        $scope.get_perspective($scope.latest_round.perspective.id)
          .then(loadSavedText);
        $interval($scope.update_elapsed_time, 1000);
      });
    }
  });

  $scope.$watch("submission.text",
    function(newValue, oldValue) {
      if (!angular.isUndefined(newValue) && $scope.perspective) {
        var key = $scope.perspective.id;
        // console.log("Saving", newValue, "under key", key);
        localStorageService.remove(key)
        localStorageService.add(key, newValue);
      }
    },
    true
  );

  function loadSavedText() {
    var key = $scope.perspective.id;
    var savedText = localStorageService.get(key)
    if (!angular.isUndefined(savedText) && savedText != null) {
      console.log("Loaded", savedText, "with key", key);
      $scope.submission.text = savedText;
    } else {
      document.querySelectorAll("div.write div.submission")[0].focus();
    }
  }

  getRandomPerspective = $scope.get_random_perspective;
  getPerspective = $scope.get_perspective;
  postSubmission = $scope.post_submission;
}
<commit_msg>Focus after loading<commit_after>var getRandomPerspective,
    getPerspective,
    postSubmission;
function WriteCtrl($scope, $routeParams, $http, $interval, localStorageService, $timeout) {
  $scope.perspective;
  $scope.submission = {
    'text' : undefined
  };

  function renderPerspective(response) {
    $scope.perspective = response.data;
    console.log($scope.perspective);
  }

  $scope.get_random_perspective = function() {
    return $http.get("/api/perspective/random").then(renderPerspective);
  }

  $scope.get_perspective = function(id) {
    return $http.get("/api/perspective/" + id).then(renderPerspective);
  }

  $scope.post_submission = function(submission) {
    submission.perspective_id = $scope.perspective.id;
    console.log("Submission:", submission);
    $http.post("/api/submission", submission).then(function(response) {
      console.log(response);
    });
  }

  $scope.latest_round;
  $scope.get_latest_round = function() {
    return $http.get("/api/round/latest").then(function(response) {
      $scope.latest_round = response.data;
    });
  }

  $scope.elapsed_time;
  $scope.update_elapsed_time = function() {
    $scope.elapsed_time = moment($scope.latest_round.start_time).fromNow();
  }

  angular.element(document).ready(function () {
    var perspective_id = $routeParams.perspectiveId;
    if (perspective_id) {
      $scope.get_perspective(perspective_id)
        .then(loadSavedText);
    } else {
      $scope.get_latest_round().then(function(response) {
        $scope.get_perspective($scope.latest_round.perspective.id)
          .then(loadSavedText);
        $interval($scope.update_elapsed_time, 1000);
      });
    }
  });

  $scope.$watch("submission.text",
    function(newValue, oldValue) {
      if (!angular.isUndefined(newValue) && $scope.perspective) {
        var key = $scope.perspective.id;
        // console.log("Saving", newValue, "under key", key);
        localStorageService.remove(key)
        localStorageService.add(key, newValue);
      }
    },
    true
  );

  function loadSavedText() {
    var key = $scope.perspective.id;
    var savedText = localStorageService.get(key)
    if (!angular.isUndefined(savedText) && savedText != null) {
      console.log("Loaded", savedText, "with key", key);
      $scope.submission.text = savedText;
    }
    $timeout(function() {
        document.querySelectorAll("div.write div.submission")[0].focus();
      }, 500
    );
  }

  getRandomPerspective = $scope.get_random_perspective;
  getPerspective = $scope.get_perspective;
  postSubmission = $scope.post_submission;
}
<|endoftext|>