'use strict'; const { DragDropContext, Draggable, Droppable } = window.ReactBeautifulDnd; const data = window.shop_data; const productStyle = (style, snapshot, removeAnim, hovered, selected) => { const custom = { opacity: snapshot.isDragging ? .7 : 1, backgroundColor: (hovered || selected) ? '#eae7f7' : 'initial', }; if (!snapshot.isDropAnimating) { return { ...style, ...custom}; } if (removeAnim) { // cannot be 0, but make it super tiny custom.transitionDuration = '0.001s'; } return { ...style, ...custom, }; } const cartStyle = (style, snapshot) => { const isDraggingOver = snapshot.isDraggingOver; return { ...style, ...{ backgroundColor: isDraggingOver ? '#f2f2f2' : '#f9f9f9', border: isDraggingOver ? '1px dashed #ccc' : '0', }, }; } const nbrConnectorsStyle = (data) => { if (!data || !data.nbrCurrentSlot) { return {}; } let p = data.nbrCurrentSlot * 100 / data.nbrSlotMax; if (p > 100) { p = 100; } return { width: `${p}%`, } }; const nbrClocksStyle = (data) => { if (!data || !data.nbrCurrentClock) { return {}; } let p = data.nbrCurrentClock * 100 / data.nbrClockMax; if (p > 100) { p = 100; } return { width: `${p}%`, } }; const copy = ( model, source, destination, droppableSource, droppableDestination ) => { const sourceClone = Array.from(source.itemIds); const destClone = Array.from(destination.items); const item = sourceClone[droppableSource.index]; destClone.splice(droppableDestination.index, 0, { ...model[item], id: uuidv4(), }); return destClone; }; const reorder = (list, startIndex, endIndex) => { const result = Array.from(list); const [removed] = result.splice(startIndex, 1); result.splice(endIndex, 0, removed); return result; }; const remove = (list, startIndex) => { const result = Array.from(list); result.splice(startIndex, 1); return result; }; const nbrOccupiedSlotsInCrate = (items) => { return items.reduce((prev, next) => { return prev + (next.hp === 8 ? 2 : 1); }, 0); }; function formatMoney(amount, decimalCount = 2, decimal = ".", thousands = ",") { // https://stackoverflow.com/questions/149055/how-can-i-format-numbers-as-currency-string-in-javascript // changes: return amount if error in order to avoid empty value try { decimalCount = Math.abs(decimalCount); decimalCount = isNaN(decimalCount) ? 2 : decimalCount; const negativeSign = amount < 0 ? "-" : ""; let i = parseInt(amount = Math.abs(Number(amount) || 0).toFixed(decimalCount)).toString(); let j = (i.length > 3) ? i.length % 3 : 0; return negativeSign + (j ? i.substr(0, j) + thousands : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : ""); } catch (e) { return amount; } }; /** * Component that provides a base layout (aside/main) for the page. */ class Layout extends React.PureComponent { static get propTypes() { return { aside: PropTypes.any, main: PropTypes.any, mobileSideMenuShouldOpen: PropTypes.bool, onClickToggleMobileSideMenu: PropTypes.func, }; } static get defaultProps() { return { mobileSideMenuShouldOpen: false, }; } render() { const { aside, main, mobileSideMenuShouldOpen, onClickToggleMobileSideMenu } = this.props; return (
{mobileSideMenuShouldOpen ? (
{main}
) : (
{main}
)}
); } } /** * Component that renders a product. * Used in the aside (e.g backlog of product) */ class ProductItem extends React.PureComponent { static get propTypes() { return { id: PropTypes.string.isRequired, index: PropTypes.number.isRequired, name: PropTypes.string.isRequired, price: PropTypes.number.isRequired, currency: PropTypes.string.isRequired, image: PropTypes.string.isRequired, specs: PropTypes.array, onClickAddItem: PropTypes.func, }; } constructor(props) { super(props); this.handleOnClickAddItem = this.handleOnClickAddItem.bind(this); } handleOnClickAddItem(index, e) { if (this.props.onClickAddItem) { this.props.onClickAddItem(index); } e.preventDefault(); } render() { const { id, index, name, price, currency, image, specs, } = this.props; const render_specs = (specs && specs.length > 0 && ( )); return (

{name}

{`${currency} ${formatMoney(price)}`}
{render_specs}
{(provided, snapshot) => ( {/* Allows to simulate a clone */} {snapshot.isDragging && ( )} )}
); } } /** * Component that renders a product. * Used in the crate */ class ProductCartItem extends React.PureComponent { static get propTypes() { return { hovered: PropTypes.bool, index: PropTypes.number.isRequired, model: PropTypes.object.isRequired, data: PropTypes.object, onToggleProgress: PropTypes.func, onToggleWarning: PropTypes.func, onToggleOverlayRemove: PropTypes.func, onClickRemoveItem: PropTypes.func, }; } static get defaultProps() { return { hovered: false, }; } constructor(props) { super(props); this.handleOnMouseEnterItem = this.handleOnMouseEnterItem.bind(this); this.handleOnMouseLeaveItem = this.handleOnMouseLeaveItem.bind(this); this.handleOnMouseEnterWarningItem = this.handleOnMouseEnterWarningItem.bind(this); this.handleOnMouseLeaveWarningItem = this.handleOnMouseLeaveWarningItem.bind(this); this.handleOnMouseEnterRemoveItem = this.handleOnMouseEnterRemoveItem.bind(this); this.handleOnMouseLeaveRemoveItem = this.handleOnMouseLeaveRemoveItem.bind(this); this.handleOnClickRemoveItem = this.handleOnClickRemoveItem.bind(this); } handleOnMouseEnterItem(index, e) { if (this.props.onToggleProgress) { this.props.onToggleProgress(index, true); } e.preventDefault(); } handleOnMouseLeaveItem(index, e) { if (this.props.onToggleProgress) { this.props.onToggleProgress(index, false); } e.preventDefault(); } handleOnMouseEnterWarningItem(index, isWarning, e) { if (!isWarning) { return; } if (this.props.onToggleWarning) { this.props.onToggleWarning(index, true); } e.preventDefault(); } handleOnMouseLeaveWarningItem(index, isWarning, e) { if (!isWarning) { return; } if (this.props.onToggleWarning) { this.props.onToggleWarning(index, false); } e.preventDefault(); } handleOnMouseEnterRemoveItem(index, e) { if (this.props.onToggleOverlayRemove) { this.props.onToggleOverlayRemove(index, true); } e.preventDefault(); } handleOnMouseLeaveRemoveItem(index, e) { if (this.props.onToggleOverlayRemove) { this.props.onToggleOverlayRemove(index, false); } e.preventDefault(); } handleOnClickRemoveItem(index, e) { if (this.props.onClickRemoveItem) { this.props.onClickRemoveItem(index); } } render() { const { hovered, model, data, index, } = this.props; let warning; if (data && data.warnings) { const warningsKeys = Object.keys(data.warnings); if (warningsKeys && warningsKeys.length > 0) { // we display only the first warning warning = data.warnings[warningsKeys[0]]; } } let render_progress; if (model.showProgress && data) { switch(model.type) { case 'kasli': case 'kasli-backplane': render_progress = (

{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}

{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}

); break; case 'zotino': case 'hd68': render_progress = (

{`${data.nbrCurrentSlot}/${model.nbrSlotMax} connectors used`}

); break; case 'clocker': render_progress = (

{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}

); break; default: break; } } return ( {(provided, snapshot) => (
{/* warning container */}
{warning && ( )} {warning && model.showWarning && (

{warning.message}

)}
{model.name}
{/* remove container */}
rm

Remove

{/* progression container */}
{model.nbrSlotMax > 0 && (
)} {model.nbrClockMax > 0 && (
)} {/* progress info when mouse over */} {render_progress}
)}
); } } /** * Component that displays a placeholder inside crate. * Allows to display how it remains space for the current crate. */ class FakePlaceholder extends React.PureComponent { static get propTypes() { return { isDraggingOver: PropTypes.bool, nbrSlots: PropTypes.number.isRequired, items: PropTypes.array.isRequired, }; } render() { const { isDraggingOver, nbrSlots, items, } = this.props; const fakePlaceholder = []; const nbrOccupied = nbrOccupiedSlotsInCrate(items); for (var i = (nbrSlots - nbrOccupied); i > 0; i--) { fakePlaceholder.push(
); } return ( {fakePlaceholder} ); } } /** * Component that displays a list of */ class Cart extends React.PureComponent { static get propTypes() { return { nbrSlots: PropTypes.number, itemHovered: PropTypes.string, data: PropTypes.object.isRequired, onToggleProgress: PropTypes.func, onToggleWarning: PropTypes.func, onToggleOverlayRemove: PropTypes.func, onClickRemoveItem: PropTypes.func, }; } render() { const { nbrSlots, itemHovered, data, onToggleProgress, onToggleWarning, onToggleOverlayRemove, onClickRemoveItem, } = this.props; const products = data.items.map((item, index) => { let itemData; if (data.itemsData && index in data.itemsData) { itemData = data.itemsData[index]; } return ( ); }); return ( {(provided, snapshot) => (
{products} {provided.placeholder && (
{provided.placeholder}
)}
)}
); } } /** * Component that displays crate modes */ class CrateMode extends React.PureComponent { static get propTypes() { return { items: PropTypes.array.isRequired, mode: PropTypes.string.isRequired, onClickMode: PropTypes.func, }; } constructor(props) { super(props); this.handleOnClickMode = this.handleOnClickMode.bind(this); } handleOnClickMode(mode, e) { if (this.props.onClickMode) { this.props.onClickMode(mode); } e.preventDefault(); } render() { const { mode, items, } = this.props; return (
{items.map(item => ( {item.name} ))}
); } } /** * Component that displays the main crate with reminder rules. * It includes and rules */ class Crate extends React.PureComponent { static get propTypes() { return { rules: PropTypes.array, cart: PropTypes.element, }; } render() { const { rules, cart, } = this.props; return (
{cart} {rules && rules.length > 0 && (
{rules.map((rule, index) => (

{rule.name}: {rule.message}

))}
)}
); } } /** * Component that renders all things for order. * It acts like-a layout, this component do nothing more. */ class OrderPanel extends React.PureComponent { static get propTypes() { return { title: PropTypes.string, description: PropTypes.string, crateMode: PropTypes.element, crate: PropTypes.element, summaryPrice: PropTypes.element, form: PropTypes.element, isMobile: PropTypes.bool, onClickToggleMobileSideMenu: PropTypes.func, }; } render() { const { title, description, crateMode, crate, summaryPrice, form, isMobile, onClickToggleMobileSideMenu } = this.props; return (

{title}

{description}

{crateMode}
{isMobile ? (
) : null} {crate}
{summaryPrice} {form}
); } } /** * Components that renders the form to request quote. */ class OrderForm extends React.PureComponent { static get propTypes() { return { onClickSubmit: PropTypes.func, }; } constructor(props) { super(props); this.state = {note: ''}; this.handleNoteChange = this.handleNoteChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleNoteChange(event) { this.setState({ note: event.target.value, }); } handleSubmit(event) { if (this.props.onClickSubmit) { this.props.onClickSubmit(this.state.note); } event.preventDefault(); } render() { return (