forked from M-Labs/web2019
Add options for the cards (#93)
Closes #85 Closes #80 Co-authored-by: Egor Savkin <es@m-labs.hk> Co-committed-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
parent
f8c19cf9b9
commit
9966593166
817
package-lock.json
generated
817
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -12,23 +12,24 @@
|
||||
"url": "https://git.m-labs.hk/M-Labs/web2019.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.22.6",
|
||||
"@babel/core": "^7.22.8",
|
||||
"@babel/preset-env": "^7.22.7",
|
||||
"@babel/preset-react": "^7.22.5",
|
||||
"babel-preset-minify": "^0.5.2",
|
||||
"@babel/cli": "^7.23.0",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@babel/preset-react": "^7.22.15",
|
||||
"babel-loader": "^9.1.3",
|
||||
"webpack": "^5.88.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"babel-preset-minify": "^0.5.2",
|
||||
"bootstrap": "^5.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"jquery": "^3.7.0",
|
||||
"uuid": "^9.0.0",
|
||||
"react-bootstrap": "^2.8.0"
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.9.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"uuid": "^9.0.1",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"json-logic-js": "^2.0.2",
|
||||
"@uidotdev/usehooks": "^2.4.1"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
|
@ -255,11 +255,31 @@ button {
|
||||
|
||||
.item-card-name,
|
||||
.price {
|
||||
> .alert-warning {
|
||||
.alert-warning, .alert-info {
|
||||
background-color: inherit;
|
||||
height: inherit;
|
||||
width: 20px;
|
||||
padding-bottom: 3px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.overlayVariant {
|
||||
min-width: 100px;
|
||||
max-height: 150px;
|
||||
min-height: 50px;
|
||||
overflow-y: scroll;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: start;
|
||||
text-align: left;
|
||||
background-color: white;
|
||||
color: black;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
|
||||
p {
|
||||
font-size: .875rem;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,7 +402,7 @@ button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/*max-width: 96px;*/
|
||||
max-width: 130px;
|
||||
max-width: 132px;
|
||||
justify-content: flex-start;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
@ -432,7 +452,7 @@ button {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
> .alert-warning {
|
||||
> .alert-warning, .alert-info {
|
||||
background-color: inherit;
|
||||
height: inherit;
|
||||
width: 20px;
|
||||
@ -464,6 +484,67 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.overlayVariant {
|
||||
top: 24px;
|
||||
width: 140px;
|
||||
min-height: 40px;
|
||||
max-height: 320px;
|
||||
overflow-y: scroll;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: start;
|
||||
text-align: left;
|
||||
background-color: white;
|
||||
color: black;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0;
|
||||
|
||||
p {
|
||||
font-size: .65rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
margin: 0.1rem 0.2rem;
|
||||
font-size: 0.75rem;
|
||||
|
||||
input {
|
||||
padding: 0;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.options-icon {
|
||||
display: inline;
|
||||
height: .875rem;
|
||||
margin-right: 0.2rem;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.form-check {
|
||||
min-height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-smallcard {
|
||||
left: -38.5px; // (card width (63) - overlay width (140)) / 2
|
||||
|
||||
&.overlay-first {
|
||||
left: 0;
|
||||
}
|
||||
&.overlay-last {
|
||||
left: -67px;
|
||||
}
|
||||
}
|
||||
.overlay-bigcard {
|
||||
left: -7px; // (card width (126) - overlay width (140)) / 2
|
||||
}
|
||||
|
||||
.hovered {
|
||||
|
1
static/images/shop/graphic-03_Urukul-4412.svg
Normal file
1
static/images/shop/graphic-03_Urukul-4412.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 28 KiB |
1
static/images/shop/icon-customize.svg
Normal file
1
static/images/shop/icon-customize.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 48 48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g enable-background="new 0 0 48 48" fill="#715ec7"><path d="m36.9 6c-0.4-1.7-2-3-3.9-3s-3.4 1.3-3.9 3h-27.1v2h27.1c0.4 1.7 2 3 3.9 3s3.4-1.3 3.9-3h9.1v-2zm-3.9 3c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/><path d="m33 37c-1.9 0-3.4 1.3-3.9 3h-27.1v2h27.1c0.4 1.7 2 3 3.9 3s3.4-1.3 3.9-3h9.1v-2h-9.1c-0.5-1.7-2-3-3.9-3zm0 6c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/><path d="m15 20c-1.9 0-3.4 1.3-3.9 3h-9.1v2h9.1c0.4 1.7 2 3 3.9 3s3.4-1.3 3.9-3h27.1v-2h-27.1c-0.5-1.7-2-3-3.9-3zm0 6c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z"/></g></svg>
|
After Width: | Height: | Size: 689 B |
File diff suppressed because one or more lines are too long
@ -1,14 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import PropTypes from "prop-types";
|
||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { OptionsDialogPopup, OptionsSummaryPopup, FilterOptions, FillExtData } from "./shop_components.jsx";
|
||||
import { OverlayTrigger } from "react-bootstrap";
|
||||
|
||||
|
||||
const data = window.shop_data;
|
||||
const itemsUnfoldedList = Array.from(data.columns.backlog.categories.map(groupId => groupId.itemIds).flat());
|
||||
|
||||
@ -257,6 +256,7 @@ class Layout extends React.PureComponent {
|
||||
conf_obj.items = conf_obj.items.map(function (item) {
|
||||
return {
|
||||
pn: item.pn,
|
||||
options: item.options ? item.options : null,
|
||||
};
|
||||
});
|
||||
|
||||
@ -535,12 +535,16 @@ class ProductCartItem extends React.PureComponent {
|
||||
isMobile: PropTypes.bool,
|
||||
isTouch: PropTypes.bool,
|
||||
hovered: PropTypes.bool,
|
||||
first: PropTypes.bool,
|
||||
last: PropTypes.bool,
|
||||
index: PropTypes.number.isRequired,
|
||||
model: PropTypes.object.isRequired,
|
||||
data: PropTypes.object,
|
||||
ext_data: PropTypes.object,
|
||||
onToggleOverlayRemove: PropTypes.func,
|
||||
onClickRemoveItem: PropTypes.func,
|
||||
onClickItem: PropTypes.func,
|
||||
onCardUpdate: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
@ -591,9 +595,13 @@ class ProductCartItem extends React.PureComponent {
|
||||
model,
|
||||
data,
|
||||
index,
|
||||
first,
|
||||
last,
|
||||
ext_data,
|
||||
onCardUpdate,
|
||||
} = this.props;
|
||||
|
||||
let warning;
|
||||
let warning, options, options_data;
|
||||
if (data && data.warnings) {
|
||||
const warningsKeys = Object.keys(data.warnings);
|
||||
if (warningsKeys && warningsKeys.length > 0) {
|
||||
@ -602,41 +610,36 @@ class ProductCartItem extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (data && data.options) {
|
||||
options = data.options;
|
||||
if (!data.options_data) data.options_data = {};
|
||||
options_data = data.options_data;
|
||||
options_data.ext_data = ext_data;
|
||||
}
|
||||
|
||||
let render_progress;
|
||||
if (data) {
|
||||
switch(model.type) {
|
||||
case 'kasli':
|
||||
render_progress = (
|
||||
<div className="k-popup-connectors">
|
||||
<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>
|
||||
<p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>
|
||||
</div>
|
||||
);
|
||||
render_progress = [
|
||||
(<p key={model.type+model.id+"EEM"}>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>),
|
||||
(<p key={model.type+model.id+"CLK"}>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>)
|
||||
];
|
||||
break;
|
||||
|
||||
case 'vhdcicarrier':
|
||||
render_progress = (
|
||||
<div className="k-popup-connectors">
|
||||
<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>
|
||||
</div>
|
||||
);
|
||||
render_progress = (<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>);
|
||||
break;
|
||||
|
||||
case 'zotino':
|
||||
case 'hd68':
|
||||
render_progress = (
|
||||
<div className="k-popup-connectors">
|
||||
<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} connectors used`}</p>
|
||||
</div>
|
||||
);
|
||||
render_progress = (<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} connectors used`}</p>);
|
||||
break;
|
||||
|
||||
case 'clocker':
|
||||
render_progress = (
|
||||
<div className="k-popup-connectors">
|
||||
<p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>
|
||||
</div>
|
||||
);
|
||||
render_progress = (<p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -656,8 +659,8 @@ class ProductCartItem extends React.PureComponent {
|
||||
provided.draggableProps.style,
|
||||
snapshot,
|
||||
true,
|
||||
hovered ? true : false,
|
||||
model.selected ? true : false,
|
||||
!!hovered,
|
||||
!!model.selected,
|
||||
true
|
||||
)}}
|
||||
onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
|
||||
@ -665,23 +668,50 @@ class ProductCartItem extends React.PureComponent {
|
||||
>
|
||||
|
||||
{/* warning container */}
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
trigger={warning ? ['click', 'hover', 'focus'] : []}
|
||||
overlay={
|
||||
warning ? (<div className="k-popup-warning">
|
||||
<p className="rule warning">
|
||||
<i>{warning.message}</i>
|
||||
</p>
|
||||
</div>) : null
|
||||
}
|
||||
rootClose
|
||||
>
|
||||
<div className="progress-container warning">
|
||||
{warning && (<img className="alert-warning" src={warning ? `/images${warning.icon}` : null}/>)}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
|
||||
<div className="progress-container warning d-flex justify-content-evenly">
|
||||
{warning &&
|
||||
(<OverlayTrigger
|
||||
placement="bottom"
|
||||
trigger={['click', 'hover', 'focus']}
|
||||
overlay={
|
||||
({arrowProps, hasDoneInitialMeasure, show, ...props}) => (
|
||||
<div className="k-popup-warning" {...props}>
|
||||
<p className="rule warning">
|
||||
<i>{warning.message}</i>
|
||||
</p>
|
||||
</div>)
|
||||
}
|
||||
rootClose
|
||||
>
|
||||
<img className="alert-warning" src={`/images${warning.icon}`}/>
|
||||
</OverlayTrigger>)
|
||||
}
|
||||
|
||||
{options && (
|
||||
<OptionsDialogPopup
|
||||
options={options}
|
||||
data={options_data}
|
||||
options_class={model.options_class}
|
||||
key={"popover" + index}
|
||||
id={"popover" + index}
|
||||
big={model.size === "big"}
|
||||
first={first}
|
||||
last={last}
|
||||
target={{
|
||||
construct: ((outvar, value) => {
|
||||
// console.log("construct", outvar, value, options_data);
|
||||
options_data[outvar] = value;
|
||||
}),
|
||||
update: ((outvar, value) => {
|
||||
// console.log("update", outvar, value, options_data);
|
||||
if (outvar in options_data) options_data[outvar] = value;
|
||||
onCardUpdate();
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h6>{model.name_number}</h6>
|
||||
|
||||
@ -711,7 +741,7 @@ class ProductCartItem extends React.PureComponent {
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
trigger={['click', 'hover', 'focus']}
|
||||
overlay={render_progress}
|
||||
overlay={({arrowProps, hasDoneInitialMeasure, show, ...props}) => (<div className="k-popup-connectors" {...props}>{render_progress}</div>)}
|
||||
rootClose
|
||||
>
|
||||
<div className="progress-container">
|
||||
@ -800,6 +830,7 @@ class Cart extends React.PureComponent {
|
||||
data: PropTypes.object.isRequired,
|
||||
onToggleOverlayRemove: PropTypes.func,
|
||||
onClickRemoveItem: PropTypes.func,
|
||||
onCardUpdate: PropTypes.func,
|
||||
onClickItem: PropTypes.func,
|
||||
};
|
||||
}
|
||||
@ -814,10 +845,14 @@ class Cart extends React.PureComponent {
|
||||
onToggleOverlayRemove,
|
||||
onClickRemoveItem,
|
||||
onClickItem,
|
||||
onCardUpdate,
|
||||
} = this.props;
|
||||
|
||||
const nbrOccupied = nbrOccupiedSlotsInCrate(data.items);
|
||||
|
||||
const products = data.items.map((item, index) => {
|
||||
let itemData;
|
||||
let ext_data = FillExtData(data.itemsData, index);
|
||||
if (data.itemsData && index in data.itemsData) {
|
||||
itemData = data.itemsData[index];
|
||||
}
|
||||
@ -827,10 +862,15 @@ class Cart extends React.PureComponent {
|
||||
isTouch={isTouch}
|
||||
hovered={item.id === itemHovered}
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
index={index}
|
||||
first={index === 0}
|
||||
last={index === data.items.length - 1 && nbrOccupied >= nbrSlots}
|
||||
data={itemData}
|
||||
ext_data={ext_data}
|
||||
onToggleOverlayRemove={onToggleOverlayRemove}
|
||||
onClickRemoveItem={onClickRemoveItem}
|
||||
onCardUpdate={onCardUpdate}
|
||||
onClickItem={onClickItem}
|
||||
model={item}>
|
||||
</ProductCartItem>
|
||||
@ -1239,7 +1279,8 @@ class OrderForm extends React.PureComponent {
|
||||
className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
|
||||
style={{'cursor': 'pointer', 'fontWeight': '700'}}
|
||||
defaultValue="Show JSON"
|
||||
onClick={onClickShow} />
|
||||
onClick={onClickShow}
|
||||
readOnly={true} />
|
||||
|
||||
<input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit" value={`${isProcessing ? 'Processing ...' : 'Request quote'}`} />
|
||||
</div>
|
||||
@ -1369,16 +1410,17 @@ class OrderSumary extends React.PureComponent {
|
||||
|
||||
<tbody>
|
||||
{summary.map((item, index) => {
|
||||
let alert;
|
||||
let warning;
|
||||
let alert, warning, options, options_data;
|
||||
|
||||
if (itemsData[index]) {
|
||||
if (itemsData[index] && itemsData[index].warnings) {
|
||||
alert = itemsData[index];
|
||||
const warningsKeys = Object.keys(alert.warnings);
|
||||
if (warningsKeys && warningsKeys.length > 0) {
|
||||
warning = alert.warnings[warningsKeys[0]];
|
||||
}
|
||||
}
|
||||
options = itemsData[index] && itemsData[index].options;
|
||||
options_data = itemsData[index] && itemsData[index].options_data;
|
||||
|
||||
return (
|
||||
<tr key={item.id}
|
||||
@ -1392,28 +1434,35 @@ class OrderSumary extends React.PureComponent {
|
||||
</td>
|
||||
|
||||
<td className="price">
|
||||
<div>
|
||||
<div className="d-inline-flex align-content-center">
|
||||
{`${currency} ${formatMoney(item.price)}`}
|
||||
|
||||
<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">
|
||||
{(warning ? (
|
||||
<img
|
||||
className="alert-warning align-self-start"
|
||||
src={`/images/${warning.icon}`}
|
||||
/>
|
||||
) : (
|
||||
<span style={{
|
||||
'display': 'inline-block',
|
||||
'width': '20px',
|
||||
}}> </span>
|
||||
))}
|
||||
{((options && options_data) ? (
|
||||
<OptionsSummaryPopup id={item.id + "options"} options={options} data={options_data} />
|
||||
) : (
|
||||
<span style={{
|
||||
'display': 'inline-block',
|
||||
'width': '20px',
|
||||
}}> </span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{warning && (
|
||||
<img
|
||||
style={{'marginLeft': '10px'}}
|
||||
className="alert-warning"
|
||||
src={`/images/${warning.icon}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!warning && (
|
||||
<span style={{
|
||||
'display': 'inline-block',
|
||||
'width': '30px',
|
||||
}}> </span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@ -1598,6 +1647,7 @@ class Shop extends React.PureComponent {
|
||||
this.handleClickShowOrder = this.handleClickShowOrder.bind(this);
|
||||
this.handleClickOpenImport = this.handleClickOpenImport.bind(this);
|
||||
this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this);
|
||||
this.handleCardsUpdated = this.handleCardsUpdated.bind(this);
|
||||
|
||||
this.timer = null;
|
||||
this.timer_remove = null;
|
||||
@ -1635,9 +1685,7 @@ class Shop extends React.PureComponent {
|
||||
(prevState.columns.cart.items !== this.state.columns.cart.items) ||
|
||||
(prevState.currentMode !== this.state.currentMode)
|
||||
) {
|
||||
this.checkAlerts(
|
||||
prevState.columns.cart.items,
|
||||
this.state.columns.cart.items);
|
||||
this.checkAlerts(this.state.columns.cart.items);
|
||||
}
|
||||
|
||||
if (this.state.newCardJustAdded) {
|
||||
@ -1653,6 +1701,10 @@ class Shop extends React.PureComponent {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
handleCardsUpdated() {
|
||||
this.checkAlerts(this.state.columns.cart.items);
|
||||
}
|
||||
|
||||
handleCrateModeChange(mode) {
|
||||
this.setState({
|
||||
currentMode: mode,
|
||||
@ -1660,8 +1712,11 @@ class Shop extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleDeleteItem(index) {
|
||||
const cloned = Array.from(this.state.columns.cart.items);
|
||||
let cloned = Array.from(this.state.columns.cart.items);
|
||||
let cloned_data = Array.from(this.state.columns.cart.itemsData);
|
||||
cloned.splice(index, 1);
|
||||
cloned_data.splice(index, 1);
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
columns: {
|
||||
@ -1669,6 +1724,7 @@ class Shop extends React.PureComponent {
|
||||
cart: {
|
||||
...this.state.columns.cart,
|
||||
items: cloned,
|
||||
itemsData: cloned_data,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -1682,6 +1738,7 @@ class Shop extends React.PureComponent {
|
||||
cart: {
|
||||
...this.state.columns.cart,
|
||||
items: [],
|
||||
itemsData: []
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -1771,10 +1828,13 @@ class Shop extends React.PureComponent {
|
||||
type: this.state.currentMode,
|
||||
};
|
||||
const clonedCart = Array.from(this.state.columns.cart.items);
|
||||
const clonedCartData = Array.from(this.state.columns.cart.itemsData);
|
||||
for (const i in clonedCart) {
|
||||
const item = clonedCart[i];
|
||||
const item_data = clonedCartData[i];
|
||||
crate.items.push({
|
||||
'pn': item.name_number,
|
||||
'options': (item_data.options_data && item_data.options) ? FilterOptions(item_data.options, item_data.options_data) : null,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1782,7 +1842,7 @@ class Shop extends React.PureComponent {
|
||||
isProcessing: false,
|
||||
shouldShowRFQFeedback: true,
|
||||
RFQBodyType: 'show',
|
||||
RFQBodyOrder: JSON.stringify(crate),
|
||||
RFQBodyOrder: JSON.stringify(crate, null, 2),
|
||||
});
|
||||
}
|
||||
|
||||
@ -1798,9 +1858,9 @@ class Shop extends React.PureComponent {
|
||||
if (!customconf) {return; }
|
||||
|
||||
const items = this.props.data.items;
|
||||
const self = this;
|
||||
|
||||
let new_items = [];
|
||||
let new_items_data = [];
|
||||
|
||||
|
||||
this.setState({
|
||||
@ -1821,14 +1881,15 @@ class Shop extends React.PureComponent {
|
||||
...items[key],
|
||||
}, {
|
||||
id: uuidv4(),
|
||||
options_data: item.options ? item.options : null,
|
||||
}));
|
||||
new_items_data.push({options_data: item.options ? item.options : null});
|
||||
}
|
||||
});
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
columns: {
|
||||
@ -1836,6 +1897,7 @@ class Shop extends React.PureComponent {
|
||||
cart: {
|
||||
...this.state.columns.cart,
|
||||
items: new_items,
|
||||
itemsData: new_items_data,
|
||||
},
|
||||
},
|
||||
currentMode: customconf.type,
|
||||
@ -1849,10 +1911,13 @@ class Shop extends React.PureComponent {
|
||||
type: this.state.currentMode,
|
||||
};
|
||||
const clonedCart = Array.from(this.state.columns.cart.items);
|
||||
const clonedCartData = Array.from(this.state.columns.cart.itemsData);
|
||||
for (const i in clonedCart) {
|
||||
const item = clonedCart[i];
|
||||
const item_data = clonedCartData[i];
|
||||
crate.items.push({
|
||||
'pn': item.name_number,
|
||||
'options': (item_data.options_data && item_data.options) ? FilterOptions(item_data.options, item_data.options_data) : null,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1860,11 +1925,18 @@ class Shop extends React.PureComponent {
|
||||
|
||||
this.setState({isProcessing: true});
|
||||
|
||||
axios.post(data.API_RFQ, {
|
||||
email,
|
||||
note,
|
||||
configuration: JSON.stringify(crate)
|
||||
fetch(data.API_RFQ, {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
note,
|
||||
configuration: JSON.stringify(crate)
|
||||
})
|
||||
}).then(response => {
|
||||
if (response.status !== 200) {
|
||||
throw Error("Response status is not OK: " + response.status + ".\n" + response);
|
||||
}
|
||||
this.setState({
|
||||
isProcessing: false,
|
||||
shouldShowRFQFeedback: true,
|
||||
@ -1872,6 +1944,7 @@ class Shop extends React.PureComponent {
|
||||
isProcessingComplete: true,
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error("Request failed, reason:", err)
|
||||
this.setState({isProcessing: false}, () => {
|
||||
alert("We cannot receive your request. Try using the export by coping the configuration and send it to us at sales[at]m-labs.hk");
|
||||
});
|
||||
@ -1906,6 +1979,10 @@ class Shop extends React.PureComponent {
|
||||
this.state.columns[source.droppableId].items,
|
||||
source.index,
|
||||
),
|
||||
itemsData: remove(
|
||||
this.state.columns[source.droppableId].itemsData,
|
||||
source.index,
|
||||
)
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -1951,6 +2028,11 @@ class Shop extends React.PureComponent {
|
||||
source.index,
|
||||
destination.index,
|
||||
),
|
||||
itemsData: reorder(
|
||||
this.state.columns[destination.droppableId].itemsData,
|
||||
source.index,
|
||||
destination.index,
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -1974,7 +2056,7 @@ class Shop extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
checkAlerts(prevItems, newItems) {
|
||||
checkAlerts(newItems) {
|
||||
console.log('--- START CHECKING CRATE WARNING ---');
|
||||
|
||||
const {
|
||||
@ -1987,6 +2069,14 @@ class Shop extends React.PureComponent {
|
||||
const itemsData = [];
|
||||
const rules = {};
|
||||
|
||||
itemsCloned.forEach((elem, idx) => {
|
||||
if (!(idx in itemsData)) itemsData[idx] = elem;
|
||||
if (idx in this.state.columns.cart.itemsData && this.state.columns.cart.itemsData[idx].options_data) {
|
||||
itemsCloned[idx].options_data = this.state.columns.cart.itemsData[idx].options_data;
|
||||
}
|
||||
itemsData[idx].warnings = {};
|
||||
});
|
||||
|
||||
|
||||
// check number of slot in crate
|
||||
const nbrOccupied = nbrOccupiedSlotsInCrate(newItems);
|
||||
@ -2036,14 +2126,31 @@ class Shop extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const process_slots = (item) => {
|
||||
if (!item.options_data
|
||||
|| item.options_data.ext_pwr === false
|
||||
|| item.options_data.mono_eem === false
|
||||
)
|
||||
return item.slotOccupied;
|
||||
else if (item.options_data.ext_pwr === true)
|
||||
return 0;
|
||||
else if (item.options_data.mono_eem === true || item.options_data.n_eem === "1 EEM")
|
||||
return 1;
|
||||
else if (item.options_data.n_eem === "3 EEM")
|
||||
return 3;
|
||||
|
||||
return item.slotOccupied;
|
||||
}
|
||||
|
||||
nbUsedSlot = slots
|
||||
.filter(item => item.type !== 'idc-bnc')
|
||||
.reduce((prev, next) => {
|
||||
return prev + next.slotOccupied;
|
||||
return prev + process_slots(next);
|
||||
}, 0);
|
||||
|
||||
nbrCurrentClock = slots
|
||||
.reduce((prev, next) => {
|
||||
return next.type === 'clocker' ? prev + next.clockOccupied : prev;
|
||||
return next.type === 'clocker' ? prev + ((next.options_data && next.options_data.ext_clk === true) ? 0 : next.clockOccupied) : prev;
|
||||
}, 0);
|
||||
|
||||
if (idx in itemsData) {
|
||||
@ -2119,11 +2226,11 @@ class Shop extends React.PureComponent {
|
||||
}
|
||||
|
||||
nbrCurrentClock = slots.reduce((prev, next) => {
|
||||
return prev + next.clockOccupied;
|
||||
return prev + ((next.options_data && next.options_data.ext_clk && next.options_data.ext_clk.checked) ? 0 : next.clockOccupied);
|
||||
}, 0);
|
||||
|
||||
if (idx in itemsData) {
|
||||
if (itemsData[idx].nbrCurrentClock) {
|
||||
if (itemsData[idx].nbrCurrentClock && itemsData[idx].type !== "clocker") {
|
||||
itemsData[idx].nbrCurrentClock += nbrCurrentClock;
|
||||
} else {
|
||||
itemsData[idx].nbrCurrentClock = nbrCurrentClock;
|
||||
@ -2141,15 +2248,6 @@ class Shop extends React.PureComponent {
|
||||
}
|
||||
|
||||
|
||||
// check for number of recommended EEM connectors
|
||||
['novo', 'urukul', 'koster'].map(_type => {
|
||||
if (itemsCloned.find(elem => elem.type === _type)) {
|
||||
rules[this.state.items[_type].rules.connectors.type] = {...this.state.items[_type].rules.connectors};
|
||||
}
|
||||
return _type;
|
||||
});
|
||||
|
||||
|
||||
if (itemsCloned.find(elem => elem.type === 'urukul')) {
|
||||
if (this.state.items['urukul'].rules.info) {
|
||||
rules[this.state.items['urukul'].rules.info.type] = {...this.state.items['urukul'].rules.info};
|
||||
@ -2260,7 +2358,6 @@ class Shop extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update state with rules
|
||||
this.setState({
|
||||
...this.state,
|
||||
@ -2332,7 +2429,7 @@ class Shop extends React.PureComponent {
|
||||
isMobile={isMobile}
|
||||
title="Order hardware"
|
||||
description="
|
||||
Drag and drop the cards you want into the crate below to see how the combination would look like. If you have any issues with this ordering system, or if you need other configurations, email us directly anytime at sales@m-****.hk. The price is estimated and must be confirmed by a quote."
|
||||
Drag and drop the cards you want into the crate below to see how the combination would look like. Setup card's configuration by tapping at the top of the card, most of the options can be modified after shipment. If you have any issues with this ordering system, or if you need other configurations, email us directly anytime at sales@m-****.hk. The price is estimated and must be confirmed by a quote."
|
||||
crateMode={
|
||||
<CrateMode
|
||||
items={crateModeItems}
|
||||
@ -2350,7 +2447,8 @@ class Shop extends React.PureComponent {
|
||||
itemHovered={currentItemHovered}
|
||||
onToggleOverlayRemove={this.handleToggleOverlayRemove}
|
||||
onClickRemoveItem={this.handleDeleteItem}
|
||||
onClickItem={this.handleShowOverlayRemove}>
|
||||
onClickItem={this.handleShowOverlayRemove}
|
||||
onCardUpdate={this.handleCardsUpdated}>
|
||||
</Cart>
|
||||
}
|
||||
rules={Object.values(rules).filter(rule => rule)}>
|
||||
|
450
static/js/shop_components.jsx
Normal file
450
static/js/shop_components.jsx
Normal file
@ -0,0 +1,450 @@
|
||||
'use strict';
|
||||
|
||||
import React, {Component} from "react";
|
||||
import jsonLogic from 'json-logic-js';
|
||||
import {useState, useEffect} from 'react';
|
||||
import {useClickAway} from "@uidotdev/usehooks";
|
||||
import {OverlayTrigger, Tooltip} from "react-bootstrap";
|
||||
|
||||
// https://stackoverflow.com/a/70511311
|
||||
const true_type_of = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
|
||||
|
||||
|
||||
function Tip({id, tip}) {
|
||||
return (
|
||||
<OverlayTrigger
|
||||
placement="auto"
|
||||
trigger={['click', 'hover', 'focus']}
|
||||
style={{display: 'inline'}}
|
||||
overlay={<Tooltip id={id}>{tip}</Tooltip>}
|
||||
>
|
||||
<img src={`/images/shop/icon-reminder.svg`} className="options-icon"/>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
class Radio extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Initialize the state object with the initial values from the props
|
||||
this.state = {
|
||||
variant: props.outvar in props.data ? props.data[props.outvar] : props.variants[props.fallback ? props.fallback : 0],
|
||||
};
|
||||
|
||||
// Bind the event handler to this
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.props.target.construct(this.props.outvar, this.state.variant);
|
||||
}
|
||||
|
||||
handleClick(variant) {
|
||||
// Update the state object with the new value for outvar
|
||||
this.setState({
|
||||
...this.state,
|
||||
variant: variant
|
||||
});
|
||||
this.props.target.update(this.props.outvar, variant);
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
<div className="shop-radio" key={this.props.id}>
|
||||
<div style={{"display": "inline"}}>
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}
|
||||
</div>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
{this.props.variants.map((variant, _) => (
|
||||
<div className="form-check" key={key + variant}>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name={key}
|
||||
id={key + variant}
|
||||
checked={this.state.variant === variant}
|
||||
onClick={() => this.handleClick(variant)}
|
||||
onChange={() => this.handleClick(variant)}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={key + variant}>
|
||||
{variant}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function RadioWrapper(target, id, data, {title, variants, outvar, fallback, icon, tip}) {
|
||||
return <Radio target={target} title={title} variants={variants} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
fallback={fallback}
|
||||
id={id} data={data}/>;
|
||||
}
|
||||
|
||||
class Switch extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Initialize the state object with the initial values from the props
|
||||
this.state = {
|
||||
checked: props.outvar in props.data ? !!(props.data[props.outvar]) : !!(props.fallback)
|
||||
};
|
||||
|
||||
// Bind the event handler to this
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.props.target.construct(this.props.outvar, this.state.checked);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
// Update the state object with the new value for outvar
|
||||
let new_checked = !this.state.checked;
|
||||
this.setState({
|
||||
checked: new_checked
|
||||
});
|
||||
this.props.target.update(this.props.outvar, new_checked);
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
<div className="shop-switch" key={this.props.id}>
|
||||
<div className="form-check form-switch" key={key}>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id={key}
|
||||
checked={this.state.checked}
|
||||
onClick={this.handleClick}
|
||||
onChange={this.handleClick}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={key} style={{"display": "inline"}}>
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}
|
||||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function SwitchWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
||||
return <Switch target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
id={id} data={data}/>;
|
||||
}
|
||||
|
||||
|
||||
class Line extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Initialize the state object with the initial values from the props
|
||||
this.state = {
|
||||
text: props.outvar in props.data ? props.data[props.outvar] : (props.fallback ? props.fallback : "")
|
||||
};
|
||||
// Bind the event handler to this
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.props.target.construct(this.props.outvar, this.state.text);
|
||||
}
|
||||
|
||||
handleClick(element) {
|
||||
let text = element.target.value;
|
||||
this.setState({
|
||||
text: text
|
||||
});
|
||||
this.props.target.update(this.props.outvar, text);
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
<div className="shop-line" key={this.props.id}>
|
||||
<label htmlFor={key} className="form-label">
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}:
|
||||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
<input type="text" className="form-control form-control-sm" id={key} onChange={this.handleClick}
|
||||
value={this.state.text}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function LineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
||||
return <Line target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
id={id} data={data}/>;
|
||||
}
|
||||
|
||||
class SwitchLine extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// Initialize the state object with the initial values from the props
|
||||
this.state = {
|
||||
text: props.outvar in props.data ? props.data[props.outvar].text : (props.fallback ? props.fallback.text : ""),
|
||||
checked: props.outvar in props.data ? props.data[props.outvar].checked : (props.fallback ? props.fallback.checked : false)
|
||||
};
|
||||
// Bind the event handler to this
|
||||
this.handleText = this.handleText.bind(this);
|
||||
this.handleCheck = this.handleCheck.bind(this);
|
||||
this.props.target.construct(this.props.outvar, this.state);
|
||||
}
|
||||
|
||||
handleText(element) {
|
||||
let new_state = {
|
||||
...this.state,
|
||||
text: element.target.value
|
||||
}
|
||||
this.setState(new_state);
|
||||
this.props.target.update(this.props.outvar, new_state);
|
||||
}
|
||||
|
||||
handleCheck() {
|
||||
// Update the state object with the new value for outvar
|
||||
let new_state = {
|
||||
...this.state,
|
||||
checked: !this.state.checked
|
||||
}
|
||||
this.setState(new_state);
|
||||
this.props.target.update(this.props.outvar, new_state);
|
||||
}
|
||||
|
||||
render() {
|
||||
let key = this.props.id + this.props.outvar;
|
||||
return (
|
||||
<div className="shop-switch-line" key={this.props.id}>
|
||||
<div className="form-check form-switch" key={key}>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id={key + "switch"}
|
||||
checked={this.state.checked}
|
||||
onClick={this.handleCheck}
|
||||
onChange={this.handleCheck}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor={key + "switch"}>
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}
|
||||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
</div>
|
||||
<input type="text" className="form-control form-control-sm" id={key + "line"} onChange={this.handleText}
|
||||
value={this.state.text} disabled={!this.state.checked}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
||||
return <SwitchLine target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||
id={id} data={data}/>;
|
||||
}
|
||||
|
||||
|
||||
function UnimplementedComponent(type, id) {
|
||||
//console.error("Missing component with type:", type)
|
||||
return <div key={type + id} style={{background: "red"}}>UNIMPLEMENTED</div>
|
||||
}
|
||||
|
||||
const componentsList = {
|
||||
"Radio": RadioWrapper,
|
||||
"Switch": SwitchWrapper,
|
||||
"Line": LineWrapper,
|
||||
"SwitchLine": SwitchLineWrapper,
|
||||
"Default": UnimplementedComponent,
|
||||
};
|
||||
|
||||
|
||||
export function ProcessOptions({options, data, target, id}) {
|
||||
let options_t = true_type_of(options);
|
||||
|
||||
if (options_t === "array") {
|
||||
return Array.from(
|
||||
options.map((option_item, i) => ProcessOptions({
|
||||
options: option_item,
|
||||
data: data,
|
||||
target: target,
|
||||
id: id + i
|
||||
}))
|
||||
);
|
||||
} else if (options_t === "object") {
|
||||
if (
|
||||
true_type_of(options.type) === "string" &&
|
||||
(true_type_of(options.args) === "object" || true_type_of(options.items) === "array")
|
||||
) {
|
||||
if (options.type in componentsList) {
|
||||
return componentsList[options.type](target, id + options.type, data, options.args);
|
||||
} else if (options.type === "Group") {
|
||||
return (
|
||||
<div className="border rounded" key={id + "group"}>
|
||||
{ProcessOptions({
|
||||
options: jsonLogic.apply(options.items, data),
|
||||
data: data,
|
||||
target: target,
|
||||
id: id
|
||||
})}
|
||||
</div>);
|
||||
} else {
|
||||
return componentsList["Default"](options.type, id + "missing");
|
||||
}
|
||||
} else {
|
||||
return ProcessOptions({options: jsonLogic.apply(options, data), data: data, target: target, id: id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function FilterOptions(options, data) {
|
||||
let options_t = true_type_of(options);
|
||||
let target = {};
|
||||
|
||||
if (options_t === "array") {
|
||||
options.map((option_item, _) => {
|
||||
Object.assign(target, FilterOptions(option_item, data))
|
||||
});
|
||||
} else if (options_t === "object") {
|
||||
if (
|
||||
true_type_of(options.type) === "string" &&
|
||||
(true_type_of(options.args) === "object" || true_type_of(options.items) === "array")
|
||||
) {
|
||||
if (options.type in componentsList) {
|
||||
target[options.args.outvar] = data[options.args.outvar];
|
||||
} else if (options.type === "Group") {
|
||||
Object.assign(target, FilterOptions(jsonLogic.apply(options.items, data), data))
|
||||
}
|
||||
} else {
|
||||
Object.assign(target, FilterOptions(jsonLogic.apply(options, data), data))
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
export function OptionsDialogPopup({options, data, target, id, big, first, last, options_class}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const ref = useClickAway((e) => {
|
||||
if (e.type === "mousedown") // ignore touchstart
|
||||
setShow(false)
|
||||
}
|
||||
);
|
||||
|
||||
let div_classes = `overlayVariant border rounded ${big ? "overlay-bigcard" : "overlay-smallcard"} ${(!big && first) ? "overlay-first" : ""} ${(!big && last) ? "overlay-last" : ""} ${options_class || ""}`;
|
||||
const handleClick = (event) => {
|
||||
setShow(!show);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<img className="alert-info" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
|
||||
onClick={handleClick}/>
|
||||
<div style={{'display': show ? 'flex' : 'none'}} className={div_classes}>
|
||||
<ProcessOptions
|
||||
options={options}
|
||||
data={data}
|
||||
key={"processed_options_" + id}
|
||||
id={"processed_options_" + id}
|
||||
target={target}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function OptionsSummaryPopup({id, options, data}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const [position, setPosition] = useState({x: 0, y: 0});
|
||||
const [size, setSize] = useState({w: 0, h: 0});
|
||||
let display_options = FilterOptions(options, data);
|
||||
const close = () => {
|
||||
setShow(false);
|
||||
document.removeEventListener("scroll", handleScroll, true);
|
||||
}
|
||||
|
||||
const ref = useClickAway(close);
|
||||
|
||||
const reposition = () => {
|
||||
let popup_button = document.getElementById(id + "img");
|
||||
if (!popup_button) {
|
||||
document.removeEventListener("scroll", handleScroll, true);
|
||||
return;
|
||||
}
|
||||
let rect = popup_button.getBoundingClientRect()
|
||||
let pos_x = (rect.left + rect.right) / 2;
|
||||
let pos_y = (rect.top + rect.bottom) / 2;
|
||||
if (pos_x + size.w > window.innerWidth) {
|
||||
setPosition({x: pos_x - size.w - 20, y: pos_y - size.h / 2});
|
||||
} else {
|
||||
setPosition({x: pos_x - size.w / 2, y: pos_y - size.h - 20});
|
||||
}
|
||||
}
|
||||
|
||||
const handleScroll = (e) => {
|
||||
if (e.target !== document.getElementById(id)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
let popup = document.getElementById(id);
|
||||
let width = popup.offsetWidth;
|
||||
let height = popup.offsetHeight;
|
||||
setSize({w: width, h: height});
|
||||
reposition()
|
||||
}
|
||||
}, [show])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
reposition();
|
||||
}
|
||||
}, [show, size])
|
||||
|
||||
const handleClick = (event) => {
|
||||
setShow(!show);
|
||||
if (!show) {
|
||||
document.addEventListener("scroll", handleScroll, true);
|
||||
}
|
||||
};
|
||||
|
||||
const stringify = (value) => {
|
||||
let value_type = true_type_of(value);
|
||||
if (value_type === "string") {
|
||||
return value;
|
||||
} else if (value_type === "object") {
|
||||
if (value.checked === false) {
|
||||
return "off";
|
||||
} else if (value.checked === true && value.text) {
|
||||
return value.text;
|
||||
}
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<img className="alert-info" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
|
||||
id={id + "img"}
|
||||
onClick={handleClick}/>
|
||||
<div style={{'display': show ? 'flex' : 'none', 'top': position.y, 'left': position.x}}
|
||||
className="overlayVariant card border rounded"
|
||||
id={id}>
|
||||
<div className="card-body">
|
||||
{Array.from(Object.entries(display_options)
|
||||
.filter(([key, value], _) => key !== "ext_data")
|
||||
.map(([key, value], _) => {
|
||||
return (<p className="card-text" key={id + key}><i>{key}</i>: {stringify(value)}</p>);
|
||||
}))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FillExtData(data, index) {
|
||||
return {
|
||||
has_other_dio: data.filter((value, item_index) => index !== item_index && value.name &&value.name.endsWith("-TTL")).length > 0,
|
||||
has_dds: data.filter(((value, _) => value.name === "DDS" && value.name_number === "4410" && (!value.options_data || !value.options_data.mono_eem))).length > 0,
|
||||
has_sampler: data.filter(((value, _) => value.name === "Sampler" && (!value.options_data || !value.options_data.mono_eem))).length > 0,
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,33 @@ const shop_data = {
|
||||
nbrCurrentClock: 0,
|
||||
slotOccupied: 1,
|
||||
clockOccupied: 0,
|
||||
options: [
|
||||
{type: "Radio", args: {title: "DRTIO role", outvar: "drtio_role", variants: ["standalone", "master", "satellite"], tip: "Distributed Real Time Input/Output allows ARTIQ RTIO channels to be distributed among several satellite devices synchronized and controlled by a central core(master) device. Standalone option disables this feature."}},
|
||||
{
|
||||
"if": [
|
||||
{
|
||||
"in": [
|
||||
{"var": "drtio_role"}, [
|
||||
"master", "standalone"
|
||||
]
|
||||
]
|
||||
},
|
||||
[
|
||||
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24", tip: "Set up IPv4 address used by core device"}},
|
||||
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6"}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}, tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
||||
{"if": [
|
||||
{"var": "optics"},
|
||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||
{type: "Radio", args: {title: "Copper cable length", outvar: "cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||
]}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
maxSlot: {
|
||||
type: 'kasli-max-slot',
|
||||
@ -125,6 +152,33 @@ const shop_data = {
|
||||
nbrCurrentClock: 0,
|
||||
slotOccupied: 1,
|
||||
clockOccupied: 0,
|
||||
options: [
|
||||
{type: "Radio", args: {title: "DRTIO role", outvar: "drtio_role", variants: ["standalone", "master", "satellite"], tip: "Distributed Real Time Input/Output allows ARTIQ RTIO channels to be distributed among several satellite devices synchronized and controlled by a central core(master) device. Standalone option disables this feature."}},
|
||||
{
|
||||
"if": [
|
||||
{
|
||||
"in": [
|
||||
{"var": "drtio_role"}, [
|
||||
"master", "standalone"
|
||||
]
|
||||
]
|
||||
},
|
||||
[
|
||||
{type: "Line", args: {title: "IPv4", outvar: "ipv4", fallback: "192.168.1.75/24", tip: "Set up IPv4 address used by core device"}},
|
||||
{type: "SwitchLine", args: {title: "IPv6", outvar: "ipv6"}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}, tip: "Use external clock reference: 10, 80 (beta), 100 or 125 MHz. Other variants may be provided if needed."}}
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
||||
{"if": [
|
||||
{"var": "optics"},
|
||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||
{type: "Radio", args: {title: "Copper cable length", outvar: "cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||
]}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
maxSlot: {
|
||||
type: 'kaslisoc-max-slot',
|
||||
@ -240,6 +294,50 @@ const shop_data = {
|
||||
nbrClockMax: 0,
|
||||
slotOccupied: 1,
|
||||
clockOccupied: 0,
|
||||
options: [
|
||||
{
|
||||
"if": [
|
||||
{"var": "ext_data.has_other_dio"},
|
||||
[
|
||||
{type: "Switch", args: {title: "Output first bank", fallback: true, outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Output first bank", outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_first_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on channel #0"}},
|
||||
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on channel #1"}},
|
||||
{type: "Switch", args: {title: "Termination #2", outvar: "term_2", tip: "Enable termination on channel #2"}},
|
||||
{type: "Switch", args: {title: "Termination #3", outvar: "term_3", tip: "Enable termination on channel #3"}},
|
||||
]},
|
||||
null
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Output second bank", outvar: "out_second_bank", fallback: true, tip: "Switch connectors 4-7 to output"}},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_second_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #4", outvar: "term_4", tip: "Enable termination on channel #4"}},
|
||||
{type: "Switch", args: {title: "Termination #5", outvar: "term_5", tip: "Enable termination on channel #5"}},
|
||||
{type: "Switch", args: {title: "Termination #6", outvar: "term_6", tip: "Enable termination on channel #6"}},
|
||||
{type: "Switch", args: {title: "Termination #7", outvar: "term_7", tip: "Enable termination on channel #7"}},
|
||||
]},
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"and": [{"var": "out_first_bank"}, {"var": "out_second_bank"}]}},
|
||||
{type: "Switch", args: {title: "Edge counter", outvar: "edge_counter", tip: "Enable edge counter for inputs"}}
|
||||
]
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
resources: {
|
||||
type: 'bnc-dio',
|
||||
@ -261,6 +359,50 @@ const shop_data = {
|
||||
],
|
||||
datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf',
|
||||
datasheet_name: '2118/2128 BNC/SMA-TTL datasheet',
|
||||
options: [
|
||||
{
|
||||
"if": [
|
||||
{"var": "ext_data.has_other_dio"},
|
||||
[
|
||||
{type: "Switch", args: {title: "Output first bank", fallback: true, outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Output first bank", outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_first_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on channel #0"}},
|
||||
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on channel #1"}},
|
||||
{type: "Switch", args: {title: "Termination #2", outvar: "term_2", tip: "Enable termination on channel #2"}},
|
||||
{type: "Switch", args: {title: "Termination #3", outvar: "term_3", tip: "Enable termination on channel #3"}},
|
||||
]},
|
||||
null
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Output second bank", outvar: "out_second_bank", fallback: true, tip: "Switch connectors 4-7 to output"}},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_second_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #4", outvar: "term_4", tip: "Enable termination on channel #4"}},
|
||||
{type: "Switch", args: {title: "Termination #5", outvar: "term_5", tip: "Enable termination on channel #5"}},
|
||||
{type: "Switch", args: {title: "Termination #6", outvar: "term_6", tip: "Enable termination on channel #6"}},
|
||||
{type: "Switch", args: {title: "Termination #7", outvar: "term_7", tip: "Enable termination on channel #7"}},
|
||||
]},
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"and": [{"var": "out_first_bank"}, {"var": "out_second_bank"}]}},
|
||||
{type: "Switch", args: {title: "Edge counter", outvar: "edge_counter", tip: "Enable edge counter for inputs"}}
|
||||
]
|
||||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
hp: 4,
|
||||
@ -294,6 +436,72 @@ const shop_data = {
|
||||
],
|
||||
datasheet_file: '/docs/sinara-datasheets/2238.pdf',
|
||||
datasheet_name: '2238 MCX-TTL datasheet',
|
||||
options: [
|
||||
{
|
||||
"if": [
|
||||
{"var": "ext_data.has_other_dio"},
|
||||
[
|
||||
{type: "Switch", args: {title: "Output first bank", fallback: true, outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Output first bank", outvar: "out_first_bank", tip: "Switch connectors 0-3 to output"}}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_first_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on channel #0"}},
|
||||
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on channel #1"}},
|
||||
{type: "Switch", args: {title: "Termination #2", outvar: "term_2", tip: "Enable termination on channel #2"}},
|
||||
{type: "Switch", args: {title: "Termination #3", outvar: "term_3", tip: "Enable termination on channel #3"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Output second bank", outvar: "out_second_bank", fallback: true, tip: "Switch connectors 4-7 to output"}},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_second_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #4", outvar: "term_4", tip: "Enable termination on channel #4"}},
|
||||
{type: "Switch", args: {title: "Termination #5", outvar: "term_5", tip: "Enable termination on channel #5"}},
|
||||
{type: "Switch", args: {title: "Termination #6", outvar: "term_6", tip: "Enable termination on channel #6"}},
|
||||
{type: "Switch", args: {title: "Termination #7", outvar: "term_7", tip: "Enable termination on channel #7"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Output third bank", outvar: "out_third_bank", fallback: true, tip: "Switch connectors 8-11 to output"}},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_third_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #8", outvar: "term_8", tip: "Enable termination on channel #8"}},
|
||||
{type: "Switch", args: {title: "Termination #9", outvar: "term_9", tip: "Enable termination on channel #9"}},
|
||||
{type: "Switch", args: {title: "Termination #10", outvar: "term_10", tip: "Enable termination on channel #10"}},
|
||||
{type: "Switch", args: {title: "Termination #11", outvar: "term_11", tip: "Enable termination on channel #11"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Output fourth bank", outvar: "out_fourth_bank", fallback: true, tip: "Switch connectors 12-15 to output"}},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"var": "out_fourth_bank"}},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #12", outvar: "term_12", tip: "Enable termination on channel #12"}},
|
||||
{type: "Switch", args: {title: "Termination #13", outvar: "term_13", tip: "Enable termination on channel #13"}},
|
||||
{type: "Switch", args: {title: "Termination #14", outvar: "term_14", tip: "Enable termination on channel #14"}},
|
||||
{type: "Switch", args: {title: "Termination #15", outvar: "term_15", tip: "Enable termination on channel #15"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": [
|
||||
{"!": {"and": [{"var": "out_first_bank"}, {"var": "out_second_bank"}, {"var": "out_third_bank"}, {"var": "out_fourth_bank"}]}},
|
||||
{type: "Switch", args: {title: "Edge counter", outvar: "edge_counter", tip: "Enable edge counter for inputs"}}
|
||||
]
|
||||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
hp: 4,
|
||||
@ -327,6 +535,67 @@ const shop_data = {
|
||||
],
|
||||
datasheet_file: '/docs/sinara-datasheets/2245.pdf',
|
||||
datasheet_name: '2245 LVDS-TTL datasheet',
|
||||
options: [
|
||||
{type: "Switch", args: {title: "Configure first RJ45", fallback: false, outvar: "conf_first_rj45", tip: "Configure channels 0-3"}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "conf_first_rj45"},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Output channel #0", fallback: true, outvar: "output_0", tip: "Set channel #0 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #1", fallback: true, outvar: "output_1", tip: "Set channel #1 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #2", fallback: true, outvar: "output_2", tip: "Set channel #2 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #3", fallback: true, outvar: "output_3", tip: "Set channel #3 to output"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Configure second RJ45", fallback: false, outvar: "conf_second_rj45", tip: "Configure channels 4-7"}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "conf_second_rj45"},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Output channel #4", fallback: true, outvar: "output_4", tip: "Set channel #4 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #5", fallback: true, outvar: "output_5", tip: "Set channel #5 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #6", fallback: true, outvar: "output_6", tip: "Set channel #6 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #7", fallback: true, outvar: "output_7", tip: "Set channel #7 to output"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Configure third RJ45", fallback: false, outvar: "conf_third_rj45", tip: "Configure channels 8-11"}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "conf_third_rj45"},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Output channel #8", fallback: true, outvar: "output_8", tip: "Set channel #8 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #9", fallback: true, outvar: "output_9", tip: "Set channel #9 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #10", fallback: true, outvar: "output_10", tip: "Set channel #10 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #11", fallback: true, outvar: "output_11", tip: "Set channel #11 to output"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Configure fourth RJ45", fallback: false, outvar: "conf_fourth_rj45", tip: "Configure channels 12-15"}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "conf_fourth_rj45"},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Output channel #12", fallback: true, outvar: "output_12", tip: "Set channel #12 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #13", fallback: true, outvar: "output_13", tip: "Set channel #13 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #14", fallback: true, outvar: "output_14", tip: "Set channel #14 to output"}},
|
||||
{type: "Switch", args: {title: "Output channel #15", fallback: true, outvar: "output_15", tip: "Set channel #15 to output"}},
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": [
|
||||
{"or": [
|
||||
{"and": [{"var": "conf_first_rj45"}, {"!": {"missing": ["output_0", "output_1", "output_2", "output_3"]}}, {"!": {"and": [{"var": "output_0"}, {"var": "output_1"}, {"var": "output_2"},{"var": "output_3"}]}}]},
|
||||
{"and": [{"var": "conf_second_rj45"}, {"!": {"missing": ["output_4", "output_5", "output_6", "output_7"]}}, {"!": {"and": [{"var": "output_4"}, {"var": "output_5"}, {"var": "output_6"},{"var": "output_7"}]}}]},
|
||||
{"and": [{"var": "conf_third_rj45"}, {"!": {"missing": ["output_8", "output_9", "output_10", "output_11"]}}, {"!": {"and": [{"var": "output_8"}, {"var": "output_9"}, {"var": "output_10"},{"var": "output_11"}]}}]},
|
||||
{"and": [{"var": "conf_fourth_rj45"}, {"!": {"missing": ["output_12", "output_13", "output_14", "output_15"]}}, {"!": {"and": [{"var": "output_12"}, {"var": "output_13"}, {"var": "output_14"},{"var": "output_15"}]}}]}
|
||||
]},
|
||||
{type: "Switch", args: {title: "Edge counter", outvar: "edge_counter", tip: "Enable edge counter for inputs"}}
|
||||
]
|
||||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
hp: 4,
|
||||
@ -354,16 +623,45 @@ const shop_data = {
|
||||
specs: [
|
||||
'4 channel 1GS/s DDS.',
|
||||
'Output frequency (-3 dB): <1 to >400 MHz.',
|
||||
'Frequency resolution ~0.25 Hz (32 bit)',
|
||||
'Nominal max output power 10 dBm.',
|
||||
'Digital step attenuator 0 to -31.5dB.',
|
||||
'RF switch (1ns temporal resolution), 70dB isolation.',
|
||||
'AD9910 or AD9912 chip.',
|
||||
'By default, we use the AD9910 as it provides more features. If you need the higher frequency resolution of the AD9912, leave us a note.',
|
||||
'AD9910 and AD9912 cards can be used at the same time in the same crate.',
|
||||
'External 5W power amplifier is available separately, leave us a note if interested.'
|
||||
],
|
||||
datasheet_file: '/docs/sinara-datasheets/4410-4412.pdf',
|
||||
datasheet_name: '4410/4412 Urukul datasheet',
|
||||
options: [
|
||||
{type: "Switch", args: {title: "Use 1 EEM", outvar: "mono_eem", tip: "Use one EEM port setup. RF switch and synchronization will be unavailable."}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "mono_eem"},
|
||||
[
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
],
|
||||
[
|
||||
{type: "Switch", args: {title: "Synchronization", outvar: "sync", tip: "Synchronize phases across Urukuls"}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "sync"},
|
||||
null,
|
||||
[
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "ext_data.has_sampler"},
|
||||
{type: "Switch", args: {title: "SUServo mode", outvar: "suservo"}},
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: 'urukul',
|
||||
hp: 4,
|
||||
@ -373,18 +671,47 @@ const shop_data = {
|
||||
slotOccupied: 2,
|
||||
clockOccupied: 1,
|
||||
rules: {
|
||||
connectors: {
|
||||
resources: {
|
||||
type: 'urukul',
|
||||
icon: '/shop/icon-reminder.svg',
|
||||
icon: '/shop/icon-warning.svg',
|
||||
name: 'Urukul',
|
||||
message: 'This configuration uses 2 EEM connectors as it is recommended. If you prefer to use 1 EEM connector, please inform us by leaving an additional note.',
|
||||
},
|
||||
info: {
|
||||
type: 'urukul-info',
|
||||
icon: '/shop/icon-reminder.svg',
|
||||
name: 'Urukul',
|
||||
message: 'The default chip is AD9910, which supports more features. If you need the higher frequency resolution of the AD9912, leave us a note.',
|
||||
message: 'This card needs a card that provides EEM and clocking connectors (e.g. Kasli) at its left.',
|
||||
},
|
||||
},
|
||||
},
|
||||
'urukul_4412': {
|
||||
id: 'urukul_4412',
|
||||
name: 'DDS',
|
||||
name_number: '4412',
|
||||
name_codename: 'Urukul',
|
||||
price: 2350,
|
||||
image: '/shop/graphic-03_Urukul-4412.svg',
|
||||
specs: [
|
||||
'4 channel 1GS/s DDS.',
|
||||
'Higher frequency resolution ~8 µHz (47 bit)',
|
||||
'Output frequency (-3 dB): <1 to >400 MHz.',
|
||||
'Nominal max output power 10 dBm.',
|
||||
'Digital step attenuator 0 to -31.5dB.',
|
||||
'RF switch (1ns temporal resolution), 70dB isolation.',
|
||||
'AD9912 chip.',
|
||||
'AD9910 and AD9912 cards can be used at the same time in the same crate.',
|
||||
'External 5W power amplifier is available separately, leave us a note if interested.'
|
||||
],
|
||||
datasheet_file: '/docs/sinara-datasheets/4410-4412.pdf',
|
||||
datasheet_name: '4410/4412 Urukul datasheet',
|
||||
options: [
|
||||
{type: "Switch", args: {title: "Use 1 EEM", outvar: "mono_eem", tip: "Use one EEM port setup. RF switch and synchronization will be unavailable."}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'small',
|
||||
type: 'urukul',
|
||||
hp: 4,
|
||||
nbrSlotMin: 0,
|
||||
nbrSlotMax: 0,
|
||||
nbrClockMax: 0,
|
||||
slotOccupied: 2,
|
||||
clockOccupied: 1,
|
||||
rules: {
|
||||
resources: {
|
||||
type: 'urukul',
|
||||
icon: '/shop/icon-warning.svg',
|
||||
@ -409,13 +736,17 @@ const shop_data = {
|
||||
'Internal MMCX clock from Kasli/Clocker and external SMA.',
|
||||
'The upconverter is optional, if you would like the baseband version please leave us a note.'
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["Baseband", "Upconverter"], fallback: 1}},
|
||||
],
|
||||
size: 'small',
|
||||
type: 'urukul',
|
||||
hp: 4,
|
||||
nbrSlotMin: 0,
|
||||
nbrSlotMax: 0,
|
||||
nbrClockMax: 0,
|
||||
slotOccupied: 2,
|
||||
slotOccupied: 1,
|
||||
clockOccupied: 1,
|
||||
rules: {
|
||||
resources: {
|
||||
@ -442,6 +773,9 @@ const shop_data = {
|
||||
'Channels can also be broken out to BNC or SMA using IDC-BNC, IDC-SMA or IDC-MCX cards.',
|
||||
'DAC temperature can be stabilized using the Sinara 8451 Thermostat (sold separately).'
|
||||
],
|
||||
options: [
|
||||
{type: "Switch", args: {title: "TEC chip", outvar: "tec_chip", tip: "Used for stabilizing temperature with Sinara 8451 Thermostat"}},
|
||||
],
|
||||
datasheet_file: '/docs/sinara-datasheets/5432.pdf',
|
||||
datasheet_name: '5432 Zotino datasheet',
|
||||
size: 'small',
|
||||
@ -619,6 +953,10 @@ const shop_data = {
|
||||
size: 'small',
|
||||
type: 'hd68',
|
||||
hp: 4,
|
||||
options: [
|
||||
{type: "Radio", args: {title: "Cable length", outvar: "cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
|
||||
],
|
||||
options_class: "hd68-idc",
|
||||
nbrSlotMin: 1,
|
||||
nbrSlotMax: 4,
|
||||
nbrCurrentSlot: 0,
|
||||
@ -662,6 +1000,31 @@ const shop_data = {
|
||||
'Full-scale input ranges between +-10mV and +-10V.',
|
||||
'Supports SU-Servo laser intensity stabilization servo in conjunction with Urukul.'
|
||||
],
|
||||
options: [
|
||||
{type: "Switch", args: {title: "1 EEM mode", outvar: "mono_eem"}},
|
||||
{
|
||||
"if": [
|
||||
{"and": [{"var": "ext_data.has_dds"}, {"!": {"var": "mono_eem"}}]},
|
||||
{type: "Switch", args: {title: "SUServo mode", outvar: "suservo"}},
|
||||
null
|
||||
]
|
||||
},
|
||||
{type: "Switch", args: {title: "Configure termination", outvar: "config_term"}},
|
||||
{
|
||||
"if": [
|
||||
{"var": "config_term"},
|
||||
{type: "Group", items: [
|
||||
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on channel #0"}},
|
||||
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on channel #1"}},
|
||||
{type: "Switch", args: {title: "Termination #2", outvar: "term_2", tip: "Enable termination on channel #2"}},
|
||||
{type: "Switch", args: {title: "Termination #3", outvar: "term_3", tip: "Enable termination on channel #3"}},
|
||||
{type: "Switch", args: {title: "Termination #4", outvar: "term_4", tip: "Enable termination on channel #4"}},
|
||||
{type: "Switch", args: {title: "Termination #5", outvar: "term_5", tip: "Enable termination on channel #5"}},
|
||||
{type: "Switch", args: {title: "Termination #6", outvar: "term_6", tip: "Enable termination on channel #6"}},
|
||||
{type: "Switch", args: {title: "Termination #7", outvar: "term_7", tip: "Enable termination on channel #7"}},
|
||||
]}]
|
||||
}
|
||||
],
|
||||
size: 'big',
|
||||
type: 'novo',
|
||||
hp: 8,
|
||||
@ -671,12 +1034,6 @@ const shop_data = {
|
||||
slotOccupied: 2,
|
||||
clockOccupied: 0,
|
||||
rules: {
|
||||
connectors: {
|
||||
type: 'novo',
|
||||
icon: '/shop/icon-reminder.svg',
|
||||
name: 'Sampler',
|
||||
message: 'This configuration uses 2 EEM connectors as it is recommended. If you prefer to use 1 EEM connector, please inform us by leaving an additional note.',
|
||||
},
|
||||
resources: {
|
||||
type: 'novo',
|
||||
icon: '/shop/icon-warning.svg',
|
||||
@ -699,6 +1056,9 @@ const shop_data = {
|
||||
'Existing stack supports summing over rectangular ROIs and reporting the result to ARTIQ kernels.',
|
||||
'Camera signal is entirely processed in the Kasli FPGA.',
|
||||
],
|
||||
options: [
|
||||
{type: "Radio", args: {title: "Connectors", outvar: "n_eem", variants: ["1 EEM", "2 EEM", "3 EEM"], tip: "Number of EEM ports to use.", fallback: 1}},
|
||||
],
|
||||
size: 'small',
|
||||
type: 'koster',
|
||||
hp: 4,
|
||||
@ -708,12 +1068,6 @@ const shop_data = {
|
||||
slotOccupied: 2,
|
||||
clockOccupied: 0,
|
||||
rules: {
|
||||
connectors: {
|
||||
type: 'koster',
|
||||
icon: '/shop/icon-reminder.svg',
|
||||
name: 'Grabber',
|
||||
message: 'This configuration uses 2 EEM connectors. If you prefer to use 1 or 3 EEM connectors, please inform us by leaving an additional note.',
|
||||
},
|
||||
resources: {
|
||||
type: 'koster',
|
||||
icon: '/shop/icon-warning.svg',
|
||||
@ -736,6 +1090,11 @@ const shop_data = {
|
||||
'Frequency up to 1GHz.',
|
||||
'Low jitter <100fs RMS.'
|
||||
],
|
||||
options: [
|
||||
{type: "Switch", args: {title: "Ext CLK", outvar: "ext_clk"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}
|
||||
],
|
||||
options_class: "clocker",
|
||||
size: 'small',
|
||||
type: 'clocker',
|
||||
hp: 4,
|
||||
@ -774,6 +1133,13 @@ const shop_data = {
|
||||
'100Base-T Ethernet.',
|
||||
'Can be controlled by Kasli or work stand-alone with PoE supply.'
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip", fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "Switch", args: {title: "Term #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
|
||||
{type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
|
||||
],
|
||||
options_class: "stabilizer",
|
||||
size: 'small',
|
||||
type: null,
|
||||
hp: 4,
|
||||
@ -798,6 +1164,9 @@ const shop_data = {
|
||||
'Lower jitter and phase noise.',
|
||||
'Large frequency changes take several milliseconds.',
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
hp: 4,
|
||||
@ -827,6 +1196,9 @@ const shop_data = {
|
||||
'Additional 4 channels up to 12 GHz.',
|
||||
'Each Almazny channel outputs twice the frequency of its corresponding Mirny channel.',
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'big',
|
||||
type: null,
|
||||
hp: 8,
|
||||
@ -859,6 +1231,9 @@ const shop_data = {
|
||||
'Up to 16W (+4A 4V) from MAX1969 drivers.',
|
||||
'100Base-T Ethernet with PoE.'
|
||||
],
|
||||
options: [
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
hp: 4,
|
||||
@ -912,6 +1287,13 @@ const shop_data = {
|
||||
'2-channel Pound Drever Hall (PDH) lock generator.',
|
||||
'AD9959 DDS (500MSPS, 10-bit).'
|
||||
],
|
||||
options: [
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip", fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}},
|
||||
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
|
||||
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
|
||||
],
|
||||
size: 'big',
|
||||
type: null,
|
||||
hp: 8,
|
||||
@ -974,6 +1356,7 @@ const shop_data = {
|
||||
itemIds: [
|
||||
'clocker',
|
||||
'urukul',
|
||||
'urukul_4412',
|
||||
'phaser',
|
||||
'mirny',
|
||||
'almazny',
|
||||
|
Loading…
Reference in New Issue
Block a user