Add options for the cards #93

Merged
sb10q merged 42 commits from esavkin/web2019:85-variants into master 2023-11-28 15:32:34 +08:00
9 changed files with 1580 additions and 520 deletions

817
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,23 +12,24 @@
"url": "https://git.m-labs.hk/M-Labs/web2019.git" "url": "https://git.m-labs.hk/M-Labs/web2019.git"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.22.6", "@babel/cli": "^7.23.0",
"@babel/core": "^7.22.8", "@babel/core": "^7.23.2",
"@babel/preset-env": "^7.22.7", "@babel/preset-env": "^7.23.2",
"@babel/preset-react": "^7.22.5", "@babel/preset-react": "^7.22.15",
"babel-preset-minify": "^0.5.2",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"webpack": "^5.88.1", "babel-preset-minify": "^0.5.2",
"webpack-cli": "^5.1.4",
"bootstrap": "^5.3.0", "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", "jquery": "^3.7.0",
"uuid": "^9.0.0", "prop-types": "^15.8.1",
"react-bootstrap": "^2.8.0" "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": { "babel": {
"presets": [ "presets": [

View File

@ -255,11 +255,31 @@ button {
.item-card-name, .item-card-name,
.price { .price {
> .alert-warning { .alert-warning, .alert-info {
background-color: inherit; background-color: inherit;
height: inherit; height: inherit;
width: 20px; 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; display: flex;
flex-direction: column; flex-direction: column;
/*max-width: 96px;*/ /*max-width: 96px;*/
max-width: 130px; max-width: 132px;
justify-content: flex-start; justify-content: flex-start;
align-content: center; align-content: center;
align-items: center; align-items: center;
@ -432,7 +452,7 @@ button {
height: 24px; height: 24px;
} }
> .alert-warning { > .alert-warning, .alert-info {
background-color: inherit; background-color: inherit;
height: inherit; height: inherit;
width: 20px; 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 { .hovered {

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View 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

View File

@ -1,14 +1,13 @@
'use strict'; 'use strict';
import React from "react"; import React from "react";
import axios from "axios";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { OptionsDialogPopup, OptionsSummaryPopup, FilterOptions, FillExtData } from "./shop_components.jsx";
import { OverlayTrigger } from "react-bootstrap"; import { OverlayTrigger } from "react-bootstrap";
const data = window.shop_data; const data = window.shop_data;
const itemsUnfoldedList = Array.from(data.columns.backlog.categories.map(groupId => groupId.itemIds).flat()); 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) { conf_obj.items = conf_obj.items.map(function (item) {
return { return {
pn: item.pn, pn: item.pn,
options: item.options ? item.options : null,
}; };
}); });
@ -535,12 +535,16 @@ class ProductCartItem extends React.PureComponent {
isMobile: PropTypes.bool, isMobile: PropTypes.bool,
isTouch: PropTypes.bool, isTouch: PropTypes.bool,
hovered: PropTypes.bool, hovered: PropTypes.bool,
first: PropTypes.bool,
last: PropTypes.bool,
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
model: PropTypes.object.isRequired, model: PropTypes.object.isRequired,
data: PropTypes.object, data: PropTypes.object,
ext_data: PropTypes.object,
onToggleOverlayRemove: PropTypes.func, onToggleOverlayRemove: PropTypes.func,
onClickRemoveItem: PropTypes.func, onClickRemoveItem: PropTypes.func,
onClickItem: PropTypes.func, onClickItem: PropTypes.func,
onCardUpdate: PropTypes.func,
}; };
} }
@ -591,9 +595,13 @@ class ProductCartItem extends React.PureComponent {
model, model,
data, data,
index, index,
first,
last,
ext_data,
onCardUpdate,
} = this.props; } = this.props;
let warning; let warning, options, options_data;
if (data && data.warnings) { if (data && data.warnings) {
const warningsKeys = Object.keys(data.warnings); const warningsKeys = Object.keys(data.warnings);
if (warningsKeys && warningsKeys.length > 0) { 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; let render_progress;
if (data) { if (data) {
switch(model.type) { switch(model.type) {
case 'kasli': case 'kasli':
render_progress = ( render_progress = [
<div className="k-popup-connectors"> (<p key={model.type+model.id+"EEM"}>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>),
<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p> (<p key={model.type+model.id+"CLK"}>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>)
<p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p> ];
</div>
);
break; break;
case 'vhdcicarrier': case 'vhdcicarrier':
render_progress = ( render_progress = (<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>);
<div className="k-popup-connectors">
<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>
</div>
);
break; break;
case 'zotino': case 'zotino':
case 'hd68': case 'hd68':
render_progress = ( render_progress = (<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} connectors used`}</p>);
<div className="k-popup-connectors">
<p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} connectors used`}</p>
</div>
);
break; break;
case 'clocker': case 'clocker':
render_progress = ( render_progress = (<p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>);
<div className="k-popup-connectors">
<p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>
</div>
);
break; break;
default: default:
@ -656,8 +659,8 @@ class ProductCartItem extends React.PureComponent {
provided.draggableProps.style, provided.draggableProps.style,
snapshot, snapshot,
true, true,
hovered ? true : false, !!hovered,
model.selected ? true : false, !!model.selected,
true true
)}} )}}
onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)} onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
@ -665,23 +668,50 @@ class ProductCartItem extends React.PureComponent {
> >
{/* warning container */} {/* warning container */}
<OverlayTrigger
<div className="progress-container warning d-flex justify-content-evenly">
{warning &&
(<OverlayTrigger
placement="bottom" placement="bottom"
trigger={warning ? ['click', 'hover', 'focus'] : []} trigger={['click', 'hover', 'focus']}
overlay={ overlay={
warning ? (<div className="k-popup-warning"> ({arrowProps, hasDoneInitialMeasure, show, ...props}) => (
<div className="k-popup-warning" {...props}>
<p className="rule warning"> <p className="rule warning">
<i>{warning.message}</i> <i>{warning.message}</i>
</p> </p>
</div>) : null </div>)
} }
rootClose rootClose
> >
<div className="progress-container warning"> <img className="alert-warning" src={`/images${warning.icon}`}/>
{warning && (<img className="alert-warning" src={warning ? `/images${warning.icon}` : null}/>)} </OverlayTrigger>)
</div> }
</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> <h6>{model.name_number}</h6>
@ -711,7 +741,7 @@ class ProductCartItem extends React.PureComponent {
<OverlayTrigger <OverlayTrigger
placement="top" placement="top"
trigger={['click', 'hover', 'focus']} trigger={['click', 'hover', 'focus']}
overlay={render_progress} overlay={({arrowProps, hasDoneInitialMeasure, show, ...props}) => (<div className="k-popup-connectors" {...props}>{render_progress}</div>)}
rootClose rootClose
> >
<div className="progress-container"> <div className="progress-container">
@ -800,6 +830,7 @@ class Cart extends React.PureComponent {
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,
onToggleOverlayRemove: PropTypes.func, onToggleOverlayRemove: PropTypes.func,
onClickRemoveItem: PropTypes.func, onClickRemoveItem: PropTypes.func,
onCardUpdate: PropTypes.func,
onClickItem: PropTypes.func, onClickItem: PropTypes.func,
}; };
} }
@ -814,10 +845,14 @@ class Cart extends React.PureComponent {
onToggleOverlayRemove, onToggleOverlayRemove,
onClickRemoveItem, onClickRemoveItem,
onClickItem, onClickItem,
onCardUpdate,
} = this.props; } = this.props;
const nbrOccupied = nbrOccupiedSlotsInCrate(data.items);
const products = data.items.map((item, index) => { const products = data.items.map((item, index) => {
let itemData; let itemData;
let ext_data = FillExtData(data.itemsData, index);
if (data.itemsData && index in data.itemsData) { if (data.itemsData && index in data.itemsData) {
itemData = data.itemsData[index]; itemData = data.itemsData[index];
} }
@ -827,10 +862,15 @@ class Cart extends React.PureComponent {
isTouch={isTouch} isTouch={isTouch}
hovered={item.id === itemHovered} hovered={item.id === itemHovered}
key={item.id} key={item.id}
id={item.id}
index={index} index={index}
first={index === 0}
last={index === data.items.length - 1 && nbrOccupied >= nbrSlots}
data={itemData} data={itemData}
ext_data={ext_data}
onToggleOverlayRemove={onToggleOverlayRemove} onToggleOverlayRemove={onToggleOverlayRemove}
onClickRemoveItem={onClickRemoveItem} onClickRemoveItem={onClickRemoveItem}
onCardUpdate={onCardUpdate}
onClickItem={onClickItem} onClickItem={onClickItem}
model={item}> model={item}>
</ProductCartItem> </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" className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
style={{'cursor': 'pointer', 'fontWeight': '700'}} style={{'cursor': 'pointer', 'fontWeight': '700'}}
defaultValue="Show JSON" 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'}`} /> <input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit" value={`${isProcessing ? 'Processing ...' : 'Request quote'}`} />
</div> </div>
@ -1369,16 +1410,17 @@ class OrderSumary extends React.PureComponent {
<tbody> <tbody>
{summary.map((item, index) => { {summary.map((item, index) => {
let alert; let alert, warning, options, options_data;
let warning;
if (itemsData[index]) { if (itemsData[index] && itemsData[index].warnings) {
alert = itemsData[index]; alert = itemsData[index];
const warningsKeys = Object.keys(alert.warnings); const warningsKeys = Object.keys(alert.warnings);
if (warningsKeys && warningsKeys.length > 0) { if (warningsKeys && warningsKeys.length > 0) {
warning = alert.warnings[warningsKeys[0]]; warning = alert.warnings[warningsKeys[0]];
} }
} }
options = itemsData[index] && itemsData[index].options;
options_data = itemsData[index] && itemsData[index].options_data;
return ( return (
<tr key={item.id} <tr key={item.id}
@ -1392,28 +1434,35 @@ class OrderSumary extends React.PureComponent {
</td> </td>
<td className="price"> <td className="price">
<div> <div className="d-inline-flex align-content-center">
{`${currency} ${formatMoney(item.price)}`} {`${currency} ${formatMoney(item.price)}`}
<button onClick={this.handleOnDeleteItem.bind(this, index)}> <button onClick={this.handleOnDeleteItem.bind(this, index)}>
<img src="/images/shop/icon-remove.svg" /> <img src="/images/shop/icon-remove.svg" />
</button> </button>
</div>
{warning && ( <div style={{'width': '45px', 'height': '20px'}} className="d-inline-flex align-content-center align-self-center justify-content-evenly">
{(warning ? (
<img <img
style={{'marginLeft': '10px'}} className="alert-warning align-self-start"
className="alert-warning"
src={`/images/${warning.icon}`} src={`/images/${warning.icon}`}
/> />
)} ) : (
{!warning && (
<span style={{ <span style={{
'display': 'inline-block', 'display': 'inline-block',
'width': '30px', 'width': '20px',
}}>&nbsp;</span> }}>&nbsp;</span>
)} ))}
{((options && options_data) ? (
<OptionsSummaryPopup id={item.id + "options"} options={options} data={options_data} />
) : (
<span style={{
'display': 'inline-block',
'width': '20px',
}}>&nbsp;</span>
))}
</div>
</div>
</td> </td>
</tr> </tr>
); );
@ -1598,6 +1647,7 @@ class Shop extends React.PureComponent {
this.handleClickShowOrder = this.handleClickShowOrder.bind(this); this.handleClickShowOrder = this.handleClickShowOrder.bind(this);
this.handleClickOpenImport = this.handleClickOpenImport.bind(this); this.handleClickOpenImport = this.handleClickOpenImport.bind(this);
this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this); this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this);
this.handleCardsUpdated = this.handleCardsUpdated.bind(this);
this.timer = null; this.timer = null;
this.timer_remove = null; this.timer_remove = null;
@ -1635,9 +1685,7 @@ class Shop extends React.PureComponent {
(prevState.columns.cart.items !== this.state.columns.cart.items) || (prevState.columns.cart.items !== this.state.columns.cart.items) ||
(prevState.currentMode !== this.state.currentMode) (prevState.currentMode !== this.state.currentMode)
) { ) {
this.checkAlerts( this.checkAlerts(this.state.columns.cart.items);
prevState.columns.cart.items,
this.state.columns.cart.items);
} }
if (this.state.newCardJustAdded) { if (this.state.newCardJustAdded) {
@ -1653,6 +1701,10 @@ class Shop extends React.PureComponent {
clearTimeout(this.timer); clearTimeout(this.timer);
} }
handleCardsUpdated() {
this.checkAlerts(this.state.columns.cart.items);
}
handleCrateModeChange(mode) { handleCrateModeChange(mode) {
this.setState({ this.setState({
currentMode: mode, currentMode: mode,
@ -1660,8 +1712,11 @@ class Shop extends React.PureComponent {
} }
handleDeleteItem(index) { 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.splice(index, 1);
cloned_data.splice(index, 1);
this.setState({ this.setState({
...this.state, ...this.state,
columns: { columns: {
@ -1669,6 +1724,7 @@ class Shop extends React.PureComponent {
cart: { cart: {
...this.state.columns.cart, ...this.state.columns.cart,
items: cloned, items: cloned,
itemsData: cloned_data,
}, },
}, },
}); });
@ -1682,6 +1738,7 @@ class Shop extends React.PureComponent {
cart: { cart: {
...this.state.columns.cart, ...this.state.columns.cart,
items: [], items: [],
itemsData: []
}, },
}, },
}); });
@ -1771,10 +1828,13 @@ class Shop extends React.PureComponent {
type: this.state.currentMode, type: this.state.currentMode,
}; };
const clonedCart = Array.from(this.state.columns.cart.items); const clonedCart = Array.from(this.state.columns.cart.items);
const clonedCartData = Array.from(this.state.columns.cart.itemsData);
for (const i in clonedCart) { for (const i in clonedCart) {
const item = clonedCart[i]; const item = clonedCart[i];
const item_data = clonedCartData[i];
crate.items.push({ crate.items.push({
'pn': item.name_number, '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, isProcessing: false,
shouldShowRFQFeedback: true, shouldShowRFQFeedback: true,
RFQBodyType: 'show', RFQBodyType: 'show',
RFQBodyOrder: JSON.stringify(crate), RFQBodyOrder: JSON.stringify(crate, null, 2),
}); });
} }
@ -1798,9 +1858,9 @@ class Shop extends React.PureComponent {
if (!customconf) {return; } if (!customconf) {return; }
const items = this.props.data.items; const items = this.props.data.items;
const self = this;
let new_items = []; let new_items = [];
let new_items_data = [];
this.setState({ this.setState({
@ -1821,14 +1881,15 @@ class Shop extends React.PureComponent {
...items[key], ...items[key],
}, { }, {
id: uuidv4(), id: uuidv4(),
options_data: item.options ? item.options : null,
})); }));
new_items_data.push({options_data: item.options ? item.options : null});
} }
}); });
return item; return item;
}); });
this.setState({ this.setState({
...this.state, ...this.state,
columns: { columns: {
@ -1836,6 +1897,7 @@ class Shop extends React.PureComponent {
cart: { cart: {
...this.state.columns.cart, ...this.state.columns.cart,
items: new_items, items: new_items,
itemsData: new_items_data,
}, },
}, },
currentMode: customconf.type, currentMode: customconf.type,
@ -1849,10 +1911,13 @@ class Shop extends React.PureComponent {
type: this.state.currentMode, type: this.state.currentMode,
}; };
const clonedCart = Array.from(this.state.columns.cart.items); const clonedCart = Array.from(this.state.columns.cart.items);
const clonedCartData = Array.from(this.state.columns.cart.itemsData);
for (const i in clonedCart) { for (const i in clonedCart) {
const item = clonedCart[i]; const item = clonedCart[i];
const item_data = clonedCartData[i];
crate.items.push({ crate.items.push({
'pn': item.name_number, '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}); this.setState({isProcessing: true});
axios.post(data.API_RFQ, { fetch(data.API_RFQ, {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email, email,
note, note,
configuration: JSON.stringify(crate) configuration: JSON.stringify(crate)
})
}).then(response => { }).then(response => {
if (response.status !== 200) {
throw Error("Response status is not OK: " + response.status + ".\n" + response);
}
this.setState({ this.setState({
isProcessing: false, isProcessing: false,
shouldShowRFQFeedback: true, shouldShowRFQFeedback: true,
@ -1872,6 +1944,7 @@ class Shop extends React.PureComponent {
isProcessingComplete: true, isProcessingComplete: true,
}); });
}).catch(err => { }).catch(err => {
console.error("Request failed, reason:", err)
this.setState({isProcessing: false}, () => { 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"); 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, this.state.columns[source.droppableId].items,
source.index, source.index,
), ),
itemsData: remove(
this.state.columns[source.droppableId].itemsData,
source.index,
)
}, },
}, },
}); });
@ -1951,6 +2028,11 @@ class Shop extends React.PureComponent {
source.index, source.index,
destination.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 ---'); console.log('--- START CHECKING CRATE WARNING ---');
const { const {
@ -1987,6 +2069,14 @@ class Shop extends React.PureComponent {
const itemsData = []; const itemsData = [];
const rules = {}; 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 // check number of slot in crate
const nbrOccupied = nbrOccupiedSlotsInCrate(newItems); 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 nbUsedSlot = slots
.filter(item => item.type !== 'idc-bnc') .filter(item => item.type !== 'idc-bnc')
.reduce((prev, next) => { .reduce((prev, next) => {
return prev + next.slotOccupied; return prev + process_slots(next);
}, 0); }, 0);
nbrCurrentClock = slots nbrCurrentClock = slots
.reduce((prev, next) => { .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); }, 0);
if (idx in itemsData) { if (idx in itemsData) {
@ -2119,11 +2226,11 @@ class Shop extends React.PureComponent {
} }
nbrCurrentClock = slots.reduce((prev, next) => { 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); }, 0);
if (idx in itemsData) { if (idx in itemsData) {
if (itemsData[idx].nbrCurrentClock) { if (itemsData[idx].nbrCurrentClock && itemsData[idx].type !== "clocker") {
itemsData[idx].nbrCurrentClock += nbrCurrentClock; itemsData[idx].nbrCurrentClock += nbrCurrentClock;
} else { } else {
itemsData[idx].nbrCurrentClock = nbrCurrentClock; 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 (itemsCloned.find(elem => elem.type === 'urukul')) {
if (this.state.items['urukul'].rules.info) { if (this.state.items['urukul'].rules.info) {
rules[this.state.items['urukul'].rules.info.type] = {...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 // update state with rules
this.setState({ this.setState({
...this.state, ...this.state,
@ -2332,7 +2429,7 @@ class Shop extends React.PureComponent {
isMobile={isMobile} isMobile={isMobile}
title="Order hardware" title="Order hardware"
description=" 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={
<CrateMode <CrateMode
items={crateModeItems} items={crateModeItems}
@ -2350,7 +2447,8 @@ class Shop extends React.PureComponent {
itemHovered={currentItemHovered} itemHovered={currentItemHovered}
onToggleOverlayRemove={this.handleToggleOverlayRemove} onToggleOverlayRemove={this.handleToggleOverlayRemove}
onClickRemoveItem={this.handleDeleteItem} onClickRemoveItem={this.handleDeleteItem}
onClickItem={this.handleShowOverlayRemove}> onClickItem={this.handleShowOverlayRemove}
onCardUpdate={this.handleCardsUpdated}>
</Cart> </Cart>
} }
rules={Object.values(rules).filter(rule => rule)}> rules={Object.values(rules).filter(rule => rule)}>

View 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,
}
}

View File

@ -64,6 +64,33 @@ const shop_data = {
nbrCurrentClock: 0, nbrCurrentClock: 0,
slotOccupied: 1, slotOccupied: 1,
clockOccupied: 0, 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: { rules: {
maxSlot: { maxSlot: {
type: 'kasli-max-slot', type: 'kasli-max-slot',
@ -125,6 +152,33 @@ const shop_data = {
nbrCurrentClock: 0, nbrCurrentClock: 0,
slotOccupied: 1, slotOccupied: 1,
clockOccupied: 0, 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: { rules: {
maxSlot: { maxSlot: {
type: 'kaslisoc-max-slot', type: 'kaslisoc-max-slot',
@ -240,6 +294,50 @@ const shop_data = {
nbrClockMax: 0, nbrClockMax: 0,
slotOccupied: 1, slotOccupied: 1,
clockOccupied: 0, 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: { rules: {
resources: { resources: {
type: 'bnc-dio', type: 'bnc-dio',
@ -261,6 +359,50 @@ const shop_data = {
], ],
datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf', datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf',
datasheet_name: '2118/2128 BNC/SMA-TTL datasheet', 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', size: 'small',
type: null, type: null,
hp: 4, hp: 4,
@ -294,6 +436,72 @@ const shop_data = {
], ],
datasheet_file: '/docs/sinara-datasheets/2238.pdf', datasheet_file: '/docs/sinara-datasheets/2238.pdf',
datasheet_name: '2238 MCX-TTL datasheet', 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', size: 'small',
type: null, type: null,
hp: 4, hp: 4,
@ -327,6 +535,67 @@ const shop_data = {
], ],
datasheet_file: '/docs/sinara-datasheets/2245.pdf', datasheet_file: '/docs/sinara-datasheets/2245.pdf',
datasheet_name: '2245 LVDS-TTL datasheet', 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', size: 'small',
type: null, type: null,
hp: 4, hp: 4,
@ -354,16 +623,45 @@ const shop_data = {
specs: [ specs: [
'4 channel 1GS/s DDS.', '4 channel 1GS/s DDS.',
'Output frequency (-3 dB): <1 to >400 MHz.', 'Output frequency (-3 dB): <1 to >400 MHz.',
'Frequency resolution ~0.25 Hz (32 bit)',
'Nominal max output power 10 dBm.', 'Nominal max output power 10 dBm.',
'Digital step attenuator 0 to -31.5dB.', 'Digital step attenuator 0 to -31.5dB.',
'RF switch (1ns temporal resolution), 70dB isolation.', '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.', '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.' 'External 5W power amplifier is available separately, leave us a note if interested.'
], ],
datasheet_file: '/docs/sinara-datasheets/4410-4412.pdf', datasheet_file: '/docs/sinara-datasheets/4410-4412.pdf',
datasheet_name: '4410/4412 Urukul datasheet', 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', size: 'small',
type: 'urukul', type: 'urukul',
hp: 4, hp: 4,
@ -373,18 +671,47 @@ const shop_data = {
slotOccupied: 2, slotOccupied: 2,
clockOccupied: 1, clockOccupied: 1,
rules: { rules: {
connectors: { resources: {
type: 'urukul', type: 'urukul',
icon: '/shop/icon-reminder.svg', icon: '/shop/icon-warning.svg',
name: 'Urukul', 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.', message: 'This card needs a card that provides EEM and clocking connectors (e.g. Kasli) at its left.',
}, },
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.',
}, },
},
'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: { resources: {
type: 'urukul', type: 'urukul',
icon: '/shop/icon-warning.svg', icon: '/shop/icon-warning.svg',
@ -409,13 +736,17 @@ const shop_data = {
'Internal MMCX clock from Kasli/Clocker and external SMA.', '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.' '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', size: 'small',
type: 'urukul', type: 'urukul',
hp: 4, hp: 4,
nbrSlotMin: 0, nbrSlotMin: 0,
nbrSlotMax: 0, nbrSlotMax: 0,
nbrClockMax: 0, nbrClockMax: 0,
slotOccupied: 2, slotOccupied: 1,
clockOccupied: 1, clockOccupied: 1,
rules: { rules: {
resources: { 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.', '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).' '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_file: '/docs/sinara-datasheets/5432.pdf',
datasheet_name: '5432 Zotino datasheet', datasheet_name: '5432 Zotino datasheet',
size: 'small', size: 'small',
@ -619,6 +953,10 @@ const shop_data = {
size: 'small', size: 'small',
type: 'hd68', type: 'hd68',
hp: 4, 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, nbrSlotMin: 1,
nbrSlotMax: 4, nbrSlotMax: 4,
nbrCurrentSlot: 0, nbrCurrentSlot: 0,
@ -662,6 +1000,31 @@ const shop_data = {
'Full-scale input ranges between +-10mV and +-10V.', 'Full-scale input ranges between +-10mV and +-10V.',
'Supports SU-Servo laser intensity stabilization servo in conjunction with Urukul.' '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', size: 'big',
type: 'novo', type: 'novo',
hp: 8, hp: 8,
@ -671,12 +1034,6 @@ const shop_data = {
slotOccupied: 2, slotOccupied: 2,
clockOccupied: 0, clockOccupied: 0,
rules: { 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: { resources: {
type: 'novo', type: 'novo',
icon: '/shop/icon-warning.svg', 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.', 'Existing stack supports summing over rectangular ROIs and reporting the result to ARTIQ kernels.',
'Camera signal is entirely processed in the Kasli FPGA.', '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', size: 'small',
type: 'koster', type: 'koster',
hp: 4, hp: 4,
@ -708,12 +1068,6 @@ const shop_data = {
slotOccupied: 2, slotOccupied: 2,
clockOccupied: 0, clockOccupied: 0,
rules: { 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: { resources: {
type: 'koster', type: 'koster',
icon: '/shop/icon-warning.svg', icon: '/shop/icon-warning.svg',
@ -736,6 +1090,11 @@ const shop_data = {
'Frequency up to 1GHz.', 'Frequency up to 1GHz.',
'Low jitter <100fs RMS.' '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', size: 'small',
type: 'clocker', type: 'clocker',
hp: 4, hp: 4,
@ -774,6 +1133,13 @@ const shop_data = {
'100Base-T Ethernet.', '100Base-T Ethernet.',
'Can be controlled by Kasli or work stand-alone with PoE supply.' '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', size: 'small',
type: null, type: null,
hp: 4, hp: 4,
@ -798,6 +1164,9 @@ const shop_data = {
'Lower jitter and phase noise.', 'Lower jitter and phase noise.',
'Large frequency changes take several milliseconds.', 'Large frequency changes take several milliseconds.',
], ],
options: [
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}}}
],
size: 'small', size: 'small',
type: null, type: null,
hp: 4, hp: 4,
@ -827,6 +1196,9 @@ const shop_data = {
'Additional 4 channels up to 12 GHz.', 'Additional 4 channels up to 12 GHz.',
'Each Almazny channel outputs twice the frequency of its corresponding Mirny channel.', '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', size: 'big',
type: null, type: null,
hp: 8, hp: 8,
@ -859,6 +1231,9 @@ const shop_data = {
'Up to 16W (+4A 4V) from MAX1969 drivers.', 'Up to 16W (+4A 4V) from MAX1969 drivers.',
'100Base-T Ethernet with PoE.' '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', size: 'small',
type: null, type: null,
hp: 4, hp: 4,
@ -912,6 +1287,13 @@ const shop_data = {
'2-channel Pound Drever Hall (PDH) lock generator.', '2-channel Pound Drever Hall (PDH) lock generator.',
'AD9959 DDS (500MSPS, 10-bit).' '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', size: 'big',
type: null, type: null,
hp: 8, hp: 8,
@ -974,6 +1356,7 @@ const shop_data = {
itemIds: [ itemIds: [
'clocker', 'clocker',
'urukul', 'urukul',
'urukul_4412',
'phaser', 'phaser',
'mirny', 'mirny',
'almazny', 'almazny',