<commit_before>import config from '../config.json';
import {autoDetectRenderer, Container, Point} from 'pixi.js';
import {Entity} from './entity';
import {Key} from './input';
import uuid from 'uuid';
import EntityService from './EntityService';
import MapService from './MapService';
import Radar from './Radar';

let name = 'top-down-arena-shooter';
let version = '0.0.1';
document.title = `${name} v${version}`;

let renderer = autoDetectRenderer(config.width, config.height);
document.body.appendChild(renderer.view);

let stage = new Container();
let entityService = EntityService.get();
let mapService = MapService.get();
let radar = new Radar();

const getOrCreateSessionID = () => {
  if (!sessionStorage.sessionID) {
    sessionStorage.sessionID = uuid();
  }

  return sessionStorage.sessionID;
};

const handleInitializeEvent = (eventData) => {
  // input: id,x,y,r|...
  stage.removeChildren();
  entityService.clear();
  eventData = eventData.split('|');
  eventData.forEach(entityData => {
    let properties = entityData.split(',');
    let id = properties[0];
    let x = parseFloat(properties[1]);
    let y = parseFloat(properties[2]);
    let r = parseFloat(properties[3]);
    let entity = new Entity(id, x, y, r);
    stage.addChild(entity.sprite);
    entityService.add(entity);
  });
};

const handleMapEvent = (eventData) => {
  // input width,height
  let dimensions = eventData.split(',');
  mapService.setDimensions(dimensions[0], dimensions[1]);
};

const handleIdentityEvent = (eventData) => {
  // input: id
  entityService.setPlayerIdentity(eventData);
};

const handleAddEvent = (eventData) => {
  // input: id,x,y,r
  let properties = eventData.split(',');
  let id = properties[0];
  let x = parseFloat(properties[1]);
  let y = parseFloat(properties[2]);
  let r = parseFloat(properties[3]);
  let isPlayer = (properties[4] === 't');
  let entity = new Entity(id, x, y, r, isPlayer);
  stage.addChild(entity.sprite);
  entityService.add(entity);
};

const handleUpdateEvent = (eventData) => {
  // input: id,x,y,r|...
  eventData = eventData.split('|');
  eventData.forEach(entityData => {
    let properties = entityData.split(',');
    let id = properties[0];
    let entity = entityService.getById(id);
    if (entity) {
      let x = parseFloat(properties[1]);
      let y = parseFloat(properties[2]);
      let r = parseFloat(properties[3]);
      entity.setPosition(x, y);
      entity.setRotation(r);
    } else {
      console.log(`can't find entity: ${id}`);
    }
  })
};

const handleRemoveEvent = (eventData) => {
  // input: id
  let entity = entityService.removeById(eventData);
  if (entity) {
    stage.removeChild(entity.sprite);
  }
};

const getEventHandler = (name) => {
  switch (name) {
    case 'initialize': return handleInitializeEvent;
    case 'map': return handleMapEvent;
    case 'identity': return handleIdentityEvent;
    case 'add': return handleAddEvent;
    case 'update': return handleUpdateEvent;
    case 'remove': return handleRemoveEvent;
  }
};

let sessionID = getOrCreateSessionID();

const socket = new WebSocket(`ws://${config.server}/${sessionID}`);

socket.addEventListener('open', (event) => {
  console.log('websocket opened:', config.server);
});

socket.addEventListener('message', (event) => {
  console.log('websocket message received:', event.data);
  let split = event.data.indexOf('|');
  if (split !== -1) {
    let command = event.data.slice(0, split);
    let parameters = event.data.slice(split + 1);
    let eventHandler = getEventHandler(command);
    eventHandler(parameters);
  }
});

socket.addEventListener('close', (event) => {
  console.log('websocket closed:', config.server);
});

let keysDown = [];

window.addEventListener('keydown', (event) => {
  // console.log('keydown:', event.keyCode);
  if (keysDown.includes(event.keyCode)) return;
  keysDown.push(event.keyCode);

  if (event.keyCode === Key.A || event.keyCode === Key.Left) {
    socket.send(`start-rotate|left`);
  }

  if (event.keyCode === Key.D || event.keyCode === Key.Right) {
    socket.send(`start-rotate|right`);
  }

  if (event.keyCode === Key.W || event.keyCode === Key.Up) {
    socket.send(`start-thrust|forward`);
  }

  if (event.keyCode === Key.S || event.keyCode === Key.Down) {
    socket.send(`start-thrust|backward`);
  }
});

window.addEventListener('keyup', (event) => {
  // console.log('keyup:', event.keyCode);
  let index = keysDown.indexOf(event.keyCode);
  if (index !== -1) {
    keysDown.splice(index, 1);
  }

  if (event.keyCode === Key.A || event.keyCode === Key.Left) {
    socket.send(`stop-rotate|left`);
  }

  if (event.keyCode === Key.D || event.keyCode === Key.Right) {
    socket.send(`stop-rotate|right`);
  }

  if (event.keyCode === Key.W || event.keyCode === Key.Up) {
    socket.send(`stop-thrust|forward`);
  }

  if (event.keyCode === Key.S || event.keyCode === Key.Down) {
    socket.send(`stop-thrust|backward`);
  }
});

const loop = () => {
  requestAnimationFrame(loop);
  renderer.render(stage);
  if (entityService.localPlayer) {
    let nearbyEntities = entityService.getNearby(config.radar.range);
    radar.update(nearbyEntities);
    radar.draw();
  }
};

loop();
<commit_msg>Added rudimentary camera logic; resolves #7.<commit_after>import config from '../config.json';
import {autoDetectRenderer, Container, Point} from 'pixi.js';
import {Entity} from './entity';
import {Key} from './input';
import uuid from 'uuid';
import EntityService from './EntityService';
import MapService from './MapService';
import Radar from './Radar';

let name = 'top-down-arena-shooter';
let version = '0.0.1';
document.title = `${name} v${version}`;

let renderer = autoDetectRenderer(config.width, config.height);
document.body.appendChild(renderer.view);

let stage = new Container();
let entityService = EntityService.get();
let mapService = MapService.get();
let radar = new Radar();

const getOrCreateSessionID = () => {
  if (!sessionStorage.sessionID) {
    sessionStorage.sessionID = uuid();
  }

  return sessionStorage.sessionID;
};

const handleInitializeEvent = (eventData) => {
  // input: id,x,y,r|...
  stage.removeChildren();
  entityService.clear();
  eventData = eventData.split('|');
  eventData.forEach(entityData => {
    let properties = entityData.split(',');
    let id = properties[0];
    let x = parseFloat(properties[1]);
    let y = parseFloat(properties[2]);
    let r = parseFloat(properties[3]);
    let entity = new Entity(id, x, y, r);
    stage.addChild(entity.sprite);
    entityService.add(entity);
  });
};

const handleMapEvent = (eventData) => {
  // input width,height
  let dimensions = eventData.split(',');
  mapService.setDimensions(dimensions[0], dimensions[1]);
};

const handleIdentityEvent = (eventData) => {
  // input: id
  entityService.setPlayerIdentity(eventData);
};

const handleAddEvent = (eventData) => {
  // input: id,x,y,r
  let properties = eventData.split(',');
  let id = properties[0];
  let x = parseFloat(properties[1]);
  let y = parseFloat(properties[2]);
  let r = parseFloat(properties[3]);
  let isPlayer = (properties[4] === 't');
  let entity = new Entity(id, x, y, r, isPlayer);
  stage.addChild(entity.sprite);
  entityService.add(entity);
};

const handleUpdateEvent = (eventData) => {
  // input: id,x,y,r|...
  eventData = eventData.split('|');
  eventData.forEach(entityData => {
    let properties = entityData.split(',');
    let id = properties[0];
    let entity = entityService.getById(id);
    if (entity) {
      let x = parseFloat(properties[1]);
      let y = parseFloat(properties[2]);
      let r = parseFloat(properties[3]);
      entity.setPosition(x, y);
      entity.setRotation(r);
    } else {
      console.log(`can't find entity: ${id}`);
    }
  });
};

const handleRemoveEvent = (eventData) => {
  // input: id
  let entity = entityService.removeById(eventData);
  if (entity) {
    stage.removeChild(entity.sprite);
  }
};

const getEventHandler = (name) => {
  switch (name) {
    case 'initialize': return handleInitializeEvent;
    case 'map': return handleMapEvent;
    case 'identity': return handleIdentityEvent;
    case 'add': return handleAddEvent;
    case 'update': return handleUpdateEvent;
    case 'remove': return handleRemoveEvent;
  }
};

let sessionID = getOrCreateSessionID();

const socket = new WebSocket(`ws://${config.server}/${sessionID}`);

socket.addEventListener('open', (event) => {
  console.log('websocket opened:', config.server);
});

socket.addEventListener('message', (event) => {
  console.log('websocket message received:', event.data);
  let split = event.data.indexOf('|');
  if (split !== -1) {
    let command = event.data.slice(0, split);
    let parameters = event.data.slice(split + 1);
    let eventHandler = getEventHandler(command);
    eventHandler(parameters);
  }
});

socket.addEventListener('close', (event) => {
  console.log('websocket closed:', config.server);
});

let keysDown = [];

window.addEventListener('keydown', (event) => {
  // console.log('keydown:', event.keyCode);
  if (keysDown.includes(event.keyCode)) return;
  keysDown.push(event.keyCode);

  if (event.keyCode === Key.A || event.keyCode === Key.Left) {
    socket.send(`start-rotate|left`);
  }

  if (event.keyCode === Key.D || event.keyCode === Key.Right) {
    socket.send(`start-rotate|right`);
  }

  if (event.keyCode === Key.W || event.keyCode === Key.Up) {
    socket.send(`start-thrust|forward`);
  }

  if (event.keyCode === Key.S || event.keyCode === Key.Down) {
    socket.send(`start-thrust|backward`);
  }
});

window.addEventListener('keyup', (event) => {
  // console.log('keyup:', event.keyCode);
  let index = keysDown.indexOf(event.keyCode);
  if (index !== -1) {
    keysDown.splice(index, 1);
  }

  if (event.keyCode === Key.A || event.keyCode === Key.Left) {
    socket.send(`stop-rotate|left`);
  }

  if (event.keyCode === Key.D || event.keyCode === Key.Right) {
    socket.send(`stop-rotate|right`);
  }

  if (event.keyCode === Key.W || event.keyCode === Key.Up) {
    socket.send(`stop-thrust|forward`);
  }

  if (event.keyCode === Key.S || event.keyCode === Key.Down) {
    socket.send(`stop-thrust|backward`);
  }
});

const loop = () => {
  requestAnimationFrame(loop);
  if (entityService.localPlayer) {
    let player = entityService.getLocalPlayer().sprite.position;
    stage.position.x = renderer.width / 2;
    stage.position.y = renderer.height / 2;
    stage.pivot.x = player.x;
    stage.pivot.y = player.y;
  }
  renderer.render(stage);
  if (entityService.localPlayer) {
    let nearbyEntities = entityService.getNearby(config.radar.range);
    radar.update(nearbyEntities);
    radar.draw();
  }
};

loop();
<|endoftext|>


<commit_before>import BaseManager from '../common/apiManager.js';
import { mergePost } from '../common/init.js';
import ApiMerger     from '../common/merger.js';

const SockJS = require('sockjs-client');

class ApiManager extends BaseManager
{
    constructor()
    {
        super();
        this.isClient   = true;
        this.formPoster = __FormPosterReqPath;
        this.socketConnected = false;
        this.saltApiForcers  = {};

        if(this.formPoster.default)
            this.formPoster = this.formPoster.default;

        if (!this.token)
            this.generateToken();
        else if (this.token.length < 10 || this.token.length > 20)
            this.generateToken();

        this.socketConnect();
        this.deleteOldCache();

        const _this = this;

        Vue.mixin({
            created()
            {
                try
                {
                    if(this.$options.preload && this.api)
                    {
                        const post     = _this.mergePost(this.api_data);
                        const api_salt = _this.getSalt(this.api, post);

                        if(window.API_DATA && window.API_DATA[api_salt])
                        {
                            ApiMerger(this, window.API_DATA[api_salt]);
                        }
                    }

                    if(this.api)
                    {
                        _this.bindVue(this, this.api, this.api_data);
                    }
                }
                catch(e)
                {
                    console.error(e);
                }
            }

        });
    }

    socketConnect()
    {
        if (!this.isClient)
            return null;

        if (this.socket) {
            try {
                console.warn('Closing old socket');
                this.socket.close();
            } catch (e) {
                console.error(e);
            }
        }

        var _this = this;
        const socket = new SockJS(this.base() + 'socketapi?token=' + this.token);
        this.socket = socket
        this.socket.onopen = function () {
            _this.socket = socket
            _this.socketConnected = true;
            _this.emitSocketOpen();
        };
        this.socket.onmessage = function (e) {
            _this.socketConnected = true;
            _this.emitSocketMessage(e);
        };
        this.socket.onclose = function () {
            _this.socketConnected = false;
            _this.emitSocketClose();
        };
    }

    base()
    {
        if (this.base_url)
            return this.base_url;

        var base = window.api_url || location.href;
        var index = base.indexOf('//');

        if (index != -1) {
            index = base.indexOf('/', index + 3);
            if (index != -1) {
                this.base_url = base.substr(0, index + 1);
                return this.base_url;
            }
        }

        return '';
    }

    //---------------------------------------------------------

    getCookie(name) {
        var dc = document.cookie;
        var prefix = name + "=";

        var begin = dc.indexOf("; " + prefix);
        var beginLength = 2 + prefix.length;

        if (begin == -1) {
            begin = dc.indexOf(";" + prefix);
            beginLength--;

            if (begin == -1 && dc.indexOf(prefix) == 0) {
                begin = 0;
                beginLength--;
            } else {
                return '';
            }
        }

        begin += beginLength;
        const end = dc.indexOf(';', begin);

        if (end == -1)
            return dc.substr(begin);

        return unescape(dc.substr(begin, end - begin));
    }

    setCookies(cookies, expiration) {
        var expires = '';
        if (expiration) {
            var d = new Date();
            d.setTime(expiration);
            expires = "expires=" + d.toUTCString() + ';';
        }

        for (var key in cookies) {
            document.cookie = key + "=" + cookies[key] + ";" + expires + "path=/";

            if (key === 'token') {
                this.token = cookies[key];

                if(this.socket)
                    this.socket.close();
            }
        }
    }

    deleteOldCache() {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    var index = key.indexOf('_');

                    if (key.substr(0, index) !== this.token) {
                        localStorage.removeItem(key);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteAllCache() {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    localStorage.removeItem(key);
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteLocalCache(api_salt, data) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.removeItem(this.token + '_' + api_salt);
            }
        } catch (e) {
            console.error(e);
        }
    }

    saveLocalCache(api_salt, data) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.setItem(this.token + '_' + api_salt, JSON.stringify(data));
            }
        } catch (e) {
            console.error(e);
        }
    }

    getLocalCache(api_salt) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                var localObject = localStorage.getItem(this.token + '_' + api_salt);
                try {
                    localObject = JSON.parse(localObject);

                    if(localObject === null && window.API_DATA && window.API_DATA[api_salt])
                    {
                        localObject = window.API_DATA[api_salt];
                    }

                    return localObject;
                } catch (e) {}
            }
        } catch (e) {
            console.error(e);
        }

        if(!window.API_DATA)
            return null;

        return window.API_DATA[api_salt] || null;
    }

    //---------------------------------------------------------

    emitSocketOpen() {
        const callbacks      = this.onOpenCallbacks;
        this.onOpenCallbacks = [];

        for (var key in callbacks) {
            try {
                callbacks[key].call(this, this);
            } catch (e) {
                console.error(e);
            }
        }

        var count = 0;
        for(var key in this.saltApiForcers) {
            count ++;
            this.saltApiForcers[key]();
        }
    }

    emitSocketClose() {
        if (this.isDev)
            console.warn('Socket closed');

        this.socketConnected = false;
        this.socket = false;
        this.fetchingApis = [];

        setTimeout(() => {
            console.log('Socket closed, reconnecting')
            this.socketConnect();
        }, 100);
    }

    emitSocketMessage(msg) {
        try {
            if (msg.type === 'message') {
                var data = msg.data;

                try {
                    data = JSON.parse(data);

                    if (data.api) {
                        this.emitApi(data.api, data.data, data.salt);
                    } else if (data.apiError) {
                        this.handleApiError(data.apiError, data.error, data.salt);
                    } else if (data.cookies) {
                        this.setCookies(data.cookies, data.expiration);
                    }
                } catch (e) {
                    console.error(e);
                }

                for (var key in this.socketHooks) {
                    try {
                        this.socketHooks[key](data);
                    } catch (e) {
                        console.error(e);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    emitApi(api, data, salt) {
        this.saveLocalCache(salt, data);
        var found = false;

        if (salt === undefined) {
            salt = this.getSalt(api, mergePost({}));
        }

        if(this.saltApiForcers[salt])
            delete this.saltApiForcers[salt];

        for (var key in this.onApiCallbacks) {
            try {

                const post = mergePost(JSON.parse(JSON.stringify(this.onApiCallbacks[key].post)));

                if (this.getSalt(this.onApiCallbacks[key].api, post) === salt)
                {
                    found = true;
                    this.onApiCallbacks[key].callback.call(this, data, api, this.onApiCallbacks[key].id);
                }
            } catch (e)
            {
                console.error(e);
            }
        }

        if (!found && this.isDev)
            console.warn('Api handler not found for', api, salt);
    }

    sendSocketMessage(msg) {
        if (!this.isClient)
            return null;

        if (typeof (msg) === 'object' || typeof (msg) === 'array') {
            try {
                msg = JSON.stringify(msg);
            } catch (e) {
                console.error(e);
            }
        }

        if (this.socketConnected === false) {
            return this.onSocketOpen(function () {
                this.socket.send(msg);
            });
        }

        this.socket.send(msg);
        return this;
    }

    onSocketOpen(cb) {
        if (this.socketConnected) {
            try {
                cb.call(this);
            } catch (e) {
                console.error(e);
            }
        } else {
            this.onOpenCallbacks.push(cb);
        }

        return this;
    }

    //---------------------------------------------------------

    sendApiAndForceResponse(api, data, salt) {
        const _this = this;

        this.saltApiForcers[salt] = function() {
            _this.sendSocketMessage({
                api:  api,
                data: data,
                salt: salt
            });
        }

        if(!this.socketConnected)
            return;

        this.saltApiForcers[salt]();
    }

    require(api, data, cb)
    {
        const res = super.require(api, data, cb);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);

        return res.id;
    }

    refresh(api, data)
    {
        const res = super.refresh(api, data);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);
    }

    post(api, data)
    {
        const _this = this;
        const res   = super.post(api, data);

        if(!res)
            return;

        return new Promise(function (resolve, reject) {

            _this.onApiCallbacks.push({
                id: res.id,
                api: api,
                post: res.data,
                //salt: salt,
                callback: function (res) {
                    _this.unbindListener(res.id);
                    resolve(res);
                },
                errorCallback: function(res) {
                    _this.unbindListener(res.id);
                    reject(res);
                }
            });

            _this.sendSocketMessage({
                api: api,
                data: res.data,
                salt: res.salt
            });
        });
    }
}

if (ApiManager.shared === undefined)
    ApiManager.shared = new ApiManager();

export default ApiManager.shared;<commit_msg>Cache quota check (full purge)<commit_after>import BaseManager from '../common/apiManager.js';
import { mergePost } from '../common/init.js';
import ApiMerger     from '../common/merger.js';

const SockJS = require('sockjs-client');

class ApiManager extends BaseManager
{
    constructor()
    {
        super();
        this.isClient   = true;
        this.formPoster = __FormPosterReqPath;
        this.socketConnected = false;
        this.saltApiForcers  = {};

        if(this.formPoster.default)
            this.formPoster = this.formPoster.default;

        if (!this.token)
            this.generateToken();
        else if (this.token.length < 10 || this.token.length > 20)
            this.generateToken();

        this.socketConnect();
        this.deleteOldCache();

        const _this = this;

        Vue.mixin({
            created()
            {
                try
                {
                    if(this.$options.preload && this.api)
                    {
                        const post     = _this.mergePost(this.api_data);
                        const api_salt = _this.getSalt(this.api, post);

                        if(window.API_DATA && window.API_DATA[api_salt])
                        {
                            ApiMerger(this, window.API_DATA[api_salt]);
                        }
                    }

                    if(this.api)
                    {
                        _this.bindVue(this, this.api, this.api_data);
                    }
                }
                catch(e)
                {
                    console.error(e);
                }
            }

        });
    }

    socketConnect()
    {
        if (!this.isClient)
            return null;

        if (this.socket) {
            try {
                console.warn('Closing old socket');
                this.socket.close();
            } catch (e) {
                console.error(e);
            }
        }

        var _this = this;
        const socket = new SockJS(this.base() + 'socketapi?token=' + this.token);
        this.socket = socket
        this.socket.onopen = function () {
            _this.socket = socket
            _this.socketConnected = true;
            _this.emitSocketOpen();
        };
        this.socket.onmessage = function (e) {
            _this.socketConnected = true;
            _this.emitSocketMessage(e);
        };
        this.socket.onclose = function () {
            _this.socketConnected = false;
            _this.emitSocketClose();
        };
    }

    base()
    {
        if (this.base_url)
            return this.base_url;

        var base = window.api_url || location.href;
        var index = base.indexOf('//');

        if (index != -1) {
            index = base.indexOf('/', index + 3);
            if (index != -1) {
                this.base_url = base.substr(0, index + 1);
                return this.base_url;
            }
        }

        return '';
    }

    //---------------------------------------------------------

    getCookie(name) {
        var dc = document.cookie;
        var prefix = name + "=";

        var begin = dc.indexOf("; " + prefix);
        var beginLength = 2 + prefix.length;

        if (begin == -1) {
            begin = dc.indexOf(";" + prefix);
            beginLength--;

            if (begin == -1 && dc.indexOf(prefix) == 0) {
                begin = 0;
                beginLength--;
            } else {
                return '';
            }
        }

        begin += beginLength;
        const end = dc.indexOf(';', begin);

        if (end == -1)
            return dc.substr(begin);

        return unescape(dc.substr(begin, end - begin));
    }

    setCookies(cookies, expiration) {
        var expires = '';
        if (expiration) {
            var d = new Date();
            d.setTime(expiration);
            expires = "expires=" + d.toUTCString() + ';';
        }

        for (var key in cookies) {
            document.cookie = key + "=" + cookies[key] + ";" + expires + "path=/";

            if (key === 'token') {
                this.token = cookies[key];

                if(this.socket)
                    this.socket.close();
            }
        }
    }

    deleteOldCache() {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    var index = key.indexOf('_');

                    if (key.substr(0, index) !== this.token) {
                        localStorage.removeItem(key);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteAllCache() {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    localStorage.removeItem(key);
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteLocalCache(api_salt, data) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.removeItem(this.token + '_' + api_salt);
            }
        } catch (e) {
            console.error(e);
        }
    }

    saveLocalCache(api_salt, data) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.setItem(this.token + '_' + api_salt, JSON.stringify(data));
            }
        } catch (e) {
            if(e.code === 22) {
                console.warn('Cache quota exceeded, purging cache.');
                this.deleteAllCache();
                return;
            }

            console.error(e);
        }
    }

    getLocalCache(api_salt) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                var localObject = localStorage.getItem(this.token + '_' + api_salt);
                try {
                    localObject = JSON.parse(localObject);

                    if(localObject === null && window.API_DATA && window.API_DATA[api_salt])
                    {
                        localObject = window.API_DATA[api_salt];
                    }

                    return localObject;
                } catch (e) {}
            }
        } catch (e) {
            console.error(e);
        }

        if(!window.API_DATA)
            return null;

        return window.API_DATA[api_salt] || null;
    }

    //---------------------------------------------------------

    emitSocketOpen() {
        const callbacks      = this.onOpenCallbacks;
        this.onOpenCallbacks = [];

        for (var key in callbacks) {
            try {
                callbacks[key].call(this, this);
            } catch (e) {
                console.error(e);
            }
        }

        var count = 0;
        for(var key in this.saltApiForcers) {
            count ++;
            this.saltApiForcers[key]();
        }
    }

    emitSocketClose() {
        if (this.isDev)
            console.warn('Socket closed');

        this.socketConnected = false;
        this.socket = false;
        this.fetchingApis = [];

        setTimeout(() => {
            console.log('Socket closed, reconnecting')
            this.socketConnect();
        }, 100);
    }

    emitSocketMessage(msg) {
        try {
            if (msg.type === 'message') {
                var data = msg.data;

                try {
                    data = JSON.parse(data);

                    if (data.api) {
                        this.emitApi(data.api, data.data, data.salt);
                    } else if (data.apiError) {
                        this.handleApiError(data.apiError, data.error, data.salt);
                    } else if (data.cookies) {
                        this.setCookies(data.cookies, data.expiration);
                    }
                } catch (e) {
                    console.error(e);
                }

                for (var key in this.socketHooks) {
                    try {
                        this.socketHooks[key](data);
                    } catch (e) {
                        console.error(e);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    emitApi(api, data, salt) {
        this.saveLocalCache(salt, data);
        var found = false;

        if (salt === undefined) {
            salt = this.getSalt(api, mergePost({}));
        }

        if(this.saltApiForcers[salt])
            delete this.saltApiForcers[salt];

        for (var key in this.onApiCallbacks) {
            try {

                const post = mergePost(JSON.parse(JSON.stringify(this.onApiCallbacks[key].post)));

                if (this.getSalt(this.onApiCallbacks[key].api, post) === salt)
                {
                    found = true;
                    this.onApiCallbacks[key].callback.call(this, data, api, this.onApiCallbacks[key].id);
                }
            } catch (e)
            {
                console.error(e);
            }
        }

        if (!found && this.isDev)
            console.warn('Api handler not found for', api, salt);
    }

    sendSocketMessage(msg) {
        if (!this.isClient)
            return null;

        if (typeof (msg) === 'object' || typeof (msg) === 'array') {
            try {
                msg = JSON.stringify(msg);
            } catch (e) {
                console.error(e);
            }
        }

        if (this.socketConnected === false) {
            return this.onSocketOpen(function () {
                this.socket.send(msg);
            });
        }

        this.socket.send(msg);
        return this;
    }

    onSocketOpen(cb) {
        if (this.socketConnected) {
            try {
                cb.call(this);
            } catch (e) {
                console.error(e);
            }
        } else {
            this.onOpenCallbacks.push(cb);
        }

        return this;
    }

    //---------------------------------------------------------

    sendApiAndForceResponse(api, data, salt) {
        const _this = this;

        this.saltApiForcers[salt] = function() {
            _this.sendSocketMessage({
                api:  api,
                data: data,
                salt: salt
            });
        }

        if(!this.socketConnected)
            return;

        this.saltApiForcers[salt]();
    }

    require(api, data, cb)
    {
        const res = super.require(api, data, cb);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);

        return res.id;
    }

    refresh(api, data)
    {
        const res = super.refresh(api, data);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);
    }

    post(api, data)
    {
        const _this = this;
        const res   = super.post(api, data);

        if(!res)
            return;

        return new Promise(function (resolve, reject) {

            _this.onApiCallbacks.push({
                id: res.id,
                api: api,
                post: res.data,
                //salt: salt,
                callback: function (res) {
                    _this.unbindListener(res.id);
                    resolve(res);
                },
                errorCallback: function(res) {
                    _this.unbindListener(res.id);
                    reject(res);
                }
            });

            _this.sendSocketMessage({
                api: api,
                data: res.data,
                salt: res.salt
            });
        });
    }
}

if (ApiManager.shared === undefined)
    ApiManager.shared = new ApiManager();

export default ApiManager.shared;<|endoftext|>


<commit_before>'use strict';

var Retext = require('..'),
    assert = require('assert');

/* istanbul ignore next: noop */
function noop() {}

describe('Retext()', function () {
    it('should be of type `function`', function () {
        assert(typeof Retext === 'function');
    });

    it('should return a newly initialized `Retext` object, when invoked ' +
        'without arguments', function () {
            assert(new Retext() instanceof Retext);
        }
    );

    it('should set the `parser` attribute to `parse-english`, when invoked ' +
        'without arguments', function () {
            var retext = new Retext();
            assert('parser' in retext);
            assert(typeof retext.parser === 'function');
            assert('TextOM' in retext.parser);
        }
    );

    it('should set the `parser` attribute to the passed in parser, ' +
        'when given', function () {
            var retext = new Retext(noop);
            assert('parser' in retext);
            assert(retext.parser === noop);
        }
    );

    it('should create a new context/parser/textom when required, thus ' +
        'not requiring from memory', function () {
            var rootNode1 = new Retext().parse(),
                rootNode2 = new Retext().parse();

            assert(rootNode1 instanceof rootNode1.constructor);
            assert(!(rootNode1 instanceof rootNode2.constructor));
            assert(rootNode2 instanceof rootNode2.constructor);
            assert(!(rootNode2 instanceof rootNode1.constructor));
        }
    );

    it('should set the `plugins` attribute to an empty array', function () {
        var retext = new Retext();
        assert('plugins' in retext);
        assert(retext.plugins instanceof Array);
        assert(retext.hasOwnProperty('plugins'));
        assert(retext.plugins.length === 0);
    });
});

describe('Retext#use', function () {
    it('should be of type `function`', function () {
        assert(typeof Retext.prototype.use === 'function');
        assert(typeof (new Retext()).use === 'function');
    });

    it('should return self', function () {
        var retext = new Retext();
        assert(retext.use(noop) === retext);
    });

    it('should throw, when something other than a function was given',
        function () {
            var retext = new Retext();

            assert.throws(function () {
                retext.use();
            }, 'undefined');

            assert.throws(function () {
                retext.use(null);
            }, 'null');

            assert.throws(function () {
                retext.use(undefined);
            }, 'undefined');

            assert.throws(function () {
                retext.use(true);
            }, 'true');

            assert.throws(function () {
                retext.use({});
            }, 'object Object');
        }
    );

    it('should attach `use`d plugins', function () {
        var retext = new Retext();
        assert(retext.plugins.length === 0);
        retext.use(noop);
        assert(retext.plugins.length === 1);
    });

    it('should not attach `use`d plugins multiple times', function () {
        var retext = new Retext();
        retext.use(noop);
        assert(retext.plugins.length === 1);
        retext.use(noop);
        assert(retext.plugins.length === 1);
    });
});

describe('Retext#parse', function () {
    it('should be of type `function`', function () {
        assert(typeof Retext.prototype.parse === 'function');
        assert(typeof (new Retext()).parse === 'function');
    });

    it('should return an instance of RootNode', function () {
        var retext = new Retext();
        assert(retext.parse() instanceof retext.parser.TextOM.RootNode);
    });

    it('should immediately call the `attach` method on a plugin, when ' +
        '`use` is called', function () {
            var retext = new Retext(),
                isCalled = false;

            /* istanbul ignore next: noop */
            function plugin () {}

            plugin.attach = function () {
                isCalled = true;
            };

            retext.use(plugin);

            assert(isCalled === true);
        }
    );

    it('should not call the `attach` method, when `parse` is called',
        function () {
            var retext = new Retext(),
                isCalled = false;

            function plugin () {}

            plugin.attach = function () {
                isCalled = true;
            };

            retext.use(plugin);

            isCalled = false;

            retext.parse();

            assert(isCalled === false);
        }
    );

    it('should call `use`d plugins, when `parse` is called', function () {
        var retext = new Retext(),
            isCalled = false;

        function plugin () {
            isCalled = true;
        }

        retext.use(plugin);

        assert(isCalled === false);

        retext.parse();

        assert(isCalled === true);
    });

    it('should call `use`d plugins with an instance of RootNode and ' +
        'Retext, when `parse` is called', function () {
            var retext = new Retext(),
                args = null,
                tree;

            function plugin () {
                args = arguments;
            }

            retext.use(plugin);

            tree = retext.parse();

            assert(args[0] === tree);
            assert(args[1] === retext);
        }
    );

    it('should immediately call (during parsing) `use`d plugins, with ' +
        'an instance of RootNode and Retext', function () {
            var retext = new Retext(),
                args = null;

            function nestedPlugin () {
                args = arguments;
            }

            function plugin (tree, retext) {
                retext.use(nestedPlugin);
                assert(args[0] === tree);
                assert(args[1] === retext);
            }

            retext.use(plugin).parse();
        }
    );

    it('should not call (during parsing) `use`d plugins, when already used',
        function () {
            var retext = new Retext();

            function nestedPlugin () {}

            function plugin (tree, retext) {
                var length = retext.plugins.length;
                retext.use(nestedPlugin);
                assert(length === retext.plugins.length);
            }

            retext.use(nestedPlugin).use(plugin).parse();
        }
    );
});
<commit_msg>Added a unit test to detect if the API actually parses<commit_after>'use strict';

var Retext = require('..'),
    assert = require('assert');

/* istanbul ignore next: noop */
function noop() {}

describe('Retext()', function () {
    it('should be of type `function`', function () {
        assert(typeof Retext === 'function');
    });

    it('should return a newly initialized `Retext` object, when invoked ' +
        'without arguments', function () {
            assert(new Retext() instanceof Retext);
        }
    );

    it('should set the `parser` attribute to `parse-english`, when invoked ' +
        'without arguments', function () {
            var retext = new Retext();
            assert('parser' in retext);
            assert(typeof retext.parser === 'function');
            assert('TextOM' in retext.parser);
        }
    );

    it('should set the `parser` attribute to the passed in parser, ' +
        'when given', function () {
            var retext = new Retext(noop);
            assert('parser' in retext);
            assert(retext.parser === noop);
        }
    );

    it('should create a new context/parser/textom when required, thus ' +
        'not requiring from memory', function () {
            var rootNode1 = new Retext().parse(),
                rootNode2 = new Retext().parse();

            assert(rootNode1 instanceof rootNode1.constructor);
            assert(!(rootNode1 instanceof rootNode2.constructor));
            assert(rootNode2 instanceof rootNode2.constructor);
            assert(!(rootNode2 instanceof rootNode1.constructor));
        }
    );

    it('should set the `plugins` attribute to an empty array', function () {
        var retext = new Retext();
        assert('plugins' in retext);
        assert(retext.plugins instanceof Array);
        assert(retext.hasOwnProperty('plugins'));
        assert(retext.plugins.length === 0);
    });
});

describe('Retext#use', function () {
    it('should be of type `function`', function () {
        assert(typeof Retext.prototype.use === 'function');
        assert(typeof (new Retext()).use === 'function');
    });

    it('should return self', function () {
        var retext = new Retext();
        assert(retext.use(noop) === retext);
    });

    it('should throw, when something other than a function was given',
        function () {
            var retext = new Retext();

            assert.throws(function () {
                retext.use();
            }, 'undefined');

            assert.throws(function () {
                retext.use(null);
            }, 'null');

            assert.throws(function () {
                retext.use(undefined);
            }, 'undefined');

            assert.throws(function () {
                retext.use(true);
            }, 'true');

            assert.throws(function () {
                retext.use({});
            }, 'object Object');
        }
    );

    it('should attach `use`d plugins', function () {
        var retext = new Retext();
        assert(retext.plugins.length === 0);
        retext.use(noop);
        assert(retext.plugins.length === 1);
    });

    it('should not attach `use`d plugins multiple times', function () {
        var retext = new Retext();
        retext.use(noop);
        assert(retext.plugins.length === 1);
        retext.use(noop);
        assert(retext.plugins.length === 1);
    });
});

describe('Retext#parse', function () {
    it('should be of type `function`', function () {
        assert(typeof Retext.prototype.parse === 'function');
        assert(typeof (new Retext()).parse === 'function');
    });

    it('should return an instance of RootNode', function () {
        var retext = new Retext();
        assert(retext.parse() instanceof retext.parser.TextOM.RootNode);
    });

    it('should immediately call the `attach` method on a plugin, when ' +
        '`use` is called', function () {
            var retext = new Retext(),
                isCalled = false;

            /* istanbul ignore next: noop */
            function plugin () {}

            plugin.attach = function () {
                isCalled = true;
            };

            retext.use(plugin);

            assert(isCalled === true);
        }
    );

    it('should not call the `attach` method, when `parse` is called',
        function () {
            var retext = new Retext(),
                isCalled = false;

            function plugin () {}

            plugin.attach = function () {
                isCalled = true;
            };

            retext.use(plugin);

            isCalled = false;

            retext.parse();

            assert(isCalled === false);
        }
    );

    it('should call `use`d plugins, when `parse` is called', function () {
        var retext = new Retext(),
            isCalled = false;

        function plugin () {
            isCalled = true;
        }

        retext.use(plugin);

        assert(isCalled === false);

        retext.parse();

        assert(isCalled === true);
    });

    it('should call `use`d plugins with an instance of RootNode and ' +
        'Retext, when `parse` is called', function () {
            var retext = new Retext(),
                args = null,
                tree;

            function plugin () {
                args = arguments;
            }

            retext.use(plugin);

            tree = retext.parse();

            assert(args[0] === tree);
            assert(args[1] === retext);
        }
    );

    it('should immediately call (during parsing) `use`d plugins, with ' +
        'an instance of RootNode and Retext', function () {
            var retext = new Retext(),
                args = null;

            function nestedPlugin () {
                args = arguments;
            }

            function plugin (tree, retext) {
                retext.use(nestedPlugin);
                assert(args[0] === tree);
                assert(args[1] === retext);
            }

            retext.use(plugin).parse();
        }
    );

    it('should not call (during parsing) `use`d plugins, when already used',
        function () {
            var retext = new Retext();

            function nestedPlugin () {}

            function plugin (tree, retext) {
                var length = retext.plugins.length;
                retext.use(nestedPlugin);
                assert(length === retext.plugins.length);
            }

            retext.use(nestedPlugin).use(plugin).parse();
        }
    );

    it('should parse something into a Text Object Model', function () {
        var root = new Retext().parse('Something something');

        assert('head' in root);
        assert('tail' in root);
        assert(root.head.parent === root);
        assert('TextOM' in root);
    });
});
<|endoftext|>


<commit_before>var xml2js = require('xml2js')
var parseAttributes = require('./parse-attribs')

module.exports = function parseBMFontXML(data) {
  data = data.toString().trim()

  var output = {
    pages: [],
    chars: [],
    kernings: []
  }

  xml2js.parseString(data, function(err, result) {
    if (err)
      throw err
    if (!result.font)
      throw "XML bitmap font doesn't have <font> root"
    result = result.font

    output.common = parseAttributes(result.common[0].$)
    output.info = parseAttributes(result.info[0].$)

    for (var i = 0; i < result.pages.length; i++) {
      var p = result.pages[i].page[0].$

      if (typeof p.id === "undefined")
        throw new Error("malformed file -- needs page id=N")
      if (typeof p.file !== "string")
        throw new Error("malformed file -- needs page file=\"path\"")

      output.pages[parseInt(p.id, 10)] = p.file
    }

    var chrArray = result.chars[0]['char'] || []
    for (var i = 0; i < chrArray.length; i++) {
      output.chars.push(parseAttributes(chrArray[i].$))
    }

    var kernArray = result.kernings[0]['kerning'] || []
    for (var i = 0; i < kernArray.length; i++) {
      output.kernings.push(parseAttributes(kernArray[i].$))
    }
  })
  return output
}<commit_msg>Avoid throwing exception for fonts without kerning<commit_after>var xml2js = require('xml2js')
var parseAttributes = require('./parse-attribs')

module.exports = function parseBMFontXML(data) {
  data = data.toString().trim()

  var output = {
    pages: [],
    chars: [],
    kernings: []
  }

  xml2js.parseString(data, function(err, result) {
    if (err)
      throw err
    if (!result.font)
      throw "XML bitmap font doesn't have <font> root"
    result = result.font

    output.common = parseAttributes(result.common[0].$)
    output.info = parseAttributes(result.info[0].$)

    for (var i = 0; i < result.pages.length; i++) {
      var p = result.pages[i].page[0].$

      if (typeof p.id === "undefined")
        throw new Error("malformed file -- needs page id=N")
      if (typeof p.file !== "string")
        throw new Error("malformed file -- needs page file=\"path\"")

      output.pages[parseInt(p.id, 10)] = p.file
    }

    if (result.chars) {
      var chrArray = result.chars[0]['char'] || []
      for (var i = 0; i < chrArray.length; i++) {
        output.chars.push(parseAttributes(chrArray[i].$))
      }
    }

    if (result.kernings) {
      var kernArray = result.kernings[0]['kerning'] || []
      for (var i = 0; i < kernArray.length; i++) {
        output.kernings.push(parseAttributes(kernArray[i].$))
      }
    }
  })
  return output
}
<|endoftext|>


<commit_before>// Project imports
var BotPlayer = require('./BotPlayer');
var MinionPlayer = require('./MinionPlayer');
var FakeSocket = require('./FakeSocket');
var PacketHandler = require('../PacketHandler');

function BotLoader(gameServer) {
    this.gameServer = gameServer;
    this.loadNames();
}

module.exports = BotLoader;

BotLoader.prototype.getName = function () {
    var name = "";

    // Picks a random name for the bot
    if (this.randomNames.length > 0) {
        var index = (this.randomNames.length * Math.random()) >>> 0;
        name = this.randomNames[index];
    } else {
        name = "bot" + ++this.nameIndex;
    }

    return name;
};

BotLoader.prototype.loadNames = function () {
    this.randomNames = [];
    var fs = require("fs");

    if (fs.existsSync("./botnames.txt")) {
        // Read and parse the names - filter out whitespace-only names
        this.randomNames = fs.readFileSync("./botnames.txt", "utf8").split(/[\r\n]+/).filter(function (x) {
            return x != ''; // filter empty names
        });
    }
    this.nameIndex = 0;
};

BotLoader.prototype.addBot = function () {
    var s = new FakeSocket(this.gameServer);
    s.playerTracker = new BotPlayer(this.gameServer, s);
    s.packetHandler = new PacketHandler(this.gameServer, s);

    // Add to client list
    this.gameServer.clients.push(s);

    // Add to world
    s.packetHandler.setNickname(this.getName());
};

BotLoader.prototype.addMinion = function() {
    var s = new FakeSocket(this.gameServer);
    s.playerTracker = new MinionPlayer(this.gameServer, s);
    s.packetHandler = new PacketHandler(this.gameServer, s);

    // Add to client list
    this.gameServer.clients.push(s);

    // Add to world & set name
    s.packetHandler.setNickname(this.gameServer.minionName);
};
<commit_msg>Update BotLoader.js<commit_after>// Project imports
var BotPlayer = require('./BotPlayer');
var MinionPlayer = require('./MinionPlayer');
var FakeSocket = require('./FakeSocket');
var PacketHandler = require('../PacketHandler');

function BotLoader(gameServer) {
    this.gameServer = gameServer;
    this.loadNames();
}

module.exports = BotLoader;

BotLoader.prototype.getName = function () {
    var name = "";

    // Picks a random name for the bot
    if (this.randomNames.length > 0) {
        var index = (this.randomNames.length * Math.random()) >>> 0;
        name = this.randomNames[index];
    } else {
        name = "bot" + ++this.nameIndex;
    }

    return name;
};

BotLoader.prototype.loadNames = function () {
    this.randomNames = [];
    var fs = require("fs");

    if (fs.existsSync("./ai/BotNames.txt")) {
        // Read and parse the names - filter out whitespace-only names
        this.randomNames = fs.readFileSync("./ai/BotNames.txt", "utf8").split(/[\r\n]+/).filter(function (x) {
            return x != ''; // filter empty names
        });
    }
    this.nameIndex = 0;
};

BotLoader.prototype.addBot = function () {
    var s = new FakeSocket(this.gameServer);
    s.playerTracker = new BotPlayer(this.gameServer, s);
    s.packetHandler = new PacketHandler(this.gameServer, s);

    // Add to client list
    this.gameServer.clients.push(s);

    // Add to world
    s.packetHandler.setNickname(this.getName());
};

BotLoader.prototype.addMinion = function() {
    var s = new FakeSocket(this.gameServer);
    s.playerTracker = new MinionPlayer(this.gameServer, s);
    s.packetHandler = new PacketHandler(this.gameServer, s);

    // Add to client list
    this.gameServer.clients.push(s);

    // Add to world & set name
    s.packetHandler.setNickname(this.gameServer.minionName);
};
<|endoftext|>


<commit_before>// Dependencies

let isArray = require('lodash/lang/isArray');
let keys = require('lodash/object/keys');
let forEach = require('lodash/collection/forEach');
let isEqual = require('lodash/lang/isEqual');
let checkIsNotNull = require('focus').util.object.checkIsNotNull;
let ArgumentNullException = require('focus').exception.ArgumentNullException;

// Components

let SingleGroupComponent = require('./single-group-component').component;
let ListSelection = require('../../../list/selection').list.component;

// Mixins

let i18nMixin = require('../../../common/i18n/mixin');

//Empty Result component to be mututalized.
let EmptyComponent = React.createClass({
    mixins: [i18nMixin],
    render(){
        return (

                    {this.i18n('search.empty')}

            );
    }
});

/**
 * Mixin used in order to create a block.
 * @type {Object}
 */
let GroupByMixin = {
    mixins: [i18nMixin],
    /**
     * Init default props.
     * @returns {object} Default props.
     */
    getDefaultProps() {
        return ({
            lineOperationList: [],
            orderableColumnList: {},
            lineComponentMapper() {
                throw new ArgumentNullException('Please provide a inferLineComponentFromList(list) function.');
            },
            emptyComponent: EmptyComponent
        });
    },
    /**
     * Change the max rows of a group.
     * @param {string} groupKey Key of the group.
     * @param {int} maxRows Number of needed rows.
     * @returns {Function} The function wich will change the max rows of the group.
     */
    changeGroupByMaxRows(groupKey, maxRows) {
        return (event) => {
            this.refs[groupKey].changeGroupByMaxRows(maxRows);
        };
    },
    getResultListComponent(advancedSearch) {
        // First check if there is any result
        if (this.state.totalRecords === 0) {
            return <this.props.emptyComponent />;
        }
        let isBounded = keys(this.state.map).length > 1;
        let noDecoration = keys(this.state.map).length === 1;
        if (noDecoration && advancedSearch) {
            let groupKey = keys(this.state.map)[0];
            return this._getSingleTypeResultList(groupKey, this.state.map[groupKey], isBounded && this.props.groupMaxRows);
        } else {
            let groupCounts = this._getGroupsCountsFromStore();
            return keys(this.state.map).reduce((groupList, groupKey) => {
                if (this.state.map[groupKey].length > 0) {
                    groupList.push(
                        <SingleGroupComponent
                            data-focus='results-group'
                            key={groupKey}
                            count={groupCounts[groupKey].count}
                            ref={groupKey}
                            renderGroupBy={this.renderGroupByBlock}
                            list={this.state.map[groupKey]}
                            groupKey={groupKey}
                            maxRows={isBounded && this.props.groupMaxRows}
                            />
                    );
                }
                return groupList;
            }, []);
        }
    },
    _getSingleTypeResultList(groupKey, list, maxRows) {
        if (maxRows) {
            list = list.slice(0, maxRows);
        }
        return (
            <ListSelection
                data-focus='results-list'
                data={list}
                ref={groupKey}
                idField={this.props.idField}
                isSelection={this.props.isSelection}
                onSelection={this._selectItem}
                onLineClick={this._lineClick}
                fetchNextPage={this.fetchNextPage}
                hasMoreData={this.state.hasMoreData}
                isLoading={this.state.isLoading}
                operationList={this.props.lineOperationList}
                lineComponent={this.props.lineComponentMapper(list)}
                parentSelector={this.props.parentSelector}
                selectionStatus={this.state.selectionStatus}
                />
        );
    },
    _getGroupsCountsFromStore() {
        let groupKeys = keys(this.state.map);
        if (groupKeys.length === 1) {
            return {[groupKeys[0]]: this.props.store.getPageInfos().totalRecords};
        }
        let facets = this.props.store.getFacet();
        let targetFacetData;
        forEach(facets, (facetData, facetKey) => {
            if (isEqual(keys(facetData).sort(), groupKeys.sort())) {
                targetFacetData = facetData;
                return false;
            }
        });
        return targetFacetData;
    },
    renderGroupByBlock(groupKey, count, list, maxRows) {
        let GroupWrapper = this.props.groupComponent;
        return (
            <GroupWrapper data-focus="group-result-container" groupKey={groupKey} count={count} query={this.state.query} showAll={this.showAllGroupListHandler}>
                {this._getSingleTypeResultList(groupKey, list, maxRows)}
            </GroupWrapper>
        );
    }
};

module.exports = {mixin: GroupByMixin};
<commit_msg>[search] Fix error on single group count<commit_after>// Dependencies

let isArray = require('lodash/lang/isArray');
let keys = require('lodash/object/keys');
let forEach = require('lodash/collection/forEach');
let isEqual = require('lodash/lang/isEqual');
let checkIsNotNull = require('focus').util.object.checkIsNotNull;
let ArgumentNullException = require('focus').exception.ArgumentNullException;

// Components

let SingleGroupComponent = require('./single-group-component').component;
let ListSelection = require('../../../list/selection').list.component;

// Mixins

let i18nMixin = require('../../../common/i18n/mixin');

//Empty Result component to be mututalized.
let EmptyComponent = React.createClass({
    mixins: [i18nMixin],
    render(){
        return (

                    {this.i18n('search.empty')}

            );
    }
});

/**
 * Mixin used in order to create a block.
 * @type {Object}
 */
let GroupByMixin = {
    mixins: [i18nMixin],
    /**
     * Init default props.
     * @returns {object} Default props.
     */
    getDefaultProps() {
        return ({
            lineOperationList: [],
            orderableColumnList: {},
            lineComponentMapper() {
                throw new ArgumentNullException('Please provide a inferLineComponentFromList(list) function.');
            },
            emptyComponent: EmptyComponent
        });
    },
    /**
     * Change the max rows of a group.
     * @param {string} groupKey Key of the group.
     * @param {int} maxRows Number of needed rows.
     * @returns {Function} The function wich will change the max rows of the group.
     */
    changeGroupByMaxRows(groupKey, maxRows) {
        return (event) => {
            this.refs[groupKey].changeGroupByMaxRows(maxRows);
        };
    },
    getResultListComponent(advancedSearch) {
        // First check if there is any result
        if (this.state.totalRecords === 0) {
            return <this.props.emptyComponent />;
        }
        let isBounded = keys(this.state.map).length > 1;
        let noDecoration = keys(this.state.map).length === 1;
        if (noDecoration && advancedSearch) {
            let groupKey = keys(this.state.map)[0];
            return this._getSingleTypeResultList(groupKey, this.state.map[groupKey], isBounded && this.props.groupMaxRows);
        } else {
            let groupCounts = this._getGroupsCountsFromStore();
            return keys(this.state.map).reduce((groupList, groupKey) => {
                if (this.state.map[groupKey].length > 0) {
                    groupList.push(
                        <SingleGroupComponent
                            data-focus='results-group'
                            key={groupKey}
                            count={groupCounts[groupKey].count}
                            ref={groupKey}
                            renderGroupBy={this.renderGroupByBlock}
                            list={this.state.map[groupKey]}
                            groupKey={groupKey}
                            maxRows={isBounded && this.props.groupMaxRows}
                            />
                    );
                }
                return groupList;
            }, []);
        }
    },
    _getSingleTypeResultList(groupKey, list, maxRows) {
        if (maxRows) {
            list = list.slice(0, maxRows);
        }
        return (
            <ListSelection
                data-focus='results-list'
                data={list}
                ref={groupKey}
                idField={this.props.idField}
                isSelection={this.props.isSelection}
                onSelection={this._selectItem}
                onLineClick={this._lineClick}
                fetchNextPage={this.fetchNextPage}
                hasMoreData={this.state.hasMoreData}
                isLoading={this.state.isLoading}
                operationList={this.props.lineOperationList}
                lineComponent={this.props.lineComponentMapper(list)}
                parentSelector={this.props.parentSelector}
                selectionStatus={this.state.selectionStatus}
                />
        );
    },
    _getGroupsCountsFromStore() {
        let groupKeys = keys(this.state.map);
        if (groupKeys.length === 1) {
            return {
                [groupKeys[0]]: {
                    count: this.props.store.getPageInfos().totalRecords
                }
            };
        }
        let facets = this.props.store.getFacet();
        let targetFacetData;
        forEach(facets, (facetData, facetKey) => {
            if (isEqual(keys(facetData).sort(), groupKeys.sort())) {
                targetFacetData = facetData;
                return false;
            }
        });
        return targetFacetData;
    },
    renderGroupByBlock(groupKey, count, list, maxRows) {
        let GroupWrapper = this.props.groupComponent;
        return (
            <GroupWrapper data-focus="group-result-container" groupKey={groupKey} count={count} query={this.state.query} showAll={this.showAllGroupListHandler}>
                {this._getSingleTypeResultList(groupKey, list, maxRows)}
            </GroupWrapper>
        );
    }
};

module.exports = {mixin: GroupByMixin};
<|endoftext|>


<commit_before>import cheerio from 'cheerio';

export default function formatDescription(unformattedDescription) {
  const $ = cheerio.load(unformattedDescription);
  let textRepresentation = $.root().text();

  if (textRepresentation.endsWith('[…]')) {
    textRepresentation = textRepresentation.slice(0, textRepresentation.length - 3);
  }

  return textRepresentation.trim();
}
<commit_msg>No-break space characters are now converted to regular spaces, which is more inline with what a user (and our tests!) expect<commit_after>import cheerio from 'cheerio';

export default function formatDescription(unformattedDescription) {
  const $ = cheerio.load(unformattedDescription);
  let textRepresentation = $.root().text();

  if (textRepresentation.endsWith('[…]')) {
    textRepresentation = textRepresentation.slice(0, textRepresentation.length - 3);
  }

  // Fix for feeds with &nbps; (NO-BREAK SPACE) characters, where we want just normal spaces
  textRepresentation = textRepresentation.replace(new RegExp('\xA0', 'g'), ' ');
  return textRepresentation.trim();
}
<|endoftext|>


<commit_before>'use strict';
// NodeBox is the top level box.

class BaseBox {
    // A nodebox is the visual representation of a node

    static nodetype() { return undefined }

    static make_nodebox(node) {
        // Make the nodebox by grouping everything together
        return new fabric.Group(this.grouped_objects(node),
                                { node : node,
                                  name : node.name,
                                  left : node.geom.left,
                                  top :  node.geom.top,
                                  hoverCursor: 'pointer',
                                  subTargetCheck: true,
                                  selectable: true,
                                  hasControls: false,
                                  hasBorders : false });
    }

    static grouped_objects(node) {
        // Returns an array of fabric objects to group
        return  [this.make_box(node)]
            .concat(this.make_ports(node, 'output'))
            .concat(this.make_ports(node, 'input'))
    }

    static make_ports(node, type) {
        // Make an array of either the 'input' or 'output' ports
        let params = type == 'input' ? node.inputs : node.outputs;
        let offset = type == 'input' ? node.outputs.length : 0;
        return params.map( (param, i) => this.make_port(node, param, type, i+offset));
    }

    static make_port(node, param, type) {
        // Return a single port circle at the appropriate position
        let pos = node.port_position(param, type);
        let prefix = type === 'input' ? 'inport-' : 'outport-';

        return new fabric.Circle({
            name :        prefix + _.UUID(),
            param :       param,
            left :        pos[0],
            top :         pos[1],
            radius :      node.geom.port_radius,
            originX :     'center',
            originY :     'center',
            stroke :      'black',
            strokeWidth : 1,
            fill:         type == 'input'? node.input_style.fill : node.output_style.fill,
            scale:        0.5})
    }

    static make_box(node) {
        // Make the 'box' i.e the smoothed rectangle background
        let flags =  { selectable: true,
                       hasControls: false,
                       hasBorders : false }
        let height = node.ports_height() + node.header_height;
        return new fabric.Rect(_.extend({}, node.style, node.geom, flags,
                                        { height: height,
                                          left: 0, top: 0 }));
    }

    static port_position(port, node) {
        // Currently unused and returns unscaled positions
        // but could lookup the group and look for the
        // ports inside (getObjects) and get the positions there.
        return node.port_position(port, type);
    }

    static update_node(view, graph, node, left, top) {
        // Move a node and update the edges
        node.geom.left = left;
        node.geom.top = top;

        for ( let in_edge of graph.node_edges(node, 'input') ) {
            Connector.update_connector(view, in_edge) }

        for ( let out_edge of graph.node_edges(node, 'output') ) {
            Connector.update_connector(view, out_edge) }
    }
}

class LabelledBox extends BaseBox {
    // Extends Basebox with a title and labels

    static nodetype() { return 'LabelledNode' }

    static make_title(node) {
        // Returns an array of fabric objects to group
        let title = new fabric.Text(node.name, {
            fontFamily: node.title_opts.fontFamily,
            strokeWidth : 0,
            originX :     'center',
            originY :     'top',
            fontSize:      node.title_opts.size,
            left: node.geom.width / 2.0,
            top: 0 });

        // Add 10% padding
        title.top = title.height * node.title_opts.top_padding_ratio;

        // Scale to fit if necessary
        let allowed_title_width = node.geom.width*node.width_ratio;
        if (title.width > allowed_title_width) {
            title.scaleX = allowed_title_width/title.width;
            title.scaleY = title.scaleX;
        }
        // Allocate space in the node neader
        node.title_height = title.top + (title.height * title.scaleY);

        return title
    }

    static make_label(name, node, type) {
        let [x,y] = node.port_position(name, type);
        let padding = node.geom.width * 0.2;
        let label = new fabric.Text(name, {
            fontFamily: node.label_opts.fontFamily,
            strokeWidth : 0,
            originX :     (type === 'input') ? 'left' : 'right',
            originY :     'center',
            fontSize:      node.label_opts.size,
            left: (type == 'input') ? x + padding : x - padding ,
            top: y });
        return label
    }

    static make_labels(node) {
        let output_labels = _.map(node.outputs, (v) => this.make_label(v, node, 'output'));
        let input_labels = _.map(node.inputs, (v) => this.make_label(v, node, 'input'));
        return output_labels.concat(input_labels)
    }

    static grouped_objects(node) {
        // Note, compute elements before concat to allow base node modifications
        let title_text = this.make_title(node);
        let labels = this.make_labels(node);
        return super.grouped_objects(node).concat([title_text]).concat(labels)
    }
}

class ImageBox extends LabelledBox {

    static nodetype() { return 'ImageNode' }

    static make_image(node, group, imopts={}) {
        // Makes a fabric Image and adds it to the supplied group when ready
        let half_height = (node.ports_height() + node.header_height)/2.0;
        let opts = _.extend( {
            top: node.title_height - half_height,
            // Depends on whether left/right ports are present
            left : node.image_left(),
            strokeWidth : node.image_opts.strokeWidth,
            stroke : node.image_opts.stroke,
            originX : 'center',
            selectable : false,
            hasBorders : false,
            hasControls: false,
        }, imopts)

        fabric.Image.fromURL(node.image_opts.imdata, (image) =>  {
            node.image = image;
            image.scaleX = node.image_scaleX();
            image.scaleY = node.image_scaleY();

            image.width = node.image_opts.xres;
            image.height = node.image_opts.yres;

            group.add(image);
            canvas.renderAll();
        }, opts);
    }

    static update_image(node, view) {
        if (node.image) {
            node.image.setSrc(node.image_opts.imdata,
                              (img) => {
                                  view.lookup(node).dirty = true;
                                  canvas.renderAll() });
        }
    }

    static make_nodebox(node) {
        // title_height added when title created
        node.header_height = node.image_opts.height;
        let group = super.make_nodebox(node);
        // The fabric.Image will add itself to the group when ready
        this.make_image(node, group);
        return group
    }
}

// class NodeBox extends ImageBox {

// }

class ViewportBox extends ImageBox {

    static nodetype() { return 'Viewport' }

    static make_rect_port(node, param) {
        // Return a single centered rectangle port
        return new fabric.Rect({
            name :        'inport-' + _.UUID(),
            param :       param,
            left :        - node.geom.width/2,
            top :         0,
            height :      node.geom.width,
            width :       node.geom.port_radius+10,
            originX :     'center',
            originY :     'center',
            stroke :      'black',
            strokeWidth : 1,
            fill:         node.input_style.fill,
            scale:        0.5})
    }

    static make_frame(node) {
        return new fabric.Rect({
            name :        'frame-' + _.UUID(),
            left :        0,
            top :         0,
            height :      node.geom.width,
            width :       node.geom.width,
            originX :     'center',
            originY :     'center',
            stroke :      'red',
            strokeWidth : 1,
            fill : undefined
        })
    }

    static make_nodebox(node) {

        let center_port = this.make_rect_port(node, 'input');
        let group = new fabric.Group([this.make_frame(node), center_port],
                                    { node : node,
                                      name : node.name,
                                      left : node.geom.left,
                                      top :  node.geom.top,
                                      // Forcing square aspect ratio
                                      width :  node.geom.width+node.geom.port_radius+10,
                                      height : node.geom.width+ node.geom.port_radius+10,
                                      padding : 0,
                                      subTargetCheck: true,
                                    })

        this.make_image(node, group, { top : 0,
                                       originY : 'center' });
        group.hasControls = true;
        group.hasRotatingPoint = false;
        let disabled_controls = ['ml','mr', 'mt','mb']
        for (let cntl of disabled_controls) {
            group.setControlVisible(cntl, false);
        }
        return group;
    }
}
<commit_msg>Restoring title_height increment to header_height<commit_after>'use strict';
// NodeBox is the top level box.

class BaseBox {
    // A nodebox is the visual representation of a node

    static nodetype() { return undefined }

    static make_nodebox(node) {
        // Make the nodebox by grouping everything together
        return new fabric.Group(this.grouped_objects(node),
                                { node : node,
                                  name : node.name,
                                  left : node.geom.left,
                                  top :  node.geom.top,
                                  hoverCursor: 'pointer',
                                  subTargetCheck: true,
                                  selectable: true,
                                  hasControls: false,
                                  hasBorders : false });
    }

    static grouped_objects(node) {
        // Returns an array of fabric objects to group
        return  [this.make_box(node)]
            .concat(this.make_ports(node, 'output'))
            .concat(this.make_ports(node, 'input'))
    }

    static make_ports(node, type) {
        // Make an array of either the 'input' or 'output' ports
        let params = type == 'input' ? node.inputs : node.outputs;
        let offset = type == 'input' ? node.outputs.length : 0;
        return params.map( (param, i) => this.make_port(node, param, type, i+offset));
    }

    static make_port(node, param, type) {
        // Return a single port circle at the appropriate position
        let pos = node.port_position(param, type);
        let prefix = type === 'input' ? 'inport-' : 'outport-';

        return new fabric.Circle({
            name :        prefix + _.UUID(),
            param :       param,
            left :        pos[0],
            top :         pos[1],
            radius :      node.geom.port_radius,
            originX :     'center',
            originY :     'center',
            stroke :      'black',
            strokeWidth : 1,
            fill:         type == 'input'? node.input_style.fill : node.output_style.fill,
            scale:        0.5})
    }

    static make_box(node) {
        // Make the 'box' i.e the smoothed rectangle background
        let flags =  { selectable: true,
                       hasControls: false,
                       hasBorders : false }
        let height = node.ports_height() + node.header_height;
        return new fabric.Rect(_.extend({}, node.style, node.geom, flags,
                                        { height: height,
                                          left: 0, top: 0 }));
    }

    static port_position(port, node) {
        // Currently unused and returns unscaled positions
        // but could lookup the group and look for the
        // ports inside (getObjects) and get the positions there.
        return node.port_position(port, type);
    }

    static update_node(view, graph, node, left, top) {
        // Move a node and update the edges
        node.geom.left = left;
        node.geom.top = top;

        for ( let in_edge of graph.node_edges(node, 'input') ) {
            Connector.update_connector(view, in_edge) }

        for ( let out_edge of graph.node_edges(node, 'output') ) {
            Connector.update_connector(view, out_edge) }
    }
}

class LabelledBox extends BaseBox {
    // Extends Basebox with a title and labels

    static nodetype() { return 'LabelledNode' }

    static make_title(node) {
        // Returns an array of fabric objects to group
        let title = new fabric.Text(node.name, {
            fontFamily: node.title_opts.fontFamily,
            strokeWidth : 0,
            originX :     'center',
            originY :     'top',
            fontSize:      node.title_opts.size,
            left: node.geom.width / 2.0,
            top: 0 });

        // Add 10% padding
        title.top = title.height * node.title_opts.top_padding_ratio;

        // Scale to fit if necessary
        let allowed_title_width = node.geom.width*node.width_ratio;
        if (title.width > allowed_title_width) {
            title.scaleX = allowed_title_width/title.width;
            title.scaleY = title.scaleX;
        }
        // Allocate space in the node neader
        node.title_height = title.top + (title.height * title.scaleY);
        node.header_height += node.title_height;
        return title
    }

    static make_label(name, node, type) {
        let [x,y] = node.port_position(name, type);
        let padding = node.geom.width * 0.2;
        let label = new fabric.Text(name, {
            fontFamily: node.label_opts.fontFamily,
            strokeWidth : 0,
            originX :     (type === 'input') ? 'left' : 'right',
            originY :     'center',
            fontSize:      node.label_opts.size,
            left: (type == 'input') ? x + padding : x - padding ,
            top: y });
        return label
    }

    static make_labels(node) {
        let output_labels = _.map(node.outputs, (v) => this.make_label(v, node, 'output'));
        let input_labels = _.map(node.inputs, (v) => this.make_label(v, node, 'input'));
        return output_labels.concat(input_labels)
    }

    static grouped_objects(node) {
        // Note, compute elements before concat to allow base node modifications
        let title_text = this.make_title(node);
        let labels = this.make_labels(node);
        return super.grouped_objects(node).concat([title_text]).concat(labels)
    }
}

class ImageBox extends LabelledBox {

    static nodetype() { return 'ImageNode' }

    static make_image(node, group, imopts={}) {
        // Makes a fabric Image and adds it to the supplied group when ready
        let half_height = (node.ports_height() + node.header_height)/2.0;
        let opts = _.extend( {
            top: node.title_height - half_height,
            // Depends on whether left/right ports are present
            left : node.image_left(),
            strokeWidth : node.image_opts.strokeWidth,
            stroke : node.image_opts.stroke,
            originX : 'center',
            selectable : false,
            hasBorders : false,
            hasControls: false,
        }, imopts)

        fabric.Image.fromURL(node.image_opts.imdata, (image) =>  {
            node.image = image;
            image.scaleX = node.image_scaleX();
            image.scaleY = node.image_scaleY();

            image.width = node.image_opts.xres;
            image.height = node.image_opts.yres;

            group.add(image);
            canvas.renderAll();
        }, opts);
    }

    static update_image(node, view) {
        if (node.image) {
            node.image.setSrc(node.image_opts.imdata,
                              (img) => {
                                  view.lookup(node).dirty = true;
                                  canvas.renderAll() });
        }
    }

    static make_nodebox(node) {
        // title_height added when title created
        node.header_height = node.image_opts.height;
        let group = super.make_nodebox(node);
        // The fabric.Image will add itself to the group when ready
        this.make_image(node, group);
        return group
    }
}

// class NodeBox extends ImageBox {

// }

class ViewportBox extends ImageBox {

    static nodetype() { return 'Viewport' }

    static make_rect_port(node, param) {
        // Return a single centered rectangle port
        return new fabric.Rect({
            name :        'inport-' + _.UUID(),
            param :       param,
            left :        - node.geom.width/2,
            top :         0,
            height :      node.geom.width,
            width :       node.geom.port_radius+10,
            originX :     'center',
            originY :     'center',
            stroke :      'black',
            strokeWidth : 1,
            fill:         node.input_style.fill,
            scale:        0.5})
    }

    static make_frame(node) {
        return new fabric.Rect({
            name :        'frame-' + _.UUID(),
            left :        0,
            top :         0,
            height :      node.geom.width,
            width :       node.geom.width,
            originX :     'center',
            originY :     'center',
            stroke :      'red',
            strokeWidth : 1,
            fill : undefined
        })
    }

    static make_nodebox(node) {

        let center_port = this.make_rect_port(node, 'input');
        let group = new fabric.Group([this.make_frame(node), center_port],
                                    { node : node,
                                      name : node.name,
                                      left : node.geom.left,
                                      top :  node.geom.top,
                                      // Forcing square aspect ratio
                                      width :  node.geom.width+node.geom.port_radius+10,
                                      height : node.geom.width+ node.geom.port_radius+10,
                                      padding : 0,
                                      subTargetCheck: true,
                                    })

        this.make_image(node, group, { top : 0,
                                       originY : 'center' });
        group.hasControls = true;
        group.hasRotatingPoint = false;
        let disabled_controls = ['ml','mr', 'mt','mb']
        for (let cntl of disabled_controls) {
            group.setControlVisible(cntl, false);
        }
        return group;
    }
}
<|endoftext|>


<commit_before>/*
 * ext-imagelib.js
 *
 * Licensed under the MIT License
 *
 * Copyright(c) 2010 Alexis Deveria
 *
 */

svgEditor.addExtension("imagelib", function() {

	var uiStrings = svgEditor.uiStrings;

	$.extend(uiStrings, {
		imagelib: {
			select_lib: 'Select an image library',
			show_list: 'Show library list',
			import_single: 'Import single',
			import_multi: 'Import multiple',
			open: 'Open as new document'
		}
	});

	var img_libs = [{
			name: 'Demo library (local)',
			url: 'extensions/imagelib/index.html',
			description: 'Demonstration library for SVG-edit on this server'
		},
		{
			name: 'IAN Symbol Libraries',
			url: 'http://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php',
			description: 'Free library of illustrations'
		}
	];

	function closeBrowser() {
		$('#imgbrowse_holder').hide();
	}

	function importImage(url) {
		var newImage = svgCanvas.addSvgElementFromJson({
			"element": "image",
			"attr": {
				"x": 0,
				"y": 0,
				"width": 0,
				"height": 0,
				"id": svgCanvas.getNextId(),
				"style": "pointer-events:inherit"
			}
		});
		svgCanvas.clearSelection();
		svgCanvas.addToSelection([newImage]);
		svgCanvas.setImageURL(url);
	}

	var mode = 's';
	var multi_arr = [];
	var cur_meta;
	var tranfer_stopped = false;
	var pending = {};

	 window.addEventListener("message", function(evt) {
		// Receive postMessage data
		var response = evt.data;

		if(!response || typeof response !== "string") { // Todo: Should namespace postMessage API for this extension and filter out here
			// Do nothing
			return;
		}
        try { // This block can be removed if embedAPI moves away from a string to an object (if IE9 support not needed)
            var res = JSON.parse(response);
            if (res.namespace) { // Part of embedAPI communications
                return;
            }
        }
        catch (e) {}

		var char1 = response.charAt(0);

		var svg_str;
		var img_str;

		if(char1 != "{" && tranfer_stopped) {
			tranfer_stopped = false;
			return;
		}

		if(char1 == '|') {
			var secondpos = response.indexOf('|', 1);
			var id = response.substr(1, secondpos-1);
			response = response.substr(secondpos+1);
			char1 = response.charAt(0);

		}

		// Hide possible transfer dialog box
		$('#dialog_box').hide();

		switch (char1) {
			case '{':
				// Metadata
				tranfer_stopped = false;
				var cur_meta = JSON.parse(response);

				pending[cur_meta.id] = cur_meta;

				var name = (cur_meta.name || 'file');

				var message = uiStrings.notification.retrieving.replace('%s', name);

				if(mode != 'm') {
					$.process_cancel(message, function() {
						tranfer_stopped = true;
						// Should a message be sent back to the frame?

						$('#dialog_box').hide();
					});
				} else {
					var entry = $('
' + message + '
').data('id', cur_meta.id);
					preview.append(entry);
					cur_meta.entry = entry;
				}

				return;
			case '<':
				svg_str = true;
				break;
			case 'd':
				if(response.indexOf('data:image/svg+xml') === 0) {
					var pre = 'data:image/svg+xml;base64,';
					var src = response.substring(pre.length);
					response = svgedit.utilities.decode64(src);
					svg_str = true;
					break;
				} else if(response.indexOf('data:image/') === 0) {
					img_str = true;
					break;
				}
				// Else fall through
			default:
				// TODO: See if there's a way to base64 encode the binary data stream
// 				var str = 'data:;base64,' + svgedit.utilities.encode64(response, true);

				// Assume it's raw image data
// 				importImage(str);

				// Don't give warning as postMessage may have been used by something else
				if(mode !== 'm') {
					closeBrowser();
				} else {
					pending[id].entry.remove();
				}
// 				$.alert('Unexpected data was returned: ' + response, function() {
// 					if(mode !== 'm') {
// 						closeBrowser();
// 					} else {
// 						pending[id].entry.remove();
// 					}
// 				});
				return;
		}

		switch (mode) {
			case 's':
				// Import one
				if(svg_str) {
					svgCanvas.importSvgString(response);
				} else if(img_str) {
					importImage(response);
				}
				closeBrowser();
				break;
			case 'm':
				// Import multiple
				multi_arr.push([(svg_str ? 'svg' : 'img'), response]);
				var cur_meta = pending[id];
				if(svg_str) {
					if(cur_meta && cur_meta.name) {
						var title = cur_meta.name;
					}  else {
						// Try to find a title
						var xml = new DOMParser().parseFromString(response, 'text/xml').documentElement;
						var title = $(xml).children('title').first().text() || '(SVG #' + response.length + ')';
					}
					if(cur_meta) {
						preview.children().each(function() {
							if($(this).data('id') == id) {
								if(cur_meta.preview_url) {
									$(this).html('' + title);
								} else {
									$(this).text(title);
								}
								submit.removeAttr('disabled');
							}
						});
					} else {
						preview.append('
'+title+'
');
						submit.removeAttr('disabled');
					}
				} else {
					if(cur_meta && cur_meta.preview_url) {
						var title = cur_meta.name || '';
					}
					if(cur_meta && cur_meta.preview_url) {
						var entry = '' + title;
					} else {
						var entry = '';
					}

					if(cur_meta) {
						preview.children().each(function() {
							if($(this).data('id') == id) {
								$(this).html(entry);
								submit.removeAttr('disabled');
							}
						});
					} else {
						preview.append($('
').append(entry));
						submit.removeAttr('disabled');
					}

				}
				break;
			case 'o':
				// Open
				if(!svg_str) break;
				svgEditor.openPrep(function(ok) {
					if(!ok) return;
					svgCanvas.clear();
					svgCanvas.setSvgString(response);
					// updateCanvas();
				});
				closeBrowser();
				break;
		}
	}, true);

	var preview, submit;

	function toggleMulti(show) {

		$('#lib_framewrap, #imglib_opts').css({right: (show ? 200 : 10)});
		if(!preview) {
			preview = $('
').css({
				position: 'absolute',
				top: 45,
				right: 10,
				width: 180,
				bottom: 45,
				background: '#fff',
				overflow: 'auto'
			}).insertAfter('#lib_framewrap');

			submit = $('<button disabled>Import selected</button>')
				.appendTo('#imgbrowse')
				.on("click touchend", function() {
				$.each(multi_arr, function(i) {
					var type = this[0];
					var data = this[1];
					if(type == 'svg') {
						svgCanvas.importSvgString(data);
					} else {
						importImage(data);
					}
					svgCanvas.moveSelectedElements(i*20, i*20, false);
				});
				preview.empty();
				multi_arr = [];
				$('#imgbrowse_holder').hide();
			}).css({
				position: 'absolute',
				bottom: 10,
				right: -10
			});

		}

		preview.toggle(show);
		submit.toggle(show);
	}

	function showBrowser() {

		var browser = $('#imgbrowse');
		if(!browser.length) {
			$('

\

').insertAfter('#svg_docprops');
			browser = $('#imgbrowse');

			var all_libs = uiStrings.imagelib.select_lib;

			var lib_opts = $('
').appendTo(browser);
			var frame = $('').prependTo(browser).hide().wrap('
');

			var header = $('
').prependTo(browser).text(all_libs).css({
				position: 'absolute',
				top: 0,
				left: 0,
				width: '100%'
			});

			var cancel = $('<button>' + uiStrings.common.cancel + '</button>')
				.appendTo(browser)
				.on("click touchend", function() {
				$('#imgbrowse_holder').hide();
			}).css({
				position: 'absolute',
				top: 5,
				right: -10
			});

			var leftBlock = $('').css({position:'absolute',top:5,left:10}).appendTo(browser);

			var back = $('<button hidden>' + uiStrings.imagelib.show_list + '</button>')
				.appendTo(leftBlock)
				.on("click touchend", function() {
				frame.attr('src', 'about:blank').hide();
				lib_opts.show();
				header.text(all_libs);
				back.hide();
			}).css({
				'margin-right': 5
			}).hide();

			var type = $('<select><option value=s>' +
			uiStrings.imagelib.import_single + '</option><option value=m>' +
			uiStrings.imagelib.import_multi + '</option><option value=o>' +
			uiStrings.imagelib.open + '</option></select>').appendTo(leftBlock).change(function() {
				mode = $(this).val();
				switch (mode) {
					case 's':
					case 'o':
						toggleMulti(false);
						break;

					case 'm':
						// Import multiple
						toggleMulti(true);
				}
			}).css({
				'margin-top': 10
			});

			cancel.prepend($.getSvgIcon('cancel', true));
			back.prepend($.getSvgIcon('tool_imagelib', true));

			$.each(img_libs, function(i, opts) {
				$('
')
					.appendTo(lib_opts)
					.text(opts.name)
					.on("click touchend", function() {
					frame.attr('src', opts.url).show();
					header.text(opts.name);
					lib_opts.hide();
					back.show();
				}).append('' + opts.description + '');
			});

		} else {
			$('#imgbrowse_holder').show();
		}
	}

	return {
		svgicons: "extensions/ext-imagelib.xml",
		buttons: [{
			id: "tool_imagelib",
			type: "app_menu", // _flyout
			position: 4,
			title: "Image library",
			events: {
				"mouseup": showBrowser
			}
		}],
		callback: function() {

			$('