<commit_before>import FetchStates from './fetch-states';
import HttpMethods from './http-methods';

/**
 * Build a function that receive parameters, headers, body and options and dispatch a request to the correct http layer method
 * The dispatch function will invoke middlewares and the responseHandler. The result of the call function will be the result of a
 * middleware or of the response handler. Middlewares are not supported yet
 * @param {Object} mapper - Mapper object
 * @param {Object} resource - Resource object
 * @param {Object} endPoint - An end point object
 * @returns {Function}
 */
export function buildCallMethod(mapper, resource, endPoint) {
  return function (params, headers, body, options) {
    const { httpLayer, httpResponseHandler } = mapper;
    const path = applyParamsToPath(resource.path + endPoint.path, params);
    const method = (endPoint.method)? endPoint.method.toUpperCase() : HttpMethods.GET;
    const headersBuilt = Object.assign({}, mapper.headers, resource.headers, endPoint.headers, headers);
    const request = buildRequest(mapper.host + path, path, params, headersBuilt, body, options);

    return dispatchRequest(method, httpResponseHandler, mapper.store, endPoint.action, httpLayer, request);
  };
};

/**
 * This function dispatch a pre-fetch action in the store and call the correct http layer method
 * @param {String} method
 * @param {Function} httpResponseHandler
 * @param {Object} store
 * @param {(Object|Function)} action
 * @param {Object} httpLayer
 * @param {Object} request
 */
export function dispatchRequest(method, httpResponseHandler, store, action, httpLayer, request) {
  let stateDispatcher = buildStateDispatcher(store.dispatch, action);

  //dispatch pre-fetch action here
  stateDispatcher(FetchStates.FETCH_STARTED);

  let lMethod = method ? method.toLowerCase() : 'get';

  if (httpLayer[lMethod]) //method is implemented
    return httpResponseHandler(stateDispatcher, httpLayer[lMethod](request));
  else //method is not implemented
    throw new Error(`Method ${lMethod} is not supported by http-layer`);
}

/**
 * This is a helper function to dispatch a new action to the store based on the current state of the request
 * @param {Function} storeDispatcher - The store dispatcher function
 * @returns {Function} - A function that dispatch a new action to the store based on the state being dispatched
 */
export function buildStateDispatcher(storeDispatcher, action) {
  return function (state, payload) {
    let actionResult = action(state);
    let actionObj;

    if (typeof actionResult === "function")
      actionObj = actionResult(payload);
    else if (typeof actionResult === "object")
      actionObj = Object.assign({}, actionResult, {payload});
    else
      throw new Error('Action mapper is not returning an action creator or action object. Cannot dispatch action to redux.');

    storeDispatcher(actionObj);
  }
}

/**
 * Apply params into a path
 * @param {String} path - A path string
 * @param {Object} params - An object containing all parameters
 * @returns {string} - A new string with the parameters applied
 */
export function applyParamsToPath(path, params) {
  if (!params)
    return path;

  let queryString = "";
  let tmpPath = path.slice();
  for (let param in params) {
    let reg = new RegExp(`{${param}}`);
    const newPath = tmpPath.replace(reg, params[param]);
    if (newPath === tmpPath)
      queryString = (queryString) ? queryString + `&${param}=${params[param]}` : `${param}=${params[param]}`;
    tmpPath = newPath;
  }
  return (queryString) ? tmpPath + '?' + queryString : tmpPath;
}

/**
 * Build a request object
 * @param {String} fullPath - A full path containing host + path
 * @param {String} path - A path string
 * @param {Object} params - A parameters object
 * @param {Object} headers - A header object
 * @param {*} body - A body
 * @param {*} options - Optional parameters
 * @returns {{path: String, params: Object, headers: Object, body: *, options: *}}
 */
export function buildRequest(fullPath, path, params, headers, body, options) {
  return {fullPath, path, params, headers, body, options};
}
<commit_msg>make actions optional<commit_after>import FetchStates from './fetch-states';
import HttpMethods from './http-methods';

/**
 * Build a function that receive parameters, headers, body and options and dispatch a request to the correct http layer method
 * The dispatch function will invoke middlewares and the responseHandler. The result of the call function will be the result of a
 * middleware or of the response handler. Middlewares are not supported yet
 * @param {Object} mapper - Mapper object
 * @param {Object} resource - Resource object
 * @param {Object} endPoint - An end point object
 * @returns {Function}
 */
export function buildCallMethod(mapper, resource, endPoint) {
  return function (params, headers, body, options) {
    const { httpLayer, httpResponseHandler } = mapper;
    const path = applyParamsToPath(resource.path + endPoint.path, params);
    const method = (endPoint.method)? endPoint.method.toUpperCase() : HttpMethods.GET;
    const headersBuilt = Object.assign({}, mapper.headers, resource.headers, endPoint.headers, headers);
    const request = buildRequest(mapper.host + path, path, params, headersBuilt, body, options);

    return dispatchRequest(method, httpResponseHandler, mapper.store, endPoint.action, httpLayer, request);
  };
};

/**
 * This function dispatch a pre-fetch action in the store and call the correct http layer method
 * @param {String} method
 * @param {Function} httpResponseHandler
 * @param {Object} store
 * @param {(Object|Function)} action
 * @param {Object} httpLayer
 * @param {Object} request
 */
export function dispatchRequest(method, httpResponseHandler, store, action, httpLayer, request) {
  let stateDispatcher = buildStateDispatcher(store.dispatch, action);

  //dispatch pre-fetch action here
  stateDispatcher(FetchStates.FETCH_STARTED);

  let lMethod = method ? method.toLowerCase() : 'get';

  if (httpLayer[lMethod]) //method is implemented
    return httpResponseHandler(stateDispatcher, httpLayer[lMethod](request));
  else //method is not implemented
    throw new Error(`Method ${lMethod} is not supported by http-layer`);
}

/**
 * This is a helper function to dispatch a new action to the store based on the current state of the request
 * @param {Function} storeDispatcher - The store dispatcher function
 * @returns {Function} - A function that dispatch a new action to the store based on the state being dispatched
 */
export function buildStateDispatcher(storeDispatcher, action) {
  return function (state, payload) {
    let actionResult = action(state);
    let actionObj;

    if (typeof actionResult === "function")
      actionObj = actionResult(payload);
    else if (typeof actionResult === "object")
      actionObj = Object.assign({}, actionResult, {payload});
    else //if aciton is invalid do not dispatch
      return;

    storeDispatcher(actionObj);
  }
}

/**
 * Apply params into a path
 * @param {String} path - A path string
 * @param {Object} params - An object containing all parameters
 * @returns {string} - A new string with the parameters applied
 */
export function applyParamsToPath(path, params) {
  if (!params)
    return path;

  let queryString = "";
  let tmpPath = path.slice();
  for (let param in params) {
    let reg = new RegExp(`{${param}}`);
    const newPath = tmpPath.replace(reg, params[param]);
    if (newPath === tmpPath)
      queryString = (queryString) ? queryString + `&${param}=${params[param]}` : `${param}=${params[param]}`;
    tmpPath = newPath;
  }
  return (queryString) ? tmpPath + '?' + queryString : tmpPath;
}

/**
 * Build a request object
 * @param {String} fullPath - A full path containing host + path
 * @param {String} path - A path string
 * @param {Object} params - A parameters object
 * @param {Object} headers - A header object
 * @param {*} body - A body
 * @param {*} options - Optional parameters
 * @returns {{path: String, params: Object, headers: Object, body: *, options: *}}
 */
export function buildRequest(fullPath, path, params, headers, body, options) {
  return {fullPath, path, params, headers, body, options};
}
<|endoftext|>


<commit_before>export default {

  navigator: {
    doc: 'Docs',
    demo: 'Demo',
    started: 'Get Started'
  },

  features: {
    userExperience: {
      title: 'Optimize Experience',
      desc: 'To make scroll more smoothly, We support flexible configuration about inertial scrolling, rebound, fade scrollbar, etc. which could optimize user experience obviously.'
    },
    application: {
      title: 'Diverse Use',
      desc: 'It could use in normal scroll list, picker, slide, index list, start guidance, etc. And could realize pull down reflesh, pull up load easy.'
    },
    dependence: {
      title: 'None Dependence',
      desc: 'As a JavaScript library, BetterScroll don\'t depend on any framework, you could use it alone, or together with MVVM framework, Such as, Vue which the offical demo used.'
    }
  },

  examples: {
    normalScrollList: 'Normal Scroll List',
    indexList: 'Index List',
    picker: 'Picker',
    slide: 'Slide',
    startGuidance: 'Start Guidance',
    freeScroll: 'Free Scroll',
    verticalScrollImg: 'vertical-scroll-en.jpeg',
    indexListImg: 'index-list.jpeg',
    pickerImg: 'picker-en.jpeg',
    slideImg: 'slide.jpeg',
    startGuidanceImg: 'full-page-slide.jpeg',
    freeScrollImg: 'free-scroll.jpeg'
  },

  normalScrollListPage: {
    desc: 'Nomal scroll list based on BetterScroll',
    scrollbar: 'Scrollbar',
    pullDownRefresh: 'Pull Down Refresh',
    pullUpLoad: 'Pull Up Load',
    previousTxt: 'I am the No.',
    followingTxt: ' line',
    newDataTxt: 'I am new data: '
  },

  scrollComponent: {
    defaultLoadTxtMore: 'Load more',
    defaultLoadTxtNoMore: 'There is no more data',
    defaultRefreshTxt: 'Refresh success'
  },

  indexListPage: {
    title: 'Current City: Beijing'
  },

  pickerPage: {
    desc: 'Picker is a typical choose component at mobile end. And it could dynamic change the data of every column to realize linkage picker.',
    picker: ' picker',
    pickerDemo: ' picker demo ...',
    oneColumn: 'One column',
    twoColumn: 'Two column',
    threeColumn: 'Three column',
    linkage: 'Linkage',
    confirmTxt: 'confirm | ok',
    cancelTxt: 'cancel | close'
  },

  slidePage: {
    desc: 'Slide is a typical component at mobile end, support horizontal move.'
  },

  fullPageSlideComponent: {
    buttonTxt: 'Start Use'
  },

  freeScrollPage: {
    desc: 'Free scroll supports horizontal and vertical move at the same time.'
  }
}
<commit_msg>optimize english<commit_after>export default {

  navigator: {
    doc: 'Docs',
    demo: 'Demo',
    started: 'Get Started'
  },

  features: {
    userExperience: {
      title: 'Optimize Experience',
      desc: 'To make scroll more smoothly, We support flexible configurations about inertial scrolling, rebound, fade scrollbar, etc. which could optimize user experience obviously.'
    },
    application: {
      title: 'Rich Features',
      desc: 'It supports lots of features which can be applied in normal scroll list, picker, slide, index list, start guidance, etc. to implement complicated needs a lot easier, such as pull down refresh, pull up load.'
    },
    dependence: {
      title: 'Dependence Free',
      desc: 'As a plain JavaScript library, BetterScroll doesn\'t depend on any framework, you could use it alone, or with any other MVVM frameworks.'
    }
  },

  examples: {
    normalScrollList: 'Normal Scroll List',
    indexList: 'Index List',
    picker: 'Picker',
    slide: 'Slide',
    startGuidance: 'Start Guidance',
    freeScroll: 'Free Scroll',
    verticalScrollImg: 'vertical-scroll-en.jpeg',
    indexListImg: 'index-list.jpeg',
    pickerImg: 'picker-en.jpeg',
    slideImg: 'slide.jpeg',
    startGuidanceImg: 'full-page-slide.jpeg',
    freeScrollImg: 'free-scroll.jpeg'
  },

  normalScrollListPage: {
    desc: 'Nomal scroll list based on BetterScroll',
    scrollbar: 'Scrollbar',
    pullDownRefresh: 'Pull Down Refresh',
    pullUpLoad: 'Pull Up Load',
    previousTxt: 'I am the No.',
    followingTxt: ' line',
    newDataTxt: 'I am new data: '
  },

  scrollComponent: {
    defaultLoadTxtMore: 'Load more',
    defaultLoadTxtNoMore: 'There is no more data',
    defaultRefreshTxt: 'Refresh success'
  },

  indexListPage: {
    title: 'Current City: Beijing'
  },

  pickerPage: {
    desc: 'Picker is a typical choose component at mobile end. And it could dynamic change the data of every column to realize linkage picker.',
    picker: ' picker',
    pickerDemo: ' picker demo ...',
    oneColumn: 'One column',
    twoColumn: 'Two column',
    threeColumn: 'Three column',
    linkage: 'Linkage',
    confirmTxt: 'confirm | ok',
    cancelTxt: 'cancel | close'
  },

  slidePage: {
    desc: 'Slide is a typical component at mobile end, support horizontal move.'
  },

  fullPageSlideComponent: {
    buttonTxt: 'Start Use'
  },

  freeScrollPage: {
    desc: 'Free scroll supports horizontal and vertical move at the same time.'
  }
}
<|endoftext|>


<commit_before>/**
 * Created by Chen on 2016-12-11.
 */

'use strict';

let AWS = require("aws-sdk");
const config = require('../config');
// AWS.config.update({
//   region: "us-west-2",
//   endpoint: "http://localhost:8000"
// });

let request = require('request');
let docClient = new AWS.DynamoDB.DocumentClient();

/**
 * Get all users' ids from Table UserIds
 * @param {string} userId
 * @returns {Promise}
 */
const getAllUserIds = (userId) => {
  return new Promise((resolve, reject) => {
    let params = {
      TableName: "UserIds",
      Key: {
        id: "allIds"
      },
    };
    // docClient.get()
    docClient.get(params, (err, data) => {
      if (err) {
        console.error("Unable to get userIds. Error JSON:", JSON.stringify(err), err.stack);
        return reject(err);
      } else {
        // console.log("Get user ids successfully", data);
        let userIds = data.Item.usersIds.values,
            candidateIds = userIds.filter(id => id !== userId);
        // console.log("candidateIds", candidateIds);
        console.log("Get all users ID successfully.");
        // console.log("userId", userId);
        // console.log("candidateIds", candidateIds);
        resolve(candidateIds);
      }
    });
  });
};

const randPickFollowers = (validCandis, followNumber) => {
  followNumber = Math.min(followNumber, validCandis.length);
  // console.log("userId", userId);
  // console.log("candidateIds", candidateIds);
  for (let i = 0; i < followNumber; i++) {
    // hardcoded value
    let index = Math.floor(Math.random() * validCandis.length);

    //Swap the two elements
    let temp = validCandis[i];
    validCandis[i] = validCandis[index];
    validCandis[index] = temp;
  }
  let pickedUsers = validCandis.slice(0, followNumber);
  console.log("Picked users successfully"/*, JSON.stringify(pickedUsers)*/);
  return pickedUsers;
};

const getPickedUsers = (pickedIds) => {
  let promises = pickedIds.map(id => {
    return new Promise((resolve, reject) => {
      let params = {
        TableName: "Users",
        Key: {
          id: id
        },
        ProjectionExpression: "id, login, avatar_url, followNumber, html_url, #name, #token",
        ExpressionAttributeNames: {
          "#name": "name",
          "#token": "token"
        },
      };
      docClient.get(params, (err, data) => {
        if (err) {
          console.error("Unable to get item. Error JSON:", JSON.stringify(err), err.stack);
          return reject(err);
        }
        let item = data.Item;
        // console.log("GetItem succeeded:", JSON.stringify(item));
        resolve(item);
      });
    });
  });

  return Promise.all(promises)
      .then((users) => {
        console.log("Fetched all users successfully");
        // console.log("users", users);
        return users;
      });
};

const followAndStore = (users, user) => {
  // console.log("fetched users", users);
  // console.log("original user", user);
  //promise -> send PUT request to follow -> add in "Friends" Table
  let promises = [];

  const followUser = (token, username) => {
    return new Promise((resolve, reject) => {
      let options = {
        url: "https://api.github.com/user/following/" + username + "?access_token=" + token,
        method: "put",
        headers: {
          'User-Agent': "GitMax"
        }
      };

      request(options, (error, response, body) => {
        if (error) {
          console.error("error following user " + username, error, error.stack);
          return reject(error);
        }
        // console.log("Successfully added")
        resolve();
      });
    })
  };

  const storeFriendship = (user1, user2) => {
    return new Promise((resolve, reject) => {
      let item = {
        "userId": user1.id,
        "username": user1.name,
        "userLogin": user1.login,
        "userUrl": user1.html_url,
        "userAvatarUrl": user1.avatar_url,
        "friendId": user2.id,
        "friendName": user2.name,
        "friendLogin": user2.login,
        "friendUrl": user2.html_url,
        "friendAvatarUrl": user2.avatar_url,
      };
      let params = {
        TableName: "Friends",
        Item: item,
      };
      docClient.put(params, (err, data) => {
        if (err) {
          console.error("Unable to add friendship to friends Table. ", JSON.stringify(err),
              err.stack);
          return reject(err);
        } else {
          // console.log("Friendship added successfully");
          resolve(item);
        }
      });
    });
  };

  users.forEach(userX => {
    // console.log("userX", userX);
    // console.log("user", user);
    promises.push(
        followUser(userX.token, user.login)
            .then(() => storeFriendship(userX, user))
            .then(() => userX)
    );
    promises.push(
        followUser(user.token, userX.login)
            .then(() => storeFriendship(user, userX))
            .then(() => null) //users only need to be added once
    );
  });

  function getNeededProps(friend) {
    return {
      id: friend.id,
      url: friend.url,
      avatar_url: friend.avatar_url,
      login: friend.login,
      name: friend.name,
      created_at: friend.created_at,
    }
  }

  return Promise.all(promises)
      .then((result) => {
        console.log("storeFriendship finished"/*, result*/);
        let addedFriends = [];
        result.forEach(friend => friend && addedFriends.push(getNeededProps(friend)));
        return (addedFriends);
      });
};

const getFriends = (userId) => {
  console.log("getFriends start with id", userId);
  const params = {
    TableName: "Friends",
    KeyConditionExpression: "#userId = :id",
    ExpressionAttributeNames: {
      "#userId": "userId"
    },
    ExpressionAttributeValues: {
      ":id": userId
    }
  };
  return new Promise((resolve, reject) => {
    docClient.query(params, (err, data) => {
      if (err) {
        console.error("Unable to get friends. Error JSON:", JSON.stringify(err), err.stack);
        return reject(err);
      } else {
        console.log("Query succeeded."/*, JSON.stringify(data)*/);
        let friends = data.Items;
        resolve(friends);
      }
    });
  });
};

const getCandisByScan = (user) => {
  const params = { // 找到与用户互相欣赏的人，作为好友candidate
    TableName: "Users",
    ProjectionExpression: "#id, #url, created_at, avatar_url, followers, following, login, #name," +
    " #token, addFollowersMax, crit_FollowersCount, crit_StargazersCount, totalStars",
    FilterExpression: "totalStars >= :user_star_crit AND followers >= :user_follower_crit AND" +
    " crit_StargazersCount <= :user_star AND crit_FollowersCount <= :user_follower",
    ExpressionAttributeNames: {
      "#id": "id",
      "#url": "url",
      "#name": "name",
      "#token": "token",
    },
    ExpressionAttributeValues: {
      ":user_star_crit": user.crit_StargazersCount,
      ":user_follower_crit": user.crit_FollowersCount,
      ":user_star": user.totalStars,
      ":user_follower": user.followers,
    }
  };

  return new Promise((resolve, reject) => {
    docClient.scan(params, (err, data) => {
      if (err) {
        console.error("Unable to get item. Error JSON:", JSON.stringify(err), err.stack);
        return reject(err);
      } else {
        console.log("Scan succeeded."/*, JSON.stringify(data)*/);//Todo, comment data
        let candidates = data.Items;
        resolve(candidates);
      }
    });
  });
};

const removeAddedCandis = (candidates, friends, userId) => {
  let set = new Set();
  friends.forEach(obj => set.add(obj.friendId));// Add all friends' Ids in a set
  set.add(userId); // Remove the user from valid candidates

  let validCandis = [];
  candidates.forEach((candidate) => {
    if (!set.has(candidate.id)) validCandis.push(candidate);
  });
  return validCandis;
};

const removeFullFriendsCandidates = (candidates) => {
  let validCandis = [];
  candidates.forEach(candi => {
    if (candi.addFollowersMax > candi.followers) validCandis.push(candi);
  });
  return validCandis;
};

const configUpdate = (user, stage) => {
  // const stage = event.requestContext.stage;
  // const stage = config.getStage();
  user.followedFriendsAt = new Date().getTime();
  const url = config.lambda[stage].configUpdateEndpoint;

  let options = {
    url: url,
    method: "POST",
    body: JSON.stringify({user: user}),
  };

  return new Promise((resolve, reject) => {
    request(options, (error, response, body) => {
      if (error) {
        return reject(error);
      }
      let newUser = JSON.parse(body).user;
      // console.log("response", response);
      // console.log("data", data);
      // console.log("Update followedAt, user:", newUser);
      //DynamoDB不能包含任何empty attributes
      resolve(newUser);
    });
  });
};

const handleFollow = (event, context, callback) => {
  // console.log("DynamoDB triggered lambda successfully");
  // console.log("event", JSON.stringify(event));
  // console.log("context", context);

  let data = JSON.parse(event.body);
  console.log("input data", JSON.stringify(data));
  if (!data.user) {
    callback(new Error("Data format error: not found `user`."));
    return;
  }

  let user = data.user;
  let friends;
  let newFriendsList;
  getFriends(user.id)//获得所有好友的ids
      .then(friendsList => friends = friendsList)
      .then(
          () => getCandisByScan(user))//scan+filterExpression获得所有符合filter的用户，只传递回，login, id, 4个crits
      .then((candidates) => removeAddedCandis(candidates, friends, user.id)) //去除已经是好友的人
      .then((candidates) => removeFullFriendsCandidates(candidates)) //去除超过上限的人
      .then(
          (validCandis) => randPickFollowers(validCandis, user.addFollowersNow))//从validCandis中调出xx个
      .then((foUsers) => followAndStore(foUsers, user)) //开始用户互相follow
      .then((newFriends) => newFriendsList = newFriends)
      .then(() => configUpdate(user, event.requestContext.stage))
      .then((newUser) => {
        // console.log("newFriends", newFriends);
        let response = {
          statusCode: 200,
          headers: {"Access-Control-Allow-Origin": "*"},
        };
        console.log("Follow user task is accomplished");
        response.body = JSON.stringify({newFriends: newFriendsList, user: newUser});
        callback(null, response);
      })//follow task is accomplished;
      .catch((err) => {
        console.error("err", err, err.stack);
        callback(new Error("Error found:", err));
      });
};

const handleGetFollowers = (event, context, callback) => {
  let data = JSON.parse(event.body);
  console.log("input data", JSON.stringify(data));
  if (!data.user) {
    callback(new Error("Data format error: not found `user`."));
    return;
  }

  let user = data.user;

  getFriends(user.id)//获得所有好友的ids
      .then(friendsList => {
        // console.log("friendsList", JSON.stringify(friendsList));
        let response = {
          statusCode: 200,
          headers: {"Access-Control-Allow-Origin": "*"},
          body: JSON.stringify(friendsList),
        };
        console.log("Get followers successfully");
        callback(null, response);
      })
      .catch((err) => {
        console.error("err", err, err.stack);
        callback(new Error("Error found:", err));
      });
};

const main = (event, context, callback) => {
  switch (event.path) {
    case "/user/getFollowers":
      handleGetFollowers(event, context, callback);
      break;
    case "user/follow":
      handleFollow(event, context, callback);
      break;
  }
};

module.exports = main;
<commit_msg>Adding more details in dynamo Friends table when user retrieve friends information<commit_after>/**
 * Created by Chen on 2016-12-11.
 */

'use strict';

let AWS = require("aws-sdk");
const config = require('../config');
// AWS.config.update({
//   region: "us-west-2",
//   endpoint: "http://localhost:8000"
// });

let request = require('request');
let docClient = new AWS.DynamoDB.DocumentClient();

/**
 * Get all users' ids from Table UserIds
 * @param {string} userId
 * @returns {Promise}
 */
const getAllUserIds = (userId) => {
  return new Promise((resolve, reject) => {
    let params = {
      TableName: "UserIds",
      Key: {
        id: "allIds"
      },
    };
    // docClient.get()
    docClient.get(params, (err, data) => {
      if (err) {
        console.error("Unable to get userIds. Error JSON:", JSON.stringify(err), err.stack);
        return reject(err);
      } else {
        // console.log("Get user ids successfully", data);
        let userIds = data.Item.usersIds.values,
            candidateIds = userIds.filter(id => id !== userId);
        // console.log("candidateIds", candidateIds);
        console.log("Get all users ID successfully.");
        // console.log("userId", userId);
        // console.log("candidateIds", candidateIds);
        resolve(candidateIds);
      }
    });
  });
};

const randPickFollowers = (validCandis, followNumber) => {
  followNumber = Math.min(followNumber, validCandis.length);
  // console.log("userId", userId);
  // console.log("candidateIds", candidateIds);
  for (let i = 0; i < followNumber; i++) {
    // hardcoded value
    let index = Math.floor(Math.random() * validCandis.length);

    //Swap the two elements
    let temp = validCandis[i];
    validCandis[i] = validCandis[index];
    validCandis[index] = temp;
  }
  let pickedUsers = validCandis.slice(0, followNumber);
  console.log("Picked users successfully"/*, JSON.stringify(pickedUsers)*/);
  return pickedUsers;
};

const getPickedUsers = (pickedIds) => {
  let promises = pickedIds.map(id => {
    return new Promise((resolve, reject) => {
      let params = {
        TableName: "Users",
        Key: {
          id: id
        },
        ProjectionExpression: "id, login, avatar_url, followNumber, html_url, #name, #token",
        ExpressionAttributeNames: {
          "#name": "name",
          "#token": "token"
        },
      };
      docClient.get(params, (err, data) => {
        if (err) {
          console.error("Unable to get item. Error JSON:", JSON.stringify(err), err.stack);
          return reject(err);
        }
        let item = data.Item;
        // console.log("GetItem succeeded:", JSON.stringify(item));
        resolve(item);
      });
    });
  });

  return Promise.all(promises)
      .then((users) => {
        console.log("Fetched all users successfully");
        // console.log("users", users);
        return users;
      });
};

const followAndStore = (users, user) => {
  // console.log("fetched users", users);
  // console.log("original user", user);
  //promise -> send PUT request to follow -> add in "Friends" Table
  let promises = [];

  const followUser = (token, username) => {
    return new Promise((resolve, reject) => {
      let options = {
        url: "https://api.github.com/user/following/" + username + "?access_token=" + token,
        method: "put",
        headers: {
          'User-Agent': "GitMax"
        }
      };

      request(options, (error, response, body) => {
        if (error) {
          console.error("error following user " + username, error, error.stack);
          return reject(error);
        }
        // console.log("Successfully added")
        resolve();
      });
    })
  };

  const storeFriendship = (user1, user2) => {
    return new Promise((resolve, reject) => {
      let item = {
        "userId": user1.id,
        "username": user1.name,
        "userLogin": user1.login,
        "userUrl": user1.html_url,
        "userAvatarUrl": user1.avatar_url,
        "friendId": user2.id,
        "friendName": user2.name,
        "friendLogin": user2.login,
        "friendUrl": user2.html_url,
        "friendAvatarUrl": user2.avatar_url,
      };
      if (user2.bio) item["friendBio"] = user2.bio;
      if (user2.company) item["friendCompany"] = user2.company;
      if (user2.followers) item["friendFollowers"] = user2.followers;
      if (user2.totalStars) item["friendTotalStars"] = user2.totalStars;
      if (user2.location) item["friendLocation"] = user2.location;
      if (user2.email) item["friendEmail"] = user2.email;
      if (user2.created_at) item["friendCreated_at"] = user2.created_at;

      let params = {
        TableName: "Friends",
        Item: item,
      };
      docClient.put(params, (err, data) => {
        if (err) {
          console.error("Unable to add friendship to friends Table. ", JSON.stringify(err),
              err.stack);
          return reject(err);
        } else {
          // console.log("Friendship added successfully");
          resolve(item);
        }
      });
    });
  };

  users.forEach(userX => {
    // console.log("userX", userX);
    // console.log("user", user);
    promises.push(
        followUser(userX.token, user.login)
            .then(() => storeFriendship(userX, user))
            .then(() => userX)
    );
    promises.push(
        followUser(user.token, userX.login)
            .then(() => storeFriendship(user, userX))
            .then(() => null) //users only need to be added once
    );
  });

  function getNeededProps(friend) {
    return {
      id: friend.id,
      url: friend.url,
      avatar_url: friend.avatar_url,
      login: friend.login,
      name: friend.name,
      created_at: friend.created_at,
    }
  }

  return Promise.all(promises)
      .then((result) => {
        console.log("storeFriendship finished"/*, result*/);
        let addedFriends = [];
        result.forEach(friend => friend && addedFriends.push(getNeededProps(friend)));
        return (addedFriends);
      });
};

const getFriends = (userId) => {
  console.log("getFriends start with id", userId);
  const params = {
    TableName: "Friends",
    KeyConditionExpression: "#userId = :id",
    ExpressionAttributeNames: {
      "#userId": "userId"
    },
    ExpressionAttributeValues: {
      ":id": userId
    }
  };
  return new Promise((resolve, reject) => {
    docClient.query(params, (err, data) => {
      if (err) {
        console.error("Unable to get friends. Error JSON:", JSON.stringify(err), err.stack);
        return reject(err);
      } else {
        console.log("Query succeeded."/*, JSON.stringify(data)*/);
        let friends = data.Items;
        resolve(friends);
      }
    });
  });
};

const getCandisByScan = (user) => {
  const params = { // 找到与用户互相欣赏的人，作为好友candidate
    TableName: "Users",
    ProjectionExpression: "#id, #url, created_at, avatar_url, followers, following, login, #name," +
    " #token, addFollowersMax, crit_FollowersCount, crit_StargazersCount, totalStars",
    FilterExpression: "totalStars >= :user_star_crit AND followers >= :user_follower_crit AND" +
    " crit_StargazersCount <= :user_star AND crit_FollowersCount <= :user_follower",
    ExpressionAttributeNames: {
      "#id": "id",
      "#url": "url",
      "#name": "name",
      "#token": "token",
    },
    ExpressionAttributeValues: {
      ":user_star_crit": user.crit_StargazersCount,
      ":user_follower_crit": user.crit_FollowersCount,
      ":user_star": user.totalStars,
      ":user_follower": user.followers,
    }
  };

  return new Promise((resolve, reject) => {
    docClient.scan(params, (err, data) => {
      if (err) {
        console.error("Unable to get item. Error JSON:", JSON.stringify(err), err.stack);
        return reject(err);
      } else {
        console.log("Scan succeeded."/*, JSON.stringify(data)*/);//Todo, comment data
        let candidates = data.Items;
        resolve(candidates);
      }
    });
  });
};

const removeAddedCandis = (candidates, friends, userId) => {
  let set = new Set();
  friends.forEach(obj => set.add(obj.friendId));// Add all friends' Ids in a set
  set.add(userId); // Remove the user from valid candidates

  let validCandis = [];
  candidates.forEach((candidate) => {
    if (!set.has(candidate.id)) validCandis.push(candidate);
  });
  return validCandis;
};

const removeFullFriendsCandidates = (candidates) => {
  let validCandis = [];
  candidates.forEach(candi => {
    if (candi.addFollowersMax > candi.followers) validCandis.push(candi);
  });
  return validCandis;
};

const configUpdate = (user, stage) => {
  // const stage = event.requestContext.stage;
  // const stage = config.getStage();
  user.followedFriendsAt = new Date().getTime();
  const url = config.lambda[stage].configUpdateEndpoint;

  let options = {
    url: url,
    method: "POST",
    body: JSON.stringify({user: user}),
  };

  return new Promise((resolve, reject) => {
    request(options, (error, response, body) => {
      if (error) {
        return reject(error);
      }
      let newUser = JSON.parse(body).user;
      // console.log("response", response);
      // console.log("data", data);
      // console.log("Update followedAt, user:", newUser);
      //DynamoDB不能包含任何empty attributes
      resolve(newUser);
    });
  });
};

const handleFollow = (event, context, callback) => {
  console.log("handleFollow start");;
  // console.log("DynamoDB triggered lambda successfully");
  // console.log("event", JSON.stringify(event));
  // console.log("context", context);

  let data = JSON.parse(event.body);
  console.log("input data", JSON.stringify(data));
  if (!data.user) {
    callback(new Error("Data format error: not found `user`."));
    return;
  }

  let user = data.user;
  let friends;
  let newFriendsList;
  getFriends(user.id)//获得所有好友的ids
      .then(friendsList => friends = friendsList)
      .then(
          () => getCandisByScan(user))//scan+filterExpression获得所有符合filter的用户，只传递回，login, id, 4个crits
      .then((candidates) => removeAddedCandis(candidates, friends, user.id)) //去除已经是好友的人
      .then((candidates) => removeFullFriendsCandidates(candidates)) //去除超过上限的人
      .then(
          (validCandis) => randPickFollowers(validCandis, user.addFollowersNow))//从validCandis中调出xx个
      .then((foUsers) => followAndStore(foUsers, user)) //开始用户互相follow
      .then((newFriends) => newFriendsList = newFriends)
      .then(() => configUpdate(user, event.requestContext.stage))
      .then((newUser) => {
        // console.log("newFriends", newFriends);
        let response = {
          statusCode: 200,
          headers: {"Access-Control-Allow-Origin": "*"},
        };
        console.log("Follow user task is accomplished");
        response.body = JSON.stringify({newFriends: newFriendsList, user: newUser});
        callback(null, response);
      })//follow task is accomplished;
      .catch((err) => {
        console.error("err", err, err.stack);
        callback(new Error("Error found:", err));
      });
};

const handleGetFollowers = (event, context, callback) => {
  console.log("handleGetFollowers start");
  let data = JSON.parse(event.body);
  console.log("input data", JSON.stringify(data));
  if (!data.user) {
    callback(new Error("Data format error: not found `user`."));
    return;
  }

  let user = data.user;

  getFriends(user.id)//获得所有好友的ids
      .then(friendsList => {
        let TruncdFriends = friendsList.map(friend => {
          return {
            friendId: friend.friendId,
            friendName: friend.friendName,
            friendLogin: friend.friendLogin,
            friendUrl: friend.friendUrl,
            friendAvatarUrl: friend.friendAvatarUrl,
            friendBio: friend.friendBio,
            friendCompany: friend.friendCompany,
            friendFollowers: friend.friendFollowers,
            friendTotalStars: friend.friendTotalStars,
            friendLocation: friend.friendLocation,
            friendEmail: friend.friendEmail,
            friendCreated_at: friend.friendCreated_at,
          }
        });
        // console.log("friendsList", JSON.stringify(friendsList));
        let response = {
          statusCode: 200,
          headers: {"Access-Control-Allow-Origin": "*"},
          body: JSON.stringify(TruncdFriends),
        };
        console.log("Get followers successfully");
        callback(null, response);
      })
      .catch((err) => {
        console.error("err", err, err.stack);
        callback(new Error("Error found:", err));
      });
};

const main = (event, context, callback) => {
  switch (event.path) {
    case "/user/getFollowers":
      handleGetFollowers(event, context, callback);
      break;
    case "/user/follow":
      handleFollow(event, context, callback);
      break;
  }
};

module.exports = main;
<|endoftext|>


<commit_before>/*global window, console, d3 */

(function(exports, $){
  "use strict";

  var _data, currentBinAccessor;

  var elbiToUrl = function(elbi){
    return $('#elbi-' + elbi + ' > a').prop('href');
  };

  // dump data into bins according to keyAccessor
  // is this the same as d3.layout .hierarchy?
  var binData = function(data, keyAccessor){
    var bin = {},
        d;
    for (var i = 0; i < data.length; i++){
      d = data[i];
      if (!bin[keyAccessor(d)]) {
        bin[keyAccessor(d)] = [];
      }
      bin[keyAccessor(d)].push(d);
    }
    return bin;
  };

  var nameComparator = function(a, b){
    return b.name < a.name ? 1 : -1;
  };
  var buildingNameComparator = function(a, b){
    return b.name_1 < a.name_1 ? 1 : -1;
  };

  var binAccessors = {
    buildingName: function(d){
      var key = d.name_1[0];
      if ($.isNumeric(key)){
        return "0–9";
      }
      return key.toUpperCase();
    },
    city: function(d){ return d.city; },
    zip: function(d){ return d.zip_code; }
  };

  var prepNameData = function(data){
    var bins = binData(data, currentBinAccessor);

    var binArray = [];
    $.each(bins, function(k, v){
      binArray.push({
        name: k,
        buildings: v.sort(buildingNameComparator)
      });
    });
    binArray = binArray.sort(nameComparator);
    prepBinsHtml(binArray);
  };

  var prepBinsHtml = function(binArray){
    var binContainer = d3.select('#name'), bins, lists, items;
    bins = binContainer.selectAll('.bin').data(binArray);
    bins
      .html(function(d){ return '
' + d.name + ' (' + d.buildings.length + ')
'; });
    bins
      .enter()
        .append('div')
        .attr('class', 'bin span4')
        .html(function(d){ return '
' + d.name + ' (' + d.buildings.length + ')
'; });
    bins
      .exit()
        .remove();
    items = bins.selectAll('.item').data(function(d){ return d.buildings; });
    items
      .text(function(d){ return d.name_1; })
      .style('display', function(d, i){ return i < 10 ? '' : 'none'; });
    items
      .enter()
        .append('div')
        .attr('class', 'item')
        .text(function(d){ return d.name_1; })
        .style('display', function(d, i){ return i < 10 ? '' : 'none'; })
        .on('click', function(d){
          var url = elbiToUrl(d.elbi);
          if (d3.event.ctrlKey){
            // TODO detect middle click
            window.open(url);
          } else {
            window.document.location.href = url;
          }
        });
    items
      .exit()
        .remove();
  };

  var processSearch = function(evt){
      var needle = this.value.toUpperCase(),
          filtered = _data.filter(function(d){ return d.name_1.indexOf(needle) !== -1; });
      console.log(filtered.length);
      if (filtered.length < 10) {
        console.log(filtered);
      }
      prepNameData(filtered);
  };

  var init = function(data){
    window.data = data; // DEBUG
    _data = data;

    var $methods = $('#search-container ul.nav > li');
    $methods.find('a').click(function(e){
      e.preventDefault();
      var $this = $(this);
      $this.parent().addClass('active').siblings().removeClass('active');

      var method = $this.attr('href').split('#')[1];
      currentBinAccessor = binAccessors[method];
      processSearch.call(document.getElementById('q'));
    });
    var method = $methods.filter('.active').find('a').attr('href').split('#')[1];
    currentBinAccessor = binAccessors[method];

    prepNameData(data);

    $('#q').on('keyup', processSearch);
  };
  $.getJSON('/chart/search/data/', init);

})(window, window.jQuery);
<commit_msg>esc clears search<commit_after>/*global window, console, d3 */

(function(exports, $){
  "use strict";

  var _data, currentBinAccessor;

  var elbiToUrl = function(elbi){
    return $('#elbi-' + elbi + ' > a').prop('href');
  };

  // dump data into bins according to keyAccessor
  // is this the same as d3.layout .hierarchy?
  var binData = function(data, keyAccessor){
    var bin = {},
        d;
    for (var i = 0; i < data.length; i++){
      d = data[i];
      if (!bin[keyAccessor(d)]) {
        bin[keyAccessor(d)] = [];
      }
      bin[keyAccessor(d)].push(d);
    }
    return bin;
  };

  var nameComparator = function(a, b){
    return b.name < a.name ? 1 : -1;
  };
  var buildingNameComparator = function(a, b){
    return b.name_1 < a.name_1 ? 1 : -1;
  };

  var binAccessors = {
    buildingName: function(d){
      var key = d.name_1[0];
      if ($.isNumeric(key)){
        return "0–9";
      }
      return key.toUpperCase();
    },
    city: function(d){ return d.city; },
    zip: function(d){ return d.zip_code; }
  };

  var prepNameData = function(data){
    var bins = binData(data, currentBinAccessor);

    var binArray = [];
    $.each(bins, function(k, v){
      binArray.push({
        name: k,
        buildings: v.sort(buildingNameComparator)
      });
    });
    binArray = binArray.sort(nameComparator);
    prepBinsHtml(binArray);
  };

  var prepBinsHtml = function(binArray){
    var binContainer = d3.select('#name'), bins, lists, items;
    bins = binContainer.selectAll('.bin').data(binArray);
    bins
      .html(function(d){ return '
' + d.name + ' (' + d.buildings.length + ')
'; });
    bins
      .enter()
        .append('div')
        .attr('class', 'bin span4')
        .html(function(d){ return '
' + d.name + ' (' + d.buildings.length + ')
'; });
    bins
      .exit()
        .remove();
    items = bins.selectAll('.item').data(function(d){ return d.buildings; });
    items
      .text(function(d){ return d.name_1; })
      .style('display', function(d, i){ return i < 10 ? '' : 'none'; });
    items
      .enter()
        .append('div')
        .attr('class', 'item')
        .text(function(d){ return d.name_1; })
        .style('display', function(d, i){ return i < 10 ? '' : 'none'; })
        .on('click', function(d){
          var url = elbiToUrl(d.elbi);
          if (d3.event.ctrlKey){
            // TODO detect middle click
            window.open(url);
          } else {
            window.document.location.href = url;
          }
        });
    items
      .exit()
        .remove();
  };

  var processSearch = function(evt){
      var needle = this.value.toUpperCase(),
          filtered = _data.filter(function(d){ return d.name_1.indexOf(needle) !== -1; });
      console.log(filtered.length);
      if (filtered.length < 10) {
        console.log(filtered);
      }
      prepNameData(filtered);
  };

  var init = function(data){
    window.data = data; // DEBUG
    _data = data;

    var $methods = $('#search-container ul.nav > li');
    $methods.find('a').click(function(e){
      e.preventDefault();
      var $this = $(this);
      $this.parent().addClass('active').siblings().removeClass('active');

      var method = $this.attr('href').split('#')[1];
      currentBinAccessor = binAccessors[method];
      processSearch.call(document.getElementById('q'));
    });
    var method = $methods.filter('.active').find('a').attr('href').split('#')[1];
    currentBinAccessor = binAccessors[method];

    prepNameData(data);

    $('#q')
      .on('keydown', function(e){ if (e.which == 27) { this.value = ''; }})
      .on('keyup', processSearch);
  };
  $.getJSON('/chart/search/data/', init);

})(window, window.jQuery);
<|endoftext|>


<commit_before>'use strict';

var config = require('./config/config'),
    promise = require('bluebird'),
    debug = require('debug')('photobooth'),
    fs = require('fs'),
    gcloud = require('gcloud')({
      projectId: config.google.project,
      keyFilename: config.google.keyFilename,
    }),
    gm = require('gm'),
    moment = require('moment'),
    request = require('request-promise'),
    tmp = require('tmp');

function joinImages(photos) {
  var deferred = promise.pending();
  tmp.tmpName(function(err, path) {
    var destPath = path + '.jpg';
    debug('Writing montage to ' + destPath);

    // Join them together in a vertical strip with 10px padding and resize to
    // max width of 1024.  I had to use the manually constructed command because
    // otherwise .geometry() is added after input arguments and doesn't actually
    // set the maximum tile size.
    gm(photos[3]).command('montage')
      .in('-geometry')
      .in('+10+10')
      .in('-tile')
      .in('2x2')
      .in(photos[0])
      .in(photos[1])
      .in(photos[2])
      .write(destPath, function() {
        deferred.resolve(destPath);
      });
  });
  return deferred.promise;
}

function uploadToStorage(path) {
  debug('Uploading to cloud storage.');
  var deferred = promise.pending();
  var gcs = gcloud.storage();
  var bucket = gcs.bucket(config.google.storageBucket);

  var filename = 'photobooth-' +
      moment().format('YYYYMMDD-hhmmss-SSS' + '.jpg');
  var options = {
    destination: filename
  };
  bucket.upload(path, options, function(err, file) {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve({bucket: file.metadata.bucket,
                        name: file.metadata.name});
    }
  });
  return deferred.promise;
}

function recordInFrontend(storageInfo) {
  debug('Recording in frontend.');
  if (config.eventId) {
    storageInfo.event = config.eventId;
  }
  return request.post(config.frontend.host + '/photos').form(storageInfo)
    .then(JSON.parse);
}

function printReceipt(photo) {
  debug('Printing receipt.');
  var url = config.frontend.host + '/' + photo._id;
  console.info('Photo available at: ' + url);

  // TODO: Print receipt
  return promise.resolve(url);
}

var Photobooth = function(panel, camera, ui, server) {
  var pendingInterval,
      pendingTimeout,
      pendingPromise,
      previewHandle,
      photos = [],
      currentState = '',
      finishedPhoto;

  function handleError(err) {
    console.trace(err);
    error();
  }

  function resetPhotos() {
    if (photos.length) {
      photos.forEach(function(path) {
        fs.unlink(path);
      });
      photos = [];
    }

    if (finishedPhoto) {
      fs.unlink(finishedPhoto);
      finishedPhoto = null;
    }
  }

  function transition(newState) {
    debug('Transitioning: ' + currentState + ' -> ' + newState);
    if (pendingTimeout) {
      clearTimeout(pendingTimeout);
    }
    if (pendingInterval) {
      clearInterval(pendingInterval);
    }
    if (pendingPromise) {
      pendingPromise.cancel();
    }
    panel.removeAllListeners('activate');
    currentState = newState;
  }

  function cleanup() {
    resetPhotos();
    if (previewHandle) {
      previewHandle.close();
    }
  }

  // State -> idle
  function idle() {
    transition('idle');
    cleanup();
    ui.idle();
    panel.on('activate', preview);
  }

  // State -> preview
  function preview() {
    transition('preview');
    resetPhotos();
    panel.on('activate', pending);

    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);
    ui.preview();

    // Timeout back to idle after 2 minutes.
    pendingTimeout = setTimeout(idle, 1000*60*2);  // 2 minutes.
  }

  // State -> pending
  function pending() {
    transition('pending');
    var secondsRemaining = config.countdown;
    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);

    pendingPromise = previewHandle.ready;
    previewHandle.ready.then(function() {
      // Initialize UI and then count down to zero.
      ui.pending(secondsRemaining, photos);
      function tick() {
        --secondsRemaining;
        ui.pending(secondsRemaining, photos);
        if (secondsRemaining > 1) {
          pendingTimeout = setTimeout(tick, 1000);
        } else {
          capture();
        }
      }
      pendingTimeout = setTimeout(tick, 1000);
    }, error);
  }

  // State -> capture
  function capture() {
    transition('capture');
    pendingPromise = camera.reset()
      .tap(function() {
        ui.capture(photos);
      })
      .then(camera.takePhoto.bind(camera))
      .then(function(photo) {
        photos.push(photo);
        if (photos.length == 4) {
          // Make sure the new photo is shown.
          ui.capture(photos);
          processing();
        } else {
          // Make sure the new photo is shown.
          ui.capture(photos);
          pending();
        }
      }, handleError);
  }

  // State -> processing
  function processing() {
    transition('processing');
    ui.processing(photos, "Composing images");
    pendingPromise = joinImages(photos)
      .then(function(path) {
        finishedPhoto = path;
        return promise.resolve(path);
      })
      .tap(finished)
      .then(uploadToStorage)
      .then(recordInFrontend)
      .then(printReceipt)
      .then(null, handleError);
  }

  // State -> error
  function error() {
    transition('error');
    cleanup();
    ui.error();
    pendingTimeout = setTimeout(idle, 1000*60*1);
  }

  // State -> finished
  function finished(url) {
    transition('finished');
    ui.finished(finishedPhoto, url);
    pendingTimeout = setTimeout(idle, 1000*60*1);
    panel.on('activate', preview);
  }

  this.run = function() {
    transition('init');
    panel.on('reset', idle);
    setImmediate(idle);
  };
};
module.exports = Photobooth;
<commit_msg>Create a thumbnail and store it.<commit_after>'use strict';

var config = require('./config/config'),
    promise = require('bluebird'),
    debug = require('debug')('photobooth'),
    fs = require('fs'),
    gcloud = require('gcloud')({
      projectId: config.google.project,
      keyFilename: config.google.keyFilename,
    }),
    gm = require('gm'),
    moment = require('moment'),
    request = require('request-promise'),
    tmp = require('tmp');

function joinImages(photos) {
  var deferred = promise.pending();
  tmp.tmpName(function(err, path) {
    var destPath = path + '.jpg';
    debug('Writing montage to ' + destPath);

    // Join them together in a vertical strip with 10px padding and resize to
    // max width of 1024.  I had to use the manually constructed command because
    // otherwise .geometry() is added after input arguments and doesn't actually
    // set the maximum tile size.
    gm(photos[3]).command('montage')
      .in('-geometry')
      .in('+10+10')
      .in('-tile')
      .in('2x2')
      .in(photos[0])
      .in(photos[1])
      .in(photos[2])
      .write(destPath, function() {
        deferred.resolve(destPath);
      });
  });
  return deferred.promise;
}

function uploadOriginal(path) {
  debug('Uploading to cloud storage.');
  var deferred = promise.pending();
  var gcs = gcloud.storage();
  var bucket = gcs.bucket(config.google.storageBucket);

  var filename = 'photobooth-' +
      moment().format('YYYYMMDD-hhmmss-SSS' + '.jpg');
  var options = {
    destination: filename
  };
  bucket.upload(path, options, function(err, file) {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve({src: path,
                        original: filename,
                        thumb: undefined});
    }
  });
  return deferred.promise;
}

function uploadThumbnail(photo) {
  debug('Creating and uploading thumbnail to cloud storage.');
  var deferred = promise.pending();
  var gcs = gcloud.storage();
  var bucket = gcs.bucket(config.google.storageBucket);

  var destName = photo.original.split('.')[0] + '-thumb.jpg';
  var writeStream = bucket.file(destName).createWriteStream();

  writeStream.on('finish', function() {
    deferred.resolve({bucket: config.google.storageBucket,
                      name: photo.original,
                      thumb: {
                        bucket: config.google.storageBucket,
                        name: destName}});
  });

  gm(photo.src)
    .resize(300, 200)
    .stream(function(err, stdout, stderr) {
      if (err) deferred.reject(err);
      stdout.pipe(writeStream);
    });

  return deferred.promise;
}

function recordInFrontend(photo) {
  debug('Recording in frontend.');
  if (config.eventId) {
    photo.event = config.eventId;
  }
  return request.post(config.frontend.host + '/photos').form(photo)
    .then(JSON.parse);
}

function printReceipt(photo) {
  debug('Printing receipt.');
  var url = config.frontend.host + '/' + photo._id;
  console.info('Photo available at: ' + url);

  // TODO: Print receipt
  return promise.resolve(url);
}

var Photobooth = function(panel, camera, ui, server) {
  var pendingInterval,
      pendingTimeout,
      pendingPromise,
      previewHandle,
      photos = [],
      currentState = '',
      finishedPhoto;

  function handleError(err) {
    console.trace(err);
    error();
  }

  function resetPhotos() {
    if (photos.length) {
      photos.forEach(function(path) {
        fs.unlink(path);
      });
      photos = [];
    }

    if (finishedPhoto) {
      fs.unlink(finishedPhoto);
      finishedPhoto = null;
    }
  }

  function transition(newState) {
    debug('Transitioning: ' + currentState + ' -> ' + newState);
    if (pendingTimeout) {
      clearTimeout(pendingTimeout);
    }
    if (pendingInterval) {
      clearInterval(pendingInterval);
    }
    if (pendingPromise) {
      pendingPromise.cancel();
    }
    panel.removeAllListeners('activate');
    currentState = newState;
  }

  function cleanup() {
    resetPhotos();
    if (previewHandle) {
      previewHandle.close();
    }
  }

  // State -> idle
  function idle() {
    transition('idle');
    cleanup();
    ui.idle();
    panel.on('activate', preview);
  }

  // State -> preview
  function preview() {
    transition('preview');
    resetPhotos();
    panel.on('activate', pending);

    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);
    ui.preview();

    // Timeout back to idle after 2 minutes.
    pendingTimeout = setTimeout(idle, 1000*60*2);  // 2 minutes.
  }

  // State -> pending
  function pending() {
    transition('pending');
    var secondsRemaining = config.countdown;
    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);

    pendingPromise = previewHandle.ready;
    previewHandle.ready.then(function() {
      // Initialize UI and then count down to zero.
      ui.pending(secondsRemaining, photos);
      function tick() {
        --secondsRemaining;
        ui.pending(secondsRemaining, photos);
        if (secondsRemaining > 1) {
          pendingTimeout = setTimeout(tick, 1000);
        } else {
          capture();
        }
      }
      pendingTimeout = setTimeout(tick, 1000);
    }, error);
  }

  // State -> capture
  function capture() {
    transition('capture');
    pendingPromise = camera.reset()
      .tap(function() {
        ui.capture(photos);
      })
      .then(camera.takePhoto.bind(camera))
      .then(function(photo) {
        photos.push(photo);
        if (photos.length == 4) {
          // Make sure the new photo is shown.
          ui.capture(photos);
          processing();
        } else {
          // Make sure the new photo is shown.
          ui.capture(photos);
          pending();
        }
      }, handleError);
  }

  // State -> processing
  function processing() {
    transition('processing');
    ui.processing(photos, "Composing images");
    pendingPromise = joinImages(photos)
      .then(function(path) {
        finishedPhoto = path;
        return promise.resolve(path);
      })
      .tap(finished)
      .then(uploadOriginal)
      .then(uploadThumbnail)
      .then(recordInFrontend)
      .then(printReceipt)
      .then(null, handleError);
  }

  // State -> error
  function error() {
    transition('error');
    cleanup();
    ui.error();
    pendingTimeout = setTimeout(idle, 1000*60*1);
  }

  // State -> finished
  function finished(url) {
    transition('finished');
    ui.finished(finishedPhoto, url);
    pendingTimeout = setTimeout(idle, 1000*60*1);
    panel.on('activate', preview);
  }

  this.run = function() {
    transition('init');
    panel.on('reset', idle);
    setImmediate(idle);
  };
};
module.exports = Photobooth;
<|endoftext|>


<commit_before>import React from 'react';
import { Formik, Field, ErrorMessage } from 'formik';
import { Debug } from './Debug';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const required = value => (value ? undefined : 'Required');

class Wizard extends React.Component {
  static Page = ({ children }) => children;

  constructor(props) {
    super(props);
    this.state = {
      page: 0,
      values: props.initialValues,
    };
  }

  next = values =>
    this.setState(state => ({
      page: Math.min(state.page + 1, this.props.children.length - 1),
      values,
    }));

  previous = () =>
    this.setState(state => ({
      page: Math.max(state.page - 1, 0),
    }));

  validate = values => {
    const activePage = React.Children.toArray(this.props.children)[
      this.state.page
    ];
    return activePage.props.validate ? activePage.props.validate(values) : {};
  };

  handleSubmit = (values, bag) => {
    const { children, onSubmit } = this.props;
    const { page } = this.state;
    const isLastPage = page === React.Children.count(children) - 1;
    if (isLastPage) {
      return onSubmit(values, bag);
    } else {
      bag.setTouched({});
      bag.setSubmitting(false);
      this.next(values);
    }
  };

  render() {
    const { children } = this.props;
    const { page, values } = this.state;
    const activePage = React.Children.toArray(children)[page];
    const isLastPage = page === React.Children.count(children) - 1;
    return (
      <Formik
        initialValues={values}
        enableReinitialize={false}
        validate={this.validate}
        onSubmit={this.handleSubmit}
        render={({ values, handleSubmit, isSubmitting, handleReset }) => (
          <form onSubmit={handleSubmit}>
            {activePage}

              {page > 0 && (
                <button
                  type="button"
                  className="secondary"
                  onClick={this.previous}
                >
                  « Previous
                </button>
              )}

              {!isLastPage && <button type="submit">Next »</button>}
              {isLastPage && (
                <button type="submit" disabled={isSubmitting}>
                  Submit
                </button>
              )}

            <Debug />
          </form>
        )}
      />
    );
  }
}

const App = () => (

Multistep / Form Wizard

    <Wizard
      initialValues={{
        firstName: '',
        lastName: '',
        email: '',
        favoriteColor: '',
      }}
      onSubmit={(values, actions) => {
        sleep(300).then(() => {
          window.alert(JSON.stringify(values, null, 2));
          actions.setSubmitting(false);
        });
      }}
    >
      <Wizard.Page>

          First Name
          <Field
            name="firstName"
            component="input"
            type="text"
            placeholder="First Name"
            validate={required}
          />
          <ErrorMessage
            name="firstName"
            component="div"
            className="field-error"
          />

          Last Name
          <Field
            name="lastName"
            component="input"
            type="text"
            placeholder="Last Name"
            validate={required}
          />
          <ErrorMessage
            name="lastName"
            component="div"
            className="field-error"
          />

      </Wizard.Page>
      <Wizard.Page
        validate={values => {
          const errors = {};
          if (!values.email) {
            errors.email = 'Required';
          }
          if (!values.favoriteColor) {
            errors.favoriteColor = 'Required';
          }
          return errors;
        }}
      >

          Email
          <Field
            name="email"
            component="input"
            type="email"
            placeholder="Email"
          />
          <ErrorMessage name="email" component="div" className="field-error" />

          Favorite Color
          <Field name="favoriteColor" component="select">
            <option value="">Select a Color</option>
            <option value="#ff0000">❤ Red</option>
            <option value="#00ff00">💚 Green</option>
            <option value="#0000ff">💙 Blue</option>
          </Field>
          <ErrorMessage
            name="favoriteColor"
            component="div"
            className="field-error"
          />

      </Wizard.Page>
    </Wizard>

);

export default App;
<commit_msg>Rewrite MultistepWizard example (#2464)<commit_after>import React, { useState } from "react";
import { ErrorMessage, Field, Form, Formik } from "formik";
import { render } from "react-dom";
import * as Yup from "yup";
import { Debug } from './Debug';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// Wizard is a single Formik instance whose children are each page of the
// multi-step form. The form is submitted on each forward transition (can only
// progress with valid input), whereas a backwards step is allowed with
// incomplete data. A snapshot of form state is used as initialValues after each
// transition. Each page has an optional submit handler, and the top-level
// submit is called when the final page is submitted.
const Wizard = ({ children, initialValues, onSubmit }) => {
  const [stepNumber, setStepNumber] = useState(0);
  const steps = React.Children.toArray(children);
  const [snapshot, setSnapshot] = useState(initialValues);

  const step = steps[stepNumber];
  const totalSteps = steps.length;
  const isLastStep = stepNumber === totalSteps - 1;

  const next = values => {
    setSnapshot(values);
    setStepNumber(Math.min(stepNumber + 1, totalSteps - 1));
  };

  const previous = values => {
    setSnapshot(values);
    setStepNumber(Math.max(stepNumber - 1, 0));
  };

  const handleSubmit = async (values, bag) => {
    if (step.props.onSubmit) {
      await step.props.onSubmit(values, bag);
    }
    if (isLastStep) {
      return onSubmit(values, bag);
    } else {
      bag.setTouched({});
      next(values);
    }
  };

  return (
    <Formik
      initialValues={snapshot}
      onSubmit={handleSubmit}
      validationSchema={step.props.validationSchema}
    >
      {formik => (
        <Form>

            Step {stepNumber + 1} of {totalSteps}

          {step}

            {stepNumber > 0 && (
              <button onClick={() => previous(formik.values)} type="button">
                Back
              </button>
            )}

              <button disabled={formik.isSubmitting} type="submit">
                {isLastStep ? "Submit" : "Next"}
              </button>

          <Debug/>
        </Form>
      )}
    </Formik>
  );
};

const WizardStep = ({ children }) => children;

const App = () => (

Formik Multistep Wizard

    <Wizard
      initialValues={{
        email: "",
        firstName: "",
        lastName: ""
      }}
      onSubmit={async values =>
        sleep(300).then(() => console.log("Wizard submit", values))
      }
    >
      <WizardStep
        onSubmit={() => console.log("Step1 onSubmit")}
        validationSchema={Yup.object({
          firstName: Yup.string().required("required"),
          lastName: Yup.string().required("required")
        })}
      >

          First Name
          <Field
            autoComplete="given-name"
            component="input"
            id="firstName"
            name="firstName"
            placeholder="First Name"
            type="text"
          />
          <ErrorMessage className="error" component="div" name="firstName" />

          Last Name
          <Field
            autoComplete="family-name"
            component="input"
            id="lastName"
            name="lastName"
            placeholder="Last Name"
            type="text"
          />
          <ErrorMessage className="error" component="div" name="lastName" />

      </WizardStep>
      <WizardStep
        onSubmit={() => console.log("Step2 onSubmit")}
        validationSchema={Yup.object({
          email: Yup.string()
            .email("Invalid email address")
            .required("required")
        })}
      >

          Email
          <Field
            autoComplete="email"
            component="input"
            id="email"
            name="email"
            placeholder="Email"
            type="text"
          />
          <ErrorMessage className="error" component="div" name="email" />

      </WizardStep>
    </Wizard>

);

export default App;
<|endoftext|>


<commit_before>const {{pascalcase name}}Component = require('./lib/components');
const {{pascalcase name}}Actions = require('./lib/actions');
const {{pascalcase name}}Store = require('./lib/stores');

/**
 * A sample role for the component.
 */
const ROLE = {
  name: "{{ pascalcase name }}",
  component: {{pascalcase name}}Component
}

/**
 * Activate all the components in the {{capitalcase name}} package.
 */
function activate() {
  // Register the {{pascalcase name}}Component as a collection tab Role in Compass
  //
  // Note that available roles are:
  //   - Collection.Tab
  //   - CollectionStats.CollectionStatsItem
  //   - Database.Tab
  //   - Instance.Tab
  //
  global.hadronApp.appRegistry.registerRole('Collection.Tab', ROLE);

  global.hadronApp.appRegistry.registerComponent('{{pascalcase scope}}:{{pascalcase name}}', {{pascalcase name}}Component);
  global.hadronApp.appRegistry.registerAction('{{pascalcase name}}Actions', {{pascalcase name}}Actions);
  global.hadronApp.appRegistry.registerStore('{{pascalcase name}}Store', {{pascalcase name}}Store);
}

/**
 * Deactivate all the components in the {{capitalcase name}} package.
 */
function deactivate() {
  global.hadronApp.appRegistry.deregisterRole('Collection.Tab', ROLE);

  global.hadronApp.appRegistry.deregisterComponent('{{pascalcase scope}}:{{pascalcase name}}');
  global.hadronApp.appRegistry.deregisterAction('{{pascalcase name}}Actions');
  global.hadronApp.appRegistry.deregisterStore('{{pascalcase name}}Store');
}

module.exports = {{pascalcase name}}Component;
module.exports.activate = activate;
module.exports.deactivate = deactivate;
<commit_msg>Adding role<commit_after>const {{pascalcase name}}Component = require('./lib/components');
const {{pascalcase name}}Actions = require('./lib/actions');
const {{pascalcase name}}Store = require('./lib/stores');

/**
 * A sample role for the component.
 */
const ROLE = {
  name: "{{ pascalcase name }}",
  component: {{pascalcase name}}Component
}

/**
 * Activate all the components in the {{capitalcase name}} package.
 */
function activate() {
  // Register the {{pascalcase name}}Component as a collection tab Role in Compass
  //
  // Note that available roles are:
  //   - Collection.Tab
  //   - CollectionStats.CollectionStatsItem
  //   - Database.Tab
  //   - Instance.Tab
  //
  global.hadronApp.appRegistry.registerRole('{{role}}', ROLE);
  global.hadronApp.appRegistry.registerAction('{{pascalcase name}}Actions', {{pascalcase name}}Actions);
  global.hadronApp.appRegistry.registerStore('{{pascalcase name}}Store', {{pascalcase name}}Store);
}

/**
 * Deactivate all the components in the {{capitalcase name}} package.
 */
function deactivate() {
  global.hadronApp.appRegistry.deregisterRole('{{role}}', ROLE);
  global.hadronApp.appRegistry.deregisterAction('{{pascalcase name}}Actions');
  global.hadronApp.appRegistry.deregisterStore('{{pascalcase name}}Store');
}

module.exports = {{pascalcase name}}Component;
module.exports.activate = activate;
module.exports.deactivate = deactivate;
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Eka's Portal Disinterest Filter
// @namespace    http://zcxv.com/
// @version      0.2
// @description  Filter out artists you don't like on Eka's Portal.
// @author       Kiri Nakatomi aka WHTB
// @match        http://aryion.com/g4/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    /**
     * Contains if debug is enabled
     *
     * @type {boolean} - true debug is enabled else false
     */
    var debug = true;

    /**
     * Contains all users we have blocked
     *
     * @type {Array} - All blocked Users
     */
    var badUserList = [];

    /**
     * Keep track of which users we've actually hidden, so we can display unblock buttons for them.
     *
     * @type {Array} - Currently hidden Users
     */
    var currentUserHiddenList = [];

    /**
     * Resets the current Block User List
     */
    function resetCurrentBlockUser() {
        currentUserHiddenList = [];
    }

    /**
     * Save the bad user list to local storage.
     */
    function saveData()
    {
        localStorage.setItem("whtb-blocklist", badUserList.join());
    }

    /**
     * Load the bad user list from local storage.
     */
    function loadData()
    {
        var loadedList = localStorage.getItem("whtb-blocklist");
        logAdd('Load Block-List');

        // Handle if list doesn't exists
        if(loadedList === null)
            return;

        badUserList = loadedList.split(",");

        // Show Loaded User in Log
        for(var i = 0; i < badUserList.length; i++)
            logAdd("Loaded bad user: " + badUserList[i]);
    }

    /**
     * Block a user by name.
     *
     * @param {string} username - Username to block
     */
    function blockUser(username)
    {
        // Check if User is already in list
        if(badUserList.indexOf(username) != -1) {
            refreshPage(); // Reload to remove wrong buttons that may cause this case
            return;
        }

        // Add User and save
        badUserList.push(username);
        refreshPage();
        saveData();
    }

    /**
     * Unblock a user by name.
     *
     * @param {string} username - Username to unblock
     */
    function unblockUser(username)
    {
        var index = badUserList.indexOf(username);

        // Check if User is in list
        if(index == -1) {
            refreshPage(); // Reload to remove wrong buttons that may cause this case
            return;
        }

        badUserList.splice(index, 1);
        refreshPage();
        saveData();
    }

    /**
     * Detect if the haystack starts with needle this function is case insensitive
     *
     * @param {string} haystack - String to check
     * @param {string} needle - Start string on haystack
     * @returns {boolean} - true if haystack starts with needle
     */
    function stringStartWith(haystack, needle)
    {
        return haystack.toLowerCase().indexOf(needle.toLowerCase()) === 0;
    }

    /**
     * Displays a message on the console if debug is enabled
     *
     * @param {string} message - Message to add
     */
    function logAdd(message) {
        if(debug)
            console.log(message);
    }

    /**
     * Creates a UnBlock button with assigned OnClick function
     *
     * @param {string} username - Username of the UnBlock-User for this Button
     * @returns {Element} - UnBlock-Button
     */
    function createUnBlockButton(username)
    {
        var restoreButton = document.createElement('BUTTON');
        restoreButton.innerHTML = username;

        /**
         * Adds the unblockUser function to this Button
         */
        restoreButton.onclick = function()
        {
            if(confirm('Do you really wan\'t unblock ' + username + '?'))
                unblockUser(username);
        };

        return restoreButton;
    }

    /**
     * Creates a Block button with assigned OnClick function
     *
     * @param {string} username - Username of the Block-User for this Button
     * @returns {Element} - BlockButton
     */
    function createBlockButton(username)
    {
        var hideButton = document.createElement('BUTTON');
        hideButton.innerHTML = 'Block ' + username;
        hideButton.className = 'whtb-block-button';

        /**
         * Adds the blockUser function to this Button
         */
        hideButton.onclick = function()
        {
            if(confirm('Are you sure to block ' + username + '?'))
                blockUser(username);
        };

        return hideButton;
    }

    /**
     * Refresh OUR data on the page. (Doesn't cause an actual page request.)
     */
    function refreshPage()
    {
        logAdd('Refresh Page...');
        resetCurrentBlockUser();

        // Check the function we need to build or stuff in use the <title> content to check
        if(stringStartWith(document.title, 'g4 :: Latest Updates'))
            refreshG4LatestUpdates();

        if(stringStartWith(document.title, 'g4 :: Messages'))
            refreshG4Messages();

        if(stringStartWith(document.title, 'g4 :: Tagged'))
            refreshG4Tagged();

        if(stringStartWith(document.title, 'g4 :: Search Results'))
            refreshG4Search();
    }

    /**
     * todo doc
     */
    function refreshG4Search()
    {
        logAdd('Function: refreshG4Search()');
        // todo implement
    }

    /**
     * todo doc
     */
    function refreshG4Tagged()
    {
        logAdd('Function: refreshG4Tagged()');
        // todo implement
    }

    /**
     * todo doc
     */
    function refreshG4Messages()
    {
        logAdd('Function: refreshG4Messages()');
        // todo implement
    }

    /**
     * Refreshes the Lastest Update Site
     */
    function refreshG4LatestUpdates()
    {
        logAdd('Function: refreshG4LatestUpdates()');

        // Handle the g4/latest.php page with this.
        var galleryBox = document.getElementsByClassName("g-box-contents");
        var i;

        if(galleryBox.length > 0) {
            galleryBox = galleryBox[0];

            // Create or find the existing unblock button box, then clear it out so we can rebuild it.
            var unblockButtonBox = document.getElementsByClassName("whtb-unblock-box");
            if(unblockButtonBox.length === 0) {
                unblockButtonBox = document.createElement("div");
                unblockButtonBox.className = "whtb-unblock-box";
                galleryBox.insertBefore(unblockButtonBox, galleryBox.firstChild);
            } else
                unblockButtonBox = unblockButtonBox[0];

            unblockButtonBox.innerHTML = "Unblock buttons: ";

            // Clear out existing block buttons from the last iteration.
            var existingBlockButtons = document.getElementsByClassName("whtb-block-button");
            while(existingBlockButtons.length > 0)
                existingBlockButtons[0].parentElement.removeChild(existingBlockButtons[0]);

            // Iterate over galley entries.
            var items = galleryBox.getElementsByClassName("detail-item");
            for(i = 0; i < items.length; i++) {

                // We'll just assume that the first user link is the user that posted it.
                // Be careful, because this can also point to comments made by users.
                var userLink = items[i].getElementsByClassName("user-link");
                if(userLink.length > 0) {
                    userLink = userLink[0];

                    var username = userLink.innerHTML;

                    if(badUserList.indexOf(username) != -1) {
                        // Found someone we want to block. Hide the element and add to our
                        // list of unblock buttons.
                        items[i].style.display = "none";

                        if(currentUserHiddenList.indexOf(username) == -1)
                            currentUserHiddenList.push(username);
                    } else {
                        // This user is fine, but just in case we want to block them, we
                        // better add a block button. We could also be coming in from an
                        // unblock command, so we need to reset the visibility.
                        items[i].style.display = "";

                        // Set up the block button.
                        var hideButton = createBlockButton(username);

                        // Stick this right next to the username.
                        userLink.parentElement.insertBefore(hideButton, userLink.nextSibling);
                    }
                }
            }

            // Generate the "Unblock button" list at the top.
            currentUserHiddenList.sort();
            for(i = 0; i < currentUserHiddenList.length; i++) {
                var restoreButton = createUnBlockButton(currentUserHiddenList[i]);
                unblockButtonBox.appendChild(restoreButton);
            }
        }
    }

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

    // Loads settings
    loadData();

    // Now just do an initial refresh to show our optional stuff.
    refreshPage();
})();
<commit_msg>Simplyfied refreshG4LatestUpdates function and add global unblock list<commit_after>// ==UserScript==
// @name         Eka's Portal Disinterest Filter
// @namespace    http://zcxv.com/
// @version      0.2
// @description  Filter out artists you don't like on Eka's Portal.
// @author       Kiri Nakatomi aka WHTB
// @match        http://aryion.com/g4/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    /**
     * Contains if debug is enabled
     *
     * @type {boolean} - true debug is enabled else false
     */
    var debug = true;

    /**
     * Contains all users we have blocked
     *
     * @type {Array} - All blocked Users
     */
    var badUserList = [];

    /**
     * Keep track of which users we've actually hidden, so we can display unblock buttons for them.
     *
     * @type {Array} - Currently hidden Users
     */
    var currentUserHiddenList = [];

    /**
     * Resets the current Block User List
     */
    function resetCurrentBlockUser() {
        currentUserHiddenList = [];
    }

    /**
     * Save the bad user list to local storage.
     */
    function saveData()
    {
        localStorage.setItem('whtb-blocklist', badUserList.join());
    }

    /**
     * Load the bad user list from local storage.
     */
    function loadData()
    {
        var loadedList = localStorage.getItem('whtb-blocklist');
        logAdd('Load Block-List');

        // Handle if list doesn't exists
        if(loadedList === null)
            return;

        badUserList = loadedList.split(',');

        // Show Loaded User in Log
        for(var i = 0; i < badUserList.length; i++)
            logAdd('Loaded bad user: ' + badUserList[i]);
    }

    /**
     * Block a user by name.
     *
     * @param {string} username - Username to block
     */
    function blockUser(username)
    {
        // Check if User is already in list
        if(badUserList.indexOf(username) != -1) {
            refreshPage(); // Reload to remove wrong buttons that may cause this case
            return;
        }

        // Add User and save
        badUserList.push(username);
        refreshPage();
        saveData();
    }

    /**
     * Unblock a user by name.
     *
     * @param {string} username - Username to unblock
     */
    function unblockUser(username)
    {
        var index = badUserList.indexOf(username);

        // Check if User is in list
        if(index == -1) {
            refreshPage(); // Reload to remove wrong buttons that may cause this case
            return;
        }

        badUserList.splice(index, 1);
        refreshPage();
        saveData();
    }

    /**
     * Detect if the haystack starts with needle this function is case insensitive
     *
     * @param {string} haystack - String to check
     * @param {string} needle - Start string on haystack
     * @returns {boolean} - true if haystack starts with needle
     */
    function stringStartWith(haystack, needle)
    {
        return haystack.toLowerCase().indexOf(needle.toLowerCase()) === 0;
    }

    /**
     * Displays a message on the console if debug is enabled
     *
     * @param {string} message - Message to add
     */
    function logAdd(message) {
        if(debug)
            console.log(message);
    }

    /**
     * Creates a UnBlock button with assigned OnClick function
     *
     * @param {string} username - Username of the UnBlock-User for this Button
     * @returns {Element} - UnBlock-Button
     */
    function createUnBlockButton(username)
    {
        var restoreButton = document.createElement('BUTTON');
        restoreButton.innerHTML = username;

        /**
         * Adds the unblockUser function to this Button
         */
        restoreButton.onclick = function()
        {
            if(confirm('Do you really wan\'t unblock ' + username + '?'))
                unblockUser(username);
        };

        return restoreButton;
    }

    /**
     * Creates a Block button with assigned OnClick function
     *
     * @param {string} username - Username of the Block-User for this Button
     * @returns {Element} - BlockButton
     */
    function createBlockButton(username)
    {
        var hideButton = document.createElement('BUTTON');
        hideButton.innerHTML = 'Block ' + username;
        hideButton.className = 'whtb-block-button';

        /**
         * Adds the blockUser function to this Button
         */
        hideButton.onclick = function()
        {
            if(confirm('Are you sure to block ' + username + '?'))
                blockUser(username);
        };

        return hideButton;
    }

    /**
     * Check if a unlock Button-container is available if not create it
     *
     * @param {string} className - Class Name of the unlock Button-Container
     * @param {Element} insertBefore - The element where to place the Button-Container(before element)
     * @returns {Element} - The unlock Button-Container
     */
    function unlockButtonContainer(className, insertBefore) {
        var unblockButtonBox = document.getElementsByClassName(className);

        if(unblockButtonBox.length == 0) {
            var newUnblockButtonBox = document.createElement('div');
            newUnblockButtonBox.className = className;
            insertBefore.insertBefore(newUnblockButtonBox, insertBefore.firstChild);

            return newUnblockButtonBox;
        }

        return unblockButtonBox[0];
    }

    /**
     * Creates UnBlock-Buttons from User Array
     *
     * @param {Array} userArray - Array with User Names
     * @param {Element} addToEl - Element where the Buttons go as child
     */
    function createUnblockButtonListFromArray(userArray, addToEl) {
        // Sort by ABC
        userArray.sort();

        for(var i = 0; i < userArray.length; i++) {
            if(userArray[i]) { // Handling empty spots in some arrays...
                var restoreButton = createUnBlockButton(userArray[i]);
                addToEl.appendChild(restoreButton);
            }
        }
    }

    /**
     * Remove all existing Buttons with the specified class name
     *
     * @param {string} className - Class Name of the Button(s)
     */
    function removeExistingButtons(className) {
        var existingButtons = document.getElementsByClassName(className);

        while(existingButtons.length > 0)
            existingButtons[0].parentNode.removeChild(existingButtons[0]);
    }

    /**
     * Refresh OUR data on the page. (Doesn't cause an actual page request.)
     */
    function refreshPage()
    {
        logAdd('Refresh Page...');
        resetCurrentBlockUser();

        // Check the function we need to build or stuff in use the <title> content to check
        if(stringStartWith(document.title, 'g4 :: Latest Updates'))
            refreshG4LatestUpdates();

        if(stringStartWith(document.title, 'g4 :: Messages'))
            refreshG4Messages();

        if(stringStartWith(document.title, 'g4 :: Tagged'))
            refreshG4Tagged();

        if(stringStartWith(document.title, 'g4 :: Search Results'))
            refreshG4Search();
    }

    /**
     * todo doc
     */
    function refreshG4Search()
    {
        logAdd('Function: refreshG4Search()');
        // todo implement
    }

    /**
     * todo doc
     */
    function refreshG4Tagged()
    {
        logAdd('Function: refreshG4Tagged()');
        // todo implement
    }

    /**
     * todo doc
     */
    function refreshG4Messages()
    {
        logAdd('Function: refreshG4Messages()');
        // todo implement
    }

    /**
     * Refreshes the Lastest Update Site
     */
    function refreshG4LatestUpdates()
    {
        logAdd('Function: refreshG4LatestUpdates()');

        // Handle the g4/latest.php page with this.
        var galleryBox = document.getElementsByClassName('g-box-contents');
        var i;

        // Check if the class exists
        if(galleryBox.length == 0)
            return;

        // Use the first occur of the class
        galleryBox = galleryBox[0];

        // Create or find the existing unblock button box, then clear it out so we can rebuild it.
        var unblockButtonBox = unlockButtonContainer('whtb-unblock-box', galleryBox);
        unblockButtonBox.innerHTML = 'Unblock User (On this Page): ';
        var globalUnblockButtonBox = unlockButtonContainer('whtb-global-unblock-box', unblockButtonBox);
        // Add Buttons to global List
        globalUnblockButtonBox.innerHTML = 'Unblock (Global List): ';
        createUnblockButtonListFromArray(badUserList, globalUnblockButtonBox);

        // Clear out existing block buttons from the last iteration.
        removeExistingButtons('whtb-block-button');

        // Iterate over galley entries.
        var items = galleryBox.getElementsByClassName('detail-item');

        for(i = 0; i < items.length; i++) {

            // We'll just assume that the first user link is the user that posted it.
            // Be careful, because this can also point to comments made by users.
            // Note: user-comments can detected by searching for <this>.parent.parent.parent(classname) == comment
            var userLink = items[i].getElementsByClassName('user-link');

            if(userLink.length > 0) {
                userLink = userLink[0];

                var username = userLink.innerHTML;

                if(badUserList.indexOf(username) != -1) {
                    // Found someone we want to block. Hide the element and add to our
                    // list of unblock buttons.
                    items[i].style.display = 'none';

                    if(currentUserHiddenList.indexOf(username) == -1)
                        currentUserHiddenList.push(username);
                } else {
                    // This user is fine, but just in case we want to block them, we
                    // better add a block button. We could also be coming in from an
                    // unblock command, so we need to reset the visibility.
                    items[i].style.display = "";

                    // Set up the block button.
                    var hideButton = createBlockButton(username);

                    // Stick this right next to the username.
                    userLink.parentElement.insertBefore(hideButton, userLink.nextSibling);
                }
            }
        }

        // Generate the "Unblock button" list at the top. just for user on this page
        currentUserHiddenList.sort();
        createUnblockButtonListFromArray(currentUserHiddenList, unblockButtonBox);
    }

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

    // Loads settings
    loadData();

    // Now just do an initial refresh to show our optional stuff.
    refreshPage();
})();
<|endoftext|>


<commit_before>const express = require('express');
const i18next = require('i18next');
const i18nextMiddleware = require('i18next-express-middleware');
const Backend = require('i18next-node-fs-backend');

const app = express();
const port = process.env.PORT || 8080;

i18next
  .use(Backend)
  .use(i18nextMiddleware.LanguageDetector)
  .init({
    backend: {
      loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json',
      addPath: __dirname + '/locales/{{lng}}/{{ns}}.missing.json'
    },
    fallbackLng: 'en',
    preload: ['en', 'de'],
    saveMissing: true
  });

app.set('view engine', 'pug')
app.use(i18nextMiddleware.handle(i18next));

app.get('/', (req, res) => {
  res.render('index')
});

app.listen(port, (err) => {
  console.log(`Server is listening on port ${port}`);
});
<commit_msg>extend sample<commit_after>const express = require('express');
const i18next = require('i18next');
const i18nextMiddleware = require('i18next-express-middleware');
const Backend = require('i18next-node-fs-backend');

const app = express();
const port = process.env.PORT || 8080;

i18next
  .use(Backend)
  .use(i18nextMiddleware.LanguageDetector)
  .init({
    backend: {
      loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json',
      addPath: __dirname + '/locales/{{lng}}/{{ns}}.missing.json'
    },
    detection: {
      order: ['querystring', 'cookie'],
      caches: ['cookie']
    },
    fallbackLng: 'en',
    preload: ['en', 'de'],
    saveMissing: true
  });

app.set('view engine', 'pug')
app.use(i18nextMiddleware.handle(i18next));

app.get('/', (req, res) => {
  res.render('index')
});

app.listen(port, (err) => {
  console.log(`Server is listening on port ${port}`);
});
<|endoftext|>