<commit_before>const chapterRepository = require('../domain/repositories/chapter-repository');
const articleRepository = require('../domain/repositories/article-repository');
const photoRepository = require('../domain/repositories/photo-repository');
const { isEmpty, flatten } = require('lodash');
const DropboxClient = require('../infrastructure/external_services/dropbox-client');
const FileReader = require('../infrastructure/external_services/file-reader');

async function sync(dropboxId) {
  function _createPhotosOfArticlesInDatabase(dropboxFilesPath) {
    return getPhotosOfArticle(dropboxFilesPath)
      .then(photos => flatten(photos))
      .then(photos => photoRepository.createPhotos(photos));
  }

  function getPhotosOfArticle(dropboxFilesPath) {
    const paths = filterOnlyGalleryPhotos(dropboxFilesPath);
    return createPhotoOfArticle(paths, dropboxId);
  }

  function filterOnlyGalleryPhotos(paths) {
    const photosPaths = paths.filter((path) => {
      const extension = path.split('.').pop();
      return extension === 'jpg' || extension === 'jpeg' || extension === 'png';
    });
    return photosPaths.filter((path) => {
      const shortName = path.split('/').pop().substring(0, 3);
      return !shortName.match('[iI]mg');
    });
  }

  function createPhotoOfArticle(paths) {
    const allImgLinks = paths.reduce((promises, path) => {
      const imgLink = serializePhoto(path);
      promises.push(imgLink);
      return promises;
    }, []);
    return Promise.all(allImgLinks);
  }

  function serializePhoto(path) {
    return DropboxClient.createSharedLink(path)
      .then(response => ({
        imgLink: _transformToImgLink(response),
        dropboxId,
      }));
  }

  function _createArticlesInDatabase({ addedArticles }) {
    return _shareImagesZeros(addedArticles)
      .then(articles => articleRepository.create(articles));
  }

  function _shareImagesZeros(articles) {
    const articlesWithAll = articles.reduce((promises, article) => {
      const articleWithAll = _shareImageZero(article);
      promises.push(articleWithAll);
      return promises;
    }, []);
    return Promise.all(articlesWithAll);
  }

  function _shareImageZero(article) {
    return Promise.all([
      DropboxClient.createSharedLink(article.imgPath),
      DropboxClient.createSharedLink(article.galleryPath),
    ])
      .then(responses => ({
        dropboxId: article.dropboxId,
        imgLink: _transformToImgLink(responses[0]),
        galleryLink: _getGalleryUrl(responses[1]),
      }));
  }

  function _insertArticlesContentsInDatabase(dropboxFilesPath) {
    return _updateTitleAndExtractChaptersFromArticleContent(dropboxFilesPath)
      .then(allChapters => flatten(allChapters))
      .then(chapters => chapterRepository.createArticleChapters(chapters));
  }

  function _updateTitleAndExtractChaptersFromArticleContent(dropboxFilesPath) {
    return Promise.all([
      DropboxClient.getFrTextFileStream(dropboxId),
      DropboxClient.getEnTextFileStream(dropboxId),
    ])
      .then(files => Promise.all([
        FileReader.read(files[0]),
        FileReader.read(files[1]),
      ]))
      .then(articleContents => _serializeArticleContents(articleContents, dropboxFilesPath))
      .then(articleInfos => _updateArticleTitles(articleInfos))
      .then(articleInfos => _shareChapterImages(articleInfos))
      .then(({ chapters }) => chapters);
  }

  function _updateArticleTitles(articleInfos) {
    const { frTitle, enTitle } = articleInfos;
    articleRepository.update({ frTitle, enTitle }, dropboxId);
    return articleInfos;
  }

  function _serializeArticleContents(rawArticles, dropboxFilesPath) {
    const cuttedArticles = rawArticles.map(rawArticle => rawArticle
      .split('*')
      .map(row => row.trim()));

    const chapters = _generateChapters(cuttedArticles, dropboxFilesPath);

    return {
      frTitle: cuttedArticles[0][0],
      enTitle: cuttedArticles[1][0],
      chapters,
    };
  }

  function _generateChapters(cuttedArticles, dropboxFilesPath) {
    const frenchArticle = cuttedArticles[0];
    const englishArticle = cuttedArticles[1];
    const chapters = [];
    for (let i = 1; i < frenchArticle.length / 3; i += 1) {
      const imgLink = dropboxFilesPath.filter(imgPath => imgPath.match(`[iI]mg-?${i}.jpg$`))[0];

      const frenchTitle = frenchArticle[(3 * i) - 2];
      const frenchSubtitle = frenchArticle[(3 * i) - 1];
      const englishTitle = englishArticle[(3 * i) - 2];
      const englishSubtitle = englishArticle[(3 * i) - 1];
      chapters[i - 1] = {
        dropboxId,
        frTitle: [frenchTitle, frenchSubtitle].join(' ').trim(),
        enTitle: [englishTitle, englishSubtitle].join(' ').trim(),
        imgLink,
        frText: frenchArticle[3 * i],
        enText: englishArticle[3 * i],
      };
    }
    return chapters;
  }

  function _shareChapterImages(articleInfos) {
    const chaptersWithSharableLink = articleInfos.chapters.reduce((promises, chapter) => {
      const promise = _shareChapterImage(chapter.imgLink);
      promises.push(promise);
      return promises;
    }, []);
    return Promise.all(chaptersWithSharableLink)
      .then((imgLinks) => {
        const newArticleInfos = articleInfos;
        for (let i = 0; i < imgLinks.length; i += 1) {
          newArticleInfos.chapters[i].imgLink = imgLinks[i];
        }
        return newArticleInfos;
      });
  }

  function _shareChapterImage(imgLink) {
    return DropboxClient.createSharedLink(imgLink)
      .then(_transformToImgLink);
  }

  function _transformToImgLink(response) {
    return isEmpty(response) ? '' : response.url.replace(/.$/, '1');
  }

  function _getGalleryUrl(response) {
    return isEmpty(response) ? '' : response.url;
  }

  await Promise.all([
    articleRepository.deleteArticle(dropboxId),
    chapterRepository.deleteChaptersOfArticle(dropboxId),
    photoRepository.deletePhotosOfArticle(dropboxId),
  ]);
  const dropboxFilesPath = await DropboxClient.getFilesFolderPaths(dropboxId);
  const imageZero = dropboxFilesPath
    .filter(path => path.match('0.jpg$'))[0];
  const report = {
    addedArticles: [
      {
        dropboxId,
        imgPath: imageZero,
        galleryPath: `/${dropboxId}`,
      },
    ],
  };
  await _createArticlesInDatabase(report);
  await _insertArticlesContentsInDatabase(dropboxFilesPath);
  await _createPhotosOfArticlesInDatabase(dropboxFilesPath);
  return report;
}

module.exports = {
  sync,
};
<commit_msg>feature(REC-78) : report missing pictures instead of crashing<commit_after>const chapterRepository = require('../domain/repositories/chapter-repository');
const articleRepository = require('../domain/repositories/article-repository');
const photoRepository = require('../domain/repositories/photo-repository');
const { isEmpty, flatten } = require('lodash');
const DropboxClient = require('../infrastructure/external_services/dropbox-client');
const FileReader = require('../infrastructure/external_services/file-reader');

async function sync(dropboxId) {
  function _createPhotosOfArticlesInDatabase(dropboxFilesPath) {
    return getPhotosOfArticle(dropboxFilesPath)
      .then(photos => flatten(photos))
      .then(photos => photoRepository.createPhotos(photos));
  }

  function getPhotosOfArticle(dropboxFilesPath) {
    const paths = filterOnlyGalleryPhotos(dropboxFilesPath);
    return createPhotoOfArticle(paths, dropboxId);
  }

  function filterOnlyGalleryPhotos(paths) {
    const photosPaths = paths.filter((path) => {
      const extension = path.split('.').pop();
      return extension === 'jpg' || extension === 'jpeg' || extension === 'png';
    });
    return photosPaths.filter((path) => {
      const shortName = path.split('/').pop().substring(0, 3);
      return !shortName.match('[iI]mg');
    });
  }

  function createPhotoOfArticle(paths) {
    const allImgLinks = paths.reduce((promises, path) => {
      const imgLink = serializePhoto(path);
      promises.push(imgLink);
      return promises;
    }, []);
    return Promise.all(allImgLinks);
  }

  function serializePhoto(path) {
    return DropboxClient.createSharedLink(path)
      .then(response => ({
        imgLink: _transformToImgLink(response),
        dropboxId,
      }));
  }

  function _createArticlesInDatabase({ addedArticles }) {
    return _shareImagesZeros(addedArticles)
      .then(articles => articleRepository.create(articles));
  }

  function _shareImagesZeros(articles) {
    const articlesWithAll = articles.reduce((promises, article) => {
      const articleWithAll = _shareImageZero(article);
      promises.push(articleWithAll);
      return promises;
    }, []);
    return Promise.all(articlesWithAll);
  }

  function _shareImageZero(article) {
    return Promise.all([
      DropboxClient.createSharedLink(article.imgPath),
      DropboxClient.createSharedLink(article.galleryPath),
    ])
      .then(responses => ({
        dropboxId: article.dropboxId,
        imgLink: _transformToImgLink(responses[0]),
        galleryLink: _getGalleryUrl(responses[1]),
      }));
  }

  function _insertArticlesContentsInDatabase(dropboxFilesPath) {
    return _updateTitleAndExtractChaptersFromArticleContent(dropboxFilesPath)
      .then(allChapters => flatten(allChapters))
      .then(chapters => chapterRepository.createArticleChapters(chapters));
  }

  function _updateTitleAndExtractChaptersFromArticleContent(dropboxFilesPath) {
    return Promise.all([
      DropboxClient.getFrTextFileStream(dropboxId),
      DropboxClient.getEnTextFileStream(dropboxId),
    ])
      .then(files => Promise.all([
        FileReader.read(files[0]),
        FileReader.read(files[1]),
      ]))
      .then(articleContents => _serializeArticleContents(articleContents, dropboxFilesPath))
      .then(articleInfos => _updateArticleTitles(articleInfos))
      .then(articleInfos => _shareChapterImages(articleInfos))
      .then(({ chapters }) => chapters);
  }

  function _updateArticleTitles(articleInfos) {
    const { frTitle, enTitle } = articleInfos;
    articleRepository.update({ frTitle, enTitle }, dropboxId);
    return articleInfos;
  }

  function _serializeArticleContents(rawArticles, dropboxFilesPath) {
    const cuttedArticles = rawArticles.map(rawArticle => rawArticle
      .split('*')
      .map(row => row.trim()));

    const chapters = _generateChapters(cuttedArticles, dropboxFilesPath);

    return {
      frTitle: cuttedArticles[0][0],
      enTitle: cuttedArticles[1][0],
      chapters,
    };
  }

  function _generateChapters(cuttedArticles, dropboxFilesPath) {
    const frenchArticle = cuttedArticles[0];
    const englishArticle = cuttedArticles[1];
    const chapters = [];
    for (let i = 1; i < frenchArticle.length / 3; i += 1) {
      const imgLink = dropboxFilesPath.filter(imgPath => imgPath.match(`[iI]mg-?${i}.jpg$`))[0];

      const frenchTitle = frenchArticle[(3 * i) - 2];
      const frenchSubtitle = frenchArticle[(3 * i) - 1];
      const englishTitle = englishArticle[(3 * i) - 2];
      const englishSubtitle = englishArticle[(3 * i) - 1];
      chapters[i - 1] = {
        dropboxId,
        frTitle: [frenchTitle, frenchSubtitle].join(' ').trim(),
        enTitle: [englishTitle, englishSubtitle].join(' ').trim(),
        imgLink,
        frText: frenchArticle[3 * i],
        enText: englishArticle[3 * i],
      };
    }
    return chapters;
  }

  function _shareChapterImages(articleInfos) {
    const chaptersWithSharableLink = articleInfos.chapters.reduce((promises, chapter) => {
      if (isEmpty(chapter.imgLink)) {
        console.error('Problème avec les données fournies dans cet article : ');
        console.error(chapter);
        return promises;
      }
      const promise = _shareChapterImage(chapter.imgLink);
      promises.push(promise);
      return promises;
    }, []);
    return Promise.all(chaptersWithSharableLink)
      .then((imgLinks) => {
        const newArticleInfos = articleInfos;
        for (let i = 0; i < imgLinks.length; i += 1) {
          newArticleInfos.chapters[i].imgLink = imgLinks[i];
        }
        return newArticleInfos;
      });
  }

  function _shareChapterImage(imgLink) {
    return DropboxClient.createSharedLink(imgLink)
      .then(_transformToImgLink);
  }

  function _transformToImgLink(response) {
    return isEmpty(response) ? '' : response.url.replace(/.$/, '1');
  }

  function _getGalleryUrl(response) {
    return isEmpty(response) ? '' : response.url;
  }

  await Promise.all([
    articleRepository.deleteArticle(dropboxId),
    chapterRepository.deleteChaptersOfArticle(dropboxId),
    photoRepository.deletePhotosOfArticle(dropboxId),
  ]);
  const dropboxFilesPath = await DropboxClient.getFilesFolderPaths(dropboxId);
  const imageZero = dropboxFilesPath
    .filter(path => path.match('0.jpg$'))[0];
  const report = {
    addedArticles: [
      {
        dropboxId,
        imgPath: imageZero,
        galleryPath: `/${dropboxId}`,
      },
    ],
  };
  await _createArticlesInDatabase(report);
  await _insertArticlesContentsInDatabase(dropboxFilesPath);
  await _createPhotosOfArticlesInDatabase(dropboxFilesPath);
  return report;
}

module.exports = {
  sync,
};
<|endoftext|>


<commit_before>const helper = require('../util/helpers.js');
const Promise = require('bluebird');
const fetch = require('isomorphic-fetch');

const getRecommendation = (UPC, callback) => {
  const categoryFunctions = [];
  const categoryFunctionsIndex = [];
  const categoryDataFunctions = [];
  const categoryDataFunctionsIndex = [];
  const listFunctions = [];
  const listFunctionsIndex = [];
  const recommendation = {};

  console.log('recommendation worker working on: ', UPC);

  const url = `http://localhost:4570/db/product?upc=${UPC}`;
  fetch(url, {
    method: 'get',
  })
  .then((res) => res.json())
  .catch((err) => callback(err))
  .then((data) => {
    if (!data.product) {
      callback('No data for the requested product');
    }
    const productInfo = data.product;
    const image = data.media[0].url;
    Object.keys(productInfo).forEach((key) => {
      if (key === 'Product_created_at' || productInfo[key] === null) {
        delete productInfo[key];
      }
    });
    recommendation.basicInfo = productInfo;
    recommendation.basicInfo.image = image;

    // loop through each category of UPC
    const categories = data.categories;
    categories.forEach((category) => {
      recommendation[`${category}`] = {};
      // get average nutrient level for each category
      categoryFunctions.push(helper.getAllCategoryNutrientLevel(category));
      categoryFunctionsIndex.push(category);
    });

    Promise.all(categoryFunctions)
      .then((categoryDataArray) => {
        categoryDataArray.forEach((categoryData, categoryDataIndex) => {
          const category = categoryFunctionsIndex[categoryDataIndex];
          // compare nutrient level if it exists in both category and product
          Object.keys(categoryData).forEach((categoryField) => {
            if (productInfo[categoryField]) {
              const DV = helper.checkDV(categoryField);
              // check if nutrient is good or bad nutrient
              const nutrientQuality = helper.checkIfBadOrGoodNutrient(categoryField);
              if (nutrientQuality) {
                // store the comparison anaylsis in recommendation object
                recommendation[`${category}`][`${nutrientQuality}Nutrients`] =
                recommendation[`${category}`][`${nutrientQuality}Nutrients`] || {};
                recommendation[`${category}`][`${nutrientQuality}Nutrients`][categoryField] = {
                  DV,
                  ratio: productInfo[categoryField] / Number(categoryData[categoryField]),
                  product: productInfo[categoryField],
                  category: Number(categoryData[categoryField]),
                };
                const checkIfWorseThanAverage = (quality) => {
                  let result;
                  if (quality === 'Good') {
                    if ((productInfo[categoryField] / Number(categoryData[categoryField])) < 1) {
                      result = true;
                    }
                  } else {
                    if ((productInfo[categoryField] / Number(categoryData[categoryField])) > 1) {
                      result = true;
                    }
                  }
                  return result;
                };
                const worseThanAverage = checkIfWorseThanAverage(nutrientQuality);
                if (worseThanAverage) {
                  // get products with better nutrients in the same category from the rank table
                  const nutrientLevel = helper.adjustNumber(categoryData[categoryField]);
                  categoryDataFunctions.push(
                    helper.getProductWithBetterNutrients(
                      nutrientQuality, categoryField, category, nutrientLevel));
                  categoryDataFunctionsIndex.push([nutrientQuality, categoryField, category]);
                }
              } else {
                recommendation[`${category}`].nutrientsWithoutRecommendation =
                recommendation[`${category}`].nutrientsWithoutRecommendation || {};
                recommendation[`${category}`].nutrientsWithoutRecommendation[categoryField] = {
                  DV,
                  ratio: productInfo[categoryField] / Number(categoryData[categoryField]),
                  product: productInfo[categoryField],
                  category: Number(categoryData[categoryField]),
                };
              }
            }
          });
        });

        Promise.all(categoryDataFunctions)
          .then((listArray) => {
            listArray.forEach((list, listIndex) => {
              const nutrientQuality = categoryDataFunctionsIndex[listIndex][0];
              const categoryField = categoryDataFunctionsIndex[listIndex][1];
              const category = categoryDataFunctionsIndex[listIndex][2];
              list.forEach((product) => {
                const recommendedUPC = product.split(':')[2];
                // request product info for each recommended product from Master DB
                const url2 = `http://localhost:4570/db/product?upc=${recommendedUPC}`;
                listFunctions.push(fetch(url2, { method: 'get' }).then((res) => res.json()));
                listFunctionsIndex.push([nutrientQuality, categoryField, category]);
              });
            });

            Promise.all(listFunctions)
              .then((recProductArray) => {
                recProductArray.forEach((recProduct, recProductIndex) => {
                  const nutrientQuality = listFunctionsIndex[recProductIndex][0];
                  const categoryField = listFunctionsIndex[recProductIndex][1];
                  const category = listFunctionsIndex[recProductIndex][2];
                  const recommendedProductInfo = recProduct.product;
                  recommendedProductInfo.image = recProduct.media[0].url;
                  Object.keys(recommendedProductInfo).forEach((key) => {
                    if (key === 'Product_created_at' || productInfo[key] === null) {
                      delete recommendedProductInfo[key];
                    }
                  });
                  // store the recommended products info in recommendation object
                  recommendation[`${category}`][`${nutrientQuality}Nutrients`]
                  [categoryField].recommendedProducts =
                  recommendation[`${category}`][`${nutrientQuality}Nutrients`]
                  [categoryField].recommendedProducts || [];
                  recommendation[`${category}`][`${nutrientQuality}Nutrients`]
                  [categoryField].recommendedProducts.push(recommendedProductInfo);
                });

                // store and send the recommendation object
                helper.storeRecommendation(UPC, JSON.stringify(recommendation));
                callback(null, recommendation);
              });
          });
      });
  });
};

module.exports = {
  getRecommendation,
};
<commit_msg>handle error<commit_after>const helper = require('../util/helpers.js');
const Promise = require('bluebird');
const fetch = require('isomorphic-fetch');

const getRecommendation = (UPC, callback) => {
  const categoryFunctions = [];
  const categoryFunctionsIndex = [];
  const categoryDataFunctions = [];
  const categoryDataFunctionsIndex = [];
  const listFunctions = [];
  const listFunctionsIndex = [];
  const recommendation = {};

  console.log('recommendation worker working on: ', UPC);

  const url = `http://localhost:4570/db/product?upc=${UPC}`;
  fetch(url, {
    method: 'get',
  })
  .then((res) => res.json())
  .catch(() => callback('No data for the requested product'))
  .then((data) => {
    if (!data.product) {
      callback('No data for the requested product');
    }
    const productInfo = data.product;
    const image = data.media[0].url;
    Object.keys(productInfo).forEach((key) => {
      if (key === 'Product_created_at' || productInfo[key] === null) {
        delete productInfo[key];
      }
    });
    recommendation.basicInfo = productInfo;
    recommendation.basicInfo.image = image;

    // loop through each category of UPC
    const categories = data.categories;
    categories.forEach((category) => {
      recommendation[`${category}`] = {};
      // get average nutrient level for each category
      categoryFunctions.push(helper.getAllCategoryNutrientLevel(category));
      categoryFunctionsIndex.push(category);
    });

    Promise.all(categoryFunctions)
      .then((categoryDataArray) => {
        categoryDataArray.forEach((categoryData, categoryDataIndex) => {
          const category = categoryFunctionsIndex[categoryDataIndex];
          // compare nutrient level if it exists in both category and product
          Object.keys(categoryData).forEach((categoryField) => {
            if (productInfo[categoryField]) {
              const DV = helper.checkDV(categoryField);
              // check if nutrient is good or bad nutrient
              const nutrientQuality = helper.checkIfBadOrGoodNutrient(categoryField);
              if (nutrientQuality) {
                // store the comparison anaylsis in recommendation object
                recommendation[`${category}`][`${nutrientQuality}Nutrients`] =
                recommendation[`${category}`][`${nutrientQuality}Nutrients`] || {};
                recommendation[`${category}`][`${nutrientQuality}Nutrients`][categoryField] = {
                  DV,
                  ratio: productInfo[categoryField] / Number(categoryData[categoryField]),
                  product: productInfo[categoryField],
                  category: Number(categoryData[categoryField]),
                };
                const checkIfWorseThanAverage = (quality) => {
                  let result;
                  if (quality === 'Good') {
                    if ((productInfo[categoryField] / Number(categoryData[categoryField])) < 1) {
                      result = true;
                    }
                  } else {
                    if ((productInfo[categoryField] / Number(categoryData[categoryField])) > 1) {
                      result = true;
                    }
                  }
                  return result;
                };
                const worseThanAverage = checkIfWorseThanAverage(nutrientQuality);
                if (worseThanAverage) {
                  // get products with better nutrients in the same category from the rank table
                  const nutrientLevel = helper.adjustNumber(categoryData[categoryField]);
                  categoryDataFunctions.push(
                    helper.getProductWithBetterNutrients(
                      nutrientQuality, categoryField, category, nutrientLevel));
                  categoryDataFunctionsIndex.push([nutrientQuality, categoryField, category]);
                }
              } else {
                recommendation[`${category}`].nutrientsWithoutRecommendation =
                recommendation[`${category}`].nutrientsWithoutRecommendation || {};
                recommendation[`${category}`].nutrientsWithoutRecommendation[categoryField] = {
                  DV,
                  ratio: productInfo[categoryField] / Number(categoryData[categoryField]),
                  product: productInfo[categoryField],
                  category: Number(categoryData[categoryField]),
                };
              }
            }
          });
        });

        Promise.all(categoryDataFunctions)
          .then((listArray) => {
            listArray.forEach((list, listIndex) => {
              const nutrientQuality = categoryDataFunctionsIndex[listIndex][0];
              const categoryField = categoryDataFunctionsIndex[listIndex][1];
              const category = categoryDataFunctionsIndex[listIndex][2];
              list.forEach((product) => {
                const recommendedUPC = product.split(':')[2];
                // request product info for each recommended product from Master DB
                const url2 = `http://localhost:4570/db/product?upc=${recommendedUPC}`;
                listFunctions.push(fetch(url2, { method: 'get' }).then((res) => res.json()));
                listFunctionsIndex.push([nutrientQuality, categoryField, category]);
              });
            });

            Promise.all(listFunctions)
              .then((recProductArray) => {
                recProductArray.forEach((recProduct, recProductIndex) => {
                  const nutrientQuality = listFunctionsIndex[recProductIndex][0];
                  const categoryField = listFunctionsIndex[recProductIndex][1];
                  const category = listFunctionsIndex[recProductIndex][2];
                  const recommendedProductInfo = recProduct.product;
                  recommendedProductInfo.image = recProduct.media[0].url;
                  Object.keys(recommendedProductInfo).forEach((key) => {
                    if (key === 'Product_created_at' || productInfo[key] === null) {
                      delete recommendedProductInfo[key];
                    }
                  });
                  // store the recommended products info in recommendation object
                  recommendation[`${category}`][`${nutrientQuality}Nutrients`]
                  [categoryField].recommendedProducts =
                  recommendation[`${category}`][`${nutrientQuality}Nutrients`]
                  [categoryField].recommendedProducts || [];
                  recommendation[`${category}`][`${nutrientQuality}Nutrients`]
                  [categoryField].recommendedProducts.push(recommendedProductInfo);
                });

                // store and send the recommendation object
                helper.storeRecommendation(UPC, JSON.stringify(recommendation));
                callback(null, recommendation);
              });
          });
      });
  });
};

module.exports = {
  getRecommendation,
};
<|endoftext|>


<commit_before>var express = require('express')

var WebUi = module.exports = function(db_albums, db_tracks, port, source) {
  var app = express()

  app.use(express.bodyParser());
  app.use(express.cookieParser());
  app.use(express.session({secret: 'mp3-wrangler-1234'}));
  app.use(app.router);
  app.use('/', express.static(__dirname + '/public'));

  app.configure(function() {
  })

  app.get('/albums', function(req, res) {
    db_albums.find({}, function(err, docs) {
      if (err) {
        res.send(err);
        return console.log('/albums error: ' + err);
      }
      res.send(docs);
    });
  })

  app.get(/file\/(.+)/, function(req, res) {
    var key = req.params[0];
    var r;
    if (r = /.*\/(.*\.mp3)$/.exec(key)) {
      var file_name = r[1];
      res.setHeader('Content-type', 'audio/mpeg3');
      var stream = source.createReadFileStream(key);
      stream.on('end', function() {
        console.log('stream end: ' + key);
      });
      stream.pipe(res);
    } else {
      res.send('key error')
    }
  })

  app.get(/id3\/(.+)/, function(req, res) {
   var key = req.params[0];
   db_tracks.findOne({key:key}, function(err, doc) {
     res.send(doc.id3);
   });
  })

  app.post('/download-list', function(req, res) {
    req.session.albums = req.body.albums;
    res.send('ok');
  })

  app.get('/download-list', function(req, res) {
    if (!req.session.albums) {
      req.session.albums = []
    }
    res.send(req.session.albums);
  })

  app.get('/download', function(req, res) {
    if (!req.session.albums) {
      return res.send('no albums');
    }

    var download_stream = source.createDownloadStream(req.session.albums);

    res.setHeader('Content-disposition', 'attachment; filename=albums.zip');
    res.setHeader('Content-type', 'application/zip');
    download_stream.pipe(res);
  })

  app.listen(port)
  console.log('started on port ' + port)
}
<commit_msg>add error handling for streams (not complete)<commit_after>var express = require('express')

var WebUi = module.exports = function(db_albums, db_tracks, port, source) {
  var app = express()

  app.use(express.bodyParser());
  app.use(express.cookieParser());
  app.use(express.session({secret: 'mp3-wrangler-1234'}));
  app.use(app.router);
  app.use('/', express.static(__dirname + '/public'));

  app.configure(function() {
  })

  app.get('/albums', function(req, res) {
    db_albums.find({}, function(err, docs) {
      if (err) {
        res.send(err);
        return console.log('/albums error: ' + err);
      }
      res.send(docs);
    });
  })

  app.get(/file\/(.+)/, function(req, res) {
    var key = req.params[0];
    var r;
    if (r = /.*\/(.*\.mp3)$/.exec(key)) {
      var file_name = r[1];
      res.setHeader('Content-type', 'audio/mpeg3');
      var stream = source.createReadFileStream(key);
      stream.on('end', function() {
        console.log('stream end: ' + key);
      });
      stream.on('error', function() {
        console.log('Error streaming ' + key);
        res.send('stream error');
      });
      stream.pipe(res);
    } else {
      res.send('key error')
    }
  })

  app.get(/id3\/(.+)/, function(req, res) {
   var key = req.params[0];
   db_tracks.findOne({key:key}, function(err, doc) {
     res.send(doc.id3);
   });
  })

  app.post('/download-list', function(req, res) {
    req.session.albums = req.body.albums;
    res.send('ok');
  })

  app.get('/download-list', function(req, res) {
    if (!req.session.albums) {
      req.session.albums = []
    }
    res.send(req.session.albums);
  })

  app.get('/download', function(req, res) {
    if (!req.session.albums) {
      return res.send('no albums');
    }

    var download_stream = source.createDownloadStream(req.session.albums);

    res.setHeader('Content-disposition', 'attachment; filename=albums.zip');
    res.setHeader('Content-type', 'application/zip');
    download_stream.pipe(res);
  })

  app.listen(port)
  console.log('started on port ' + port)
}
<|endoftext|>


<commit_before>/* global console, Backbone, _, $ */
angular
	.module('indx')
	.factory('collection', function () {
		'use strict';

		var Model = function () {};
		Model.extend = function (obj) {
			/// @ignore -- TODO
			var _F = function () {};
			_F.prototype = _.extend({}, Model.prototype, obj);
			return _F;
		};
		_.extend(Model.prototype, {
			is_new: false,
			is_editing: false,
			is_selected: false,
			initialize: function () {
				var that = this;
				this._update_new_attributes();
				this.on('change', function () {
					that._update_new_attributes();
				})
			},
			_update_new_attributes: function () {
				if (!this.is_editing) {
					this.newAttributes = _.clone(this.attributes);
				}
			},
			create: function () {
				var that = this;
				console.log('creating item');
				this.box.get_obj(this.id).then(function (new_obj) {
					console.log('new item');
					new_obj.save(that.newAttributes).then(function () {
						console.log('saved');
						that.trigger('created', new_obj);
					});
				});
				return this;
			},
			remove: function () {
				var that = this;
				console.log('remove item', this);
				this.destroy().then(function () {
					console.log('destroyed item');
					that.trigger('restore', this);
				});
			},
			edit: function (is_new) {
				if (this.is_editing) { return; }
				console.log('edit item', this);
				this._update_new_attributes();
				this.is_new = is_new;
				this.is_editing = true;
				this.trigger('edit');
				return this;
			},
			stage_and_save: function () {
				var that = this;
				console.log('stage and save');
				if (this.is_new) {
					console.log('new, so create');
					return this.create();
				} else {
					this.save(this.newAttributes).then(function () {
						console.log('saved');
						that.restore();
						// u.safe_apply($scope); TODO
					});
				}
			},
			save: function () {
				if (this.is_new) {
					console.warn('supressing save');
				} else {
					return Backbone.Model.prototype.save.apply(this, arguments);
				}
			},
			restore: function () {
				this.is_editing = false;
				this.trigger('restore');
				return this;
			},
			select: function (selected, options) {
				options = options || {};
				console.log('select', selected);
				selected = _.isBoolean(selected) ? selected : true;
				if (this.is_selected !== selected) {
					console.log('selecting', selected);
					this.is_selected = selected;
					this.trigger('select', selected, options);
				}
			}
		});
		var Collection = Backbone.Collection.extend({
			/// the collection.Model the cast each model to
			model: Model,
			selected: undefined,
			initialize: function (models, options) {
				var that = this;
				this.options = options || {};
				this.box = this.options.box;
				this.obj = this.options.obj;
				this.on('add remove', function () {
					console.log('something in the collection has changed');
					that.save();
				}).on('add remove reset', function () {
					that._set_new_model(that._new_model);
				});
				console.log('setting up');
				this.on('change', function () {
					that.sort();
				}).on('sort', function () {
					that._set_new_model(that._new_model);
				});
				this.reset(models);
				this.populate();
				this._set_new_model();
			},
			///
			/// @opt <{}> attributes Attributes of new model
			/// @opt <{}> options
			///
			/// @return <model> Model inheriting collection.model
			///
			/// Make a new instance of the model. When the model is saved,
			/// it will be put in the box as an obj and appended to the array.
			new_model: function (attributes, options) {
				var that = this,
					model = new Backbone.Model(attributes);
				options = options || {};
				this._extend_model(model);
				model
					.edit(true)
					.set({ timestamp: now() })
					.on('created', function (model) {
						console.log('going for the add');
						that.add(model);
						that._set_new_model();
						if (options.select) { model.select(); }
						model.trigger('restore');
					})
					.on('restore', function () {
						that._set_new_model();
						if (options.select) { that.selected = undefined; }
					});
				this._set_new_model(model);
				if (options.select) { model.select(true, { save: false }); }
				return model;
			},
			_set_new_model: function (model) {
				this._new_model = model;
				this.all_models = model ? [model].concat(this.models) : this.models;
				this.filter();
			},
			/// Remove the model from the array
			remove: function (model) {
				console.log('!!!remove!!!', arguments);
				if (this.selected === model) { this.selected = undefined; }
				return Backbone.Collection.prototype.remove.apply(this, arguments);
			},
			_extend_model: function (model) {
				var that = this,
					prototype = this.model.prototype;
				_.extend(model, prototype);
				if (prototype.initialize) {
					prototype.initialize.apply(model);
				}
				if (prototype.defaults) {
					_.defaults(model.attributes, prototype.defaults);
				}
				model.box = this.box;
				model.on('select', function (selected, options) {
					options = options || {};
					if (selected && that.selected === model) { return; }
					if (that.selected) { that.selected.select(false); }
					if (selected) {
						that.selected = model;
						that.trigger('select', model);
					}
					if (options.save !== false &&
							(that.options.save_selected || that.options.sync_selected)) {
						model.save({ selected: selected });
						that.each(function (m) {
							if (m !== model && pop(m.get('selected'))) {
								m.save({ selected: false });
							}
						});
					}
				}).on('restore', function () {
					that.trigger('update');
				});
				/*
				model.on('destroyed', function () { // TODO: unbind to prevent memory leaks
					console.log('detected destroy, removing model');
					that.remove(model);
				});*/
			},
			add: function (model, options) {
				var that = this;
				if (_.isArray(model)) {
					_.each(model, function (model) { that.add(model, options); });
				} else {
					if (!_.isObject(model)) {
						console.warn('There was a non-object stored??', model);
						return;
					}
					this._extend_model(model);
					if (this.options.save_selected || this.options.sync_selected) {
						if (pop(model.get('selected')) === true) {
							model.select();
						}
						if (this.options.sync_selected) {
							model.on('change:selected', function () {
								model.select(pop(model.get('selected')));
							});
						}
					}
					return Backbone.Collection.prototype.add.apply(this, arguments);
				}
			},
			/// Fetch the object and update the collection with models in the array
			fetch: function () {
				var that = this,
					promise = $.Deferred();
				this.obj.fetch().then(function () {
					that.populate();
					promise.resolve(that);
				});
				return promise;
			},
			/// Update the collection with models in the array. This will happen automatically when the array changes.
			populate: function () {
				if (!this.obj || !this.options.array_key) { return; }
				var that = this,
					obj = this.obj,
					array_key = this.options.array_key,
					arr = obj.get(array_key) || [];
				this.reset(arr);
				obj.on('change:' + array_key, function (obj, arr) {
					console.log('updating list -->', arguments);
					that.add(arr, { merge: true, silent: true });
					var ids = _.pluck(arr, 'id');
					that.remove(that.select(function (model) {
						return ids.indexOf(model.id) < 0;
					}), { silent: true });
					that.trigger('reset');
				});
			},
			/// Save the obj with the current array state. This will happen automatically when the array changes.
			save: function () {
				var that = this,
					promise = $.Deferred(),
					array_key = that.options.array_key;
				console.log('trying to save list = ', that.models);
				this.obj.save(array_key, that.models).then(function () {
					promise.resolve();
				}).fail(function () { promise.reject('Could not save'); });
				return promise;
			},
			filter: function (fn) {
				if (fn) { this._filter_fn = fn; }
				if (this._filter_fn) {
					this.filtered_models = _.filter(this.all_models, fn);
				} else {
					this.filtered_models = this.all_models;
				}
				this.trigger('update', this);
				return this;
			},
			comparator: function () { return 1; },
			move: function (item, collection) {
				this.copy(item, collection);
				this.remove(item);
			},
			copy: function (item, collection) { // actually a link
				collection.add(item);
			}
		});

		var now = function () {
			return (new Date()).valueOf();
		};
		var pop = function (arr) {
			if (!_.isArray(arr)) { return arr; }
			return _.first(arr);
		};

		return {
			Model: Model,
			Collection: Collection,
			now: now
		};

	});<commit_msg>docs in indx-collection<commit_after>/* global console, Backbone, _, $ */
angular
	.module('indx')
	.factory('collection', function () {
		'use strict';

		var Model = function () {};
		/// Extend behaviour of Model
		Model.extend = function (obj) {
			/// @ignore -- TODO
			var _F = function () {};
			_F.prototype = _.extend({}, Model.prototype, obj);
			return _F;
		};
		_.extend(Model.prototype, {
			is_new: false,
			is_editing: false,
			is_selected: false,
			/// @constructor
			initialize: function () {
				var that = this;
				this._update_new_attributes();
				this.on('change', function () {
					that._update_new_attributes();
				})
			},
			_update_new_attributes: function () {
				if (!this.is_editing) {
					this.newAttributes = _.clone(this.attributes);
				}
			},
			/// @chain
			///
			/// Create an obj in the box based on this model, saving its
			/// attributes as the obj attributes. This will trigger `created`
			/// when completed.
			create: function () {
				var that = this,
					promise = $.Deferred();
				console.log('creating item');
				this.box.get_obj(this.id).then(function (new_obj) {
					console.log('new item');
					new_obj.save(that.newAttributes).then(function () {
						console.log('saved');
						that.trigger('created', new_obj);
					});
				});
				return this;
			},

			/// @then When model has been destroyed
			///
			/// Destroy the model and it's associated object. Triggers
			/// `restore` when destroyed.
			remove: function () {
				var that = this;
				console.log('remove item', this);
				return this.destroy().then(function () {
					console.log('destroyed item');
					that.trigger('restore', this);
				});
			},
			/// @chain
			///
			/// Set the model to edit mode. In this mode, the newAttributes
			/// attribute may be written to to "stage" changes. To save these
			/// changes, call `stage_and_save`, or call `restore` to cancel.
			/// Check if a model is being edited using is_editing attribute.
			/// Triggers `edit`.
			edit: function (_is_new) {
				if (this.is_editing) { return; }
				console.log('edit item', this);
				this._update_new_attributes();
				this.is_new = _is_new;
				this.is_editing = true;
				this.trigger('edit');
				return this;
			},
			/// @then (<Box>) When model has been saved or created successfully
			/// @fail
			///   (<{ code: 409 }> response) box already exists
			///   (<{ code: -1, error: error obj }> response) other error
			///
			/// Take all staged changes in newAttributes attribute and save
			/// them to the obj. If the obj has not been created yet it will
			/// be created.
			stage_and_save: function () {
				var that = this;
				console.log('stage and save');
				if (this.is_new) {
					console.log('new, so create');
					return this.create();
				} else {
					return this.save(this.newAttributes).then(function () {
						console.log('saved');
						that.restore();
						// u.safe_apply($scope); TODO
					});
				}
			},
			///
			/// @opt <{}> attributes Attributes to change
			/// @opt <{}> options
			///
			/// @then When model has been created successfully
			///
			/// Make a new instance of the model. When the model is saved,
			/// it will be put in the box as an obj and appended to the array.
			save: function () {
				if (this.is_new) {
					console.warn('supressing save');
				} else {
					return Backbone.Model.prototype.save.apply(this, arguments);
				}
			},
			/// Switch off edit mode. Triggers `restore`.
			restore: function () {
				this.is_editing = false;
				this.trigger('restore');
				return this;
			},
			/// @opt <boolean> selected If true, select the model, otherwise unselect it.
			/// @opt <{}> options
			///
			/// Selects this model. Triggers `select` with selected boolean and options.
			select: function (selected, options) {
				options = options || {};
				console.log('select', selected);
				selected = _.isBoolean(selected) ? selected : true;
				if (this.is_selected !== selected) {
					console.log('selecting', selected);
					this.is_selected = selected;
					this.trigger('select', selected, options);
				}
			}
		});
		var Collection = Backbone.Collection.extend({
			/// the collection.Model the cast each model to
			model: Model,
			selected: undefined,
			initialize: function (models, options) {
				var that = this;
				this.options = options || {};
				this.box = this.options.box;
				this.obj = this.options.obj;
				this.on('add remove', function () {
					console.log('something in the collection has changed');
					that.save();
				}).on('add remove reset', function () {
					that._set_new_model(that._new_model);
				});
				console.log('setting up');
				this.on('change', function () {
					that.sort();
				}).on('sort', function () {
					that._set_new_model(that._new_model);
				});
				this.reset(models);
				this.populate();
				this._set_new_model();
			},
			///
			/// @opt <{}> attributes Attributes of new model
			/// @opt <{}> options
			///
			/// @return <model> Model inheriting collection.model
			///
			/// Make a new instance of the model. When the model is saved,
			/// it will be put in the box as an obj and appended to the array.
			new_model: function (attributes, options) {
				var that = this,
					model = new Backbone.Model(attributes);
				options = options || {};
				this._extend_model(model);
				model
					.edit(true)
					.set({ timestamp: now() })
					.on('created', function (model) {
						console.log('going for the add');
						that.add(model);
						that._set_new_model();
						if (options.select) { model.select(); }
						model.trigger('restore');
					})
					.on('restore', function () {
						that._set_new_model();
						if (options.select) { that.selected = undefined; }
					});
				this._set_new_model(model);
				if (options.select) { model.select(true, { save: false }); }
				return model;
			},
			_set_new_model: function (model) {
				this._new_model = model;
				this.all_models = model ? [model].concat(this.models) : this.models;
				this.filter();
			},
			/// Remove the model from the array
			remove: function (model) {
				console.log('!!!remove!!!', arguments);
				if (this.selected === model) { this.selected = undefined; }
				return Backbone.Collection.prototype.remove.apply(this, arguments);
			},
			_extend_model: function (model) {
				var that = this,
					prototype = this.model.prototype;
				_.extend(model, prototype);
				if (prototype.initialize) {
					prototype.initialize.apply(model);
				}
				if (prototype.defaults) {
					_.defaults(model.attributes, prototype.defaults);
				}
				model.box = this.box;
				model.on('select', function (selected, options) {
					options = options || {};
					if (selected && that.selected === model) { return; }
					if (that.selected) { that.selected.select(false); }
					if (selected) {
						that.selected = model;
						that.trigger('select', model);
					}
					if (options.save !== false &&
							(that.options.save_selected || that.options.sync_selected)) {
						model.save({ selected: selected });
						that.each(function (m) {
							if (m !== model && pop(m.get('selected'))) {
								m.save({ selected: false });
							}
						});
					}
				}).on('restore', function () {
					that.trigger('update');
				});
				/*
				model.on('destroyed', function () { // TODO: unbind to prevent memory leaks
					console.log('detected destroy, removing model');
					that.remove(model);
				});*/
			},
			add: function (model, options) {
				var that = this;
				if (_.isArray(model)) {
					_.each(model, function (model) { that.add(model, options); });
				} else {
					if (!_.isObject(model)) {
						console.warn('There was a non-object stored??', model);
						return;
					}
					this._extend_model(model);
					if (this.options.save_selected || this.options.sync_selected) {
						if (pop(model.get('selected')) === true) {
							model.select();
						}
						if (this.options.sync_selected) {
							model.on('change:selected', function () {
								model.select(pop(model.get('selected')));
							});
						}
					}
					return Backbone.Collection.prototype.add.apply(this, arguments);
				}
			},
			/// Fetch the object and update the collection with models in the array
			fetch: function () {
				var that = this,
					promise = $.Deferred();
				this.obj.fetch().then(function () {
					that.populate();
					promise.resolve(that);
				});
				return promise;
			},
			/// Update the collection with models in the array. This will happen automatically when the array changes.
			populate: function () {
				if (!this.obj || !this.options.array_key) { return; }
				var that = this,
					obj = this.obj,
					array_key = this.options.array_key,
					arr = obj.get(array_key) || [];
				this.reset(arr);
				obj.on('change:' + array_key, function (obj, arr) {
					console.log('updating list -->', arguments);
					that.add(arr, { merge: true, silent: true });
					var ids = _.pluck(arr, 'id');
					that.remove(that.select(function (model) {
						return ids.indexOf(model.id) < 0;
					}), { silent: true });
					that.trigger('reset');
				});
			},
			/// Save the obj with the current array state. This will happen automatically when the array changes.
			save: function () {
				var that = this,
					promise = $.Deferred(),
					array_key = that.options.array_key;
				console.log('trying to save list = ', that.models);
				this.obj.save(array_key, that.models).then(function () {
					promise.resolve();
				}).fail(function () { promise.reject('Could not save'); });
				return promise;
			},
			filter: function (fn) {
				if (fn) { this._filter_fn = fn; }
				if (this._filter_fn) {
					this.filtered_models = _.filter(this.all_models, fn);
				} else {
					this.filtered_models = this.all_models;
				}
				this.trigger('update', this);
				return this;
			},
			comparator: function () { return 1; },
			move: function (item, collection) {
				this.copy(item, collection);
				this.remove(item);
			},
			copy: function (item, collection) { // actually a link
				collection.add(item);
			}
		});

		var now = function () {
			return (new Date()).valueOf();
		};
		var pop = function (arr) {
			if (!_.isArray(arr)) { return arr; }
			return _.first(arr);
		};

		return {
			Model: Model,
			Collection: Collection,
			now: now
		};

	});<|endoftext|>


<commit_before>(function() {

var create_deep_links = function(entity, _seen) {
    // Mimicks the appearance of deep-links in the Shotgun API.

    // Recursion guard (for extra safety).
    _seen = _seen || {}
    _seen[entity.type] = _seen[entity.type] || {}
    if (_seen[entity.type][entity.id]) {
        return;
    }
    _seen[entity.type][entity.id] = true;

    entity[entity.type] = entity
    for (var key in entity) {
        if (entity.hasOwnProperty(key) && entity[key] && entity[key].type && entity[key].id) {
            create_deep_links(entity[key], _seen)
        }
    }

}

var clone = function(x) {
    return JSON.parse(JSON.stringify(x));
}

var get_selected_entities = function(entity_page) {

    // SG.StreamDetailHeader is for just a single entity.
    if (entity_page.entity) {
        var entity = entity_page.entity;
        var record = entity_page.entity_data[entity.type][entity.id].record;
        var data = clone(record.row.data);
        data.type = entity.type;
        create_deep_links(data);
        return [data];
    }

    // SG.Widget.EntityQuery.EntityQueryPage is the main grid layout.
    var selected_eids = entity_page.selected_entities;
    if (!selected_eids || selected_eids.length < 1) {
        return [];
    }

    var content_widget = entity_page.get_content_widget();
    var entity_set = content_widget.get_entity_data_store();
    var entity_type = entity_page.get_content_widget_entity_type();
    var schema_fields = SG.schema.entity_fields[entity_type];

    var eid_map = {};
    var entities = [];

    // Copy the basic data for each selected entity.
    for (i = 0; i < selected_eids.length; i++) {
        var eid = selected_eids[i];

        var record = entity_set.id_map[eid];
        var data = clone(record.row.data); // This is fast. Really.

        data.type = entity_type; // Shotgun doesn't store this here.

        eid_map[eid] = data;
        entities.push(data)
    }

    // Extract the data used for grouping (which isn't in the record).
    if (entity_set.grouped) {

        var group_defs = entity_set.grouping;
        var groups = entity_set.groups_with_idx;
        for (i = 0; i < groups.length; i++) {
            var group = groups[i];

            for (j = 0; j < group.ids.length; j++) {
                var eid = group.ids[j];

                var entity = eid_map[eid];
                if (!entity) {
                    // Not selected, so don't care.
                    continue
                }

                for (k = 0; k < group_defs.length; k++) {
                    entity[group_defs[k].column] = clone(group.template_values[k])
                }
            }
        }
    }

    // Provide typed links to self in all entities.
    for (i = 0; i < entities.length; i++) {
        create_deep_links(entities[i]);
    }

    return entities;
}

var evaluate_filter = function(filter, entity) {

    // recurse for more complicated filters
    if (typeof filter[0] != "string") {
        for (var i = 0; i < filter.length; i++) {
            if (!evaluate_filter(filter[i], entity)) {
                return false;
            }
        }
        return true;
    }

    var attrs = filter[0].split('.')
    var head = entity;
    try {
        for (var i = 0; i < attrs.length; i++) {
            var attr = attrs[i]
            head = head[attr]
        }
    } catch (e) {
        return;
    }

    switch (filter[1]) {

        case 'is': // in Shotgun
        case 'eq':
        case '==':
            return head == filter[2];

        case 'is_not': // in Shotgun
        case 'not_eq':
        case '!=':
            return head != filter[2];

        case 'starts_with': // in Shotgun
            return filter[2].indexOf(head) == 0;

        case 'contains': // in Shotgun
        case 'in':
            return filter[2].indexOf(head) != -1;

        case 'not_contains': // in Shotgun
        case 'not_in':
            return filter[2].indexOf(head) == -1;

        default:
            throw "unknown operator: " + filter[1];

    }

}

var evaluate_filter_for_many = function(filter, entities) {
    var totals = {
        pass: 0,
        fail: 0,
        unknown: 0,
        total: 0
    }
    for (var i = 0; i < entities.length; i++) {

        var res;
        try {
            res = evaluate_filter(filter, entities[i]);
        } catch (e) {
            console.log('error during evaluate_filter', filter, entities[i], e);
            res = undefined;
        }

        if (res == undefined) {
            totals.unknown += 1
        } else {
            totals[res ? 'pass' : 'fail'] += 1
        }
        totals.total += 1
    }
    return totals;

}

// monkey-patch menu render (for icons, filtering, etc.)
var original_render = window.SG.Menu.prototype.render_menu_items;
window.Ext.override(window.SG.Menu, {
    render_menu_items: function() {

        try {

            var selected = null

            // Parse and set the data.
            for (var i = 0; i < this.items.length; i++) {

                var item = this.items[i];

                // The undefined check is our tenuous hold on folders
                // that are created for ActionMenuItems.
                if (item.url || (item.order !== undefined && item.disabled === undefined)) {

                    var filter = null;

                    // Method 1 of encoding rich data; the second path segment of the URL:
                    // e.g.: `sgactions:xxx/h=Heading&t=Title&i=icon&f={json-of-filter}`
                    // This is not prefered since there are URL-length limitations when
                    // using the fallback dispatch methods, also because it cannot affect
                    // folders.
                    if (/^sgaction/.test(item.url || '')) {

                        var rich = {};
                        var m = /^(.+?)\/(.+?)$/.exec(item.url);
                        if (m) {
                            item.url = m[1]
                            var pairs = m[2].split('&');
                            for (var j = 0; j < pairs.length; j++) {
                                var pair = pairs[j].split('=');
                                rich[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
                            }
                        }

                        item.html = rich.t || item.html;
                        item.icon_name = 'silk-icon silk-icon-' + (rich.i || 'brick');
                        item.heading = rich.h;

                        filter = rich.f ? JSON.parse(rich.f) : null;

                    }

                    // New method: Rich data in the title itself, e.g.:
                    // "Heading / Title"
                    var m = /^\s*(.+?)\s+\/\s+(.+?)\s*$/.exec(item.html || '');
                    if (m) {
                        item.heading = m[1];
                        item.html = m[2];
                    }

                    // Icon in square-brackets at end: "Title [icon]"
                    m = /^(.+?)\s*\[(.+?)\]$/.exec(item.html || '');
                    if (m) {
                        item.html = m[1];
                        item.icon_name = 'silk-icon silk-icon-' + m[2];
                    }

                    // Evalute the filters, and disable/style the results.
                    if (filter) {
                        if (selected == null) {
                            selected = get_selected_entities(this.parent)
                        }
                        var totals = evaluate_filter_for_many(filter, selected);
                        if (totals.pass && totals.fail) {
                            item.html = item.html + ' (' + totals.pass + ' of ' + totals.total + ')'
                            item.item_class = 'sgactions-filter-passfail'
                        } else if (totals.fail) {
                            item.disabled = true;
                        }
                    }

                }

            }

            // Collapse identical headings, and parse icons.
            var current_heading = null;
            for (var i = 0; i < this.items.length; i++) {

                if (this.items[i].heading != current_heading) {

                    // Create all-new heading objects because they have
                    // icons as of (approx.) Shotgun 7.
                    if (this.items[i].heading) {

                        var item = {
                            heading: this.items[i].heading
                        }

                        // Icon in square-brackets at end: "Title [icon]"
                        m = /^(.+?)\s*\[(.+?)\]$/.exec(item.heading || '');
                        if (m) {
                            item.heading = m[1];
                            item.icon_name = 'silk-icon silk-icon-' + m[2];
                        }

                        this.items.splice(i, 0, item);
                        i++; // skip it during iteration

                    } else {

                        // Add a line to seperate from the header.
                        this.items.splice(i, 0, {line: true});
                        i++;

                    }

                    current_heading = this.items[i].heading;

                }

                // Remove the heading from the items themselves.
                this.items[i].heading = null;

            }

            // Remove double lines before headline changes.
            // An item with a "heading" implies a line, but there
            // is an item `{line: true}` before our ActionMenuItems.
            for (var i = 1; i < this.items.length; i++) {
                if ((this.items[i].line || this.items[i].heading) && this.items[i-1].line && !this.items[i-1].html) {
                    this.items[i-1].line = false;
                }
            }

        } catch (err) {
            console.log('[SGActions] error in render_menu_items:', err);
            console.log(err.stack);
        }

        // this.items.push({html: "Submenu test", submenu:{items:[{html: 'Child'}]}});

        return original_render.apply(this, arguments);
    }
});

// monkey-patch base action handler (for page-level actions, etc.)
var original_action = window.SG.Widget.Base.prototype.on_custom_external_action;
window.Ext.override(window.SG.Widget.Base, {
    on_custom_external_action: function(selected_entity, action_url, action_poll_for_data_updates) {

        // Don't do anything if it isn't one of ours.
        if (!SGActions.nativeCapabilities.dispatch || action_url.indexOf("sgaction:") != 0) {
            return original_action.apply(this, arguments);
        }

        try {

            // Lifted from Shotgun's source.
            var req = {
                user_id: SG.globals.current_user.id,
                user_login: SG.globals.current_user.login,
                session_uuid: SG.globals.session_uuid,
                entity_type: selected_entity.type,
                selected_ids: selected_entity.id,
                ids: selected_entity.id,
                server_hostname: SG.globals.hostname,
                title: SG.schema.entity_types[selected_entity.type].display_name + ' ' + selected_entity.id
            };
            var project = this.single_project_from_context();
            if (project) {
                req.project_name = project.name;
                req.project_id = project.id;
                req.title = req.project_name + ' - ' + req.title;
            }

            // SGActions!
            var url = action_url + "?" + Ext.urlEncode(req);
            SGActions.postNative({
                type: 'dispatch',
                url: url
            })

            // Lifted from Shotgun's source.
            if (action_poll_for_data_updates) {
                SG.Repo.request_news()
            }

            SGActions.scheduleMessage({
                html: 'Running ' + action_url.substr(9),
                close_x: true,
                type: 'dispatch'
            })

        } catch (e) {
            console.log('[SGActions] error in on_custom_external_action:', e);
            return original_action.apply(this, arguments);
        }

    }
})

// monkey-patch context menu launcher
var original_launch = window.SG.Widget.EntityQuery.EntityQueryPage.prototype.custom_external_action_launch;
window.Ext.override(window.SG.Widget.EntityQuery.EntityQueryPage, {
    custom_external_action_launch: function() {

        try {

            var base_url = this.custom_external_action.base_url;
            var poll_for_data_updates = this.custom_external_action.poll_for_data_updates;

            // Don't do anything if it isn't one of ours.
            if (!SGActions.nativeCapabilities.dispatch || base_url.indexOf("sgaction:") != 0) {
                return original_launch.apply(this, arguments);
            }

            // Lifted from Shotgun's source.
            var url = base_url + "?" + Ext.urlEncode(this.custom_external_action);

            // SGActions!
            console.log('[SGActions] native dispatch:', base_url);
            SGActions.postNative({
                type: 'dispatch',
                url: url
            })

            // Lifted from Shotgun's source.
            delete this.custom_external_action.base_url;
            delete this.custom_external_action.poll_for_data_updates;
            var content_widget = this.get_content_widget();
            content_widget.hide_loading_overlay();
            if (poll_for_data_updates) {
                SG.Repo.request_news()
            }

            SGActionsUI.scheduleMessage({
                html: 'Running ' + base_url.substr(9),
                close_x: true,
                type: 'dispatch'
            })

        } catch(err) {

            console.log('[SGActions] error in custom_external_action_launch:', err);

            // Restore state, and fall back onto the original.
            if (base_url != undefined) {
                this.custom_external_action.base_url = base_url;
            }
            if (poll_for_data_updates != undefined) {
                this.custom_external_action.poll_for_data_updates = poll_for_data_updates;
            }
            return original_launch.apply(this, arguments);

        }

    }
})

})(); // Closure
<commit_msg>fix action dispatch<commit_after>(function() {

var create_deep_links = function(entity, _seen) {
    // Mimicks the appearance of deep-links in the Shotgun API.

    // Recursion guard (for extra safety).
    _seen = _seen || {}
    _seen[entity.type] = _seen[entity.type] || {}
    if (_seen[entity.type][entity.id]) {
        return;
    }
    _seen[entity.type][entity.id] = true;

    entity[entity.type] = entity
    for (var key in entity) {
        if (entity.hasOwnProperty(key) && entity[key] && entity[key].type && entity[key].id) {
            create_deep_links(entity[key], _seen)
        }
    }

}

var clone = function(x) {
    return JSON.parse(JSON.stringify(x));
}

var get_selected_entities = function(entity_page) {

    // SG.StreamDetailHeader is for just a single entity.
    if (entity_page.entity) {
        var entity = entity_page.entity;
        var record = entity_page.entity_data[entity.type][entity.id].record;
        var data = clone(record.row.data);
        data.type = entity.type;
        create_deep_links(data);
        return [data];
    }

    // SG.Widget.EntityQuery.EntityQueryPage is the main grid layout.
    var selected_eids = entity_page.selected_entities;
    if (!selected_eids || selected_eids.length < 1) {
        return [];
    }

    var content_widget = entity_page.get_content_widget();
    var entity_set = content_widget.get_entity_data_store();
    var entity_type = entity_page.get_content_widget_entity_type();
    var schema_fields = SG.schema.entity_fields[entity_type];

    var eid_map = {};
    var entities = [];

    // Copy the basic data for each selected entity.
    for (i = 0; i < selected_eids.length; i++) {
        var eid = selected_eids[i];

        var record = entity_set.id_map[eid];
        var data = clone(record.row.data); // This is fast. Really.

        data.type = entity_type; // Shotgun doesn't store this here.

        eid_map[eid] = data;
        entities.push(data)
    }

    // Extract the data used for grouping (which isn't in the record).
    if (entity_set.grouped) {

        var group_defs = entity_set.grouping;
        var groups = entity_set.groups_with_idx;
        for (i = 0; i < groups.length; i++) {
            var group = groups[i];

            for (j = 0; j < group.ids.length; j++) {
                var eid = group.ids[j];

                var entity = eid_map[eid];
                if (!entity) {
                    // Not selected, so don't care.
                    continue
                }

                for (k = 0; k < group_defs.length; k++) {
                    entity[group_defs[k].column] = clone(group.template_values[k])
                }
            }
        }
    }

    // Provide typed links to self in all entities.
    for (i = 0; i < entities.length; i++) {
        create_deep_links(entities[i]);
    }

    return entities;
}

var evaluate_filter = function(filter, entity) {

    // recurse for more complicated filters
    if (typeof filter[0] != "string") {
        for (var i = 0; i < filter.length; i++) {
            if (!evaluate_filter(filter[i], entity)) {
                return false;
            }
        }
        return true;
    }

    var attrs = filter[0].split('.')
    var head = entity;
    try {
        for (var i = 0; i < attrs.length; i++) {
            var attr = attrs[i]
            head = head[attr]
        }
    } catch (e) {
        return;
    }

    switch (filter[1]) {

        case 'is': // in Shotgun
        case 'eq':
        case '==':
            return head == filter[2];

        case 'is_not': // in Shotgun
        case 'not_eq':
        case '!=':
            return head != filter[2];

        case 'starts_with': // in Shotgun
            return filter[2].indexOf(head) == 0;

        case 'contains': // in Shotgun
        case 'in':
            return filter[2].indexOf(head) != -1;

        case 'not_contains': // in Shotgun
        case 'not_in':
            return filter[2].indexOf(head) == -1;

        default:
            throw "unknown operator: " + filter[1];

    }

}

var evaluate_filter_for_many = function(filter, entities) {
    var totals = {
        pass: 0,
        fail: 0,
        unknown: 0,
        total: 0
    }
    for (var i = 0; i < entities.length; i++) {

        var res;
        try {
            res = evaluate_filter(filter, entities[i]);
        } catch (e) {
            console.log('error during evaluate_filter', filter, entities[i], e);
            res = undefined;
        }

        if (res == undefined) {
            totals.unknown += 1
        } else {
            totals[res ? 'pass' : 'fail'] += 1
        }
        totals.total += 1
    }
    return totals;

}

// monkey-patch menu render (for icons, filtering, etc.)
var original_render = window.SG.Menu.prototype.render_menu_items;
window.Ext.override(window.SG.Menu, {
    render_menu_items: function() {

        try {

            var selected = null

            // Parse and set the data.
            for (var i = 0; i < this.items.length; i++) {

                var item = this.items[i];

                // The undefined check is our tenuous hold on folders
                // that are created for ActionMenuItems.
                if (item.url || (item.order !== undefined && item.disabled === undefined)) {

                    var filter = null;

                    // Method 1 of encoding rich data; the second path segment of the URL:
                    // e.g.: `sgactions:xxx/h=Heading&t=Title&i=icon&f={json-of-filter}`
                    // This is not prefered since there are URL-length limitations when
                    // using the fallback dispatch methods, also because it cannot affect
                    // folders.
                    if (/^sgaction/.test(item.url || '')) {

                        var rich = {};
                        var m = /^(.+?)\/(.+?)$/.exec(item.url);
                        if (m) {
                            item.url = m[1]
                            var pairs = m[2].split('&');
                            for (var j = 0; j < pairs.length; j++) {
                                var pair = pairs[j].split('=');
                                rich[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
                            }
                        }

                        item.html = rich.t || item.html;
                        item.icon_name = 'silk-icon silk-icon-' + (rich.i || 'brick');
                        item.heading = rich.h;

                        filter = rich.f ? JSON.parse(rich.f) : null;

                    }

                    // New method: Rich data in the title itself, e.g.:
                    // "Heading / Title"
                    var m = /^\s*(.+?)\s+\/\s+(.+?)\s*$/.exec(item.html || '');
                    if (m) {
                        item.heading = m[1];
                        item.html = m[2];
                    }

                    // Icon in square-brackets at end: "Title [icon]"
                    m = /^(.+?)\s*\[(.+?)\]$/.exec(item.html || '');
                    if (m) {
                        item.html = m[1];
                        item.icon_name = 'silk-icon silk-icon-' + m[2];
                    }

                    // Evalute the filters, and disable/style the results.
                    if (filter) {
                        if (selected == null) {
                            selected = get_selected_entities(this.parent)
                        }
                        var totals = evaluate_filter_for_many(filter, selected);
                        if (totals.pass && totals.fail) {
                            item.html = item.html + ' (' + totals.pass + ' of ' + totals.total + ')'
                            item.item_class = 'sgactions-filter-passfail'
                        } else if (totals.fail) {
                            item.disabled = true;
                        }
                    }

                }

            }

            // Collapse identical headings, and parse icons.
            var current_heading = null;
            for (var i = 0; i < this.items.length; i++) {

                if (this.items[i].heading != current_heading) {

                    // Create all-new heading objects because they have
                    // icons as of (approx.) Shotgun 7.
                    if (this.items[i].heading) {

                        var item = {
                            heading: this.items[i].heading
                        }

                        // Icon in square-brackets at end: "Title [icon]"
                        m = /^(.+?)\s*\[(.+?)\]$/.exec(item.heading || '');
                        if (m) {
                            item.heading = m[1];
                            item.icon_name = 'silk-icon silk-icon-' + m[2];
                        }

                        this.items.splice(i, 0, item);
                        i++; // skip it during iteration

                    } else {

                        // Add a line to seperate from the header.
                        this.items.splice(i, 0, {line: true});
                        i++;

                    }

                    current_heading = this.items[i].heading;

                }

                // Remove the heading from the items themselves.
                this.items[i].heading = null;

            }

            // Remove double lines before headline changes.
            // An item with a "heading" implies a line, but there
            // is an item `{line: true}` before our ActionMenuItems.
            for (var i = 1; i < this.items.length; i++) {
                if ((this.items[i].line || this.items[i].heading) && this.items[i-1].line && !this.items[i-1].html) {
                    this.items[i-1].line = false;
                }
            }

        } catch (err) {
            console.log('[SGActions] error in render_menu_items:', err);
            console.log(err.stack);
        }

        // this.items.push({html: "Submenu test", submenu:{items:[{html: 'Child'}]}});

        return original_render.apply(this, arguments);
    }
});

// monkey-patch base action handler (for page-level actions, etc.)
var original_action = window.SG.Widget.Base.prototype.on_custom_external_action;
window.Ext.override(window.SG.Widget.Base, {
    on_custom_external_action: function(selected_entity, action_url, action_poll_for_data_updates) {

        // Don't do anything if it isn't one of ours.
        if (!SGActions.nativeCapabilities.dispatch || action_url.url.indexOf("sgaction:") != 0) {
            return original_action.apply(this, arguments);
        }

        try {

            // Lifted from Shotgun's source.
            var req = {
                user_id: SG.globals.current_user.id,
                user_login: SG.globals.current_user.login,
                session_uuid: SG.globals.session_uuid,
                entity_type: selected_entity.type,
                selected_ids: selected_entity.id,
                ids: selected_entity.id,
                server_hostname: SG.globals.hostname,
                title: SG.schema.entity_types[selected_entity.type].display_name + ' ' + selected_entity.id
            };
            var project = this.single_project_from_context();
            if (project) {
                req.project_name = project.name;
                req.project_id = project.id;
                req.title = req.project_name + ' - ' + req.title;
            }

            // SGActions!
            var url = action_url.url + "?" + Ext.urlEncode(req);
            SGActions.postNative({
                type: 'dispatch',
                url: url
            })

            // Lifted from Shotgun's source.
            if (action_poll_for_data_updates) {
                SG.Repo.request_news()
            }

            SGActions.scheduleMessage({
                html: 'Running ' + action_url.substr(9),
                close_x: true,
                type: 'dispatch'
            })

        } catch (e) {
            console.log('[SGActions] error in on_custom_external_action:', e);
            return original_action.apply(this, arguments);
        }

    }
})

// monkey-patch context menu launcher
var original_launch = window.SG.Widget.EntityQuery.EntityQueryPage.prototype.custom_external_action_launch;
window.Ext.override(window.SG.Widget.EntityQuery.EntityQueryPage, {
    custom_external_action_launch: function(action_url) {

        try {

            var base_url = action_url.url;
            var poll_for_data_updates = this.custom_external_action.poll_for_data_updates;

            // Don't do anything if it isn't one of ours.
            if (!SGActions.nativeCapabilities.dispatch || base_url.indexOf("sgaction:") != 0) {
                return original_launch.apply(this, arguments);
            }

            // Lifted from Shotgun's source.
            var url = base_url + "?" + Ext.urlEncode(this.custom_external_action);

            // SGActions!
            console.log('[SGActions] native dispatch:', base_url);
            SGActions.postNative({
                type: 'dispatch',
                url: url
            })

            // Lifted from Shotgun's source.
            delete this.custom_external_action.base_url;
            delete this.custom_external_action.poll_for_data_updates;
            var content_widget = this.get_content_widget();
            content_widget.hide_loading_overlay();
            if (poll_for_data_updates) {
                SG.Repo.request_news()
            }

            SGActionsUI.scheduleMessage({
                html: 'Running ' + base_url.substr(9),
                close_x: true,
                type: 'dispatch'
            })

        } catch(err) {

            console.log('[SGActions] error in custom_external_action_launch:', err);

            // Restore state, and fall back onto the original.
            if (base_url != undefined) {
                this.custom_external_action.base_url = base_url;
            }
            if (poll_for_data_updates != undefined) {
                this.custom_external_action.poll_for_data_updates = poll_for_data_updates;
            }
            return original_launch.apply(this, arguments);

        }

    }
})

})(); // Closure
<|endoftext|>


<commit_before>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

 /**
  * @name Component
  * @class
  */
var Component = Base.extend(Callback, /** @lends Component# */{
    _class: 'Component',
    _events: [ 'onChange', 'onClick' ],

    // DOCS: All!

    // Meta-information, by type. This is stored in _meta on the components.
    _types: {
        'boolean': {
            type: 'checkbox',
            value: 'checked'
        },

        string: {
            type: 'text'
        },

        number: {
            type: 'number',
            number: true
        },

        button: {
            type: 'button'
        },

        text: {
            tag: 'div',
            // This will return the native textContent through DomElement.get():
            value: 'text'
        },

        slider: {
            type: 'range',
            number: true
        },

        list: {
            tag: 'select',

            setOptions: function() {
                DomElement.removeChildren(this._input);
                DomElement.create(Base.each(this._options, function(option) {
                    this.push('option', { value: option, text: option });
                }, []), this._input);
            }
        },

        color: {
            type: 'color',

            getValue: function(value) {
                // Always convert internal string representation back to a
                // paper.js color object.
                return new Color(value);
            },

            setValue: function(value) {
                // Only enfore hex values if the input field is indeed of
                // color type. This allows sketch.paperjs.org to plug in
                // the Spectrum.js library with alpha support.
                return new Color(value).toCSS(
                        DomElement.get(this._input, 'type') === 'color');
            }
        }
    },

    initialize: function Component(obj) {
        this._id = Component._id = (Component._id || 0) + 1;
        var type = this._type = obj.type in this._types
            ? obj.type
            : 'options' in obj
                ? 'list'
                : 'onClick' in obj
                    ? 'button'
                    : typeof obj.value,
            meta = this._meta = this._types[type] || { type: type },
            name = this._name = obj.name || 'component-' + this._id,
            that = this;
        this._input = DomElement.create(meta.tag || 'input', {
            id: 'palettejs-input-' + name,
            type: meta.type,
            events: {
                change: function() {
                    that.setValue(
                        DomElement.get(this, meta.value || 'value'));
                },
                click: function() {
                    that.fire('click');
                }
            }
        });
        // Attach default 'change' even that delegates to palette
        this.attach('change', function(value) {
            if (!this._dontFire)
                this._palette.fire('change', this, this._name, value);
        });
        this._element = DomElement.create('tr',
                { class: 'palettejs-row', id: 'palettejs-row-' + name }, [
                    this._labelCell = DomElement.create('td',
                        { class: 'palettejs-label' }),
                    'td', { class: 'palettejs-input' }, [this._input]
               ]);
        this._dontFire = true;
        // Now that everything is set up, copy over values fro obj.
        // NOTE: This triggers setters, which is why we set _dontFire = true,
        // and why we can only call this after everything else is set up (e.g.
        // setLabel() requires this._labelCell).
        Base.set(this, obj);
        this._defaultValue = this._value; // after Base.set, through #setValue()
        // Start firing change events after we have initialized.
        this._dontFire = false;
    },

    getType: function() {
        return this._type;
    },

    getName: function() {
        return this._name;
    },

    getLabel: function() {
        return this._label;
    },

    setLabel: function(label) {
        this._label = label;
        DomElement.set(this._labelNode = this._labelNode
                || this._labelCell.appendChild(DomElement.create('label',
                    { 'for': 'palettejs-input-' + this._name })),
                'text', label);
    },

    getSuffix: function() {
        return this._suffix;
    },

    setSuffix: function(suffix) {
        this._suffix = suffix;
        DomElement.set(this._suffixNode = this._suffixNode
                || this._input.parentNode.appendChild(DomElement.create('label',
                    { 'for': 'palettejs-input-' + this._name })),
                'text', suffix);
    },

    getOptions: function() {
        return this._options;
    },

    setOptions: function(options) {
        this._options = options;
        var setOptions = this._meta.setOptions;
        if (setOptions)
            setOptions.call(this);
    },

    getValue: function() {
        var value = this._value,
            getValue = this._meta.getValue;
        return getValue ? getValue.call(this, value) : value;
    },

    setValue: function(value) {
        var meta = this._meta,
            key = meta.value || 'value',
            setValue = meta.setValue;
        if (setValue)
            value = setValue.call(this, value);
        DomElement.set(this._input, key, value);
        // Read back and convert from input again, to make sure we're in sync
        value = DomElement.get(this._input, key);
        if (meta.number)
            value = parseFloat(value, 10);
        if (this._value !== value) {
            this._value = value;
            if (!this._dontFire)
                this.fire('change', this.getValue());
        }
    },

    getRange: function() {
        return [parseFloat(DomElement.get(this._input, 'min')),
                parseFloat(DomElement.get(this._input, 'max'))];
    },

    setRange: function(min, max) {
        var range = Array.isArray(min) ? min : [min, max];
        DomElement.set(this._input, { min: range[0], max: range[1] });
    },

    getMin: function() {
        return this.getRange()[0];
    },

    setMin: function(min) {
        this.setRange(min, this.getMax());
    },

    getMax: function() {
        return this.getRange()[1];
    },

    setMax: function(max) {
        this.setRange(this.getMin(), max);
    },

    getStep: function() {
        return parseFloat(DomElement.get(this._input, 'step'));
    },

    setStep: function(step) {
        DomElement.set(this._input, 'step', step);
    },

    reset: function() {
        this.setValue(this._defaultValue);
    }
});
<commit_msg>Always set Component#value at the end of all other values.<commit_after>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

 /**
  * @name Component
  * @class
  */
var Component = Base.extend(Callback, /** @lends Component# */{
    _class: 'Component',
    _events: [ 'onChange', 'onClick' ],

    // DOCS: All!

    // Meta-information, by type. This is stored in _meta on the components.
    _types: {
        'boolean': {
            type: 'checkbox',
            value: 'checked'
        },

        string: {
            type: 'text'
        },

        number: {
            type: 'number',
            number: true
        },

        button: {
            type: 'button'
        },

        text: {
            tag: 'div',
            // This will return the native textContent through DomElement.get():
            value: 'text'
        },

        slider: {
            type: 'range',
            number: true
        },

        list: {
            tag: 'select',

            setOptions: function() {
                DomElement.removeChildren(this._input);
                DomElement.create(Base.each(this._options, function(option) {
                    this.push('option', { value: option, text: option });
                }, []), this._input);
            }
        },

        color: {
            type: 'color',

            getValue: function(value) {
                // Always convert internal string representation back to a
                // paper.js color object.
                return new Color(value);
            },

            setValue: function(value) {
                // Only enfore hex values if the input field is indeed of
                // color type. This allows sketch.paperjs.org to plug in
                // the Spectrum.js library with alpha support.
                return new Color(value).toCSS(
                        DomElement.get(this._input, 'type') === 'color');
            }
        }
    },

    initialize: function Component(obj) {
        this._id = Component._id = (Component._id || 0) + 1;
        var type = this._type = obj.type in this._types
            ? obj.type
            : 'options' in obj
                ? 'list'
                : 'onClick' in obj
                    ? 'button'
                    : typeof obj.value,
            meta = this._meta = this._types[type] || { type: type },
            name = this._name = obj.name || 'component-' + this._id,
            that = this;
        this._input = DomElement.create(meta.tag || 'input', {
            id: 'palettejs-input-' + name,
            type: meta.type,
            events: {
                change: function() {
                    that.setValue(
                        DomElement.get(this, meta.value || 'value'));
                },
                click: function() {
                    that.fire('click');
                }
            }
        });
        // Attach default 'change' even that delegates to palette
        this.attach('change', function(value) {
            if (!this._dontFire)
                this._palette.fire('change', this, this._name, value);
        });
        this._element = DomElement.create('tr',
                { class: 'palettejs-row', id: 'palettejs-row-' + name }, [
                    this._labelCell = DomElement.create('td',
                        { class: 'palettejs-label' }),
                    'td', { class: 'palettejs-input' }, [this._input]
               ]);
        this._dontFire = true;
        // Now that everything is set up, copy over values fro obj.
        // NOTE: This triggers setters, which is why we set _dontFire = true,
        // and why we can only call this after everything else is set up (e.g.
        // setLabel() requires this._labelCell).
        // Exclude name because it's already set, and value since we want to set
        // it after range.
        Base.set(this, obj, { name: true, value: true });
        this.setValue(obj.value);
        this._defaultValue = this._value;
        // Start firing change events after we have initialized.
        this._dontFire = false;
    },

    getType: function() {
        return this._type;
    },

    getName: function() {
        return this._name;
    },

    getLabel: function() {
        return this._label;
    },

    setLabel: function(label) {
        this._label = label;
        DomElement.set(this._labelNode = this._labelNode
                || this._labelCell.appendChild(DomElement.create('label',
                    { 'for': 'palettejs-input-' + this._name })),
                'text', label);
    },

    getSuffix: function() {
        return this._suffix;
    },

    setSuffix: function(suffix) {
        this._suffix = suffix;
        DomElement.set(this._suffixNode = this._suffixNode
                || this._input.parentNode.appendChild(DomElement.create('label',
                    { 'for': 'palettejs-input-' + this._name })),
                'text', suffix);
    },

    getOptions: function() {
        return this._options;
    },

    setOptions: function(options) {
        this._options = options;
        var setOptions = this._meta.setOptions;
        if (setOptions)
            setOptions.call(this);
    },

    getValue: function() {
        var value = this._value,
            getValue = this._meta.getValue;
        return getValue ? getValue.call(this, value) : value;
    },

    setValue: function(value) {
        var meta = this._meta,
            key = meta.value || 'value',
            setValue = meta.setValue;
        if (setValue)
            value = setValue.call(this, value);
        DomElement.set(this._input, key, value);
        // Read back and convert from input again, to make sure we're in sync
        value = DomElement.get(this._input, key);
        if (meta.number)
            value = parseFloat(value, 10);
        if (this._value !== value) {
            this._value = value;
            if (!this._dontFire)
                this.fire('change', this.getValue());
        }
    },

    getRange: function() {
        return [parseFloat(DomElement.get(this._input, 'min')),
                parseFloat(DomElement.get(this._input, 'max'))];
    },

    setRange: function(min, max) {
        var range = Array.isArray(min) ? min : [min, max];
        DomElement.set(this._input, { min: range[0], max: range[1] });
    },

    getMin: function() {
        return this.getRange()[0];
    },

    setMin: function(min) {
        this.setRange(min, this.getMax());
    },

    getMax: function() {
        return this.getRange()[1];
    },

    setMax: function(max) {
        this.setRange(this.getMin(), max);
    },

    getStep: function() {
        return parseFloat(DomElement.get(this._input, 'step'));
    },

    setStep: function(step) {
        DomElement.set(this._input, 'step', step);
    },

    reset: function() {
        this.setValue(this._defaultValue);
    }
});
<|endoftext|>


<commit_before>/**
 * module  asqueue
 * author  Kael Zhang

 Asynchronous and Synchronous Queue:
 - Converter: put all specified methods into an executing queue before initialization methods completed
 - Runner: execute a specified list of methods

 which could:
 - keep the executing ORDER even if the queue is mixed with both asynchronous and synchronous methods
 - make sure method A will be executed before method B if assigned to
 - make sure a method will be executed only once if specified

 	// .plugin method is an asynchronous method, but .init method relies on the effect which the .plugin method caused
 	new Switch().plugin('carousel').init({...});

 example:

 var ASQ = require('until/asqueue'),
 	 asq_r,
 	 module = {
 	 	a: function(){
 	 		console.log('a');
 	 	},

 	 	b: function(){
 	 		console.log('b');
 	 		asyncMethod(function(){
 	 			console.log('b callback');
 	 			asq_r.resume();
 	 		});
 	 	}
 	 }

 asq_r = new ASQ.Runner([
 	{
 		method: 'b',
 		auto: false
 	},

 	'a'
 ], module);

 asq_r.run();

 result:
 b
 b callback
 a

 */

KM.define([], function(K){

var NOOP = function(){},

ASQueue = {},

// meta prototypes
ASQ_meta = {
	Runner: {
		/**
	     * run the list of configured methods
	     */
	    run: function(queue){
	    	var self = this;

	    	self._stack = self._sd( queue ? K.makeArray(queue) : self._presetItems );

	    	self.resume();
	    },

	    stop: function(){
	    	this._stack.length = 0;
	    },

	    // santitize data
	    _sd: function(queue){
	    	var self = this,
	    		ret = [];

	    	queue.forEach(function(q){
	    		ret.push(self._santitize(q));
	    	});

	    	return ret;
	    }
	},

	Converter: {
		/**
	     * make all specified method queue-supported
	     */
	    on: function(){
	    	var self = this, host = self.host;

	    	self._items = {};
	    	self._history = [];

	    	if(host){
		    	self._presetItems.forEach(function(i){
		    		i && self._add(i);
		    	});
		    }

		    self._converted = true;

	    	return self;
	    },

	    /**
	     * recover the converted methods
	     */
	    off: function(){
	    	var identifier,
	    		self = this,
	    		host = self.host;

	    	for(name in self._items){
	    		delete host[name];
	    		host[name] = self._items[name];
	    	}

	    	self._clean();
	    	self._converted = false;

	    	return self;
	    },

	    _clean: function(){
	    	var self = this;
	    	self._items = {};
	    	self._stack.length = self._history.length = 0;
	    },

	    _add: function(obj){
	    	var self = this,
	    		host = self.host,
	    		identifier;

	    	if(self._converted || !host) return self;

			obj = self._santitize(obj);
			identifier = obj.id;

			delete obj.id;

			if(identifier){
				self._items[identifier] = obj.method;

				host[identifier] = function(){
					var id = identifier, clone, mix;

					if(
						self._history.indexOf(obj.before) === -1 &&
						(!obj.once ||
							self._history.indexOf(id) === -1
						)
					){
						mix = K.mix;
						clone = mix({}, obj);
						clone.args = arguments;

						self._stack.push(clone);
						self._history.push(id);
					}

					if(obj.once){
						self._items[id] = NOOP;
					}

					// avoid recursive invocation
					setTimeout(function(){ self._next(); }, 0);

					return host; // chain
				}
			}
	    }
	}
},

ASQ_proto = {
    _stack: [],

    /**
     * @param {Array.<function(),string,Object>} items
     	{function()}
     	{string}
     	{Object} detail configuration for each method
     		{
     			auto: {Boolean},
     			once: {Boolean},
     			method: {function()|string}
     			args: {Array} arguments
     		}
     * @param {Object} host
     */
    initialize: function(items, host){
    	var self = this;

    	self._presetItems = K.makeArray(items);
    	self.host = host || null;
    },

    /**
     * resume the paused executing queue
     */
    resume: function(){
    	var self = this;
    	self.processing = false;

    	return self._next();
    },

    // apply default settings
    // 'method' -> {name: 'method'}
    _santitize: function(obj, undef){
    	var self = this,
    		host = self.host,
    		mix = K.mix;

    	if(K.isFunction(obj)){
    		obj = {
    			method: obj
    		};

    	}else if(K.isPlainObject(obj)){
    		obj = mix(obj, self._santitize(obj.method), true, ['method', 'bind', 'id']);

    		host && !('bind' in obj) && (obj.bind = host);

    	}else if(K.isString(obj) && host){
    		obj = {
    			method: host[obj],
    			bind: host,
    			id: obj
    		};

    	}else{
    		obj = {};
    	}

    	return mix({
    		auto: true,
    		once: false
    	}, obj);
    },

    _next: function(){
    	var self = this,
    		current, fn;

    	if(!self.processing && (current = self._stack.shift()) && (fn = current.method)){
    		self.processing = true;

    		fn.apply(current.bind || null, current.args || []);

    		return current.auto && self.resume();
    	}
    }
};

// ASQueue.Runner
// ASQueue.Converter
['Runner', 'Converter'].each(function(type){
	var ASQ = ASQueue[type] = new Class(ASQ_proto);

	K.mix(ASQ.prototype, ASQ_meta[type]);
});

return ASQueue;

});<commit_msg>util/asqueue: remove asqueue<commit_after><|endoftext|>