<commit_before>var Telegram = require('node-telegram-bot-api');
var fs = require('fs');
var path = require('path');
var irc = require('./irc');
var nodeStatic = require('node-static');
var mkdirp = require('mkdirp');
var crypto = require('crypto');
var nickcolor = require('./nickcolor');
var myUser = {};

// tries to read chat ids from a file
var readChatIds = function(arr) {
    console.log('\n');
    console.log('NOTE!');
    console.log('=====');

    var idMissing = false;
    try {
        var json = JSON.parse(fs.readFileSync(process.env.HOME + '/.teleirc/chat_ids'));
        for (var i = 0; i < arr.length; i++) {
            var key = arr[i].tgGroup;
            if (key in json) {
                arr[i].tgChatId = json[key];
                console.log('id found for:', key, ':', json[key]);
            } else {
                console.log('id not found:', key);
                idMissing = true;
            }
        }
    } catch (e) {
        console.log('~/.teleirc/chat_ids file not found!');
        idMissing = true;
    }

    if (idMissing) {
        console.log(
            '\nPlease add your Telegram bot to a Telegram group and have' +
            '\nsomeone send a message to that group.' +
            '\nteleirc will then automatically store your group chat_id.');
    }

    console.log('\n');
};

var writeChatIds = function(config) {
    var json = {};
    for (var i = 0; i < config.channels.length; i++) {
        if (config.channels[i].tgChatId) {
            json[config.channels[i].tgGroup] = config.channels[i].tgChatId;
        }
    }
    json = JSON.stringify(json);
    fs.writeFile(process.env.HOME + '/.teleirc/chat_ids', json, function(err) {
        if (err) {
            console.log('error while storing chat ID:');
            console.log(err);
        } else {
            console.log('successfully stored chat ID in ~/.teleirc/chat_ids');
        }
    });
};

var getName = function(user, config) {
    var name = config.nameFormat;

    if (user.username) {
        name = name.replace('%username%', user.username, 'g');
    } else {
        // if user lacks username, use fallback format string instead
        name = name.replace('%username%', config.usernameFallbackFormat, 'g');
    }

    name = name.replace('%firstName%', user.first_name || '', 'g');
    name = name.replace('%lastName%', user.last_name || '', 'g');

    // get rid of leading and trailing whitespace
    name = name.replace(/(^\s*)|(\s*$)/g, '');

    if (config.nickcolor) {
        return nickcolor(name);
    }

    return name;
};

var getIRCName = function(msg, config) {
    var ircNickMatchRE = /^<(.*)>/;
    var results = ircNickMatchRE.exec(msg.text);
    var name;
    if (!results) {
        // Fall back to telegram name (i.e. for the topic change message)
        name = getName(msg.from, config);
    } else {
        name = results[1];
    }

    return name;
};

function randomValueBase64(len) {
    return crypto.randomBytes(Math.ceil(len * 3 / 4))
        .toString('base64')
        .slice(0, len)
        .replace(/\+/g, '0')
        .replace(/\//g, '0');
}

var serveFile = function(fileId, config, tg, callback) {
    var randomString = randomValueBase64(config.mediaRandomLenght);
    mkdirp(process.env.HOME + '/.teleirc/files/' + randomString);
    tg.downloadFile(fileId, process.env.HOME + '/.teleirc/files/' +
                                   randomString).then(function(filePath) {
        callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
    });
};

module.exports = function(config, sendTo) {
    // start HTTP server for media files if configured to do so
    if (config.showMedia) {
        var fileServer = new nodeStatic.Server(process.env.HOME + '/.teleirc/files');
        mkdirp(process.env.HOME + '/.teleirc/files');

        require('http').createServer(function(req, res) {
            req.addListener('end', function() {
                fileServer.serve(req, res);
            }).resume();
        }).listen(config.httpPort);
    }

    var tg = new Telegram(config.tgToken, {polling: true});

    // Get our own Telegram user
    tg.getMe().then(function(me) {
        myUser = me;
    });
    readChatIds(config.channels);

    tg.on('message', function(msg) {
        var age = Math.floor(Date.now() / 1000) - msg.date;
        if (config.maxMsgAge && age > config.maxMsgAge) {
            return console.log('skipping ' + age + ' seconds old message! ' +
                'NOTE: change this behaviour with config.maxMsgAge, also check your system clock');
        }

        var channel = config.channels.filter(function(channel) {
            return channel.tgGroup === msg.chat.title;
        })[0];

        if (!channel) {
            return;
        }

        if (!channel.tgChatId) {
            console.log('storing chat ID: ' + msg.chat.id);
            channel.tgChatId = msg.chat.id;
            writeChatIds(config);
        }

        if (msg.text && !msg.text.indexOf('/names')) {
            var names = sendTo.ircNames(channel);
            names.sort();
            names = 'Users on ' + (channel.chanAlias || channel.ircChan) + ':\n\n' +
                names.join(', ');

            return tg.sendMessage(channel.tgChatId, names);
        }

        // skip posts containing media if it's configured off
        if ((msg.audio || msg.document || msg.photo || msg.sticker || msg.video ||
            msg.voice || msg.contact || msg.location) && !config.showMedia) {
            return;
        }
        var text;
        if (msg.reply_to_message && msg.text) {
            var replyName;
            if (msg.reply_to_message.from.username == myUser.username) {
                replyName = getIRCName(msg.reply_to_message, config);
            } else {
                replyName = getName(msg.reply_to_message.from, config);
            }
            text = msg.text.replace(/\n/g , '\n<' + getName(msg.from, config) + '>: ');
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '@' + replyName + ', ' + text);
        } else if (msg.audio) {
            serveFile(msg.audio.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Audio, ' + msg.audio.duration + 's)' + url);
            });
        } else if (msg.document) {
            serveFile(msg.document.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Document) ' + url);
            });
        } else if (msg.photo) {
            // pick the highest quality photo
            var photo = msg.photo[msg.photo.length - 1];

            serveFile(photo.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Photo, ' + photo.width + 'x' + photo.height + ') ' + url);
            });
        } else if (msg.new_chat_photo) {
            // pick the highest quality photo
            var chatPhoto = msg.new_chat_photo[msg.new_chat_photo.length - 1];

            serveFile(chatPhoto.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(New chat photo, ' + chatPhoto.width + 'x' + chatPhoto.height + ') ' + url);
            });
        } else if (msg.sticker) {
            serveFile(msg.sticker.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Sticker, ' + msg.sticker.width + 'x' + msg.sticker.height + ') ' + url);
            });
        } else if (msg.video) {
            serveFile(msg.video.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Video, ' + msg.video.duration + 's)' + url);
            });
        } else if (msg.voice) {
            serveFile(msg.voice.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Voice, ' + msg.voice.duration + 's)' + url);
            });
        } else if (msg.contact) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Contact, ' + '"' + msg.contact.first_name + ' ' +
                msg.contact.last_name + '", ' +
                msg.contact.phone_number + ')');
        } else if (msg.location) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Location, ' + 'lon: ' + msg.location.longitude +
                              ', lat: ' + msg.location.latitude + ')');
        } else if (msg.new_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.new_chat_participant, config) +
                ' was added by: ' + getName(msg.from, config));
        } else if (msg.left_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.left_chat_participant, config) +
                ' was removed by: ' + getName(msg.from, config));
        } else {
            text = msg.text.replace(/\n/g , '\n<' + getName(msg.from, config) + '>: ');
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' + text);
        }
    });

    sendTo.tg = function(channel, msg) {
        console.log('  >> relaying to TG: ' + msg);

        if (!channel.tgChatId) {
            var err = 'ERROR: No chat_id set! Add me to a Telegram group ' +
                      'and say hi so I can find your group\'s chat_id!';
            sendTo.irc(channel.ircChan, err);
            console.error(err);
            return;
        }

        tg.sendMessage(channel.tgChatId, msg);
    };
};
<commit_msg>Add caption to photo and video<commit_after>var Telegram = require('node-telegram-bot-api');
var fs = require('fs');
var path = require('path');
var irc = require('./irc');
var nodeStatic = require('node-static');
var mkdirp = require('mkdirp');
var crypto = require('crypto');
var nickcolor = require('./nickcolor');
var myUser = {};

// tries to read chat ids from a file
var readChatIds = function(arr) {
    console.log('\n');
    console.log('NOTE!');
    console.log('=====');

    var idMissing = false;
    try {
        var json = JSON.parse(fs.readFileSync(process.env.HOME + '/.teleirc/chat_ids'));
        for (var i = 0; i < arr.length; i++) {
            var key = arr[i].tgGroup;
            if (key in json) {
                arr[i].tgChatId = json[key];
                console.log('id found for:', key, ':', json[key]);
            } else {
                console.log('id not found:', key);
                idMissing = true;
            }
        }
    } catch (e) {
        console.log('~/.teleirc/chat_ids file not found!');
        idMissing = true;
    }

    if (idMissing) {
        console.log(
            '\nPlease add your Telegram bot to a Telegram group and have' +
            '\nsomeone send a message to that group.' +
            '\nteleirc will then automatically store your group chat_id.');
    }

    console.log('\n');
};

var writeChatIds = function(config) {
    var json = {};
    for (var i = 0; i < config.channels.length; i++) {
        if (config.channels[i].tgChatId) {
            json[config.channels[i].tgGroup] = config.channels[i].tgChatId;
        }
    }
    json = JSON.stringify(json);
    fs.writeFile(process.env.HOME + '/.teleirc/chat_ids', json, function(err) {
        if (err) {
            console.log('error while storing chat ID:');
            console.log(err);
        } else {
            console.log('successfully stored chat ID in ~/.teleirc/chat_ids');
        }
    });
};

var getName = function(user, config) {
    var name = config.nameFormat;

    if (user.username) {
        name = name.replace('%username%', user.username, 'g');
    } else {
        // if user lacks username, use fallback format string instead
        name = name.replace('%username%', config.usernameFallbackFormat, 'g');
    }

    name = name.replace('%firstName%', user.first_name || '', 'g');
    name = name.replace('%lastName%', user.last_name || '', 'g');

    // get rid of leading and trailing whitespace
    name = name.replace(/(^\s*)|(\s*$)/g, '');

    if (config.nickcolor) {
        return nickcolor(name);
    }

    return name;
};

var getIRCName = function(msg, config) {
    var ircNickMatchRE = /^<(.*)>/;
    var results = ircNickMatchRE.exec(msg.text);
    var name;
    if (!results) {
        // Fall back to telegram name (i.e. for the topic change message)
        name = getName(msg.from, config);
    } else {
        name = results[1];
    }

    return name;
};

function randomValueBase64(len) {
    return crypto.randomBytes(Math.ceil(len * 3 / 4))
        .toString('base64')
        .slice(0, len)
        .replace(/\+/g, '0')
        .replace(/\//g, '0');
}

var serveFile = function(fileId, config, tg, callback) {
    var randomString = randomValueBase64(config.mediaRandomLenght);
    mkdirp(process.env.HOME + '/.teleirc/files/' + randomString);
    tg.downloadFile(fileId, process.env.HOME + '/.teleirc/files/' +
                                   randomString).then(function(filePath) {
        callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
    });
};

module.exports = function(config, sendTo) {
    // start HTTP server for media files if configured to do so
    if (config.showMedia) {
        var fileServer = new nodeStatic.Server(process.env.HOME + '/.teleirc/files');
        mkdirp(process.env.HOME + '/.teleirc/files');

        require('http').createServer(function(req, res) {
            req.addListener('end', function() {
                fileServer.serve(req, res);
            }).resume();
        }).listen(config.httpPort);
    }

    var tg = new Telegram(config.tgToken, {polling: true});

    // Get our own Telegram user
    tg.getMe().then(function(me) {
        myUser = me;
    });
    readChatIds(config.channels);

    tg.on('message', function(msg) {
        var age = Math.floor(Date.now() / 1000) - msg.date;
        if (config.maxMsgAge && age > config.maxMsgAge) {
            return console.log('skipping ' + age + ' seconds old message! ' +
                'NOTE: change this behaviour with config.maxMsgAge, also check your system clock');
        }

        var channel = config.channels.filter(function(channel) {
            return channel.tgGroup === msg.chat.title;
        })[0];

        if (!channel) {
            return;
        }

        if (!channel.tgChatId) {
            console.log('storing chat ID: ' + msg.chat.id);
            channel.tgChatId = msg.chat.id;
            writeChatIds(config);
        }

        if (msg.text && !msg.text.indexOf('/names')) {
            var names = sendTo.ircNames(channel);
            names.sort();
            names = 'Users on ' + (channel.chanAlias || channel.ircChan) + ':\n\n' +
                names.join(', ');

            return tg.sendMessage(channel.tgChatId, names);
        }

        // skip posts containing media if it's configured off
        if ((msg.audio || msg.document || msg.photo || msg.sticker || msg.video ||
            msg.voice || msg.contact || msg.location) && !config.showMedia) {
            return;
        }
        var text;
        if (msg.reply_to_message && msg.text) {
            var replyName;
            if (msg.reply_to_message.from.username == myUser.username) {
                replyName = getIRCName(msg.reply_to_message, config);
            } else {
                replyName = getName(msg.reply_to_message.from, config);
            }
            text = msg.text.replace(/\n/g , '\n<' + getName(msg.from, config) + '>: ');
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '@' + replyName + ', ' + text);
        } else if (msg.audio) {
            serveFile(msg.audio.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Audio, ' + msg.audio.duration + 's)' + url);
            });
        } else if (msg.document) {
            serveFile(msg.document.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Document) ' + url);
            });
        } else if (msg.photo) {
            // pick the highest quality photo
            var photo = msg.photo[msg.photo.length - 1];

            serveFile(photo.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Photo, ' + photo.width + 'x' + photo.height + ') ' +
                    url + (msg.caption ? ' ' + msg.caption : ''));
            });
        } else if (msg.new_chat_photo) {
            // pick the highest quality photo
            var chatPhoto = msg.new_chat_photo[msg.new_chat_photo.length - 1];

            serveFile(chatPhoto.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(New chat photo, ' + chatPhoto.width + 'x' + chatPhoto.height + ') ' + url);
            });
        } else if (msg.sticker) {
            serveFile(msg.sticker.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Sticker, ' + msg.sticker.width + 'x' + msg.sticker.height + ') ' + url);
            });
        } else if (msg.video) {
            serveFile(msg.video.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Video, ' + msg.video.duration + 's)' +
                    url + (msg.caption ? ' ' + msg.caption : ''));
            });
        } else if (msg.voice) {
            serveFile(msg.voice.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                    '(Voice, ' + msg.voice.duration + 's)' + url);
            });
        } else if (msg.contact) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Contact, ' + '"' + msg.contact.first_name + ' ' +
                msg.contact.last_name + '", ' +
                msg.contact.phone_number + ')');
        } else if (msg.location) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' +
                '(Location, ' + 'lon: ' + msg.location.longitude +
                              ', lat: ' + msg.location.latitude + ')');
        } else if (msg.new_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.new_chat_participant, config) +
                ' was added by: ' + getName(msg.from, config));
        } else if (msg.left_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.left_chat_participant, config) +
                ' was removed by: ' + getName(msg.from, config));
        } else {
            text = msg.text.replace(/\n/g , '\n<' + getName(msg.from, config) + '>: ');
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '>: ' + text);
        }
    });

    sendTo.tg = function(channel, msg) {
        console.log('  >> relaying to TG: ' + msg);

        if (!channel.tgChatId) {
            var err = 'ERROR: No chat_id set! Add me to a Telegram group ' +
                      'and say hi so I can find your group\'s chat_id!';
            sendTo.irc(channel.ircChan, err);
            console.error(err);
            return;
        }

        tg.sendMessage(channel.tgChatId, msg);
    };
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { Map, ScaleControl } from 'react-leaflet';
import { connect } from 'react-redux';

import Navbar from 'react-bootstrap/lib/Navbar';
import Row from 'react-bootstrap/lib/Row';
import Nav from 'react-bootstrap/lib/Nav';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';

import NavbarHeader from 'fm3/components/NavbarHeader';
import Layers from 'fm3/components/Layers';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import ProgressIndicator from 'fm3/components/ProgressIndicator';
import Toasts from 'fm3/components/Toasts';

import SearchMenu from 'fm3/components/SearchMenu';
import SearchResults from 'fm3/components/SearchResults';

import ObjectsMenu from 'fm3/components/ObjectsMenu';
import ObjectsResult from 'fm3/components/ObjectsResult';

import MeasurementMenu from 'fm3/components/MeasurementMenu';
import DistanceMeasurementResult from 'fm3/components/DistanceMeasurementResult';
import AreaMeasurementResult from 'fm3/components/AreaMeasurementResult';
import ElevationMeasurementResult from 'fm3/components/ElevationMeasurementResult';
import LocationResult from 'fm3/components/LocationResult';

import RoutePlannerMenu from 'fm3/components/RoutePlannerMenu';
import RoutePlannerResult from 'fm3/components/RoutePlannerResult';

import TrackViewerMenu from 'fm3/components/TrackViewerMenu';
import TrackViewerResult from 'fm3/components/TrackViewerResult';

import SelectHomeLocationMenu from 'fm3/components/SelectHomeLocationMenu';

import GalleryMenu from 'fm3/components/GalleryMenu';
import GalleryResult from 'fm3/components/GalleryResult';
import GalleryPicker from 'fm3/components/GalleryPicker';

import Settings from 'fm3/components/Settings';
import ExternalApps from 'fm3/components/ExternalApps';
import AsyncElevationChart from 'fm3/components/AsyncElevationChart';

import InfoPointMenu from 'fm3/components/InfoPointMenu';
import InfoPoint from 'fm3/components/InfoPoint';

import ChangesetsMenu from 'fm3/components/ChangesetsMenu';
import Changesets from 'fm3/components/Changesets';

import MapDetailsMenu from 'fm3/components/MapDetailsMenu';
import MapDetails from 'fm3/components/MapDetails';

import ShareMapModal from 'fm3/components/ShareMapModal';
import EmbedMapModal from 'fm3/components/EmbedMapModal';

import * as FmPropTypes from 'fm3/propTypes';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';

import { mapRefocus } from 'fm3/actions/mapActions';
import { setTool, setActiveModal, setLocation, exportGpx, clearMap, toggleLocate } from 'fm3/actions/mainActions';
import { authLogin, authStartLogout, authCheckLogin } from 'fm3/actions/authActions';

import { setMapLeafletElement } from 'fm3/leafletElementHolder';

import 'fm3/styles/main.scss';
import 'leaflet/dist/leaflet.css';

class Main extends React.Component {
  static propTypes = {
    lat: PropTypes.number.isRequired,
    lon: PropTypes.number.isRequired,
    zoom: PropTypes.number.isRequired,
    tool: FmPropTypes.tool,
    mapType: FmPropTypes.mapType.isRequired,
    onToolSet: PropTypes.func.isRequired,
    onMapRefocus: PropTypes.func.isRequired,
    activeModal: PropTypes.string,
    onModalLaunch: PropTypes.func.isRequired,
    progress: PropTypes.bool.isRequired,
    onLocationSet: PropTypes.func.isRequired,
    mouseCursor: PropTypes.string.isRequired,
    embeddedMode: PropTypes.bool.isRequired,
    onLogin: PropTypes.func.isRequired,
    onLogout: PropTypes.func.isRequired,
    onCheckLogin: PropTypes.func.isRequired,
    user: PropTypes.shape({
      name: PropTypes.string.isRequired,
    }),
    ignoreEscape: PropTypes.bool.isRequired,
    showElevationChart: PropTypes.bool.isRequired,
    showGalleryPicker: PropTypes.bool.isRequired,
    onGpxExport: PropTypes.func.isRequired,
    onMapClear: PropTypes.func.isRequired,
    onLocate: PropTypes.func.isRequired,
    locate: PropTypes.bool.isRequired,
  };

  componentWillMount() {
    this.props.onCheckLogin();
  }

  componentDidMount() {
    setMapLeafletElement(this.map.leafletElement);
    document.addEventListener('keydown', (event) => {
      if (event.keyCode === 27 /* escape key */ && !this.props.ignoreEscape) {
        this.props.onToolSet(null);
      }
    });

    if (this.props.embeddedMode) {
      document.body.classList.add('embedded');
    }
  }

  componentWillUnmount() {
    setMapLeafletElement(null);
  }

  handleMapMoveEnd = () => {
    // TODO analyze why this can be null
    if (!this.map) {
      return;
    }

    const map = this.map.leafletElement;
    const { lat, lng: lon } = map.getCenter();
    const zoom = map.getZoom();

    if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== zoom) {
      this.props.onMapRefocus({ lat, lon, zoom });
    }
  }

  handleLocationFound = (e) => {
    this.props.onLocationSet(e.latitude, e.longitude, e.accuracy);
  }

  handleToolSelect(tool) {
    this.props.onToolSet(this.props.tool === tool ? null : tool);
  }

  createToolMenu() {
    return [
      createMenuItem(10, 'share', 'Exportovať do GPX', this.props.onGpxExport),
      createMenuItem(11, 'eraser', 'Vyčistiť mapu', this.props.onMapClear),
      <MenuItem divider key="_1" />,
      createMenuItem(2, 'map-signs', 'Plánovač', () => this.handleToolSelect('route-planner')),
      createMenuItem(1, 'map-marker', 'Miesta', () => this.handleToolSelect('objects')),
      createMenuItem(4, 'dot-circle-o', 'Kde som?', this.props.onLocate, { active: this.props.locate }),
      createMenuItem(8, 'picture-o', 'Fotografie', () => this.handleToolSelect('gallery')),
      createMenuItem(3, 'arrows-h', 'Meranie', () => this.handleToolSelect('measure-dist')),
      createMenuItem(5, 'road', 'Prehliadač trás', () => this.handleToolSelect('track-viewer')),
      createMenuItem(6, 'thumb-tack', 'Bod v mape', () => this.handleToolSelect('info-point')),
      createMenuItem(7, 'pencil', 'Zmeny v mape', () => this.handleToolSelect('changesets')),
      createMenuItem(9, 'info', 'Detaily v mape', () => this.handleToolSelect('map-details')),
    ];
  }

  createMoreMenu() {
    const { user, onLogout, onLogin, onModalLaunch } = this.props;

    return [
      user ?
        createMenuItem('login', 'sign-out', `Odhlás ${user.name}`, () => onLogout())
        :
        createMenuItem('login', 'sign-in', 'Prihlásenie', () => onLogin()),
      createMenuItem(1, 'cog', 'Nastavenia', () => onModalLaunch('settings')),
      <MenuItem divider key="_1" />,
      createMenuItem(6, 'mobile', 'Exporty mapy', 'http://wiki.freemap.sk/FileDownload'),
      createMenuItem(8, 'share-alt', 'Zdieľať mapu', () => onModalLaunch('share')),
      createMenuItem(9, 'code', 'Vložiť do webstránky', () => onModalLaunch('embed')),
      <MenuItem divider key="_2" />,
      createMenuItem(7, 'book', 'Pre začiatočníkov', 'http://wiki.freemap.sk/StarterGuide'),
      createMenuItem(4, 'github', 'Projekt na GitHub-e', 'https://github.com/FreemapSlovakia/freemap-v3-react'),
      <MenuItem divider key="_3" />,
      createMenuItem(2, 'exclamation-triangle', 'Nahlás chybu zobrazenia v mape', 'http://wiki.freemap.sk/NahlasenieChyby'),
      createMenuItem(3, 'exclamation-triangle', 'Nahlás chybu v portáli', 'https://github.com/FreemapSlovakia/freemap-v3-react/issues'),
    ];
  }

  openFreemapInNonEmbedMode = () => {
    const currentURL = window.location.href;
    window.open(currentURL.replace('&embed=true', ''), '_blank');
  }

  render() {
    // eslint-disable-next-line
    const { tool, activeModal, progress, mouseCursor, embeddedMode, lat, lon, zoom, mapType, showElevationChart, showGalleryPicker } = this.props;
    const showDefaultMenu = [null, 'location'].includes(tool);

    return (

 this.handleToolSelect('track-viewer')}>
        {embeddedMode && <button id="freemap-logo" className="embedded" onClick={this.openFreemapInNonEmbedMode} />}
        <Toasts />
        {!embeddedMode &&
          <Row>
            <Navbar fluid>
              <NavbarHeader />
              <Navbar.Collapse>
                {tool === 'objects' && <ObjectsMenu />}
                {(showDefaultMenu || tool === 'search') && <SearchMenu />}
                {tool === 'route-planner' && <RoutePlannerMenu />}
                {(tool === 'measure-dist' || tool === 'measure-ele' || tool === 'measure-area') && <MeasurementMenu />}
                {tool === 'track-viewer' && <TrackViewerMenu />}
                {tool === 'info-point' && <InfoPointMenu />}
                {tool === 'changesets' && <ChangesetsMenu />}
                {tool === 'gallery' && <GalleryMenu />}
                {tool === 'select-home-location' && <SelectHomeLocationMenu />}
                {tool === 'map-details' && <MapDetailsMenu />}
                {activeModal === 'settings' && <Settings />}
                {activeModal === 'share' && <ShareMapModal />}
                {activeModal === 'embed' && <EmbedMapModal />}
                {showDefaultMenu &&

                    <NavDropdown title={<FontAwesomeIcon icon="briefcase" /> Nástroje} id="tools">
                      {this.createToolMenu()}
                    </NavDropdown>
                    <ExternalApps lat={lat} lon={lon} zoom={zoom} mapType={mapType} />
                    {showDefaultMenu &&
                      <NavDropdown title={<FontAwesomeIcon icon="ellipsis-v" /> Viac} id="additional-menu-items">
                        {this.createMoreMenu()}
                      </NavDropdown>
                    }

                }
              </Navbar.Collapse>
            </Navbar>
          </Row>
        }
        {!embeddedMode &&
          <Row>
            <ProgressIndicator active={progress} />
          </Row>
        }
        <Row className={`map-holder active-map-type-${mapType}`}>
          <Map
            ref={(map) => { this.map = map; }}
            center={L.latLng(lat, lon)}
            zoom={zoom}
            onMoveend={this.handleMapMoveEnd}
            onMousemove={handleMapMouseMove}
            onMouseover={handleMapMouseOver}
            onMouseout={handleMapMouseOut}
            onClick={handleMapClick}
            onLocationfound={this.handleLocationFound}
            style={{ cursor: mouseCursor }}
            maxBounds={[[47.040256, 15.4688], [49.837969, 23.906238]]}
          >
            <Layers />
            <ScaleControl imperial={false} position="bottomright" />
            {(showDefaultMenu || tool === 'search') && <SearchResults />}

            <ObjectsResult />
            <RoutePlannerResult />
            <DistanceMeasurementResult />
            <ElevationMeasurementResult />
            <AreaMeasurementResult />
            <LocationResult />
            <TrackViewerResult />
            <InfoPoint />

            {tool === 'changesets' && <Changesets />}
            {tool === 'map-details' && <MapDetails />}
            {showElevationChart && <AsyncElevationChart />}
            {(tool === null || tool === 'gallery') && showGalleryPicker && <GalleryPicker />}
            <GalleryResult />
          </Map>
        </Row>

    );
  }
}

export default connect(
  state => ({
    lat: state.map.lat,
    lon: state.map.lon,
    zoom: state.map.zoom,
    tool: state.main.tool,
    mapType: state.map.mapType,
    activeModal: state.main.activeModal,
    progress: !!state.main.progress.length,
    mouseCursor: selectMouseCursor(state),
    embeddedMode: state.main.embeddedMode,
    user: state.auth.user,
    ignoreEscape: !!(state.main.activeModal && state.main.activeModal !== 'settings' // TODO settings dialog gets also closed
      || state.gallery.activeImageId),
    showElevationChart: !!state.elevationChart.elevationProfilePoints,
    showGalleryPicker: state.map.overlays.includes('I'),
    locate: state.main.locate,
  }),
  dispatch => ({
    onToolSet(tool) {
      dispatch(setTool(tool));
    },
    onMapRefocus(changes) {
      dispatch(mapRefocus(changes));
    },
    onModalLaunch(modalName) {
      dispatch(setActiveModal(modalName));
    },
    onLocationSet(lat, lon, accuracy) {
      dispatch(setLocation(lat, lon, accuracy));
    },
    onLogin() {
      dispatch(authLogin());
    },
    onLogout() {
      dispatch(authStartLogout());
    },
    onCheckLogin() {
      dispatch(authCheckLogin());
    },
    onGpxExport() {
      dispatch(exportGpx());
    },
    onMapClear() {
      dispatch(clearMap());
    },
    onLocate() {
      dispatch(toggleLocate());
    },
  }),
)(Main);

function createMenuItem(key, icon, title, onClick, props = {}) {
  const p = { key, ...props };
  if (typeof onClick === 'function') {
    p.onClick = onClick;
  } else {
    p.href = onClick;
    p.target = '_blank';
  }
  return React.createElement(MenuItem, p, <FontAwesomeIcon icon={icon} />, ` ${title}`);
}

function handleMapClick({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mapClick', lat, lon);
}

function handleMapMouseMove({ latlng: { lat, lng: lon }, originalEvent }) {
  mapEventEmitter.emit('mouseMove', lat, lon, originalEvent);
}

function handleMapMouseOver({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOver', lat, lon);
}

function handleMapMouseOut({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOut', lat, lon);
}

function selectMouseCursor(state) {
  switch (state.main.tool) {
    case 'measure-dist':
    case 'measure-ele':
    case 'measure-area':
    case 'select-home-location':
    case 'map-details':
    case 'route-planner':
      return state.routePlanner.pickMode ? 'crosshair' : 'auto';
    case 'gallery':
    case null:
      return state.map.overlays.includes('I') ? 'crosshair' : 'auto';
    default:
      return 'auto';
  }
}
<commit_msg>fixed (not) showing gallery picker<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { Map, ScaleControl } from 'react-leaflet';
import { connect } from 'react-redux';

import Navbar from 'react-bootstrap/lib/Navbar';
import Row from 'react-bootstrap/lib/Row';
import Nav from 'react-bootstrap/lib/Nav';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';

import NavbarHeader from 'fm3/components/NavbarHeader';
import Layers from 'fm3/components/Layers';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import ProgressIndicator from 'fm3/components/ProgressIndicator';
import Toasts from 'fm3/components/Toasts';

import SearchMenu from 'fm3/components/SearchMenu';
import SearchResults from 'fm3/components/SearchResults';

import ObjectsMenu from 'fm3/components/ObjectsMenu';
import ObjectsResult from 'fm3/components/ObjectsResult';

import MeasurementMenu from 'fm3/components/MeasurementMenu';
import DistanceMeasurementResult from 'fm3/components/DistanceMeasurementResult';
import AreaMeasurementResult from 'fm3/components/AreaMeasurementResult';
import ElevationMeasurementResult from 'fm3/components/ElevationMeasurementResult';
import LocationResult from 'fm3/components/LocationResult';

import RoutePlannerMenu from 'fm3/components/RoutePlannerMenu';
import RoutePlannerResult from 'fm3/components/RoutePlannerResult';

import TrackViewerMenu from 'fm3/components/TrackViewerMenu';
import TrackViewerResult from 'fm3/components/TrackViewerResult';

import SelectHomeLocationMenu from 'fm3/components/SelectHomeLocationMenu';

import GalleryMenu from 'fm3/components/GalleryMenu';
import GalleryResult from 'fm3/components/GalleryResult';
import GalleryPicker from 'fm3/components/GalleryPicker';

import Settings from 'fm3/components/Settings';
import ExternalApps from 'fm3/components/ExternalApps';
import AsyncElevationChart from 'fm3/components/AsyncElevationChart';

import InfoPointMenu from 'fm3/components/InfoPointMenu';
import InfoPoint from 'fm3/components/InfoPoint';

import ChangesetsMenu from 'fm3/components/ChangesetsMenu';
import Changesets from 'fm3/components/Changesets';

import MapDetailsMenu from 'fm3/components/MapDetailsMenu';
import MapDetails from 'fm3/components/MapDetails';

import ShareMapModal from 'fm3/components/ShareMapModal';
import EmbedMapModal from 'fm3/components/EmbedMapModal';

import * as FmPropTypes from 'fm3/propTypes';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';

import { mapRefocus } from 'fm3/actions/mapActions';
import { setTool, setActiveModal, setLocation, exportGpx, clearMap, toggleLocate } from 'fm3/actions/mainActions';
import { authLogin, authStartLogout, authCheckLogin } from 'fm3/actions/authActions';

import { setMapLeafletElement } from 'fm3/leafletElementHolder';

import 'fm3/styles/main.scss';
import 'leaflet/dist/leaflet.css';

class Main extends React.Component {
  static propTypes = {
    lat: PropTypes.number.isRequired,
    lon: PropTypes.number.isRequired,
    zoom: PropTypes.number.isRequired,
    tool: FmPropTypes.tool,
    mapType: FmPropTypes.mapType.isRequired,
    onToolSet: PropTypes.func.isRequired,
    onMapRefocus: PropTypes.func.isRequired,
    activeModal: PropTypes.string,
    onModalLaunch: PropTypes.func.isRequired,
    progress: PropTypes.bool.isRequired,
    onLocationSet: PropTypes.func.isRequired,
    mouseCursor: PropTypes.string.isRequired,
    embeddedMode: PropTypes.bool.isRequired,
    onLogin: PropTypes.func.isRequired,
    onLogout: PropTypes.func.isRequired,
    onCheckLogin: PropTypes.func.isRequired,
    user: PropTypes.shape({
      name: PropTypes.string.isRequired,
    }),
    ignoreEscape: PropTypes.bool.isRequired,
    showElevationChart: PropTypes.bool.isRequired,
    showGalleryPicker: PropTypes.bool.isRequired,
    onGpxExport: PropTypes.func.isRequired,
    onMapClear: PropTypes.func.isRequired,
    onLocate: PropTypes.func.isRequired,
    locate: PropTypes.bool.isRequired,
  };

  componentWillMount() {
    this.props.onCheckLogin();
  }

  componentDidMount() {
    setMapLeafletElement(this.map.leafletElement);
    document.addEventListener('keydown', (event) => {
      if (event.keyCode === 27 /* escape key */ && !this.props.ignoreEscape) {
        this.props.onToolSet(null);
      }
    });

    if (this.props.embeddedMode) {
      document.body.classList.add('embedded');
    }
  }

  componentWillUnmount() {
    setMapLeafletElement(null);
  }

  handleMapMoveEnd = () => {
    // TODO analyze why this can be null
    if (!this.map) {
      return;
    }

    const map = this.map.leafletElement;
    const { lat, lng: lon } = map.getCenter();
    const zoom = map.getZoom();

    if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== zoom) {
      this.props.onMapRefocus({ lat, lon, zoom });
    }
  }

  handleLocationFound = (e) => {
    this.props.onLocationSet(e.latitude, e.longitude, e.accuracy);
  }

  handleToolSelect(tool) {
    this.props.onToolSet(this.props.tool === tool ? null : tool);
  }

  createToolMenu() {
    return [
      createMenuItem(10, 'share', 'Exportovať do GPX', this.props.onGpxExport),
      createMenuItem(11, 'eraser', 'Vyčistiť mapu', this.props.onMapClear),
      <MenuItem divider key="_1" />,
      createMenuItem(2, 'map-signs', 'Plánovač', () => this.handleToolSelect('route-planner')),
      createMenuItem(1, 'map-marker', 'Miesta', () => this.handleToolSelect('objects')),
      createMenuItem(4, 'dot-circle-o', 'Kde som?', this.props.onLocate, { active: this.props.locate }),
      createMenuItem(8, 'picture-o', 'Fotografie', () => this.handleToolSelect('gallery')),
      createMenuItem(3, 'arrows-h', 'Meranie', () => this.handleToolSelect('measure-dist')),
      createMenuItem(5, 'road', 'Prehliadač trás', () => this.handleToolSelect('track-viewer')),
      createMenuItem(6, 'thumb-tack', 'Bod v mape', () => this.handleToolSelect('info-point')),
      createMenuItem(7, 'pencil', 'Zmeny v mape', () => this.handleToolSelect('changesets')),
      createMenuItem(9, 'info', 'Detaily v mape', () => this.handleToolSelect('map-details')),
    ];
  }

  createMoreMenu() {
    const { user, onLogout, onLogin, onModalLaunch } = this.props;

    return [
      user ?
        createMenuItem('login', 'sign-out', `Odhlás ${user.name}`, () => onLogout())
        :
        createMenuItem('login', 'sign-in', 'Prihlásenie', () => onLogin()),
      createMenuItem(1, 'cog', 'Nastavenia', () => onModalLaunch('settings')),
      <MenuItem divider key="_1" />,
      createMenuItem(6, 'mobile', 'Exporty mapy', 'http://wiki.freemap.sk/FileDownload'),
      createMenuItem(8, 'share-alt', 'Zdieľať mapu', () => onModalLaunch('share')),
      createMenuItem(9, 'code', 'Vložiť do webstránky', () => onModalLaunch('embed')),
      <MenuItem divider key="_2" />,
      createMenuItem(7, 'book', 'Pre začiatočníkov', 'http://wiki.freemap.sk/StarterGuide'),
      createMenuItem(4, 'github', 'Projekt na GitHub-e', 'https://github.com/FreemapSlovakia/freemap-v3-react'),
      <MenuItem divider key="_3" />,
      createMenuItem(2, 'exclamation-triangle', 'Nahlás chybu zobrazenia v mape', 'http://wiki.freemap.sk/NahlasenieChyby'),
      createMenuItem(3, 'exclamation-triangle', 'Nahlás chybu v portáli', 'https://github.com/FreemapSlovakia/freemap-v3-react/issues'),
    ];
  }

  openFreemapInNonEmbedMode = () => {
    const currentURL = window.location.href;
    window.open(currentURL.replace('&embed=true', ''), '_blank');
  }

  render() {
    // eslint-disable-next-line
    const { tool, activeModal, progress, mouseCursor, embeddedMode, lat, lon, zoom, mapType, showElevationChart, showGalleryPicker } = this.props;
    const showDefaultMenu = [null, 'location'].includes(tool);

    return (

 this.handleToolSelect('track-viewer')}>
        {embeddedMode && <button id="freemap-logo" className="embedded" onClick={this.openFreemapInNonEmbedMode} />}
        <Toasts />
        {!embeddedMode &&
          <Row>
            <Navbar fluid>
              <NavbarHeader />
              <Navbar.Collapse>
                {tool === 'objects' && <ObjectsMenu />}
                {(showDefaultMenu || tool === 'search') && <SearchMenu />}
                {tool === 'route-planner' && <RoutePlannerMenu />}
                {(tool === 'measure-dist' || tool === 'measure-ele' || tool === 'measure-area') && <MeasurementMenu />}
                {tool === 'track-viewer' && <TrackViewerMenu />}
                {tool === 'info-point' && <InfoPointMenu />}
                {tool === 'changesets' && <ChangesetsMenu />}
                {tool === 'gallery' && <GalleryMenu />}
                {tool === 'select-home-location' && <SelectHomeLocationMenu />}
                {tool === 'map-details' && <MapDetailsMenu />}
                {activeModal === 'settings' && <Settings />}
                {activeModal === 'share' && <ShareMapModal />}
                {activeModal === 'embed' && <EmbedMapModal />}
                {showDefaultMenu &&

                    <NavDropdown title={<FontAwesomeIcon icon="briefcase" /> Nástroje} id="tools">
                      {this.createToolMenu()}
                    </NavDropdown>
                    <ExternalApps lat={lat} lon={lon} zoom={zoom} mapType={mapType} />
                    {showDefaultMenu &&
                      <NavDropdown title={<FontAwesomeIcon icon="ellipsis-v" /> Viac} id="additional-menu-items">
                        {this.createMoreMenu()}
                      </NavDropdown>
                    }

                }
              </Navbar.Collapse>
            </Navbar>
          </Row>
        }
        {!embeddedMode &&
          <Row>
            <ProgressIndicator active={progress} />
          </Row>
        }
        <Row className={`map-holder active-map-type-${mapType}`}>
          <Map
            ref={(map) => { this.map = map; }}
            center={L.latLng(lat, lon)}
            zoom={zoom}
            onMoveend={this.handleMapMoveEnd}
            onMousemove={handleMapMouseMove}
            onMouseover={handleMapMouseOver}
            onMouseout={handleMapMouseOut}
            onClick={handleMapClick}
            onLocationfound={this.handleLocationFound}
            style={{ cursor: mouseCursor }}
            maxBounds={[[47.040256, 15.4688], [49.837969, 23.906238]]}
          >
            <Layers />
            <ScaleControl imperial={false} position="bottomright" />
            {(showDefaultMenu || tool === 'search') && <SearchResults />}

            <ObjectsResult />
            <RoutePlannerResult />
            <DistanceMeasurementResult />
            <ElevationMeasurementResult />
            <AreaMeasurementResult />
            <LocationResult />
            <TrackViewerResult />
            <InfoPoint />

            {tool === 'changesets' && <Changesets />}
            {tool === 'map-details' && <MapDetails />}
            {showElevationChart && <AsyncElevationChart />}
            {(tool === null || tool === 'gallery') && showGalleryPicker && <GalleryPicker />}
            <GalleryResult />
          </Map>
        </Row>

    );
  }
}

export default connect(
  state => ({
    lat: state.map.lat,
    lon: state.map.lon,
    zoom: state.map.zoom,
    tool: state.main.tool,
    mapType: state.map.mapType,
    activeModal: state.main.activeModal,
    progress: !!state.main.progress.length,
    mouseCursor: selectMouseCursor(state),
    embeddedMode: state.main.embeddedMode,
    user: state.auth.user,
    ignoreEscape: !!(state.main.activeModal && state.main.activeModal !== 'settings' // TODO settings dialog gets also closed
      || state.gallery.activeImageId),
    showElevationChart: !!state.elevationChart.elevationProfilePoints,
    showGalleryPicker: isShowGalleryPicker(state),
    locate: state.main.locate,
  }),
  dispatch => ({
    onToolSet(tool) {
      dispatch(setTool(tool));
    },
    onMapRefocus(changes) {
      dispatch(mapRefocus(changes));
    },
    onModalLaunch(modalName) {
      dispatch(setActiveModal(modalName));
    },
    onLocationSet(lat, lon, accuracy) {
      dispatch(setLocation(lat, lon, accuracy));
    },
    onLogin() {
      dispatch(authLogin());
    },
    onLogout() {
      dispatch(authStartLogout());
    },
    onCheckLogin() {
      dispatch(authCheckLogin());
    },
    onGpxExport() {
      dispatch(exportGpx());
    },
    onMapClear() {
      dispatch(clearMap());
    },
    onLocate() {
      dispatch(toggleLocate());
    },
  }),
)(Main);

function createMenuItem(key, icon, title, onClick, props = {}) {
  const p = { key, ...props };
  if (typeof onClick === 'function') {
    p.onClick = onClick;
  } else {
    p.href = onClick;
    p.target = '_blank';
  }
  return React.createElement(MenuItem, p, <FontAwesomeIcon icon={icon} />, ` ${title}`);
}

function handleMapClick({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mapClick', lat, lon);
}

function handleMapMouseMove({ latlng: { lat, lng: lon }, originalEvent }) {
  mapEventEmitter.emit('mouseMove', lat, lon, originalEvent);
}

function handleMapMouseOver({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOver', lat, lon);
}

function handleMapMouseOut({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOut', lat, lon);
}

function selectMouseCursor(state) {
  switch (state.main.tool) {
    case 'measure-dist':
    case 'measure-ele':
    case 'measure-area':
    case 'select-home-location':
    case 'map-details':
    case 'route-planner':
      return state.routePlanner.pickMode ? 'crosshair' : 'auto';
    default:
      return isShowGalleryPicker(state) ? 'crosshair' : 'auto';
  }
}

function isShowGalleryPicker(state) {
  return (state.main.tool === null || state.main.tool === 'gallery')
    && state.map.overlays.includes('I')
    && state.gallery.pickingPositionForId === null;
}
<|endoftext|>


<commit_before>/*
	it will draw the x axis and y axis for the chart. optionally, it could draw the grid guides along the x and y axis.

	Initalization:
		initialize: function(containerInfo) {
			var axis =  this.mixin("Axis",  guideLineG, {
				info: containerInfo,
				x: 'ordinal',
				y: 'log',
				width: this.base.attr('width'),  // optional
				height: this.base.attr('height'),  // optional
				guide: true,
				ticksOnResize: true
			});
		}

		// Passing options object as a third argument. Options is required.

	Arguments:
		Attributes
			since it inherit from scale.js, it will have all its private & public attr. Read core/scale.js Arguments section for details

		public attributes:
			guide: Boolean. Optional,  default to false. Draw the grid guides along x, y axis when guide is true
			ticksOnResize: Boolean, Optional, default to false. Redraw the ticks when resize the window based on different ratio
			xAxis, yAxis   ( # could use to override its default value, like defined ticks(), orient(), etc. )
			xAxisG, yAxisG ( # could use to defined custom element attributes )

		Note:  currently, xGuide, yGuide is remaining as private object, if needed, it could expose out to the api

	APIs:
		onDataBind: function() { }
			# fn is actually handling the custom dataBind value to each individual group

		axis.xLabel(_label, leftRightPosition, upDownPosition)
			# _label:  Required, text will be shown in the label section
			# leftRightPosition: Optional, it will be positive number. move the text on the left and right position
			# upDownPosition: Optional, it will be positive number. move the text on the up and down position

			By default, the text will be translated to margin.left and margin.bottom
			Use  .x .label  selector to style the label

		axis.yLabel(_label, upDownPosition, leftRightPosition) {
			# _label:  Required, text will be shown in the label section
			# upDownPosition: Optional, it will be positive number. move the text on the up and down position
			# leftRightPosition: Optional, it will be positive number. move the text on the left and right position

			By default, the text will be translated to margin.left and margin.top, rotate -90 degrees
			Use  .y .label  selector to style the label
*/
d3.chart('Base').extend("Axis", {

	initialize: function(options) {
		options = options || {};

		this.guide = options.guide || false;
		this.ticksOnResize = options.ticksOnResize || false;

		this.xAxis = d3.svg.axis().scale(this.xScale).orient('bottom');

		this.yAxis = d3.svg.axis().scale(this.yScale).orient('left');

		if(this.guide) {
			this.xAxis
				.tickPadding(5)
				.tickSize(-this.height, 0, 6); //axis.tickSize([major[[, minor], end]])

			this.yAxis
				.tickPadding(5)
				.tickSize(-this.width, 0, 6);
		}

		this.xAxisG =  this.base.append('g');

		this.xAxisG.attr({
			'class': 'x axis'
			//'transform': 'translate(0,' + this.height + ')'
		});

		this.xPos();

		this.yAxisG = this.base.append('g');

		this.yAxisG.attr({
			'class': 'y axis'
		});

		this.yPos();
	},

	transform: function(data) {
		//Acutally draw the xAxis, yAxis on the screen
		if(this.onDataBind) { this.onDataBind(data); }

		this.xAxisG.call( this.xAxis);
		this.yAxisG.call( this.yAxis);

		this._onWindowResize(data);

		return data;
	},

	xPos: function(_value) {
		var _value = (_value) ? _value : this.height;

		this.xAxisG.attr({
			'transform': 'translate(0,' + _value + ')'
		});

		return this;
	},

	yPos: function(_value) {
		if(_value) {
			this.yAxisG.attr({
				'transform': 'translate(' + _value  + ' , 0)'
			});
		}
		return this;
	},

	// Update Scale, Box Size, and attr values
	_update: function(_width, _height) {
		this.xAxisG.attr({'transform': 'translate(0,' + _height + ')'});

		if(this.ticksOnResize) this._redrawTicksOnResize();

		this.xAxisG.call( this.xAxis);
		this.yAxisG.call( this.yAxis);
	},

	_redrawTicksOnResize: function() {
		var xScaleRange = this.xScale.range(),
			yScaleRange = this.yScale.range();

		if( this._x !== 'ordinal' ) {
			this.xAxis.ticks(Math.abs(xScaleRange[1] - xScaleRange[0]) / 100);
		}
		if( this._y !== 'ordinal' ) {
			this.yAxis.ticks(Math.abs(yScaleRange[1] - yScaleRange[0]) / 100);
		}
	},

	xLabel: function(_label, leftRightPosition, upDownPosition) {
		var containerInfo = this.info,
			xValue = leftRightPosition + containerInfo.marginLeft,
			yValue = upDownPosition - containerInfo.marginBottom;

		this.xAxisG.append('text').classed('label', true).attr({
			'x': xValue || containerInfo.marginLeft - 15, // control left and right of the label
			'y': yValue || 0, // control up and down of the label
			'transform': 'translate(' + this.info.marginLeft+ ',' + this.info.marginBottom + ')'
		}).text(_label);

		return this;
	},

	yLabel: function(_label, upDownPosition, leftRightPosition) {
		var containerInfo = this.info,
			xValue = (-upDownPosition) + containerInfo.marginTop - 15,
			yValue = Math.abs(leftRightPosition - containerInfo.marginLeft) - 5;

		this.yAxisG.append('text').classed('label', true).attr({
			'x':  xValue || -15, // control up and down of the label
			'y':  yValue || 15,  // control left and right of the label
			'transform':  'rotate(-90) translate(' + (-this.info.marginTop)+ ',' + (-this.info.marginLeft) + ')'
		}).text(_label);

		return this;
	}
});
<commit_msg>update the axis chart info<commit_after>/*
	it will draw the x axis and y axis for the chart. optionally, it could draw the grid guides along the x and y axis.

	Initalization:
		initialize: function(containerInfo) {
			var axis =  this.mixin("Axis",  guideLineG, {
				info: containerInfo,
				x: 'ordinal',
				y: 'log',
				width: this.base.attr('width'),  // optional
				height: this.base.attr('height'),  // optional
				guide: true,
				ticksOnResize: true
			});
		}

		// Passing options object as a third argument. Options is required.

	Arguments:
		Attributes
			since it inherit from scale.js, it will have all its private & public attr. Read core/scale.js Arguments section for details

		public attributes:
			guide: Boolean. Optional,  default to false. Draw the grid guides along x, y axis when guide is true
			ticksOnResize: Boolean, Optional, default to false. Redraw the ticks when resize the window based on different ratio
			xAxis, yAxis   ( # could use to override its default value, like defined ticks(), orient(), etc. )
			xAxisG, yAxisG ( # could use to defined custom element attributes )

		Note:  currently, xGuide, yGuide is remaining as private object, if needed, it could expose out to the api

	APIs:
		onDataBind: function() { }
			# fn is actually handling the custom dataBind value to each individual group

		axis.xLabel(_label, leftRightPosition, upDownPosition)
			# _label:  Required, text will be shown in the label section
			# leftRightPosition: Optional, it will be positive number. move the text on the left and right position
			# upDownPosition: Optional, it will be positive number. move the text on the up and down position

			By default, the text will be translated to margin.left and margin.bottom
			Use  .x .label  selector to style the label

		axis.yLabel(_label, upDownPosition, leftRightPosition) {
			# _label:  Required, text will be shown in the label section
			# upDownPosition: Optional, it will be positive number. move the text on the up and down position
			# leftRightPosition: Optional, it will be positive number. move the text on the left and right position

			By default, the text will be translated to margin.left and margin.top, rotate -90 degrees
			Use  .y .label  selector to style the label
*/
d3.chart('Base').extend("Axis", {

	initialize: function(options) {
		options = options || {};

		this.guide = options.guide || false;
		this.ticksOnResize = options.ticksOnResize || false;

		this.xAxis = d3.svg.axis().scale(this.xScale).orient('bottom');

		this.yAxis = d3.svg.axis().scale(this.yScale).orient('left');

		if(this.guide) {
			this.xAxis
				.tickPadding(5)
				.tickSize(-this.height, 0, 6); //axis.tickSize([major[[, minor], end]])

			this.yAxis
				.tickPadding(5)
				.tickSize(-this.width, 0, 6);
		}

		this.xAxisG =  this.base.append('g');

		this.xAxisG.attr({
			'class': 'x axis'
			//'transform': 'translate(0,' + this.height + ')'
		});

		this.xPos();

		this.yAxisG = this.base.append('g');

		this.yAxisG.attr({
			'class': 'y axis'
		});

		this.yPos();
	},

	transform: function(data) {
		//Acutally draw the xAxis, yAxis on the screen
		if(this.onDataBind) { this.onDataBind(data); }

		this.xAxisG
			.transition()
			.duration(400)
			.ease('cubic-out')
			.call( this.xAxis);

		this.yAxisG
			.transition()
			.duration(400)
			.ease('cubic-out')
			.call( this.yAxis);

		this._onWindowResize(data);

		return data;
	},

	xPos: function(_value) {
		var _value = (_value) ? _value : this.height;

		this.xAxisG.attr({
			'transform': 'translate(0,' + _value + ')'
		});

		return this;
	},

	yPos: function(_value) {
		if(_value) {
			this.yAxisG.attr({
				'transform': 'translate(' + _value  + ' , 0)'
			});
		}
		return this;
	},

	// Update Scale, Box Size, and attr values
	_update: function(_width, _height) {
		this.xAxisG.attr({'transform': 'translate(0,' + _height + ')'});

		if(this.ticksOnResize) this._redrawTicksOnResize();

		this.xAxisG
			.transition()
			.duration(400)
			.ease('cubic-out')
			.call( this.xAxis);

		this.yAxisG
			.transition()
			.duration(400)
			.ease('cubic-out')
			.call( this.yAxis);
	},

	_redrawTicksOnResize: function() {
		var xScaleRange = this.xScale.range(),
			yScaleRange = this.yScale.range();

		if( this._x !== 'ordinal' ) {
			this.xAxis.ticks(Math.abs(xScaleRange[1] - xScaleRange[0]) / 100);
		}
		if( this._y !== 'ordinal' ) {
			this.yAxis.ticks(Math.abs(yScaleRange[1] - yScaleRange[0]) / 100);
		}
	},

	xLabel: function(_label, leftRightPosition, upDownPosition) {
		var containerInfo = this.info,
			xValue = leftRightPosition + containerInfo.marginLeft,
			yValue = upDownPosition - containerInfo.marginBottom;

		this.xAxisG.append('text').classed('label', true).attr({
			'x': xValue || containerInfo.marginLeft - 15, // control left and right of the label
			'y': yValue || 0, // control up and down of the label
			'transform': 'translate(' + this.info.marginLeft+ ',' + this.info.marginBottom + ')'
		})
			.style('opacity', 1e-6)
			.text(_label)
				.transition()
				.duration(1000)
				.ease('cubic-out')
				.style('opacity', 1);

		return this;
	},

	yLabel: function(_label, upDownPosition, leftRightPosition) {
		var containerInfo = this.info,
			xValue = (-upDownPosition) + containerInfo.marginTop - 15,
			yValue = Math.abs(leftRightPosition - containerInfo.marginLeft) - 5;

		this.yAxisG.append('text').classed('label', true).attr({
			'x':  xValue || -15, // control up and down of the label
			'y':  yValue || 15,  // control left and right of the label
			'transform':  'rotate(-90) translate(' + (-this.info.marginTop)+ ',' + (-this.info.marginLeft) + ')'
		})
			.style('opacity', 1e-6)
			.text(_label)
				.transition()
				.duration(800)
				.ease('cubic-out')
				.style('opacity', 1);

		return this;
	}
});
<|endoftext|>


<commit_before>var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var Infobox = require('../../../../components/infobox/infobox-factory');
var InfoboxModel = require('../../../../components/infobox/infobox-model');
var InfoboxCollection = require('../../../../components/infobox/infobox-collection');
var PanelWithOptionsView = require('../../../../components/view-options/panel-with-options-view');
var panelTemplate = require('./panel-with-options.tpl');
var LayerContentAnalysesView = require('./layer-content-analyses-view');
var OnboardingLauncher = require('../../../../components/onboardings/generic/generic-onboarding-launcher');
var OnboardingView = require('../../../../components/onboardings/layers/analysis-onboarding/analysis-onboarding-view');
var layerOnboardingKey = require('../../../../components/onboardings/layers/layer-onboarding-key');
var checkAndBuildOpts = require('../../../../helpers/required-opts');
var fetchAllQueryObjectsIfNecessary = require('../../../../helpers/fetch-all-query-objects');
var AnalysesService = require('./analyses-service');

var REQUIRED_OPTS = [
  'userActions',
  'analysisFormsCollection',
  'layerDefinitionModel',
  'analysisDefinitionNodesCollection',
  'configModel',
  'userModel',
  'editorModel',
  'stackLayoutModel',
  'onboardings',
  'onboardingNotification'
];

module.exports = CoreView.extend({

  initialize: function (opts) {
    checkAndBuildOpts(opts, REQUIRED_OPTS, this);

    this._selectedNodeId = opts.selectedNodeId;
    this._infoboxState = this._infoboxState.bind(this);

    this._infoboxModel = new InfoboxModel({
      state: ''
    });

    this._overlayModel = new Backbone.Model({
      visible: this._isLayerHidden()
    });

    this._onboardingLauncher = new OnboardingLauncher({
      view: OnboardingView,
      onboardingNotification: this._onboardingNotification,
      notificationKey: layerOnboardingKey,
      onboardings: this._onboardings
    }, {
      editorModel: this._editorModel,
      selector: 'LayerOnboarding'
    });

    this._getQueryAndCheckState();

    this._initBinds();
  },

  render: function () {
    this._launchOnboarding();
    this.clearSubViews();
    this.$el.empty();

    var self = this;
    var infoboxSstates = [
      {
        state: 'georeference',
        createContentView: function () {
          var name = this._layerDefinitionModel ? this._layerDefinitionModel.getTableName() : '';
          var body = [
            _t('editor.tab-pane.layers.georeference.body', { name: name }),
            _t('editor.layers.georeference.manually-add')
          ].join(' ');

          return Infobox.createWithAction({
            type: 'alert',
            title: _t('editor.tab-pane.layers.georeference.title'),
            body: body,
            mainAction: {
              label: _t('editor.layers.georeference.georeference-button')
            }
          });
        },
        mainAction: self._onGeoreferenceClicked.bind(self)
      }, {
        state: 'layer-hidden',
        createContentView: function () {
          return Infobox.createWithAction({
            type: 'alert',
            title: _t('editor.messages.layer-hidden.title'),
            body: _t('editor.messages.layer-hidden.body'),
            mainAction: {
              label: _t('editor.messages.layer-hidden.show')
            }
          });
        },
        mainAction: self._showHiddenLayer.bind(self)
      }
    ];

    var infoboxCollection = new InfoboxCollection(infoboxSstates);

    var panelWithOptionsView = new PanelWithOptionsView({
      template: panelTemplate,
      className: 'Editor-content',
      editorModel: self._editorModel,
      infoboxModel: self._infoboxModel,
      infoboxCollection: infoboxCollection,
      createContentView: function () {
        return new LayerContentAnalysesView({
          className: 'Editor-content',
          userActions: self._userActions,
          analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
          userModel: self._userModel,
          analysisFormsCollection: self._analysisFormsCollection,
          configModel: self._configModel,
          layerDefinitionModel: self._layerDefinitionModel,
          stackLayoutModel: self._stackLayoutModel,
          selectedNodeId: self._selectedNodeId,
          overlayModel: self._overlayModel
        });
      }
    });

    this.$el.append(panelWithOptionsView.render().el);
    this.addView(panelWithOptionsView);
    return this;
  },

  _initBinds: function () {
    this.listenTo(this._layerDefinitionModel, 'change:visible', this._infoboxState);
    // Only when there is no nodes we should check if node can be georeferenced
    this.listenTo(this._analysisFormsCollection, 'reset remove', this._getQueryAndCheckState);
  },

  _getQueryAndCheckState: function () {
    if (!this._analysisFormsCollection.isEmpty()) {
      this._infoboxState();
      return;
    }

    var analysisDefinitionNodeModel = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();

    fetchAllQueryObjectsIfNecessary(
      {
        queryRowsCollection: analysisDefinitionNodeModel.queryRowsCollection,
        queryGeometryModel: analysisDefinitionNodeModel.queryGeometryModel,
        querySchemaModel: analysisDefinitionNodeModel.querySchemaModel
      },
      this._infoboxState
    );
  },

  _launchOnboarding: function () {
    if (this._onboardingNotification.getKey(layerOnboardingKey)) {
      return;
    }

    if (this._analysisFormsCollection.isEmpty()) {
      this._onboardingLauncher.launch();
    }
  },

  _infoboxState: function () {
    if (this._isLayerHidden()) {
      this._infoboxModel.set({ state: 'layer-hidden' });
      this._overlayModel.set({ visible: true });
    } else if (this._layerDefinitionModel.canBeGeoreferenced() && this._analysisFormsCollection.isEmpty()) {
      this._infoboxModel.set({ state: 'georeference' });
      this._overlayModel.set({ visible: false });
    } else {
      this._infoboxModel.set({ state: '' });
      this._overlayModel.set({ visible: false });
    }
  },

  _showHiddenLayer: function () {
    this._layerDefinitionModel.toggleVisible();
    this._userActions.saveLayer(this._layerDefinitionModel);

    this._infoboxState();
  },

  _isLayerHidden: function () {
    return this._layerDefinitionModel.get('visible') === false;
  },

  _onGeoreferenceClicked: function () {
    AnalysesService.addGeoreferenceAnalysis();
  }
});
<commit_msg>Remove georeference state from infobox<commit_after>var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var Infobox = require('../../../../components/infobox/infobox-factory');
var InfoboxModel = require('../../../../components/infobox/infobox-model');
var InfoboxCollection = require('../../../../components/infobox/infobox-collection');
var PanelWithOptionsView = require('../../../../components/view-options/panel-with-options-view');
var panelTemplate = require('./panel-with-options.tpl');
var LayerContentAnalysesView = require('./layer-content-analyses-view');
var OnboardingLauncher = require('../../../../components/onboardings/generic/generic-onboarding-launcher');
var OnboardingView = require('../../../../components/onboardings/layers/analysis-onboarding/analysis-onboarding-view');
var layerOnboardingKey = require('../../../../components/onboardings/layers/layer-onboarding-key');
var checkAndBuildOpts = require('../../../../helpers/required-opts');
var fetchAllQueryObjectsIfNecessary = require('../../../../helpers/fetch-all-query-objects');
var AnalysesService = require('./analyses-service');

var REQUIRED_OPTS = [
  'userActions',
  'analysisFormsCollection',
  'layerDefinitionModel',
  'analysisDefinitionNodesCollection',
  'configModel',
  'userModel',
  'editorModel',
  'stackLayoutModel',
  'onboardings',
  'onboardingNotification'
];

module.exports = CoreView.extend({

  initialize: function (opts) {
    checkAndBuildOpts(opts, REQUIRED_OPTS, this);

    this._selectedNodeId = opts.selectedNodeId;
    this._infoboxState = this._infoboxState.bind(this);

    this._infoboxModel = new InfoboxModel({
      state: ''
    });

    this._overlayModel = new Backbone.Model({
      visible: this._isLayerHidden()
    });

    this._onboardingLauncher = new OnboardingLauncher({
      view: OnboardingView,
      onboardingNotification: this._onboardingNotification,
      notificationKey: layerOnboardingKey,
      onboardings: this._onboardings
    }, {
      editorModel: this._editorModel,
      selector: 'LayerOnboarding'
    });

    this._getQueryAndCheckState();

    this._initBinds();
  },

  render: function () {
    this._launchOnboarding();
    this.clearSubViews();
    this.$el.empty();

    var self = this;
    var infoboxSstates = [
      {
        state: 'layer-hidden',
        createContentView: function () {
          return Infobox.createWithAction({
            type: 'alert',
            title: _t('editor.messages.layer-hidden.title'),
            body: _t('editor.messages.layer-hidden.body'),
            mainAction: {
              label: _t('editor.messages.layer-hidden.show')
            }
          });
        },
        mainAction: self._showHiddenLayer.bind(self)
      }
    ];

    var infoboxCollection = new InfoboxCollection(infoboxSstates);

    var panelWithOptionsView = new PanelWithOptionsView({
      template: panelTemplate,
      className: 'Editor-content',
      editorModel: self._editorModel,
      infoboxModel: self._infoboxModel,
      infoboxCollection: infoboxCollection,
      createContentView: function () {
        return new LayerContentAnalysesView({
          className: 'Editor-content',
          userActions: self._userActions,
          analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
          userModel: self._userModel,
          analysisFormsCollection: self._analysisFormsCollection,
          configModel: self._configModel,
          layerDefinitionModel: self._layerDefinitionModel,
          stackLayoutModel: self._stackLayoutModel,
          selectedNodeId: self._selectedNodeId,
          overlayModel: self._overlayModel
        });
      }
    });

    this.$el.append(panelWithOptionsView.render().el);
    this.addView(panelWithOptionsView);
    return this;
  },

  _initBinds: function () {
    this.listenTo(this._layerDefinitionModel, 'change:visible', this._infoboxState);
    // Only when there is no nodes we should check if node can be georeferenced
    this.listenTo(this._analysisFormsCollection, 'reset remove', this._getQueryAndCheckState);
  },

  _getQueryAndCheckState: function () {
    if (!this._analysisFormsCollection.isEmpty()) {
      this._infoboxState();
      return;
    }

    var analysisDefinitionNodeModel = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();

    fetchAllQueryObjectsIfNecessary(
      {
        queryRowsCollection: analysisDefinitionNodeModel.queryRowsCollection,
        queryGeometryModel: analysisDefinitionNodeModel.queryGeometryModel,
        querySchemaModel: analysisDefinitionNodeModel.querySchemaModel
      },
      this._infoboxState
    );
  },

  _launchOnboarding: function () {
    if (this._onboardingNotification.getKey(layerOnboardingKey)) {
      return;
    }

    if (this._analysisFormsCollection.isEmpty()) {
      this._onboardingLauncher.launch();
    }
  },

  _infoboxState: function () {
    if (this._isLayerHidden()) {
      this._infoboxModel.set({ state: 'layer-hidden' });
      this._overlayModel.set({ visible: true });
    } else {
      this._infoboxModel.set({ state: '' });
      this._overlayModel.set({ visible: false });
    }
  },

  _showHiddenLayer: function () {
    this._layerDefinitionModel.toggleVisible();
    this._userActions.saveLayer(this._layerDefinitionModel);

    this._infoboxState();
  },

  _isLayerHidden: function () {
    return this._layerDefinitionModel.get('visible') === false;
  },

  _onGeoreferenceClicked: function () {
    AnalysesService.addGeoreferenceAnalysis();
  }
});
<|endoftext|>


<commit_before>'use strict';

import request from 'superagent';
import qs from 'qs';
import uniqueId from 'lodash/utility/uniqueId';
import BPromise from 'bluebird';

BPromise.config({
  warnings: true,
  longStackTraces: true,
  cancellation: true
});

const API = {

  config(options = {}) {
    let { onRequestError, onRequestCompleted, requestData,  } = options;

    this.onRequestCompleted = onRequestCompleted;
    this.onRequestError = onRequestError;
    this.requestData = requestData;
  },

	request(options = {}) {

		let { method, data, endpoint, onSuccess } = options;

		if (!method) { method = 'get' }
		if (!data) { data = {} }
		if (!endpoint) {
			throw new Error('Please provide an endpoint for an API call');
		}

		if (!onSuccess) {
			throw new Error('Please provide onSuccess callback that accepts responseBody object');
		}

		let doRequest;

		Object.assign(data, this.requestData);

    if (method === 'post' || method === 'put') {
      doRequest = request[method](url_for(endpoint))
        .accept('json')
        .send(data);
    } else if (method === 'get' || method == 'del') {
      doRequest = request[method](url_for(endpoint))
        .accept('json')
        .query(
          qs.stringify(
            data,
            { arrayFormat: 'brackets' }
          )
        );
    }

    /*
      requestId is used on onSuccess callback to allow models to
      prevent loops when setting same attributes
      multiple times
     */
    let requestId = uniqueId('request_');
    let isCancelled = false;
    return new BPromise( (resolve, reject) => {

    	doRequest.end( (err, response) => {

        if (this.onRequestCompleted) this.onRequestCompleted(response);

        let resolveOptions;

	      if (!response.ok) {
	      	let errors = response.body ? response.body.errors : 'Something bad happened';
      		let statusCode = response.status;

      		if (this.onRequestError) this.onRequestError({ statusCode, errors });

          /*
            we resolve promise even if request
            was not successfull to reduce boilerplat
            + because we  typically don't want ui do
            have some specific behaviour in this case
           */
          resolveOptions = {
            ok: false,
            errors: 'Something bad happened',
            requestId
          };

	      } else {
	      	// showSuccessMessage(successType)

	      	onSuccess({
            json: response.body,
            requestId
          });

          resolveOptions = {
            ok: true,
            body: response.body,
            requestId,
          };

        }

        resolve(resolveOptions);

      });

    });

	}
}

export default API;<commit_msg>added urlRoot config option<commit_after>'use strict';

import request from 'superagent';
import qs from 'qs';
import uniqueId from 'lodash/utility/uniqueId';
import BPromise from 'bluebird';

BPromise.config({
  warnings: true,
  longStackTraces: true,
  cancellation: true
});

const API = {

  config(options = {}) {
    let { onRequestError, onRequestCompleted, requestData, urlRoot } = options;

    this.onRequestCompleted = onRequestCompleted;
    this.onRequestError = onRequestError;
    this.requestData = requestData;
    this.urlRoot = urlRoot;
  },

	request(options = {}) {

		let { method, data, endpoint, onSuccess } = options;

		if (!method) { method = 'get' }
		if (!data) { data = {} }
		if (!endpoint) {
			throw new Error('Please provide an endpoint for an API call');
		}

		if (!onSuccess) {
			throw new Error('Please provide onSuccess callback that accepts responseBody object');
		}

		let doRequest;

		Object.assign(data, this.requestData);

    if (method === 'post' || method === 'put') {
      doRequest = request[method](this.urlRoot+endpoint)
        .accept('json')
        .send(data);
    } else if (method === 'get' || method == 'del') {
      doRequest = request[method](this.urlRoot+endpoint)
        .accept('json')
        .query(
          qs.stringify(
            data,
            { arrayFormat: 'brackets' }
          )
        );
    }

    /*
      requestId is used on onSuccess callback to allow models to
      prevent loops when setting same attributes
      multiple times
     */
    let requestId = uniqueId('request_');
    let isCancelled = false;
    return new BPromise( (resolve, reject) => {

    	doRequest.end( (err, response) => {

        if (this.onRequestCompleted) this.onRequestCompleted(response);

        let resolveOptions;

	      if (!response.ok) {
	      	let errors = response.body ? response.body.errors : 'Something bad happened';
      		let statusCode = response.status;

      		if (this.onRequestError) this.onRequestError({ statusCode, errors });

          /*
            we resolve promise even if request
            was not successfull to reduce boilerplat
            + because we  typically don't want ui do
            have some specific behaviour in this case
           */
          resolveOptions = {
            ok: false,
            errors: 'Something bad happened',
            requestId
          };

	      } else {
	      	// showSuccessMessage(successType)

	      	onSuccess({
            json: response.body,
            requestId
          });

          resolveOptions = {
            ok: true,
            body: response.body,
            requestId,
          };

        }

        resolve(resolveOptions);

      });

    });

	}
}

export default API;<|endoftext|>


<commit_before>var assert = require('assert');
var cp = require('child_process');
var which = require('which');
var attach = require('../promise');

for (var k in assert) global[k] = assert[k];

try {
  which.sync('nvim');
} catch (e) {
  console.error('A Neovim installation is required to run the tests',
                '(see https://github.com/neovim/neovim/wiki/Installing)');
  process.exit(1);
}

describe('Nvim Promise API', function() {
  var nvim, requests, notifications;

  before(function(done) {
    nvim = cp.spawn('nvim', ['-u', 'NONE', '-N', '--embed'], {
      cwd: __dirname
    });

    attach(nvim.stdin, nvim.stdout).then(function(n) {
      nvim = n;
      nvim.on('request', function(method, args, resp) {
        requests.push({method: method, args: args});
        resp.send('received ' + method + '(' + args + ')');
      });
      nvim.on('notification', function(method, args) {
        notifications.push({method: method, args: args});
      });
      done();
    });
  });

  beforeEach(function() {
    requests = [];
    notifications = [];
  });

  it('can send requests and receive response', function() {
    return nvim.eval('{"k1": "v1", "k2": 2}').then(function(res) {
      deepEqual(res, {k1: 'v1', k2: 2});
    });
  });

  it('can receive requests and send responses', function() {
    return nvim.eval('rpcrequest(1, "request", 1, 2, 3)').then(function(res) {
      equal(res, 'received request(1,2,3)');
      deepEqual(requests, [{method: 'request', args: [1, 2, 3]}]);
      deepEqual(notifications, []);
    });
  });

  it('can receive notifications', function(done) {
    return nvim.eval('rpcnotify(1, "notify", 1, 2, 3)').then(function(res) {
      equal(res, 1);
      deepEqual(requests, []);
      setImmediate(function() {
        deepEqual(notifications, [{method: 'notify', args: [1, 2, 3]}]);
        done();
      });
    });
  });

  it('can deal with custom types', function(done) {
    return nvim.command('vsp').then(function(res) {
      nvim.getWindows().then(function(windows) {
        equal(windows.length, 2);
        // equal(windows[0] instanceof nvim.Window, true);
        // equal(windows[1] instanceof nvim.Window, true);
        nvim.setCurrentWindow(windows[1]).then(function(res) {
          nvim.getCurrentWindow().then(function(win) {
            equal(win._data, windows[1]._data);
            nvim.getCurrentBuffer().then(function(buf) {
              // equal(buf instanceof nvim.Buffer, true);
              buf.getLineSlice(0, -1, true, true).then(function(lines) {
                deepEqual(lines, ['']);
                buf.setLineSlice(0, -1, true, true, ['line1', 'line2']).then(function() {
                  buf.getLineSlice(0, -1, true, true).then(function(lines) {
                    deepEqual(lines, ['line1', 'line2']);
                    done();
                  });
                });
              });
            });
          });
        });
      });
    });

  });

  it('emits "disconnect" after quit', function(done) {
    nvim.on('disconnect', done);
    nvim.quit();
  });
});
<commit_msg>Fix attach tests<commit_after>/* eslint-env jest */
const cp = require('child_process');
const which = require('which');
const attach = require('./attach');

try {
  which.sync('nvim');
} catch (e) {
  console.error(
    'A Neovim installation is required to run the tests',
    '(see https://github.com/neovim/neovim/wiki/Installing)'
  );
  process.exit(1);
}

describe('Nvim Promise API', () => {
  let proc;
  let nvim;
  let requests;
  let notifications;

  beforeAll(async done => {
    proc = cp.spawn('nvim', ['-u', 'NONE', '-N', '--embed'], {
      cwd: __dirname,
    });

    nvim = await attach({ proc });
    nvim.on('request', (method, args, resp) => {
      requests.push({ method, args });
      resp.send(`received ${method}(${args})`);
    });
    nvim.on('notification', (method, args) => {
      notifications.push({ method, args });
    });
    await nvim.apiPromise;

    done();
  });

  beforeEach(() => {
    requests = [];
    notifications = [];
  });

  it('can send requests and receive response', async () => {
    const result = await nvim.eval('{"k1": "v1", "k2": 2}');
    expect(result).toEqual({ k1: 'v1', k2: 2 });
  });

  it('can receive requests and send responses', async () => {
    const res = await nvim.eval('rpcrequest(1, "request", 1, 2, 3)');
    expect(res).toEqual('received request(1,2,3)');
    expect(requests).toEqual(
      [{ method: 'request', args: [1, 2, 3] }]
    );
    expect(notifications).toEqual([]);
  });

  it('can receive notifications', async () => {
    const res = await nvim.eval('rpcnotify(1, "notify", 1, 2, 3)');
    expect(res).toEqual(1);
    expect(requests).toEqual([]);
    return new Promise((resolve) => setImmediate(() => {
      expect(notifications).toEqual([{ method: 'notify', args: [1, 2, 3] }]);
      resolve();
    }));
  });

  it('can deal with custom types', async () => {
    await nvim.command('vsp');
    await nvim.command('vsp');
    await nvim.command('vsp');
    const windows = await nvim.listWins();

    expect(windows.length).toEqual(4);
    expect(windows[0] instanceof nvim.Window).toEqual(true);
    expect(windows[1] instanceof nvim.Window).toEqual(true);

    await nvim.setCurrentWin(windows[2]);
    const win = await nvim.getCurrentWin();

    expect(win).not.toEqual(windows[0]);
    expect(win).toEqual(windows[2]);

    const buf = await nvim.getCurrentBuf();
    expect(buf instanceof nvim.Buffer).toEqual(true);

    const lines = await buf.getLines(0, -1, true);
    expect(lines).toEqual(['']);

    await buf.setLines(0, -1, true, ['line1', 'line2']);
    const newLines = await buf.getLines(0, -1, true);
    expect(newLines).toEqual(['line1', 'line2']);
  });

  it('emits "disconnect" after quit', done => {
    nvim.on('disconnect', done);
    nvim.quit();
    // Event doesn't actually emit when we quit nvim, but when the child process is killed
    proc.kill();
  });
});
<|endoftext|>


<commit_before>import { cleanPath } from '../util/path'
import { isSameRoute, isIncludedRoute } from '../util/route'
import { normalizeLocation } from '../util/location'

export default {
  name: 'router-link',
  props: {
    to: {
      type: [String, Object],
      required: true
    },
    tag: {
      type: String,
      default: 'a'
    },
    exact: Boolean,
    append: Boolean,
    replace: Boolean,
    activeClass: String
  },
  render (h) {
    const router = this.$router
    const current = this.$route
    const to = normalizeLocation(this.to, current, this.append)
    const resolved = router.match(to)
    const fullPath = resolved.redirectedFrom || resolved.fullPath
    const base = router.history.base
    const href = base ? cleanPath(base + fullPath) : fullPath
    const classes = {}
    const activeClass = this.activeClass || router.options.linkActiveClass || 'router-link-active'
    classes[activeClass] = this.exact
      ? isSameRoute(current, to)
      : isIncludedRoute(current, to)

    const data = {
      class: classes,
      on: {
        click: (e) => {
          e.preventDefault()
          if (this.replace) {
            router.replace(to)
          } else {
            router.push(to)
          }
        }
      }
    }

    if (this.tag === 'a') {
      data.attrs = { href }
    } else {
      // find the first <a> child and apply href
      const a = findAnchor(this.$slots.default)
      if (a) {
        const aData = a.data || (a.data = {})
        const aAttrs = aData.attrs || (aData.attrs = {})
        aAttrs.href = href
      }
    }

    return h(this.tag, data, this.$slots.default)
  }
}

function findAnchor (children) {
  if (children) {
    let child
    for (let i = 0; i < children.length; i++) {
      child = children[i]
      if (child.tag === 'a') {
        return child
      }
      if (child.children && (child = findAnchor(child.children))) {
        return child
      }
    }
  }
}
<commit_msg>fix named routes active matching (fix #622)<commit_after>import { cleanPath } from '../util/path'
import { isSameRoute, isIncludedRoute } from '../util/route'
import { normalizeLocation } from '../util/location'

export default {
  name: 'router-link',
  props: {
    to: {
      type: [String, Object],
      required: true
    },
    tag: {
      type: String,
      default: 'a'
    },
    exact: Boolean,
    append: Boolean,
    replace: Boolean,
    activeClass: String
  },
  render (h) {
    const router = this.$router
    const current = this.$route
    const to = normalizeLocation(this.to, current, this.append)
    const resolved = router.match(to)
    const fullPath = resolved.redirectedFrom || resolved.fullPath
    const base = router.history.base
    const href = base ? cleanPath(base + fullPath) : fullPath
    const classes = {}
    const activeClass = this.activeClass || router.options.linkActiveClass || 'router-link-active'
    const compareTarget = to.path ? to : resolved
    classes[activeClass] = this.exact
      ? isSameRoute(current, compareTarget)
      : isIncludedRoute(current, compareTarget)

    const data = {
      class: classes,
      on: {
        click: (e) => {
          e.preventDefault()
          if (this.replace) {
            router.replace(to)
          } else {
            router.push(to)
          }
        }
      }
    }

    if (this.tag === 'a') {
      data.attrs = { href }
    } else {
      // find the first <a> child and apply href
      const a = findAnchor(this.$slots.default)
      if (a) {
        const aData = a.data || (a.data = {})
        const aAttrs = aData.attrs || (aData.attrs = {})
        aAttrs.href = href
      }
    }

    return h(this.tag, data, this.$slots.default)
  }
}

function findAnchor (children) {
  if (children) {
    let child
    for (let i = 0; i < children.length; i++) {
      child = children[i]
      if (child.tag === 'a') {
        return child
      }
      if (child.children && (child = findAnchor(child.children))) {
        return child
      }
    }
  }
}
<|endoftext|>


<commit_before><commit_msg>added id to the side-buttons <li><commit_after><|endoftext|>


<commit_before>const ContextBuilder = require('./ContextBuilder');

module.exports = Action;

function Action(options) {
    const fn = options.execute;
    if (typeof(fn) !== 'function')
        throw new InvalidExecuteFunctionError(fn);

    fn.cache = options.cache;
    fn.validator = options.validator;
    fn.criteriaBuilder = options.criteriaBuilder;

    fn.prepare = function () {
        const self = this;

        return (req, res) => {
            // 1. Generate a context from the middleware, req : res
            const context = new ContextBuilder(self.criteriaBuilder).build(req, res);

            // 2. If the action has been configured with a cache action
            // try to get the cache entry from the req's url.
            if (Action.cache && self.cache && self.cache.store) {
                const entry = Action.cache.get(req.originalUrl);
                if (entry)
                    return context._submit(entry);
            }
            // 3. else if the cache has been configured to bust some entries..
            else if (Action.cache && self.cache && self.cache.bust)
                Action.cache.remove(self.cache.bust);

            // >>> Idylle FLOW :
            // 1. Executing the action
            // 2. Setting a cache entry if configured to do so.
            // 3. Responding to the client.
            // 4. Handling the error and responding to the client.
            self(context)
                .then(setCache)
                .then(context._submit)
                .catch(res.error);

            function setCache(data) {
                if (self.cache && self.cache.store)
                    self.cache.set(self.cache, req.originalUrl, data);

                return data;
            }
        };
    };

    fn.prepare = fn.prepare.bind(fn);

    return fn
}

function InvalidExecuteFunctionError(fn) {
    this.name = 'InvalidExecuteFunctionError';
    this.message = `expecting function got ${typeof fn}`
}

InvalidExecuteFunctionError.prototype = Object.create(Error.prototype);<commit_msg>fixed invalid reference for cache in action<commit_after>const ContextBuilder = require('./ContextBuilder');

module.exports = Action;

function Action(options) {
    const fn = options.execute;
    if (typeof(fn) !== 'function')
        throw new InvalidExecuteFunctionError(fn);

    fn.cache = options.cache;
    fn.validator = options.validator;
    fn.criteriaBuilder = options.criteriaBuilder;

    fn.prepare = function () {
        const self = this;

        return (req, res) => {
            // 1. Generate a context from the middleware, req : res
            const context = new ContextBuilder(self.criteriaBuilder).build(req, res);

            // 2. If the action has been configured with a cache action
            // try to get the cache entry from the req's url.
            if (Action.cache && self.cache && self.cache.store) {
                const entry = Action.cache.get(req.originalUrl);
                if (entry)
                    return context._submit(entry);
            }
            // 3. else if the cache has been configured to bust some entries..
            else if (Action.cache && self.cache && self.cache.bust)
                Action.cache.remove(self.cache.bust);

            // >>> Idylle FLOW :
            // 1. Executing the action
            // 2. Setting a cache entry if configured to do so.
            // 3. Responding to the client.
            // 4. Handling the error and responding to the client.
            self(context)
                .then(setCache)
                .then(context._submit)
                .catch(res.error);

            function setCache(data) {
                if (Action.cache && self.cache && self.cache.store)
                    Action.cache.set(self.cache, req.originalUrl, data);

                return data;
            }
        };
    };

    fn.prepare = fn.prepare.bind(fn);

    return fn
}

function InvalidExecuteFunctionError(fn) {
    this.name = 'InvalidExecuteFunctionError';
    this.message = `expecting function got ${typeof fn}`
}

InvalidExecuteFunctionError.prototype = Object.create(Error.prototype);<|endoftext|>