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