<commit_before>import React, { Component } from 'react';
import TextField from 'material-ui/lib/text-field';
import { primaryColor,primaryColorText,primaryColorLight,accentColor,primaryColorDark } from './colors';
import {baseUrl,ERROR_CODE_FROM_EMAIL,ERROR_CODE_TO_EMAIL,ERROR_CODE_INVALID_URL} from './Const';
import RaisedButton from 'material-ui/lib/raised-button';
import CircularProgress from 'material-ui/lib/circular-progress';
import axios from 'axios';
import cookie from 'react-cookie';
import isEmpty from 'lodash/isEmpty';
import Snackbar from 'material-ui/lib/snackbar';
import Dialog from 'material-ui/lib/dialog';
import FlatButton from 'material-ui/lib/flat-button';
import Email from './Email';

let send_url_error_text = "";
let snackbar_msg = "";

class Main extends Component {

  constructor(props) {
    super(props);

    this.state = {
      showProgressBar: false,
      showSnackbar: false,
      showPreviewDialog: false,
      preview: {
        content: "",
      },
      filePath: "",
      error_from_email:"",
      error_to_email:"",
    };

  }

  render() {
    return (

        <TextField style={styles.input}
                   underlineFocusStyle={styles.underlineStyle}
                   ref="sendUrl"
                   errorText={send_url_error_text}
                   floatingLabelText="请输入要推送的网址"
                   onChange={this.sendTextOnChange.bind(this)}
                   multiLine={true}
        />

          {this._sendBtn()}

            {this._showPreviewDialog()}
            <RaisedButton label="预览" backgroundColor={primaryColorLight} labelColor={primaryColorText}
                          onClick={this.preview.bind(this)}
                          style={styles.button}/>
            <RaisedButton label="附件" backgroundColor={primaryColorLight} labelColor={primaryColorText}
                          style={styles.button}>
              <input type="file" style={styles.fileInput} onChange={this.chooseFile.bind(this)}/>
            </RaisedButton>

        {this._progressBar()}

        <Email
          ref="email"
          error_from_email={this.state.error_from_email}
          error_to_email={this.state.error_to_email}
        />

        <Snackbar
          open={this.state.showSnackbar}
          onRequestClose={this._onRequestClose.bind(this)}
          message={snackbar_msg}
          autoHideDuration={3000}
        />

    );
  }

  sendTextOnChange(event) {
    if (isEmpty(this.refs.sendUrl.getValue())) {
      this.setState({filePath: ""});
      return;
    }
  }

  chooseFile(event) {
    var filePath = event.target.value;
    if (isEmpty(filePath)) {
      return;
    }
    this.setState({filePath: filePath});
    this.refs.sendUrl.setValue(filePath);
  }

  _sendBtn() {
    if (isEmpty(this.state.filePath)) {
      return (
        <RaisedButton label="发送" backgroundColor={primaryColor} labelColor={primaryColorText}
                      onClick={this.send.bind(this)}
                      style={styles.button}/>
      );
    } else {
      return (
        <RaisedButton label="发送附件" backgroundColor={primaryColor} labelColor={primaryColorText}
                      onClick={this.attach.bind(this)}
                      style={styles.button}/>
      );
    }
  }

  send() {
    const sendApi = baseUrl + 'send';

    let sendUrl = this.refs.sendUrl.getValue();

    if (isEmpty(sendUrl)) {
      send_url_error_text = "请输入要推送的网址"
      this.forceUpdate();
      return;
    }

    let from_email = this.refs.email.getFromMail();
    let to_email = this.refs.email.getToMail();

    if (this.state.showProgressBar) {
      snackbar_msg = "发送中...."
      this.forceUpdate();
      return;
    }

    this._resetWarning();

    axios.post(sendApi, {
        url: sendUrl,
        from_email: from_email,
        to_email: to_email,
      })
      .then((res) => {
        snackbar_msg = "已发送"
        this.setState({showSnackbar: true});
        this.setState({showProgressBar: false});

        this.forceUpdate();
      })
      .catch((res) => {
        this.setState({showProgressBar: false});
        switch (res.data.code) {
          case ERROR_CODE_FROM_EMAIL:
            this.setState({error_from_email:res.data.error});
            break;
          case ERROR_CODE_TO_EMAIL:
            this.setState({error_to_email:res.data.error});
            break;
          case ERROR_CODE_INVALID_URL:
            send_url_error_text = res.data.error;
            break;
          default:
            snackbar_msg = res.data.error
            this.setState({showSnackbar: true});
            break;
        }
        this.forceUpdate();
      });
  }

  _resetWarning() {
    this.setState({showProgressBar: true});
    this.setState({error_from_email: ""});
    this.setState({error_to_email: ""});
    send_url_error_text = ""
  }

  preview() {
    const sendApi = baseUrl + 'preview';
    const sendUrl = this.refs.sendUrl.getValue();

    if (isEmpty(sendUrl)) {
      send_url_error_text = "请输入要预览的网址"
      this.forceUpdate();
      return;
    }

    if (this.state.showProgressBar) {
      snackbar_msg = "发送中...."
      this.forceUpdate();
      return;
    }

    this._resetWarning();

    axios.post(sendApi, {
        url: sendUrl,
      })
      .then((res) => {
        this.setState({showPreviewDialog: true});
        this.setState({showProgressBar: false});
        this.setState({
          preview: {
            content: res.data.content,
          }
        });
      })
      .catch((res) => {
        this.setState({showProgressBar: false});
        switch (res.data.code) {
          case ERROR_CODE_INVALID_URL:
            send_url_error_text = res.data.error;
            break;
          default:
            snackbar_msg = res.data.error
            this.setState({showSnackbar: true});
            break;
        }
        this.forceUpdate();
      });
  }

  attach() {
    let api = baseUrl + 'uploads';

    let sendUrl = this.refs.sendUrl.getValue();

    if (isEmpty(sendUrl)) {
      send_url_error_text = "请选择要发送的附件"
      this.forceUpdate();
      return;
    }

    if (this.state.showProgressBar) {
      snackbar_msg = "发送中...."
      this.forceUpdate();
      return;
    }

    this._resetWarning();

    this.setState({showProgressBar: true});

    let fileData = new FormData();
    fileData.append("to_email", to_email);
    fileData.append("from_email", from_email);

    data.append('file', document.getElementById('file').files[0]);
    axios.post(api, data)
      .then((res) => {
        snackbar_msg = "已发送"
        this.setState({showSnackbar: true});
        this.setState({showProgressBar: false});

        this.forceUpdate();
      })
      .catch((res) => {
        this.setState({showProgressBar: false});
        switch (res.data.code) {
          case ERROR_CODE_FROM_EMAIL:
            error_from_email = res.data.error;
            break;
          case ERROR_CODE_TO_EMAIL:
            error_to_email = res.data.error;
            break;
          case ERROR_CODE_INVALID_URL:
            send_url_error_text = res.data.error;
            break;
          default:
            snackbar_msg = res.data.error
            this.setState({showSnackbar: true});
            break;
        }
        this.forceUpdate();
      });
  }

  handleDialogClose() {
    this.setState({showPreviewDialog: false});
  };

  _showPreviewDialog() {
    const actions = [
      <FlatButton
        label="关闭"
        primary={true}
        keyboardFocused={true}
        onTouchTap={this.handleDialogClose.bind(this)}
      />,
    ];

    const customContentStyle = {
      overflow: 'auto',
    };

    return (
      <Dialog
        actions={actions}
        modal={false}
        open={this.state.showPreviewDialog}
        bodyStyle={customContentStyle}
        onRequestClose={this.handleDialogClose.bind(this)}
      >

      </Dialog>
    );
  }

  showSnackbar() {
    snackbar_msg = "开发中的功能,暂不能用...";
    this.setState({showSnackbar: true});
  }

  _onRequestClose() {
    this.setState({showSnackbar: false});
  }

  _progressBar() {
    if (this.state.showProgressBar) {
      return (
        <CircularProgress
          color={primaryColor}
          style={styles.progress}/>
      );
    }
  }

}

var styles = {
  container: {
    display: 'flex',
    justifyContent: 'center',
    flexDirection: 'column',
    paddingTop: '2%',
    alignItems: 'center',
  },

  input: {
    minWidth: '39%',
  },

  underlineStyle: {
    borderColor: primaryColor,
  },

  buttonGroup: {
    display: 'flex',
    minWidth: '39%',
    marginTop: '2vh',
  },

  button: {
    flexGrow: 1,
    textAlign: 'center',
    margin: '1vh',
  },

  main_child_button_group: {
    display: 'flex',
    flexGrow: 2,
    textAlign: 'center',
  },

  progress: {
    marginTop: '5vh',
  },

  labelStyle: {
    fontSize: '18px',
    fontWeight: 'bold'
  },

  iframe: {
    display: 'flex',
    flexDirection: 'column',
    overflowY: 'scroll',
  },

  fileInput: {
    cursor: 'pointer',
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    width: '100%',
    opacity: 0,
  },
};

export default Main;<commit_msg>code attch logic<commit_after>import React, { Component } from 'react';
import TextField from 'material-ui/lib/text-field';
import { primaryColor,primaryColorText,primaryColorLight,accentColor,primaryColorDark } from './colors';
import {baseUrl,ERROR_CODE_FROM_EMAIL,ERROR_CODE_TO_EMAIL,ERROR_CODE_INVALID_URL} from './Const';
import RaisedButton from 'material-ui/lib/raised-button';
import CircularProgress from 'material-ui/lib/circular-progress';
import axios from 'axios';
import cookie from 'react-cookie';
import isEmpty from 'lodash/isEmpty';
import Snackbar from 'material-ui/lib/snackbar';
import Dialog from 'material-ui/lib/dialog';
import FlatButton from 'material-ui/lib/flat-button';
import Email from './Email';

let send_url_error_text = "";
let snackbar_msg = "";

class Main extends Component {

  constructor(props) {
    super(props);

    this.state = {
      showProgressBar: false,
      showSnackbar: false,
      showPreviewDialog: false,
      preview: {
        content: "",
      },
      filePath: "",
      error_from_email:"",
      error_to_email:"",
    };

  }

  render() {
    return (

        <TextField style={styles.input}
                   underlineFocusStyle={styles.underlineStyle}
                   ref="sendUrl"
                   errorText={send_url_error_text}
                   floatingLabelText="请输入要推送的网址"
                   onChange={this.sendTextOnChange.bind(this)}
                   multiLine={true}
        />

          {this._sendBtn()}

            {this._showPreviewDialog()}
            <RaisedButton label="预览" backgroundColor={primaryColorLight} labelColor={primaryColorText}
                          onClick={this.preview.bind(this)}
                          style={styles.button}/>
            <RaisedButton label="附件" backgroundColor={primaryColorLight} labelColor={primaryColorText}
                          style={styles.button}>
              <input ref="file" type="file" style={styles.fileInput} onChange={this.chooseFile.bind(this)}/>
            </RaisedButton>

        {this._progressBar()}

        <Email
          ref="email"
          error_from_email={this.state.error_from_email}
          error_to_email={this.state.error_to_email}
        />

        <Snackbar
          open={this.state.showSnackbar}
          onRequestClose={this._onRequestClose.bind(this)}
          message={snackbar_msg}
          autoHideDuration={3000}
        />

    );
  }

  sendTextOnChange(event) {
    if (isEmpty(this.refs.sendUrl.getValue())) {
      this.setState({filePath: ""});
      return;
    }
  }

  chooseFile(event) {
    var filePath = event.target.files[0].name;
    if (isEmpty(filePath)) {
      return;
    }
    this.setState({filePath: filePath});
    this.refs.sendUrl.setValue(filePath);
  }

  _sendBtn() {
    if (isEmpty(this.state.filePath)) {
      return (
        <RaisedButton label="发送" backgroundColor={primaryColor} labelColor={primaryColorText}
                      onClick={this.send.bind(this)}
                      style={styles.button}/>
      );
    } else {
      return (
        <RaisedButton label="发送附件" backgroundColor={primaryColor} labelColor={primaryColorText}
                      onClick={this.attach.bind(this)}
                      style={styles.button}/>
      );
    }
  }

  send() {
    const sendApi = baseUrl + 'send';

    let sendUrl = this.refs.sendUrl.getValue();

    if (isEmpty(sendUrl)) {
      send_url_error_text = "请输入要推送的网址"
      this.forceUpdate();
      return;
    }

    let from_email = this.refs.email.getFromMail();
    let to_email = this.refs.email.getToMail();

    if (this.state.showProgressBar) {
      snackbar_msg = "发送中...."
      this.forceUpdate();
      return;
    }

    this._resetWarning();

    axios.post(sendApi, {
        url: sendUrl,
        from_email: from_email,
        to_email: to_email,
      })
      .then((res) => {
        snackbar_msg = "已发送"
        this.setState({showSnackbar: true});
        this.setState({showProgressBar: false});

        this.forceUpdate();
      })
      .catch((res) => {
        this.setState({showProgressBar: false});
        switch (res.data.code) {
          case ERROR_CODE_FROM_EMAIL:
            this.setState({error_from_email:res.data.error});
            break;
          case ERROR_CODE_TO_EMAIL:
            this.setState({error_to_email:res.data.error});
            break;
          case ERROR_CODE_INVALID_URL:
            send_url_error_text = res.data.error;
            break;
          default:
            snackbar_msg = res.data.error
            this.setState({showSnackbar: true});
            break;
        }
        this.forceUpdate();
      });
  }

  _resetWarning() {
    this.setState({showProgressBar: true});
    this.setState({error_from_email: ""});
    this.setState({error_to_email: ""});
    send_url_error_text = ""
  }

  preview() {
    const sendApi = baseUrl + 'preview';
    const sendUrl = this.refs.sendUrl.getValue();

    if (isEmpty(sendUrl)) {
      send_url_error_text = "请输入要预览的网址"
      this.forceUpdate();
      return;
    }

    if (this.state.showProgressBar) {
      snackbar_msg = "发送中...."
      this.forceUpdate();
      return;
    }

    this._resetWarning();

    axios.post(sendApi, {
        url: sendUrl,
      })
      .then((res) => {
        this.setState({showPreviewDialog: true});
        this.setState({showProgressBar: false});
        this.setState({
          preview: {
            content: res.data.content,
          }
        });
      })
      .catch((res) => {
        this.setState({showProgressBar: false});
        switch (res.data.code) {
          case ERROR_CODE_INVALID_URL:
            send_url_error_text = res.data.error;
            break;
          default:
            snackbar_msg = res.data.error
            this.setState({showSnackbar: true});
            break;
        }
        this.forceUpdate();
      });
  }

  attach() {
    let api = baseUrl + 'upload';

    let sendUrl = this.refs.sendUrl.getValue();

    if (isEmpty(sendUrl)) {
      send_url_error_text = "请选择要发送的附件"
      this.forceUpdate();
      return;
    }

    this._resetWarning();

    if (this.state.showProgressBar) {
      snackbar_msg = "发送中...."
      this.forceUpdate();
      return;
    }

    let fileData = new FormData();
    let from_email = this.refs.email.getFromMail();
    let to_email = this.refs.email.getToMail();

    fileData.append("to_email", to_email);
    fileData.append("from_email", from_email);

    fileData.append('file', this.refs.file.files[0]);
    axios.post(api, fileData)
      .then((res) => {
        snackbar_msg = "已发送"
        this.setState({showSnackbar: true});
        this.setState({showProgressBar: false});

        this.forceUpdate();
      })
      .catch((res) => {
        this.setState({showProgressBar: false});
        switch (res.data.code) {
          case ERROR_CODE_FROM_EMAIL:
            this.setState({error_from_email:res.data.error});
            break;
          case ERROR_CODE_TO_EMAIL:
            this.setState({error_to_email:res.data.error});
            break;
          case ERROR_CODE_INVALID_URL:
            send_url_error_text = res.data.error;
            break;
          default:
            snackbar_msg = res.data.error
            this.setState({showSnackbar: true});
            break;
        }
        this.forceUpdate();
      });
  }

  handleDialogClose() {
    this.setState({showPreviewDialog: false});
  };

  _showPreviewDialog() {
    const actions = [
      <FlatButton
        label="关闭"
        primary={true}
        keyboardFocused={true}
        onTouchTap={this.handleDialogClose.bind(this)}
      />,
    ];

    const customContentStyle = {
      overflow: 'auto',
    };

    return (
      <Dialog
        actions={actions}
        modal={false}
        open={this.state.showPreviewDialog}
        bodyStyle={customContentStyle}
        onRequestClose={this.handleDialogClose.bind(this)}
      >

      </Dialog>
    );
  }

  showSnackbar() {
    snackbar_msg = "开发中的功能,暂不能用...";
    this.setState({showSnackbar: true});
  }

  _onRequestClose() {
    this.setState({showSnackbar: false});
  }

  _progressBar() {
    if (this.state.showProgressBar) {
      return (
        <CircularProgress
          color={primaryColor}
          style={styles.progress}/>
      );
    }
  }

}

var styles = {
  container: {
    display: 'flex',
    justifyContent: 'center',
    flexDirection: 'column',
    paddingTop: '2%',
    alignItems: 'center',
  },

  input: {
    minWidth: '39%',
  },

  underlineStyle: {
    borderColor: primaryColor,
  },

  buttonGroup: {
    display: 'flex',
    minWidth: '39%',
    marginTop: '2vh',
  },

  button: {
    flexGrow: 1,
    textAlign: 'center',
    margin: '1vh',
  },

  main_child_button_group: {
    display: 'flex',
    flexGrow: 2,
    textAlign: 'center',
  },

  progress: {
    marginTop: '5vh',
  },

  labelStyle: {
    fontSize: '18px',
    fontWeight: 'bold'
  },

  iframe: {
    display: 'flex',
    flexDirection: 'column',
    overflowY: 'scroll',
  },

  fileInput: {
    cursor: 'pointer',
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    width: '100%',
    opacity: 0,
  },
};

export default Main;<|endoftext|>


<commit_before>"use strict";

const u = require("./UtilFunctions.js"),
	ID = require("./ID.js"),
	Finger = require("./Finger.js");

class Node{
	constructor(chord){
		//TODO
		this.id = chord.id;
		this.chord = chord;
		this.finger = [];
		this.predecessor = this;

		//Create Finger Table
		for(var i=0; i<chord.config.idWidth; i++)
			this.finger[i] = new Finger(this, i);
	}

	//NOTE:
	//These show the algorithms, but they do not account for RPC, promises, callbacks etc...
	//Redo these as time goes on.

	setFinger(index, node){
		//Set the node, and then check further indices to see if this node is a better fit.
		//Improvement over std chord.
		do {
			this.finger[index++].node = node;
		} while(index<this.finger.length
			&& ID.inRightOpenBound(node.id, this.finger[index].start, this.finger[index].node.id));
	}

	removeFinger(id){
		//Scan right to left for a block of fingers represented by this id.
		//Replace them all with the node found immediately to the right of the block.
		//If last element was removed, replace with self.
		//Improvement over std chord.

		let index = this.finger.length - 1,
			replace;

		while(index >=0){
			replace = (index === this.finger.length-1) ? this : this.finger[index+1].node;

			while(index >=0 && ID.compare(id, this.finger[index].node.id) === 0){
				this.finger[index].node = replace;

				index--;
			}

			index--;
		}
	}

	//Promise updated
	getSuccessor(){
		return new Promise((resolve, reject) => {
			resolve(this.finger[0].node)
		});
	}

	//Promise updated
	setSuccessor(val){
		return new Promise((resolve, reject) => {
			//this.finger[0].node = val;
			this.setFinger(0, val);
			resolve(val)
		});
	}

	//Promise updated
	getPredecessor(){
		return new Promise((resolve, reject) => {
			resolve(this.predecessor)
		});
	}

	//Promise updated
	setPredecessor(val){
		return new Promise((resolve, reject) => {
			this.predecessor = val;
			resolve(val);
		});
	}

	//Promise updated
	initOn(knownNode){
		this.chord.server.connect = true;
		if(knownNode){
			return this.initialiseFingerTable(knownNode)
				.then( () => {
					return this.updateOthers();
				} )
				.then( () => {
					this.chord.server.connect = false;
				} );
			//move keys from successor to self as well.
		} else {
			for(var i=0; i<chord.config.idwidth; i++)
				this.finger[i].node = this;
			this.predecessor = this;
			this.chord.server.connect = false;
			return Promise.resolve();
		}

	}

	//Promise updated
	initialiseFingerTable(knownNode){
		return knownNode.findSuccessor(this.finger[0].start)
			.then(
				succ => {return this.setSuccessor(succ);}
			)
			.then(
				succ => {return succ.getPredecessor();}
			)
			.then(
				pred => {return this.setPredecessor(pred);}
			)
			.then(
				() => {
					let proms = [];

					for(var i=0; i<this.finger.length-1; i++) {
						let p = (proms.length===0) ? Promise.resolve() : proms[proms.length-1],
							f = ((i)=>{
								return () => {
									if(ID.inRightOpenBound(this.finger[i+1].start, this.id, this.finger[i].node.id)){
										//this.finger[i+1].node = this.finger[i].node;
										this.setFinger(i+1, this.finger[i].node)
									} else {
										console.log(`Using new unknown node.`)
										proms.push(
											knownNode.findSuccessor(this.finger[i+1].start)
												.then(
													succ => {
														//this.finger[i+1].node = succ;
														this.setFinger(i+1, succ);
													}
												)
										)
									}
								};
							})(i);
						p.then(f);
					}

					return Promise.all(proms);
				}
			);
	}

	//Promise updated
	updateOthers(){
		//Inform other nodes about our existence.
		let proms = [];

		for (var i = 0; i < this.finger.length; i++) {
			//find last node p whose ith finger might be n
			(i=>{proms.push(
				this.findPredecessor(this.id.subtract(ID.powerOfTwoBuffer(i)))
					.then(
						p => {return p.updateFingerTable(this, i)}
					)
				)
			})(i);
		}

		return Promise.all(proms);
	}

	//Promise updated
	updateFingerTable(foreignNode, index){
		//Update the finger of some remote node
		if(ID.inRightOpenBound(foreignNode.id, this.id, this.finger[index].node.id)){
			//this.finger[index].node = foreignNode;
			this.setFinger(index, foreignNode);
			return this.getPredecessor()
				.then(
					res => {return res.updateFingerTable(foreignNode, index)}
				)
		} else {
			return Promise.resolve();
		}

	}

	//Promise updated
	closestPrecedingFinger(id){
		return new Promise( resolve => {
			for (var i = this.finger.length - 1; i >= 0; i--) {
				if(ID.inOpenBound(this.finger[i].node.id, this.id, id)){
					resolve(this.finger[i].node);
					break;
				}
			}
			resolve(this);
		} );
	}

	//Promise updated
	findSuccessor(id){
		return this.findPredecessor(id)
			.then( nPrime => { return nPrime.getSuccessor() } )
	}

	//Promise updated
	findPredecessor(id){
		return new Promise( (resolve, reject) => {
			let nPrime = this;

			let condUpd = () => {
				nPrime.getSuccessor()
					.then(
						succ => {
							if (!ID.inLeftOpenBound(id, nPrime.id, succ.id)) {
								nPrime.closestPrecedingFinger(id)
									.then(
										newPrime => {
											nPrime = newPrime;
											condUpd();
										},
										rea => reject(rea)
									)
							} else {
								resolve(nPrime);
							}
						},
						rea => reject(rea)
					)
				};

			condUpd();
		} )
	}

	//Stabilization methods
	//Promise updated
	stableJoin(knownNode){
		return this.setPredecessor(null)
			.then(
				() => {
					this.chord.server.connect = true;
					return knownNode.findSuccessor(this.id);
				}
			)
			.then(
				res => {
					return this.setSuccessor(res);
				}
			);
	}

	//periodically verify n's immediate successor
	//and tell the successor about n.
	//Promise updated
	stabilize(){
		let oSucc;

		u.log(this.chord, `ME:`);
		u.log(this.chord, this.id.idString);

		return this.getSuccessor()
			.then(succ => {
				oSucc = succ;
				u.log(this.chord, `MY SUCCESSOR:`);
				u.log(this.chord, succ.id.idString);
				return succ.getPredecessor();
			})
			.then(pred => {
				u.log(this.chord, `MY SUCCESSOR'S PREDECESSOR:`);
				u.log(this.chord, (pred) ? pred.id.idString : pred);

				if(pred && ID.inOpenBound(pred.id, this.id, oSucc.id)) {
					u.log(this.chord, `NEW SUCCESSOR FOUND`);
					return this.setSuccessor(pred);
				} else {
					return Promise.resolve();
				}
			})
			.then(
				res => {
					u.log(this.chord, `NOTIFYING SUCCESSOR ABOUT:`);
					u.log(this.chord, this.id.idString);
					return oSucc.notify(this);
				}
			)

	}

	//Promise updated
	notify(nPrime){
		u.log(this.chord, `NOTIFIED BY:`);
		u.log(this.chord, ID.coerceString(nPrime.id));

		if(this.predecessor === null || ID.inOpenBound(nPrime.id, this.predecessor.id, this.id))
			this.predecessor = nPrime;

		return Promise.resolve();
	}

	//Promise updated
	fixFingers(){
		let i = Math.floor(Math.random() * (this.finger.length-1) + 1);
		//this.finger[i].node = this.findSuccessor(this.finger[i].start);

		return this.findSuccessor(this.finger[i].start)
			.then(
				succ => this.setFinger(i, succ)//this.finger[i].node = succ
			)
	}

	//Custom

	message(id, msg, bypass){
		//TODO

		// debugger;

		u.log(this.chord, `Received message at the local node for ${id}: ${msg}
			I am ${this.id.idString}`);

		if(this.chord.server.connect && ID.compare(id, this.id)!== 0) {
			this.chord.server.node.message(id, msg);
		} else if (!this.predecessor && ID.compare(id, this.id)!== 0 ) {
			this.getSuccessor()
				.then(
					successor => successor.message(id, msg)
				)
		} else if (!bypass && (ID.compare(this.id, id)===0 || ID.inLeftOpenBound(id, this.predecessor.id, this.id))){
			//Pass to appropriate handler - this is our message.
			this.chord.registry.parse(msg);
		} else {
			//Pass along the chain to a responsible node.
			this.closestPrecedingFinger(id)
				.then(
					node => node.message(id, msg)
				)
		}
	}

	unlinkClient(idString){
		if(this.chord.config.isServer && this.chord.directNodes[idString]){
			// delete this.chord.directNodes[idString];
			return true;
		}

		return false;
	}

	// Item management

	lookup(id){
		//TODO
	}

	add(id, value){
		//TODO
	}
}

module.exports = Node;
<commit_msg>Attempting to move connection to successor into the join protocol.<commit_after>"use strict";

const u = require("./UtilFunctions.js"),
	ID = require("./ID.js"),
	Finger = require("./Finger.js");

class Node{
	constructor(chord){
		//TODO
		this.id = chord.id;
		this.chord = chord;
		this.finger = [];
		this.predecessor = this;

		//Create Finger Table
		for(var i=0; i<chord.config.idWidth; i++)
			this.finger[i] = new Finger(this, i);
	}

	//NOTE:
	//These show the algorithms, but they do not account for RPC, promises, callbacks etc...
	//Redo these as time goes on.

	setFinger(index, node){
		//Set the node, and then check further indices to see if this node is a better fit.
		//Improvement over std chord.
		do {
			this.finger[index++].node = node;
		} while(index<this.finger.length
			&& ID.inRightOpenBound(node.id, this.finger[index].start, this.finger[index].node.id));
	}

	removeFinger(id){
		//Scan right to left for a block of fingers represented by this id.
		//Replace them all with the node found immediately to the right of the block.
		//If last element was removed, replace with self.
		//Improvement over std chord.

		let index = this.finger.length - 1,
			replace;

		while(index >=0){
			replace = (index === this.finger.length-1) ? this : this.finger[index+1].node;

			while(index >=0 && ID.compare(id, this.finger[index].node.id) === 0){
				this.finger[index].node = replace;

				index--;
			}

			index--;
		}
	}

	//Promise updated
	getSuccessor(){
		return new Promise((resolve, reject) => {
			resolve(this.finger[0].node)
		});
	}

	//Promise updated
	setSuccessor(val){
		return new Promise((resolve, reject) => {
			//this.finger[0].node = val;
			this.setFinger(0, val);
			resolve(val)
		});
	}

	//Promise updated
	getPredecessor(){
		return new Promise((resolve, reject) => {
			resolve(this.predecessor)
		});
	}

	//Promise updated
	setPredecessor(val){
		return new Promise((resolve, reject) => {
			this.predecessor = val;
			resolve(val);
		});
	}

	//Promise updated
	initOn(knownNode){
		this.chord.server.connect = true;
		if(knownNode){
			return this.initialiseFingerTable(knownNode)
				.then( () => {
					return this.updateOthers();
				} )
				.then( () => {
					this.chord.server.connect = false;
				} );
			//move keys from successor to self as well.
		} else {
			for(var i=0; i<chord.config.idwidth; i++)
				this.finger[i].node = this;
			this.predecessor = this;
			this.chord.server.connect = false;
			return Promise.resolve();
		}

	}

	//Promise updated
	initialiseFingerTable(knownNode){
		return knownNode.findSuccessor(this.finger[0].start)
			.then(
				succ => {return this.setSuccessor(succ);}
			)
			.then(
				succ => {return succ.getPredecessor();}
			)
			.then(
				pred => {return this.setPredecessor(pred);}
			)
			.then(
				() => {
					let proms = [];

					for(var i=0; i<this.finger.length-1; i++) {
						let p = (proms.length===0) ? Promise.resolve() : proms[proms.length-1],
							f = ((i)=>{
								return () => {
									if(ID.inRightOpenBound(this.finger[i+1].start, this.id, this.finger[i].node.id)){
										//this.finger[i+1].node = this.finger[i].node;
										this.setFinger(i+1, this.finger[i].node)
									} else {
										console.log(`Using new unknown node.`)
										proms.push(
											knownNode.findSuccessor(this.finger[i+1].start)
												.then(
													succ => {
														//this.finger[i+1].node = succ;
														this.setFinger(i+1, succ);
													}
												)
										)
									}
								};
							})(i);
						p.then(f);
					}

					return Promise.all(proms);
				}
			);
	}

	//Promise updated
	updateOthers(){
		//Inform other nodes about our existence.
		let proms = [];

		for (var i = 0; i < this.finger.length; i++) {
			//find last node p whose ith finger might be n
			(i=>{proms.push(
				this.findPredecessor(this.id.subtract(ID.powerOfTwoBuffer(i)))
					.then(
						p => {return p.updateFingerTable(this, i)}
					)
				)
			})(i);
		}

		return Promise.all(proms);
	}

	//Promise updated
	updateFingerTable(foreignNode, index){
		//Update the finger of some remote node
		if(ID.inRightOpenBound(foreignNode.id, this.id, this.finger[index].node.id)){
			//this.finger[index].node = foreignNode;
			this.setFinger(index, foreignNode);
			return this.getPredecessor()
				.then(
					res => {return res.updateFingerTable(foreignNode, index)}
				)
		} else {
			return Promise.resolve();
		}

	}

	//Promise updated
	closestPrecedingFinger(id){
		return new Promise( resolve => {
			for (var i = this.finger.length - 1; i >= 0; i--) {
				if(ID.inOpenBound(this.finger[i].node.id, this.id, id)){
					resolve(this.finger[i].node);
					break;
				}
			}
			resolve(this);
		} );
	}

	//Promise updated
	findSuccessor(id){
		return this.findPredecessor(id)
			.then( nPrime => { return nPrime.getSuccessor() } )
	}

	//Promise updated
	findPredecessor(id){
		return new Promise( (resolve, reject) => {
			let nPrime = this;

			let condUpd = () => {
				nPrime.getSuccessor()
					.then(
						succ => {
							if (!ID.inLeftOpenBound(id, nPrime.id, succ.id)) {
								nPrime.closestPrecedingFinger(id)
									.then(
										newPrime => {
											nPrime = newPrime;
											condUpd();
										},
										rea => reject(rea)
									)
							} else {
								resolve(nPrime);
							}
						},
						rea => reject(rea)
					)
				};

			condUpd();
		} )
	}

	//Stabilization methods
	//Promise updated
	stableJoin(knownNode){
		let succ;

		return this.setPredecessor(null)
			.then(
				() => {
					this.chord.server.connect = true;
					return knownNode.findSuccessor(this.id);
				}
			)
			.then(
				foundSucc => {
					succ = foundSucc;
					return this.chord.conductor.connectTo(ID.coerceString(succ.id), "Conductor-Chord");
				}
			)
			.then(
				res => {
					return this.setSuccessor(res);
				}
			);
	}

	//periodically verify n's immediate successor
	//and tell the successor about n.
	//Promise updated
	stabilize(){
		let oSucc;

		u.log(this.chord, `ME:`);
		u.log(this.chord, this.id.idString);

		return this.getSuccessor()
			.then(succ => {
				oSucc = succ;
				u.log(this.chord, `MY SUCCESSOR:`);
				u.log(this.chord, succ.id.idString);
				return succ.getPredecessor();
			})
			.then(pred => {
				u.log(this.chord, `MY SUCCESSOR'S PREDECESSOR:`);
				u.log(this.chord, (pred) ? pred.id.idString : pred);

				if(pred && ID.inOpenBound(pred.id, this.id, oSucc.id)) {
					u.log(this.chord, `NEW SUCCESSOR FOUND`);
					return this.setSuccessor(pred);
				} else {
					return Promise.resolve();
				}
			})
			.then(
				res => {
					u.log(this.chord, `NOTIFYING SUCCESSOR ABOUT:`);
					u.log(this.chord, this.id.idString);
					return oSucc.notify(this);
				}
			)

	}

	//Promise updated
	notify(nPrime){
		u.log(this.chord, `NOTIFIED BY:`);
		u.log(this.chord, ID.coerceString(nPrime.id));

		if(this.predecessor === null || ID.inOpenBound(nPrime.id, this.predecessor.id, this.id))
			this.predecessor = nPrime;

		return Promise.resolve();
	}

	//Promise updated
	fixFingers(){
		let i = Math.floor(Math.random() * (this.finger.length-1) + 1);
		//this.finger[i].node = this.findSuccessor(this.finger[i].start);

		return this.findSuccessor(this.finger[i].start)
			.then(
				succ => this.setFinger(i, succ)//this.finger[i].node = succ
			)
	}

	//Custom

	message(id, msg, bypass){
		//TODO

		// debugger;

		u.log(this.chord, `Received message at the local node for ${id}: ${msg}
			I am ${this.id.idString}`);

		if(this.chord.server.connect && ID.compare(id, this.id)!== 0) {
			this.chord.server.node.message(id, msg);
		} else if (!this.predecessor && ID.compare(id, this.id)!== 0 ) {
			this.getSuccessor()
				.then(
					successor => successor.message(id, msg)
				)
		} else if (!bypass && (ID.compare(this.id, id)===0 || ID.inLeftOpenBound(id, this.predecessor.id, this.id))){
			//Pass to appropriate handler - this is our message.
			this.chord.registry.parse(msg);
		} else {
			//Pass along the chain to a responsible node.
			this.closestPrecedingFinger(id)
				.then(
					node => node.message(id, msg)
				)
		}
	}

	unlinkClient(idString){
		if(this.chord.config.isServer && this.chord.directNodes[idString]){
			// delete this.chord.directNodes[idString];
			return true;
		}

		return false;
	}

	// Item management

	lookup(id){
		//TODO
	}

	add(id, value){
		//TODO
	}
}

module.exports = Node;
<|endoftext|>


<commit_before>/*
 * animations.js - jordansne.com
 *
 * Author: Jordan Sne
 * License: MIT
 */

/* jshint esversion: 6 */

class Content {

    constructor(contentID, nextContent) {
        this.contentID = '#' + contentID;
        this.contentLinkID = this.contentID + '_link';
        this.nextContent = nextContent;

        $(this.contentID).css("opacity", 0);
        this.addScrollEvent();
    }

    /* Registers navbar buttons to their respective scroll events. */
    addScrollEvent() {
        const self = this;
        const page = $('html, body');

        const stopFunction = function() { page.stop(); };

        $(self.contentLinkID).click(function() {
            page.animate({
                scrollTop: $(self.contentID).offset().top - 145
            }, 750, function() {
                page.off('scroll mousedown mousewheel wheel keyup touchmove DOMMouseScroll resize', stopFunction);
                page.trigger('clicklink');
            });

            page.one('scroll mousedown mousewheel wheel keyup touchmove DOMMouseScroll resize', stopFunction);
        });
    }

    /* Show content immediately if in view or register to an event handler if not. */
    readyToShow() {
        const self = this;
        const page = $('html, body');

        const animate = function() {
            $(self.contentID).animate({
                opacity: 1
            }, 400, function() {
                if (self.nextContent !== null) {
                    self.nextContent.readyToShow();
                }
            });
        };

        if (self.isInView(self.contentID)) {
            animate();
        } else {
            $(self.contentID).one('show', animate);
            self.addShowEvent();
        }
    }

    /* Adds event handler for scrolling to check for in-view content. */
    addShowEvent() {
        const self = this;
        const page = $('html, body');

        page.on('ready scroll mousedown mousewheel wheel keyup touchmove DOMMouseScroll resize clicklink', function() {
            if (self.isInView(self.contentID)) {
                $(self.contentID).trigger('show');
            }
        });
    }

    /* Returns true if div content in view of browser. */
    isInView() {
        const contentTop = $(this.contentID).offset().top;
        const contentBottom = contentTop + $(this.contentID).height();

        const viewTop = $(window).scrollTop();
        const viewBottom = viewTop + $(window).height();

        // TODO Adjust for top header offset.
        return (viewTop + 150 < contentBottom && viewBottom - 150 > contentTop);
    }
}

$(document).ready(function() {
    const page = $('html, body');

    const contactContent = new Content('contact', null);
    const projectsContent = new Content('projects', contactContent);
    const aboutContent = new Content('about', projectsContent);

    // Ensure page is at top before starting show animations
    page.animate({
        scrollTop: 0
    }, 300, function() {
        aboutContent.readyToShow();
    });
});
<commit_msg>Adjust scroll animation offset<commit_after>/*
 * animations.js - jordansne.com
 *
 * Author: Jordan Sne
 * License: MIT
 */

/* jshint esversion: 6 */

class Content {

    constructor(contentID, nextContent) {
        this.contentID = '#' + contentID;
        this.contentLinkID = this.contentID + '_link';
        this.nextContent = nextContent;

        $(this.contentID).css("opacity", 0);
        this.addScrollEvent();
    }

    /* Registers navbar buttons to their respective scroll events. */
    addScrollEvent() {
        const self = this;
        const page = $('html, body');

        const stopFunction = function() { page.stop(); };

        $(self.contentLinkID).click(function() {
            page.animate({
                scrollTop: $(self.contentID).offset().top - 50
            }, 750, function() {
                page.off('scroll mousedown mousewheel wheel keyup touchmove DOMMouseScroll resize', stopFunction);
                page.trigger('clicklink');
            });

            page.one('scroll mousedown mousewheel wheel keyup touchmove DOMMouseScroll resize', stopFunction);
        });
    }

    /* Show content immediately if in view or register to an event handler if not. */
    readyToShow() {
        const self = this;
        const page = $('html, body');

        const animate = function() {
            $(self.contentID).animate({
                opacity: 1
            }, 400, function() {
                if (self.nextContent !== null) {
                    self.nextContent.readyToShow();
                }
            });
        };

        if (self.isInView(self.contentID)) {
            animate();
        } else {
            $(self.contentID).one('show', animate);
            self.addShowEvent();
        }
    }

    /* Adds event handler for scrolling to check for in-view content. */
    addShowEvent() {
        const self = this;
        const page = $('html, body');

        page.on('ready scroll mousedown mousewheel wheel keyup touchmove DOMMouseScroll resize clicklink', function() {
            if (self.isInView(self.contentID)) {
                $(self.contentID).trigger('show');
            }
        });
    }

    /* Returns true if div content in view of browser. */
    isInView() {
        const contentTop = $(this.contentID).offset().top;
        const contentBottom = contentTop + $(this.contentID).height();

        const viewTop = $(window).scrollTop();
        const viewBottom = viewTop + $(window).height();

        // TODO Adjust for top header offset.
        return (viewTop + 150 < contentBottom && viewBottom - 150 > contentTop);
    }
}

$(document).ready(function() {
    const page = $('html, body');

    const contactContent = new Content('contact', null);
    const projectsContent = new Content('projects', contactContent);
    const aboutContent = new Content('about', projectsContent);

    // Ensure page is at top before starting show animations
    page.animate({
        scrollTop: 0
    }, 300, function() {
        aboutContent.readyToShow();
    });
});
<|endoftext|>


<commit_before>// @flow
import { api } from 'neon-js'
import { extend, isEmpty, unionBy } from 'lodash-es'
import { createActions } from 'spunky'

import { getNode } from './nodeStorageActions'
import { ASSETS } from '../core/constants'
import { COIN_DECIMAL_LENGTH } from '../core/formatters'

type Props = {
  net: string,
  address: string,
  tokens: Array<TokenItemType>
}

export const ID = 'balances'

async function getBalances({ net, address, tokens }: Props) {
  let endpoint = await getNode()
  if (isEmpty(endpoint)) {
    endpoint = await api.getRPCEndpointFrom({ net }, api.neoscan)
  }

  const chunks = tokens
    .filter(token => !token.isUserGenerated)
    .reduce((accum, currVal, index) => {
      if (!accum.length) {
        accum.push([currVal.scriptHash])
        return accum
      }

      if (accum[accum.length - 1].length < 4) {
        accum[accum.length - 1].push(currVal.scriptHash)
      } else {
        accum.push([currVal.scriptHash])
      }
      return accum
    }, [])

  const promiseMap = chunks.map(chunk =>
    api.nep5.getTokenBalances(endpoint, chunk, address)
  )
  const results = await Promise.all(promiseMap)
  const resultObject = results.reduce((result, currentObject) => {
    Object.keys(currentObject).forEach(key => {
      // eslint-disable-next-line
      result[key] = currentObject[key]
    })
    return result
  }, {})

  const parsedTokenBalances = Object.keys(resultObject)
    .map(tokenKey => {
      const foundToken = tokens.find(token => token.symbol === tokenKey)
      if (foundToken && resultObject[tokenKey]) {
        return {
          [foundToken.scriptHash]: {
            ...foundToken,
            balance: resultObject[tokenKey]
          }
        }
      }
      return {}
    })
    .filter(tokenBalance => !isEmpty(tokenBalance))

  // Handle manually added script hashses here
  const userGeneratedTokenInfo = []
  // eslint-disable-next-line
  for (const token of tokens.filter(token => token.isUserGenerated)) {
    // eslint-disable-next-line
    const info = await api.nep5
      .getToken(endpoint, token.scriptHash, address)
      .catch(error => {
        // eslint-disable-next-line
        console.error(
          'An error occurrred attempting to fetch custom script hash balance info.',
          { error }
        )
        return Promise.resolve()
      })
    if (info) {
      userGeneratedTokenInfo.push({
        scriptHash: token.scriptHash,
        ...info
      })
    }
  }
  userGeneratedTokenInfo.forEach(token => {
    parsedTokenBalances.push({
      [token.scriptHash]: {
        ...token
      }
    })
  })

  // asset balances
  const assetBalances = await api.getBalanceFrom({ net, address }, api.neoscan)

  const { assets } = assetBalances.balance
  // The API doesn't always return NEO or GAS keys if, for example, the address only has one asset
  const neoBalance = assets.NEO ? assets.NEO.balance.toString() : '0'
  const gasBalance = assets.GAS
    ? assets.GAS.balance.round(COIN_DECIMAL_LENGTH).toString()
    : '0'
  const parsedAssets = [
    { [ASSETS.NEO]: neoBalance },
    { [ASSETS.GAS]: gasBalance }
  ]
  // $FlowFixMe
  return extend({}, ...parsedTokenBalances, ...parsedAssets)
}

export default createActions(
  ID,
  ({ net, address, tokens }: Props = {}) => async () =>
    getBalances({ net, address, tokens })
)
<commit_msg>fixes duplicate iteration<commit_after>// @flow
import { api } from 'neon-js'
import { extend, isEmpty, unionBy } from 'lodash-es'
import { createActions } from 'spunky'

import { getNode } from './nodeStorageActions'
import { ASSETS } from '../core/constants'
import { COIN_DECIMAL_LENGTH } from '../core/formatters'

const MAX_SCRIPT_HASH_CHUNK_SIZE = 5

type Props = {
  net: string,
  address: string,
  tokens: Array<TokenItemType>
}

export const ID = 'balances'

async function getBalances({ net, address, tokens }: Props) {
  let endpoint = await getNode()
  if (isEmpty(endpoint)) {
    endpoint = await api.getRPCEndpointFrom({ net }, api.neoscan)
  }

  const chunks = tokens
    .filter(token => !token.isUserGenerated)
    .reduce((accum, currVal, index) => {
      if (!accum.length) {
        accum.push([currVal.scriptHash])
        return accum
      }

      if (accum[accum.length - 1].length < MAX_SCRIPT_HASH_CHUNK_SIZE) {
        accum[accum.length - 1].push(currVal.scriptHash)
      } else {
        accum.push([currVal.scriptHash])
      }
      return accum
    }, [])

  const promiseMap = chunks.map(chunk =>
    api.nep5.getTokenBalances(endpoint, chunk, address)
  )
  const results = await Promise.all(promiseMap)

  const parsedTokenBalances = results.reduce((accum, currBalance) => {
    Object.keys(currBalance).forEach(key => {
      const foundToken = tokens.find(token => token.symbol === key)
      if (foundToken && currBalance[key]) {
        accum.push({
          [foundToken.scriptHash]: {
            ...foundToken,
            balance: currBalance[key]
          }
        })
      }
    })
    return accum
  }, [])

  // Handle manually added script hashses here
  const userGeneratedTokenInfo = []
  // eslint-disable-next-line
  for (const token of tokens.filter(token => token.isUserGenerated)) {
    // eslint-disable-next-line
    const info = await api.nep5
      .getToken(endpoint, token.scriptHash, address)
      .catch(error => {
        // eslint-disable-next-line
        console.error(
          'An error occurrred attempting to fetch custom script hash balance info.',
          { error }
        )
        return Promise.resolve()
      })
    if (info) {
      userGeneratedTokenInfo.push({
        scriptHash: token.scriptHash,
        ...info
      })
    }
  }
  userGeneratedTokenInfo.forEach(token => {
    parsedTokenBalances.push({
      [token.scriptHash]: {
        ...token
      }
    })
  })

  // asset balances
  const assetBalances = await api.getBalanceFrom({ net, address }, api.neoscan)

  const { assets } = assetBalances.balance
  // The API doesn't always return NEO or GAS keys if, for example, the address only has one asset
  const neoBalance = assets.NEO ? assets.NEO.balance.toString() : '0'
  const gasBalance = assets.GAS
    ? assets.GAS.balance.round(COIN_DECIMAL_LENGTH).toString()
    : '0'
  const parsedAssets = [
    { [ASSETS.NEO]: neoBalance },
    { [ASSETS.GAS]: gasBalance }
  ]
  // $FlowFixMe
  return extend({}, ...parsedTokenBalances, ...parsedAssets)
}

export default createActions(
  ID,
  ({ net, address, tokens }: Props = {}) => async () =>
    getBalances({ net, address, tokens })
)
<|endoftext|>


<commit_before>var allDrinks = [];

//object constructor
function Drink(drinkName, liquor, feeling, flavor, imageSrc, ingredients) {
  this.drinkName = drinkName;
  this.liquor = liquor;
  this.feeling = feeling;
  this.flavor = flavor;
  this.imageSrc = '';
  this.ingredients = ingredients;
  this.like = false;
  this.disLike = false;
  allDrinks.push(this);
}

var screwDriver = new Drink('Screwdriver', 'vodka', 'triedandtrue', 'sweet', 'img/screwdriver.png', ['Vodka', 'Orange Juice']);
var ginTonic = new Drink('Gin and Tonic', 'gin', 'triedandtrue', 'sour', 'img/gin-and-tonic.png', ['Gin', 'Tonic']);
var teqSunrise = new Drink('Tequila Sunrise', 'tequila', 'adventurous', 'sweet', 'img/tequila-sunrise.png', ['Tequila', 'Orange Juice', 'Cherry Grenadine']);
var amf = new Drink('Adios Mother F****r', 'tequila', 'party', 'strong', 'img/amf.png', ['Tequila', 'Vodka', 'Triple Sec', 'Gin', 'Blue Curacao', 'Sprite', 'Sour Mix']);
var birthdayCake = new Drink('Birthday Cake Shot', 'vodka', 'party', 'sweet', 'img/birthday-cake.png', ['Baileys Irish Cream', 'Smirnoff Cake Flavored Vodka', 'Whipped Cream', 'Sprinkles']);
var bloodyMary = new Drink('Bloody Mary', 'vodka', 'triedandtrue', 'strong', 'img/bloody-mary.png', ['']);
var blueMoon = new Drink('Blue Moon', 'beer', 'triedandtrue', 'strong', 'img/blue-moon.png', ['']);
var cadMargarita = new Drink('Cadillac Margarita', 'tequila', 'party', 'sour', 'img/cadillac-margarita.png', ['Tequila', 'Grand Marnier', 'Lime Juice', 'Salt on the Rim']);
var ciroc = new Drink('Ciroc', 'vodka', 'party', 'strong', 'img/ciroc.png', ['Ciroc Vodka']);
var cosmo = new Drink('Cosmo', 'vodka', 'triedandtrue', 'sour', 'img/cosmo.png', ['']);
var cubaLibre = new Drink('Cuba Libre', 'rum', 'triedandtrue', 'sweet', 'img/cuba-libre.png', ['Rum', 'Coke', 'Lime']);
var donJulio = new Drink('Don Julio', 'tequila', 'party', 'strong', 'img/don-julio.png', ['Don Julio Blanco Tequila']);
var duckFart = new Drink('Duck Fart', 'whiskey', 'party', 'sweet', 'img/duck-fart.png', ['Whiskey', 'Baileys Irish Cream', 'Kahlua']);
var elecTea = new Drink('Electric Iced Tea', 'gin', 'adventurous', 'strong', 'img/electric-iced-tea.png', ['Gin', 'Vodka', 'Light Rum', 'Tequila', 'Blue Curacao', 'Sour Mix', 'Lemon-lime Soda']);
var fourHorse = new Drink ('The Four Horsemen', 'whiskey', 'adventurous', 'strong', 'img/four-horsemen.png', ['']);
var ginAndJuice = new Drink('Gin and Juice', 'gin', 'triedandtrue', 'sweet', 'img/gin-and-juice.png', ['']);
var greyGoose = new Drink('Grey Goose', 'vodka', 'party', 'strong', 'img/grey-goose.png', ['']);
var hefe = new Drink('Hefeweizen', 'beer', 'triedandtrue', 'strong', 'img/hefe.png' , ['']);
var hennessy = new Drink('Hennessy', '', 'triedandtrue', 'strong', 'img/hennessy.png', ['']);
var ipa = new Drink('IPA', 'beer', 'triedandtrue', 'strong', 'img/ipa.png', ['']);
var jackDaniels = new Drink('Jack Daniels', 'whiskey', 'triedandtrue', 'strong', 'img/jack-daniels.png', ['Jack Daniels Whiskey']);
var jameson  = new Drink('Jameson', 'whiskey', 'triedandtrue', 'strong', 'img/jameson.png', ['Jameson Whiskey']);
var longIsland = new Drink('Long Island', 'gin', 'party', 'strong', 'img/long-island.png', ['']);

for (var i = 0; i < allDrinks.length; i++) {
  var imgDiv = document.getElementById('allImg');
  var createUl = document.createElement('ul');
  imgDiv.appendChild(createUl);
  var createLiImages = document.createElement('li');
  createLiImages.innerHTML = allDrinks[i].imageSrc;
  createUl.appendChild(createLiImages);
  var liName = document.createElement('li');
  liName.textContent = allDrinks[i].drinkName;
  createUl.appendChild(liName);
  var liIngredients = document.createElement('li');
  createUl.appendChild(liIngredients);
  liIngredients.textContent = 'Ingredient List:';
  var ingUl = document.createElement('ul');
  createUl.appendChild(ingUl);
  for (var j = 0; j < allDrinks[i].ingredients.length; j++) {
    var ingredientsLi = document.createElement('li');
    ingredientsLi.textContent = allDrinks[i].ingredients[j];
    ingUl.appendChild(ingredientsLi);
  }
  var button = document.createElement('button');
  var tn = document.createTextNode('Add Review');
  button.appendChild(tn);
  button.class = 'review-buttons';
  imgDiv.appendChild(button);
}
<commit_msg>created dropdown and text area for reviews<commit_after>var allDrinks = [];

//object constructor
function Drink(drinkName, liquor, feeling, flavor, imageSrc, ingredients) {
  this.drinkName = drinkName;
  this.liquor = liquor;
  this.feeling = feeling;
  this.flavor = flavor;
  this.imageSrc = '';
  this.ingredients = ingredients;
  this.like = false;
  this.disLike = false;
  allDrinks.push(this);
}

var screwDriver = new Drink('Screwdriver', 'vodka', 'triedandtrue', 'sweet', 'img/screwdriver.png', ['Vodka', 'Orange Juice']);
var ginTonic = new Drink('Gin and Tonic', 'gin', 'triedandtrue', 'sour', 'img/gin-and-tonic.png', ['Gin', 'Tonic']);
var teqSunrise = new Drink('Tequila Sunrise', 'tequila', 'adventurous', 'sweet', 'img/tequila-sunrise.png', ['Tequila', 'Orange Juice', 'Cherry Grenadine']);
var amf = new Drink('Adios Mother F****r', 'tequila', 'party', 'strong', 'img/amf.png', ['Tequila', 'Vodka', 'Triple Sec', 'Gin', 'Blue Curacao', 'Sprite', 'Sour Mix']);
var birthdayCake = new Drink('Birthday Cake Shot', 'vodka', 'party', 'sweet', 'img/birthday-cake.png', ['Baileys Irish Cream', 'Smirnoff Cake Flavored Vodka', 'Whipped Cream', 'Sprinkles']);
var bloodyMary = new Drink('Bloody Mary', 'vodka', 'triedandtrue', 'strong', 'img/bloody-mary.png', ['']);
var blueMoon = new Drink('Blue Moon', 'beer', 'triedandtrue', 'strong', 'img/blue-moon.png', ['']);
var cadMargarita = new Drink('Cadillac Margarita', 'tequila', 'party', 'sour', 'img/cadillac-margarita.png', ['Tequila', 'Grand Marnier', 'Lime Juice', 'Salt on the Rim']);
var ciroc = new Drink('Ciroc', 'vodka', 'party', 'strong', 'img/ciroc.png', ['Ciroc Vodka']);
var cosmo = new Drink('Cosmo', 'vodka', 'triedandtrue', 'sour', 'img/cosmo.png', ['']);
var cubaLibre = new Drink('Cuba Libre', 'rum', 'triedandtrue', 'sweet', 'img/cuba-libre.png', ['Rum', 'Coke', 'Lime']);
var donJulio = new Drink('Don Julio', 'tequila', 'party', 'strong', 'img/don-julio.png', ['Don Julio Blanco Tequila']);
var duckFart = new Drink('Duck Fart', 'whiskey', 'party', 'sweet', 'img/duck-fart.png', ['Whiskey', 'Baileys Irish Cream', 'Kahlua']);
var elecTea = new Drink('Electric Iced Tea', 'gin', 'adventurous', 'strong', 'img/electric-iced-tea.png', ['Gin', 'Vodka', 'Light Rum', 'Tequila', 'Blue Curacao', 'Sour Mix', 'Lemon-lime Soda']);
var fourHorse = new Drink ('The Four Horsemen', 'whiskey', 'adventurous', 'strong', 'img/four-horsemen.png', ['']);
var ginAndJuice = new Drink('Gin and Juice', 'gin', 'triedandtrue', 'sweet', 'img/gin-and-juice.png', ['']);
var greyGoose = new Drink('Grey Goose', 'vodka', 'party', 'strong', 'img/grey-goose.png', ['']);
var hefe = new Drink('Hefeweizen', 'beer', 'triedandtrue', 'strong', 'img/hefe.png' , ['']);
var hennessy = new Drink('Hennessy', '', 'triedandtrue', 'strong', 'img/hennessy.png', ['']);
var ipa = new Drink('IPA', 'beer', 'triedandtrue', 'strong', 'img/ipa.png', ['']);
var jackDaniels = new Drink('Jack Daniels', 'whiskey', 'triedandtrue', 'strong', 'img/jack-daniels.png', ['Jack Daniels Whiskey']);
var jameson  = new Drink('Jameson', 'whiskey', 'triedandtrue', 'strong', 'img/jameson.png', ['Jameson Whiskey']);
var longIsland = new Drink('Long Island', 'gin', 'party', 'strong', 'img/long-island.png', ['']);

var imgDiv = document.getElementById('allImg');

for (var i = 0; i < allDrinks.length; i++) {
  var createUl = document.createElement('ul');
  imgDiv.appendChild(createUl);
  var createLiImages = document.createElement('li');
  createLiImages.innerHTML = allDrinks[i].imageSrc;
  createUl.appendChild(createLiImages);
  var liName = document.createElement('li');
  liName.textContent = allDrinks[i].drinkName;
  createUl.appendChild(liName);
  var liIngredients = document.createElement('li');
  createUl.appendChild(liIngredients);
  liIngredients.textContent = 'Ingredient List:';
  var ingUl = document.createElement('ul');
  createUl.appendChild(ingUl);
  for (var j = 0; j < allDrinks[i].ingredients.length; j++) {
    var ingredientsLi = document.createElement('li');
    ingredientsLi.textContent = allDrinks[i].ingredients[j];
    ingUl.appendChild(ingredientsLi);
  }
  // var button = document.createElement('button');
  // var tn = document.createTextNode('Add Review');
  // button.appendChild(tn);
  // button.class = 'review-buttons';
  // imgDiv.appendChild(button);

}
//form and dropdown with full list of drinks
var createForm = document.createElement('form');
imgDiv.appendChild(createForm);
var heading = document.createElement('h2');
heading.innerHTML = 'Add a Review';
createForm.appendChild(heading);
var linebreak = document.createElement('br');
createForm.appendChild(linebreak);
var drinkLabel = document.createElement('label');
drinkLabel.innerHTML = 'Name of Drink';
createForm.appendChild(drinkLabel);
var drinkSelect = document.createElement('select');
drinkSelect.id = 'drink-selection';

for (var c = 0; c < allDrinks.length; c++) {
  var options = document.createElement('option');
  options.value = allDrinks[c].drinkName;
  var theText = document.createTextNode(allDrinks[c].drinkName);
  options.appendChild(theText);
  drinkSelect.appendChild(options);

}

createForm.appendChild(drinkSelect);

var linebreakTwo = document.createElement('br');
createForm.appendChild(linebreakTwo);
var textLabel = document.createElement('label');
textLabel.innerHTML = 'Your Review';
createForm.appendChild(textLabel);
var textArea = document.createElement('textarea');
textArea.setAttribute('id', 'text-area');
createForm.appendChild(textArea);
<|endoftext|>


<commit_before>const openTutorial = () => {
  return { type: 'OPEN_TUTORIAL' };
};

const closeTutorial = () => {
  return { type: 'CLOSE_TUTORIAL' };
};

const viewNext = () => {
  return { type: 'VIEW_NEXT' };
};

const viewPrevious = () => {
  return { type: 'VIEW_PREVIOUS' };
};

const setTutorialLength = (len) => {
  return {
    type: 'SET_LENGTH',
    totalPopUps: len
  };
};

const toggleHelpHighlight = () => {
  return { type: 'TOGGLE_HELP_HIGHLIGHT' };
};

export default {
  openTutorial,
  closeTutorial,
  viewNext,
  setTutorialLength,
  viewPrevious,
  toggleHelpHighlight
};<commit_msg>Remove unecessary action<commit_after>const openTutorial = () => {
  return { type: 'OPEN_TUTORIAL' };
};

const closeTutorial = () => {
  return { type: 'CLOSE_TUTORIAL' };
};

const viewNext = () => {
  return { type: 'VIEW_NEXT' };
};

const viewPrevious = () => {
  return { type: 'VIEW_PREVIOUS' };
};

const toggleHelpHighlight = () => {
  return { type: 'TOGGLE_HELP_HIGHLIGHT' };
};

export default {
  openTutorial,
  closeTutorial,
  viewNext,
  viewPrevious,
  toggleHelpHighlight
};<|endoftext|>


<commit_before>"use strict";

// PAGE SETUP & EVENT LISTENERS

$(function() {
  setBookListeners();
  $("#new-author-field-set").hide()
  $("#select-categories-field-set").hide()
});

function setBookListeners() {
  $("#fiction-link").click(function(e) {
    displayCategory(e, "fiction");
  });
  $("#non-fiction-link").click(function(e) {
    displayCategory(e, "non_fiction")
  });
  $("#most-borrowed-link").click(function(e) {
     displayMostBorrowed(e)
  });
  $("#add-new-author-button").click(function(e) {
     displayAuthorFieldSet();
  });
  $("#select-categories-button").click(function(e) {
     displayCategoryFieldSet();
  });
};

//BOOK MODEL

function Book(book) {
  this.id = book.id
  this.title = book.title
  this.synopsis = book.synopsis
  this.available_copies = book.available_copies
  this.publication_year = book.publication_year
  this.classification = book.classification
  this.copies = book.copies
  this.author = book.author
  this.categories = book.categories
}

Book.prototype.listBook = function() {
  let html = `

${this.title} by ${this.author.name}

  `
  return html
}

// BOOKS#INDEX FUNCTIONS

function displayCategory(e, category) {
  e.preventDefault();
  $.get('/books.json', books => {
     booksList(books, category)
  });
};

function displayMostBorrowed(e) {
  e.preventDefault();
  $.get('/books/most_borrowed.json', books => {
    mostBorrowedList(books)
  });
};

function booksList(books, classification) {
  $("#books-index").html(unorderedList());
  let alphaBooks = alphabetize(books, "title")
  alphaBooks.forEach(book => {
    if (book.classification == classification) {
      let newBook = new Book(book);
      let html = newBook.listBook();
      $("#books-index ul").append(html);
    }
  })
};

function mostBorrowedList(books) {
  $("#books-index").html(orderedList());
  books.forEach(book => {
    let newBook = new Book(book);
    let html = newBook.listBook();
    $("#books-index ol").append(html);
  });
};

//BOOKS#NEW FUNCTIONS

function displayAuthorFieldSet() {
  $("#new-author-field-set").toggle()
};

function displayCategoryFieldSet(e) {
    $("#select-categories-field-set").toggle()
};
<commit_msg>Refactor event listeners<commit_after>"use strict";

// PAGE SETUP & EVENT LISTENERS

$(function() {
  setBookListeners();
  $("#new-author-field-set").hide()
  $("#select-categories-field-set").hide()
});

function setBookListeners() {
  $("#fiction-link").click(function() {
    displayCategory("fiction");
  });
  $("#non-fiction-link").click(function() {
    displayCategory("non_fiction")
  });
  $("#most-borrowed-link").click(function() {
     displayMostBorrowed()
  });
  $("#add-new-author-button").click(function() {
     displayAuthorFieldSet();
  });
  $("#select-categories-button").click(function() {
     displayCategoryFieldSet();
  });
};

//BOOK MODEL

function Book(book) {
  this.id = book.id
  this.title = book.title
  this.synopsis = book.synopsis
  this.available_copies = book.available_copies
  this.publication_year = book.publication_year
  this.classification = book.classification
  this.copies = book.copies
  this.author = book.author
  this.categories = book.categories
}

Book.prototype.listBook = function() {
  let html = `

${this.title} by ${this.author.name}

  `
  return html
}

// BOOKS#INDEX FUNCTIONS

function displayCategory(category) {
  $.get('/books.json', books => {
     booksList(books, category)
  });
};

function displayMostBorrowed() {
  $.get('/books/most_borrowed.json', books => {
    mostBorrowedList(books)
  });
};

function booksList(books, classification) {
  $("#books-index").html(unorderedList());
  let alphaBooks = alphabetize(books, "title")
  alphaBooks.forEach(book => {
    if (book.classification == classification) {
      let newBook = new Book(book);
      let html = newBook.listBook();
      $("#books-index ul").append(html);
    }
  })
};

function mostBorrowedList(books) {
  $("#books-index").html(orderedList());
  books.forEach(book => {
    let newBook = new Book(book);
    let html = newBook.listBook();
    $("#books-index ol").append(html);
  });
};

//BOOKS#NEW FUNCTIONS

function displayAuthorFieldSet() {
  $("#new-author-field-set").toggle()
};

function displayCategoryFieldSet(e) {
    $("#select-categories-field-set").toggle()
};
<|endoftext|>


<commit_before>require('./glsl-mode');

var glsl = require('../glsl/glsl');
var esprima = require('../vendor/esprima');
var Signals = require('../signals/signals');

window.esprima = esprima;

function Editor(editor, ctx, type) {
    Signals.call(this);

    this.editor = editor;
    this.type = type;

    this._completionContext = {};

    this.options = {
        check_timeout: 300
    };

    var keymap = {
        Tab: function(cm) {
            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
            cm.replaceSelection(spaces);
        },

        Backspace: function(cm) {
            var doc = cm.getDoc();
            var cur = doc.getCursor();

            if (!doc.somethingSelected() && cur.ch !== 0) {
                var line = doc.getLine(cur.line);

                var prefix = line.slice(0, cur.ch);

                for (var i = prefix.length - 1; i >= 0; i--) {
                    if (prefix[i] !== ' ') {
                        return CodeMirror.Pass;
                    }
                }

                var m = 0;

                if (cur.ch == line.length) {
                    m = line.length;
                } else {
                    var n = cm.getOption("indentUnit");

                    // all spaces, remove up to N indentUnit
                    m = prefix.length % n;

                    if (m == 0) {
                        m = n;
                    }
                }

                doc.replaceRange('', {line: cur.line, ch: cur.ch - m}, cur);

                if (cur.ch == line.length) {
                    CodeMirror.commands.delCharBefore(cm);
                    cur.line -= 1;
                }

                cm.indentLine(cur.line, null, true);

                return;
            }

            if (cur.ch === 0 && cur.line !== 0) {
                CodeMirror.commands.delCharBefore(cm);
                cm.indentLine(cur.line - 1, null, true);
                return;
            }

            return CodeMirror.Pass;
        },

        Enter: function(cm) {
            var doc = cm.getDoc();

            if (doc.somethingSelected()) {
                doc.replaceSelection('');
            }

            var cur = doc.getCursor();
            var line = doc.getLine(cur.line);

            for (var i = 0; i < line.length; i++) {
                if (line[i] != ' ') {
                    return CodeMirror.Pass;
                }
            }

            doc.replaceRange('', {line: cur.line, ch: 0}, {line: cur.line, ch: line.length});
            return CodeMirror.Pass;
        }
    };

    var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
    var mac = ios || /Mac/.test(navigator.platform);

    if (mac) {
        keymap['Cmd-Left'] = 'goLineLeftSmart';
    }

    if (type === glsl.source.VERTEX || type === glsl.source.FRAGMENT) {
        this._preprocessor_options = glsl.preprocessor.Preprocessor.options_from_context(ctx.gl);

        this.builtins = glsl.builtins.Builtins.create_for_context(ctx.gl, type);

        if (type === glsl.source.VERTEX) {
            this.editor.setOption('mode', 'glslv');
        } else {
            this.editor.setOption('mode', 'glslf');
        }
    } else {
        this.editor.setOption('mode', type);

        if (type === 'javascript') {
            keymap['.'] = (function(cm) {
                cm.replaceSelection('.');
                cm.indentLine(cm.getCursor().line);

                this._hint();
            }).bind(this);

            keymap['Ctrl-Space'] = (function(cm) {
                this._hint();
            }).bind(this);

            var mode = this.editor.getMode();
            var indentOrig = mode.indent;

            mode.indent = function(state, textAfter) {
                var ret = indentOrig.call(this, state, textAfter);

                if (textAfter[0] === '.') {
                    ret += editor.getOption('indentUnit');
                }

                return ret;
            };
        }
    }

    this._change_timeout = 0;

    this._internal_errors = {
        markers: [],
        errors: []
    };

    this._external_errors = {
        markers: [],
        errors: []
    };

    this._error_message = null;

    this.editor.on('change', this._on_change.bind(this));
    this.editor.on('cursorActivity', this._on_cursor_activity.bind(this));

    editor.addKeyMap(keymap);

    this.parsed = null;
    this._on_notify_parsed = this.register_signal('notify::parsed');
}

Editor.prototype = Object.create(Signals.prototype);
Editor.prototype.constructor = Editor;

Editor.prototype._hint = function() {
    this.editor.showHint({
        completeSingle: false,
        context: this._completionContext,
    });
}

Editor.prototype.completionContext = function(context) {
    if (!context) {
        return this._completionContext;
    }

    this._completionContext = context;
}

Editor.prototype._on_cursor_activity = function() {
    var doc = this.editor.getDoc();
    var cursor = this.cursor();
    var marks = doc.findMarksAt(cursor);

    var errs = [];

    for (var i = 0; i < marks.length; i++) {
        var m = marks[i];

        if (m.className === 'error') {
            errs.push(m.error);
        }
    }

    if (this._error_message !== null && (errs.length === 0 || this._error_message.errors[0] !== errs[0])) {
        this._error_message.widget.parentElement.removeChild(this._error_message.widget);
        this._error_message = null;
    }

    if (this._error_message === null && errs.length > 0) {
        var c = document.createElement('div');
        c.classList.add('error-message-container');

        var w = document.createElement('ul');
        w.classList.add('error-message');

        c.appendChild(w);

        for (var i = 0; i < errs.length; i++) {
            var li = document.createElement('li');
            li.textContent = errs[i].formatted_message();

            w.appendChild(li);
        }

        this._error_message = {
            errors: errs,
            widget: c
        };

        this.editor.addWidget({line: cursor.line, ch: 0}, this._error_message.widget, false, 'above');
        c.style.left = '';    }
}

Editor.prototype.focus = function() {
    this.editor.focus();
}

Editor.prototype.cursor = function(v) {
    if (typeof v === 'undefined') {
        return this.editor.getCursor();
    }

    this.editor.setCursor(v);
}

Editor.prototype.value = function(v) {
    if (typeof v === 'undefined') {
        return this.editor.getValue();
    }

    this.editor.setValue(v);

    if (this._change_timeout !== 0) {
        clearTimeout(this._change_timeout);
        this._change_timeout = 0;
    }

    this._on_change_timeout();
}

Editor.prototype.history = function(v) {
    if (typeof v === 'undefined') {
        return this.editor.getHistory();
    }

    this.editor.setHistory(v);
}

Editor.prototype._make_loc = function(l) {
    return {line: l.line - 1, ch: l.column - 1};
}

Editor.prototype._on_change_timeout = function() {
    this._change_timeout = 0;

    if (this.type === glsl.source.VERTEX || this.type === glsl.source.FRAGMENT) {
        this._on_change_timeout_glsl();
    } else {
        this._on_change_timeout_js();
    }
}

Editor.prototype._on_change_timeout_js = function() {
    try {
        esprima.parse(this.value());
    } catch (e) {
        this.runtime_error({
            message: e.description,
            location: {
                line: e.lineNumber,
                column: e.column
            }
        });

        return;
    }

    this._process_errors(this._internal_errors, []);
}

Editor.prototype._on_change_timeout_glsl = function() {
    this.parsed = new glsl.ast.Parser(this.value(), this.type, {
        preprocessor: this._preprocessor_options
    });

    glsl.sst.Annotate(this.parsed, {
        builtins: this.builtins
    });

    this._process_errors(this._internal_errors, this.parsed.errors());
    this._on_notify_parsed();
}

Editor.prototype.runtime_error = function(error) {
    var tok = this.editor.getTokenAt(CodeMirror.Pos(error.location.line - 1, error.location.column));

    var err = {
        location: {
            start: {
                line: error.location.line,
                column: tok.start + 1
            },
            end: {
                line: error.location.line,
                column: tok.end + 1
            }
        },

        message: error.message,

        formatted_message: function() {
            return error.location.line + '.' + (tok.start + 1) + '-' + error.location.line + '.' + (tok.end + 1) + ': ' + error.message;
        }
    };

    this._process_errors(this._internal_errors, [err]);
}

Editor.prototype.external_errors = function(errors) {
    this._process_errors(this._external_errors, errors);
}

Editor.prototype._process_errors = function(ctx, errors) {
    var doc = this.editor.getDoc();

    ctx.errors = errors.slice(0);

    for (var i = 0; i < ctx.markers.length; i++) {
        ctx.markers[i].clear();
    }

    ctx.markers = [];

    if (this._internal_errors.errors.length === 0 && this._external_errors.errors.length === 0) {
        this.editor.display.wrapper.classList.remove('error');
    } else {
        this.editor.display.wrapper.classList.add('error');
    }

    for (var i = 0; i < errors.length; i++) {
        var e = errors[i];
        var l = e.location.start.line - 1;

        var m = doc.markText(this._make_loc(e.location.start),
                             this._make_loc(e.location.end), {
                                className: 'error',
                                title: e.message,
                                inclusiveLeft: false,
                                inclusiveRight: true
                             });

        m.error = e;
        ctx.markers.push(m);
    }

    this._on_cursor_activity();
}

Editor.prototype._on_change = function(c, o) {
    if (this._change_timeout !== 0) {
        clearTimeout(this._change_timeout);
        this._change_timeout = 0;
    }

    this._change_timeout = setTimeout(this._on_change_timeout.bind(this), this.options.check_timeout);
}

module.exports = Editor;

// vi:ts=4:et
<commit_msg>Store limited amount of history<commit_after>require('./glsl-mode');

var glsl = require('../glsl/glsl');
var esprima = require('../vendor/esprima');
var Signals = require('../signals/signals');

window.esprima = esprima;

function Editor(editor, ctx, type) {
    Signals.call(this);

    this.editor = editor;
    this.type = type;

    this._completionContext = {};

    this.options = {
        check_timeout: 300,
        history_size: 100
    };

    var keymap = {
        Tab: function(cm) {
            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
            cm.replaceSelection(spaces);
        },

        Backspace: function(cm) {
            var doc = cm.getDoc();
            var cur = doc.getCursor();

            if (!doc.somethingSelected() && cur.ch !== 0) {
                var line = doc.getLine(cur.line);

                var prefix = line.slice(0, cur.ch);

                for (var i = prefix.length - 1; i >= 0; i--) {
                    if (prefix[i] !== ' ') {
                        return CodeMirror.Pass;
                    }
                }

                var m = 0;

                if (cur.ch == line.length) {
                    m = line.length;
                } else {
                    var n = cm.getOption("indentUnit");

                    // all spaces, remove up to N indentUnit
                    m = prefix.length % n;

                    if (m == 0) {
                        m = n;
                    }
                }

                doc.replaceRange('', {line: cur.line, ch: cur.ch - m}, cur);

                if (cur.ch == line.length) {
                    CodeMirror.commands.delCharBefore(cm);
                    cur.line -= 1;
                }

                cm.indentLine(cur.line, null, true);

                return;
            }

            if (cur.ch === 0 && cur.line !== 0) {
                CodeMirror.commands.delCharBefore(cm);
                cm.indentLine(cur.line - 1, null, true);
                return;
            }

            return CodeMirror.Pass;
        },

        Enter: function(cm) {
            var doc = cm.getDoc();

            if (doc.somethingSelected()) {
                doc.replaceSelection('');
            }

            var cur = doc.getCursor();
            var line = doc.getLine(cur.line);

            for (var i = 0; i < line.length; i++) {
                if (line[i] != ' ') {
                    return CodeMirror.Pass;
                }
            }

            doc.replaceRange('', {line: cur.line, ch: 0}, {line: cur.line, ch: line.length});
            return CodeMirror.Pass;
        }
    };

    var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
    var mac = ios || /Mac/.test(navigator.platform);

    if (mac) {
        keymap['Cmd-Left'] = 'goLineLeftSmart';
    }

    if (type === glsl.source.VERTEX || type === glsl.source.FRAGMENT) {
        this._preprocessor_options = glsl.preprocessor.Preprocessor.options_from_context(ctx.gl);

        this.builtins = glsl.builtins.Builtins.create_for_context(ctx.gl, type);

        if (type === glsl.source.VERTEX) {
            this.editor.setOption('mode', 'glslv');
        } else {
            this.editor.setOption('mode', 'glslf');
        }
    } else {
        this.editor.setOption('mode', type);

        if (type === 'javascript') {
            keymap['.'] = (function(cm) {
                cm.replaceSelection('.');
                cm.indentLine(cm.getCursor().line);

                this._hint();
            }).bind(this);

            keymap['Ctrl-Space'] = (function(cm) {
                this._hint();
            }).bind(this);

            var mode = this.editor.getMode();
            var indentOrig = mode.indent;

            mode.indent = function(state, textAfter) {
                var ret = indentOrig.call(this, state, textAfter);

                if (textAfter[0] === '.') {
                    ret += editor.getOption('indentUnit');
                }

                return ret;
            };
        }
    }

    this._change_timeout = 0;

    this._internal_errors = {
        markers: [],
        errors: []
    };

    this._external_errors = {
        markers: [],
        errors: []
    };

    this._error_message = null;

    this.editor.on('change', this._on_change.bind(this));
    this.editor.on('cursorActivity', this._on_cursor_activity.bind(this));

    editor.addKeyMap(keymap);

    this.parsed = null;
    this._on_notify_parsed = this.register_signal('notify::parsed');
}

Editor.prototype = Object.create(Signals.prototype);
Editor.prototype.constructor = Editor;

Editor.prototype._hint = function() {
    this.editor.showHint({
        completeSingle: false,
        context: this._completionContext,
    });
}

Editor.prototype.completionContext = function(context) {
    if (!context) {
        return this._completionContext;
    }

    this._completionContext = context;
}

Editor.prototype._on_cursor_activity = function() {
    var doc = this.editor.getDoc();
    var cursor = this.cursor();
    var marks = doc.findMarksAt(cursor);

    var errs = [];

    for (var i = 0; i < marks.length; i++) {
        var m = marks[i];

        if (m.className === 'error') {
            errs.push(m.error);
        }
    }

    if (this._error_message !== null && (errs.length === 0 || this._error_message.errors[0] !== errs[0])) {
        this._error_message.widget.parentElement.removeChild(this._error_message.widget);
        this._error_message = null;
    }

    if (this._error_message === null && errs.length > 0) {
        var c = document.createElement('div');
        c.classList.add('error-message-container');

        var w = document.createElement('ul');
        w.classList.add('error-message');

        c.appendChild(w);

        for (var i = 0; i < errs.length; i++) {
            var li = document.createElement('li');
            li.textContent = errs[i].formatted_message();

            w.appendChild(li);
        }

        this._error_message = {
            errors: errs,
            widget: c
        };

        this.editor.addWidget({line: cursor.line, ch: 0}, this._error_message.widget, false, 'above');
        c.style.left = '';    }
}

Editor.prototype.focus = function() {
    this.editor.focus();
}

Editor.prototype.cursor = function(v) {
    if (typeof v === 'undefined') {
        return this.editor.getCursor();
    }

    this.editor.setCursor(v);
}

Editor.prototype.value = function(v) {
    if (typeof v === 'undefined') {
        return this.editor.getValue();
    }

    this.editor.setValue(v);

    if (this._change_timeout !== 0) {
        clearTimeout(this._change_timeout);
        this._change_timeout = 0;
    }

    this._on_change_timeout();
}

Editor.prototype.history = function(v) {
    if (typeof v === 'undefined') {
        var hist = this.editor.getHistory();

        var ret = {
            done: [],
            undone: []
        };

        if (hist.undone.length > this.options.history_size) {
            ret.undone = hist.undone.slice(0, this.options.history_size);
        } else {
            ret.undone = hist.undone;
        }

        if (ret.undone.length < this.options.history_size) {
            var rem = this.options.history_size - ret.undone.length;

            if (hist.done.length > rem) {
                ret.done = hist.done.slice(0, rem);
            } else {
                ret.done = hist.done;
            }
        }

        return ret;
    }

    this.editor.setHistory(v);
}

Editor.prototype._make_loc = function(l) {
    return {line: l.line - 1, ch: l.column - 1};
}

Editor.prototype._on_change_timeout = function() {
    this._change_timeout = 0;

    if (this.type === glsl.source.VERTEX || this.type === glsl.source.FRAGMENT) {
        this._on_change_timeout_glsl();
    } else {
        this._on_change_timeout_js();
    }
}

Editor.prototype._on_change_timeout_js = function() {
    try {
        esprima.parse(this.value());
    } catch (e) {
        this.runtime_error({
            message: e.description,
            location: {
                line: e.lineNumber,
                column: e.column
            }
        });

        return;
    }

    this._process_errors(this._internal_errors, []);
}

Editor.prototype._on_change_timeout_glsl = function() {
    this.parsed = new glsl.ast.Parser(this.value(), this.type, {
        preprocessor: this._preprocessor_options
    });

    glsl.sst.Annotate(this.parsed, {
        builtins: this.builtins
    });

    this._process_errors(this._internal_errors, this.parsed.errors());
    this._on_notify_parsed();
}

Editor.prototype.runtime_error = function(error) {
    var tok = this.editor.getTokenAt(CodeMirror.Pos(error.location.line - 1, error.location.column));

    var err = {
        location: {
            start: {
                line: error.location.line,
                column: tok.start + 1
            },
            end: {
                line: error.location.line,
                column: tok.end + 1
            }
        },

        message: error.message,

        formatted_message: function() {
            return error.location.line + '.' + (tok.start + 1) + '-' + error.location.line + '.' + (tok.end + 1) + ': ' + error.message;
        }
    };

    this._process_errors(this._internal_errors, [err]);
}

Editor.prototype.external_errors = function(errors) {
    this._process_errors(this._external_errors, errors);
}

Editor.prototype._process_errors = function(ctx, errors) {
    var doc = this.editor.getDoc();

    ctx.errors = errors.slice(0);

    for (var i = 0; i < ctx.markers.length; i++) {
        ctx.markers[i].clear();
    }

    ctx.markers = [];

    if (this._internal_errors.errors.length === 0 && this._external_errors.errors.length === 0) {
        this.editor.display.wrapper.classList.remove('error');
    } else {
        this.editor.display.wrapper.classList.add('error');
    }

    for (var i = 0; i < errors.length; i++) {
        var e = errors[i];
        var l = e.location.start.line - 1;

        var m = doc.markText(this._make_loc(e.location.start),
                             this._make_loc(e.location.end), {
                                className: 'error',
                                title: e.message,
                                inclusiveLeft: false,
                                inclusiveRight: true
                             });

        m.error = e;
        ctx.markers.push(m);
    }

    this._on_cursor_activity();
}

Editor.prototype._on_change = function(c, o) {
    if (this._change_timeout !== 0) {
        clearTimeout(this._change_timeout);
        this._change_timeout = 0;
    }

    this._change_timeout = setTimeout(this._on_change_timeout.bind(this), this.options.check_timeout);
}

module.exports = Editor;

// vi:ts=4:et
<|endoftext|>