forked from M-Labs/web2019
Make cards behavior working somewhat properly
This commit is contained in:
parent
ec2c0a3b80
commit
6b92bf9145
|
@ -9,17 +9,13 @@ import {useShopStore} from "./shop_store";
|
||||||
*/
|
*/
|
||||||
export function Backlog() {
|
export function Backlog() {
|
||||||
const {
|
const {
|
||||||
currency,
|
|
||||||
data,
|
data,
|
||||||
items,
|
items,
|
||||||
onClickAddItem,
|
|
||||||
onClickToggleMobileSideMenu,
|
onClickToggleMobileSideMenu,
|
||||||
isMobile,
|
isMobile,
|
||||||
} = useShopStore(state=> ({
|
} = useShopStore(state=> ({
|
||||||
currency: state.currency,
|
|
||||||
data: state.groups,
|
data: state.groups,
|
||||||
items: state.cards,
|
items: state.cards,
|
||||||
onClickAddItem: state.addCardFromBacklog,
|
|
||||||
onClickToggleMobileSideMenu: state.switchSideMenu,
|
onClickToggleMobileSideMenu: state.switchSideMenu,
|
||||||
isMobile: state.isMobile
|
isMobile: state.isMobile
|
||||||
}));
|
}));
|
||||||
|
@ -46,20 +42,7 @@ export function Backlog() {
|
||||||
{group.items.map(item => {
|
{group.items.map(item => {
|
||||||
item_index++;
|
item_index++;
|
||||||
return (
|
return (
|
||||||
<ProductItem
|
<ProductItem card_index={item_index}/>
|
||||||
key={item.id}
|
|
||||||
id={uuidv4()}
|
|
||||||
index={item_index}
|
|
||||||
name={`${item.name_number} ${item.name}`}
|
|
||||||
name_codename={item.name_codename}
|
|
||||||
price={item.price}
|
|
||||||
currency={currency}
|
|
||||||
image={`/images/${item.image}`}
|
|
||||||
specs={item.specs}
|
|
||||||
datasheet_file={item.datasheet_file}
|
|
||||||
datasheet_name={item.datasheet_name}
|
|
||||||
onClickAddItem={onClickAddItem}
|
|
||||||
></ProductItem>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,29 +4,23 @@ import {cartStyle} from "./utils";
|
||||||
import {ProductCartItem} from "./ProductCartItem.jsx";
|
import {ProductCartItem} from "./ProductCartItem.jsx";
|
||||||
import {FakePlaceholder} from "./FakePlaceholder.jsx";
|
import {FakePlaceholder} from "./FakePlaceholder.jsx";
|
||||||
import {FillExtData} from "./options/utils";
|
import {FillExtData} from "./options/utils";
|
||||||
import {CountResources, crate_type_to_hp, hp_to_slots, resource_counters} from "./count_resources";
|
import {hp_to_slots, resource_counters} from "./count_resources";
|
||||||
import {useShopStore} from "./shop_store";
|
import {useShopStore} from "./shop_store";
|
||||||
import {TriggerCardWarnings} from "./warnings";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays a list of <ProductCartItem>
|
* Component that displays a list of <ProductCartItem>
|
||||||
*/
|
*/
|
||||||
export function Cart({crate_index}) {
|
export function Cart({crate_index}) {
|
||||||
// isMobile, isTouch, crate, onToggleOverlayRemove, onClickRemoveItem, onCardUpdate, onClickItem
|
const {crate, crateParams} = useShopStore(state => ({
|
||||||
const {crate} = useShopStore(state => ({
|
crate: state.crates[crate_index],
|
||||||
crate: state.crates[crate_index]
|
crateParams: state.crateParams
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(resource_counters, crate)
|
const nbrOccupied = hp_to_slots(crate.occupiedHP);
|
||||||
|
const nbrSlots = hp_to_slots(crateParams(crate.crate_mode).hp);
|
||||||
const nbrOccupied = hp_to_slots(resource_counters.hp(crate.items, -1));
|
|
||||||
const nbrSlots = hp_to_slots(crate_type_to_hp(crate.crate_mode));
|
|
||||||
console.log(nbrOccupied, nbrSlots);
|
|
||||||
|
|
||||||
const products = crate.items.map((item, index) => {
|
const products = crate.items.map((item, index) => {
|
||||||
const ext_data = FillExtData(crate.items, index);
|
const ext_data = FillExtData(crate.items, index);
|
||||||
const resources = CountResources(crate.items, index);
|
|
||||||
const warnings = TriggerCardWarnings(crate.items, index, resources);
|
|
||||||
return (
|
return (
|
||||||
<ProductCartItem
|
<ProductCartItem
|
||||||
card_index={index}
|
card_index={index}
|
||||||
|
@ -34,8 +28,6 @@ export function Cart({crate_index}) {
|
||||||
ext_data={ext_data}
|
ext_data={ext_data}
|
||||||
first={index === 0}
|
first={index === 0}
|
||||||
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
|
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
|
||||||
resources={resources}
|
|
||||||
warnings={warnings}
|
|
||||||
key={item.id}/>
|
key={item.id}/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,9 +34,7 @@ export function Crate({crate_index}) {
|
||||||
|
|
||||||
<Cart crate_index={crate_index}/>
|
<Cart crate_index={crate_index}/>
|
||||||
|
|
||||||
{1 || (rules && rules.length > 0) && (
|
<CrateWarnings crate_index={crate_index} />
|
||||||
<CrateWarnings crate_index={crate_index} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,7 @@ export function CrateMode({crate_index}) {
|
||||||
const {modes_order, crate_modes, crate, setMode} = useShopStore(state => ({
|
const {modes_order, crate_modes, crate, setMode} = useShopStore(state => ({
|
||||||
modes_order: state.modes_order,
|
modes_order: state.modes_order,
|
||||||
crate_modes: state.crate_modes,
|
crate_modes: state.crate_modes,
|
||||||
crate: state.crates[crate_index].crate_mode,
|
crate: state.crates[crate_index],
|
||||||
setMode: state.setCrateMode
|
setMode: state.setCrateMode
|
||||||
}))
|
}))
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {TriggerCrateWarnings} from "./warnings";
|
import {LevelUI} from "./warnings";
|
||||||
import {useShopStore} from "./shop_store";
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
export function CrateWarnings({crate_index}) {
|
export function CrateWarnings({crate_index}) {
|
||||||
const crate = useShopStore(state => (state.crates[crate_index]))
|
const crate = useShopStore(state => (state.crates[crate_index]))
|
||||||
const crate_warnings = TriggerCrateWarnings(crate);
|
const crate_warnings = crate.warnings;
|
||||||
// TODO UI/colors
|
// TODO UI/colors
|
||||||
return (
|
return (
|
||||||
<div className="crate-info">
|
<div className="crate-info">
|
||||||
{crate_warnings.map((rule, index) => (
|
{crate_warnings.map((rule, index) => (
|
||||||
<p key={index} className="rule" style={{'color': "red"}}>
|
<p key={index} className="rule" style={{'color': LevelUI(rule.level).color}}>
|
||||||
<img src={`/images${rule.icon}`} /> <i><strong>{rule.name}:</strong> {rule.message}</i>
|
<img src={LevelUI(rule.level).icon} /> <i>{rule.message}</i>
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function OrderPanel({title, description}) {
|
||||||
<CrateList/>
|
<CrateList/>
|
||||||
|
|
||||||
<section className="summary">
|
<section className="summary">
|
||||||
|
<OrderSummary/>
|
||||||
|
|
||||||
<OrderForm/>
|
<OrderForm/>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,190 +1,149 @@
|
||||||
import React, {PureComponent} from 'react';
|
import React from 'react';
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import {SummaryPopup} from "./options/SummaryPopup.jsx";
|
import {SummaryPopup} from "./options/SummaryPopup.jsx";
|
||||||
import {formatMoney} from "./utils";
|
import {formatMoney} from "./utils";
|
||||||
import {WarningIndicator} from "./CardWarnings.jsx";
|
import {WarningIndicator} from "./CardWarnings.jsx";
|
||||||
import {total_order_price} from "./count_resources";
|
import {useShopStore} from "./shop_store";
|
||||||
import {data as shared_data} from "./utils";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components that displays the list of card that are used in the crate.
|
* Components that displays the list of card that are used in the crate.
|
||||||
* It is a summary of purchase
|
* It is a summary of purchase
|
||||||
*/
|
*/
|
||||||
export class OrderSummary extends PureComponent {
|
export function OrderSummary() {
|
||||||
|
|
||||||
static get propTypes() {
|
const {
|
||||||
return {
|
currency,
|
||||||
currency: PropTypes.string,
|
crates,
|
||||||
crates: PropTypes.object,
|
total_price,
|
||||||
onDeleteItem: PropTypes.func,
|
crateParams,
|
||||||
onDeleteAllItems: PropTypes.func,
|
deleteCard,
|
||||||
onMouseEnterItem: PropTypes.func,
|
setHighlight,
|
||||||
onMouseLeaveItem: PropTypes.func,
|
resetHighlight,
|
||||||
onClickSelectItem: PropTypes.func,
|
highlighted,
|
||||||
};
|
clearCrate,
|
||||||
}
|
clearAll
|
||||||
|
} = useShopStore(state =>({
|
||||||
|
currency: state.currency,
|
||||||
|
crates: state.crates,
|
||||||
|
total_price: state.totalOrderPrice(),
|
||||||
|
crateParams: state.crateParams,
|
||||||
|
deleteCard: state.deleteCard,
|
||||||
|
setHighlight: state.highlightCard,
|
||||||
|
resetHighlight: state.highlightReset,
|
||||||
|
highlighted: state.highlighted,
|
||||||
|
clearAll: state.clearAll,
|
||||||
|
clearCrate: state.clearCrate
|
||||||
|
}));
|
||||||
|
|
||||||
constructor(props) {
|
return (
|
||||||
super(props);
|
<div className="summary-price">
|
||||||
this.handleOnDeleteItem = this.handleOnDeleteItem.bind(this);
|
|
||||||
this.handleOnDeleteAllItems = this.handleOnDeleteAllItems.bind(this);
|
|
||||||
this.handleOnMouseEnterItem = this.handleOnMouseEnterItem.bind(this);
|
|
||||||
this.handleOnMouseLeaveItem = this.handleOnMouseLeaveItem.bind(this);
|
|
||||||
this.handleOnClickSelectItem = this.handleOnClickSelectItem.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnDeleteItem(index, e) {
|
<table>
|
||||||
if (this.props.onDeleteItem) {
|
|
||||||
this.props.onDeleteItem(index);
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnDeleteAllItems(e) {
|
<thead>
|
||||||
if (this.props.onDeleteAllItems) {
|
<tr>
|
||||||
this.props.onDeleteAllItems();
|
<td colSpan="2" className="summary-remove-all">
|
||||||
}
|
<span className="item-card-name">Remove all cards</span>
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnMouseEnterItem(id, e) {
|
<button onClick={clearAll}>
|
||||||
if (this.props.onMouseEnterItem) {
|
<img src="/images/shop/icon-remove.svg"/>
|
||||||
this.props.onMouseEnterItem(id);
|
</button>
|
||||||
}
|
</td>
|
||||||
e.preventDefault();
|
</tr>
|
||||||
}
|
</thead>
|
||||||
|
|
||||||
handleOnMouseLeaveItem(e) {
|
{crates.map((crate, _i) => {
|
||||||
if (this.props.onMouseLeaveItem) {
|
let crate_type = crateParams(crate.crate_mode);
|
||||||
this.props.onMouseLeaveItem();
|
return (
|
||||||
}
|
<tbody key={"summary_crate_body" + crate.id}>
|
||||||
e.preventDefault();
|
<tr key={"summary_crate_" + crate.id}>
|
||||||
}
|
<td className="item-card-name">{crate_type.name}</td>
|
||||||
|
<td className="price">
|
||||||
|
<div>
|
||||||
|
{`${currency} ${formatMoney(crate_type.price)}`}
|
||||||
|
|
||||||
handleOnClickSelectItem(index, e) {
|
<button style={{'opacity': '0', 'cursor': 'initial'}}>
|
||||||
if (e.target.tagName !== 'IMG') {
|
<img src="/images/shop/icon-remove.svg"/>
|
||||||
if (this.props.onClickSelectItem) {
|
</button>
|
||||||
this.props.onClickSelectItem(index);
|
</div>
|
||||||
}
|
|
||||||
}
|
|
||||||
return e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
<span style={{
|
||||||
const {
|
'display': 'inline-block',
|
||||||
currency,
|
'width': '30px',
|
||||||
crates
|
}}> </span>
|
||||||
} = this.props;
|
</td>
|
||||||
|
</tr>
|
||||||
|
{crate.items.map((item, index) => {
|
||||||
|
const options = item && item.options;
|
||||||
|
const options_data = item && item.options_data;
|
||||||
|
const warnings = item && item.show_warnings;
|
||||||
|
const selected = crate.id === highlighted.crate && index === highlighted.card;
|
||||||
|
|
||||||
const total_price = total_order_price(crates);
|
return (<tr key={"summary_crate_" + crate.id + item.id}
|
||||||
|
className={`hoverable ${selected ? 'selected' : ''}`}
|
||||||
return (
|
onClick={() => setHighlight(crate.id, index)}
|
||||||
<div className="summary-price">
|
onMouseEnter={() => setHighlight(crate.id, index)}
|
||||||
|
onMouseLeave={() => resetHighlight()}>
|
||||||
<table>
|
<td className="item-card-name">
|
||||||
|
<div>{`${item.name_number} ${item.name} ${item.name_codename}`}</div>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td colSpan="2" className="summary-remove-all">
|
|
||||||
<span className="item-card-name">Remove all cards</span>
|
|
||||||
|
|
||||||
<button onClick={this.handleOnDeleteAllItems}>
|
|
||||||
<img src="/images/shop/icon-remove.svg" />
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
{Object.entries(crates).map(([crate_id, crate], _i) => {
|
|
||||||
let crate_type = shared_data.crateModes[crate.crate_type];
|
|
||||||
return (
|
|
||||||
<tbody key={"summary_crate_body"+crate_id}>
|
|
||||||
<tr key={"summary_crate_"+crate_id}>
|
|
||||||
<td className="item-card-name">{crate_type.name}</td>
|
|
||||||
<td className="price">
|
|
||||||
<div>
|
|
||||||
{`${currency} ${formatMoney(crate_type.price)}`}
|
|
||||||
|
|
||||||
<button style={{'opacity': '0', 'cursor': 'initial'}}>
|
|
||||||
<img src="/images/shop/icon-remove.svg" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span style={{
|
|
||||||
'display': 'inline-block',
|
|
||||||
'width': '30px',
|
|
||||||
}}> </span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
{crate.items.map((item, index) => {
|
|
||||||
let options = item && item.options;
|
|
||||||
let options_data = item && item.options_data;
|
|
||||||
const warnings = item && item.show_warnings;
|
|
||||||
|
|
||||||
return (<tr key={"summary_crate_" + crate_id+item.id}
|
<td className="price">
|
||||||
className={`hoverable ${item.selected ? 'selected' : ''}`}
|
<div className="d-inline-flex align-content-center">
|
||||||
onClick={this.handleOnClickSelectItem.bind(this, index)}
|
{`${currency} ${formatMoney(item.price)}`}
|
||||||
onMouseEnter={this.handleOnMouseEnterItem.bind(this, item.id)}
|
|
||||||
onMouseLeave={this.handleOnMouseLeaveItem}>
|
|
||||||
<td className="item-card-name">
|
|
||||||
<div>{`${item.name_number} ${item.name} ${item.name_codename}`}</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className="price">
|
<button onClick={() => deleteCard(crate.id, index)}>
|
||||||
<div className="d-inline-flex align-content-center">
|
<img src="/images/shop/icon-remove.svg"/>
|
||||||
{`${currency} ${formatMoney(item.price)}`}
|
</button>
|
||||||
|
|
||||||
<button onClick={this.handleOnDeleteItem.bind(this, index)}>
|
<div style={{'width': '45px', 'height': '20px'}}
|
||||||
<img src="/images/shop/icon-remove.svg" />
|
className="d-inline-flex align-content-center align-self-center justify-content-evenly">
|
||||||
</button>
|
{(warnings && warnings.length > 0 ? (
|
||||||
|
<WarningIndicator warnings={warnings}/>
|
||||||
|
) : (
|
||||||
|
<span style={{
|
||||||
|
'display': 'inline-block',
|
||||||
|
'width': '20px',
|
||||||
|
}}> </span>
|
||||||
|
))}
|
||||||
|
{((options && options_data) ? (
|
||||||
|
<SummaryPopup id={item.id + "options"} options={options}
|
||||||
|
data={options_data}/>
|
||||||
|
) : (
|
||||||
|
<span style={{
|
||||||
|
'display': 'inline-block',
|
||||||
|
'width': '20px',
|
||||||
|
}}> </span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
<div style={{'width': '45px', 'height': '20px'}} className="d-inline-flex align-content-center align-self-center justify-content-evenly">
|
<tfoot>
|
||||||
{(warnings && warnings.length > 0 ? (
|
<tr>
|
||||||
<WarningIndicator warnings={warnings}/>
|
<td className="item-card-name">Price estimate</td>
|
||||||
) : (
|
<td className="price">
|
||||||
<span style={{
|
<div>
|
||||||
'display': 'inline-block',
|
{currency} {formatMoney(total_price)}
|
||||||
'width': '20px',
|
<button style={{'opacity': '0', 'cursor': 'initial'}}>
|
||||||
}}> </span>
|
<img src="/images/shop/icon-remove.svg" alt="icon remove"/>
|
||||||
))}
|
</button>
|
||||||
{((options && options_data) ? (
|
</div>
|
||||||
<SummaryPopup id={item.id + "options"} options={options} data={options_data} />
|
|
||||||
) : (
|
|
||||||
<span style={{
|
|
||||||
'display': 'inline-block',
|
|
||||||
'width': '20px',
|
|
||||||
}}> </span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
)})}
|
|
||||||
|
|
||||||
<tfoot>
|
<span style={{
|
||||||
<tr>
|
'display': 'inline-block',
|
||||||
<td className="item-card-name">Price estimate</td>
|
'width': '30px',
|
||||||
<td className="price">
|
}}> </span>
|
||||||
<div>
|
</td>
|
||||||
${currency} ${formatMoney(total_price)}
|
</tr>
|
||||||
<button style={{'opacity': '0', 'cursor': 'initial'}}>
|
</tfoot>
|
||||||
<img src="/images/shop/icon-remove.svg" alt="icon remove"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span style={{
|
</table>
|
||||||
'display': 'inline-block',
|
|
||||||
'width': '30px',
|
|
||||||
}}> </span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
|
|
||||||
</table>
|
</div>
|
||||||
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -10,7 +10,7 @@ import {useShopStore} from "./shop_store";
|
||||||
* Component that renders a product.
|
* Component that renders a product.
|
||||||
* Used in the crate
|
* Used in the crate
|
||||||
*/
|
*/
|
||||||
export function ProductCartItem({card_index, crate_index, ext_data, first, last, resources, warnings}) {
|
export function ProductCartItem({card_index, crate_index, ext_data, first, last}) {
|
||||||
const {card, crate, highlighted, setHighlight, removeHighlight, onCardUpdate, onCardRemove} = useShopStore(state => ({
|
const {card, crate, highlighted, setHighlight, removeHighlight, onCardUpdate, onCardRemove} = useShopStore(state => ({
|
||||||
card: state.crates[crate_index].items[card_index],
|
card: state.crates[crate_index].items[card_index],
|
||||||
highlighted: state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card,
|
highlighted: state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card,
|
||||||
|
@ -22,7 +22,8 @@ export function ProductCartItem({card_index, crate_index, ext_data, first, last,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
let options, options_data;
|
let options, options_data;
|
||||||
//const warnings = data && data.show_warnings;
|
const warnings = card && card.show_warnings;
|
||||||
|
const resources = card && card.counted_resources;
|
||||||
|
|
||||||
if (card && card.options) {
|
if (card && card.options) {
|
||||||
options = card.options;
|
options = card.options;
|
||||||
|
@ -94,7 +95,7 @@ export function ProductCartItem({card_index, crate_index, ext_data, first, last,
|
||||||
|
|
||||||
<img
|
<img
|
||||||
className='item-cart'
|
className='item-cart'
|
||||||
src={`/images${card.image}`}/>
|
src={card.image}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* remove container */}
|
{/* remove container */}
|
||||||
|
|
|
@ -1,120 +1,84 @@
|
||||||
import React, {PureComponent} from 'react';
|
import React from 'react';
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import {Draggable} from "@hello-pangea/dnd";
|
import {Draggable} from "@hello-pangea/dnd";
|
||||||
import {formatMoney, productStyle} from "./utils";
|
import {formatMoney, productStyle} from "./utils";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders a product.
|
* Component that renders a product.
|
||||||
* Used in the aside (e.g backlog of product)
|
* Used in the aside (e.g backlog of product)
|
||||||
*/
|
*/
|
||||||
export class ProductItem extends PureComponent {
|
export function ProductItem({card_index}) {
|
||||||
|
const {card, currency, onAddCard} = useShopStore(state => ({
|
||||||
|
card: state.getCardDescription(card_index),
|
||||||
|
currency: state.currency,
|
||||||
|
onAddCard: state.addCardFromBacklog
|
||||||
|
}));
|
||||||
|
|
||||||
static get propTypes() {
|
|
||||||
return {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
index: PropTypes.number.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
name_codename: PropTypes.string,
|
|
||||||
price: PropTypes.number.isRequired,
|
|
||||||
currency: PropTypes.string.isRequired,
|
|
||||||
image: PropTypes.string.isRequired,
|
|
||||||
specs: PropTypes.array,
|
|
||||||
datasheet_file: PropTypes.string,
|
|
||||||
datasheet_name: PropTypes.string,
|
|
||||||
onClickAddItem: PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
const render_specs = (card.specs && card.specs.length > 0 && (
|
||||||
super(props);
|
<ul>
|
||||||
this.handleOnClickAddItem = this.handleOnClickAddItem.bind(this);
|
{card.specs.map((spec, index) =>
|
||||||
}
|
<li key={index}>{spec}</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
));
|
||||||
|
|
||||||
handleOnClickAddItem(id, tap, e) {
|
const render_datasheet_link = (card.datasheet_file && card.datasheet_name && (
|
||||||
if (this.props.onClickAddItem) {
|
<div className="ds">
|
||||||
this.props.onClickAddItem(id, tap);
|
<span className='doc-icon'></span>
|
||||||
}
|
<a href={card.datasheet_file} target="_blank" rel="noopener noreferrer">
|
||||||
e.preventDefault();
|
{card.datasheet_name}
|
||||||
}
|
</a>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const {
|
<section className="productItem">
|
||||||
id,
|
|
||||||
index,
|
|
||||||
name,
|
|
||||||
name_codename,
|
|
||||||
price,
|
|
||||||
currency,
|
|
||||||
image,
|
|
||||||
specs,
|
|
||||||
datasheet_file,
|
|
||||||
datasheet_name,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const render_specs = (specs && specs.length > 0 && (
|
<div className="content">
|
||||||
<ul>
|
<h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name}</h3>
|
||||||
{specs.map((spec, index) =>
|
{card.name_codename ? (
|
||||||
<li key={index}>{spec}</li>
|
<p>{card.name_codename}</p>
|
||||||
)}
|
) : null}
|
||||||
</ul>
|
|
||||||
));
|
|
||||||
|
|
||||||
const render_datasheet_link = (datasheet_file && datasheet_name && (
|
<div className="price">{`${currency} ${formatMoney(card.price)}`}</div>
|
||||||
<div className="ds">
|
|
||||||
<span className='doc-icon'></span>
|
{render_specs}
|
||||||
<a href={datasheet_file} target="_blank" rel="noopener noreferrer">
|
|
||||||
{datasheet_name}
|
{render_datasheet_link}
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
<div className="content">
|
||||||
<section className="productItem">
|
|
||||||
|
|
||||||
<div className="content">
|
<button onClick={() => onAddCard(null, card_index, null)}>
|
||||||
<h3 style={{ 'marginBottom': name_codename ? '5px' : '20px'}}>{name}</h3>
|
<img src="/images/shop/icon-add.svg" alt="add"/>
|
||||||
{name_codename ? (
|
</button>
|
||||||
<p>{name_codename}</p>
|
|
||||||
) : null }
|
|
||||||
|
|
||||||
<div className="price">{`${currency} ${formatMoney(price)}`}</div>
|
<Draggable draggableId={card.id} index={card_index}>
|
||||||
|
{(provided, snapshot) => (
|
||||||
{render_specs}
|
<React.Fragment>
|
||||||
|
<img
|
||||||
{render_datasheet_link}
|
ref={provided.innerRef}
|
||||||
</div>
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
<div className="content">
|
style={productStyle(
|
||||||
|
provided.draggableProps.style,
|
||||||
<button onClick={this.handleOnClickAddItem.bind(this, index, true)}>
|
snapshot,
|
||||||
<img src="/images/shop/icon-add.svg" alt="add" />
|
true, // hack: remove weird animation after a drop
|
||||||
</button>
|
|
||||||
|
|
||||||
<Draggable draggableId={id} index={index}>
|
|
||||||
{(provided, snapshot) => (
|
|
||||||
<React.Fragment>
|
|
||||||
<img
|
|
||||||
ref={provided.innerRef}
|
|
||||||
{...provided.draggableProps}
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
style={productStyle(
|
|
||||||
provided.draggableProps.style,
|
|
||||||
snapshot,
|
|
||||||
true, // hack: remove weird animation after a drop
|
|
||||||
)}
|
|
||||||
src={image} />
|
|
||||||
|
|
||||||
{/* Allows to simulate a clone */}
|
|
||||||
{snapshot.isDragging && (
|
|
||||||
<img className="simclone" src={image} />
|
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
src={card.image}/>
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
|
|
||||||
</div>
|
{/* Allows to simulate a clone */}
|
||||||
|
{snapshot.isDragging && (
|
||||||
|
<img className="simclone" src={card.image}/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, {useEffect} from 'react';
|
||||||
import {DragDropContext} from "@hello-pangea/dnd";
|
import {DragDropContext} from "@hello-pangea/dnd";
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,28 +12,13 @@ import {useShopStore} from "./shop_store";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Shop() {
|
export function Shop() {
|
||||||
const {addCardFromBacklog, moveCard, deleteCard} = useShopStore(state => ({
|
const {addCardFromBacklog, moveCard, deleteCard, cardIndexById} = useShopStore(state => ({
|
||||||
addCardFromBacklog: state.addCardFromBacklog,
|
addCardFromBacklog: state.addCardFromBacklog,
|
||||||
moveCard: state.moveCard,
|
moveCard: state.moveCard,
|
||||||
deleteCard: state.deleteCard
|
deleteCard: state.deleteCard,
|
||||||
|
cardIndexById: state.cardIndexById
|
||||||
}));
|
}));
|
||||||
const handleOnDragEnd = (drop_result, provided) => {
|
const handleOnDragEnd = (drop_result, provided) => {
|
||||||
console.log(drop_result, provided)
|
|
||||||
//{
|
|
||||||
// "draggableId": "42dc17e9-9e75-45ee-ad27-2233b6f07a5e",
|
|
||||||
// "type": "DEFAULT",
|
|
||||||
// "source": {
|
|
||||||
// "index": 17,
|
|
||||||
// "droppableId": "backlog"
|
|
||||||
// },
|
|
||||||
// "reason": "DROP",
|
|
||||||
// "mode": "FLUID",
|
|
||||||
// "destination": {
|
|
||||||
// "droppableId": "crate0",
|
|
||||||
// "index": 0
|
|
||||||
// },
|
|
||||||
// "combine": null
|
|
||||||
// }
|
|
||||||
if (drop_result.source.droppableId === "backlog")
|
if (drop_result.source.droppableId === "backlog")
|
||||||
addCardFromBacklog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
|
addCardFromBacklog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
|
||||||
else if(drop_result.destination.droppableId === "backlog")
|
else if(drop_result.destination.droppableId === "backlog")
|
||||||
|
@ -42,6 +27,10 @@ export function Shop() {
|
||||||
moveCard(drop_result.source.droppableId, drop_result.source.index, drop_result.destination.droppableId, drop_result.destination.index)
|
moveCard(drop_result.source.droppableId, drop_result.source.index, drop_result.destination.droppableId, drop_result.destination.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addCardFromBacklog(null, [cardIndexById("kasli"), cardIndexById("eem_pwr_mod")], -1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {create} from "zustand";
|
import {create} from "zustand";
|
||||||
import {data, itemsUnfoldedList} from "./utils";
|
import {data as shared_data, itemsUnfoldedList} from "./utils";
|
||||||
import {true_type_of} from "./options/utils";
|
import {true_type_of} from "./options/utils";
|
||||||
import {v4 as uuidv4} from "uuid";
|
import {v4 as uuidv4} from "uuid";
|
||||||
import {FillResources} from "./count_resources";
|
import {FillResources} from "./count_resources";
|
||||||
|
@ -9,15 +9,18 @@ import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
|
||||||
|
|
||||||
|
|
||||||
const useBacklog = ((set, get) => ({
|
const useBacklog = ((set, get) => ({
|
||||||
cards: data.items,
|
cards: shared_data.items,
|
||||||
groups: data.columns.backlog,
|
groups: shared_data.columns.backlog,
|
||||||
cards_list: itemsUnfoldedList,
|
cards_list: itemsUnfoldedList,
|
||||||
currency: data.currency
|
currency: shared_data.currency,
|
||||||
|
getCardDescription: index => get().cards[get().cards_list[index]],
|
||||||
|
cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useCrateModes = ((set, get) => ({
|
const useCrateModes = ((set, get) => ({
|
||||||
crate_modes: data.crateModes,
|
crate_modes: shared_data.crateModes,
|
||||||
modes_order: data.crateModeOrder
|
modes_order: shared_data.crateModeOrder,
|
||||||
|
crateParams: mode => get().crate_modes[mode],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useLayout = ((set, get) => ({
|
const useLayout = ((set, get) => ({
|
||||||
|
@ -47,25 +50,41 @@ const useSubmitForm = ((set, get) => ({
|
||||||
isProcessingComplete: true,
|
isProcessingComplete: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useCart = ((set, get) => ({
|
const useHighlighted = ((set, get) => ({
|
||||||
crates: data.columns.crates,
|
|
||||||
active_crate: "crate0",
|
|
||||||
highlighted: {
|
highlighted: {
|
||||||
crate: "",
|
crate: "",
|
||||||
card: 0
|
card: 0
|
||||||
},
|
},
|
||||||
|
highlightCard: (crate_id, index) => set(state => ({
|
||||||
|
highlighted: {
|
||||||
|
crate: crate_id,
|
||||||
|
card: index
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
highlightReset: () => set(state => ({
|
||||||
|
highlighted: {
|
||||||
|
crate: "",
|
||||||
|
card: 0
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
newCrate: () => set((state) => ({crates: state.crates.concat({
|
const useCart = ((set, get) => ({
|
||||||
id: "crate"+state.crates.length,
|
crates: shared_data.columns.crates,
|
||||||
|
active_crate: "crate0",
|
||||||
|
|
||||||
|
_newCrate: (crate_id) => set((state) => ({crates: state.crates.concat({
|
||||||
|
id: crate_id || "crate" + state.crates.length,
|
||||||
crate_mode: "rack",
|
crate_mode: "rack",
|
||||||
items: [],
|
items: [],
|
||||||
warnings: []
|
warnings: [],
|
||||||
|
occupiedHP: 0
|
||||||
})})),
|
})})),
|
||||||
delCrate: (id) => set(state => ({
|
delCrate: (id) => set(state => ({
|
||||||
crates: state.crates.filter((crate => crate.id !== id))
|
crates: state.crates.filter((crate => crate.id !== id))
|
||||||
})),
|
})),
|
||||||
setCrateMode: (id, mode) => set(state => ({
|
_setCrateMode: (id, mode) => set(state => ({
|
||||||
crates: state.crates.map((crate, _i) => {
|
crates: state.crates.map((crate, _i) => {
|
||||||
if (crate.id === id) {
|
if (crate.id === id) {
|
||||||
return {
|
return {
|
||||||
|
@ -76,12 +95,13 @@ const useCart = ((set, get) => ({
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
setActiveCrate: (id) => set(state => ({active_crate: id})),
|
setActiveCrate: (id) => set(state => ({active_crate: id})),
|
||||||
addCardFromBacklog: (crate_to, index_from, index_to) => set(state => {
|
_addCardFromBacklog: (crate_to, index_from, index_to) => set(state => {
|
||||||
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
|
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
|
||||||
const dest = crate_to || state.active_crate;
|
const dest = crate_to || state.active_crate;
|
||||||
return {
|
return {
|
||||||
crates: state.crates.map((crate, _i) => {
|
crates: state.crates.map((crate, _i) => {
|
||||||
if (dest === crate.id) {
|
if (dest === crate.id) {
|
||||||
|
index_to = index_to != null ? index_to : crate.items.length;
|
||||||
return {
|
return {
|
||||||
...crate,
|
...crate,
|
||||||
items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => {
|
items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => {
|
||||||
|
@ -92,7 +112,8 @@ const useCart = ((set, get) => ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
|
_moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
|
||||||
|
console.log(crate_from, index_from, crate_to, index_to)
|
||||||
const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from];
|
const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from];
|
||||||
return {
|
return {
|
||||||
crates: state.crates.map((crate, _i) => {
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
@ -102,7 +123,7 @@ const useCart = ((set, get) => ({
|
||||||
delete items_copy[index_from];
|
delete items_copy[index_from];
|
||||||
return {
|
return {
|
||||||
...crate,
|
...crate,
|
||||||
items: items_copy.toSpliced(index_to, 0, the_card).filter((item, _) => !!item)
|
items: items_copy.toSpliced(index_to+1, 0, the_card).filter((item, _) => !!item)
|
||||||
}
|
}
|
||||||
} else if (crate_to === crate.id) {
|
} else if (crate_to === crate.id) {
|
||||||
return {
|
return {
|
||||||
|
@ -119,18 +140,18 @@ const useCart = ((set, get) => ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
deleteCard: (crate, index) => set(state => ({
|
_deleteCard: (crate_id, index) => set(state => ({
|
||||||
crates: state.crates.map((crate, _i) => {
|
crates: state.crates.map((crate, _i) => {
|
||||||
if (crate === crate.id) {
|
if (crate_id === crate.id) {
|
||||||
return {
|
return {
|
||||||
...crate,
|
...crate,
|
||||||
items: crate.items.splice(index, 1)
|
items: crate.items.toSpliced(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else return crate;
|
else return crate;
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
clearCrate: (id) => set(state => ({
|
_clearCrate: (id) => set(state => ({
|
||||||
crates: state.crates.map((crate, _i) => {
|
crates: state.crates.map((crate, _i) => {
|
||||||
if (id === crate.id) {
|
if (id === crate.id) {
|
||||||
return {
|
return {
|
||||||
|
@ -141,9 +162,12 @@ const useCart = ((set, get) => ({
|
||||||
else return crate;
|
else return crate;
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
updateOptions: (crate, index, new_options) => set(state => ({
|
clearAll: () => set(state => ({
|
||||||
|
crates: []
|
||||||
|
})),
|
||||||
|
_updateOptions: (crate_id, index, new_options) => set(state => ({
|
||||||
crates: state.crates.map((crate, _i) => {
|
crates: state.crates.map((crate, _i) => {
|
||||||
if (crate === crate.id) {
|
if (crate_id === crate.id) {
|
||||||
let itemsCopy = Array.from(crate.items);
|
let itemsCopy = Array.from(crate.items);
|
||||||
itemsCopy[index].options_data = {...itemsCopy[index].options_data, ...new_options};
|
itemsCopy[index].options_data = {...itemsCopy[index].options_data, ...new_options};
|
||||||
return {
|
return {
|
||||||
|
@ -154,37 +178,74 @@ const useCart = ((set, get) => ({
|
||||||
else return crate;
|
else return crate;
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
highlightCard: (crate, index) => set(state => ({
|
|
||||||
highlighted: {
|
|
||||||
crate: crate,
|
|
||||||
card: index
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
highlightReset: () => set(state => ({
|
|
||||||
highlighted: {
|
|
||||||
crate: "",
|
|
||||||
card: 0
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
|
|
||||||
fillWarnings: (crate) => set(state => ({
|
fillWarnings: (crate_id) => set(state => ({
|
||||||
// actually seems to be just render-time action, no need to put data in it,
|
|
||||||
// though needs to be optimized to be done only on real crate updates
|
|
||||||
crates: state.crates.map((crate, _i) => {
|
crates: state.crates.map((crate, _i) => {
|
||||||
if (crate === crate.id) {
|
if (crate_id === crate.id) {
|
||||||
let itemsCopy = Array.from(crate.items);
|
let itemsCopy = Array.from(crate.items);
|
||||||
itemsCopy = FillResources(itemsCopy);
|
itemsCopy = FillResources(itemsCopy);
|
||||||
itemsCopy = TriggerWarnings(itemsCopy);
|
itemsCopy = TriggerWarnings(itemsCopy);
|
||||||
const crate_warnings = TriggerCrateWarnings(crate);
|
const [crate_warnings, occupied] = TriggerCrateWarnings(crate);
|
||||||
return {
|
return {
|
||||||
...crate,
|
...crate,
|
||||||
items: itemsCopy,
|
items: itemsCopy,
|
||||||
warnings: crate_warnings
|
warnings: crate_warnings,
|
||||||
|
occupiedHP: occupied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else return crate;
|
else return crate;
|
||||||
})
|
})
|
||||||
}))
|
})),
|
||||||
|
|
||||||
|
totalOrderPrice: () => {
|
||||||
|
let sum = 0;
|
||||||
|
get().crates.forEach( (crate, _i) => {
|
||||||
|
sum += get().crate_modes[crate.crate_mode].price;
|
||||||
|
crate.items.forEach((item, _) => {
|
||||||
|
sum += item.price;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return sum;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Composite actions that require warnings recalculation:
|
||||||
|
|
||||||
|
newCrate: () => {
|
||||||
|
const crate_id = "crate" + get().crates.length;
|
||||||
|
get()._newCrate(crate_id)
|
||||||
|
get().fillWarnings(crate_id);
|
||||||
|
},
|
||||||
|
|
||||||
|
setCrateMode: (id, mode) => {
|
||||||
|
console.log("setCrateMode", id, mode)
|
||||||
|
get()._setCrateMode(id, mode)
|
||||||
|
get().fillWarnings(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
addCardFromBacklog: (crate_to, index_from, index_to) => {
|
||||||
|
const dest = crate_to || get().active_crate;
|
||||||
|
get()._addCardFromBacklog(dest, index_from, index_to)
|
||||||
|
get().fillWarnings(dest);
|
||||||
|
},
|
||||||
|
|
||||||
|
moveCard: (crate_from, index_from, crate_to, index_to) => {
|
||||||
|
get()._moveCard(crate_from, index_from, crate_to, index_to);
|
||||||
|
get().fillWarnings(crate_to);
|
||||||
|
if (crate_from !== crate_to) get().fillWarnings(crate_from);
|
||||||
|
},
|
||||||
|
deleteCard: (crate_id, index) => {
|
||||||
|
get()._deleteCard(crate_id, index);
|
||||||
|
get().fillWarnings(crate_id);
|
||||||
|
},
|
||||||
|
clearCrate: (id) => {
|
||||||
|
get()._clearCrate(id);
|
||||||
|
get().fillWarnings(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOptions: (crate_id, index, new_options) => {
|
||||||
|
get()._updateOptions(crate_id, index, new_options);
|
||||||
|
get().fillWarnings(crate_id);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO load and save jsons?
|
// TODO load and save jsons?
|
||||||
}))
|
}))
|
||||||
|
@ -195,5 +256,6 @@ export const useShopStore = create((...params) => ({
|
||||||
...useCrateModes(...params),
|
...useCrateModes(...params),
|
||||||
...useCart(...params),
|
...useCart(...params),
|
||||||
...useSubmitForm(...params),
|
...useSubmitForm(...params),
|
||||||
...useLayout(...params)
|
...useLayout(...params),
|
||||||
|
...useHighlighted(...params),
|
||||||
}))
|
}))
|
|
@ -7,10 +7,11 @@
|
||||||
|
|
||||||
import {crate_type_to_hp, item_occupied_counters, resource_counters} from "./count_resources";
|
import {crate_type_to_hp, item_occupied_counters, resource_counters} from "./count_resources";
|
||||||
import {data as shared_data} from "./utils";
|
import {data as shared_data} from "./utils";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
const Levels = {
|
const Levels = {
|
||||||
"reminder": {priority: 1, icon: '/images/shop/icon-reminder.svg'},
|
"reminder": {priority: 1, icon: '/images/shop/icon-reminder.svg', color: "black"},
|
||||||
"warning": {priority: 2, icon: '/images/shop/icon-warning.svg'},
|
"warning": {priority: 2, icon: '/images/shop/icon-warning.svg', color: "#c75e5e"},
|
||||||
}
|
}
|
||||||
|
|
||||||
const find_in_counters = (counters, name) => {
|
const find_in_counters = (counters, name) => {
|
||||||
|
@ -97,21 +98,6 @@ const Types = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function TriggerCardWarnings(data, index, precounted) {
|
|
||||||
const element = data[index];
|
|
||||||
return (element.warnings && element.warnings
|
|
||||||
.map((warning, _) => {
|
|
||||||
if (!!Types[warning])
|
|
||||||
return Types[warning].trigger(data, index, precounted) ? {trigger: undefined, name: warning, ...Types[warning]} : null;
|
|
||||||
else
|
|
||||||
return Types.default;
|
|
||||||
})
|
|
||||||
.filter((warning, _) => {
|
|
||||||
return !!warning
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TriggerWarnings(data) {
|
export function TriggerWarnings(data) {
|
||||||
return data.map((element, index) => {
|
return data.map((element, index) => {
|
||||||
if (!element.warnings) return element;
|
if (!element.warnings) return element;
|
||||||
|
@ -137,23 +123,26 @@ export function MaxLevel(warnings) {
|
||||||
return mx;
|
return mx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function LevelUI(warning_level) {
|
||||||
|
const warning_t = Levels[warning_level];
|
||||||
|
return {icon: warning_t.icon, color: warning_t.color};
|
||||||
|
}
|
||||||
|
|
||||||
const crate_warnings = {
|
const crate_warnings = {
|
||||||
"overfit": {
|
"overfit": {
|
||||||
message: "You have reached the maximum number of slots allowed for this crate. Consider removing cards.",
|
message: "You have reached the maximum number of slots allowed for this crate. Consider removing cards.",
|
||||||
level: "warning",
|
level: "warning",
|
||||||
trigger: (crate, occupied) => {
|
trigger: (crate, occupied) => {
|
||||||
const nbrHP = crate_type_to_hp(crate.crate_type);
|
const nbrHP = useShopStore.getState().crateParams(crate.crate_mode).hp;
|
||||||
return occupied > nbrHP;
|
return occupied > nbrHP && nbrHP > 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"underfit_rack": {
|
"underfit_rack": {
|
||||||
message: "The selected cards fit in a 42hp desktop crate, consider switching to it for a more compact system",
|
message: "The selected cards fit in a 42hp desktop crate, consider switching to it for a more compact system",
|
||||||
level: "reminder",
|
level: "reminder",
|
||||||
trigger: (crate, occupied) => {
|
trigger: (crate, occupied) => {
|
||||||
const nbrHPDesktop = shared_data.crateModes.desktop.hp;
|
const nbrHPDesktop = useShopStore.getState().crate_modes.desktop.hp;
|
||||||
return crate.crate_type === shared_data.crateModes.rack.id && occupied < nbrHPDesktop;
|
return crate.crate_mode === useShopStore.getState().crate_modes.rack.id && occupied < nbrHPDesktop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,5 +153,5 @@ export function TriggerCrateWarnings(crate) {
|
||||||
Object.entries(crate_warnings).forEach(([id, warning], _) => {
|
Object.entries(crate_warnings).forEach(([id, warning], _) => {
|
||||||
if (warning.trigger(crate, nbrOccupied)) warnings.push({...warning, id: id, trigger: undefined});
|
if (warning.trigger(crate, nbrOccupied)) warnings.push({...warning, id: id, trigger: undefined});
|
||||||
})
|
})
|
||||||
return warnings;
|
return [warnings, nbrOccupied];
|
||||||
}
|
}
|
|
@ -70,7 +70,7 @@ const shop_data = {
|
||||||
name_number: '1124',
|
name_number: '1124',
|
||||||
name_codename: 'Kasli 2.0',
|
name_codename: 'Kasli 2.0',
|
||||||
price: 3600,
|
price: 3600,
|
||||||
image: '/shop/graphic-03_kasli.svg',
|
image: '/images/shop/graphic-03_kasli.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'FPGA core device, runs ARTIQ kernels, controls the EEMs.',
|
'FPGA core device, runs ARTIQ kernels, controls the EEMs.',
|
||||||
'4 SFP 6Gb/s slots for Ethernet or DRTIO.',
|
'4 SFP 6Gb/s slots for Ethernet or DRTIO.',
|
||||||
|
@ -126,7 +126,7 @@ const shop_data = {
|
||||||
name_number: '1125',
|
name_number: '1125',
|
||||||
name_codename: 'Kasli-SoC',
|
name_codename: 'Kasli-SoC',
|
||||||
price: 5100,
|
price: 5100,
|
||||||
image: '/shop/graphic-03_kaslisoc.svg',
|
image: '/images/shop/graphic-03_kaslisoc.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Core device based on Zynq-7000 CPU+FPGA system-on-chip.',
|
'Core device based on Zynq-7000 CPU+FPGA system-on-chip.',
|
||||||
'Runs ARTIQ kernels on 1GHz Cortex-A9 CPU with hardware FPU.',
|
'Runs ARTIQ kernels on 1GHz Cortex-A9 CPU with hardware FPU.',
|
||||||
|
@ -194,7 +194,7 @@ const shop_data = {
|
||||||
name_number: '1008',
|
name_number: '1008',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 400,
|
price: 400,
|
||||||
image: '/shop/graphic-03_VHDCI_carrier.svg',
|
image: '/images/shop/graphic-03_VHDCI_carrier.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Passive adapter between VHDCI and EEMs.',
|
'Passive adapter between VHDCI and EEMs.',
|
||||||
'VHDCI (SCSI-3) cables can carry EEM signals over short distances between crates.',
|
'VHDCI (SCSI-3) cables can carry EEM signals over short distances between crates.',
|
||||||
|
@ -221,7 +221,7 @@ const shop_data = {
|
||||||
name_number: '2118',
|
name_number: '2118',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 450,
|
price: 450,
|
||||||
image: '/shop/graphic-03_BNC-TTL.svg',
|
image: '/images/shop/graphic-03_BNC-TTL.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Two banks of four digital channels each, with BNC connectors.',
|
'Two banks of four digital channels each, with BNC connectors.',
|
||||||
'Each bank with individual ground isolation.',
|
'Each bank with individual ground isolation.',
|
||||||
|
@ -293,7 +293,7 @@ const shop_data = {
|
||||||
name_number: '2128',
|
name_number: '2128',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 400,
|
price: 400,
|
||||||
image: '/shop/graphic-03_SMA-TTL.svg',
|
image: '/images/shop/graphic-03_SMA-TTL.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Same as above, but with SMA connectors.'
|
'Same as above, but with SMA connectors.'
|
||||||
],
|
],
|
||||||
|
@ -359,7 +359,7 @@ const shop_data = {
|
||||||
name_number: '2238',
|
name_number: '2238',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 600,
|
price: 600,
|
||||||
image: '/shop/graphic-03_MCX-TTL.svg',
|
image: '/images/shop/graphic-03_MCX-TTL.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'16 single-ended digital signals on MCX connectors.',
|
'16 single-ended digital signals on MCX connectors.',
|
||||||
'Direction selectable in banks of four signals.',
|
'Direction selectable in banks of four signals.',
|
||||||
|
@ -451,7 +451,7 @@ const shop_data = {
|
||||||
name_number: '2245',
|
name_number: '2245',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 390,
|
price: 390,
|
||||||
image: '/shop/graphic-03_LVDS.svg',
|
image: '/images/shop/graphic-03_LVDS.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Supplies 16 LVDS pairs via 4 front-panel RJ45 connectors.',
|
'Supplies 16 LVDS pairs via 4 front-panel RJ45 connectors.',
|
||||||
'Each RJ45 supplies 4 LVDS DIOs.',
|
'Each RJ45 supplies 4 LVDS DIOs.',
|
||||||
|
@ -538,7 +538,7 @@ const shop_data = {
|
||||||
name_number: '4410',
|
name_number: '4410',
|
||||||
name_codename: 'Urukul',
|
name_codename: 'Urukul',
|
||||||
price: 2350,
|
price: 2350,
|
||||||
image: '/shop/graphic-03_Urukul.svg',
|
image: '/images/shop/graphic-03_Urukul.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'4 channel 1GS/s DDS.',
|
'4 channel 1GS/s DDS.',
|
||||||
'Output frequency (-3 dB): <1 to >400 MHz.',
|
'Output frequency (-3 dB): <1 to >400 MHz.',
|
||||||
|
@ -599,7 +599,7 @@ const shop_data = {
|
||||||
name_number: '4412',
|
name_number: '4412',
|
||||||
name_codename: 'Urukul',
|
name_codename: 'Urukul',
|
||||||
price: 2350,
|
price: 2350,
|
||||||
image: '/shop/graphic-03_Urukul-4412.svg',
|
image: '/images/shop/graphic-03_Urukul-4412.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'4 channel 1GS/s DDS.',
|
'4 channel 1GS/s DDS.',
|
||||||
'Higher frequency resolution ~8 µHz (47 bit)',
|
'Higher frequency resolution ~8 µHz (47 bit)',
|
||||||
|
@ -635,7 +635,7 @@ const shop_data = {
|
||||||
name_number: '4624',
|
name_number: '4624',
|
||||||
name_codename: 'Phaser',
|
name_codename: 'Phaser',
|
||||||
price: 4260,
|
price: 4260,
|
||||||
image: '/shop/graphic-03_Phaser.svg',
|
image: '/images/shop/graphic-03_Phaser.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'2x 1.25 GS/s IQ upconverters.',
|
'2x 1.25 GS/s IQ upconverters.',
|
||||||
'dual IQ mixer + 0.3 GHz to 4.8 GHz VCO + PLL.',
|
'dual IQ mixer + 0.3 GHz to 4.8 GHz VCO + PLL.',
|
||||||
|
@ -667,7 +667,7 @@ const shop_data = {
|
||||||
name_number: '5432',
|
name_number: '5432',
|
||||||
name_codename: 'Zotino',
|
name_codename: 'Zotino',
|
||||||
price: 1600,
|
price: 1600,
|
||||||
image: '/shop/graphic-03_Zotino.svg',
|
image: '/images/shop/graphic-03_Zotino.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'32-channel DAC.',
|
'32-channel DAC.',
|
||||||
'16-bit resolution.',
|
'16-bit resolution.',
|
||||||
|
@ -702,7 +702,7 @@ const shop_data = {
|
||||||
name_number: '5632',
|
name_number: '5632',
|
||||||
name_codename: 'Fastino',
|
name_codename: 'Fastino',
|
||||||
price: 3390,
|
price: 3390,
|
||||||
image: '/shop/graphic-03_Fastino.svg',
|
image: '/images/shop/graphic-03_Fastino.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'32-channel DAC.',
|
'32-channel DAC.',
|
||||||
'16-bit resolution.',
|
'16-bit resolution.',
|
||||||
|
@ -731,7 +731,7 @@ const shop_data = {
|
||||||
name_number: '5518',
|
name_number: '5518',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 160,
|
price: 160,
|
||||||
image: '/shop/graphic-03_IDC-BNC-adapter.svg',
|
image: '/images/shop/graphic-03_IDC-BNC-adapter.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Breaks out analog signals from Zotino or HD68-IDC to BNC connectors.',
|
'Breaks out analog signals from Zotino or HD68-IDC to BNC connectors.',
|
||||||
'Each card provides 8 channels.',
|
'Each card provides 8 channels.',
|
||||||
|
@ -753,7 +753,7 @@ const shop_data = {
|
||||||
name_number: '5528',
|
name_number: '5528',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 160,
|
price: 160,
|
||||||
image: '/shop/graphic-03_SMA-IDC.svg',
|
image: '/images/shop/graphic-03_SMA-IDC.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Breaks out analog signals from Zotino or HD68-IDC to SMA connectors.',
|
'Breaks out analog signals from Zotino or HD68-IDC to SMA connectors.',
|
||||||
'Each card provides 8 channels.',
|
'Each card provides 8 channels.',
|
||||||
|
@ -775,7 +775,7 @@ const shop_data = {
|
||||||
name_number: '5538',
|
name_number: '5538',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 160,
|
price: 160,
|
||||||
image: '/shop/graphic-03_MCX-IDC.svg',
|
image: '/images/shop/graphic-03_MCX-IDC.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Breaks out analog signals from Zotino or HD68-IDC to MCX connectors.',
|
'Breaks out analog signals from Zotino or HD68-IDC to MCX connectors.',
|
||||||
'Each card provides 8 channels.',
|
'Each card provides 8 channels.',
|
||||||
|
@ -797,7 +797,7 @@ const shop_data = {
|
||||||
name_number: '5568',
|
name_number: '5568',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 150,
|
price: 150,
|
||||||
image: '/shop/graphic-03_HD68.svg',
|
image: '/images/shop/graphic-03_HD68.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Connects an external HD68 cable to IDC-BNC, IDC-SMA or IDC-MCX cards.',
|
'Connects an external HD68 cable to IDC-BNC, IDC-SMA or IDC-MCX cards.',
|
||||||
],
|
],
|
||||||
|
@ -823,7 +823,7 @@ const shop_data = {
|
||||||
name_number: '5108',
|
name_number: '5108',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 1600,
|
price: 1600,
|
||||||
image: '/shop/graphic-03_Sampler.svg',
|
image: '/images/shop/graphic-03_Sampler.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'8-channel ADC.',
|
'8-channel ADC.',
|
||||||
'16-bit resolution.',
|
'16-bit resolution.',
|
||||||
|
@ -874,7 +874,7 @@ const shop_data = {
|
||||||
name_number: '6302',
|
name_number: '6302',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 550,
|
price: 550,
|
||||||
image: '/shop/graphic-03_Grabber.svg',
|
image: '/images/shop/graphic-03_Grabber.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Camera input interface card.',
|
'Camera input interface card.',
|
||||||
'Supports some EMCCD cameras.',
|
'Supports some EMCCD cameras.',
|
||||||
|
@ -901,7 +901,7 @@ const shop_data = {
|
||||||
name_number: '7210',
|
name_number: '7210',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 525,
|
price: 525,
|
||||||
image: '/shop/graphic-03_Clocker.svg',
|
image: '/images/shop/graphic-03_Clocker.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Distribute a low jitter clock signal among cards.',
|
'Distribute a low jitter clock signal among cards.',
|
||||||
'2 inputs.',
|
'2 inputs.',
|
||||||
|
@ -936,7 +936,7 @@ const shop_data = {
|
||||||
name_number: '8452',
|
name_number: '8452',
|
||||||
name_codename: 'Stabilizer',
|
name_codename: 'Stabilizer',
|
||||||
price: 2000,
|
price: 2000,
|
||||||
image: '/shop/graphic-03_Stabilizer.svg',
|
image: '/images/shop/graphic-03_Stabilizer.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'CPU-based dual-channel fast servo.',
|
'CPU-based dual-channel fast servo.',
|
||||||
'400MHz STM32H743ZIT6.',
|
'400MHz STM32H743ZIT6.',
|
||||||
|
@ -968,7 +968,7 @@ const shop_data = {
|
||||||
name_number: '4456',
|
name_number: '4456',
|
||||||
name_codename: 'Mirny',
|
name_codename: 'Mirny',
|
||||||
price: 2660,
|
price: 2660,
|
||||||
image: '/shop/graphic-03_Mirny.svg',
|
image: '/images/shop/graphic-03_Mirny.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'4-channel Wide-band PLL/VCO-based microwave frequency synthesiser.',
|
'4-channel Wide-band PLL/VCO-based microwave frequency synthesiser.',
|
||||||
'53 MHz to >4 GHz.',
|
'53 MHz to >4 GHz.',
|
||||||
|
@ -998,7 +998,7 @@ const shop_data = {
|
||||||
name_number: '4457',
|
name_number: '4457',
|
||||||
name_codename: 'Mirny + Almazny',
|
name_codename: 'Mirny + Almazny',
|
||||||
price: 3660,
|
price: 3660,
|
||||||
image: '/shop/graphic-03_Almazny.svg',
|
image: '/images/shop/graphic-03_Almazny.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Mirny with high frequency mezzanine.',
|
'Mirny with high frequency mezzanine.',
|
||||||
'Additional 4 channels up to 12 GHz.',
|
'Additional 4 channels up to 12 GHz.',
|
||||||
|
@ -1025,7 +1025,7 @@ const shop_data = {
|
||||||
name_number: '8453',
|
name_number: '8453',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 2600,
|
price: 2600,
|
||||||
image: '/shop/graphic-03_Thermostat-EEM.svg',
|
image: '/images/shop/graphic-03_Thermostat-EEM.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'4 TEC channels.',
|
'4 TEC channels.',
|
||||||
'Sensor channel count: 8 differential, 16 single-ended.',
|
'Sensor channel count: 8 differential, 16 single-ended.',
|
||||||
|
@ -1053,7 +1053,7 @@ const shop_data = {
|
||||||
name_number: '5716',
|
name_number: '5716',
|
||||||
name_codename: 'Shuttler',
|
name_codename: 'Shuttler',
|
||||||
price: 8500,
|
price: 8500,
|
||||||
image: '/shop/graphic-03_Shuttler.svg',
|
image: '/images/shop/graphic-03_Shuttler.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'16-ch, 125 MSPS DAC EEM with remote analog front end board.',
|
'16-ch, 125 MSPS DAC EEM with remote analog front end board.',
|
||||||
'High DC resolution (up to ~18 bits with sigma-delta modulation) for trap electrode bias.',
|
'High DC resolution (up to ~18 bits with sigma-delta modulation) for trap electrode bias.',
|
||||||
|
@ -1080,7 +1080,7 @@ const shop_data = {
|
||||||
name_number: '4459',
|
name_number: '4459',
|
||||||
name_codename: 'Stabilizer + Pounder',
|
name_codename: 'Stabilizer + Pounder',
|
||||||
price: 4460,
|
price: 4460,
|
||||||
image: '/shop/graphic-03_Pounder.svg',
|
image: '/images/shop/graphic-03_Pounder.svg',
|
||||||
specs: [
|
specs: [
|
||||||
'Stabilizer with Pounder daughter card.',
|
'Stabilizer with Pounder daughter card.',
|
||||||
'2-channel Pound Drever Hall (PDH) lock generator.',
|
'2-channel Pound Drever Hall (PDH) lock generator.',
|
||||||
|
@ -1111,7 +1111,7 @@ const shop_data = {
|
||||||
name_number: '1106',
|
name_number: '1106',
|
||||||
name_codename: '',
|
name_codename: '',
|
||||||
price: 750,
|
price: 750,
|
||||||
image: '/shop/graphic-03_eem_pwr_mod.svg',
|
image: '/images/shop/graphic-03_eem_pwr_mod.svg',
|
||||||
specs: [
|
specs: [
|
||||||
"EEM AC power module.",
|
"EEM AC power module.",
|
||||||
"400W with forced cooling (25CFM), 200W with free air convection.",
|
"400W with forced cooling (25CFM), 200W with free air convection.",
|
||||||
|
@ -1189,7 +1189,8 @@ const shop_data = {
|
||||||
id: "crate0",
|
id: "crate0",
|
||||||
crate_mode: "rack",
|
crate_mode: "rack",
|
||||||
items: [],
|
items: [],
|
||||||
warnings: []
|
warnings: [],
|
||||||
|
occupiedHP: 0,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue