<commit_before>import Ember from 'ember';
import MultiStatsSocket from 'ui/utils/multi-stats';

export default Ember.Route.extend({
  statsSocket: null,

  model() {
    return this.modelFor('service').get('service');
  },

  activate() {
    var stats = MultiStatsSocket.create({
      resource: this.modelFor('service').get('service'),
      linkName: 'containerStats',
    });

    this.set('statsSocket',stats);
    stats.on('dataPoint', (data) => {
      var controller = this.get('controller');
      if ( controller )
      {
        controller.onDataPoint(data);
      }
    });
  },

  deactivate() {
    this.get('statsSocket').close();
  }
});
<commit_msg>Fix unknown host on services<commit_after>import Ember from 'ember';
import MultiStatsSocket from 'ui/utils/multi-stats';

export default Ember.Route.extend({
  statsSocket: null,

  model() {
    var promises = [];

    // Load the hosts for the instances if they're not already there
    var service = this.modelFor('service').get('service');
    service.get('instances').forEach((instance) => {
      if ( !instance.get('primaryHost') )
      {
        promises.push(instance.importLink('hosts'));
      }
    });

    return Ember.RSVP.all(promises).then(() => {
      return service;
    });
  },

  activate() {
    var stats = MultiStatsSocket.create({
      resource: this.modelFor('service').get('service'),
      linkName: 'containerStats',
    });

    this.set('statsSocket',stats);
    stats.on('dataPoint', (data) => {
      var controller = this.get('controller');
      if ( controller )
      {
        controller.onDataPoint(data);
      }
    });
  },

  deactivate() {
    this.get('statsSocket').close();
  }
});
<|endoftext|>


<commit_before>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import EXIF from 'exif-js';
import {
  extractFileExtension,
  extractContainingDirectoryPath,
  extractFileName,
  getMetaDirectoryPath
} from '../utils/paths';
import { base64ToArrayBuffer } from '../utils/misc';
import AppConfig from '../config';
import PlatformIO from '../services/platform-io';
import { Pro } from '../pro';

const maxSize = AppConfig.maxThumbSize;

const supportedImgs = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp'];
const supportedContainers = [
  'zip',
  'epub',
  'docx',
  'pptx',
  'pptm',
  'potx',
  'potm',
  'ppxs',
  'ppsm',
  'sldx',
  'sldm',
  'dotx',
  'dotm',
  'xlsx',
  'xlsm',
  'xlst',
  'odp',
  'odg',
  'ods',
  'odt'
];
const supportedText = [
  'txt',
  'md',
  'coffee',
  'c',
  'cpp',
  'css',
  'groovy',
  'haxe',
  'xml',
  'java',
  'js',
  'json',
  'less',
  'markdown',
  'mdown',
  'php',
  'pl',
  'py',
  'rb',
  'ini',
  'sh',
  'sql',
  'mhtml'
];
const supportedVideos = ['mp4', 'webm', 'ogv', 'm4v'];
const maxFileSize = 30 * 1024 * 1024;

function saveThumbnailPromise(filePath, dataURL) {
  return new Promise((resolve, reject) => {
    if (!dataURL || dataURL.length < 7) { // data:,
      return reject('Invalid dataURL');
    }
    const baseString = dataURL.split(',').pop();
    const content = base64ToArrayBuffer(baseString);
    PlatformIO.saveBinaryFilePromise(filePath, content, true)
      .then(() => resolve(filePath))
      .catch((error) => {
        console.warn('Saving thumbnail for ' + filePath + ' failed with ' + JSON.stringify(error));
        return reject('Saving tmb failed for: ' + filePath);
      });
  });
}

function getThumbFileLocation(filePath) {
  const containingFolder = extractContainingDirectoryPath(filePath);
  const metaFolder = getMetaDirectoryPath(containingFolder);
  return metaFolder + AppConfig.dirSeparator + extractFileName(filePath) + AppConfig.thumbFileExt;
}

export function getThumbnailURLPromise(filePath) {
  return new Promise((resolve) => {
    PlatformIO.getPropertiesPromise(filePath).then((origStats) => {
      const thumbFilePath = getThumbFileLocation(filePath);
      PlatformIO.getPropertiesPromise(thumbFilePath).then((stats) => {
        if (stats) { // Thumbnail exists
          if (origStats.lmdt > stats.lmdt) { // Checking if is up to date
            createThumbnailPromise(filePath, origStats.size, thumbFilePath).then((tmbPath) => resolve({ filePath, tmbPath })).catch((err) => {
              console.warn('Thumb generation failed ' + err);
              resolve({ filePath, tmbPath: thumbFilePath });
            });
          } else { // Tmb up to date
            resolve({ filePath });
          }
        } else {
          // Thumbnail does not exists
          createThumbnailPromise(filePath, origStats.size, thumbFilePath).then((tmbPath) => resolve({ filePath, tmbPath })).catch(err => {
            console.warn('Thumb generation failed ' + err);
            resolve({ filePath });
          });
        }
      }).catch(err => {
        console.warn('Error getting tmb properties ' + err);
        resolve({ filePath });
      });
    }).catch(err => {
      console.warn('Error getting file properties ' + err);
      resolve({ filePath });
    });
  });
}

export function createThumbnailPromise(
  filePath: string,
  fileSize: number,
  thumbFilePath: string
) {
  return new Promise((resolve) => {
    generateThumbnailPromise(filePath, fileSize).then((dataURL) => {
      if (dataURL && dataURL.length) {
        saveThumbnailPromise(thumbFilePath, dataURL)
          .then(() => resolve(thumbFilePath))
          .catch((err) => {
            console.warn('Thumb saving failed ' + err);
            resolve();
          });
      } else {
        resolve();
      }
    }).catch((err) => {
      console.warn('Thumb generation failed ' + err);
      resolve();
    });
  });
}

export function generateThumbnailPromise(
  fileURL: string,
  fileSize: number
) {
  const ext = extractFileExtension(fileURL).toLowerCase();

  if (supportedImgs.indexOf(ext) >= 0) {
    if (fileSize && fileSize < maxFileSize) {
      return generateImageThumbnail(fileURL);
    }
  } else if (Pro && ext === 'pdf') {
    return Pro.ThumbsGenerator.generatePDFThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'html') {
    return Pro.ThumbsGenerator.generateHtmlThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'url') {
    return Pro.ThumbsGenerator.generateUrlThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'tiff') {
    return Pro.ThumbsGenerator.generateTiffThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'mp3') {
    if (fileSize && fileSize < maxFileSize) {
      // return Pro.ThumbsGenerator.generateMp3Thumbnail(fileURL, maxSize);
    }
  } else if (Pro && supportedText.indexOf(ext) >= 0) {
    return Pro.ThumbsGenerator.generateTextThumbnail(fileURL, maxSize);
  } else if (Pro && supportedContainers.indexOf(ext) >= 0) {
    if (fileSize && fileSize < maxFileSize) {
      return Pro.ThumbsGenerator.generateZipContainerImageThumbnail(fileURL, maxSize, supportedImgs);
    }
  } else if (Pro && supportedVideos.indexOf(ext) >= 0) {
    return Pro.ThumbsGenerator.generateVideoThumbnail(fileURL, maxSize);
  }
  return generateDefaultThumbnail();
}

function generateDefaultThumbnail() {
  return Promise.resolve('');
}

function generateImageThumbnail(fileURL) {
  return new Promise((resolve) => {
    let canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    let img = new Image();

    const errorHandler = err => {
      console.warn(
        'Error while generating thumbnail for: ' +
        fileURL +
        ' - ' +
        JSON.stringify(err)
      );
      resolve('');
    };

    img.crossOrigin = 'anonymous';
    img.onload = () => {
      EXIF.getData(img, function () { // eslint-disable-line
        // TODO Use EXIF only for jpegs
        const orientation = EXIF.getTag(this, 'Orientation');
        /*
          1 - 0 degrees – the correct orientation, no adjustment is required.
          2 - 0 degrees, mirrored – image has been flipped back-to-front.
          3 - 180 degrees – image is upside down.
          4 - 180 degrees, mirrored – image is upside down and flipped back-to-front.
          5 - 90 degrees – image is on its side.
          6 - 90 degrees, mirrored – image is on its side and flipped back-to-front.
          7 - 270 degrees – image is on its far side.
          8 - 270 degrees, mirrored – image is on its far side and flipped back-to-front.
        */
        let angleInRadians;
        switch (orientation) {
        case 8:
          angleInRadians = 270 * (Math.PI / 180);
          break;
        case 3:
          angleInRadians = 180 * (Math.PI / 180);
          break;
        case 6:
          angleInRadians = 90 * (Math.PI / 180);
          break;
        case 1:
          // ctx.rotate(0);
          break;
        default:
          // ctx.rotate(0);
        }
        if (img.width >= img.height) {
          canvas.width = maxSize;
          canvas.height = (maxSize * img.height) / img.width;
        } else {
          canvas.height = maxSize;
          canvas.width = (maxSize * img.width) / img.height;
        }
        const width = canvas.width;
        const height = canvas.height;
        const x = canvas.width / 2;
        const y = canvas.height / 2;

        ctx.translate(x, y);
        ctx.rotate(angleInRadians);
        ctx.drawImage(img, -width / 2, -height / 2, width, height);
        ctx.rotate(-angleInRadians);
        ctx.translate(-x, -y);
        const dataurl = canvas.toDataURL(AppConfig.thumbType);
        resolve(dataurl);
        img = null;
        canvas = null;
      });
    };
    img.src = fileURL;
    img.onerror = errorHandler;
  });
}
<commit_msg>Added function to generate video thumbnails.<commit_after>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import EXIF from 'exif-js';
import {
  extractFileExtension,
  extractContainingDirectoryPath,
  extractFileName,
  getMetaDirectoryPath
} from '../utils/paths';
import { base64ToArrayBuffer } from '../utils/misc';
import AppConfig from '../config';
import PlatformIO from '../services/platform-io';
import { Pro } from '../pro';

const maxSize = AppConfig.maxThumbSize;

const supportedImgs = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp'];
const supportedContainers = [
  'zip',
  'epub',
  'docx',
  'pptx',
  'pptm',
  'potx',
  'potm',
  'ppxs',
  'ppsm',
  'sldx',
  'sldm',
  'dotx',
  'dotm',
  'xlsx',
  'xlsm',
  'xlst',
  'odp',
  'odg',
  'ods',
  'odt'
];
const supportedText = [
  'txt',
  'md',
  'coffee',
  'c',
  'cpp',
  'css',
  'groovy',
  'haxe',
  'xml',
  'java',
  'js',
  'json',
  'less',
  'markdown',
  'mdown',
  'php',
  'pl',
  'py',
  'rb',
  'ini',
  'sh',
  'sql',
  'mhtml'
];
const supportedVideos = ['mp4', 'webm', 'ogv', 'm4v'];
const maxFileSize = 30 * 1024 * 1024;

function saveThumbnailPromise(filePath, dataURL) {
  return new Promise((resolve, reject) => {
    if (!dataURL || dataURL.length < 7) { // data:,
      return reject('Invalid dataURL');
    }
    const baseString = dataURL.split(',').pop();
    const content = base64ToArrayBuffer(baseString);
    PlatformIO.saveBinaryFilePromise(filePath, content, true)
      .then(() => resolve(filePath))
      .catch((error) => {
        console.warn('Saving thumbnail for ' + filePath + ' failed with ' + JSON.stringify(error));
        return reject('Saving tmb failed for: ' + filePath);
      });
  });
}

function getThumbFileLocation(filePath) {
  const containingFolder = extractContainingDirectoryPath(filePath);
  const metaFolder = getMetaDirectoryPath(containingFolder);
  return metaFolder + AppConfig.dirSeparator + extractFileName(filePath) + AppConfig.thumbFileExt;
}

export function getThumbnailURLPromise(filePath) {
  return new Promise((resolve) => {
    PlatformIO.getPropertiesPromise(filePath).then((origStats) => {
      const thumbFilePath = getThumbFileLocation(filePath);
      PlatformIO.getPropertiesPromise(thumbFilePath).then((stats) => {
        if (stats) { // Thumbnail exists
          if (origStats.lmdt > stats.lmdt) { // Checking if is up to date
            createThumbnailPromise(filePath, origStats.size, thumbFilePath).then((tmbPath) => resolve({ filePath, tmbPath })).catch((err) => {
              console.warn('Thumb generation failed ' + err);
              resolve({ filePath, tmbPath: thumbFilePath });
            });
          } else { // Tmb up to date
            resolve({ filePath });
          }
        } else {
          // Thumbnail does not exists
          createThumbnailPromise(filePath, origStats.size, thumbFilePath).then((tmbPath) => resolve({ filePath, tmbPath })).catch(err => {
            console.warn('Thumb generation failed ' + err);
            resolve({ filePath });
          });
        }
      }).catch(err => {
        console.warn('Error getting tmb properties ' + err);
        resolve({ filePath });
      });
    }).catch(err => {
      console.warn('Error getting file properties ' + err);
      resolve({ filePath });
    });
  });
}

export function createThumbnailPromise(
  filePath: string,
  fileSize: number,
  thumbFilePath: string
) {
  return new Promise((resolve) => {
    generateThumbnailPromise(filePath, fileSize).then((dataURL) => {
      if (dataURL && dataURL.length) {
        saveThumbnailPromise(thumbFilePath, dataURL)
          .then(() => resolve(thumbFilePath))
          .catch((err) => {
            console.warn('Thumb saving failed ' + err);
            resolve();
          });
      } else {
        resolve();
      }
    }).catch((err) => {
      console.warn('Thumb generation failed ' + err);
      resolve();
    });
  });
}

export function generateThumbnailPromise(
  fileURL: string,
  fileSize: number
) {
  const ext = extractFileExtension(fileURL).toLowerCase();

  if (supportedImgs.indexOf(ext) >= 0) {
    if (fileSize && fileSize < maxFileSize) {
      return generateImageThumbnail(fileURL);
    }
  } else if (Pro && ext === 'pdf') {
    return Pro.ThumbsGenerator.generatePDFThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'html') {
    return Pro.ThumbsGenerator.generateHtmlThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'url') {
    return Pro.ThumbsGenerator.generateUrlThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'tiff') {
    return Pro.ThumbsGenerator.generateTiffThumbnail(fileURL, maxSize);
  } else if (Pro && ext === 'mp3') {
    if (fileSize && fileSize < maxFileSize) {
      // return Pro.ThumbsGenerator.generateMp3Thumbnail(fileURL, maxSize);
    }
  } else if (Pro && supportedText.indexOf(ext) >= 0) {
    return Pro.ThumbsGenerator.generateTextThumbnail(fileURL, maxSize);
  } else if (Pro && supportedContainers.indexOf(ext) >= 0) {
    if (fileSize && fileSize < maxFileSize) {
      return Pro.ThumbsGenerator.generateZipContainerImageThumbnail(fileURL, maxSize, supportedImgs);
    }
  } else if (supportedVideos.indexOf(ext) >= 0) {
    if (Pro) {
      return Pro.ThumbsGenerator.generateVideoThumbnail(fileURL, maxSize);
    } else {
      return generateVideoThumbnail(fileURL);
    }
  }
  return generateDefaultThumbnail();
}

function generateDefaultThumbnail() {
  return Promise.resolve('');
}

function generateImageThumbnail(fileURL) {
  return new Promise((resolve) => {
    let canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    let img = new Image();

    const errorHandler = err => {
      console.warn(
        'Error while generating thumbnail for: ' +
        fileURL +
        ' - ' +
        JSON.stringify(err)
      );
      resolve('');
    };

    img.crossOrigin = 'anonymous';
    img.onload = () => {
      EXIF.getData(img, function () { // eslint-disable-line
        // TODO Use EXIF only for jpegs
        const orientation = EXIF.getTag(this, 'Orientation');
        /*
          1 - 0 degrees – the correct orientation, no adjustment is required.
          2 - 0 degrees, mirrored – image has been flipped back-to-front.
          3 - 180 degrees – image is upside down.
          4 - 180 degrees, mirrored – image is upside down and flipped back-to-front.
          5 - 90 degrees – image is on its side.
          6 - 90 degrees, mirrored – image is on its side and flipped back-to-front.
          7 - 270 degrees – image is on its far side.
          8 - 270 degrees, mirrored – image is on its far side and flipped back-to-front.
        */
        let angleInRadians;
        switch (orientation) {
          case 8:
            angleInRadians = 270 * (Math.PI / 180);
            break;
          case 3:
            angleInRadians = 180 * (Math.PI / 180);
            break;
          case 6:
            angleInRadians = 90 * (Math.PI / 180);
            break;
          case 1:
            // ctx.rotate(0);
            break;
          default:
          // ctx.rotate(0);
        }
        if (img.width >= img.height) {
          canvas.width = maxSize;
          canvas.height = (maxSize * img.height) / img.width;
        } else {
          canvas.height = maxSize;
          canvas.width = (maxSize * img.width) / img.height;
        }
        const width = canvas.width;
        const height = canvas.height;
        const x = canvas.width / 2;
        const y = canvas.height / 2;

        ctx.translate(x, y);
        ctx.rotate(angleInRadians);
        ctx.drawImage(img, -width / 2, -height / 2, width, height);
        ctx.rotate(-angleInRadians);
        ctx.translate(-x, -y);
        const dataurl = canvas.toDataURL(AppConfig.thumbType);
        resolve(dataurl);
        img = null;
        canvas = null;
      });
    };
    img.src = fileURL;
    img.onerror = errorHandler;
  });
}

function generateVideoThumbnail(fileURL) {
  return new Promise((resolve) => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const img = new Image();
    const video = document.createElement('video');
    const captureTime = 0; // time in seconds at which to capture the image from the video

    const errorHandler = err => {
      console.warn(
        'Error while generating thumbnail for: ' +
        fileURL +
        ' - ' +
        JSON.stringify(err)
      );
      resolve('');
    };

    video.onloadedmetadata = function () {
      if ('function' === typeof captureTime) {
        captureTime = secs(this.duration);
      }
      // limit the captureTime to the max video duration if it exceeds it
      this.currentTime = Math.min(Math.max(0, (captureTime < 0 ? this.duration : 0) + captureTime), this.duration);
    };

    video.onseeked = function (e) {
      canvas.height = video.videoHeight;
      canvas.width = video.videoWidth;
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      const dataurl = canvas.toDataURL(AppConfig.thumbType);
      img.onerror = errorHandler;
      resolve(dataurl);
      img = null;
      canvas = null;
      video = null;
    };
    video.onerror = errorHandler;
    video.src = fileURL;
  });
}
<|endoftext|>


<commit_before>
// PORTAL RENDER LIMIT HANDLER ///////////////////////////////////////
// Functions to handle hiding low level portal when portal render
// limit is reached.
//
// On initialization, previous minLevel will preserve to previousMinLevel
// and modify with zoom level difference.
//
// After initialized and reset in window.requestData(), "processPortals"
// intercept all portals data in "handleDataResponse". Put the count of
// new portals to newPortalsPerLevel[portal level]. Portals with level >=
// previousMinLevel and already on map will return as result and continue
// to render. Others will save to portalsLowerThanPrevMinLv. If there is
// no more active request of map data, portalsLowerThanPrevMinLv will add
// back to result and render base on current minLevel.
//
// "window.handleFailRequest" is added to handle the case when the last request
// failed and "processPortals" didn't get called. It will get
// portalsLowerThanPrevMinLv base on current minLevel and render them.
//
// "getMinLevel" will be called by "getMinPortalLevel" in utils_misc.js
// to determine min portal level to draw on map.
//
// "getMinLevel" will return minLevel and call "setMinLevel" if
// minLevel hasn't set yet.
//
// In "setMinLevel", it will loop through all portal level from
// high to low, and sum total portal count (old + new) to check
// minLevel.
//
// minLevel is preserved and only replaced when render limit reached in
// higher level, until next window.requestData() called and reset.
//

window.portalRenderLimit = function() {}

window.portalRenderLimit.initialized = false;
window.portalRenderLimit.minLevelSet = false;
window.portalRenderLimit.minLevel = -1;
window.portalRenderLimit.previousMinLevel = -1;
window.portalRenderLimit.previousZoomLevel = null;
window.portalRenderLimit.newPortalsPerLevel = new Array(MAX_PORTAL_LEVEL + 1);
window.portalRenderLimit.portalsLowerThanPrevMinLv = new Array(MAX_PORTAL_LEVEL + 1);

window.portalRenderLimit.init = function () {
  var currentZoomLevel = map.getZoom();
  // previousZoomLevel set to current zoom level on the first run
  portalRenderLimit.previousZoomLevel = portalRenderLimit.previousZoomLevel || currentZoomLevel;

  // If there is a minLevel set in previous run, calculate previousMinLevel with it.
  if(portalRenderLimit.minLevelSet) {
    var zoomDiff = currentZoomLevel - portalRenderLimit.previousZoomLevel;
    portalRenderLimit.previousMinLevel = Math.max(portalRenderLimit.minLevel - zoomDiff, -1);
    portalRenderLimit.previousMinLevel = Math.min(portalRenderLimit.previousMinLevel, MAX_PORTAL_LEVEL);
  }

  portalRenderLimit.previousZoomLevel = currentZoomLevel;

  portalRenderLimit.minLevel = -1;
  portalRenderLimit.resetCounting();
  portalRenderLimit.resetPortalsLowerThanPrevMinLv();
  portalRenderLimit.initialized = true;
}

window.portalRenderLimit.resetCounting = function() {
  portalRenderLimit.minLevelSet = false;
  for(var i = 0; i <= MAX_PORTAL_LEVEL; i++) {
    portalRenderLimit.newPortalsPerLevel[i] = 0;
  }
}

window.portalRenderLimit.resetPortalsLowerThanPrevMinLv = function() {
  for(var i = 0; i <= MAX_PORTAL_LEVEL; i++) {
    portalRenderLimit.portalsLowerThanPrevMinLv[i] = {};
  }
}

// Use to clean up level of portals which is over render limit after counting new portals
window.portalRenderLimit.cleanUpOverLimitPortalLevel = function() {
  var currentMinLevel = window.getMinPortalLevel();
  for(var i = 0; i < currentMinLevel; i++) {
    portalsLayers[i].clearLayers();
  }
}

// Count new portals. Then split lower level portal if it's not last request.
// And Merge back if it's last request and render limit not yet hit
window.portalRenderLimit.splitOrMergeLowLevelPortals = function(originPortals) {
  portalRenderLimit.resetCounting();
  portalRenderLimit.countingPortals(originPortals);

  var resultPortals = requests.isLastRequest('getThinnedEntitiesV2')
    ? portalRenderLimit.mergeLowLevelPortals(originPortals)
    : portalRenderLimit.splitLowLevelPortals(originPortals);

  return resultPortals;
}

window.portalRenderLimit.countingPortals = function(portals) {
  $.each(portals, function(ind, portal) {
    var portalGuid = portal[0];
    var portalLevel = parseInt(getPortalLevel(portal[2]));
    var layerGroup = portalsLayers[portalLevel];

    if(findEntityInLeaflet(layerGroup, window.portals, portalGuid)) return true;

    portalRenderLimit.newPortalsPerLevel[portalLevel]++;
  });
}

// Split the portal if it's lower level and not on map
window.portalRenderLimit.splitLowLevelPortals = function(portals) {
  var resultPortals = {};

  $.each(portals || {}, function(guid, portal) {
    var portalLevel = parseInt(getPortalLevel(portal[2]));
    var portalOnMap = window.portals[guid];

    if(!portalOnMap && portalLevel < portalRenderLimit.previousMinLevel) {
      portalRenderLimit.portalsLowerThanPrevMinLv[portalLevel][guid] = portal;
    } else {
      resultPortals[guid] = portal;
    }
  });
  return resultPortals;
}

window.portalRenderLimit.mergeLowLevelPortals = function(appendTo) {
  var resultPortals = appendTo ? appendTo : {};
  for(var i = portalRenderLimit.getMinLevel();
      i < portalRenderLimit.previousMinLevel;
     i++) {
    $.extend(resultPortals, portalRenderLimit.portalsLowerThanPrevMinLv[i]);
  }

  // Reset portalsLowerThanPrevMinLv, ensure they return only once
  portalRenderLimit.resetPortalsLowerThanPrevMinLv();
  return resultPortals;
}

window.portalRenderLimit.getMinLevel = function() {
  if(!portalRenderLimit.initialized) return -1;
  if(!portalRenderLimit.minLevelSet) portalRenderLimit.setMinLevel();
  return portalRenderLimit.minLevel;
}

window.portalRenderLimit.setMinLevel = function() {
  var totalPortalsCount = 0;
  var newMinLevel = MAX_PORTAL_LEVEL + 1;

  // Find the min portal level under render limit
  while(newMinLevel > 0) {
    var oldPortalCount = layerGroupLength(portalsLayers[newMinLevel - 1]);
    var storedPortalCount = Object.keys(portalRenderLimit.portalsLowerThanPrevMinLv[newMinLevel - 1]).length;
    var newPortalCount = Math.max(storedPortalCount, portalRenderLimit.newPortalsPerLevel[newMinLevel - 1]);

    totalPortalsCount += oldPortalCount + newPortalCount;
    if(totalPortalsCount >= MAX_DRAWN_PORTALS)
      break;
    newMinLevel--;
  }

  // If render limit reached at max portal level, still let portal at max level render
  newMinLevel = Math.min(newMinLevel, MAX_PORTAL_LEVEL);

  portalRenderLimit.minLevel = Math.max(newMinLevel, portalRenderLimit.minLevel);
  portalRenderLimit.minLevelSet = true;
}
<commit_msg>Bug fix: selected portal should not remove when zooming/panning out of range<commit_after>
// PORTAL RENDER LIMIT HANDLER ///////////////////////////////////////
// Functions to handle hiding low level portal when portal render
// limit is reached.
//
// On initialization, previous minLevel will preserve to previousMinLevel
// and modify with zoom level difference.
//
// After initialized and reset in window.requestData(), "processPortals"
// intercept all portals data in "handleDataResponse". Put the count of
// new portals to newPortalsPerLevel[portal level]. Portals with level >=
// previousMinLevel and already on map will return as result and continue
// to render. Others will save to portalsLowerThanPrevMinLv. If there is
// no more active request of map data, portalsLowerThanPrevMinLv will add
// back to result and render base on current minLevel.
//
// "window.handleFailRequest" is added to handle the case when the last request
// failed and "processPortals" didn't get called. It will get
// portalsLowerThanPrevMinLv base on current minLevel and render them.
//
// "getMinLevel" will be called by "getMinPortalLevel" in utils_misc.js
// to determine min portal level to draw on map.
//
// "getMinLevel" will return minLevel and call "setMinLevel" if
// minLevel hasn't set yet.
//
// In "setMinLevel", it will loop through all portal level from
// high to low, and sum total portal count (old + new) to check
// minLevel.
//
// minLevel is preserved and only replaced when render limit reached in
// higher level, until next window.requestData() called and reset.
//

window.portalRenderLimit = function() {}

window.portalRenderLimit.initialized = false;
window.portalRenderLimit.minLevelSet = false;
window.portalRenderLimit.minLevel = -1;
window.portalRenderLimit.previousMinLevel = -1;
window.portalRenderLimit.previousZoomLevel = null;
window.portalRenderLimit.newPortalsPerLevel = new Array(MAX_PORTAL_LEVEL + 1);
window.portalRenderLimit.portalsLowerThanPrevMinLv = new Array(MAX_PORTAL_LEVEL + 1);

window.portalRenderLimit.init = function () {
  var currentZoomLevel = map.getZoom();
  // previousZoomLevel set to current zoom level on the first run
  portalRenderLimit.previousZoomLevel = portalRenderLimit.previousZoomLevel || currentZoomLevel;

  // If there is a minLevel set in previous run, calculate previousMinLevel with it.
  if(portalRenderLimit.minLevelSet) {
    var zoomDiff = currentZoomLevel - portalRenderLimit.previousZoomLevel;
    portalRenderLimit.previousMinLevel = Math.max(portalRenderLimit.minLevel - zoomDiff, -1);
    portalRenderLimit.previousMinLevel = Math.min(portalRenderLimit.previousMinLevel, MAX_PORTAL_LEVEL);
  }

  portalRenderLimit.previousZoomLevel = currentZoomLevel;

  portalRenderLimit.minLevel = -1;
  portalRenderLimit.resetCounting();
  portalRenderLimit.resetPortalsLowerThanPrevMinLv();
  portalRenderLimit.initialized = true;
}

window.portalRenderLimit.resetCounting = function() {
  portalRenderLimit.minLevelSet = false;
  for(var i = 0; i <= MAX_PORTAL_LEVEL; i++) {
    portalRenderLimit.newPortalsPerLevel[i] = 0;
  }
}

window.portalRenderLimit.resetPortalsLowerThanPrevMinLv = function() {
  for(var i = 0; i <= MAX_PORTAL_LEVEL; i++) {
    portalRenderLimit.portalsLowerThanPrevMinLv[i] = {};
  }
}

// Use to clean up level of portals which is over render limit after counting new portals
window.portalRenderLimit.cleanUpOverLimitPortalLevel = function() {
  var currentMinLevel = window.getMinPortalLevel();
  for(var i = 0; i < currentMinLevel; i++) {
    portalsLayers[i].eachLayer(function(item) {
      var itemGuid = item.options.guid;
      // check if 'item' is a portal
      if(getTypeByGuid(itemGuid) != TYPE_PORTAL) return true;
      // Don’t remove if it is selected.
      if(itemGuid == window.selectedPortal) return true;
      portalsLayers[i].removeLayer(item);
    });
  }
}

// Count new portals. Then split lower level portal if it's not last request.
// And Merge back if it's last request and render limit not yet hit
window.portalRenderLimit.splitOrMergeLowLevelPortals = function(originPortals) {
  portalRenderLimit.resetCounting();
  portalRenderLimit.countingPortals(originPortals);

  var resultPortals = requests.isLastRequest('getThinnedEntitiesV2')
    ? portalRenderLimit.mergeLowLevelPortals(originPortals)
    : portalRenderLimit.splitLowLevelPortals(originPortals);

  return resultPortals;
}

window.portalRenderLimit.countingPortals = function(portals) {
  $.each(portals, function(ind, portal) {
    var portalGuid = portal[0];
    var portalLevel = parseInt(getPortalLevel(portal[2]));
    var layerGroup = portalsLayers[portalLevel];

    if(findEntityInLeaflet(layerGroup, window.portals, portalGuid)) return true;

    portalRenderLimit.newPortalsPerLevel[portalLevel]++;
  });
}

// Split the portal if it's lower level and not on map
window.portalRenderLimit.splitLowLevelPortals = function(portals) {
  var resultPortals = {};

  $.each(portals || {}, function(guid, portal) {
    var portalLevel = parseInt(getPortalLevel(portal[2]));
    var portalOnMap = window.portals[guid];

    if(!portalOnMap && portalLevel < portalRenderLimit.previousMinLevel) {
      portalRenderLimit.portalsLowerThanPrevMinLv[portalLevel][guid] = portal;
    } else {
      resultPortals[guid] = portal;
    }
  });
  return resultPortals;
}

window.portalRenderLimit.mergeLowLevelPortals = function(appendTo) {
  var resultPortals = appendTo ? appendTo : {};
  for(var i = portalRenderLimit.getMinLevel();
      i < portalRenderLimit.previousMinLevel;
     i++) {
    $.extend(resultPortals, portalRenderLimit.portalsLowerThanPrevMinLv[i]);
  }

  // Reset portalsLowerThanPrevMinLv, ensure they return only once
  portalRenderLimit.resetPortalsLowerThanPrevMinLv();
  return resultPortals;
}

window.portalRenderLimit.getMinLevel = function() {
  if(!portalRenderLimit.initialized) return -1;
  if(!portalRenderLimit.minLevelSet) portalRenderLimit.setMinLevel();
  return portalRenderLimit.minLevel;
}

window.portalRenderLimit.setMinLevel = function() {
  var totalPortalsCount = 0;
  var newMinLevel = MAX_PORTAL_LEVEL + 1;

  // Find the min portal level under render limit
  while(newMinLevel > 0) {
    var oldPortalCount = layerGroupLength(portalsLayers[newMinLevel - 1]);
    var storedPortalCount = Object.keys(portalRenderLimit.portalsLowerThanPrevMinLv[newMinLevel - 1]).length;
    var newPortalCount = Math.max(storedPortalCount, portalRenderLimit.newPortalsPerLevel[newMinLevel - 1]);

    totalPortalsCount += oldPortalCount + newPortalCount;
    if(totalPortalsCount >= MAX_DRAWN_PORTALS)
      break;
    newMinLevel--;
  }

  // If render limit reached at max portal level, still let portal at max level render
  newMinLevel = Math.min(newMinLevel, MAX_PORTAL_LEVEL);

  portalRenderLimit.minLevel = Math.max(newMinLevel, portalRenderLimit.minLevel);
  portalRenderLimit.minLevelSet = true;
}
<|endoftext|>


<commit_before>$(function(){
	var sub = grabHash(window.location.hash);
	posts = renderPost(sub);

	// Bind click event handlers to our expanders and collapsers.
	$('body').delegate(".expander", "click", function(){
		$(this).parents('ul').first().find('.comments').text('yo yo yo');
		$(this).attr('src', 'img/collapse.png');
		$(this).addClass('collapse');
		$(this).removeClass('expander');
	});
	$('body').delegate(".collapse", "click", function(){
		$(this).parents('ul').first().find('.comments').text('');
		$(this).attr('src', 'img/expand.png');
		$(this).addClass('expander');
		$(this).removeClass('collapse');
	});
	// On a hash change, be sure to reload the page.
	$(window).bind('hashchange', function(e){
		var hash = e.fragment;
		$('body').html('');
		renderPost(hash);
	});
});

function renderPost(subreddit){
		//Determine what subreddit we want, contact reddit accordingly.
		if (subreddit != ''){
			subreddit = "http://www.reddit.com/r/"+subreddit+"/.json?jsonp=?";
		}
		else {
			subreddit = "http://www.reddit.com/.json?jsonp=?";
		}
		var posts = $.getJSON(subreddit,
			 function(posts) {
				var posting;
				var items = posts;
				subreddit = window.location.hash
				posting = "
\n\
require('readme.php');\n\
session_start();
"
				if (subreddit){
					posting += "
$subreddit = $_GET[\""+subreddit+"\"]
";
				}
				else {
					posting += "
$subreddit = $_GET[\"frontpage\"];
";
				}

				$('body').append(posting);
				$.each(items.data.children, function(entry){
					var true_entry = items.data.children[entry].data
					var title = createTitle(true_entry, entry);
					$.get('php.html', function(template) {
						template = template.replace("${title}",title);
						//if (true_entry.domain == "imgur.com"){
						//	true_entry.url == "i." + true_entry.url
						//}
						if (/\.(jpg)|(gif)|(png)$/.test(true_entry.url)){
							template = template.replace("${fancybox}", "image");
						}
						else if (/www.youtube.com/.test(true_entry.url)){
							template = template.replace("${fancybox}", "video");
						}
						else {
							template = template.replace("${fancybox}", "");
						}
						template = template.replace("${subreddit}", true_entry.subreddit);
						template = template.replace("${url}", true_entry.url);
						template = template.replace("${author}", true_entry.author);
						template = template.replace("${score}", true_entry.score);
						template = template.replace("${domain}", true_entry.domain);
						template = template.replace("${fulltitle}", true_entry.title);
						template = template.replace("${num_comments}", true_entry.num_comments);
						$('body').append(template);
						$("pre.php").snippet("php", {style:"emacs", showNum: false, transparent: true, menu: false});
						//alert(title);
						$("a.image").fancybox({
							'transitionIn'	:	'elastic',
							'transitionOut'	:	'elastic',
							'speedIn'		:	600,
							'speedOut'		:	200
						});
						$("a.video").fancybox({
							'padding' : 0,
							'autoScale' : false,
							'transitionIn' : 'elastic',
							'transitionOut' : 'elastic',
							'speedIn' : 600,
							'speedOut': 200,
							'title' : this.title,
							'width' : 640,
							'height': 385,
							'href' : this.href.replace(new RegExp("watch\\?v=", "i"), 'v/'),
							'type' : 'swf',
							'swf' : {
								'wmode' : 'transparent',
								'allowfullscreen' : 'true'
							}
						});
						$("#loading").remove();
					});

				});
		});
		return posts;
}

function createTitle(entry, number){
	var post;

	// Emulate camel case, slice ridiculous title lengths, and remove characters.
	var title = entry.title
				.toLowerCase().substr(0,20).replace(/\W+/gi, '').split(' ');
	for (token in title){
		title[token] = title[token].charAt(0).toUpperCase() + title[token].slice(1);
	};
	title = title.join('');
	return title;
}
function grabHash(hash){
	return hash.replace('#','');
}
function getComments(entry){
	var subreddit = entry.subreddit;
	alert(subreddit);
	return subreddit;
}

<commit_msg>working on comments<commit_after>$(function(){
	var sub = grabHash(window.location.hash);
	posts = renderPost(sub);

	// Bind click event handlers to our expanders and collapsers.
	$('body').delegate(".expander", "click", function(){
		$(this).parents('ul').first().find('.comments').text(getComments($(this).attr('id')));
		$(this).attr('src', 'img/collapse.png');
		$(this).addClass('collapse');
		$(this).removeClass('expander');
	});
	$('body').delegate(".collapse", "click", function(){
		$(this).parents('ul').first().find('.comments').text('');
		$(this).attr('src', 'img/expand.png');
		$(this).addClass('expander');
		$(this).removeClass('collapse');
	});
	// On a hash change, be sure to reload the page.
	$(window).bind('hashchange', function(e){
		var hash = e.fragment;
		$('body').html('');
		renderPost(hash);
	});
});

function renderPost(subreddit){
		//Determine what subreddit we want, contact reddit accordingly.
		if (subreddit != ''){
			subreddit = "http://www.reddit.com/r/"+subreddit+"/.json?jsonp=?";
		}
		else {
			subreddit = "http://www.reddit.com/.json?jsonp=?";
		}
		var posts = $.getJSON(subreddit,
			 function(posts) {
				var posting;
				var items = posts;
				subreddit = window.location.hash
				posting = "
\n\
require('readme.php');\n\
session_start();
"
				if (subreddit){
					posting += "
$subreddit = $_GET[\""+subreddit+"\"];
";
				}
				else {
					posting += "
$subreddit = $_GET[\"frontpage\"];
";
				}

				$('body').append(posting);
				$.each(items.data.children, function(entry){
					var true_entry = items.data.children[entry].data
					var title = createTitle(true_entry, entry);
					$.get('php.html', function(template) {
						template = template.replace("${title}",title);
						// People sometimes post non-direct images, most commonly imgur.com
						// this block will guess the direct link equivalent. Not very accurately...Need to look into changing this.

						if (true_entry.selftext){
							template = template.replace("${selftext}", formatSelfText(true_entry.selftext));
						}
						else {
							template = template.replace("${selftext}", "");
						}
						if (true_entry.domain == "imgur.com"){
							true_entry.url = true_entry.url.replace("imgur.com", "i.imgur.com") + ".jpg"
						}
						if (/\.(jpg)|(gif)|(png)$/.test(true_entry.url)){
							template = template.replace("${fancybox}", "image");
						}
						else if (/www.youtube.com/.test(true_entry.url)){
							template = template.replace("${fancybox}", "video");
						}
						else {
							template = template.replace("${fancybox}", "");
						}
						template = template.replace("${subreddit}", true_entry.subreddit);
						template = template.replace("${suburl}", "#" + true_entry.subreddit);
						template = template.replace("${url}", true_entry.url);
						template = template.replace("${author}", true_entry.author);
						template = template.replace("${score}", true_entry.score);
						template = template.replace("${domain}", true_entry.domain);
						template = template.replace("${fulltitle}", true_entry.title);
						template = template.replace("${num_comments}", true_entry.num_comments);
						template = template.replace("${entry_id}", true_entry.name);
						$('body').append(template);
						$("pre.php").snippet("php", {style:"emacs", showNum: false, transparent: true, menu: false});
						//alert(title);
						$("a.image").fancybox({
							'transitionIn'	:	'elastic',
							'transitionOut'	:	'elastic',
							'speedIn'		:	600,
							'speedOut'		:	200
						});
						// TODO: Take a look at this, it's for youtube videos in fancybox.
						//$("a.video").fancybox({
						//	'padding' : 0,
						//	'autoScale' : false,
						//	'transitionIn' : 'elastic',
						//	'transitionOut' : 'elastic',
						//	'speedIn' : 600,
						//	'speedOut': 200,
						//	'title' : this.title,
						//	'width' : 640,
						//	'height': 385,
						//	'href' : this.href.replace(new RegExp("watch\\?v=", "i"), 'v/'),
						//	'type' : 'swf',
						//	'swf' : {
						//		'wmode' : 'transparent',
						//		'allowfullscreen' : 'true'
						//	}
						//});

					});

				});
		});
		return posts;
}

function createTitle(entry, number){
	var post;

	// Emulate camel case, slice ridiculous title lengths, and remove characters.
	var title = entry.title
				.toLowerCase().substring(0,20).replace(/[^A-Za-z0-9\s\s+]/gi, '').split(' ');
	for (token in title){
		title[token] = title[token].charAt(0).toUpperCase() + title[token].slice(1);
	};
	title = title.join('');
	return title;
}
function grabHash(hash){
	return hash.replace('#','');
}
function getComments(entry){
	// Retrieve and display comments
	var id = entry.name.replace('t3_', '');
	return id;
}
function formatSelfText(text){
	// If it's a self text we're going to output a large
	// comment block.
	text = text.replace(/(\r\n|\n|\r)/gm,"");
	var new_text = "";
	num_splits = Math.floor(text.length / 80);
	i = 0
	while (i <= num_splits){
		new_text = new_text + "// " + text.substring(i * 80, (i+1)*80) + "\n" + "    ";
		i++;
	}
	return new_text;
}<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import BaseDataView from './BaseDataView'

function getCookieValue(a) {
  var b = document.cookie.match('(^|;)\\s*' + a + '\\s*=\\s*([^;]+)');
  return b ? b.pop() : '';
}

const client = new ApolloClient({
  uri: process.env.REACT_APP_GRAPHQL_API_URL + 'graphql',
  headers: {
    'Authorization': 'Bearer ' + getCookieValue('token')
  }
  // options: { mode: 'no-cors' }
});
window.apiUrl = process.env.REACT_APP_FLASK_API_URL;

const Root = ({ store }) => (
  <ApolloProvider client={client}>
    <Provider store={store}>
      <BrowserRouter>
        <BaseDataView />
      </BrowserRouter>
    </Provider>
  </ApolloProvider>
)

Root.propTypes = {
  store: PropTypes.object.isRequired
}

export default Root
<commit_msg>Use correct uri for AppolloClient<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import BaseDataView from './BaseDataView'

function getCookieValue(a) {
  var b = document.cookie.match('(^|;)\\s*' + a + '\\s*=\\s*([^;]+)');
  return b ? b.pop() : '';
}

const client = new ApolloClient({
  uri: process.env.REACT_APP_FLASK_API_URL + 'graphql',
  headers: {
    'Authorization': 'Bearer ' + getCookieValue('token')
  }
  // options: { mode: 'no-cors' }
});
window.apiUrl = process.env.REACT_APP_FLASK_API_URL;

const Root = ({ store }) => (
  <ApolloProvider client={client}>
    <Provider store={store}>
      <BrowserRouter>
        <BaseDataView />
      </BrowserRouter>
    </Provider>
  </ApolloProvider>
)

Root.propTypes = {
  store: PropTypes.object.isRequired
}

export default Root
<|endoftext|>


<commit_before>'use strict';

var repl = require('repl');
var _ = require('lodash');
var RSVP = require('rsvp');
var request = require('request');
var util = require('util');

var FunctionsEmulator = require('../lib/functionsEmulator');
var LocalFunction = require('../lib/localFunction');
var Command = require('../lib/command');
var logger = require('../lib/logger');
var requireAccess = require('../lib/requireAccess');
var requireConfig = require('../lib/requireConfig');
var scopes = require('../lib/scopes');

module.exports = new Command('experimental:functions:shell')
  .description('launch full Node shell with emulated functions')
  .option('-p, --port <port>', 'the port on which to emulate functions (default: 5000)', 5000)
  .before(requireConfig)
  .before(requireAccess, [scopes.CLOUD_PLATFORM])
  .action(function(options) {
    options.port = parseInt(options.port, 10);
    var emulator = new FunctionsEmulator(options);

    return emulator.start(true).then(function() {
      if (emulator.emulatedFunctions.length === 0) {
        logger.info('No functions emulated.');
        process.exit();
      }

      var writer = function(output) {
        // Prevent full print out of Request object when a request is made
        if (output instanceof request.Request) {
          return 'Sent request to function.';
        }
        return util.inspect(output);
      };

      var prompt = process.platform === 'darwin' ? '🔥 > ' : 'firebase > ';

      var replServer = repl.start({
        prompt: prompt,
        writer: writer,
        useColors: true
      });

      _.forEach(emulator.triggers, function(trigger) {
        if (_.includes(emulator.emulatedFunctions, trigger.name)) {
          var localFunction = new LocalFunction(trigger, emulator.urls, emulator.controller);
          replServer.context[trigger.name] = localFunction.call;
        }
      });
      replServer.context.config = emulator.config;
      replServer.context.help = 'Instructions for the Functions Shell can be found at: ' +
        'https://firebase.google.com/docs/functions/local-emulator';
    }).then(function() {
      return new RSVP.Promise(function(resolve) {
        process.on('SIGINT', function() {
          return emulator.stop().then(resolve).catch(resolve);
        });
      });
    });
  });
<commit_msg>Modify functions shell prompt to use "firebase >". (#469)<commit_after>'use strict';

var repl = require('repl');
var _ = require('lodash');
var RSVP = require('rsvp');
var request = require('request');
var util = require('util');

var FunctionsEmulator = require('../lib/functionsEmulator');
var LocalFunction = require('../lib/localFunction');
var Command = require('../lib/command');
var logger = require('../lib/logger');
var requireAccess = require('../lib/requireAccess');
var requireConfig = require('../lib/requireConfig');
var scopes = require('../lib/scopes');

module.exports = new Command('experimental:functions:shell')
  .description('launch full Node shell with emulated functions')
  .option('-p, --port <port>', 'the port on which to emulate functions (default: 5000)', 5000)
  .before(requireConfig)
  .before(requireAccess, [scopes.CLOUD_PLATFORM])
  .action(function(options) {
    options.port = parseInt(options.port, 10);
    var emulator = new FunctionsEmulator(options);

    return emulator.start(true).then(function() {
      if (emulator.emulatedFunctions.length === 0) {
        logger.info('No functions emulated.');
        process.exit();
      }

      var writer = function(output) {
        // Prevent full print out of Request object when a request is made
        if (output instanceof request.Request) {
          return 'Sent request to function.';
        }
        return util.inspect(output);
      };

      var prompt = 'firebase > ';

      var replServer = repl.start({
        prompt: prompt,
        writer: writer,
        useColors: true
      });

      _.forEach(emulator.triggers, function(trigger) {
        if (_.includes(emulator.emulatedFunctions, trigger.name)) {
          var localFunction = new LocalFunction(trigger, emulator.urls, emulator.controller);
          replServer.context[trigger.name] = localFunction.call;
        }
      });
      replServer.context.config = emulator.config;
      replServer.context.help = 'Instructions for the Functions Shell can be found at: ' +
        'https://firebase.google.com/docs/functions/local-emulator';
    }).then(function() {
      return new RSVP.Promise(function(resolve) {
        process.on('SIGINT', function() {
          return emulator.stop().then(resolve).catch(resolve);
        });
      });
    });
  });
<|endoftext|>


<commit_before>import { uniq } from 'lodash';
import config from 'config';
import createDebug from 'debug';
import pgFormat from 'pg-format';

import { parseQuery, queryComplexity } from '../search/parser';
import {
  IN_POSTS,
  IN_COMMENTS,
  Condition,
  IN_ALL,
  ScopeStart,
  AnyText,
  InScope
} from '../search/query-tokens';
import { List } from '../open-lists';
import { Comment } from '../../models';

import { sqlIn, sqlNotIn, sqlIntarrayIn } from './utils';

///////////////////////////////////////////////////
// Search
///////////////////////////////////////////////////

const debug = createDebug('freefeed:searchEngine');

const searchTrait = (superClass) =>
  class extends superClass {
    async search(
      query,
      {
        viewerId = null,
        limit = 30,
        offset = 0,
        sort = 'bumped',
        maxQueryComplexity = config.search.maxQueryComplexity
      } = {}
    ) {
      const parsedQuery = parseQuery(query);

      if (queryComplexity(parsedQuery) > maxQueryComplexity) {
        throw new Error(`The search query is too complex, try to simplify it`);
      }

      if (
        !viewerId &&
        parsedQuery.some((t) => t instanceof Condition && t.condition === 'in-my')
      ) {
        throw new Error(`Please sign in to use 'in-my:' filter`);
      }

      /**
       * There are three search scopes:
       * - IN_ALL (default scope, search something in posts OR in comments)
       * - IN_POSTS (search something in posts only)
       * - IN_COMMENTS (search something in comments only)
       *
       * These scopes produces a corresponding conditions that joins by AND.
       * IN_ALL query, in turn, consists of two sub-conditions, for 'posts' and
       * for 'comments' tables, joined by OR. Also we have additional
       * restrictions for the posts (privacy and bans) and comments (hideType
       * and bans). So the resulting query is looks like:
       *
       * ((Posts_inAll AND Posts_restr) OR (Comments_inAll AND Comments_restr))
       * AND (Posts_inPosts AND Posts_restr) AND (Comments_inComments AND
       * Comments_restr)
       *
       * Using the UNION statement instead of explicit OR we can write this
       * request in the following form:
       *
       * Posts_inAll AND Posts_inPosts
       * AND Comments_inComments AND Posts_restr AND Comments_restr
       * UNION
       * Comments_inAll
       * AND Comments_inComments AND Posts_restr AND Comments_restr
       *
       * It is the full form of request but in practice it may be simple. For
       * example, if query uses only the IN_ALL scope we have:
       *
       * Posts_inAll AND Posts_restr
       * UNION
       * Comments_inAll AND Posts_restr AND Comments_restr
       */

      // Map from username to User/Group object (or null)
      const accounts = await this._getAccountsUsedInQuery(
        parsedQuery,
        viewerId
      );

      // Authorship
      const allContentAuthors = getAuthorNames(parsedQuery, IN_ALL);
      let postAuthors = getAuthorNames(parsedQuery, IN_POSTS);
      const commentAuthors = getAuthorNames(parsedQuery, IN_COMMENTS);

      for (const list of [allContentAuthors, postAuthors, commentAuthors]) {
        list.items = list.items
          .map((name) => accounts[name] && accounts[name].id)
          .filter(Boolean);
      }

      // Posts feeds
      const postsFeedIdsLists = await this._getFeedIdsLists(
        parsedQuery,
        accounts
      );

      // Special case: in-my:discussions
      //
      // The in-my:discussions filter is effectively a "commented-by:me | liked-by:me |
      // posts-from:me". But the first two parts are feeds and the last is an authorship, so we can
      // not express this in one simple form and must process the "| posts-from:me" part separately.
      let orPostsFromMe = false;

      for (const token of parsedQuery) {
        if (
          token instanceof Condition &&
          token.condition === 'in-my' &&
          token.args.some((a) => /discussion/.test(a))
        ) {
          if (token.exclude) {
            // ! (| posts-from:me) === & (!posts-from:me)
            postAuthors = List.intersection(
              postAuthors,
              new List([viewerId], false)
            );
          } else {
            orPostsFromMe = true;
          }
        }
      }

      // Text search
      const inAllTSQuery = getTSQuery(parsedQuery, IN_ALL);
      const inPostsTSQuery = getTSQuery(parsedQuery, IN_POSTS);
      const inCommentsTSQuery = getTSQuery(parsedQuery, IN_COMMENTS);

      // Create partial SQL queries
      const inAllPostsSQL = andJoin([
        inAllTSQuery && `p.body_tsvector @@ ${inAllTSQuery}`,
        sqlIn('p.user_id', allContentAuthors)
      ]);

      const inAllCommentsSQL = andJoin([
        inAllTSQuery && `c.body_tsvector @@ ${inAllTSQuery}`,
        sqlIn('c.user_id', allContentAuthors)
      ]);

      let postsFeedsSQL = andJoin([
        ...postsFeedIdsLists.map((list) =>
          sqlIntarrayIn('p.feed_ids', list)
        )
      ]);

      // Special hack for in-my:discussions
      if (orPostsFromMe) {
        postsFeedsSQL = orJoin([postsFeedsSQL, pgFormat('p.user_id=%L', viewerId)], 'true');
      }

      const inPostsSQL = andJoin([
        inPostsTSQuery && `p.body_tsvector @@ ${inPostsTSQuery}`,
        sqlIn('p.user_id', postAuthors),
        postsFeedsSQL
      ]);

      const inCommentsSQL = andJoin([
        inCommentsTSQuery && `c.body_tsvector @@ ${inCommentsTSQuery}`,
        sqlIn('c.user_id', commentAuthors)
      ]);

      // Are we using the 'comments' table?
      const useCommentsTable =
        inAllCommentsSQL !== 'true' || inCommentsSQL !== 'true';

      const [
        // Private feeds viewer can read
        visiblePrivateFeedIntIds,
        // Users who banned viewer or banned by viewer (viewer should not see their posts)
        bannedUsersIds,
        // Users banned by viewer (for comments)
        bannedByViewer
      ] = await Promise.all([
        viewerId ? this.getVisiblePrivateFeedIntIds(viewerId) : [],
        viewerId ? this.getUsersBansOrWasBannedBy(viewerId) : [],
        viewerId && useCommentsTable ? await this.getUserBansIds(viewerId) : []
      ]);

      // Additional restrictions for comments
      const commentsRestrictionSQL = useCommentsTable
        ? andJoin([
          pgFormat('c.hide_type=%L', Comment.VISIBLE),
          sqlNotIn('c.user_id', bannedByViewer)
        ])
        : 'true';

      // Additional restrictions for posts
      const postsRestrictionsSQL = andJoin([
        // Privacy
        viewerId
          ? pgFormat(
            `(not p.is_private or p.destination_feed_ids && %L)`,
            `{${visiblePrivateFeedIntIds.join(',')}}`
          )
          : 'not p.is_protected',
        // Bans
        sqlNotIn('p.user_id', bannedUsersIds)
      ]);

      // Now we buid full query
      const postsPart = andJoin([
        inAllPostsSQL,
        inPostsSQL,
        postsRestrictionsSQL,
        inCommentsSQL,
        inCommentsSQL !== 'true' && commentsRestrictionSQL
      ]);
      const commentsPart =
        useCommentsTable &&
        andJoin([
          inAllCommentsSQL,
          inPostsSQL,
          postsRestrictionsSQL,
          inCommentsSQL,
          commentsRestrictionSQL
        ]);

      const fullPostsSQL = `select p.uid, p.${sort}_at as date from posts p ${
        inCommentsSQL !== 'true'
          ? 'left join comments c on c.post_id = p.uid'
          : ''
      } where ${postsPart}`;

      const fullCommentsSQL =
        useCommentsTable &&
        `select p.uid, p.${sort}_at as date from posts p ` +
          ` join comments c on c.post_id = p.uid where ${commentsPart}`;

      const fullSQL = `${fullPostsSQL} ${
        fullCommentsSQL ? `\nunion\n${fullCommentsSQL}` : ''
      }\norder by date desc limit ${+limit} offset ${+offset}`;

      debug(fullSQL);

      return (await this.database.raw(fullSQL)).rows.map((r) => r.uid);
    }

    async _getAccountsUsedInQuery(parsedQuery, viewerId) {
      const conditionsWithAccNames = [
        'in',
        'commented-by',
        'liked-by',
        'from',
        'author'
      ];

      const accounts = {}; // Map from username to User/Group object (or null)

      let accountNames = [];
      viewerId && accountNames.push('me');

      for (const token of parsedQuery) {
        if (
          token instanceof Condition &&
          conditionsWithAccNames.includes(token.condition)
        ) {
          accountNames.push(...token.args);
        }
      }

      accountNames = uniq(accountNames);

      let meUser = null;

      if (accountNames.includes('me')) {
        if (!viewerId) {
          throw new Error(`Please sign in to use 'me' as username`);
        }

        meUser = await this.getFeedOwnerById(viewerId);
      }

      const accountObjects = await this.getFeedOwnersByUsernames(accountNames);

      for (const ao of accountObjects) {
        accounts[ao.username] = ao;
      }

      for (const name of accountNames) {
        if (name === 'me') {
          accounts[name] = meUser;
        } else if (!accounts[name]) {
          accounts[name] = null;
        }
      }

      return accounts;
    }

    /**
     * Post can belongs to many feeds, so this function returns an array of Lists of feed intId's.
     *
     * @param {Array} tokens
     * @param {Object} accountsMap
     * @returns {Promise<Array>}
     */
    async _getFeedIdsLists(tokens, accountsMap) {
      const condToFeedNames = {
        in:             'Posts',
        'commented-by': 'Comments',
        'liked-by':     'Likes'
      };
      const myFeedNames = ['saves', 'directs', 'discussions', 'friends'];

      return await Promise.all(
        tokens
          .filter(
            (t) =>
              t instanceof Condition &&
              (!!condToFeedNames[t.condition] || t.condition === 'in-my')
          )
          .map(async (t) => {
            // in:, commented-by:, liked-by:
            if (condToFeedNames[t.condition]) {
              const userIds = uniq(t.args)
                .map((n) => accountsMap[n] && accountsMap[n].id)
                .filter(Boolean);

              const feedIntIds = await this.getUsersNamedFeedsIntIds(userIds, [
                condToFeedNames[t.condition]
              ]);
              return new List(feedIntIds, !t.exclude);
            }

            // in-my:
            const currentUser = accountsMap['me'];
            const feedIntIds = await Promise.all(
              uniq(t.args)
                .map((n) => (/s$/i.test(n) ? n : `${n}s`))
                .filter((n) => myFeedNames.includes(n))
                .map(async (name) => {
                  switch (name) {
                    case 'saves': {
                      return [
                        await currentUser.getGenericTimelineIntId('Saves')
                      ];
                    }
                    case 'directs': {
                      return [
                        await currentUser.getGenericTimelineIntId('Directs')
                      ];
                    }
                    case 'discussions': {
                      return await Promise.all([
                        currentUser.getCommentsTimelineIntId(),
                        currentUser.getLikesTimelineIntId()
                      ]);
                    }
                    case 'friends': {
                      const homeFeed = await currentUser.getRiverOfNewsTimeline();
                      const { destinations } = await this.getSubscriprionsIntIds(homeFeed);
                      return destinations;
                    }
                  }

                  return [];
                })
            );
            return new List(feedIntIds, !t.exclude);
          })
      );
    }
  };

export default searchTrait;

function joinThem(array, joinBy, defaultValue, shortcutValue, skipValue) {
  if (array.some((x) => x === shortcutValue)) {
    return shortcutValue;
  }

  const parts = array.filter((x) => !!x && x !== skipValue);

  if (parts.length === 0) {
    return defaultValue;
  }

  if (parts.length === 1) {
    return parts[0];
  }

  return `(${parts.join(` ${joinBy} `)})`;
}

function andJoin(array, def = 'true') {
  return joinThem(array, 'and', def, 'false', 'true');
}

function orJoin(array, def = 'false') {
  return joinThem(array, 'or', def, 'true', 'false');
}

function walkWithScope(tokens, action) {
  let currentScope = IN_ALL;

  for (const token of tokens) {
    if (token instanceof ScopeStart) {
      currentScope = token.scope;
      continue;
    }

    action(token, currentScope);
  }
}

function getTSQuery(tokens, targetScope) {
  const result = [];

  walkWithScope(tokens, (token, currentScope) => {
    if (token instanceof AnyText && currentScope === targetScope) {
      result.push(token.toTSQuery());
    }

    if (token instanceof InScope && token.scope === targetScope) {
      result.push(...token.anyTexts.map((t) => t.toTSQuery()));
    }
  });

  return result.length > 1 ? `(${result.join(' && ')})` : result.join(' && ');
}

function getAuthorNames(tokens, targetScope) {
  let result = List.everything();

  walkWithScope(tokens, (token, currentScope) => {
    if (
      token instanceof Condition &&
      ((token.condition === 'from' && targetScope === IN_POSTS) ||
        (token.condition === 'author' && targetScope === currentScope))
    ) {
      result = List.intersection(
        result,
        token.exclude ? List.inverse(token.args) : token.args
      );
    }
  });

  return result;
}
<commit_msg>Use CTE in search SQL query for better performance<commit_after>import { uniq } from 'lodash';
import config from 'config';
import createDebug from 'debug';
import pgFormat from 'pg-format';

import { parseQuery, queryComplexity } from '../search/parser';
import {
  IN_POSTS,
  IN_COMMENTS,
  Condition,
  IN_ALL,
  ScopeStart,
  AnyText,
  InScope
} from '../search/query-tokens';
import { List } from '../open-lists';
import { Comment } from '../../models';

import { sqlIn, sqlNotIn, sqlIntarrayIn } from './utils';

///////////////////////////////////////////////////
// Search
///////////////////////////////////////////////////

const debug = createDebug('freefeed:searchEngine');

const searchTrait = (superClass) =>
  class extends superClass {
    async search(
      query,
      {
        viewerId = null,
        limit = 30,
        offset = 0,
        sort = 'bumped',
        maxQueryComplexity = config.search.maxQueryComplexity
      } = {}
    ) {
      const parsedQuery = parseQuery(query);

      if (queryComplexity(parsedQuery) > maxQueryComplexity) {
        throw new Error(`The search query is too complex, try to simplify it`);
      }

      if (
        !viewerId &&
        parsedQuery.some((t) => t instanceof Condition && t.condition === 'in-my')
      ) {
        throw new Error(`Please sign in to use 'in-my:' filter`);
      }

      /**
       * There are three search scopes:
       * - IN_ALL (default scope, search something in posts OR in comments)
       * - IN_POSTS (search something in posts only)
       * - IN_COMMENTS (search something in comments only)
       *
       * These scopes produces a corresponding conditions that joins by AND.
       * IN_ALL query, in turn, consists of two sub-conditions, for 'posts' and
       * for 'comments' tables, joined by OR. Also we have additional
       * restrictions for the posts (privacy and bans) and comments (hideType
       * and bans). So the resulting query is looks like:
       *
       * ((Posts_inAll AND Posts_restr) OR (Comments_inAll AND Comments_restr))
       * AND (Posts_inPosts AND Posts_restr) AND (Comments_inComments AND
       * Comments_restr)
       *
       * Using the UNION statement instead of explicit OR we can write this
       * request in the following form:
       *
       * Posts_inAll AND Posts_inPosts
       * AND Comments_inComments AND Posts_restr AND Comments_restr
       * UNION
       * Comments_inAll
       * AND Comments_inComments AND Posts_restr AND Comments_restr
       *
       * It is the full form of request but in practice it may be simple. For
       * example, if query uses only the IN_ALL scope we have:
       *
       * Posts_inAll AND Posts_restr
       * UNION
       * Comments_inAll AND Posts_restr AND Comments_restr
       */

      // Map from username to User/Group object (or null)
      const accounts = await this._getAccountsUsedInQuery(
        parsedQuery,
        viewerId
      );

      // Authorship
      const allContentAuthors = getAuthorNames(parsedQuery, IN_ALL);
      let postAuthors = getAuthorNames(parsedQuery, IN_POSTS);
      const commentAuthors = getAuthorNames(parsedQuery, IN_COMMENTS);

      for (const list of [allContentAuthors, postAuthors, commentAuthors]) {
        list.items = list.items
          .map((name) => accounts[name] && accounts[name].id)
          .filter(Boolean);
      }

      // Posts feeds
      const postsFeedIdsLists = await this._getFeedIdsLists(
        parsedQuery,
        accounts
      );

      // Special case: in-my:discussions
      //
      // The in-my:discussions filter is effectively a "commented-by:me | liked-by:me |
      // posts-from:me". But the first two parts are feeds and the last is an authorship, so we can
      // not express this in one simple form and must process the "| posts-from:me" part separately.
      let orPostsFromMe = false;

      for (const token of parsedQuery) {
        if (
          token instanceof Condition &&
          token.condition === 'in-my' &&
          token.args.some((a) => /discussion/.test(a))
        ) {
          if (token.exclude) {
            // ! (| posts-from:me) === & (!posts-from:me)
            postAuthors = List.intersection(
              postAuthors,
              new List([viewerId], false)
            );
          } else {
            orPostsFromMe = true;
          }
        }
      }

      // Text search
      const inAllTSQuery = getTSQuery(parsedQuery, IN_ALL);
      const inPostsTSQuery = getTSQuery(parsedQuery, IN_POSTS);
      const inCommentsTSQuery = getTSQuery(parsedQuery, IN_COMMENTS);

      // Create partial SQL queries
      const inAllPostsSQL = andJoin([
        inAllTSQuery && `p.body_tsvector @@ ${inAllTSQuery}`,
        sqlIn('p.user_id', allContentAuthors)
      ]);

      const inAllCommentsSQL = andJoin([
        inAllTSQuery && `c.body_tsvector @@ ${inAllTSQuery}`,
        sqlIn('c.user_id', allContentAuthors)
      ]);

      let postsFeedsSQL = andJoin([
        ...postsFeedIdsLists.map((list) =>
          sqlIntarrayIn('p.feed_ids', list)
        )
      ]);

      // Special hack for in-my:discussions
      if (orPostsFromMe) {
        postsFeedsSQL = orJoin([postsFeedsSQL, pgFormat('p.user_id=%L', viewerId)], 'true');
      }

      const inPostsSQL = andJoin([
        inPostsTSQuery && `p.body_tsvector @@ ${inPostsTSQuery}`,
        sqlIn('p.user_id', postAuthors),
        postsFeedsSQL
      ]);

      const inCommentsSQL = andJoin([
        inCommentsTSQuery && `c.body_tsvector @@ ${inCommentsTSQuery}`,
        sqlIn('c.user_id', commentAuthors)
      ]);

      // Are we using the 'comments' table?
      const useCommentsTable =
        inAllCommentsSQL !== 'true' || inCommentsSQL !== 'true';

      const [
        // Private feeds viewer can read
        visiblePrivateFeedIntIds,
        // Users who banned viewer or banned by viewer (viewer should not see their posts)
        bannedUsersIds,
        // Users banned by viewer (for comments)
        bannedByViewer
      ] = await Promise.all([
        viewerId ? this.getVisiblePrivateFeedIntIds(viewerId) : [],
        viewerId ? this.getUsersBansOrWasBannedBy(viewerId) : [],
        viewerId && useCommentsTable ? await this.getUserBansIds(viewerId) : []
      ]);

      // Additional restrictions for comments
      const commentsRestrictionSQL = useCommentsTable
        ? andJoin([
          pgFormat('c.hide_type=%L', Comment.VISIBLE),
          sqlNotIn('c.user_id', bannedByViewer)
        ])
        : 'true';

      // Additional restrictions for posts
      const postsRestrictionsSQL = andJoin([
        // Privacy
        viewerId
          ? pgFormat(
            `(not p.is_private or p.destination_feed_ids && %L)`,
            `{${visiblePrivateFeedIntIds.join(',')}}`
          )
          : 'not p.is_protected',
        // Bans
        sqlNotIn('p.user_id', bannedUsersIds)
      ]);

      // Now we buid full query
      const postsPart = andJoin([
        inAllPostsSQL,
        // inPostsSQL, // Use in in CTE (see fullSQL below)
        postsRestrictionsSQL,
        inCommentsSQL,
        inCommentsSQL !== 'true' && commentsRestrictionSQL
      ]);
      const commentsPart =
        useCommentsTable &&
        andJoin([
          inAllCommentsSQL,
          // inPostsSQL, // Use in in CTE (see fullSQL below)
          postsRestrictionsSQL,
          inCommentsSQL,
          commentsRestrictionSQL
        ]);

      const fullPostsSQL = `select p.uid, p.${sort}_at as date from posts p ${
        inCommentsSQL !== 'true'
          ? 'left join comments c on c.post_id = p.uid'
          : ''
      } where ${postsPart}`;

      const fullCommentsSQL =
        useCommentsTable &&
        `select p.uid, p.${sort}_at as date from posts p ` +
          ` join comments c on c.post_id = p.uid where ${commentsPart}`;

      const fullSQL = [
        // Use CTE here for better performance. PostgreSQL optimizer cannot
        // properly optimize conditions like `where feed_ids && '{111}' and
        // user_id <> '222-222-222'`. It is better to filter `feed_ids &&` first
        // and `user_id <>` later. We force this order using the CTE (inPostsSQL
        // is mostly about `feed_ids &&` conditions).
        inPostsSQL !== 'true' && `with posts as (select * from posts p where ${inPostsSQL})`,
        fullPostsSQL,
        fullCommentsSQL && `union\n${fullCommentsSQL}`,
        `order by date desc limit ${+limit} offset ${+offset}`
      ].filter(Boolean).join('\n');

      debug(fullSQL);

      return (await this.database.raw(fullSQL)).rows.map((r) => r.uid);
    }

    async _getAccountsUsedInQuery(parsedQuery, viewerId) {
      const conditionsWithAccNames = [
        'in',
        'commented-by',
        'liked-by',
        'from',
        'author'
      ];

      const accounts = {}; // Map from username to User/Group object (or null)

      let accountNames = [];
      viewerId && accountNames.push('me');

      for (const token of parsedQuery) {
        if (
          token instanceof Condition &&
          conditionsWithAccNames.includes(token.condition)
        ) {
          accountNames.push(...token.args);
        }
      }

      accountNames = uniq(accountNames);

      let meUser = null;

      if (accountNames.includes('me')) {
        if (!viewerId) {
          throw new Error(`Please sign in to use 'me' as username`);
        }

        meUser = await this.getFeedOwnerById(viewerId);
      }

      const accountObjects = await this.getFeedOwnersByUsernames(accountNames);

      for (const ao of accountObjects) {
        accounts[ao.username] = ao;
      }

      for (const name of accountNames) {
        if (name === 'me') {
          accounts[name] = meUser;
        } else if (!accounts[name]) {
          accounts[name] = null;
        }
      }

      return accounts;
    }

    /**
     * Post can belongs to many feeds, so this function returns an array of Lists of feed intId's.
     *
     * @param {Array} tokens
     * @param {Object} accountsMap
     * @returns {Promise<Array>}
     */
    async _getFeedIdsLists(tokens, accountsMap) {
      const condToFeedNames = {
        in:             'Posts',
        'commented-by': 'Comments',
        'liked-by':     'Likes'
      };
      const myFeedNames = ['saves', 'directs', 'discussions', 'friends'];

      return await Promise.all(
        tokens
          .filter(
            (t) =>
              t instanceof Condition &&
              (!!condToFeedNames[t.condition] || t.condition === 'in-my')
          )
          .map(async (t) => {
            // in:, commented-by:, liked-by:
            if (condToFeedNames[t.condition]) {
              const userIds = uniq(t.args)
                .map((n) => accountsMap[n] && accountsMap[n].id)
                .filter(Boolean);

              const feedIntIds = await this.getUsersNamedFeedsIntIds(userIds, [
                condToFeedNames[t.condition]
              ]);
              return new List(feedIntIds, !t.exclude);
            }

            // in-my:
            const currentUser = accountsMap['me'];
            const feedIntIds = await Promise.all(
              uniq(t.args)
                .map((n) => (/s$/i.test(n) ? n : `${n}s`))
                .filter((n) => myFeedNames.includes(n))
                .map(async (name) => {
                  switch (name) {
                    case 'saves': {
                      return [
                        await currentUser.getGenericTimelineIntId('Saves')
                      ];
                    }
                    case 'directs': {
                      return [
                        await currentUser.getGenericTimelineIntId('Directs')
                      ];
                    }
                    case 'discussions': {
                      return await Promise.all([
                        currentUser.getCommentsTimelineIntId(),
                        currentUser.getLikesTimelineIntId()
                      ]);
                    }
                    case 'friends': {
                      const homeFeed = await currentUser.getRiverOfNewsTimeline();
                      const { destinations } = await this.getSubscriprionsIntIds(homeFeed);
                      return destinations;
                    }
                  }

                  return [];
                })
            );
            return new List(feedIntIds, !t.exclude);
          })
      );
    }
  };

export default searchTrait;

function joinThem(array, joinBy, defaultValue, shortcutValue, skipValue) {
  if (array.some((x) => x === shortcutValue)) {
    return shortcutValue;
  }

  const parts = array.filter((x) => !!x && x !== skipValue);

  if (parts.length === 0) {
    return defaultValue;
  }

  if (parts.length === 1) {
    return parts[0];
  }

  return `(${parts.join(` ${joinBy} `)})`;
}

function andJoin(array, def = 'true') {
  return joinThem(array, 'and', def, 'false', 'true');
}

function orJoin(array, def = 'false') {
  return joinThem(array, 'or', def, 'true', 'false');
}

function walkWithScope(tokens, action) {
  let currentScope = IN_ALL;

  for (const token of tokens) {
    if (token instanceof ScopeStart) {
      currentScope = token.scope;
      continue;
    }

    action(token, currentScope);
  }
}

function getTSQuery(tokens, targetScope) {
  const result = [];

  walkWithScope(tokens, (token, currentScope) => {
    if (token instanceof AnyText && currentScope === targetScope) {
      result.push(token.toTSQuery());
    }

    if (token instanceof InScope && token.scope === targetScope) {
      result.push(...token.anyTexts.map((t) => t.toTSQuery()));
    }
  });

  return result.length > 1 ? `(${result.join(' && ')})` : result.join(' && ');
}

function getAuthorNames(tokens, targetScope) {
  let result = List.everything();

  walkWithScope(tokens, (token, currentScope) => {
    if (
      token instanceof Condition &&
      ((token.condition === 'from' && targetScope === IN_POSTS) ||
        (token.condition === 'author' && targetScope === currentScope))
    ) {
      result = List.intersection(
        result,
        token.exclude ? List.inverse(token.args) : token.args
      );
    }
  });

  return result;
}
<|endoftext|>


<commit_before>angular.module('MyApp').

controller('LoaderCtrl', function($scope, $timeout) {
	$scope.loader.show = false;
	$scope.loader.cnt = 0;

	$scope.loader.reset = function(){
		$scope.loader.show = false;
		$scope.loader.cnt = 0;
	};

	$scope.loader.showLoader = function(){
		$scope.loader.cnt+=1;
		$scope.loader.show = true;

	};

	$scope.loader.hideLoader = function(forceHide){
		$scope.loader.cnt-=1;

		if($scope.loader.cnt < 1 || forceHide){
			$scope.loader.show = false;
			$scope.loader.cnt = 0;
		}

	};
}).

controller('AppCtrl', function($scope, $resource, $location, $util) {
	window.scope = {
		main: $scope
	};

	$scope.navigation = {
		paths: [],
		args: [],
		plainPage: false,
		stackUrl: ''
	};

	//loader
	$scope.loader = {
		show: false
	};

	//login data
	$scope.user = {
		username: '',
		role: '',
		roleId: '',
		login: false,
	};

	$scope.accessList = [];

	//misc
	$scope.actives = [{name:'active', value:true}, {name:'inactive', value:false}];

	//menu
	$scope.needToggleMenu = false;
	$scope.toggleMenu = function() {
		$scope.needToggleMenu = !$scope.needToggleMenu;
	};

	$scope.checkUser = function() {
		var x = $resource('api/access-right-list').get(
			function() {
				$scope.accessList = x;
				//x console.log($scope.accessList.ApproveLeave);
			},
			function(response) {
				$util.handleErrorMsg(response);
			}
		);

		$resource('api/current-user').get(
			function(response) {
				var isLogin = response.login;
				if(!isLogin) {

					//temporary keep previous requested URL path
					if($location.url() != '/login') {
						$scope.navigation.stackUrl = $location.url();
					}

					$location.path('/login');
				} else {
					$scope.user = response;
				}
			},
			function(response) {
				$util.handleErrorMsg(response);
			}
		);
	};

	$scope.logout = function() {
		$resource('api/logout').get(
			function() {
				$location.path('/login');
			},
			function() {
				$util.setMsg('logout fail', 'error');
			}
		);
	}
	/*
	$scope.$on('$locationChangeStart', function(event){
		$scope.loader.showLoader();
	});
	*/
	$scope.$on('$locationChangeSuccess', function(event){
		$scope.navigation.plainPage = false;
		$scope.navigation.paths = $location.path().split('/');
		$scope.navigation.args = $location.search();
		$scope.navigation.url = $location.path();

		$scope.checkUser();

		//unregister onbeforeunload event
		window.onbeforeunload = null;

		//unregister $locationChangeStart event
		$scope.$on('$locationChangeStart', function(event) {
		    return;
		});
	});
});<commit_msg>fix adm app controller bugs<commit_after>angular.module('MyApp').

controller('LoaderCtrl', function($scope, $timeout) {
	$scope.loader.show = false;
	$scope.loader.cnt = 0;

	$scope.loader.reset = function(){
		$scope.loader.show = false;
		$scope.loader.cnt = 0;
	};

	$scope.loader.showLoader = function(){
		$scope.loader.cnt+=1;
		$scope.loader.show = true;

	};

	$scope.loader.hideLoader = function(forceHide){
		$scope.loader.cnt-=1;

		if($scope.loader.cnt < 1 || forceHide){
			$scope.loader.show = false;
			$scope.loader.cnt = 0;
		}

	};
}).

controller('AppCtrl', function($scope, $resource, $location, $util) {
	window.scope = {
		main: $scope
	};

	$scope.navigation = {
		paths: [],
		args: [],
		plainPage: false,
		stackUrl: ''
	};

	//loader
	$scope.loader = {
		show: false
	};

	//login data
	$scope.user = {
		username: '',
		role: '',
		roleId: '',
		login: false,
	};

	$scope.accessList = [];

	//misc
	$scope.actives = [{name:'active', value:true}, {name:'inactive', value:false}];

	//menu
	$scope.needToggleMenu = false;
	$scope.toggleMenu = function() {
		$scope.needToggleMenu = !$scope.needToggleMenu;
	};

	$scope.checkUser = function() {
		/*
		var x = $resource('api/access-right-list').get(
			function() {
				$scope.accessList = x;
				//x console.log($scope.accessList.ApproveLeave);
			},
			function(response) {
				$util.handleErrorMsg(response);
			}
		);
		*/
		$resource('api/current-user').get(
			function(response) {
				var isLogin = response.login;
				if(!isLogin) {

					//temporary keep previous requested URL path
					if($location.url() != '/login') {
						$scope.navigation.stackUrl = $location.url();
					}

					$location.path('/login');
				} else {
					$scope.user = response;
				}
			},
			function(response) {
				$util.handleErrorMsg(response);
			}
		);
	};

	$scope.logout = function() {
		$resource('api/logout').get(
			function() {
				$location.path('/login');
			},
			function() {
				$util.setMsg('logout fail', 'error');
			}
		);
	}
	/*
	$scope.$on('$locationChangeStart', function(event){
		$scope.loader.showLoader();
	});
	*/
	$scope.$on('$locationChangeSuccess', function(event){
		$scope.navigation.plainPage = false;
		$scope.navigation.paths = $location.path().split('/');
		$scope.navigation.args = $location.search();
		$scope.navigation.url = $location.path();

		$scope.checkUser();

		//unregister onbeforeunload event
		window.onbeforeunload = null;

		//unregister $locationChangeStart event
		$scope.$on('$locationChangeStart', function(event) {
		    return;
		});
	});
});<|endoftext|>


<commit_before><commit_msg>added printPage<commit_after><|endoftext|>


<commit_before>var UIconfig = require('../vue/public/UIconfig');

var config = {};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                          GENERAL SETTINGS
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

config.silent = false;
config.debug = true;

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                       CONFIGURING TRADING ADVICE
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

config.tradingAdvisor = {
}

config.candleWriter = {
  enabled: false
}

config.backtestResultExporter = {
  enabled: false,
  writeToDisk: false,
  data: {
    stratUpdates: false,
    roundtrips: true,
    stratCandles: true,
    trades: true
  }
}

config.childToParent = {
  enabled: false,
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                       CONFIGURING ADAPTER
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// configurable in the UIconfig
config.adapter = UIconfig.adapter;

config.sqlite = {
  path: 'plugins/sqlite',
  version: 0.1,
  dataDirectory: 'history',
  journalMode: require('../isWindows.js') ? 'PERSIST' : 'WAL',
  dependencies: [{
    module: 'sqlite3',
    version: '3.1.4'
  }]
}

  // Postgres adapter example config (please note: requires postgres >= 9.5):
config.postgresql = {
  path: 'plugins/postgresql',
  version: 0.1,
  connectionString: 'postgres://user:pass@localhost:5432', // if default port
  database: null, // if set, we'll put all tables into a single database.
  schema: 'public',
  dependencies: [{
    module: 'pg',
    version: '7.4.3'
  }]
}

// Mongodb adapter, requires mongodb >= 3.3 (no version earlier tested)
config.mongodb = {
  path: 'plugins/mongodb',
  version: 0.1,
  connectionString: 'mongodb://mongodb/gekko', // connection to mongodb server
  dependencies: [{
    module: 'mongojs',
    version: '2.4.0'
  }]
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                       CONFIGURING BACKTESTING
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Note that these settings are only used in backtesting mode, see here:
// @link: https://github.com/askmike/gekko/blob/stable/docs/Backtesting.md

config.backtest = {
  daterange: 'scan',
  batchSize: 50
}

config.importer = {
  daterange: {
    // NOTE: these dates are in UTC
    from: "2016-06-01 12:00:00"
  }
}

module.exports = config;
<commit_msg>make sure the baseConfig uses the dist UIconfig<commit_after>var UIconfig = require('../vue/dist/UIconfig');

var config = {};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                          GENERAL SETTINGS
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

config.silent = false;
config.debug = true;

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                       CONFIGURING TRADING ADVICE
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

config.tradingAdvisor = {
}

config.candleWriter = {
  enabled: false
}

config.backtestResultExporter = {
  enabled: false,
  writeToDisk: false,
  data: {
    stratUpdates: false,
    roundtrips: true,
    stratCandles: true,
    trades: true
  }
}

config.childToParent = {
  enabled: false,
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                       CONFIGURING ADAPTER
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// configurable in the UIconfig
config.adapter = UIconfig.adapter;

config.sqlite = {
  path: 'plugins/sqlite',
  version: 0.1,
  dataDirectory: 'history',
  journalMode: require('../isWindows.js') ? 'PERSIST' : 'WAL',
  dependencies: [{
    module: 'sqlite3',
    version: '3.1.4'
  }]
}

  // Postgres adapter example config (please note: requires postgres >= 9.5):
config.postgresql = {
  path: 'plugins/postgresql',
  version: 0.1,
  connectionString: 'postgres://user:pass@localhost:5432', // if default port
  database: null, // if set, we'll put all tables into a single database.
  schema: 'public',
  dependencies: [{
    module: 'pg',
    version: '7.4.3'
  }]
}

// Mongodb adapter, requires mongodb >= 3.3 (no version earlier tested)
config.mongodb = {
  path: 'plugins/mongodb',
  version: 0.1,
  connectionString: 'mongodb://mongodb/gekko', // connection to mongodb server
  dependencies: [{
    module: 'mongojs',
    version: '2.4.0'
  }]
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                       CONFIGURING BACKTESTING
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Note that these settings are only used in backtesting mode, see here:
// @link: https://github.com/askmike/gekko/blob/stable/docs/Backtesting.md

config.backtest = {
  daterange: 'scan',
  batchSize: 50
}

config.importer = {
  daterange: {
    // NOTE: these dates are in UTC
    from: "2016-06-01 12:00:00"
  }
}

module.exports = config;
<|endoftext|>


<commit_before>import React, { Component } from 'react'

import './Header.scss'

class Header extends Component {
  render() {
    return (

            remindre

            sign in

          <input type="text" className="uk-input" placeholder="Add new reminder..." />

    )
  }
}

export default Header
<commit_msg>Header icon nav<commit_after>import React, { Component } from 'react'

import './Header.scss'

class Header extends Component {
  render() {
    return (

            remindre

grid

list

sign out

          <input type="text" className="uk-input" placeholder="Add new reminder..." />

    )
  }
}

export default Header
<|endoftext|>


<commit_before>'use strict';

// Declare app level module which depends on filters, and services

var app = angular.module( 'myApp', [
    'ngRoute',

    'myApp.controllers',
    'myApp.filters',
    'myApp.services',
    'myApp.directives',

    // 3rd party dependencies
    'btford.socket-io'
] );

app.config( function ( $routeProvider, $locationProvider ) {
    $routeProvider.
    when( '/home', {
        templateUrl: 'partials/home',
        controller: 'HomeCtrl'
    } ).
    when( '/artist/:artistURI', {
        templateUrl: 'partials/artist',
        controller: 'ArtistCtrl'
    } ).
    when( '/album/:albumURI', {
        templateUrl: 'partials/album',
        controller: 'AlbumCtrl'
    } ).
    otherwise( {
        redirectTo: '/home'
    } );

    $locationProvider.html5Mode( true );
});
<commit_msg>Install this the right way<commit_after>'use strict';

// Declare app level module which depends on filters, and services

var app = angular.module( 'myApp', [
    'ngRoute',

    'myApp.controllers',
    'myApp.filters',
    'myApp.services',
    'myApp.directives',

    // 3rd party dependencies
    'btford.socket-io',
    'fully-loaded'
] );

app.config( function ( $routeProvider, $locationProvider ) {
    $routeProvider.
    when( '/home', {
        templateUrl: 'partials/home',
        controller: 'HomeCtrl'
    } ).
    when( '/artist/:artistURI', {
        templateUrl: 'partials/artist',
        controller: 'ArtistCtrl'
    } ).
    when( '/album/:albumURI', {
        templateUrl: 'partials/album',
        controller: 'AlbumCtrl'
    } ).
    otherwise( {
        redirectTo: '/home'
    } );

    $locationProvider.html5Mode( true );
});
<|endoftext|>