<commit_before>// @flow
import os from 'os';
import { app, BrowserWindow, shell } from 'electron';
import { client } from 'electron-connect';
import { includes } from 'lodash';
import { Logger } from './utils/logging';
import { setupLogging, updateUserSystemInfoLog } from './utils/setupLogging';
import { getNumberOfEpochsConsolidated } from './utils/getNumberOfEpochsConsolidated';
import { handleDiskSpace } from './utils/handleDiskSpace';
import { createMainWindow } from './windows/main';
import { installChromeExtensions } from './utils/installChromeExtensions';
import { getSystemStartTimeChannel } from './ipc/getSystemStartTime.ipc';
import { environment } from './environment';
import mainErrorHandler from './utils/mainErrorHandler';
import { launcherConfig, frontendOnlyMode } from './config';
import { setupCardano } from './cardano/setup';
import { CardanoNode } from './cardano/CardanoNode';
import { safeExitWithCode } from './utils/safeExitWithCode';
import { buildAppMenus } from './utils/buildAppMenus';
import { getLocale } from './utils/getLocale';
import { ensureXDGDataIsSet } from './cardano/config';
import { rebuildApplicationMenu } from './ipc/rebuild-application-menu';
import { stringifyError } from '../common/utils/logging';
import { CardanoNodeStates } from '../common/types/cardano-node.types';
import type { CheckDiskSpaceResponse } from '../common/types/no-disk-space.types';

// Global references to windows to prevent them from being garbage collected
let mainWindow: BrowserWindow;
let cardanoNode: ?CardanoNode;

const {
  isDev, isWatchMode, network, current, os: osName,
  version: daedalusVersion, buildNumber: cardanoVersion,
} = environment;

const safeExit = async () => {
  if (!cardanoNode || cardanoNode.state === CardanoNodeStates.STOPPED) {
    Logger.info('Daedalus:safeExit: exiting Daedalus with code 0', { code: 0 });
    return safeExitWithCode(0);
  }
  if (cardanoNode.state === CardanoNodeStates.STOPPING) return;
  try {
    const pid = cardanoNode.pid || null;
    Logger.info(`Daedalus:safeExit: stopping cardano-node with PID: ${pid}`, { pid });
    await cardanoNode.stop();
    Logger.info('Daedalus:safeExit: exiting Daedalus with code 0', { code: 0 });
    safeExitWithCode(0);
  } catch (error) {
    Logger.error('Daedalus:safeExit: cardano-node did not exit correctly', { error: `${stringifyError(error)}` });
    safeExitWithCode(0);
  }
};

const onAppReady = async () => {
  setupLogging();

  const cpu = os.cpus();
  const isInSafeMode = includes(process.argv.slice(1), '--safe-mode');
  const platformVersion = os.release();
  const ram = JSON.stringify(os.totalmem(), null, 2);
  const startTime = new Date().toISOString();
  // systemStart refers to the Cardano Demo cluster start time!
  const systemStart = parseInt(launcherConfig.configuration.systemStart, 10);

  const systemInfo = updateUserSystemInfoLog({
    cardanoVersion,
    cpu,
    current,
    daedalusVersion,
    isInSafeMode,
    network,
    osName,
    platformVersion,
    ram,
    startTime,
  });

  Logger.info(`========== Daedalus is starting at ${startTime} ==========`);

  Logger.info('Updating System-info.json file', { ...systemInfo.data });

  ensureXDGDataIsSet();
  await installChromeExtensions(isDev);

  // Detect locale
  let locale = getLocale(network);

  mainWindow = createMainWindow(isInSafeMode, locale);

  const onCheckDiskSpace = ({ isNotEnoughDiskSpace }: CheckDiskSpaceResponse) => {
    // Daedalus is not managing cardano-node in `frontendOnlyMode`
    // so we don't have a way to stop it in case there is not enough disk space
    if (frontendOnlyMode) return;

    if (cardanoNode) {
      if (isNotEnoughDiskSpace) {
        if (
          cardanoNode.state !== CardanoNodeStates.STOPPING &&
          cardanoNode.state !== CardanoNodeStates.STOPPED
        ) {
          try {
            cardanoNode.stop();
          } catch (e) {} // eslint-disable-line
        }
      } else if (
        cardanoNode.state !== CardanoNodeStates.STARTING &&
        cardanoNode.state !== CardanoNodeStates.RUNNING
      ) {
        cardanoNode.restart();
      }
    }
  };
  const handleCheckDiskSpace = handleDiskSpace(mainWindow, onCheckDiskSpace);
  const onMainError = (error: string) => {
    if (error.indexOf('ENOSPC') > -1) {
      handleCheckDiskSpace();
      return false;
    }
  };
  mainErrorHandler(onMainError);
  await handleCheckDiskSpace();

  cardanoNode = setupCardano(launcherConfig, mainWindow);

  if (isWatchMode) {
    // Connect to electron-connect server which restarts / reloads windows on file changes
    client.create(mainWindow);
  }

  getSystemStartTimeChannel.onRequest(() => Promise.resolve(systemStart));

  getNumberOfEpochsConsolidated();

  mainWindow.on('close', async (event) => {
    Logger.info('mainWindow received <close> event. Safe exiting Daedalus now.');
    event.preventDefault();
    await safeExit();
  });

  buildAppMenus(mainWindow, cardanoNode, isInSafeMode, locale);

  await rebuildApplicationMenu.onReceive(() => (
    new Promise(resolve => {
      locale = getLocale(network);
      buildAppMenus(mainWindow, cardanoNode, isInSafeMode, locale);
      mainWindow.updateTitle(locale);
      resolve();
    })
  ));

  // Security feature: Prevent creation of new browser windows
  // https://github.com/electron/electron/blob/master/docs/tutorial/security.md#14-disable-or-limit-creation-of-new-windows
  app.on('web-contents-created', (_, contents) => {
    contents.on('new-window', (event, url) => {
      // Prevent creation of new BrowserWindows via links / window.open
      event.preventDefault();
      Logger.info('Prevented creation of new browser window', { url });
      // Open these links with the default browser
      shell.openExternal(url);
    });
  });

  // Wait for controlled cardano-node shutdown before quitting the app
  app.on('before-quit', async (event) => {
    Logger.info('app received <before-quit> event. Safe exiting Daedalus now.');
    event.preventDefault(); // prevent Daedalus from quitting immediately
    await safeExit();
  });
};

// Make sure this is the only Daedalus instance running per cluster before doing anything else
const isSingleInstance = app.requestSingleInstanceLock();

if (!isSingleInstance) {
  app.quit();
} else {
  app.on('second-instance', () => {
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore();
      mainWindow.focus();
    }
  });
  app.on('ready', onAppReady);
}
<commit_msg>[DDW-517] Fixes flow error<commit_after>// @flow
import os from 'os';
import { app, BrowserWindow, shell } from 'electron';
import { client } from 'electron-connect';
import { includes } from 'lodash';
import { Logger } from './utils/logging';
import { setupLogging, updateUserSystemInfoLog } from './utils/setupLogging';
import { getNumberOfEpochsConsolidated } from './utils/getNumberOfEpochsConsolidated';
import { handleDiskSpace } from './utils/handleDiskSpace';
import { createMainWindow } from './windows/main';
import { installChromeExtensions } from './utils/installChromeExtensions';
import { getSystemStartTimeChannel } from './ipc/getSystemStartTime.ipc';
import { environment } from './environment';
import mainErrorHandler from './utils/mainErrorHandler';
import { launcherConfig, frontendOnlyMode } from './config';
import { setupCardano } from './cardano/setup';
import { CardanoNode } from './cardano/CardanoNode';
import { safeExitWithCode } from './utils/safeExitWithCode';
import { buildAppMenus } from './utils/buildAppMenus';
import { getLocale } from './utils/getLocale';
import { ensureXDGDataIsSet } from './cardano/config';
import { rebuildApplicationMenu } from './ipc/rebuild-application-menu';
import { stringifyError } from '../common/utils/logging';
import { CardanoNodeStates } from '../common/types/cardano-node.types';
import type { CheckDiskSpaceResponse } from '../common/types/no-disk-space.types';

// Global references to windows to prevent them from being garbage collected
let mainWindow: BrowserWindow;
let cardanoNode: ?CardanoNode;

const {
  isDev, isWatchMode, network, current, os: osName,
  version: daedalusVersion, buildNumber: cardanoVersion,
} = environment;

const safeExit = async () => {
  if (!cardanoNode || cardanoNode.state === CardanoNodeStates.STOPPED) {
    Logger.info('Daedalus:safeExit: exiting Daedalus with code 0', { code: 0 });
    return safeExitWithCode(0);
  }
  if (cardanoNode.state === CardanoNodeStates.STOPPING) return;
  try {
    const pid = cardanoNode.pid || 'null';
    Logger.info(`Daedalus:safeExit: stopping cardano-node with PID: ${pid}`, { pid });
    await cardanoNode.stop();
    Logger.info('Daedalus:safeExit: exiting Daedalus with code 0', { code: 0 });
    safeExitWithCode(0);
  } catch (error) {
    Logger.error('Daedalus:safeExit: cardano-node did not exit correctly', { error: `${stringifyError(error)}` });
    safeExitWithCode(0);
  }
};

const onAppReady = async () => {
  setupLogging();

  const cpu = os.cpus();
  const isInSafeMode = includes(process.argv.slice(1), '--safe-mode');
  const platformVersion = os.release();
  const ram = JSON.stringify(os.totalmem(), null, 2);
  const startTime = new Date().toISOString();
  // systemStart refers to the Cardano Demo cluster start time!
  const systemStart = parseInt(launcherConfig.configuration.systemStart, 10);

  const systemInfo = updateUserSystemInfoLog({
    cardanoVersion,
    cpu,
    current,
    daedalusVersion,
    isInSafeMode,
    network,
    osName,
    platformVersion,
    ram,
    startTime,
  });

  Logger.info(`========== Daedalus is starting at ${startTime} ==========`);

  Logger.info('Updating System-info.json file', { ...systemInfo.data });

  ensureXDGDataIsSet();
  await installChromeExtensions(isDev);

  // Detect locale
  let locale = getLocale(network);

  mainWindow = createMainWindow(isInSafeMode, locale);

  const onCheckDiskSpace = ({ isNotEnoughDiskSpace }: CheckDiskSpaceResponse) => {
    // Daedalus is not managing cardano-node in `frontendOnlyMode`
    // so we don't have a way to stop it in case there is not enough disk space
    if (frontendOnlyMode) return;

    if (cardanoNode) {
      if (isNotEnoughDiskSpace) {
        if (
          cardanoNode.state !== CardanoNodeStates.STOPPING &&
          cardanoNode.state !== CardanoNodeStates.STOPPED
        ) {
          try {
            cardanoNode.stop();
          } catch (e) {} // eslint-disable-line
        }
      } else if (
        cardanoNode.state !== CardanoNodeStates.STARTING &&
        cardanoNode.state !== CardanoNodeStates.RUNNING
      ) {
        cardanoNode.restart();
      }
    }
  };
  const handleCheckDiskSpace = handleDiskSpace(mainWindow, onCheckDiskSpace);
  const onMainError = (error: string) => {
    if (error.indexOf('ENOSPC') > -1) {
      handleCheckDiskSpace();
      return false;
    }
  };
  mainErrorHandler(onMainError);
  await handleCheckDiskSpace();

  cardanoNode = setupCardano(launcherConfig, mainWindow);

  if (isWatchMode) {
    // Connect to electron-connect server which restarts / reloads windows on file changes
    client.create(mainWindow);
  }

  getSystemStartTimeChannel.onRequest(() => Promise.resolve(systemStart));

  getNumberOfEpochsConsolidated();

  mainWindow.on('close', async (event) => {
    Logger.info('mainWindow received <close> event. Safe exiting Daedalus now.');
    event.preventDefault();
    await safeExit();
  });

  buildAppMenus(mainWindow, cardanoNode, isInSafeMode, locale);

  await rebuildApplicationMenu.onReceive(() => (
    new Promise(resolve => {
      locale = getLocale(network);
      buildAppMenus(mainWindow, cardanoNode, isInSafeMode, locale);
      mainWindow.updateTitle(locale);
      resolve();
    })
  ));

  // Security feature: Prevent creation of new browser windows
  // https://github.com/electron/electron/blob/master/docs/tutorial/security.md#14-disable-or-limit-creation-of-new-windows
  app.on('web-contents-created', (_, contents) => {
    contents.on('new-window', (event, url) => {
      // Prevent creation of new BrowserWindows via links / window.open
      event.preventDefault();
      Logger.info('Prevented creation of new browser window', { url });
      // Open these links with the default browser
      shell.openExternal(url);
    });
  });

  // Wait for controlled cardano-node shutdown before quitting the app
  app.on('before-quit', async (event) => {
    Logger.info('app received <before-quit> event. Safe exiting Daedalus now.');
    event.preventDefault(); // prevent Daedalus from quitting immediately
    await safeExit();
  });
};

// Make sure this is the only Daedalus instance running per cluster before doing anything else
const isSingleInstance = app.requestSingleInstanceLock();

if (!isSingleInstance) {
  app.quit();
} else {
  app.on('second-instance', () => {
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore();
      mainWindow.focus();
    }
  });
  app.on('ready', onAppReady);
}
<|endoftext|>


<commit_before>;(function() {

Editor = function() {}

Editor.prototype = {
  init: function(elem, data) {
    // UI elements
    this.$container      = elem;
    this.$itemsContainer = elem.find('[data-editor-items]');
    this.$title          = elem.find('[data-editor-title]');
    this.$addButton      = elem.find('[data-editor-add]');
    this.$publishButton  = elem.find('[data-editor-publish]');

    this.lastSearch = '';

    // Content item template
    var template = $('#content-template').html();
    this.contentItem = Handlebars.compile(template);

    // Add event listeners
    this.events();

    if (typeof data === 'object') {
      this.deserialize(data);
    }
    else {
      this.addItem();
    }
  },
  events: function() {
    var _this = this;

    // Make each item sortable
    this.$itemsContainer.sortable({
      handle: '[data-editor-handle]',
      placeholder: 'editor__placeholder'
    });

    // Block carriage returns on title editor, blur the field instead
    this.$title.on('keypress', function(event) {
      if (event.which === 13) {
        $(this).blur();
      }
    });

    // Add text or image units when those buttons are clicked
    this.$addButton.click(function() {
      _this.addItem();
    });

    // Delete an item when that button is clicked
    this.$itemsContainer.on('click', '[data-editor-delete]', function() {
      _this.deleteItem($(this));
    });

    // Serialize and save when publish button is clicked
    this.$publishButton.click(function() {
      _this.save();
      return false;
    })
  },
  addItem: function(data) {
    var _this = this
    var $contentItem = $(this.contentItem(data || {}));
    $contentItem
      .appendTo(this.$itemsContainer)
      .hide(0)
      .slideDown(250);
    var gif_searcher = new GIFSearcher($contentItem.find('.gif-searcher'), this);

    this.$container.find('.content-item__body').on('keypress', function(event) {
      if (event.which == '.'.charCodeAt(0) || event.which == '?'.charCodeAt(0) || event.which == "!".charCodeAt(0)) {
        var content = $(this).val();
        var request = $.getJSON('http://guarded-caverns-4613.herokuapp.com/ner', {text: content});
        request.done(function(data) {
          var ul = _this.$container.find('.gif-searcher__suggest > ul');
          ul.empty();
          $.each(data.best_guess, function(index, item) {
                  var list_item = $('
'+item+'
')
                  ul.append(list_item);
                  list_item.click(function() {
                          gif_searcher.getImages(item)
                      });
              });
        });
      }
    });
  },
  deleteItem: function(elem) {
    elem.closest('.content-item').remove();
  },
  serialize: function() {
    var json = [];
    this.$itemsContainer.find('.content-item').each(function() {
      json.push({
        'title': $(this).find('.content-item__title').val(),
        'image': $(this).find('.gif-searcher__show > img').attr('src'),
        'body': $(this).find('.content-item__body').val()
      });
    });

    return json;
  },
  save: function() {
    return this.serialize();
  },
  deserialize: function(data) {
    var _this = this;

    $.each(data, function(index, item) {
      _this.addItem(item);
    });
  }
}

var GIFSearcher = function($elem, editor) {
  this.$container = $elem;

  this.views = {
    search:  this.$container.children('.gif-searcher__search'),
    results: this.$container.children('.gif-searcher__results'),
    image:   this.$container.children('.gif-searcher__show'),
    suggest: this.$container.children('.gif-searcher__suggest')
  }
  this.query = '';

  this.editor = editor;

  this.events();

  if (typeof this.views.search.children('img').attr('src') === 'undefined') {
    this.showSearch();
  }
  else {
    this.showImage();
  }
}
GIFSearcher.prototype = {
  events: function() {
    var _this = this;

    this.views.search.children('input').on('keypress', function(event) {
      if (event.which !== 13) return;

      var searchTerm = this.value;
      var search = _this.getImages(searchTerm);
    });

    this.views.results.on('click', 'li', function() {
      var src = $(this).children('img').attr('src');
      _this.showImage(src);
    });
  },

  showSearch: function() {
    this.views.search.show();
    this.views.results.hide();
    this.views.image.hide();

    if (this.editor.lastSearch) {
      this.views.search.children('input').val(this.editor.lastSearch);
    }
  },
  showResults: function(data) {
    var _this = this;

    this.views.search.hide();
    this.views.results.show();
    this.views.image.hide();

    // If there's no data, do a search for some
    if (typeof data === 'undefined') {

    }
    else {
      var $container = this.views.results.children('ul').eq(0);
      $container.empty();
      $.each(data.data, function(i, image) {
        var $item = $('

');
        $container.append($item);
      });
    }
  },
  showImage: function(src) {
    this.views.search.hide();
    this.views.results.hide();
    this.views.image.show();

    if (typeof src !== 'undefined') {
      this.views.image.children('img').attr('src', src);
    }
  },

  getImages: function(query) {
        var _this = this;
        fn = function() {
            // Set a default
            if (typeof query === 'undefined' || query == '') query = this.query || 'cats';
            // Encode it
            query = encodeURI(query.replace(" ","+"));

            var queryURL = 'http://api.giphy.com/v1/gifs/search'
            , params   = {q: query, limit: 9, api_key: 'dc6zaTOxFJmzC'}

            var search = $.getJSON(queryURL, params);
            return search;
        }().done(function(data, textStatus, jqxhr) {
                if (data.data.length === 0) {
                    _this.views.search.append('
Couldn\'t find any images :( Try again?');
                }
                else {
                    _this.editor.lastSearch = query;
                    _this.showResults(data);
                }
            })
        .fail(function(jqxhr, textStatus, error) {
                console.log(textStatus+', '+error);
            });
  }
}

})();<commit_msg>Fixed suggest with multiple sections<commit_after>;(function() {

Editor = function() {}

Editor.prototype = {
  init: function(elem, data) {
    // UI elements
    this.$container      = elem;
    this.$itemsContainer = elem.find('[data-editor-items]');
    this.$title          = elem.find('[data-editor-title]');
    this.$addButton      = elem.find('[data-editor-add]');
    this.$publishButton  = elem.find('[data-editor-publish]');

    this.lastSearch = '';

    // Content item template
    var template = $('#content-template').html();
    this.contentItem = Handlebars.compile(template);

    // Add event listeners
    this.events();

    if (typeof data === 'object') {
      this.deserialize(data);
    }
    else {
      this.addItem();
    }
  },
  events: function() {
    var _this = this;

    // Make each item sortable
    this.$itemsContainer.sortable({
      handle: '[data-editor-handle]',
      placeholder: 'editor__placeholder'
    });

    // Block carriage returns on title editor, blur the field instead
    this.$title.on('keypress', function(event) {
      if (event.which === 13) {
        $(this).blur();
      }
    });

    // Add text or image units when those buttons are clicked
    this.$addButton.click(function() {
      _this.addItem();
    });

    // Delete an item when that button is clicked
    this.$itemsContainer.on('click', '[data-editor-delete]', function() {
      _this.deleteItem($(this));
    });

    // Serialize and save when publish button is clicked
    this.$publishButton.click(function() {
      _this.save();
      return false;
    })
  },
  addItem: function(data) {
    var _this = this;
    var $contentItem = $(this.contentItem(data || {}));
    $contentItem
      .appendTo(this.$itemsContainer)
      .hide(0)
      .slideDown(250);
    var gif_searcher = new GIFSearcher($contentItem.find('.gif-searcher'), this);

    $contentItem.find('.content-item__body').on('keypress', function(event) {
      if (event.which == '.'.charCodeAt(0) || event.which == '?'.charCodeAt(0) || event.which == "!".charCodeAt(0)) {
        var content = $(this).val();
        var request = $.getJSON('http://guarded-caverns-4613.herokuapp.com/ner', {text: content});
        request.done(function(data) {
          var ul = $contentItem.find('.gif-searcher__suggest > ul');
          ul.empty();
          $.each(data.best_guess, function(index, item) {
                  var list_item = $('
'+item+'
')
                  ul.append(list_item);
                  list_item.click(function() {
                          gif_searcher.getImages(item)
                      });
              });
        });
      }
    });
  },
  deleteItem: function(elem) {
    elem.closest('.content-item').remove();
  },
  serialize: function() {
    var json = [];
    this.$itemsContainer.find('.content-item').each(function() {
      json.push({
        'title': $(this).find('.content-item__title').val(),
        'image': $(this).find('.gif-searcher__show > img').attr('src'),
        'body': $(this).find('.content-item__body').val()
      });
    });

    return json;
  },
  save: function() {
    return this.serialize();
  },
  deserialize: function(data) {
    var _this = this;

    $.each(data, function(index, item) {
      _this.addItem(item);
    });
  }
}

var GIFSearcher = function($elem, editor) {
  this.$container = $elem;

  this.views = {
    search:  this.$container.children('.gif-searcher__search'),
    results: this.$container.children('.gif-searcher__results'),
    image:   this.$container.children('.gif-searcher__show'),
    suggest: this.$container.children('.gif-searcher__suggest')
  }
  this.query = '';

  this.editor = editor;

  this.events();

  if (typeof this.views.search.children('img').attr('src') === 'undefined') {
    this.showSearch();
  }
  else {
    this.showImage();
  }
}
GIFSearcher.prototype = {
  events: function() {
    var _this = this;

    this.views.search.children('input').on('keypress', function(event) {
      if (event.which !== 13) return;

      var searchTerm = this.value;
      var search = _this.getImages(searchTerm);
    });

    this.views.results.on('click', 'li', function() {
      var src = $(this).children('img').attr('src');
      _this.showImage(src);
    });
  },

  showSearch: function() {
    this.views.search.show();
    this.views.results.hide();
    this.views.image.hide();

    if (this.editor.lastSearch) {
      this.views.search.children('input').val(this.editor.lastSearch);
    }
  },
  showResults: function(data) {
    var _this = this;

    this.views.search.hide();
    this.views.results.show();
    this.views.image.hide();

    // If there's no data, do a search for some
    if (typeof data === 'undefined') {

    }
    else {
      var $container = this.views.results.children('ul').eq(0);
      $container.empty();
      $.each(data.data, function(i, image) {
        var $item = $('

');
        $container.append($item);
      });
    }
  },
  showImage: function(src) {
    this.views.search.hide();
    this.views.results.hide();
    this.views.image.show();

    if (typeof src !== 'undefined') {
      this.views.image.children('img').attr('src', src);
    }
  },

  getImages: function(query) {
        var _this = this;
        fn = function() {
            // Set a default
            if (typeof query === 'undefined' || query == '') query = this.query || 'cats';
            // Encode it
            query = encodeURI(query.replace(" ","+"));

            var queryURL = 'http://api.giphy.com/v1/gifs/search'
            , params   = {q: query, limit: 9, api_key: 'dc6zaTOxFJmzC'}

            var search = $.getJSON(queryURL, params);
            return search;
        }().done(function(data, textStatus, jqxhr) {
                if (data.data.length === 0) {
                    _this.views.search.append('
Couldn\'t find any images :( Try again?');
                }
                else {
                    _this.editor.lastSearch = query;
                    _this.showResults(data);
                }
            })
        .fail(function(jqxhr, textStatus, error) {
                console.log(textStatus+', '+error);
            });
  }
}

})();<|endoftext|>


<commit_before>// Copyright 2014-2015, University of Colorado Boulder

/**
 * Node that represents a block in the view.  The blocks in the model are 2D, and this class gives them some perspective
 * in order to make them appear to be 3D.
 *
 * @author John Blanco
 * @author Martin Veillette
 * @author Jesse Greenberg
 */
define( function( require ) {
  'use strict';

  // modules
  var Color = require( 'SCENERY/util/Color' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunkNode = require( 'ENERGY_FORMS_AND_CHANGES/common/view/EnergyChunkNode' );
  var EnergyChunkContainerSliceNode = require( 'ENERGY_FORMS_AND_CHANGES/intro/view/EnergyChunkContainerSliceNode' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Shape = require( 'KITE/Shape' );
  var Text = require( 'SCENERY/nodes/Text' );
  var ThermalElementDragHandler = require( 'ENERGY_FORMS_AND_CHANGES/intro/view/ThermalElementDragHandler' );
  var ThermalItemMotionConstraint = require( 'ENERGY_FORMS_AND_CHANGES/intro/view/ThermalItemMotionConstraint' );
  var Vector2 = require( 'DOT/Vector2' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Transform3 = require( 'DOT/Transform3' );
  var Matrix3 = require( 'DOT/Matrix3' );

  // constants
  var LABEL_FONT = new PhetFont( 32, false );
  var OUTLINE_LINEWIDTH = 3;
  var OUTLINE_STROKE = Color.DARK_GRAY;

  var SHOW_2D_REPRESENTATION = true;

  /**
   * Constructor for a BlockNode.
   *
   * @param model
   * @param {Block} block
   * @param {Bounds2} stageBounds
   * @param {ModelViewTransform2} modelViewTransform
   * @constructor
   */
  function BlockNode( model, block, stageBounds, modelViewTransform ) {

    var thisNode = this;

    Node.call( this, {
      cursor: 'pointer'
    } );

    this.block = block;
    this.approachingEnergyChunkParentNode = null;
    this.modelViewTransform = modelViewTransform;

    // Extract the scale transform from the MVT so that we can separate the shape from the position of the block.
    var scaleVector = modelViewTransform.matrix.getScaleVector();
    var scaleTransform = new Transform3( Matrix3.scaling( scaleVector.x, scaleVector.y ) );

    // Note that blockRect is in view coordinates.
    // The shift by the block height is not in the original Java, but without it, the blocks sit too low.
    var blockShape = block.getRawShape();
    var blockRect = scaleTransform.transformShape( blockShape.shiftedY( -blockShape.height ) );

    // Create the shape for the front of the block.
    var perspectiveEdgeSize = modelViewTransform.modelToViewDeltaX( block.getBounds().width * EFACConstants.BLOCK_PERSPECTIVE_EDGE_PROPORTION );
    var blockFaceOffset = new Vector2( -perspectiveEdgeSize / 2, 0 ).rotated( -EFACConstants.BLOCK_PERSPECTIVE_ANGLE );
    var backCornersOffset = new Vector2( perspectiveEdgeSize, 0 ).rotated( -EFACConstants.BLOCK_PERSPECTIVE_ANGLE );
    var lowerLeftFrontCorner = new Vector2( blockRect.minX, blockRect.getMaxY() ).plus( blockFaceOffset );
    var lowerRightFrontCorner = new Vector2( blockRect.maxX, blockRect.getMaxY() ).plus( blockFaceOffset );
    var upperRightFrontCorner = new Vector2( blockRect.maxX, blockRect.getMinY() ).plus( blockFaceOffset );
    var upperLeftFrontCorner = new Vector2( blockRect.minX, blockRect.getMinY() ).plus( blockFaceOffset );
    var blockFaceShape = Shape.rectangle( lowerLeftFrontCorner.x, upperLeftFrontCorner.y, blockRect.width, blockRect.height );

    // Create the shape of the top of the block.
    var upperLeftBackCorner = upperLeftFrontCorner.plus( backCornersOffset );
    var upperRightBackCorner = upperRightFrontCorner.plus( backCornersOffset );
    var blockTopShape = new Shape();
    blockTopShape.moveToPoint( upperLeftFrontCorner )
      .lineToPoint( upperRightFrontCorner )
      .lineToPoint( upperRightBackCorner )
      .lineToPoint( upperLeftBackCorner )
      .lineToPoint( upperLeftFrontCorner );

    // Create the shape of the side of the block.
    var lowerRightBackCorner = lowerRightFrontCorner.plus( backCornersOffset );
    var blockSideShape = new Shape();
    blockSideShape.moveToPoint( upperRightFrontCorner )
      .lineToPoint( lowerRightFrontCorner )
      .lineToPoint( lowerRightBackCorner )
      .lineToPoint( upperRightBackCorner )
      .lineToPoint( upperRightFrontCorner );

    // Create the shape for the back of the block.
    var lowerLeftBackCorner = lowerLeftFrontCorner.plus( backCornersOffset );
    var blockBackShape = new Shape();
    blockBackShape.moveToPoint( lowerLeftBackCorner )
      .lineToPoint( lowerRightBackCorner )
      .moveToPoint( lowerLeftBackCorner )
      .lineToPoint( lowerLeftFrontCorner )
      .moveToPoint( lowerLeftBackCorner )
      .lineToPoint( upperLeftBackCorner );

    // Add the back of the block.
    var blockBack = new Path( blockBackShape, {
      lineWidth: OUTLINE_LINEWIDTH,
      stroke: OUTLINE_STROKE
    } );
    this.addChild( blockBack );

    // Create the layers where the energy chunks will be placed.
    this.energyChunkRootNode = new Node();
    this.addChild( this.energyChunkRootNode ); // TODO: Adding this later.  There seems to be some layout issue with this.
    for ( var i = block.slices.length - 1; i >= 0; i-- ) {
      this.energyChunkRootNode.addChild( new EnergyChunkContainerSliceNode( block.slices[ i ], modelViewTransform ) );
    }

    // Add the face, top, and sides of the block.
    var blockFace = this.createSurface( blockFaceShape, block.getColor(), block.getFrontTextureImage() );
    var blockTop = this.createSurface( blockTopShape, block.getColor(), block.getTopTextureImage() );
    var blockSide = this.createSurface( blockSideShape, block.getColor(), block.getSideTextureImage() );
    this.addChild( blockFace );
    this.addChild( blockTop );
    this.addChild( blockSide );

    if ( SHOW_2D_REPRESENTATION ) {
      this.addChild( new Rectangle( modelViewTransform.modelToViewBounds( block.getRawShape(), {
        fill: 'red',
        lineWidth: 1
      } ) ) );
    }

    // Position and add the label.
    var label = new Text( block.getLabel() );
    label.setFont( LABEL_FONT );
    if ( label.bounds.width >= modelViewTransform.modelToViewDeltaX( EFACConstants.BLOCK_SURFACE_WIDTH * 0.9 ) ) {
      // Scale the label to fit on the face of the block.  This also supports translations.
      var scale = ( modelViewTransform.modelToViewDeltaX( EFACConstants.BLOCK_SURFACE_WIDTH * 0.9 ) / label.bounds.width );
      label.setScale( scale );
    }
    var labelCenterX = ( upperLeftFrontCorner.x + upperRightFrontCorner.x ) / 2;
    var labelCenterY = ( upperLeftFrontCorner.y - modelViewTransform.modelToViewDeltaY( EFACConstants.BLOCK_SURFACE_WIDTH ) / 2 );
    label.center = new Vector2( labelCenterX, labelCenterY );
    this.addChild( label );

    // Watch for coming and going of energy chunks that are approaching this model element and add/remove them as needed.
    block.approachingEnergyChunks.addItemAddedListener( function( addedEnergyChunk ) {
      var energyChunkNode = new EnergyChunkNode( addedEnergyChunk, modelViewTransform );

      var parentNode = ( thisNode.approachingEnergyChunkParentNode === null ) ?
        thisNode.energyChunkRootNode :
        thisNode.approachingEnergyChunkParentNode;

      parentNode.addChild( energyChunkNode );

      block.approachingEnergyChunks.addItemRemovedListener( function removalListener( removedEnergyChunk ) {
        if ( removedEnergyChunk === addedEnergyChunk ) {
          console.log( 'BN: Removing chunk node (approaching chunks)' );
          parentNode.removeChild( energyChunkNode );
          block.approachingEnergyChunks.removeItemRemovedListener( removalListener );
        }
      } );
    } );

    // Make the block be transparent when the energy chunks are visible so that it looks like they are in the block.
    block.energyChunksVisibleProperty.link( function( energyChunksVisible ) {
      var opaqueness = energyChunksVisible ? 0.5 : 1.0;
      blockFace.opacity = opaqueness;
      blockTop.opacity = opaqueness;
      blockSide.opacity = opaqueness;
      label.opacity = opaqueness;
    } );

    // Update the offset if and when the model position changes.
    block.positionProperty.link( function( newPosition ) {

      thisNode.translation = modelViewTransform.modelToViewPosition( newPosition );

      // Compensate the energy chunk layer so that the energy chunk nodes can handle their own positioning.
      thisNode.energyChunkRootNode.translation =
        modelViewTransform.modelToViewPosition( newPosition ).rotated( Math.PI );
      // var offset = block.getRawShape().height;
      // thisNode.translation = modelViewTransform.modelToViewPosition( newPosition.plusXY( 0, offset ) );

      // // Compensate the energy chunk layer so that the energy chunk nodes can handle their own positioning.
      // thisNode.energyChunkRootNode.translation =
      //   modelViewTransform.modelToViewPosition( newPosition.plusXY( 0, offset ) ).rotated( Math.PI );
    } );

    // Add the drag handler.
    var offsetPosToCenter = new Vector2(
      this.bounds.centerX - modelViewTransform.modelToViewX( block.position.x ),
      this.bounds.centerY - modelViewTransform.modelToViewY( block.position.y ) );
    this.addInputListener( new ThermalElementDragHandler( block, this, modelViewTransform,
      new ThermalItemMotionConstraint( model, block, this, stageBounds, modelViewTransform, offsetPosToCenter ) ) );
  }

  energyFormsAndChanges.register( 'BlockNode', BlockNode );

  return inherit( Node, BlockNode, {

    /**
     *
     * @param {Node} node
     */
    setApproachingEnergyChunkParentNode: function( node ) {
      // This should not be set more than once.
      assert && assert( this.approachingEnergyChunkParentNode === null );
      this.approachingEnergyChunkParentNode = node;
    },

    /**
     * Convenience method to avoid code duplication.  Adds a node of the given shape, color, and texture (if a texture
     * is specified).
     *
     * @param {Shape} shape
     * @param {Color} fillColor
     * @param {Image} textureImage
     * @returns {Node}
     * @private
     */
    createSurface: function( shape, fillColor, textureImage ) {

      var root = new Node( {
        clipArea: shape
      } );

      // Add the filled shape.  Note that in cases where a texture is provided, this may end up getting partially or
      // entirely covered up.
      root.addChild( new Path( shape, {
        fill: fillColor
      } ) );

      if ( textureImage !== null ) {

        // Add the texture image.
        var texture = new Image( textureImage );

        // Scale up the texture image if needed.
        var textureScale = 1;
        if ( texture.bounds.width < shape.bounds.width ) {
          textureScale = shape.bounds.width / texture.bounds.width;
        }
        if ( texture.bounds.height < shape.bounds.height ) {
          textureScale = Math.max( shape.bounds.height / texture.bounds.height, textureScale );
        }
        texture.scale( textureScale );

        // Add the texture to the clip node in order to clip it.
        texture.leftTop = new Vector2( shape.bounds.minX, shape.bounds.minY );
        root.addChild( texture );
      }

      // Add the outlined shape so that edges are visible.
      root.addChild( new Path( shape, {
        lineWidth: OUTLINE_LINEWIDTH,
        stroke: OUTLINE_STROKE
      } ) );

      return root;

    }
  } );
} );

<commit_msg>BlockNode cleanup<commit_after>// Copyright 2014-2015, University of Colorado Boulder

/**
 * Node that represents a block in the view.  The blocks in the model are 2D, and this class gives them some perspective
 * in order to make them appear to be 3D.
 *
 * @author John Blanco
 * @author Martin Veillette
 * @author Jesse Greenberg
 */
define( function( require ) {
  'use strict';

  // modules
  var Color = require( 'SCENERY/util/Color' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunkNode = require( 'ENERGY_FORMS_AND_CHANGES/common/view/EnergyChunkNode' );
  var EnergyChunkContainerSliceNode = require( 'ENERGY_FORMS_AND_CHANGES/intro/view/EnergyChunkContainerSliceNode' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Shape = require( 'KITE/Shape' );
  var Text = require( 'SCENERY/nodes/Text' );
  var ThermalElementDragHandler = require( 'ENERGY_FORMS_AND_CHANGES/intro/view/ThermalElementDragHandler' );
  var ThermalItemMotionConstraint = require( 'ENERGY_FORMS_AND_CHANGES/intro/view/ThermalItemMotionConstraint' );
  var Vector2 = require( 'DOT/Vector2' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Transform3 = require( 'DOT/Transform3' );
  var Matrix3 = require( 'DOT/Matrix3' );

  // constants
  var LABEL_FONT = new PhetFont( 32, false );
  var OUTLINE_LINEWIDTH = 3;
  var OUTLINE_STROKE = Color.DARK_GRAY;

  var SHOW_2D_REPRESENTATION = true;

  /**
   * Constructor for a BlockNode.
   *
   * @param model
   * @param {Block} block
   * @param {Bounds2} stageBounds
   * @param {ModelViewTransform2} modelViewTransform
   * @constructor
   */
  function BlockNode( model, block, stageBounds, modelViewTransform ) {

    var self = this;

    Node.call( this, {
      cursor: 'pointer'
    } );

    this.block = block;
    this.approachingEnergyChunkParentNode = null;
    this.modelViewTransform = modelViewTransform;

    // Extract the scale transform from the MVT so that we can separate the shape from the position of the block.
    var scaleVector = modelViewTransform.matrix.getScaleVector();
    var scaleTransform = new Transform3( Matrix3.scaling( scaleVector.x, scaleVector.y ) );

    // Note that blockRect is in view coordinates.
    // The shift by the block height is not in the original Java, but without it, the blocks sit too low.
    var blockShape = block.getRawShape();
    var blockRect = scaleTransform.transformShape( blockShape.shiftedY( -blockShape.height ) );

    // Create the shape for the front of the block.
    var perspectiveEdgeSize = modelViewTransform.modelToViewDeltaX(
      block.getBounds().width * EFACConstants.BLOCK_PERSPECTIVE_EDGE_PROPORTION );
    var blockFaceOffset = new Vector2( -perspectiveEdgeSize / 2, 0 ).rotated( -EFACConstants.BLOCK_PERSPECTIVE_ANGLE );
    var backCornersOffset = new Vector2( perspectiveEdgeSize, 0 ).rotated( -EFACConstants.BLOCK_PERSPECTIVE_ANGLE );
    var lowerLeftFrontCorner = new Vector2( blockRect.minX, blockRect.getMaxY() ).plus( blockFaceOffset );
    var lowerRightFrontCorner = new Vector2( blockRect.maxX, blockRect.getMaxY() ).plus( blockFaceOffset );
    var upperRightFrontCorner = new Vector2( blockRect.maxX, blockRect.getMinY() ).plus( blockFaceOffset );
    var upperLeftFrontCorner = new Vector2( blockRect.minX, blockRect.getMinY() ).plus( blockFaceOffset );
    var blockFaceShape = Shape.rectangle(
      lowerLeftFrontCorner.x,
      upperLeftFrontCorner.y,
      blockRect.width,
      blockRect.height );

    // Create the shape of the top of the block.
    var upperLeftBackCorner = upperLeftFrontCorner.plus( backCornersOffset );
    var upperRightBackCorner = upperRightFrontCorner.plus( backCornersOffset );
    var blockTopShape = new Shape();
    blockTopShape.moveToPoint( upperLeftFrontCorner )
      .lineToPoint( upperRightFrontCorner )
      .lineToPoint( upperRightBackCorner )
      .lineToPoint( upperLeftBackCorner )
      .lineToPoint( upperLeftFrontCorner );

    // Create the shape of the side of the block.
    var lowerRightBackCorner = lowerRightFrontCorner.plus( backCornersOffset );
    var blockSideShape = new Shape();
    blockSideShape.moveToPoint( upperRightFrontCorner )
      .lineToPoint( lowerRightFrontCorner )
      .lineToPoint( lowerRightBackCorner )
      .lineToPoint( upperRightBackCorner )
      .lineToPoint( upperRightFrontCorner );

    // Create the shape for the back of the block.
    var lowerLeftBackCorner = lowerLeftFrontCorner.plus( backCornersOffset );
    var blockBackShape = new Shape();
    blockBackShape.moveToPoint( lowerLeftBackCorner )
      .lineToPoint( lowerRightBackCorner )
      .moveToPoint( lowerLeftBackCorner )
      .lineToPoint( lowerLeftFrontCorner )
      .moveToPoint( lowerLeftBackCorner )
      .lineToPoint( upperLeftBackCorner );

    // Add the back of the block.
    var blockBack = new Path( blockBackShape, {
      lineWidth: OUTLINE_LINEWIDTH,
      stroke: OUTLINE_STROKE
    } );
    this.addChild( blockBack );

    // Create the layers where the energy chunks will be placed.
    this.energyChunkRootNode = new Node();
    this.addChild( this.energyChunkRootNode );
    for ( var i = block.slices.length - 1; i >= 0; i-- ) {
      this.energyChunkRootNode.addChild( new EnergyChunkContainerSliceNode( block.slices[ i ], modelViewTransform ) );
    }

    // Add the face, top, and sides of the block.
    var blockFace = this.createSurface( blockFaceShape, block.getColor(), block.getFrontTextureImage() );
    var blockTop = this.createSurface( blockTopShape, block.getColor(), block.getTopTextureImage() );
    var blockSide = this.createSurface( blockSideShape, block.getColor(), block.getSideTextureImage() );
    this.addChild( blockFace );
    this.addChild( blockTop );
    this.addChild( blockSide );

    if ( SHOW_2D_REPRESENTATION ) {
      this.addChild( new Rectangle( modelViewTransform.modelToViewBounds( block.getRawShape(), {
        fill: 'red',
        lineWidth: 1
      } ) ) );
    }

    // Position and add the label.
    var label = new Text( block.getLabel() );
    label.setFont( LABEL_FONT );
    if ( label.bounds.width >= modelViewTransform.modelToViewDeltaX( EFACConstants.BLOCK_SURFACE_WIDTH * 0.9 ) ) {
      // Scale the label to fit on the face of the block.  This also supports translations.
      var scale = modelViewTransform.modelToViewDeltaX( EFACConstants.BLOCK_SURFACE_WIDTH * 0.9 ) / label.bounds.width;
      label.setScale( scale );
    }
    var labelCenterX = ( upperLeftFrontCorner.x + upperRightFrontCorner.x ) / 2;
    var labelCenterY =
      ( upperLeftFrontCorner.y - modelViewTransform.modelToViewDeltaY( EFACConstants.BLOCK_SURFACE_WIDTH ) / 2 );
    label.center = new Vector2( labelCenterX, labelCenterY );
    this.addChild( label );

    // Watch for coming and going of energy chunks that are approaching
    // this model element and add/remove them as needed.
    block.approachingEnergyChunks.addItemAddedListener( function( addedEnergyChunk ) {
      var energyChunkNode = new EnergyChunkNode( addedEnergyChunk, modelViewTransform );

      var parentNode = ( self.approachingEnergyChunkParentNode === null ) ?
        self.energyChunkRootNode :
        self.approachingEnergyChunkParentNode;

      parentNode.addChild( energyChunkNode );

      block.approachingEnergyChunks.addItemRemovedListener( function removalListener( removedEnergyChunk ) {
        if ( removedEnergyChunk === addedEnergyChunk ) {
          console.log( 'BN: Removing chunk node (approaching chunks)' );
          parentNode.removeChild( energyChunkNode );
          block.approachingEnergyChunks.removeItemRemovedListener( removalListener );
        }
      } );
    } );

    // Make the block be transparent when the energy chunks are visible so that it looks like they are in the block.
    block.energyChunksVisibleProperty.link( function( energyChunksVisible ) {
      var opaqueness = energyChunksVisible ? 0.5 : 1.0;
      blockFace.opacity = opaqueness;
      blockTop.opacity = opaqueness;
      blockSide.opacity = opaqueness;
      label.opacity = opaqueness;
    } );

    // Update the offset if and when the model position changes.
    block.positionProperty.link( function( newPosition ) {

      self.translation = modelViewTransform.modelToViewPosition( newPosition );

      // Compensate the energy chunk layer so that the energy chunk nodes can handle their own positioning.
      self.energyChunkRootNode.translation =
        modelViewTransform.modelToViewPosition( newPosition ).rotated( Math.PI );
    } );

    // Add the drag handler.
    var offsetPosToCenter = new Vector2(
      this.bounds.centerX - modelViewTransform.modelToViewX( block.position.x ),
      this.bounds.centerY - modelViewTransform.modelToViewY( block.position.y ) );
    this.addInputListener( new ThermalElementDragHandler( block, this, modelViewTransform,
      new ThermalItemMotionConstraint( model, block, this, stageBounds, modelViewTransform, offsetPosToCenter ) ) );
  }

  energyFormsAndChanges.register( 'BlockNode', BlockNode );

  return inherit( Node, BlockNode, {

    /**
     *
     * @param {Node} node
     */
    setApproachingEnergyChunkParentNode: function( node ) {
      // This should not be set more than once.
      assert && assert( this.approachingEnergyChunkParentNode === null );
      this.approachingEnergyChunkParentNode = node;
    },

    /**
     * Convenience method to avoid code duplication.  Adds a node of the given shape, color, and texture (if a texture
     * is specified).
     *
     * @param {Shape} shape
     * @param {Color} fillColor
     * @param {Image} textureImage
     * @returns {Node}
     * @private
     */
    createSurface: function( shape, fillColor, textureImage ) {

      var root = new Node( {
        clipArea: shape
      } );

      // Add the filled shape.  Note that in cases where a texture is provided, this may end up getting partially or
      // entirely covered up.
      root.addChild( new Path( shape, {
        fill: fillColor
      } ) );

      if ( textureImage !== null ) {

        // Add the texture image.
        var texture = new Image( textureImage );

        // Scale up the texture image if needed.
        var textureScale = 1;
        if ( texture.bounds.width < shape.bounds.width ) {
          textureScale = shape.bounds.width / texture.bounds.width;
        }
        if ( texture.bounds.height < shape.bounds.height ) {
          textureScale = Math.max( shape.bounds.height / texture.bounds.height, textureScale );
        }
        texture.scale( textureScale );

        // Add the texture to the clip node in order to clip it.
        texture.leftTop = new Vector2( shape.bounds.minX, shape.bounds.minY );
        root.addChild( texture );
      }

      // Add the outlined shape so that edges are visible.
      root.addChild( new Path( shape, {
        lineWidth: OUTLINE_LINEWIDTH,
        stroke: OUTLINE_STROKE
      } ) );

      return root;

    }
  } );
} );

<|endoftext|>


<commit_before>module.exports = function (grunt) {
    grunt.initConfig({
        "pkg": grunt.file.readJSON("package.json"),
        "meta": {
            "paths": {
                "source": "Source",
                "dist": "Distribution"
            }
        },
        "tslint": {
            "options": {
                "configuration": grunt.file.readJSON("tslint.json")
            },
            "files": {
                "src": ["<%= meta.paths.source %>/<%= pkg.name %>.ts"]
            }
        },
        "typescript": {
            "base": {
                "src": "<%= meta.paths.source %>/<%= pkg.name %>.ts",
                "dest": "<%= meta.paths.source %>/<%= pkg.name %>.js"
            }
        },
        "clean": ["<%= meta.paths.dist %>"],
        "copy": {
            "default": {
                "files": [{
                    "src": "<%= meta.paths.source %>/<%= pkg.name %>.js",
                    "dest": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.js"
                }, {
                    "src": "<%= meta.paths.source %>/<%= pkg.name %>.ts",
                    "dest": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.ts"
                }, {
                    "src": "<%= meta.paths.source %>/References/*.ts",
                    "dest": "<%= meta.paths.dist %>/",
                    "expand": true,
                    "flatten": true
                }, {
                    "src": "README.md",
                    "dest": "<%= meta.paths.dist %>/"
                }, {
                    "src": "README.md",
                    "dest": "<%= meta.paths.source %>/"
                }, {
                    "src": "LICENSE.txt",
                    "dest": "<%= meta.paths.dist %>/"
                }, {
                    "src": "LICENSE.txt",
                    "dest": "<%= meta.paths.source %>/"
                }]
            }
        },
        "uglify": {
            "options": {
                "compress": true
            },
            "dist": {
                "files": {
                    "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.min.js": ["<%= meta.paths.source %>/<%= pkg.name %>.js"],
                }
            }
        },
        "preprocess": {
            "dist": {
                "src": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.ts",
                "dest": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.ts"
            }
        },
        "mocha_phantomjs": {
            "all": ["Tests/*.html"]
        }
    });

    grunt.loadNpmTasks("grunt-contrib-clean");
    grunt.loadNpmTasks("grunt-contrib-copy");
    grunt.loadNpmTasks("grunt-contrib-uglify");
    grunt.loadNpmTasks("grunt-mocha-phantomjs");
    grunt.loadNpmTasks("grunt-preprocess");
    grunt.loadNpmTasks("grunt-tslint");
    grunt.loadNpmTasks("grunt-typescript");
    grunt.registerTask("default", [
        "tslint", "typescript", "clean", "copy", "uglify", "preprocess", "mocha_phantomjs"
    ]);
};<commit_msg>Added js.map options to grunt uglify<commit_after>module.exports = function (grunt) {
    grunt.initConfig({
        "pkg": grunt.file.readJSON("package.json"),
        "meta": {
            "paths": {
                "source": "Source",
                "dist": "Distribution"
            }
        },
        "tslint": {
            "options": {
                "configuration": grunt.file.readJSON("tslint.json")
            },
            "files": {
                "src": ["<%= meta.paths.source %>/<%= pkg.name %>.ts"]
            }
        },
        "typescript": {
            "base": {
                "src": "<%= meta.paths.source %>/<%= pkg.name %>.ts",
                "dest": "<%= meta.paths.source %>/<%= pkg.name %>.js"
            }
        },
        "clean": ["<%= meta.paths.dist %>"],
        "copy": {
            "default": {
                "files": [{
                    "src": "<%= meta.paths.source %>/<%= pkg.name %>.js",
                    "dest": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.js"
                }, {
                    "src": "<%= meta.paths.source %>/<%= pkg.name %>.ts",
                    "dest": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.ts"
                }, {
                    "src": "<%= meta.paths.source %>/References/*.ts",
                    "dest": "<%= meta.paths.dist %>/",
                    "expand": true,
                    "flatten": true
                }, {
                    "src": "README.md",
                    "dest": "<%= meta.paths.dist %>/"
                }, {
                    "src": "README.md",
                    "dest": "<%= meta.paths.source %>/"
                }, {
                    "src": "LICENSE.txt",
                    "dest": "<%= meta.paths.dist %>/"
                }, {
                    "src": "LICENSE.txt",
                    "dest": "<%= meta.paths.source %>/"
                }]
            }
        },
        "uglify": {
            "options": {
                "compress": true,
                "screwIE8": true,
                "sourceMap": true
            },
            "dist": {
                "files": {
                    "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.min.js": ["<%= meta.paths.source %>/<%= pkg.name %>.js"],
                }
            }
        },
        "preprocess": {
            "dist": {
                "src": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.ts",
                "dest": "<%= meta.paths.dist %>/<%= pkg.name %>-<%= pkg.version %>.ts"
            }
        },
        "mocha_phantomjs": {
            "all": ["Tests/*.html"]
        }
    });

    grunt.loadNpmTasks("grunt-contrib-clean");
    grunt.loadNpmTasks("grunt-contrib-copy");
    grunt.loadNpmTasks("grunt-contrib-uglify");
    grunt.loadNpmTasks("grunt-mocha-phantomjs");
    grunt.loadNpmTasks("grunt-preprocess");
    grunt.loadNpmTasks("grunt-tslint");
    grunt.loadNpmTasks("grunt-typescript");
    grunt.registerTask("default", [
        "tslint", "typescript", "clean", "copy", "uglify", "preprocess", "mocha_phantomjs"
    ]);
};<|endoftext|>


<commit_before>// @flow

import {Platform, StyleSheet} from 'react-native'
import {createStackNavigator} from 'react-navigation'
import * as c from './views/components/colors'

import CalendarView, {EventDetail as EventDetailView} from './views/calendar'
import {ContactsView, ContactsDetailView} from './views/contacts'
import {
	DictionaryView,
	DictionaryDetailView,
	DictionaryEditorView,
} from './views/dictionary'
import {HomeView, EditHomeView} from './views/home'
import StreamingView, {
	KSTOScheduleView,
	KRLXScheduleView,
} from './views/streaming'
import {
	MenusView,
	CarletonBurtonMenuScreen,
	CarletonLDCMenuScreen,
	CarletonWeitzMenuScreen,
	CarletonSaylesMenuScreen,
} from './views/menus'
import NewsView from './views/news'
import SISView, {
	JobDetailView,
	CourseDetailView,
	CourseSearchResultsView,
} from './views/sis'
import {
	BuildingHoursView,
	BuildingHoursDetailView,
	BuildingHoursProblemReportView,
	BuildingHoursScheduleEditorView,
} from './views/building-hours'
import TransportationView, {
	BusMap as BusMapView,
	OtherModesDetailView,
} from './views/transportation'
import SettingsView from './views/settings'
import CreditsView from './views/settings/credits'
import PrivacyView from './views/settings/privacy'
import LegalView from './views/settings/legal'
import {IconSettingsView} from './views/settings/icon'
import {StudentOrgsView, StudentOrgsDetailView} from './views/student-orgs'
import {FaqView} from './views/faqs'
import HelpView from './views/help'
import {PrintJobsView, PrinterListView, PrintJobReleaseView} from './views/stoprint'

const styles = StyleSheet.create({
	header: {
		backgroundColor: c.navigationBackground,
	},
	card: {
		backgroundColor: Platform.select({
			ios: c.iosLightBackground,
			android: c.androidLightBackground,
		}),
	},
})

export const AppNavigator = createStackNavigator(
	{
		HomeView: {screen: HomeView},
		BuildingHoursDetailView: {screen: BuildingHoursDetailView},
		BuildingHoursView: {screen: BuildingHoursView},
		BuildingHoursProblemReportView: {screen: BuildingHoursProblemReportView},
		BuildingHoursScheduleEditorView: {screen: BuildingHoursScheduleEditorView},
		CalendarView: {screen: CalendarView},
		ContactsView: {screen: ContactsView},
		ContactsDetailView: {screen: ContactsDetailView},
		CreditsView: {screen: CreditsView},
		DictionaryDetailView: {screen: DictionaryDetailView},
		DictionaryView: {screen: DictionaryView},
		DictionaryEditorView: {screen: DictionaryEditorView},
		EditHomeView: {screen: EditHomeView},
		EventDetailView: {screen: EventDetailView},
		FaqView: {screen: FaqView},
		HelpView: {screen: HelpView},
		JobDetailView: {screen: JobDetailView},
		LegalView: {screen: LegalView},
		MenusView: {screen: MenusView},
		NewsView: {screen: NewsView},
		PrivacyView: {screen: PrivacyView},
		SettingsView: {screen: SettingsView},
		IconSettingsView: {screen: IconSettingsView},
		SISView: {screen: SISView},
		CourseSearchResultsView: {screen: CourseSearchResultsView},
		CourseDetailView: {screen: CourseDetailView},
		StreamingView: {screen: StreamingView},
		KSTOScheduleView: {screen: KSTOScheduleView},
		KRLXScheduleView: {screen: KRLXScheduleView},
		StudentOrgsDetailView: {screen: StudentOrgsDetailView},
		StudentOrgsView: {screen: StudentOrgsView},
		TransportationView: {screen: TransportationView},
		OtherModesDetailView: {screen: OtherModesDetailView},
		BusMapView: {screen: BusMapView},
		CarletonBurtonMenuView: {screen: CarletonBurtonMenuScreen},
		CarletonLDCMenuView: {screen: CarletonLDCMenuScreen},
		CarletonWeitzMenuView: {screen: CarletonWeitzMenuScreen},
		CarletonSaylesMenuView: {screen: CarletonSaylesMenuScreen},
		PrintJobsView: {screen: PrintJobsView},
		PrinterListView: {screen: PrinterListView},
		PrintJobReleaseView: {screen: PrintJobReleaseView},
	},
	{
		navigationOptions: {
			headerStyle: styles.header,
			headerTintColor: c.navigationForeground,
		},
		headerTransitionPreset: Platform.select({
			ios: 'uikit',
			android: undefined,
		}),
		cardStyle: styles.card,
	},
)
<commit_msg>prettier<commit_after>// @flow

import {Platform, StyleSheet} from 'react-native'
import {createStackNavigator} from 'react-navigation'
import * as c from './views/components/colors'

import CalendarView, {EventDetail as EventDetailView} from './views/calendar'
import {ContactsView, ContactsDetailView} from './views/contacts'
import {
	DictionaryView,
	DictionaryDetailView,
	DictionaryEditorView,
} from './views/dictionary'
import {HomeView, EditHomeView} from './views/home'
import StreamingView, {
	KSTOScheduleView,
	KRLXScheduleView,
} from './views/streaming'
import {
	MenusView,
	CarletonBurtonMenuScreen,
	CarletonLDCMenuScreen,
	CarletonWeitzMenuScreen,
	CarletonSaylesMenuScreen,
} from './views/menus'
import NewsView from './views/news'
import SISView, {
	JobDetailView,
	CourseDetailView,
	CourseSearchResultsView,
} from './views/sis'
import {
	BuildingHoursView,
	BuildingHoursDetailView,
	BuildingHoursProblemReportView,
	BuildingHoursScheduleEditorView,
} from './views/building-hours'
import TransportationView, {
	BusMap as BusMapView,
	OtherModesDetailView,
} from './views/transportation'
import SettingsView from './views/settings'
import CreditsView from './views/settings/credits'
import PrivacyView from './views/settings/privacy'
import LegalView from './views/settings/legal'
import {IconSettingsView} from './views/settings/icon'
import {StudentOrgsView, StudentOrgsDetailView} from './views/student-orgs'
import {FaqView} from './views/faqs'
import HelpView from './views/help'
import {
	PrintJobsView,
	PrinterListView,
	PrintJobReleaseView,
} from './views/stoprint'

const styles = StyleSheet.create({
	header: {
		backgroundColor: c.navigationBackground,
	},
	card: {
		backgroundColor: Platform.select({
			ios: c.iosLightBackground,
			android: c.androidLightBackground,
		}),
	},
})

export const AppNavigator = createStackNavigator(
	{
		HomeView: {screen: HomeView},
		BuildingHoursDetailView: {screen: BuildingHoursDetailView},
		BuildingHoursView: {screen: BuildingHoursView},
		BuildingHoursProblemReportView: {screen: BuildingHoursProblemReportView},
		BuildingHoursScheduleEditorView: {screen: BuildingHoursScheduleEditorView},
		CalendarView: {screen: CalendarView},
		ContactsView: {screen: ContactsView},
		ContactsDetailView: {screen: ContactsDetailView},
		CreditsView: {screen: CreditsView},
		DictionaryDetailView: {screen: DictionaryDetailView},
		DictionaryView: {screen: DictionaryView},
		DictionaryEditorView: {screen: DictionaryEditorView},
		EditHomeView: {screen: EditHomeView},
		EventDetailView: {screen: EventDetailView},
		FaqView: {screen: FaqView},
		HelpView: {screen: HelpView},
		JobDetailView: {screen: JobDetailView},
		LegalView: {screen: LegalView},
		MenusView: {screen: MenusView},
		NewsView: {screen: NewsView},
		PrivacyView: {screen: PrivacyView},
		SettingsView: {screen: SettingsView},
		IconSettingsView: {screen: IconSettingsView},
		SISView: {screen: SISView},
		CourseSearchResultsView: {screen: CourseSearchResultsView},
		CourseDetailView: {screen: CourseDetailView},
		StreamingView: {screen: StreamingView},
		KSTOScheduleView: {screen: KSTOScheduleView},
		KRLXScheduleView: {screen: KRLXScheduleView},
		StudentOrgsDetailView: {screen: StudentOrgsDetailView},
		StudentOrgsView: {screen: StudentOrgsView},
		TransportationView: {screen: TransportationView},
		OtherModesDetailView: {screen: OtherModesDetailView},
		BusMapView: {screen: BusMapView},
		CarletonBurtonMenuView: {screen: CarletonBurtonMenuScreen},
		CarletonLDCMenuView: {screen: CarletonLDCMenuScreen},
		CarletonWeitzMenuView: {screen: CarletonWeitzMenuScreen},
		CarletonSaylesMenuView: {screen: CarletonSaylesMenuScreen},
		PrintJobsView: {screen: PrintJobsView},
		PrinterListView: {screen: PrinterListView},
		PrintJobReleaseView: {screen: PrintJobReleaseView},
	},
	{
		navigationOptions: {
			headerStyle: styles.header,
			headerTintColor: c.navigationForeground,
		},
		headerTransitionPreset: Platform.select({
			ios: 'uikit',
			android: undefined,
		}),
		cardStyle: styles.card,
	},
)
<|endoftext|>


<commit_before>import {obj2Node,node2Obj} from "../exporter/json"

export class ImportNative {
    /* Save document information into the database */

    constructor(aDocument, aBibDB, anImageDB, entries, user, bibDB, imageDB, callback) {
        this.aDocument = aDocument
        this.aBibDB = aBibDB // These are new values
        this.anImageDB = anImageDB // These are new values
        this.entries = entries
        this.user = user
        this.bibDB = bibDB // These are values stored in the database
        this.imageDB = imageDB // These are values stored in the database
        this.callback = callback
        this.newBibEntries = []
        this.importNative()
    }

    importNative() {
        let that = this
        let BibTranslationTable = {},
            newBibEntries = [],
            shrunkImageDBObject = {},
            ImageTranslationTable = [],
            newImageEntries = [],
            simplifiedShrunkImageDB = []

        // Add the id to each object in the this.bibDB to be able to look it up when comparing to this.aBibDB below
        for (let key in this.bibDB) {
            this.bibDB[key]['id'] = key
        }
        for (let key in this.aBibDB) {
            //this.aBibDB[key]['entry_type']=_.findWhere(BibEntryTypes,{name:this.aBibDB[key]['bibtype']}).id
            //delete this.aBibDB[key].bibtype
            let matchEntries = _.where(this.bibDB, this.aBibDB[key])

            if (0 === matchEntries.length) {
                //create new
                newBibEntries.push({
                    oldId: key,
                    oldEntryKey: this.aBibDB[key].entry_key,
                    entry: this.aBibDB[key]
                })
            } else if (1 === matchEntries.length && parseInt(key) !==
                matchEntries[0].id) {
                BibTranslationTable[parseInt(key)] = matchEntries[0].id
            } else if (1 < matchEntries.length) {
                if (!(_.findWhere(matchEntries, {
                        id: parseInt(key)
                    }))) {
                    // There are several matches, and none of the matches have the same id as the key in this.aBibDB.
                    // We now pick the first match.
                    // TODO: Figure out if this behavior is correct.
                    BibTranslationTable[parseInt(key)] = matchEntries[0].id
                }
            }
        }

        // Remove the id values again
        for (let key in this.bibDB) {
            delete this.bibDB[key].id
        }

        // We need to remove the pk from the entry in the this.anImageDB so that
        // we also get matches with this.entries with other pk values.
        // We therefore convert to an associative array/object.
        for (let key in this.anImageDB) {
            simplifiedShrunkImageDB.push(_.omit(this.anImageDB[key], 'image',
                'thumbnail', 'cats', 'added'))
        }

        for (let image in simplifiedShrunkImageDB) {
            shrunkImageDBObject[simplifiedShrunkImageDB[image].pk] =
                simplifiedShrunkImageDB[image]
            delete shrunkImageDBObject[simplifiedShrunkImageDB[image].pk].pk
        }

        for (let key in shrunkImageDBObject) {
            let matchEntries = _.where(this.imageDB, shrunkImageDBObject[key])
            if (0 === matchEntries.length) {
                //create new
                let sIDBEntry = _.findWhere(this.anImageDB, {
                    pk: parseInt(key)
                })
                newImageEntries.push({
                    oldId: parseInt(key),
                    oldUrl: sIDBEntry.image,
                    title: sIDBEntry.title,
                    file_type: sIDBEntry.file_type,
                    checksum: sIDBEntry.checksum
                })
            } else if (1 === matchEntries.length && parseInt(key) !==
                matchEntries[0].pk) {
                ImageTranslationTable.push({
                    oldId: parseInt(key),
                    newId: matchEntries[0].pk,
                    oldUrl: _.findWhere(this.anImageDB, {
                        pk: parseInt(key)
                    }).image,
                    newUrl: matchEntries[0].image
                })
            } else if (1 < matchEntries.length) {
                if (!(_.findWhere(matchEntries, {
                        pk: parseInt(key)
                    }))) {
                    // There are several matches, and none of the matches have
                    // the same id as the key in this.anImageDB.
                    // We now pick the first match.
                    // TODO: Figure out if this behavior is correct.
                    ImageTranslationTable.push({
                        oldId: key,
                        newId: matchEntries[0].pk,
                        oldUrl: _.findWhere(this.anImageDB, {
                            pk: parseInt(key)
                        }).image,
                        newUrl: matchEntries[0].image
                    })
                }
            }
        }

        if (newBibEntries.length !== 0 || newImageEntries.length !== 0) {
            // We need to create new entries in the DB for images and/or
            // bibliography items.
            this.getImageData(BibTranslationTable, ImageTranslationTable, newBibEntries,
                newImageEntries, this.entries)
        } else if (!(jQuery.isEmptyObject(BibTranslationTable)) || !(jQuery.isEmptyObject(
                ImageTranslationTable))) {
            // We need to change some reference numbers in the document contents
            translateReferenceIds(BibTranslationTable,
                ImageTranslationTable)
        } else {
            // We are good to go. All the used images and bibliography entries
            // exist in the DB for this user with the same numbers.
            // We can go ahead and create the new document entry in the
            // bibliography without any changes.
            this.createNewDocument()
        }

    }

    getImageData(BibTranslationTable, ImageTranslationTable, newBibEntries,
        newImageEntries, entries) {
        let that = this,
            counter = 0

        function getImageZipEntry() {
            if (counter < newImageEntries.length) {
                _.findWhere(entries, {
                    filename: newImageEntries[counter].oldUrl.split('/').pop()
                }).getData(
                    new zip.BlobWriter(newImageEntries[counter].file_type),
                    function(
                        file) {
                        newImageEntries[counter]['file'] = file
                        counter++
                        getImageZipEntry()
                    })
            } else {
                that.sendNewImageAndBibEntries(BibTranslationTable, ImageTranslationTable, newBibEntries,
                    newImageEntries)
            }
        }

        function getImageUrlEntry() {
            if (counter < newImageEntries.length) {
                let getUrl = _.findWhere(entries, {
                    filename: newImageEntries[counter].oldUrl.split('/').pop()
                }).url
                let xhr = new XMLHttpRequest()
                xhr.open('GET', getUrl, true)
                xhr.responseType = 'blob'

                xhr.onload = function(e) {
                    if (this.status == 200) {
                        // Note: .response instead of .responseText
                        newImageEntries[counter]['file'] = new Blob([this.response], {
                            type: newImageEntries[counter].file_type
                        })
                        counter++
                        getImageUrlEntry()
                    }
                }

                xhr.send()

            } else {
                that.sendNewImageAndBibEntries(BibTranslationTable, ImageTranslationTable, newBibEntries,
                    newImageEntries)
            }
        }
        if (entries.length > 0) {
            if (entries[0].hasOwnProperty('url')) {
                getImageUrlEntry()
            } else {
                getImageZipEntry()
            }
        }

    }

    translateReferenceIds(BibTranslationTable,
        ImageTranslationTable) {
        let contents = obj2Node(this.aDocument.contents)
        jQuery(contents).find('img').each(function() {
            let translationEntry = _.findWhere(ImageTranslationTable, {
                oldUrl: jQuery(this).attr('src')
            })
            if (translationEntry) {
                jQuery(this).attr('src', translationEntry.newUrl)
            }
        })
        jQuery(contents).find('figure').each(function() {
            let translationEntry = _.findWhere(ImageTranslationTable, {
                oldId: parseInt(jQuery(this).attr('data-image'))
            })
            if (translationEntry) {
                jQuery(this).attr('data-image', translationEntry.newId)
            }
        })
        jQuery(contents).find('.citation').each(function() {
            let citekeys = jQuery(this).attr('data-bib-entry').split(',')
            for (let i = 0; i < citekeys.length; i++) {
                if (citekeys[i] in BibTranslationTable) {
                    citekeys[i] = BibTranslationTable[citekeys[i]]
                }
            }
            jQuery(this).attr('data-bib-entry', citekeys.join(','))
        })

        this.aDocument.contents = node2Obj(contents)

        this.createNewDocument()
    }

    sendNewImageAndBibEntries(BibTranslationTable, ImageTranslationTable, newBibEntries,
        newImageEntries) {
        let that = this,
            counter = 0

        function sendImage() {
            if (counter < newImageEntries.length) {
                let formValues = new FormData()
                formValues.append('id', 0)
                formValues.append('title', newImageEntries[counter].title)
                formValues.append('imageCats', '')
                formValues.append('image', newImageEntries[counter].file,
                    newImageEntries[counter].oldUrl.split('/').pop())
                formValues.append('checksum', newImageEntries[counter].checksum),

                    jQuery.ajax({
                        url: '/usermedia/save/',
                        data: formValues,
                        type: 'POST',
                        dataType: 'json',
                        success: function(response, textStatus, jqXHR) {
                            that.imageDB[response.values.pk] = response.values
                            let imageTranslation = {}
                            imageTranslation.oldUrl = newImageEntries[counter].oldUrl
                            imageTranslation.oldId = newImageEntries[counter].oldId
                            imageTranslation.newUrl = response.values.image
                            imageTranslation.newId = response.values.pk
                            ImageTranslationTable.push(imageTranslation)
                            counter++
                            sendImage()
                        },
                        error: function() {
                            jQuery.addAlert('error', gettext('Could not save ') +
                                newImageEntries[counter].title)
                        },
                        complete: function() {},
                        cache: false,
                        contentType: false,
                        processData: false
                    })
            } else {
                sendBibItems()
            }
        }

        function sendBibItems() {

            if (newBibEntries.length > 0) {
                let bibEntries = _.pluck(newBibEntries, 'entry'),
                    bibDict = {}

                for (let i = 0; i < bibEntries.length; i++) {
                    bibEntries[i]['bibtype'] = BibEntryTypes[bibEntries[i]['entry_type']].name
                    bibDict[bibEntries[i]['entry_key']] = bibEntries[i]
                    delete bibDict[bibEntries[i]['entry_key']].entry_type

                    delete bibDict[bibEntries[i]['entry_key']].entry_cat
                    delete bibDict[bibEntries[i]['entry_key']].entry_key
                }
                jQuery.ajax({
                    url: '/bibliography/import_bibtex/',
                    data: {
                        bibs: JSON.stringify(bibDict)
                    },
                    type: 'POST',
                    dataType: 'json',
                    success: function(response, textStatus, jqXHR) {
                        let errors = response.errors,
                            warnings = response.warning,
                            len = errors.length

                        for (let i = 0; i < len; i++) {
                            $.addAlert('error', errors[i])
                        }
                        len = warnings.length
                        for (let i = 0; i < len; i++) {
                            $.addAlert('warning', warnings[i])
                        }
                        _.each(response.key_translations, function(newKey, oldKey) {
                            let newID = _.findWhere(response.bibs, {
                                    entry_key: newKey
                                }).id,
                                oldID = _.findWhere(newBibEntries, {
                                    oldEntryKey: oldKey
                                }).oldId
                            BibTranslationTable[oldID] = newID
                        })
                        that.newBibEntries = response.bibs
                        that.translateReferenceIds(BibTranslationTable, ImageTranslationTable)
                    },
                    error: function() {
                        console.log(jqXHR.responseText)
                    },
                    complete: function() {}
                })
            } else {
                that.translateReferenceIds(BibTranslationTable,
                    ImageTranslationTable)
            }
        }

        sendImage()
    }

    createNewDocument() {
        let that = this
        let postData = {
            title: this.aDocument.title,
            contents: JSON.stringify(this.aDocument.contents),
            settings: JSON.stringify(this.aDocument.settings),
            metadata: JSON.stringify(this.aDocument.metadata)
        }
        jQuery.ajax({
            url: '/document/import/',
            data: postData,
            type: 'POST',
            dataType: 'json',
            success: function(data, textStatus, jqXHR) {
                let aDocumentValues = {
                    last_diffs: [],
                    is_owner: true,
                    rights: 'w',
                    changed: false,
                    titleChanged: false
                }
                that.aDocument.owner = {
                    id: that.user.id,
                    name: that.user.name,
                    avatar: that.user.avatar
                }
                that.aDocument.id = data['document_id'];
                that.aDocument.version = 0;
                that.aDocument.comment_version = 0
                that.aDocument.added = data['added']
                that.aDocument.updated = data['updated']
                that.aDocument.revisions = []
                return that.callback(true, {
                    aDocument: that.aDocument,
                    aDocumentValues,
                    newBibEntries: that.newBibEntries
                })
            },
            error: function() {
                that.callback(false, gettext('Could not save ') + that.aDocument.title)
            }
        })
    }
}
<commit_msg>fix importing files with bibliographies<commit_after>import {obj2Node,node2Obj} from "../exporter/json"
import {BibEntryTypes} from "../bibliography/statics"

export class ImportNative {
    /* Save document information into the database */

    constructor(aDocument, aBibDB, anImageDB, entries, user, bibDB, imageDB, callback) {
        this.aDocument = aDocument
        this.aBibDB = aBibDB // These are new values
        this.anImageDB = anImageDB // These are new values
        this.entries = entries
        this.user = user
        this.bibDB = bibDB // These are values stored in the database
        this.imageDB = imageDB // These are values stored in the database
        this.callback = callback
        this.newBibEntries = []
        this.importNative()
    }

    importNative() {
        let that = this
        let BibTranslationTable = {},
            newBibEntries = [],
            shrunkImageDBObject = {},
            ImageTranslationTable = [],
            newImageEntries = [],
            simplifiedShrunkImageDB = []

        // Add the id to each object in the this.bibDB to be able to look it up when comparing to this.aBibDB below
        for (let key in this.bibDB) {
            this.bibDB[key]['id'] = key
        }
        for (let key in this.aBibDB) {
            //this.aBibDB[key]['entry_type']=_.findWhere(BibEntryTypes,{name:this.aBibDB[key]['bibtype']}).id
            //delete this.aBibDB[key].bibtype
            let matchEntries = _.where(this.bibDB, this.aBibDB[key])

            if (0 === matchEntries.length) {
                //create new
                newBibEntries.push({
                    oldId: key,
                    oldEntryKey: this.aBibDB[key].entry_key,
                    entry: this.aBibDB[key]
                })
            } else if (1 === matchEntries.length && parseInt(key) !==
                matchEntries[0].id) {
                BibTranslationTable[parseInt(key)] = matchEntries[0].id
            } else if (1 < matchEntries.length) {
                if (!(_.findWhere(matchEntries, {
                        id: parseInt(key)
                    }))) {
                    // There are several matches, and none of the matches have the same id as the key in this.aBibDB.
                    // We now pick the first match.
                    // TODO: Figure out if this behavior is correct.
                    BibTranslationTable[parseInt(key)] = matchEntries[0].id
                }
            }
        }

        // Remove the id values again
        for (let key in this.bibDB) {
            delete this.bibDB[key].id
        }

        // We need to remove the pk from the entry in the this.anImageDB so that
        // we also get matches with this.entries with other pk values.
        // We therefore convert to an associative array/object.
        for (let key in this.anImageDB) {
            simplifiedShrunkImageDB.push(_.omit(this.anImageDB[key], 'image',
                'thumbnail', 'cats', 'added'))
        }

        for (let image in simplifiedShrunkImageDB) {
            shrunkImageDBObject[simplifiedShrunkImageDB[image].pk] =
                simplifiedShrunkImageDB[image]
            delete shrunkImageDBObject[simplifiedShrunkImageDB[image].pk].pk
        }

        for (let key in shrunkImageDBObject) {
            let matchEntries = _.where(this.imageDB, shrunkImageDBObject[key])
            if (0 === matchEntries.length) {
                //create new
                let sIDBEntry = _.findWhere(this.anImageDB, {
                    pk: parseInt(key)
                })
                newImageEntries.push({
                    oldId: parseInt(key),
                    oldUrl: sIDBEntry.image,
                    title: sIDBEntry.title,
                    file_type: sIDBEntry.file_type,
                    checksum: sIDBEntry.checksum
                })
            } else if (1 === matchEntries.length && parseInt(key) !==
                matchEntries[0].pk) {
                ImageTranslationTable.push({
                    oldId: parseInt(key),
                    newId: matchEntries[0].pk,
                    oldUrl: _.findWhere(this.anImageDB, {
                        pk: parseInt(key)
                    }).image,
                    newUrl: matchEntries[0].image
                })
            } else if (1 < matchEntries.length) {
                if (!(_.findWhere(matchEntries, {
                        pk: parseInt(key)
                    }))) {
                    // There are several matches, and none of the matches have
                    // the same id as the key in this.anImageDB.
                    // We now pick the first match.
                    // TODO: Figure out if this behavior is correct.
                    ImageTranslationTable.push({
                        oldId: key,
                        newId: matchEntries[0].pk,
                        oldUrl: _.findWhere(this.anImageDB, {
                            pk: parseInt(key)
                        }).image,
                        newUrl: matchEntries[0].image
                    })
                }
            }
        }

        if (newBibEntries.length !== 0 || newImageEntries.length !== 0) {
            // We need to create new entries in the DB for images and/or
            // bibliography items.
            this.getImageData(BibTranslationTable, ImageTranslationTable, newBibEntries,
                newImageEntries, this.entries)
        } else if (!(jQuery.isEmptyObject(BibTranslationTable)) || !(jQuery.isEmptyObject(
                ImageTranslationTable))) {
            // We need to change some reference numbers in the document contents
            translateReferenceIds(BibTranslationTable,
                ImageTranslationTable)
        } else {
            // We are good to go. All the used images and bibliography entries
            // exist in the DB for this user with the same numbers.
            // We can go ahead and create the new document entry in the
            // bibliography without any changes.
            this.createNewDocument()
        }

    }

    getImageData(BibTranslationTable, ImageTranslationTable, newBibEntries,
        newImageEntries, entries) {
        let that = this,
            counter = 0

        function getImageZipEntry() {
            if (counter < newImageEntries.length) {
                _.findWhere(entries, {
                    filename: newImageEntries[counter].oldUrl.split('/').pop()
                }).getData(
                    new zip.BlobWriter(newImageEntries[counter].file_type),
                    function(
                        file) {
                        newImageEntries[counter]['file'] = file
                        counter++
                        getImageZipEntry()
                    })
            } else {
                that.sendNewImageAndBibEntries(BibTranslationTable, ImageTranslationTable, newBibEntries,
                    newImageEntries)
            }
        }

        function getImageUrlEntry() {
            if (counter < newImageEntries.length) {
                let getUrl = _.findWhere(entries, {
                    filename: newImageEntries[counter].oldUrl.split('/').pop()
                }).url
                let xhr = new XMLHttpRequest()
                xhr.open('GET', getUrl, true)
                xhr.responseType = 'blob'

                xhr.onload = function(e) {
                    if (this.status == 200) {
                        // Note: .response instead of .responseText
                        newImageEntries[counter]['file'] = new Blob([this.response], {
                            type: newImageEntries[counter].file_type
                        })
                        counter++
                        getImageUrlEntry()
                    }
                }

                xhr.send()

            } else {
                that.sendNewImageAndBibEntries(BibTranslationTable, ImageTranslationTable, newBibEntries,
                    newImageEntries)
            }
        }
        if (entries.length > 0) {
            if (entries[0].hasOwnProperty('url')) {
                getImageUrlEntry()
            } else {
                getImageZipEntry()
            }
        }

    }

    translateReferenceIds(BibTranslationTable,
        ImageTranslationTable) {
        let contents = obj2Node(this.aDocument.contents)
        jQuery(contents).find('img').each(function() {
            let translationEntry = _.findWhere(ImageTranslationTable, {
                oldUrl: jQuery(this).attr('src')
            })
            if (translationEntry) {
                jQuery(this).attr('src', translationEntry.newUrl)
            }
        })
        jQuery(contents).find('figure').each(function() {
            let translationEntry = _.findWhere(ImageTranslationTable, {
                oldId: parseInt(jQuery(this).attr('data-image'))
            })
            if (translationEntry) {
                jQuery(this).attr('data-image', translationEntry.newId)
            }
        })
        jQuery(contents).find('.citation').each(function() {
            let citekeys = jQuery(this).attr('data-bib-entry').split(',')
            for (let i = 0; i < citekeys.length; i++) {
                if (citekeys[i] in BibTranslationTable) {
                    citekeys[i] = BibTranslationTable[citekeys[i]]
                }
            }
            jQuery(this).attr('data-bib-entry', citekeys.join(','))
        })

        this.aDocument.contents = node2Obj(contents)

        this.createNewDocument()
    }

    sendNewImageAndBibEntries(BibTranslationTable, ImageTranslationTable, newBibEntries,
        newImageEntries) {
        let that = this,
            counter = 0

        function sendImage() {
            if (counter < newImageEntries.length) {
                let formValues = new FormData()
                formValues.append('id', 0)
                formValues.append('title', newImageEntries[counter].title)
                formValues.append('imageCats', '')
                formValues.append('image', newImageEntries[counter].file,
                    newImageEntries[counter].oldUrl.split('/').pop())
                formValues.append('checksum', newImageEntries[counter].checksum),

                    jQuery.ajax({
                        url: '/usermedia/save/',
                        data: formValues,
                        type: 'POST',
                        dataType: 'json',
                        success: function(response, textStatus, jqXHR) {
                            that.imageDB[response.values.pk] = response.values
                            let imageTranslation = {}
                            imageTranslation.oldUrl = newImageEntries[counter].oldUrl
                            imageTranslation.oldId = newImageEntries[counter].oldId
                            imageTranslation.newUrl = response.values.image
                            imageTranslation.newId = response.values.pk
                            ImageTranslationTable.push(imageTranslation)
                            counter++
                            sendImage()
                        },
                        error: function() {
                            jQuery.addAlert('error', gettext('Could not save ') +
                                newImageEntries[counter].title)
                        },
                        complete: function() {},
                        cache: false,
                        contentType: false,
                        processData: false
                    })
            } else {
                sendBibItems()
            }
        }

        function sendBibItems() {

            if (newBibEntries.length > 0) {
                let bibEntries = _.pluck(newBibEntries, 'entry'),
                    bibDict = {}

                for (let i = 0; i < bibEntries.length; i++) {
                    bibEntries[i]['bibtype'] = BibEntryTypes[bibEntries[i]['entry_type']].name
                    bibDict[bibEntries[i]['entry_key']] = bibEntries[i]
                    delete bibDict[bibEntries[i]['entry_key']].entry_type

                    delete bibDict[bibEntries[i]['entry_key']].entry_cat
                    delete bibDict[bibEntries[i]['entry_key']].entry_key
                }
                jQuery.ajax({
                    url: '/bibliography/import_bibtex/',
                    data: {
                        bibs: JSON.stringify(bibDict)
                    },
                    type: 'POST',
                    dataType: 'json',
                    success: function(response, textStatus, jqXHR) {
                        let errors = response.errors,
                            warnings = response.warning,
                            len = errors.length

                        for (let i = 0; i < len; i++) {
                            $.addAlert('error', errors[i])
                        }
                        len = warnings.length
                        for (let i = 0; i < len; i++) {
                            $.addAlert('warning', warnings[i])
                        }
                        _.each(response.key_translations, function(newKey, oldKey) {
                            let newID = _.findWhere(response.bibs, {
                                    entry_key: newKey
                                }).id,
                                oldID = _.findWhere(newBibEntries, {
                                    oldEntryKey: oldKey
                                }).oldId
                            BibTranslationTable[oldID] = newID
                        })
                        that.newBibEntries = response.bibs
                        that.translateReferenceIds(BibTranslationTable, ImageTranslationTable)
                    },
                    error: function() {
                        console.log(jqXHR.responseText)
                    },
                    complete: function() {}
                })
            } else {
                that.translateReferenceIds(BibTranslationTable,
                    ImageTranslationTable)
            }
        }

        sendImage()
    }

    createNewDocument() {
        let that = this
        let postData = {
            title: this.aDocument.title,
            contents: JSON.stringify(this.aDocument.contents),
            settings: JSON.stringify(this.aDocument.settings),
            metadata: JSON.stringify(this.aDocument.metadata)
        }
        jQuery.ajax({
            url: '/document/import/',
            data: postData,
            type: 'POST',
            dataType: 'json',
            success: function(data, textStatus, jqXHR) {
                let aDocumentValues = {
                    last_diffs: [],
                    is_owner: true,
                    rights: 'w',
                    changed: false,
                    titleChanged: false
                }
                that.aDocument.owner = {
                    id: that.user.id,
                    name: that.user.name,
                    avatar: that.user.avatar
                }
                that.aDocument.id = data['document_id'];
                that.aDocument.version = 0;
                that.aDocument.comment_version = 0
                that.aDocument.added = data['added']
                that.aDocument.updated = data['updated']
                that.aDocument.revisions = []
                return that.callback(true, {
                    aDocument: that.aDocument,
                    aDocumentValues,
                    newBibEntries: that.newBibEntries
                })
            },
            error: function() {
                that.callback(false, gettext('Could not save ') + that.aDocument.title)
            }
        })
    }
}
<|endoftext|>


<commit_before>var bodyParser = require('body-parser'),
	constants = require('../../lib/constants'),
	cookieParser = require('cookie-parser'),
	express = require('express'),
	fs = require('fs'),
	https = require('https'),
	path = require('path');

var certDir = path.join(__dirname,'..','fixtures'),
	key = fs.readFileSync(path.join(certDir, 'mw-test-server-key.pem')),
	cert = fs.readFileSync(path.join(certDir, 'mw-test-server-cert.pem'));

// prep express
var app = express();
app.listen = function() {
	var server = https.createServer({
		key: key,
		cert: cert
	}, this);
	return server.listen.apply(server, arguments);
};
app.use(bodyParser());
app.use(cookieParser());

// auth route
app.post('/api/v1/auth/login', function(req, res) {
	var user = req.param('username'),
		pass = req.param('password');

	// auth
	if ((user !== 'test' && user !== 'nocookie') || pass !== 'test') {
		return res.status(400).type('text/plain').send('invalid username/password combo');
	}

	// add cookie and send
	if (user !== 'nocookie') {
		res.cookie('connect.sid', 'sessioncookie');
	}
	return res.status(200).type('text/plain').send('logged in');
});

// enable route
app.post('/api/v1/apibuilder/enable', function(req, res) {
	var appId = req.param('appId');

	// sent a cookie, right?
	if (!req.cookies['connect.sid']) {
		return res.status(400).type('text/plain').send('no cookie');
	}

	// make sure we have an appId
	if (!appId) {
		return res.status(400).type('text/plain').send('appId is a required parameter');
	}

	// make sure appId exists
	if (appId === 'bad.api') {
		return res.status(500).type('text/plain').send('failed to load MW keys');
	} else if (appId !== 'ti.mw.todo') {
		return res.status(400).type('text/plain').send('appId does not exist');
	}

	// send keys via fixture data
	try {
		return res.status(200).type('application/json').send({
			success: true,
			result: {
				keys: {
					development: 'developmentkey',
					production: 'productionkey'
				}
			}
		});
	} catch (e) {
		return res.status(500).type('text/plain').send('failed to load MW keys');
	}
});

app.on('error', function(err) {
	console.error('error listen: ' + err);
});

module.exports = app;<commit_msg>use constant for url path<commit_after>var bodyParser = require('body-parser'),
	constants = require('../../lib/constants'),
	cookieParser = require('cookie-parser'),
	express = require('express'),
	fs = require('fs'),
	https = require('https'),
	path = require('path');

var certDir = path.join(__dirname,'..','fixtures'),
	key = fs.readFileSync(path.join(certDir, 'mw-test-server-key.pem')),
	cert = fs.readFileSync(path.join(certDir, 'mw-test-server-cert.pem'));

// prep express
var app = express();
app.listen = function() {
	var server = https.createServer({
		key: key,
		cert: cert
	}, this);
	return server.listen.apply(server, arguments);
};
app.use(bodyParser());
app.use(cookieParser());

// auth route
app.post('/api/v1/auth/login', function(req, res) {
	var user = req.param('username'),
		pass = req.param('password');

	// auth
	if ((user !== 'test' && user !== 'nocookie') || pass !== 'test') {
		return res.status(400).type('text/plain').send('invalid username/password combo');
	}

	// add cookie and send
	if (user !== 'nocookie') {
		res.cookie('connect.sid', 'sessioncookie');
	}
	return res.status(200).type('text/plain').send('logged in');
});

// enable route
app.post(constants.ENABLE_API, function(req, res) {
	var appId = req.param('appId');

	// sent a cookie, right?
	if (!req.cookies['connect.sid']) {
		return res.status(400).type('text/plain').send('no cookie');
	}

	// make sure we have an appId
	if (!appId) {
		return res.status(400).type('text/plain').send('appId is a required parameter');
	}

	// make sure appId exists
	if (appId === 'bad.api') {
		return res.status(500).type('text/plain').send('failed to load MW keys');
	} else if (appId !== 'ti.mw.todo') {
		return res.status(400).type('text/plain').send('appId does not exist');
	}

	// send keys via fixture data
	try {
		return res.status(200).type('application/json').send({
			success: true,
			result: {
				keys: {
					development: 'developmentkey',
					production: 'productionkey'
				}
			}
		});
	} catch (e) {
		return res.status(500).type('text/plain').send('failed to load MW keys');
	}
});

app.on('error', function(err) {
	console.error('error listen: ' + err);
});

module.exports = app;<|endoftext|>