<commit_before>(function ($) {
    $.fn.uniformHeight = function () {
        var maxHeight = 0,
            wrapper,
            wrapperHeight;
        return this.each(function () {
            // Applying a wrapper to the contents of the current element to get reliable height
            wrapper = $(this).wrapInner('
').children('.wrapper');
            wrapperHeight = wrapper.outerHeight();
            maxHeight = Math.max(maxHeight, wrapperHeight);
            // Remove the wrapper
            wrapper.children().unwrap();
        }).height(maxHeight);
    }
})(jQuery);

var addDescriptionSlider = function(){
    $('.liz-project-img').parent().mouseenter(function(){
      var self = $(this);
      self.find('.liz-project-desc').slideDown();
      self.css('cursor','pointer');
    }).mouseleave(function(){
      var self = $(this);
      self.find('.liz-project-desc').hide();
    }).click(function(){
      var self = $(this);
      window.location = self.parent().find('a.liz-project-view').attr('href');
      return false;
    });
}

var resizeThumbnails = function(){
    $(".thumbnail h5").height('auto');
    $('.thumbnails').height('auto');
    $('.thumbnails').each(function () {
        $(this).find('.thumbnail h5').height(Math.max.apply(null, $(this).find('.thumbnail h5').map(function() { return $(this).height(); })));
        $(this).find('.thumbnail').uniformHeight();
    });
}

var searchProjects = function(){
    // Hide search if there are no projects
    if ($("#content.container li .liz-project-title").length === 0) {
        $("#search-project").hide();
        return;
    }

    // Activate tooltips
    $('#toggle-search, #search-project').tooltip();

    // Handle keywords/title toggle
    $('#toggle-search').click(function(){
        // Reboot search
        $('#search-project').val('');
        $("#content.container .liz-repository-project-item").show();
        $("#content.container .liz-repository-title").show();

        if ($(this).text() === '#'){
            $(this).text('T');

            $('.project-keyword').addClass('hide');
            $('#search-project-keywords-selected').text('');
        }else{
            $(this).text('#');
        }
    });

    var onlyUnique = function (value, index, self) {
        return self.indexOf(value) === index;
    }

    // Get unique keywords for visible projects
    var getProjectsKeywords = function() {
        var keywordList = [];
        var selector = '.liz-repository-project-item :visible .keywordList';

        $(selector).each(function () {
            if ($(this).text() !== '') {
                keywordList = keywordList.concat($(this).text().toUpperCase().split(', '));
            }
        });

        return keywordList.filter(onlyUnique);
    }

    // Function to show only projects with selected keywords
    var filterProjectsBySelectedKeywords = function () {
        var selectedKeywords = getSelectedKeywords();

        if (selectedKeywords.length === 0) {
            $("#content.container .liz-repository-project-item").show();
            $("#content.container .liz-repository-title").show();
        } else {
            // Hide everything then show projects and titles corresponding to the selected keywords
            $("#content.container .liz-repository-project-item").hide();
            $("#content.container .liz-repository-title").hide();

            // Show project when its keywords match all keywords in selectedKeywords
            $('.keywordList').each(function () {
                var keywordList = $(this).text().toUpperCase().split(', ');

                var showProject = selectedKeywords.every(function (currentValue) {
                    return (keywordList.indexOf(currentValue) !== -1);
                });

                if (showProject) {
                    $(this).closest('.liz-repository-project-item').show();
                    $(this).closest('.liz-repository-project-list').prev('.liz-repository-title').show();
                }
            });
        }
    }

    // Returns array of selected keywords
    var getSelectedKeywords = function () {
        var selectedKeywords = [];
        $('#search-project-keywords-selected .keyword-label').each(function () {
            selectedKeywords.push($(this).text().toUpperCase());
        });

        return selectedKeywords;
    }

    var unHighlightkeywords = function (){
        $('#search-project-result .project-keyword').each(function () {
            $(this).text($(this).text());
        });
    }

    // Display keywords based on displayed projects
    var displayRemainingKeywords = function () {
        var projectKeywords = getProjectsKeywords();
        var selectedKeywords = getSelectedKeywords();

        // Hide all keywords
        $('#search-project-result .project-keyword').addClass('hide');

        for (var index = 0; index < projectKeywords.length; index++) {
            if (selectedKeywords.indexOf(projectKeywords[index]) === -1) {
                $('#search-project-result .project-keyword').filter(function () {
                    return $(this).text().toUpperCase() === projectKeywords[index];
                }).removeClass('hide');
            }
        }
        unHighlightkeywords();
    }

    var uniqueKeywordList = getProjectsKeywords();

    var keywordsHTML = '';
    for (var index = 0; index < uniqueKeywordList.length; index++) {
        keywordsHTML += '' + uniqueKeywordList[index].toLowerCase() + '';
    }
    $('#search-project-result').html(keywordsHTML);

    // Add click handler on project keywords
    $('.project-keyword').click(function(){
        // Move keyword in #search-project-keywords-selected
        $('#search-project-keywords-selected').append('' + $(this).text() +'x');
        // Add close event
        $('#search-project-keywords-selected .remove-keyword').click(function(){
            $(this).parent().remove();
            filterProjectsBySelectedKeywords();
            if ($('#search-project-keywords-selected .remove-keyword').length === 0){
                $('#search-project-result .project-keyword').addClass('hide');
            }else{
                displayRemainingKeywords();
            }
        });
        // Hide projects then display projects with selected keyword
        filterProjectsBySelectedKeywords();
        // Empty search input
        $('#search-project').val('')
        // Display remaining keywords for visible projects not yet selected
        displayRemainingKeywords();
    });

    // Handle search
    $("#search-project").keyup(function () {
        // Scroll to projects
        $('html').animate({
            scrollTop: $("#anchor-top-projects").offset().top - $('#header').height()
        }, 500);

        // Search by keywords
        if ($('#toggle-search').text() === '#'){
            var searchedTerm = this.value.trim().toUpperCase();

            displayRemainingKeywords();
            if (searchedTerm === '' && getSelectedKeywords().length === 0) {
                $('#search-project-result .project-keyword').addClass('hide');
            } else {
                $('#search-project-result .project-keyword:visible').each(function () {
                    var keyword = $(this).text().toUpperCase();
                    // Set keyword visibility and bold on string part found
                    if (keyword.indexOf(searchedTerm) !== -1) {
                        var re = new RegExp(searchedTerm, "g");
                        var keywordHighlighted = keyword.replace(re, '' + searchedTerm + '');
                        $(this).html(keywordHighlighted.toLowerCase());
                    } else {
                        $(this).addClass('hide');
                    }
                });
            }
        }else{ // Search by title
            var val = $.trim(this.value).toUpperCase();
            // If the search bar is empty, show everything
            if (val === "") {
                $("#content.container .liz-repository-project-item").show();
                $("#content.container .liz-repository-title").show();
            }
            // Hide everything then show projects and titles corresponding to the search bar
            else {
                $("#content.container .liz-repository-project-item").hide();
                $("#content.container .liz-repository-title").hide();

                val = val.toUpperCase();
                $("#content.container li .liz-project-title").filter(function () {
                    return -1 != $(this).text().toUpperCase().indexOf(val);
                }).closest('.liz-repository-project-item').show();

                $("#content.container li .liz-project-title").filter(function () {
                    return -1 != $(this).text().toUpperCase().indexOf(val);
                }).closest('.liz-repository-project-list').prev('.liz-repository-title').show();

            }
        }
    });
}

$( window ).load(function() {
    addDescriptionSlider();
    resizeThumbnails();
    searchProjects();
});

$(window).resize(function () {
    resizeThumbnails();
});
<commit_msg>Fix https://github.com/3liz/lizmap-web-client/pull/1382#issuecomment-553876385<commit_after>(function ($) {
    $.fn.uniformHeight = function () {
        var maxHeight = 0,
            wrapper,
            wrapperHeight;
        return this.each(function () {
            // Applying a wrapper to the contents of the current element to get reliable height
            wrapper = $(this).wrapInner('
').children('.wrapper');
            wrapperHeight = wrapper.outerHeight();
            maxHeight = Math.max(maxHeight, wrapperHeight);
            // Remove the wrapper
            wrapper.children().unwrap();
        }).height(maxHeight);
    }
})(jQuery);

var addDescriptionSlider = function(){
    $('.liz-project-img').parent().mouseenter(function(){
      var self = $(this);
      self.find('.liz-project-desc').slideDown();
      self.css('cursor','pointer');
    }).mouseleave(function(){
      var self = $(this);
      self.find('.liz-project-desc').hide();
    }).click(function(){
      var self = $(this);
      window.location = self.parent().find('a.liz-project-view').attr('href');
      return false;
    });
}

var resizeThumbnails = function(){
    $(".thumbnail h5").height('auto');
    $('.thumbnails').height('auto');
    $('.thumbnails').each(function () {
        $(this).find('.thumbnail h5').height(Math.max.apply(null, $(this).find('.thumbnail h5').map(function() { return $(this).height(); })));
        $(this).find('.thumbnail').uniformHeight();
    });
}

var searchProjects = function(){
    // Hide search if there are no projects
    if ($("#content.container li .liz-project-title").length === 0) {
        $("#search").hide();
        return;
    }

    // Activate tooltips
    $('#toggle-search, #search-project').tooltip();

    // Handle keywords/title toggle
    $('#toggle-search').click(function(){
        // Reboot search
        $('#search-project').val('');
        $("#content.container .liz-repository-project-item").show();
        $("#content.container .liz-repository-title").show();

        if ($(this).text() === '#'){
            $(this).text('T');

            $('.project-keyword').addClass('hide');
            $('#search-project-keywords-selected').text('');
        }else{
            $(this).text('#');
        }
    });

    var onlyUnique = function (value, index, self) {
        return self.indexOf(value) === index;
    }

    // Get unique keywords for visible projects
    var getProjectsKeywords = function() {
        var keywordList = [];
        var selector = '.liz-repository-project-item :visible .keywordList';

        $(selector).each(function () {
            if ($(this).text() !== '') {
                keywordList = keywordList.concat($(this).text().toUpperCase().split(', '));
            }
        });

        return keywordList.filter(onlyUnique);
    }

    // Function to show only projects with selected keywords
    var filterProjectsBySelectedKeywords = function () {
        var selectedKeywords = getSelectedKeywords();

        if (selectedKeywords.length === 0) {
            $("#content.container .liz-repository-project-item").show();
            $("#content.container .liz-repository-title").show();
        } else {
            // Hide everything then show projects and titles corresponding to the selected keywords
            $("#content.container .liz-repository-project-item").hide();
            $("#content.container .liz-repository-title").hide();

            // Show project when its keywords match all keywords in selectedKeywords
            $('.keywordList').each(function () {
                var keywordList = $(this).text().toUpperCase().split(', ');

                var showProject = selectedKeywords.every(function (currentValue) {
                    return (keywordList.indexOf(currentValue) !== -1);
                });

                if (showProject) {
                    $(this).closest('.liz-repository-project-item').show();
                    $(this).closest('.liz-repository-project-list').prev('.liz-repository-title').show();
                }
            });
        }
    }

    // Returns array of selected keywords
    var getSelectedKeywords = function () {
        var selectedKeywords = [];
        $('#search-project-keywords-selected .keyword-label').each(function () {
            selectedKeywords.push($(this).text().toUpperCase());
        });

        return selectedKeywords;
    }

    var unHighlightkeywords = function (){
        $('#search-project-result .project-keyword').each(function () {
            $(this).text($(this).text());
        });
    }

    // Display keywords based on displayed projects
    var displayRemainingKeywords = function () {
        var projectKeywords = getProjectsKeywords();
        var selectedKeywords = getSelectedKeywords();

        // Hide all keywords
        $('#search-project-result .project-keyword').addClass('hide');

        for (var index = 0; index < projectKeywords.length; index++) {
            if (selectedKeywords.indexOf(projectKeywords[index]) === -1) {
                $('#search-project-result .project-keyword').filter(function () {
                    return $(this).text().toUpperCase() === projectKeywords[index];
                }).removeClass('hide');
            }
        }
        unHighlightkeywords();
    }

    var uniqueKeywordList = getProjectsKeywords();

    var keywordsHTML = '';
    for (var index = 0; index < uniqueKeywordList.length; index++) {
        keywordsHTML += '' + uniqueKeywordList[index].toLowerCase() + '';
    }
    $('#search-project-result').html(keywordsHTML);

    // Add click handler on project keywords
    $('.project-keyword').click(function(){
        // Move keyword in #search-project-keywords-selected
        $('#search-project-keywords-selected').append('' + $(this).text() +'x');
        // Add close event
        $('#search-project-keywords-selected .remove-keyword').click(function(){
            $(this).parent().remove();
            filterProjectsBySelectedKeywords();
            if ($('#search-project-keywords-selected .remove-keyword').length === 0){
                $('#search-project-result .project-keyword').addClass('hide');
            }else{
                displayRemainingKeywords();
            }
        });
        // Hide projects then display projects with selected keyword
        filterProjectsBySelectedKeywords();
        // Empty search input
        $('#search-project').val('')
        // Display remaining keywords for visible projects not yet selected
        displayRemainingKeywords();
    });

    // Handle search
    $("#search-project").keyup(function () {
        // Scroll to projects
        $('html').animate({
            scrollTop: $("#anchor-top-projects").offset().top - $('#header').height()
        }, 500);

        // Search by keywords
        if ($('#toggle-search').text() === '#'){
            var searchedTerm = this.value.trim().toUpperCase();

            displayRemainingKeywords();
            if (searchedTerm === '' && getSelectedKeywords().length === 0) {
                $('#search-project-result .project-keyword').addClass('hide');
            } else {
                $('#search-project-result .project-keyword:visible').each(function () {
                    var keyword = $(this).text().toUpperCase();
                    // Set keyword visibility and bold on string part found
                    if (keyword.indexOf(searchedTerm) !== -1) {
                        var re = new RegExp(searchedTerm, "g");
                        var keywordHighlighted = keyword.replace(re, '' + searchedTerm + '');
                        $(this).html(keywordHighlighted.toLowerCase());
                    } else {
                        $(this).addClass('hide');
                    }
                });
            }
        }else{ // Search by title
            var val = $.trim(this.value).toUpperCase();
            // If the search bar is empty, show everything
            if (val === "") {
                $("#content.container .liz-repository-project-item").show();
                $("#content.container .liz-repository-title").show();
            }
            // Hide everything then show projects and titles corresponding to the search bar
            else {
                $("#content.container .liz-repository-project-item").hide();
                $("#content.container .liz-repository-title").hide();

                val = val.toUpperCase();
                $("#content.container li .liz-project-title").filter(function () {
                    return -1 != $(this).text().toUpperCase().indexOf(val);
                }).closest('.liz-repository-project-item').show();

                $("#content.container li .liz-project-title").filter(function () {
                    return -1 != $(this).text().toUpperCase().indexOf(val);
                }).closest('.liz-repository-project-list').prev('.liz-repository-title').show();

            }
        }
    });
}

$( window ).load(function() {
    addDescriptionSlider();
    resizeThumbnails();
    searchProjects();
});

$(window).resize(function () {
    resizeThumbnails();
});
<|endoftext|>


<commit_before>/*jshint node:true*/
'use strict';

module.exports = function(/* environment, appConfig */) {
  return {
    gptIframeJail: false,
    gptIframeRootUrl: ''
  };
};
<commit_msg>remove useless return from config/environment.js<commit_after>/*jshint node:true*/
'use strict';

module.exports = function(/* environment, appConfig */) {
  return { };
};
<|endoftext|>


<commit_before>import db from './request';

const baseUri = "http://reactpeoplesearch.azurewebsites.net/";
//const baseUri = "http://localhost:59206/";

class PersonApi {
    static getAll(data) {
        return db.doGet(baseUri + 'api/Person/GetPeople', data);
    }
    static save(person) {
        return db.doPost(baseUri+'api/Person/Save', person);
    }
}

export default PersonApi;<commit_msg>cleanup<commit_after>import db from './request';

const baseUri = "http://reactpeoplesearchapi.azurewebsites.net/";
//const baseUri = "http://localhost:59206/";

class PersonApi {
    static getAll(data) {
        return db.doGet(baseUri + 'api/Person/GetPeople', data);
    }
    static save(person) {
        return db.doPost(baseUri+'api/Person/Save', person);
    }
}

export default PersonApi;<|endoftext|>


<commit_before>/* global process */
import cluster from 'cluster';
import WebSocket from 'uws';
import uuid from 'uuid/v4';
import hash from 'string-hash';
import { URL } from 'url';

import { Rustler, Stream, BannedStream, User } from '../db';
const isValidAdvancedUrl = require('../util/is-valid-advanced-url')(URL);

const debug = require('debug')('overrustle:websocket');

// Services whose channel names are case insensitive. Channel names on these
// services are lowercased in the database in order to prevent duplicates from
// arising due to differences in casing. For example, if one user is watching
// "twitch/Destiny", and another is watching "twitch/destiny", we track this as
// being two users watching "twitch/destiny", since they are both actually
// watching the same stream on this particular service. An example of a
// case-sensitive service would be YouTube.
const CASE_INSENSITIVE_SERVICES = [
  'angelthump',
  'hitbox',
  'twitch',
  'ustream',
];

export default function makeWebSocketServer(server) {

  const wss = new WebSocket.Server({ server });
  const rustler_sockets = new Map(); // Rustler.id => websocket map

  // update all rustlers for this stream, and the lobby
  let updateRustlers = async stream_id => {
    // ensure we're actually updating a stream and not the lobby on accident
    if (!stream_id) {
      return;
    }
    const [ rustlers, rustler_count, stream ] = await Promise.all([ // we can do this in parallel
      // send this update to everyone on this stream and everyone in the lobby
      Rustler.findAll({
        where: {
          $or: [
            { stream_id },
            { stream_id: null },
          ],
        },
      }),
      // need to get the amount of rustlers watching this stream too
      Stream.findRustlersFor(stream_id),
      Stream.findById(stream_id),
    ]);

    // send update to these rustlers
    debug(`updating the ${rustlers.length} rustlers on stream ${stream.service}/${stream.channel} (${stream_id})`);
    for (const rustler of rustlers) {
      const ws = rustler_sockets.get(rustler.id);
      // check that we have this rustler, they might be using another websocket server
      if (ws) {
        ws.send(JSON.stringify(['RUSTLERS_SET', stream_id, rustler_count]));
      }
    }

    // get rid of the stream if no one's watching it anymore
    if (rustler_count === 0) {
      await Stream.destroy({ where: { id: stream_id } });
    }
  };
  const updateLobby = async () => {
    // get all streams and count rustlers
    const streams = await Stream.findAllWithRustlers();
    // send `STREAMS_SET`
    for (const ws of rustler_sockets.values()) {
      ws.send(JSON.stringify([
        'STREAMS_SET',
        streams,
      ]));
    }
  };
  // need to set up some relaying if we're multi-processing
  // set up basic eventing between master and slave
  if (cluster.isWorker) {
    // define events to listen to from master
    const events = {
      updateRustlers,
      updateLobby,
    };
    // listen for events from master
    process.on('message', message => {
      debug('got message from master %j', message);
      if (message.event) {
        const { event, args } = message;
        const handler = events[event];
        if (handler) {
          debug(`handling event from master "${event}"`);
          handler(...(args || []));
        }
      }
    });
    // make our `updateRustlers` function notify master instead
    updateRustlers = (...args) => process.send({
      type: 'forward',
      payload: {
        event: 'updateRustlers',
        args: args,
      },
    });
  }

  const wsEventHandlers = {
    // get information about a stream
    // getStream('fee55b7e-fac7-46b8-a5dd-4e86b106e846');
    async getStream(id, stream_id) {
      const ws = rustler_sockets.get(id);
      try {
        const stream = await Stream.findById(stream_id);
        if (!stream) {
          throw new Error(`Stream "${stream_id}" not found`);
        }
        const rustlers = await Stream.findRustlersFor(stream.id);
        // send `SET_STREAM` acknowledgement
        ws.send(JSON.stringify(['STREAM_GET', {
          ...stream.toJSON(),
          rustlers,
        }]));
      }
      catch (err) {
        debug(`Failed to respond to \`getStream\` event from rustler ${id}`, err);
        ws.send(JSON.stringify([
          'ERR',
          err.message,
        ]));
      }
    },

    // set the user's stream to [channel, service], or id, or null (for lobby)
    // eg, on client:
    // setStream('destiny', 'twitch');
    // setStream('destiny'); // where `destiny` is the name of the overrustle user
    // setStream(null); // lobby
    async setStream(id, channel, service) {
      const ws = rustler_sockets.get(id);
      try {
        // Basic channel name sanitization.
        if ((service === 'advanced' && !isValidAdvancedUrl(channel))
          || (service !== 'advanced' && !/^[a-zA-Z0-9\-_]{1,64}$/.test(channel))) {
          debug(`rejecting ${service}/${channel} as invalid`);
          channel = null;
        }

        if (service === 'advanced') {
          channel = new URL(channel).href;
        }

        // get our rustler and their stream from the db
        const [ rustler ] = await Rustler.findAll({
          where: { id },
          limit: 1,
          include: [
            {
              model: Stream,
              as: 'stream',
            },
          ],
        });
        // get our stream
        let stream;
        let bannedStream;
        if (!channel) {
          // we must be setting our stream to null (for lobby)
          stream = null;
        }
        else {
          if (!service) {
            // must be setting by overrustle user
            ([ stream ] = await Stream.findAll({
              where: { overrustle_id: channel },
              include: [
                {
                  model: User,
                  as: 'overrustle',
                },
              ],
              limit: 1,
            }));
            if (!stream) {
              const user = await User.findById(channel);
              // ensure that `channel` is a real overrustle user
              if (!user) {
                throw new Error(`User "${channel}" does not exist`);
              }
              // ensure that the user has a channel
              if (!user.channel || !user.service) {
                throw new Error(`User "${channel}" does not have a channel`);
              }
              stream = await Stream.create({
                id: hash(`${user.service}/${user.channel}`),
                channel: user.channel,
                service: user.service,
                overrustle_id: channel,
              });
            }
          }
          else {
            if (CASE_INSENSITIVE_SERVICES.includes(service)) {
              channel = channel.toLowerCase();
            }

            // must be setting by channel-service pair
            ([ stream ] = await Stream.findAll({
              where: { channel, service },
              limit: 1,
            }));
            if (!stream) {
              stream = await Stream.create({
                id: hash(`${service}/${channel}`),
                channel,
                service,
              });
            }
          }
        }
        const prevStream = rustler.stream ? rustler.stream.toJSON() : null;
        let banned = false;
        if (stream) {
          ([ bannedStream ] = await BannedStream.findAll({
            where: { channel: stream.channel, service: stream.service },
            limit: 1,
          }));
          if (bannedStream) {
            banned = true;
          }
        }
        if (stream && !banned) {
          debug('rustler set stream %j => %j', prevStream, stream.toJSON());
          // update rustler
          await rustler.update({ stream_id: stream.id });
          const rustlers = await Stream.findRustlersFor(stream.id);
          // send `SET_STREAM` acknowledgement
          ws.send(JSON.stringify(['STREAM_SET', {
            ...stream.toJSON(),
            rustlers,
          }]));
          // update everyone else
          await updateRustlers(stream.id);
        }
        else {
          debug('rustler set stream %j => null', prevStream);
          // update rustler
          await rustler.update({ stream_id: null });
          if (banned) {
            await stream.destroy();
            ws.send(JSON.stringify([
              'STREAM_BANNED',
              null,
            ]));
          }
          // get all streams and count rustlers
          const streams = await Stream.findAllWithRustlers();
          // send `SET_STREAM` acknowledgement
          ws.send(JSON.stringify([
            'STREAM_SET',
            null,
          ]));
          // send `STREAMS_SET` now (because they're in the lobby)
          ws.send(JSON.stringify([
            'STREAMS_SET',
            streams,
          ]));
        }
        // update everyone else
        if (prevStream) {
          await updateRustlers(prevStream.id);
        }
      }
      catch (err) {
        debug(`Failed to respond to \`setStream\` event from rustler ${id}`, err);
        ws.send(JSON.stringify([
          'ERR',
          err.message,
        ]));
      }
    },
    async disconnect(id) {
      try {
        rustler_sockets.delete(id);
        const rustler = await Rustler.findById(id);

        // This rustler is not in the database, so nothing needs to be done here.
        if (!rustler) {
          debug('no known rustler with id', id);
          return;
        }

        debug('rustler disconnect %j', rustler.toJSON());
        const { stream_id } = rustler;
        await rustler.destroy();
        if (stream_id) {
          await updateRustlers(stream_id);
        }
      }
      catch (err) {
        debug('Error disconnecting', err);
      }
    },
  };

  wss.on('connection', ws => {
    try {
      const id = uuid();
      rustler_sockets.set(id, ws);
      const rustler_create = Rustler.create({ id, stream_id: null });
      ws.on('message', async message => {
        // handle client 'ping', which is an empty string
        if (!message) {
          return;
        }
        try {
          const [ event, ...args ] = JSON.parse(message);
          const handler = wsEventHandlers[event];
          if (!handler) {
            throw new TypeError(`Invalid event "${event}"`);
          }
          // ensure we've created this rustler first
          await Promise.resolve(rustler_create);
          debug(`event [${event}] (${id})`);
          handler(id, ...args);
        }
        catch (err) {
          debug(`Failed to handle incoming websocket message:\n${message}\n`, err);
          ws.send(JSON.stringify([
            'ERR',
            err.message,
          ]));
        }
      });
      ws.on('close', () => wsEventHandlers.disconnect(id));
    }
    catch (err) {
      debug('Failed to handle incoming websocket connection', err);
    }
  });
}
<commit_msg>fix incorrect stream-rustlers count<commit_after>/* global process */
import cluster from 'cluster';
import WebSocket from 'uws';
import uuid from 'uuid/v4';
import hash from 'string-hash';
import { URL } from 'url';

import { Rustler, Stream, BannedStream, User } from '../db';
const isValidAdvancedUrl = require('../util/is-valid-advanced-url')(URL);

const debug = require('debug')('overrustle:websocket');

// Services whose channel names are case insensitive. Channel names on these
// services are lowercased in the database in order to prevent duplicates from
// arising due to differences in casing. For example, if one user is watching
// "twitch/Destiny", and another is watching "twitch/destiny", we track this as
// being two users watching "twitch/destiny", since they are both actually
// watching the same stream on this particular service. An example of a
// case-sensitive service would be YouTube.
const CASE_INSENSITIVE_SERVICES = [
  'angelthump',
  'hitbox',
  'twitch',
  'ustream',
];

export default function makeWebSocketServer(server) {

  const wss = new WebSocket.Server({ server });
  const rustler_sockets = new Map(); // Rustler.id => websocket map

  // update all rustlers for this stream, and the lobby
  let updateRustlers = async stream_id => {
    // ensure we're actually updating a stream and not the lobby on accident
    if (!stream_id) {
      return;
    }
    const [ rustlers, rustler_count, stream ] = await Promise.all([ // we can do this in parallel
      // send this update to everyone on this stream and everyone in the lobby
      Rustler.findAll({
        where: {
          $or: [
            { stream_id },
            { stream_id: null },
          ],
        },
      }),
      // need to get the amount of rustlers watching this stream too
      Stream.findRustlersFor(stream_id),
      Stream.findById(stream_id),
    ]);

    // send update to these rustlers
    debug(`updating the ${rustler_count} rustlers on stream ${stream.service}/${stream.channel} (${stream_id})`);
    for (const rustler of rustlers) {
      const ws = rustler_sockets.get(rustler.id);
      // check that we have this rustler, they might be using another websocket server
      if (ws) {
        ws.send(JSON.stringify(['RUSTLERS_SET', stream_id, rustler_count]));
      }
    }

    // get rid of the stream if no one's watching it anymore
    if (rustler_count === 0) {
      await Stream.destroy({ where: { id: stream_id } });
    }
  };
  const updateLobby = async () => {
    // get all streams and count rustlers
    const streams = await Stream.findAllWithRustlers();
    // send `STREAMS_SET`
    for (const ws of rustler_sockets.values()) {
      ws.send(JSON.stringify([
        'STREAMS_SET',
        streams,
      ]));
    }
  };
  // need to set up some relaying if we're multi-processing
  // set up basic eventing between master and slave
  if (cluster.isWorker) {
    // define events to listen to from master
    const events = {
      updateRustlers,
      updateLobby,
    };
    // listen for events from master
    process.on('message', message => {
      debug('got message from master %j', message);
      if (message.event) {
        const { event, args } = message;
        const handler = events[event];
        if (handler) {
          debug(`handling event from master "${event}"`);
          handler(...(args || []));
        }
      }
    });
    // make our `updateRustlers` function notify master instead
    updateRustlers = (...args) => process.send({
      type: 'forward',
      payload: {
        event: 'updateRustlers',
        args: args,
      },
    });
  }

  const wsEventHandlers = {
    // get information about a stream
    // getStream('fee55b7e-fac7-46b8-a5dd-4e86b106e846');
    async getStream(id, stream_id) {
      const ws = rustler_sockets.get(id);
      try {
        const stream = await Stream.findById(stream_id);
        if (!stream) {
          throw new Error(`Stream "${stream_id}" not found`);
        }
        const rustlers = await Stream.findRustlersFor(stream.id);
        // send `SET_STREAM` acknowledgement
        ws.send(JSON.stringify(['STREAM_GET', {
          ...stream.toJSON(),
          rustlers,
        }]));
      }
      catch (err) {
        debug(`Failed to respond to \`getStream\` event from rustler ${id}`, err);
        ws.send(JSON.stringify([
          'ERR',
          err.message,
        ]));
      }
    },

    // set the user's stream to [channel, service], or id, or null (for lobby)
    // eg, on client:
    // setStream('destiny', 'twitch');
    // setStream('destiny'); // where `destiny` is the name of the overrustle user
    // setStream(null); // lobby
    async setStream(id, channel, service) {
      const ws = rustler_sockets.get(id);
      try {
        // Basic channel name sanitization.
        if ((service === 'advanced' && !isValidAdvancedUrl(channel))
          || (service !== 'advanced' && !/^[a-zA-Z0-9\-_]{1,64}$/.test(channel))) {
          debug(`rejecting ${service}/${channel} as invalid`);
          channel = null;
        }

        if (service === 'advanced') {
          channel = new URL(channel).href;
        }

        // get our rustler and their stream from the db
        const [ rustler ] = await Rustler.findAll({
          where: { id },
          limit: 1,
          include: [
            {
              model: Stream,
              as: 'stream',
            },
          ],
        });
        // get our stream
        let stream;
        let bannedStream;
        if (!channel) {
          // we must be setting our stream to null (for lobby)
          stream = null;
        }
        else {
          if (!service) {
            // must be setting by overrustle user
            ([ stream ] = await Stream.findAll({
              where: { overrustle_id: channel },
              include: [
                {
                  model: User,
                  as: 'overrustle',
                },
              ],
              limit: 1,
            }));
            if (!stream) {
              const user = await User.findById(channel);
              // ensure that `channel` is a real overrustle user
              if (!user) {
                throw new Error(`User "${channel}" does not exist`);
              }
              // ensure that the user has a channel
              if (!user.channel || !user.service) {
                throw new Error(`User "${channel}" does not have a channel`);
              }
              stream = await Stream.create({
                id: hash(`${user.service}/${user.channel}`),
                channel: user.channel,
                service: user.service,
                overrustle_id: channel,
              });
            }
          }
          else {
            if (CASE_INSENSITIVE_SERVICES.includes(service)) {
              channel = channel.toLowerCase();
            }

            // must be setting by channel-service pair
            ([ stream ] = await Stream.findAll({
              where: { channel, service },
              limit: 1,
            }));
            if (!stream) {
              stream = await Stream.create({
                id: hash(`${service}/${channel}`),
                channel,
                service,
              });
            }
          }
        }
        const prevStream = rustler.stream ? rustler.stream.toJSON() : null;
        let banned = false;
        if (stream) {
          ([ bannedStream ] = await BannedStream.findAll({
            where: { channel: stream.channel, service: stream.service },
            limit: 1,
          }));
          if (bannedStream) {
            banned = true;
          }
        }
        if (stream && !banned) {
          debug('rustler set stream %j => %j', prevStream, stream.toJSON());
          // update rustler
          await rustler.update({ stream_id: stream.id });
          const rustlers = await Stream.findRustlersFor(stream.id);
          // send `SET_STREAM` acknowledgement
          ws.send(JSON.stringify(['STREAM_SET', {
            ...stream.toJSON(),
            rustlers,
          }]));
          // update everyone else
          await updateRustlers(stream.id);
        }
        else {
          debug('rustler set stream %j => null', prevStream);
          // update rustler
          await rustler.update({ stream_id: null });
          if (banned) {
            await stream.destroy();
            ws.send(JSON.stringify([
              'STREAM_BANNED',
              null,
            ]));
          }
          // get all streams and count rustlers
          const streams = await Stream.findAllWithRustlers();
          // send `SET_STREAM` acknowledgement
          ws.send(JSON.stringify([
            'STREAM_SET',
            null,
          ]));
          // send `STREAMS_SET` now (because they're in the lobby)
          ws.send(JSON.stringify([
            'STREAMS_SET',
            streams,
          ]));
        }
        // update everyone else
        if (prevStream) {
          await updateRustlers(prevStream.id);
        }
      }
      catch (err) {
        debug(`Failed to respond to \`setStream\` event from rustler ${id}`, err);
        ws.send(JSON.stringify([
          'ERR',
          err.message,
        ]));
      }
    },
    async disconnect(id) {
      try {
        rustler_sockets.delete(id);
        const rustler = await Rustler.findById(id);

        // This rustler is not in the database, so nothing needs to be done here.
        if (!rustler) {
          debug('no known rustler with id', id);
          return;
        }

        debug('rustler disconnect %j', rustler.toJSON());
        const { stream_id } = rustler;
        await rustler.destroy();
        if (stream_id) {
          await updateRustlers(stream_id);
        }
      }
      catch (err) {
        debug('Error disconnecting', err);
      }
    },
  };

  wss.on('connection', ws => {
    try {
      const id = uuid();
      rustler_sockets.set(id, ws);
      const rustler_create = Rustler.create({ id, stream_id: null });
      ws.on('message', async message => {
        // handle client 'ping', which is an empty string
        if (!message) {
          return;
        }
        try {
          const [ event, ...args ] = JSON.parse(message);
          const handler = wsEventHandlers[event];
          if (!handler) {
            throw new TypeError(`Invalid event "${event}"`);
          }
          // ensure we've created this rustler first
          await Promise.resolve(rustler_create);
          debug(`event [${event}] (${id})`);
          handler(id, ...args);
        }
        catch (err) {
          debug(`Failed to handle incoming websocket message:\n${message}\n`, err);
          ws.send(JSON.stringify([
            'ERR',
            err.message,
          ]));
        }
      });
      ws.on('close', () => wsEventHandlers.disconnect(id));
    }
    catch (err) {
      debug('Failed to handle incoming websocket connection', err);
    }
  });
}
<|endoftext|>


<commit_before>(function(){
  'use strict';
  angular.module('civic.states', ['ui.router'])
    .config(routesConfig);

  // @ngInject
  function routesConfig($stateProvider, $urlRouterProvider, $stickyStateProvider) {
    $stickyStateProvider.enableDebug(true);

    // static frontend pages
    $stateProvider
      .state('home', {
        url: '/home',
        controller: 'HomeCtrl',
        templateUrl: '/civic-client/pages/home.tpl.html'
      })
      .state('collaborate', {
        url: '/collaborate',
        controller: 'CollaborateCtrl',
        templateUrl: '/civic-client/pages/collaborate.tpl.html'
      })
      .state('help', {
        url: '/help',
        controller: 'HelpCtrl',
        templateUrl: '/civic-client/pages/help.tpl.html'
      })
      .state('contact', {
        url: '/contact',
        controller: 'ContactCtrl',
        templateUrl: '/civic-client/pages/contact.tpl.html'
      });

    // browse
    $stateProvider
      .state('browse', {
        url: '/browse',
        controller: 'BrowseCtrl',
        templateUrl: '/civic-client/views/browse/browse.tpl.html'
      });

    // EVENT VIEWS
    // gene, variant, evidence & associated activity views
    $stateProvider
      .state('events', {
        url: '/events',
        controller: 'EventsViewCtrl',
        templateUrl: '/civic-client/views/events/eventsView.tpl.html'
      })
      .state('events.genes', {
        abstract: true,
        url: '/genes/:geneId',
        controller: 'GenesViewCtrl',
        templateUrl: '/civic-client/views/events/genes/genesView.tpl.html',
        sticky: true

      })
      .state('events.genes.summary', {
        url: '', // blank url defaults to parent abstract state's url
        template: '<gene-summary class="col-xs-12"></gene-summary>',
        deepStateRedirect: true,
        sticky: true
      })
      .state('events.genes.talk', {
        url: '/talk',
        template: '<gene-talk></gene-talk>',
        sticky: true
      })
      .state('events.genes.edit', {
        url: '/edit',
        template: '<gene-edit class="col-xs-12"></gene-edit>',
        controller: 'GeneEditCtrl',
        sticky: true
      })
      .state('events.genes.summary.variants', {
        abstract: true,
        url: '/variants/:variantId',
        controller: 'VariantsViewCtrl',
        templateUrl: '/civic-client/views/events/variants/variantsView.tpl.html'
      })
      .state('events.genes.summary.variants.summary', {
        url: '',
        template: '<variant-summary></variant-summary>',
        sticky: true,
        deepStateRedirect: true
      })
      .state('events.genes.summary.variants.talk', {
        url:'/talk',
        template: '<variant-talk></variant-talk>',
        sticky: true
      })
      .state('events.genes.summary.variants.edit', {
        url:'/edit',
        template: '
Edit Variant {{ variant.name }}
',
        sticky: true
      })
      .state('events.genes.summary.variants.summary.evidence', {
        abstract: true,
        url: '/evidence/:evidenceId',
        controller: 'EvidenceViewCtrl',
        templateUrl: '/civic-client/views/events/evidence/evidenceView.tpl.html'
      })
      .state('events.genes.summary.variants.summary.evidence.summary',{
        url: '',
        template: '<evidence-summary></evidence-summary>',
        sticky: true,
        deepStateRedirect: true
      })
      .state('events.genes.summary.variants.summary.evidence.talk', {
        url: '/talk',
        template: '<evidence-talk></evidence-talk>',
        sticky: true
      })
      .state('events.genes.summary.variants.summary.evidence.edit', {
        url: '/edit',
        template: '
Edit Evidence {{ evidence.id }}
',
        sticky: true
      });

    // event edit
    // editing genes, variants, evidence

    // account, profile
    // viewing & editing user options, profile
    // route to home state if no state supplied
    $urlRouterProvider.otherwise('home');
  }
})();<commit_msg>fixed issue where deepStateRedirect on event.genes.summary.variants.summary would cause other tabs in variants tabs menu to cease working after evidence item was clicked.<commit_after>(function(){
  'use strict';
  angular.module('civic.states', ['ui.router'])
    .config(routesConfig);

  // @ngInject
  function routesConfig($stateProvider, $urlRouterProvider, $stickyStateProvider) {
    $stickyStateProvider.enableDebug(true);

    // static frontend pages
    $stateProvider
      .state('home', {
        url: '/home',
        controller: 'HomeCtrl',
        templateUrl: '/civic-client/pages/home.tpl.html'
      })
      .state('collaborate', {
        url: '/collaborate',
        controller: 'CollaborateCtrl',
        templateUrl: '/civic-client/pages/collaborate.tpl.html'
      })
      .state('help', {
        url: '/help',
        controller: 'HelpCtrl',
        templateUrl: '/civic-client/pages/help.tpl.html'
      })
      .state('contact', {
        url: '/contact',
        controller: 'ContactCtrl',
        templateUrl: '/civic-client/pages/contact.tpl.html'
      });

    // browse
    $stateProvider
      .state('browse', {
        url: '/browse',
        controller: 'BrowseCtrl',
        templateUrl: '/civic-client/views/browse/browse.tpl.html'
      });

    // EVENT VIEWS
    // gene, variant, evidence & associated activity views
    $stateProvider
      .state('events', {
        url: '/events',
        controller: 'EventsViewCtrl',
        templateUrl: '/civic-client/views/events/eventsView.tpl.html'
      })
      .state('events.genes', {
        abstract: true,
        url: '/genes/:geneId',
        controller: 'GenesViewCtrl',
        templateUrl: '/civic-client/views/events/genes/genesView.tpl.html'
      })
      .state('events.genes.summary', {
        url: '/summary',
        template: '<gene-summary class="col-xs-12"></gene-summary>',
        deepStateRedirect: true
      })
      .state('events.genes.talk', {
        url: '/talk',
        template: '<gene-talk></gene-talk>'
      })
      .state('events.genes.edit', {
        url: '/edit',
        template: '<gene-edit class="col-xs-12"></gene-edit>',
        controller: 'GeneEditCtrl'
      })
      .state('events.genes.summary.variants', {
        abtract: true,
        url: '/variants/:variantId',
        controller: 'VariantsViewCtrl',
        templateUrl: '/civic-client/views/events/variants/variantsView.tpl.html',
      })
      .state('events.genes.summary.variants.summary', {
        url: '/summary',
        template: '<variant-summary></variant-summary>',
        // deepStateRedirect: true
      })
      .state('events.genes.summary.variants.talk', {
        url:'/talk',
        template: '<variant-talk></variant-talk>'
      })
      .state('events.genes.summary.variants.edit', {
        url:'/edit',
        template: '
Edit Variant {{ variant.name }}
'
      })
      .state('events.genes.summary.variants.summary.evidence', {
        abstract: true,
        url: '/evidence/:evidenceId',
        controller: 'EvidenceViewCtrl',
        templateUrl: '/civic-client/views/events/evidence/evidenceView.tpl.html'
      })
      .state('events.genes.summary.variants.summary.evidence.summary',{
        url: '/summary',
        template: '<evidence-summary></evidence-summary>'
      })
      .state('events.genes.summary.variants.summary.evidence.talk', {
        url: '/talk',
        template: '<evidence-talk></evidence-talk>'
      })
      .state('events.genes.summary.variants.summary.evidence.edit', {
        url: '/edit',
        template: '
Edit Evidence {{ evidence.id }}
'
      });

    // event edit
    // editing genes, variants, evidence

    // account, profile
    // viewing & editing user options, profile
    // route to home state if no state supplied
    $urlRouterProvider.otherwise('home');
  }
})();<|endoftext|>


<commit_before>import PARAMETERS from '../store/parameters'
import { routing } from './routes'

const { VIEW, VIEW_CENTER, LAYERS, LEGEND, ZOOM, EMBED } = PARAMETERS

// This are the known broken legacy links
const legacyRoutes = [
  // https://www.parool.nl/amsterdam/kaart-met-onontplofte-bommen-in-amsterdam-nu-openbaar~a4539314/
  {
    from:
      '#?mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&lse=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://www.telegraaf.nl/nieuws/1256075/ligt-er-een-bom-uit-woii-in-je-achtertuin-met-deze-kaart-kom-je-er-achter
  {
    from:
      '#?mpb=topografie&mpz=11&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3815892:4.8626601&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&mpb=topografie&mpz=11&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3815892:4.8626601&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&lse=T&mpb=topografie&mpz=11&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3815892:4.8626601&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://www.amsterdamsdagblad.nl/gemeente/duizend-bommen-en-granaten-de-bommenkaart
  {
    from:
      '#?mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&lse=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://intranet.alliander.com/blog/${VIEW}/5359847/kaart-met-onontplofte-bommen-in-amsterdam
  {
    from:
      '#?mpb=topografie&mpz=14&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3889979:4.9094038&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '?_sp=144b47f5-2817-4a1f-888c-d1d1b69c89cb.1510908859477#?ate=T&mpb=topografie&mpz=14&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3889979:4.9094038&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://www.amsterdam.nl/ondernemen/biz/
  {
    from: '#?mpb=topografie&mpz=9&mpfs=T&mpo=biz::T&mpv=52.3676245:4.8804992&pgn=home&uvm=T',
    to: `${routing.data.path}?${VIEW}=kaart&${LAYERS}=biz%3A1&${LEGEND}=true&${ZOOM}=9`,
  },
  {
    from: '#?ate=T&mpb=topografie&mpz=9&mpfs=T&mpo=biz::T&mpv=52.3676245:4.8804992&pgn=home&uvm=T',
    to: `${routing.data.path}?${VIEW}=kaart&${EMBED}=true&${LAYERS}=biz%3A1&${LEGEND}=true&${ZOOM}=9`,
  },
  // home map
  {
    from: '#?mpb=topografie&mpz=11&mpfs=T&mpv=52.3731081:4.8932945&pgn=home&uvm=T',
    to: `${routing.data.path}?${VIEW}=kaart`,
  },
]
const shortUrls = [
  {
    from: '/themakaart/taxi',
    to: `${routing.data.path}?${VIEW}=kaart&${LAYERS}=oovtig%3A1%7Cvezips%3A1%7Cmzt%3A0%7Cslpb%3A1%7Cslpnb%3A1%7Cbgt%3A1%7Ctar%3A1%7Cpvrts%3A1%7Cpvrll%3A1%7Cpvrpr%3A1&${LEGEND}=true`,
  },
  {
    from: '/themakaart/veiligheid-en-overlast',
    to: `${routing.data.path}?${VIEW}=kaart&${LAYERS}=oovorlv%3A1%7Coovodlrs%3A1%7Coovoalg%3A1%7Coovctg%3A1%7Coovoalco%3A1&${LEGEND}=true`,
  },
  {
    from: '/datablog',
    to: 'https://amsterdam.github.io/datablog/',
  },
]

export const routesDictionary = [...legacyRoutes, ...shortUrls]

const resolveRedirects = () => {
  let routePath = routesDictionary.filter(r => r.from === window.location.pathname)
  if (window.location.hash.match(/#\?/g)) {
    routePath = routesDictionary.filter(r => r.from === window.location.hash)
    window.location.replace(routePath.length && routePath[0].to ? routePath[0].to : '/verplaatst/')
    return true
  }
  if (routePath.length) {
    window.location.replace(routePath[0].to)
    return true
  }

  return false
}

export default resolveRedirects
<commit_msg>Tweak order themakaart<commit_after>import PARAMETERS from '../store/parameters'
import { routing } from './routes'

const { VIEW, VIEW_CENTER, LAYERS, LEGEND, ZOOM, EMBED } = PARAMETERS

// This are the known broken legacy links
const legacyRoutes = [
  // https://www.parool.nl/amsterdam/kaart-met-onontplofte-bommen-in-amsterdam-nu-openbaar~a4539314/
  {
    from:
      '#?mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&lse=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://www.telegraaf.nl/nieuws/1256075/ligt-er-een-bom-uit-woii-in-je-achtertuin-met-deze-kaart-kom-je-er-achter
  {
    from:
      '#?mpb=topografie&mpz=11&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3815892:4.8626601&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&mpb=topografie&mpz=11&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3815892:4.8626601&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&lse=T&mpb=topografie&mpz=11&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3815892:4.8626601&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://www.amsterdamsdagblad.nl/gemeente/duizend-bommen-en-granaten-de-bommenkaart
  {
    from:
      '#?mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '#?ate=T&lse=T&mpb=topografie&mpz=8&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3733262:4.8934106&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://intranet.alliander.com/blog/${VIEW}/5359847/kaart-met-onontplofte-bommen-in-amsterdam
  {
    from:
      '#?mpb=topografie&mpz=14&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3889979:4.9094038&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  {
    from:
      '?_sp=144b47f5-2817-4a1f-888c-d1d1b69c89cb.1510908859477#?ate=T&mpb=topografie&mpz=14&mpfs=T&mpo=exvg::T:exin::T:exuo::T&mpv=52.3889979:4.9094038&pgn=home',
    to: `${routing.data.path}?${VIEW}=kaart&${VIEW_CENTER}=52.3787158140549%2C4.893662070270319&${EMBED}=true&${LAYERS}=exvg%3A1%7Cexin%3A1%7Cexuo%3A1&${LEGEND}=false&${ZOOM}=8`,
  },
  // https://www.amsterdam.nl/ondernemen/biz/
  {
    from: '#?mpb=topografie&mpz=9&mpfs=T&mpo=biz::T&mpv=52.3676245:4.8804992&pgn=home&uvm=T',
    to: `${routing.data.path}?${VIEW}=kaart&${LAYERS}=biz%3A1&${LEGEND}=true&${ZOOM}=9`,
  },
  {
    from: '#?ate=T&mpb=topografie&mpz=9&mpfs=T&mpo=biz::T&mpv=52.3676245:4.8804992&pgn=home&uvm=T',
    to: `${routing.data.path}?${VIEW}=kaart&${EMBED}=true&${LAYERS}=biz%3A1&${LEGEND}=true&${ZOOM}=9`,
  },
  // home map
  {
    from: '#?mpb=topografie&mpz=11&mpfs=T&mpv=52.3731081:4.8932945&pgn=home&uvm=T',
    to: `${routing.data.path}?${VIEW}=kaart`,
  },
]
const shortUrls = [
  {
    from: '/themakaart/taxi',
    to: `${routing.data.path}?${VIEW}=kaart&${LAYERS}=oovtig%3A1%7Cvezips%3A1%7Cmzt%3A0%7Cslpb%3A1%7Cslpnb%3A1%7Cbgt%3A1%7Ctar%3A1%7Cpvrts%3A1%7Cpvrll%3A1%7Cpvrpr%3A1&${LEGEND}=true`,
  },
  {
    from: '/themakaart/veiligheid-en-overlast',
    to: `${routing.data.path}?${VIEW}=kaart&${LAYERS}=oovorlv%3A1%7Coovoalco%3A1%7Coovctg%3A1%7Coovodlrs%3A1%7Coovoalg%3A1&${LEGEND}=true`,
  },
  {
    from: '/datablog',
    to: 'https://amsterdam.github.io/datablog/',
  },
]

export const routesDictionary = [...legacyRoutes, ...shortUrls]

const resolveRedirects = () => {
  let routePath = routesDictionary.filter(r => r.from === window.location.pathname)
  if (window.location.hash.match(/#\?/g)) {
    routePath = routesDictionary.filter(r => r.from === window.location.hash)
    window.location.replace(routePath.length && routePath[0].to ? routePath[0].to : '/verplaatst/')
    return true
  }
  if (routePath.length) {
    window.location.replace(routePath[0].to)
    return true
  }

  return false
}

export default resolveRedirects
<|endoftext|>


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

/**
 * Singleton to be used to log messages from any class. This object should probably not be used
 * directly since any class in AriaTemplates inherits from JsObject and as such has the $log() method. Using the $log()
 * method makes it easier because it doesn't require you to pass your current classname.
 *
 *

 * aria.core.Log.warn('my.class.Name', 'Warning!!');
 * aria.core.Log.info('my.class.Name', 'Something was processed ...');
 *

 *
 * Additionally, this object can be used when debugging, to set the level of log you want to see per package/class. By
 * default, levels are already set, but you may use your own custom levels for debugging purposes
 *
 *

 * aria.core.Log.resetLoggingLevels();
 * aria.core.Log.setLoggingLevel('modules.*', aria.core.Log.LEVEL_DEBUG);
 * aria.core.Log.setLoggingLevel('my.class.Name', aria.core.Log.LEVEL_WARN);
 *

 *
 * When logging messages to the logger, appenders are used to actually print out the messages. By default, the only
 * appender used is the one printing results in the window.console object (Firebug, FirebugLite). It is however possible
 * to add appenders for debugging purposes:
 *
 *

 * aria.core.Log.clearAppenders();
 * aria.core.Log.addAppender(new aria.core.log.WindowAppender());
 *

 *
 * @singleton
 */
module.exports = Aria.classDefinition({
    $classpath : "aria.core.Log",
    $singleton : true,
    $statics : {
        /**
         * Debug log level
         * @type Number
         */
        LEVEL_DEBUG : 1,

        /**
         * Info log level
         * @type Number
         */
        LEVEL_INFO : 2,

        /**
         * Warning log level
         * @type Number
         */
        LEVEL_WARN : 3,

        /**
         * Error log level
         * @type Number
         */
        LEVEL_ERROR : 4
    },
    $constructor : function () {
        /**
         * The list of loggers already created so far, per className
         * @type Array
         * @private
         */
        this._loggers = [];

        /**
         * The logging configuration telling the logger which messages will make it to the console for each className
         * @type Array
         * @private
         */
        this._loggingLevels = {};

        /**
         * List of all the appenders configured in the logging system. By default, only one is present, the
         * DefaultAppender (outputting in the firebug console)
         * @private
         * @type Array
         */
        this._appenders = [];

        var oSelf = this;
        var jsObjectProto = ariaCoreJsObject.prototype;
        // map function on JsObject prototype and Aria

        jsObjectProto.$logDebug = function (msg, msgArgs, obj) {
            oSelf.debug(this.$classpath, msg, msgArgs, obj);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logDebug = jsObjectProto.$logDebug;

        jsObjectProto.$logInfo = function (msg, msgArgs, obj) {
            oSelf.info(this.$classpath, msg, msgArgs, obj);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logInfo = jsObjectProto.$logInfo;

        jsObjectProto.$logWarn = function (msg, msgArgs, obj) {
            oSelf.warn(this.$classpath, msg, msgArgs, obj);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logWarn = jsObjectProto.$logWarn;

        jsObjectProto.$logError = function (msg, msgArgs, err) {
            oSelf.error(this.$classpath, msg, msgArgs, err);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logError = jsObjectProto.$logError;

        this.resetLoggingLevels();
        this.setLoggingLevel("*", Aria.debug ? this.LEVEL_DEBUG : this.LEVEL_ERROR);
        this.addAppender(new ariaCoreLogDefaultAppender());
    },
    $destructor : function () {
        this.resetLoggingLevels();
        this.clearAppenders();

        // remove functions on JsObject and Aria (added in the constructor):

        var jsObjectProto = ariaCoreJsObject.prototype;
        var alert = Aria.$window.alert;
        jsObjectProto.$logError = function (msg, msgArgs, err) {
            alert(["Error after aria.core.Log is disposed:\nin:", this.$classpath, "\nmsg:", msg].join(''));
        };
        Aria.$logError = jsObjectProto.$logError;

        var emptyFunction = function () {};
        jsObjectProto.$logDebug = emptyFunction;
        Aria.$logDebug = emptyFunction;
        jsObjectProto.$logInfo = emptyFunction;
        Aria.$logInfo = emptyFunction;
        jsObjectProto.$logWarn = emptyFunction;
        Aria.$logWarn = emptyFunction;
    },
    $prototype : {

        /**
         * Used to verify if a given level is within the known list of levels
         * @param {Number} level The level to test
         * @return {Boolean}
         */
        isValidLevel : function (level) {
            return (level == this.LEVEL_DEBUG || level == this.LEVEL_INFO || level == this.LEVEL_WARN || level == this.LEVEL_ERROR);
        },

        /**
         * Static method that is used to set the logging configuration for a specific className or part of a className.
         * By default, the Aria logger is already set with default values. This method is intended to be used during
         * debugging only to filter what you want to see printed in the console.
         *
         *

         * aria.core.Log.setLoggingLevel("a.*", aria.core.Log.LEVEL_ERROR);
         * aria.core.Log.setLoggingLevel("util.*", aria.core.Log.LEVEL_INFO);
         * aria.core.Log.setLoggingLevel("util.layouts.CardLayout", aria.core.Log.LEVEL_DEBUG);
         *

         *
         * @param {String} className The className to assign the level to
         * @param {Number} level The level of logging to allow
         */
        setLoggingLevel : function (className, level) {
            if (!this.isValidLevel(level)) {
                this.error(this.$classpath, "Invalid level passed to setLoggingLevel");
            } else {
                // TODO: Add here a check for the validity of a className:
                // possible values are a.b.c, or a.b.*
                this._loggingLevels[className] = level;
            }
        },

        /**
         * Reset all logging level configuration
         */
        resetLoggingLevels : function () {
            this._loggingLevels = [];
        },

        /**
         * Get the currently allowed logging level for a particular className
         * @param {String} className
         * @return {Number} level or false if no logging level is defined
         */
        getLoggingLevel : function (className) {
            var loggingLevel = this._loggingLevels[className];
            if (!!loggingLevel) {
                // If there is a logging level stored for the exact classname passed in parameter
                return loggingLevel;
            } else {
                // Else, look for package names
                var str = className;

                while (str.indexOf(".") != -1) {
                    str = str.substring(0, str.lastIndexOf("."));
                    if (this._loggingLevels[str]) {
                        return this._loggingLevels[str];
                    } else if (this._loggingLevels[str + ".*"]) {
                        return this._loggingLevels[str + ".*"];
                    }
                }

                return this._loggingLevels["*"] || false;
            }
        },

        /**
         * Is a specific level loggable for a given classname. Depending on the configuration of the logger sub-system,
         * we need to check if our current className supports at least this level of logging.
         * @param {String} level The level to test
         * @param {String} className The className to check
         * @return {Boolean}
         */
        isLogEnabled : function (level, className) {
            var minimumLevel = this.getLoggingLevel(className);

            // If we could find a minimum level authorized for this className
            // (or a best match)
            if (minimumLevel) {
                return level >= minimumLevel;
            } else {
                // If nothing was found in the conf for this class, no logging
                // is possible
                return false;
            }
        },

        /**
         * Add a new appender to the logging system
         * @param {aria.core.log.DefaultAppender} appender An instance of appender (must implement the same methods as
         * aria.core.log.DefaultAppender)
         */
        addAppender : function (appender) {
            this._appenders.push(appender);
        },

        /**
         * Removes an appender from the logging system
         * @param {aria.core.log.DefaultAppender} appender An instance of appender to be removed (must implement the
         * same methods as aria.core.log.DefaultAppender)
         */
        removeAppender : function (appender) {
            if (appender) {
                this._clearAppenders(appender);
            }
        },

        /**
         * Remove all appenders in the logging system, if an appender is passed then will remove just a specific
         * appender instance
         * @param {aria.core.log.DefaultAppender} appender An instance of appender to be removed (must implement the
         * same methods as aria.core.log.DefaultAppender)
         */
        _clearAppenders : function (appender) {
            var apps = this._appenders;
            for (var i = 0, l = apps.length; i < l; i++) {
                if (!appender || apps[i] === appender) {
                    apps[i].$dispose();
                    apps.splice(i, 1);
                    i--;
                    l--;
                }
            }
        },

        /**
         * Remove all appenders in the logging system
         */
        clearAppenders : function () {
            this._clearAppenders();
        },

        /**
         * Get the list of current appenders
         * @param {String} classpath Optional argument, if passed, only the appenders of the given classpath will be
         * returned
         * @return {Array} The list of appenders
         */
        getAppenders : function (classpath) {
            var apps = this._appenders;

            if (classpath) {
                apps = [];
                for (var i = 0; i < this._appenders.length; i++) {
                    if (this._appenders[i].$classpath == classpath) {
                        apps.push(this._appenders[i]);
                    }
                }
            }

            return apps;
        },

        /**
         * Prepare a message to be logged from a message text and an optional array of arguments
         * @param {String} msg Message text (which may contain %n)
         * @param {Array} msgArgs optional list of arguments to be replaced in the message with %n
         * @param {Object} errorContext Optional object passed in case of template parsing error only
         * @return {String} The message
         */
        prepareLoggedMessage : function (msg, msgArgs, errorContext) {
            if (msgArgs) {
                msg = ariaUtilsString.substitute(msg, msgArgs);
            }
            for (var error in errorContext) {
                if (errorContext.hasOwnProperty(error)) {
                    msg += "\n" + error + " : " + errorContext[error] + "\n";
                }
            }
            return msg;
        },

        /**
         * Log a debug message.
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} o An optional object to inspect
         */
        debug : function (className, msg, msgArgs, o) {
            this.log(className, this.LEVEL_DEBUG, msgArgs, msg, o);
        },

        /**
         * Log an info message
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} o An optional object to inspect
         */
        info : function (className, msg, msgArgs, o) {
            this.log(className, this.LEVEL_INFO, msgArgs, msg, o);
        },

        /**
         * Log a warning message
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} o An optional object to inspect
         */
        warn : function (className, msg, msgArgs, o) {
            this.log(className, this.LEVEL_WARN, msgArgs, msg, o);
        },

        /**
         * Log an error message
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} e A JavaScript exception object or a object to inspect
         */
        error : function (className, msg, msgArgs, e) {
            this.log(className, this.LEVEL_ERROR, msgArgs, msg, e);
        },

        /**
         * Actually do the logging to the current appenders
         * @param {String} className The className that is logging
         * @param {String} level The level to log the message
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {String} msg The message text
         * @param {Object} objOrErr Either an option object to be logged or an error message in case of error or fatal
         * levels
         */
        log : function (className, level, msgArgs, msgText, objOrErr) {
            if (!this.isValidLevel(level)) {
                this.error(this.$classpath, "Invalid level passed for logging the message");
            } else {
                if (this.isLogEnabled(level, className)) {
                    var msg = this.prepareLoggedMessage(msgText, msgArgs);
                    var apps = this.getAppenders();
                    for (var i = 0; i < apps.length; i++) {
                        if (level == this.LEVEL_DEBUG) {
                            apps[i].debug(className, msg, msgText, objOrErr);
                        }
                        if (level == this.LEVEL_INFO) {
                            apps[i].info(className, msg, msgText, objOrErr);
                        }
                        if (level == this.LEVEL_WARN) {
                            apps[i].warn(className, msg, msgText, objOrErr);
                        }
                        if (level == this.LEVEL_ERROR) {
                            apps[i].error(className, msg, msgText, objOrErr);
                        }
                    }
                }
            }
        }
    }
});
<commit_msg>fix #1703 Removing alert when logging after aria.core.Log is disposed<commit_after>/*
 * Copyright 2012 Amadeus s.a.s.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var Aria = require("../Aria");
var ariaCoreLogDefaultAppender = require("./log/DefaultAppender");
var ariaUtilsString = require("../utils/String");
var ariaCoreJsObject = require("./JsObject");

var afterDisposeLog = function (logType) {
    var console = Aria.$window.console;
    if (!console) {
        return Aria.empty;
    }
    var consoleFn = console[logType] || console.log;
    return function (msg, msgArgs, err) {
        consoleFn.call(console, [logType, " after aria.core.Log is disposed:\nin:", this.$classpath, "\nmsg:", msg].join(''));
    };
};

/**
 * Singleton to be used to log messages from any class. This object should probably not be used
 * directly since any class in AriaTemplates inherits from JsObject and as such has the $log() method. Using the $log()
 * method makes it easier because it doesn't require you to pass your current classname.
 *
 *

 * aria.core.Log.warn('my.class.Name', 'Warning!!');
 * aria.core.Log.info('my.class.Name', 'Something was processed ...');
 *

 *
 * Additionally, this object can be used when debugging, to set the level of log you want to see per package/class. By
 * default, levels are already set, but you may use your own custom levels for debugging purposes
 *
 *

 * aria.core.Log.resetLoggingLevels();
 * aria.core.Log.setLoggingLevel('modules.*', aria.core.Log.LEVEL_DEBUG);
 * aria.core.Log.setLoggingLevel('my.class.Name', aria.core.Log.LEVEL_WARN);
 *

 *
 * When logging messages to the logger, appenders are used to actually print out the messages. By default, the only
 * appender used is the one printing results in the window.console object (Firebug, FirebugLite). It is however possible
 * to add appenders for debugging purposes:
 *
 *

 * aria.core.Log.clearAppenders();
 * aria.core.Log.addAppender(new aria.core.log.WindowAppender());
 *

 *
 * @singleton
 */
module.exports = Aria.classDefinition({
    $classpath : "aria.core.Log",
    $singleton : true,
    $statics : {
        /**
         * Debug log level
         * @type Number
         */
        LEVEL_DEBUG : 1,

        /**
         * Info log level
         * @type Number
         */
        LEVEL_INFO : 2,

        /**
         * Warning log level
         * @type Number
         */
        LEVEL_WARN : 3,

        /**
         * Error log level
         * @type Number
         */
        LEVEL_ERROR : 4
    },
    $constructor : function () {
        /**
         * The list of loggers already created so far, per className
         * @type Array
         * @private
         */
        this._loggers = [];

        /**
         * The logging configuration telling the logger which messages will make it to the console for each className
         * @type Array
         * @private
         */
        this._loggingLevels = {};

        /**
         * List of all the appenders configured in the logging system. By default, only one is present, the
         * DefaultAppender (outputting in the firebug console)
         * @private
         * @type Array
         */
        this._appenders = [];

        var oSelf = this;
        var jsObjectProto = ariaCoreJsObject.prototype;
        // map function on JsObject prototype and Aria

        jsObjectProto.$logDebug = function (msg, msgArgs, obj) {
            oSelf.debug(this.$classpath, msg, msgArgs, obj);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logDebug = jsObjectProto.$logDebug;

        jsObjectProto.$logInfo = function (msg, msgArgs, obj) {
            oSelf.info(this.$classpath, msg, msgArgs, obj);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logInfo = jsObjectProto.$logInfo;

        jsObjectProto.$logWarn = function (msg, msgArgs, obj) {
            oSelf.warn(this.$classpath, msg, msgArgs, obj);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logWarn = jsObjectProto.$logWarn;

        jsObjectProto.$logError = function (msg, msgArgs, err) {
            oSelf.error(this.$classpath, msg, msgArgs, err);
            // return empty string to be used easily in templates
            return "";
        };
        Aria.$logError = jsObjectProto.$logError;

        this.resetLoggingLevels();
        this.setLoggingLevel("*", Aria.debug ? this.LEVEL_DEBUG : this.LEVEL_ERROR);
        this.addAppender(new ariaCoreLogDefaultAppender());
    },
    $destructor : function () {
        this.resetLoggingLevels();
        this.clearAppenders();

        // remove functions on JsObject and Aria (added in the constructor):

        var jsObjectProto = ariaCoreJsObject.prototype;
        Aria.$logError = jsObjectProto.$logError = afterDisposeLog("error");
        Aria.$logDebug = jsObjectProto.$logDebug = afterDisposeLog("debug");
        Aria.$logInfo = jsObjectProto.$logInfo = afterDisposeLog("info");
        Aria.$logWarn = jsObjectProto.$logWarn = afterDisposeLog("warn");
    },
    $prototype : {

        /**
         * Used to verify if a given level is within the known list of levels
         * @param {Number} level The level to test
         * @return {Boolean}
         */
        isValidLevel : function (level) {
            return (level == this.LEVEL_DEBUG || level == this.LEVEL_INFO || level == this.LEVEL_WARN || level == this.LEVEL_ERROR);
        },

        /**
         * Static method that is used to set the logging configuration for a specific className or part of a className.
         * By default, the Aria logger is already set with default values. This method is intended to be used during
         * debugging only to filter what you want to see printed in the console.
         *
         *

         * aria.core.Log.setLoggingLevel("a.*", aria.core.Log.LEVEL_ERROR);
         * aria.core.Log.setLoggingLevel("util.*", aria.core.Log.LEVEL_INFO);
         * aria.core.Log.setLoggingLevel("util.layouts.CardLayout", aria.core.Log.LEVEL_DEBUG);
         *

         *
         * @param {String} className The className to assign the level to
         * @param {Number} level The level of logging to allow
         */
        setLoggingLevel : function (className, level) {
            if (!this.isValidLevel(level)) {
                this.error(this.$classpath, "Invalid level passed to setLoggingLevel");
            } else {
                // TODO: Add here a check for the validity of a className:
                // possible values are a.b.c, or a.b.*
                this._loggingLevels[className] = level;
            }
        },

        /**
         * Reset all logging level configuration
         */
        resetLoggingLevels : function () {
            this._loggingLevels = [];
        },

        /**
         * Get the currently allowed logging level for a particular className
         * @param {String} className
         * @return {Number} level or false if no logging level is defined
         */
        getLoggingLevel : function (className) {
            var loggingLevel = this._loggingLevels[className];
            if (!!loggingLevel) {
                // If there is a logging level stored for the exact classname passed in parameter
                return loggingLevel;
            } else {
                // Else, look for package names
                var str = className;

                while (str.indexOf(".") != -1) {
                    str = str.substring(0, str.lastIndexOf("."));
                    if (this._loggingLevels[str]) {
                        return this._loggingLevels[str];
                    } else if (this._loggingLevels[str + ".*"]) {
                        return this._loggingLevels[str + ".*"];
                    }
                }

                return this._loggingLevels["*"] || false;
            }
        },

        /**
         * Is a specific level loggable for a given classname. Depending on the configuration of the logger sub-system,
         * we need to check if our current className supports at least this level of logging.
         * @param {String} level The level to test
         * @param {String} className The className to check
         * @return {Boolean}
         */
        isLogEnabled : function (level, className) {
            var minimumLevel = this.getLoggingLevel(className);

            // If we could find a minimum level authorized for this className
            // (or a best match)
            if (minimumLevel) {
                return level >= minimumLevel;
            } else {
                // If nothing was found in the conf for this class, no logging
                // is possible
                return false;
            }
        },

        /**
         * Add a new appender to the logging system
         * @param {aria.core.log.DefaultAppender} appender An instance of appender (must implement the same methods as
         * aria.core.log.DefaultAppender)
         */
        addAppender : function (appender) {
            this._appenders.push(appender);
        },

        /**
         * Removes an appender from the logging system
         * @param {aria.core.log.DefaultAppender} appender An instance of appender to be removed (must implement the
         * same methods as aria.core.log.DefaultAppender)
         */
        removeAppender : function (appender) {
            if (appender) {
                this._clearAppenders(appender);
            }
        },

        /**
         * Remove all appenders in the logging system, if an appender is passed then will remove just a specific
         * appender instance
         * @param {aria.core.log.DefaultAppender} appender An instance of appender to be removed (must implement the
         * same methods as aria.core.log.DefaultAppender)
         */
        _clearAppenders : function (appender) {
            var apps = this._appenders;
            for (var i = 0, l = apps.length; i < l; i++) {
                if (!appender || apps[i] === appender) {
                    apps[i].$dispose();
                    apps.splice(i, 1);
                    i--;
                    l--;
                }
            }
        },

        /**
         * Remove all appenders in the logging system
         */
        clearAppenders : function () {
            this._clearAppenders();
        },

        /**
         * Get the list of current appenders
         * @param {String} classpath Optional argument, if passed, only the appenders of the given classpath will be
         * returned
         * @return {Array} The list of appenders
         */
        getAppenders : function (classpath) {
            var apps = this._appenders;

            if (classpath) {
                apps = [];
                for (var i = 0; i < this._appenders.length; i++) {
                    if (this._appenders[i].$classpath == classpath) {
                        apps.push(this._appenders[i]);
                    }
                }
            }

            return apps;
        },

        /**
         * Prepare a message to be logged from a message text and an optional array of arguments
         * @param {String} msg Message text (which may contain %n)
         * @param {Array} msgArgs optional list of arguments to be replaced in the message with %n
         * @param {Object} errorContext Optional object passed in case of template parsing error only
         * @return {String} The message
         */
        prepareLoggedMessage : function (msg, msgArgs, errorContext) {
            if (msgArgs) {
                msg = ariaUtilsString.substitute(msg, msgArgs);
            }
            for (var error in errorContext) {
                if (errorContext.hasOwnProperty(error)) {
                    msg += "\n" + error + " : " + errorContext[error] + "\n";
                }
            }
            return msg;
        },

        /**
         * Log a debug message.
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} o An optional object to inspect
         */
        debug : function (className, msg, msgArgs, o) {
            this.log(className, this.LEVEL_DEBUG, msgArgs, msg, o);
        },

        /**
         * Log an info message
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} o An optional object to inspect
         */
        info : function (className, msg, msgArgs, o) {
            this.log(className, this.LEVEL_INFO, msgArgs, msg, o);
        },

        /**
         * Log a warning message
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} o An optional object to inspect
         */
        warn : function (className, msg, msgArgs, o) {
            this.log(className, this.LEVEL_WARN, msgArgs, msg, o);
        },

        /**
         * Log an error message
         * @param {String} className The classname for which the log is to be done
         * @param {String} msg the message text
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {Object} e A JavaScript exception object or a object to inspect
         */
        error : function (className, msg, msgArgs, e) {
            this.log(className, this.LEVEL_ERROR, msgArgs, msg, e);
        },

        /**
         * Actually do the logging to the current appenders
         * @param {String} className The className that is logging
         * @param {String} level The level to log the message
         * @param {Array} msgArgs An array of arguments to be used for string replacement in the message text
         * @param {String} msg The message text
         * @param {Object} objOrErr Either an option object to be logged or an error message in case of error or fatal
         * levels
         */
        log : function (className, level, msgArgs, msgText, objOrErr) {
            if (!this.isValidLevel(level)) {
                this.error(this.$classpath, "Invalid level passed for logging the message");
            } else {
                if (this.isLogEnabled(level, className)) {
                    var msg = this.prepareLoggedMessage(msgText, msgArgs);
                    var apps = this.getAppenders();
                    for (var i = 0; i < apps.length; i++) {
                        if (level == this.LEVEL_DEBUG) {
                            apps[i].debug(className, msg, msgText, objOrErr);
                        }
                        if (level == this.LEVEL_INFO) {
                            apps[i].info(className, msg, msgText, objOrErr);
                        }
                        if (level == this.LEVEL_WARN) {
                            apps[i].warn(className, msg, msgText, objOrErr);
                        }
                        if (level == this.LEVEL_ERROR) {
                            apps[i].error(className, msg, msgText, objOrErr);
                        }
                    }
                }
            }
        }
    }
});
<|endoftext|>