Compare commits

...

42 Commits

Author SHA1 Message Date
Egor Savkin e7cb6619e3 Update variants -> drtio role and add a tip
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-28 14:41:10 +08:00
Egor Savkin b815c4dd4e Move first and last cards' options popup so that it will be accessible
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-28 12:16:16 +08:00
Egor Savkin 38541f8928 Combine warning and options iconed buttons in one line
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-27 17:12:22 +08:00
Egor Savkin b7087711aa Replace axios with built-in Fetch API
Decreases size of the bundle from 375 to 347 KB

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-21 16:48:21 +08:00
Egor Savkin f11ebc1152 Update options
* Separate Urukul 4412
* Add options for LVDS TTL
* Remove usb flash option
* Add cable lengths for fiber and copper options
* Add IP option for Stabilizer and Pounder
* Added note that most options can be changed after shipment

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-21 15:30:45 +08:00
Egor Savkin a4ff9f7057 Rebase and fix warning in development mode
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 281f0a26f0 Add termination choices to stabilizer
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 159d4b631a Add phaser baseband/upconverter variants
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin e3a50f1b9a Add options to zotino, thermostat, sampler and pounder
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 72c1b1747c Add edge counter for the options to TTL
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 291332eaf4 Add HD68 cable options and fix first small cards options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 2480b9ad54 Leave opt-out option only to the first kasli
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin cd780b83e4 Change ttl groups to banks and remove green leaf
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin bd15b8d19d Slightly promote new functionality and consider mono_eem modes on sampler/urukul for suservo
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin acdaeda699 Fix cards state being not updated on touchables
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 1a0b613044 Fix not-disappearing warnings and clocker clock slots
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 58bc01cf06 Small duplication removal
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 5559a04403 Remove redundant notifications about connectors and AD9910
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin ce31357802 Integrate clock/slots configuration into the calculator
Also adjust sizes

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 79e4253e0f Fixed initial display of empty options in the options summary
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 840b0223a7 Still fixing the bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin dfbf9cbfe7 Fix scroll issue and found another bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin c55687e05a Add groups of options and some fixes for long lists of options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin f67aa7fc51 Add external data for use per every card. Apply it for TTL and Suservo
Signed-off-by: esavkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin b245655c41 Fix tooltip showed out when options overlay was closed
Signed-off-by: esavkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin a3075a5691 Use tips on more cards
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 8db77cf8c7 Add tooltips with hints for the user
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 788653abaa Add icons to options titles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 0df69499b8 Remove MAC from options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 8bdfedc4b8 Reposition summary popup relative to icon respective to window width, hide on scroll
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 0fe665c0ba Hide popups on clicking outside of them
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 894c1d2e14 Update bundle file
Add custom options to more cards

Minor design adjustments: add margin-left to icon and optimize the icon

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 23c7508358 Add popover for cart summary with options data
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 288a0cc74c Make overlay appear on button click
And fix options absense for non-carrier cards

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 0be655f83a Make the design more compact
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin d72a295aa5 Adjust styles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 4d5dd505ae Fix add of useless options to JSON
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin ac87fede4b Add switchline compact design
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin ff8a6d54bc Add switch and line
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin f93cf142f0 Fix JSON shop load
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin 1a7d44c121 Make it update state
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +08:00
Egor Savkin d543e54326 Add basic demo example
Kinda works, but buggy

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-20 15:40:44 +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"
},
"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": [

View File

@ -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 {

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';
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',
}}>&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>
{warning && (
<img
style={{'marginLeft': '10px'}}
className="alert-warning"
src={`/images/${warning.icon}`}
/>
)}
{!warning && (
<span style={{
'display': 'inline-block',
'width': '30px',
}}>&nbsp;</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)}>

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,
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',