Source: frontend/nav/Nav.js

//Brennan Wilkes

//Imports
import React from "react";
import "../bootstrap-import.js";
import { FaSlidersH, FaPlusSquare, FaMinusSquare } from "react-icons/fa";

import "./nav.css";
import "./rangeSlider.css";
import FloatingLabel from "../floatingLabel/FloatingLabel.js";

//Capitalization one liner with regex
const capitalize = s => String(s).toLowerCase().replace(/(?:^|\s|["'([{])+\S/g, l => l.toUpperCase());

//Un-capitalizes non-primary words
//What is my english lol
const capitalizePrimary = s => capitalize(s).replace("And","and").replace("Is","is").replace("The","the");

/**
	Username button
	@class
	@memberof frontend
	@extends React.Component
*/
class User extends React.Component{

	/**
		Renders out a button with the provided name, or "Invalid User" as a default
	*/
	render(){
		return <>
			<button type="button" className="btn btn-outline-secondary btn-within-collapsable">{
				(!this.props.name) || this.props.name.length < 1 ? "Invalid User": this.props.name
			}</button>;
		</>
	}
}

/**
	A custom controlled slider input
	@class
	@memberof frontend
	@extends React.Component
*/
class RangeSlider extends React.Component{

	/**
		Initializes state
		@param {any[]} props Should have a default value, label , id, scale and output multipler
	*/
	constructor(props){
		super(props);
		this.state = {
			val: (this.props.default ? this.props.default : 0)
		}
	}

	/**
		Renders a div wrapped range input, with auto updating output value.
		Manipulates output according to prop parameters
	*/
	render(){
		return <>
			<div className="range">
				<output className="output-label" >{this.props.label}</output>
				<input
					type="range"
					className="form-control py-0"
					id={this.props.id}
					name="range"
					value={this.state.val}
					onChange={(event) => {

						let rounded = Math.round(event.target.value/this.props.scale)*this.props.scale;
						let prev = this.state.val;
						this.setState({val:rounded});
						$(`#${this.props.id}-output`)[0].value=rounded * (this.props.outputMultipler?this.props.outputMultipler: 1);
						$(`#${this.props.id}`)[0].value=rounded;

						if(rounded !== prev){
							this.props.onChange();
						}
				}} />
				<output
					className="output-val"
					id={`${this.props.id}-output`}
					value={this.state.val * (this.props.outputMultipler?this.props.outputMultipler: 1)} >{
						this.state.val * (this.props.outputMultipler?this.props.outputMultipler: 1)
				}</output>
			</div>
		</>
	}
}

/**
	Auto detailed search button
	@class
	@memberof frontend
	@extends React.Component
*/
class AdvancedSearchButton extends React.Component{

	/**
		Renders a detailed button with a Fa Slider icon if a target is provided
		Will call expandCallback on click
	*/
	render(){
		if(!this.props.target){
			return <></>;
		}

		return <>
			<button
				className="AdvancedSearch-btn btn btn-light py-2"
				type="button"
				onClick={this.props.expandCallback}
				data-toggle="collapse"
				data-target={`#${this.props.target}`}>
				<FaSlidersH />
			</button>
		</>;
	}
}

/**
	A self controlling, auto additonal input controller
	@class
	@memberof frontend
	@extends React.Component
*/
class MultiInput extends React.Component{

	/**
		Initializes state
		@param {any[]} Must contain an identifier and on change callback
	*/
	constructor(props){
		super(props);

		this.state = {
			copies: []
		};
	}

	/**
		Renders out a list of inputs with auto + and - buttons for adjusting the amount of inputs.
		Will automatically control values, and run on change callbacks when any change.
	*/
	render(){
		return <>
			<div className="multiInput">
				<div>
					<FloatingLabel
						type="text"
						label="Ingredient"
						className={`form-control ${this.props.identifier}`}
						onChange={this.props.callback} />

					<button className="btn btn-success" onClick={event => {
						if(this.state.copies.length < 7){
							this.setState({copies:[...this.state.copies,""]});
						}
					}}><FaPlusSquare size={28} /></button>
				</div>
				{
					this.state.copies.map((copy,i) => <>
						<div>
							<input
								className={`form-control ${this.props.identifier} ${this.props.identifier}-c${i}`}
								value={this.state.copies[i]}
								onChange={event=>{
									let copies = this.state.copies;
									copies[i] = event.target.value;
									this.setState({copies:copies});

									this.props.callback();
							}} />

							<button className="btn btn-danger" onClick={event=>{
								let copies = this.state.copies.slice(0);
								copies.splice(i,1);
								this.props.callback(i);
								this.setState({copies:copies});

							}}><FaMinusSquare size={28} /></button>
						</div>
					</>)
				}
			</div>
		</>;
	}
}

/**
	Collapsable advanced search pannel with all the parameters required to search for drinks.
	@class
	@memberof frontend
	@extends React.Component
*/
class AdvancedSearchPannel extends React.Component{

	/**
		Binds methods
		@param {any[]} props
	*/
	constructor(props){
		super(props);
		this.updateSubmit = this.updateSubmit.bind(this);
	}

	/**
		Sets up event listener for hitting enter while in an input.
		On enter, will unfocus the target input.
	*/
	componentDidMount(){
		$("input").keypress(function(e) {
			if(e.which == 10 || e.which == 13) {
				$(e.target).blur();
			}
		});
	}

	/**
		Parses and manipulates input values before providing them to the given onchange callback
		@param {object} event Sneakilly can be a index to pop from the contains array
	*/
	updateSubmit(event){

		//Get contains values as an array
		let contains = $(".containsIng");
		let containsVal = []
		for(let i=0;i<contains.length;i++){
			containsVal.push(contains[i].value);
		}

		//Special callback param for removing last contains value
		if(event!==undefined && typeof(event)=="number" && event < containsVal.length){
			containsVal.splice(event,1);
		}

		//Run callback with manipulated values
		this.props.callback({
			name: $("#advName")[0].value,
			contains: containsVal,
			mixMethod: $("#mixMethod")[0].value,
			onIce: $("#onIce")[0].value,
			orderedBy: $("#orderedBy")[0].value,
			isSweet: $("#isSweet")[0].value,
			liquor: $("#liquor")[0].value,
			percentage: parseInt($("#percentage")[0].value),
			rating: parseInt($("#rating")[0].value)/10 - 2,
			price: parseInt($("#price")[0].value)/4,
			glass: $("#glass")[0].value,
		});
	}

	/**
		Renders out the full panel. Contains all inputs and connecting callback methods.
		Is fully collapsable and mobile friendly
	*/
	render(){
		return <>
			<form className="collapse bg-dark form-group py-3" id={this.props.id} onSubmit={event=>{
				event.preventDefault();
				setTimeout(()=>{
					$("#menu").animate({
						marginTop: `${$("#nav-wrapper").height() + 50}px`
					},200);
				},250);
			}}>
				<div className="row py-2 justify-content-md-center">
					<div className="col-md-9">
						<FloatingLabel
							id="advName"
							type="text"
							label="Name"
							className="form-control"
							onChange={this.updateSubmit} />
					</div>
				</div>
				<div className="row py-2 justify-content-md-center">
					<div className="col-md-9">
						<MultiInput identifier="containsIng" callback={this.updateSubmit} />
					</div>
				</div>
				<div className="row py-2 justify-content-md-center">
					<div className="col-md-3">
						<select className="form-control" id="onIce" onChange={this.updateSubmit} >
							<option value="">Any ice amount</option>
							<option value="1">Rocks</option>
							<option value="0">Neat</option>
						</select>
					</div>
					<div className="col-md-3">
						<select className="form-control" id="mixMethod" onChange={this.updateSubmit}>
							<option value="">All mix methods</option>
							<option value="Shaken">Shaken</option>
							<option value="Stirred">Stirred</option>
						</select>
					</div>
					<div className="col-md-3">
						<select className="form-control" id="isSweet" onChange={this.updateSubmit} >
							<option value="">All mixers</option>
							<option value="1">Sweet Juices</option>
							<option value="0">Savoury Juices</option>
						</select>
					</div>
				</div>
				<div className="row py-2 justify-content-md-center">
					<div className="col-md-3">
						<select className="form-control" id="liquor" onChange={this.updateSubmit} >
							<option value="">All bases</option>
							<option value="1">Liquor</option>
							<option value="0">Liqueur</option>
						</select>
					</div>
					<div className="col-md-6">
						<RangeSlider label="Minimum Percentage" scale={5} id="percentage" onChange={this.updateSubmit} />
					</div>
				</div>
				<div className="row py-2 justify-content-md-center">
					<div className="col-md-3">
						<select className="form-control" id="orderedBy" onChange={this.updateSubmit} >
							<option value="">All menu items</option>
							<option value={this.props.name}>Ordered by me ({this.props.name})</option>
							<option value="_">Ordered by anyone</option>
						</select>
					</div>
					<div className="col-md-6">
						<RangeSlider label="Minimum rating" outputMultipler={0.05} scale={20} id="rating" onChange={this.updateSubmit} />
					</div>
				</div>
				<div className="row py-2 justify-content-md-center">
					<div className="col-md-3">
						<select className="form-control" id="glass" onChange={this.updateSubmit} >
							<option value="">All Glasses</option>
							{
								this.props.glasses.map(g => <option value={g.id}>{capitalizePrimary(g.name)}</option>)
							}

						</select>
					</div>
					<div className="col-md-6">
						<RangeSlider label="Maximum Price" default={100} outputMultipler={0.25} scale={4} id="price" onChange={this.updateSubmit} />
					</div>
				</div>
			</form>
		</>
	}
}

/**
	Self contained and controlling search bar
	@class
	@memberof frontend
	@extends React.Component
*/
class Search extends React.Component{

	/**
		Renders out a search bar input siblinged to an advanced search toggler.
		Connects them with callback methods.
	*/
	render(){
		return <>
			<form className="form-inline p-0 mx-3" onSubmit={event => {
				event.preventDefault();
				$(event.target).children().blur();
			}}>
				<AdvancedSearchButton target={this.props.advancedSearch} expandCallback={this.props.advancedSearchCallback} />
				<FloatingLabel
					type="text"
					label="Search"
					className="form-control"
					onChange={event => {
						this.props.callback(event.target.value);
					}} />
			</form>
		</>;
	}
}

/**
	Main navigation component for the menu
	@class
	@memberof frontend
	@extends React.Component
*/
class Nav extends React.Component{

	/**
		Renders out a mobile friendly, collapsable nav bar with all search elements nested correctly.
		Connects navigation elements with callback methods
	*/
	render(){
		return <>
			<div className="fixed-top" id="nav-wrapper">
				<nav className="navbar navbar-expand-md navbar-dark bg-dark py-3">
					<a className="navbar-brand ml-3 nav-brand-item" href=".">Every Last Drop</a>
					<button className="navbar-toggler mr-3" type="button" data-toggle="collapse" data-target="#navbarCollapse" onClick={event => {
						$("#advancedOptions").removeClass("show");
						this.props.advancedSearchToggleCallback();
					}}>
						<span className="navbar-toggler-icon"></span>
					</button>
					<div className="collapse navbar-collapse" id="navbarCollapse">
						<ul className="mr-3 ml-3 navbar-nav mr-auto">
							<li className="nav-item active">
								<a className="nav-link" href=".">Home</a>
							</li>
							<li className="nav-item">
								<User name={this.props.user} />
							</li>
						</ul>
						<Search callback={this.props.searchCallback} advancedSearchCallback={this.props.advancedSearchToggleCallback} advancedSearch="advancedOptions"/>
					</div>
				</nav>
				<AdvancedSearchPannel id="advancedOptions" glasses={this.props.glasses} name={this.props.user} callback={this.props.advSearchCallback}/>
			</div>
		</>;
	}
}

export default Nav;