Compare commits

...

38 Commits

Author SHA1 Message Date
Egor Savkin 851b9014fb Rebase and fix warning in development mode
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 12:21:02 +08:00
Egor Savkin db8897e5ad Add termination choices to stabilizer
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:42 +08:00
Egor Savkin 5a8df32e14 Add phaser baseband/upconverter variants
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:42 +08:00
Egor Savkin 324dc98c15 Add options to zotino, thermostat, sampler and pounder
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:41 +08:00
Egor Savkin 4477cbfcac Add edge counter for the options to TTL
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:38 +08:00
Egor Savkin c9786dc8cb Add HD68 cable options and fix first small cards options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:36 +08:00
Egor Savkin a06a8623c0 Leave opt-out option only to the first kasli
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:33 +08:00
Egor Savkin 41ba846ac7 Change ttl groups to banks and remove green leaf
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:30 +08:00
Egor Savkin 06498beaf1 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-08 10:06:29 +08:00
Egor Savkin f43a59fbcc Fix cards state being not updated on touchables
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:06:21 +08:00
Egor Savkin 6e7248d83b Fix not-disappearing warnings and clocker clock slots
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:05:19 +08:00
Egor Savkin 7c7d3b4e42 Small duplication removal
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:05:17 +08:00
Egor Savkin ab42fc4a31 Remove redundant notifications about connectors and AD9910
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:05:14 +08:00
Egor Savkin 5e500d397a Integrate clock/slots configuration into the calculator
Also adjust sizes

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:05:13 +08:00
Egor Savkin fe66ff3fa1 Fixed initial display of empty options in the options summary
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:05:02 +08:00
Egor Savkin 0eb24c181a Still fixing the bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:04:45 +08:00
Egor Savkin 9474c9a583 Fix scroll issue and found another bug
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:04:35 +08:00
Egor Savkin 27aab21dd5 Add groups of options and some fixes for long lists of options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:04:10 +08:00
Egor Savkin f0cc548f28 Add external data for use per every card. Apply it for TTL and Suservo
Signed-off-by: esavkin <es@m-labs.hk>
2023-11-08 10:04:08 +08:00
Egor Savkin 714e2e14f7 Fix tooltip showed out when options overlay was closed
Signed-off-by: esavkin <es@m-labs.hk>
2023-11-08 10:03:21 +08:00
Egor Savkin 6dd2eaf94c Use tips on more cards
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:03:21 +08:00
Egor Savkin 6ff3c2c6e7 Add tooltips with hints for the user
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:03:21 +08:00
Egor Savkin fb71584692 Add icons to options titles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:03:15 +08:00
Egor Savkin 23d73286fa Remove MAC from options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:02:59 +08:00
Egor Savkin 9779313524 Reposition summary popup relative to icon respective to window width, hide on scroll
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:02:52 +08:00
Egor Savkin 02bdad3436 Hide popups on clicking outside of them
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:02:36 +08:00
Egor Savkin 56f0241549 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-08 10:02:15 +08:00
Egor Savkin d54d3b79e5 Add popover for cart summary with options data
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:02:06 +08:00
Egor Savkin 6ba388428d 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-08 10:01:53 +08:00
Egor Savkin e9236dac36 Make the design more compact
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:32 +08:00
Egor Savkin e7107b02e8 Adjust styles
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:32 +08:00
Egor Savkin e13bcc7a86 Fix add of useless options to JSON
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:32 +08:00
Egor Savkin fe76d6d4e6 Add switchline compact design
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:32 +08:00
Egor Savkin 262eefc3a4 Add switch and line
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:32 +08:00
Egor Savkin 7fadbd7cdf Fix JSON shop load
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:32 +08:00
Egor Savkin 00f2449167 Make it update state
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:32 +08:00
Egor Savkin 1c6f5eb5fa Add basic demo example
Kinda works, but buggy

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-08 10:01:30 +08:00
Egor Savkin a5b217c183 Fix hover logic on touchables
* fix cards removal
* fix cards progress bar overlay
* fix cards warning overlay

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-11-07 14:59:20 +08:00
9 changed files with 1741 additions and 651 deletions

1009
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,22 +12,25 @@
"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",
"axios": "^1.6.0",
"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"
"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,12 +255,35 @@ button {
.item-card-name,
.price {
> .alert-warning {
> .alert-warning, .alert-info {
background-color: inherit;
height: inherit;
width: 20px;
padding-bottom: 3px;
}
.alert-info {
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;
}
}
thead, tbody, tfoot {
@ -382,7 +405,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,56 +455,12 @@ button {
height: 24px;
}
> .alert-warning {
> .alert-warning, .alert-info {
background-color: inherit;
height: inherit;
width: 20px;
padding-bottom: 3px;
}
.k-popup-connectors,
.k-popup-warning {
position: absolute;
background-color: $color-primary;
color: white;
font-weight: 700;
font-size: .6rem;
padding: .8rem 1rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
text-align: left;
p {
margin-bottom: 0;
}
p + p {
padding-bottom: 8px;
}
}
.k-popup-connectors {
min-width: 200px;
bottom: 50px;
}
.k-popup-warning {
background-color: #c75e5e;
min-width: 350px;
.rule.warning {
display: flex;
font-size: .75rem;
img {
margin-right: 10px;
height: 100%;
}
}
}
.k-popup-warning.inverted {
right: 0;
}
}
.overlayRemove {
@ -508,6 +487,65 @@ 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;
}
&.hd68-idc, &.stabilizer, &.clocker {
width: 85px;
left: -11px; // (card width (63) - overlay width (85)) / 2
}
}
}
.overlay-smallcard {
left: -38.5px; // (card width (63) - overlay width (140)) / 2
}
.overlay-bigcard {
left: -7px; // (card width (126) - overlay width (140)) / 2
}
.hovered {
@ -551,3 +589,42 @@ button {
}
}
.k-popup-connectors,
.k-popup-warning {
background-color: $color-primary;
color: white;
font-weight: 700;
font-size: .6rem;
padding: .8rem 1rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
text-align: left;
p {
margin-bottom: 0;
}
p + p {
padding-bottom: 8px;
}
}
.k-popup-connectors {
min-width: 200px;
}
.k-popup-warning {
background-color: #c75e5e;
min-width: 300px;
.rule.warning {
display: flex;
font-size: .75rem;
img {
margin-right: 10px;
height: 100%;
}
}
}

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

@ -6,7 +6,8 @@ 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());
@ -256,6 +257,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,
};
});
@ -532,15 +534,16 @@ class ProductCartItem extends React.PureComponent {
static get propTypes() {
return {
isMobile: PropTypes.bool,
isTouch: PropTypes.bool,
hovered: PropTypes.bool,
index: PropTypes.number.isRequired,
model: PropTypes.object.isRequired,
data: PropTypes.object,
onToggleProgress: PropTypes.func,
onToggleWarning: PropTypes.func,
ext_data: PropTypes.object,
onToggleOverlayRemove: PropTypes.func,
onClickRemoveItem: PropTypes.func,
shouldTooltipWarningClassInverted: PropTypes.bool,
onClickItem: PropTypes.func,
onCardUpdate: PropTypes.func,
};
}
@ -552,47 +555,10 @@ class ProductCartItem extends React.PureComponent {
constructor(props) {
super(props);
this.handleOnMouseEnterItem = this.handleOnMouseEnterItem.bind(this);
this.handleOnMouseLeaveItem = this.handleOnMouseLeaveItem.bind(this);
this.handleOnMouseEnterWarningItem = this.handleOnMouseEnterWarningItem.bind(this);
this.handleOnMouseLeaveWarningItem = this.handleOnMouseLeaveWarningItem.bind(this);
this.handleOnMouseEnterRemoveItem = this.handleOnMouseEnterRemoveItem.bind(this);
this.handleOnMouseLeaveRemoveItem = this.handleOnMouseLeaveRemoveItem.bind(this);
this.handleOnClickRemoveItem = this.handleOnClickRemoveItem.bind(this);
}
handleOnMouseEnterItem(index, e) {
if (this.props.onToggleProgress) {
this.props.onToggleProgress(index, true);
}
e.preventDefault();
}
handleOnMouseLeaveItem(index, e) {
if (this.props.onToggleProgress) {
this.props.onToggleProgress(index, false);
}
e.preventDefault();
}
handleOnMouseEnterWarningItem(index, isWarning, e) {
if (!isWarning) {
return;
}
if (this.props.onToggleWarning) {
this.props.onToggleWarning(index, true);
}
e.preventDefault();
}
handleOnMouseLeaveWarningItem(index, isWarning, e) {
if (!isWarning) {
return;
}
if (this.props.onToggleWarning) {
this.props.onToggleWarning(index, false);
}
e.preventDefault();
this.handleOnClickItem = this.handleOnClickItem.bind(this);
}
handleOnMouseEnterRemoveItem(index, e) {
@ -609,6 +575,13 @@ class ProductCartItem extends React.PureComponent {
e.preventDefault();
}
handleOnClickItem(index, e) {
if (this.props.onClickItem && this.props.isTouch) {
this.props.onClickItem(index);
}
e.preventDefault();
}
handleOnClickRemoveItem(index, e) {
if (this.props.onClickRemoveItem) {
this.props.onClickRemoveItem(index);
@ -621,10 +594,11 @@ class ProductCartItem extends React.PureComponent {
model,
data,
index,
shouldTooltipWarningClassInverted,
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) {
@ -633,41 +607,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 (model.showProgress && data) {
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:
@ -696,28 +665,52 @@ class ProductCartItem extends React.PureComponent {
>
{/* warning container */}
<div
className="progress-container warning"
onMouseEnter={this.handleOnMouseEnterWarningItem.bind(this, index, warning)}
onMouseLeave={this.handleOnMouseLeaveWarningItem.bind(this, index, warning)}>
{warning && (
<img className="alert-warning" src={warning ? `/images${warning.icon}` : null} />
)}
{warning && model.showWarning && (
<div className={`k-popup-warning ${shouldTooltipWarningClassInverted ? 'inverted': ''}`}>
<OverlayTrigger
placement="bottom"
trigger={warning ? ['click', 'hover', 'focus'] : []}
overlay={
({arrowProps, hasDoneInitialMeasure, show, ...props}) => (
<div className="k-popup-warning" {...props}>
<p className="rule warning">
<i>{warning.message}</i>
</p>
</div>
)}
</div>
</div>)
}
rootClose
>
<div className="progress-container warning">
{warning ?
(<img className="alert-warning" src={warning ? `/images${warning.icon}` : null}/>) :
(options && (
<OptionsDialogPopup
options={options}
data={options_data}
options_class={model.options_class}
key={"popover" + index}
id={"popover" + index}
big={model.size === "big"}
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>
</OverlayTrigger>
<h6>{model.name_number}</h6>
<div
onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
onClick={this.handleOnClickItem.bind(this, index)}
>
<img
@ -737,26 +730,30 @@ class ProductCartItem extends React.PureComponent {
</div>
{/* progression container */}
<div
className="progress-container"
onMouseEnter={this.handleOnMouseEnterItem.bind(this, index)}
onMouseLeave={this.handleOnMouseLeaveItem.bind(this, index)}>
{render_progress && (
<OverlayTrigger
placement="top"
trigger={['click', 'hover', 'focus']}
overlay={({arrowProps, hasDoneInitialMeasure, show, ...props}) => (<div className="k-popup-connectors" {...props}>{render_progress}</div>)}
rootClose
>
<div className="progress-container">
{model.nbrSlotMax > 0 && (
<div className="nbr-connectors">
<div style={{ ...nbrConnectorsStyle(data)}}></div>
{model.nbrSlotMax > 0 && (
<div className="nbr-connectors">
<div style={{ ...nbrConnectorsStyle(data)}}></div>
</div>
)}
{model.nbrClockMax > 0 && (
<div className="nbr-clocks">
<div style={{ ...nbrClocksStyle(data)}}></div>
</div>
)}
</div>
)}
</OverlayTrigger>
)}
{model.nbrClockMax > 0 && (
<div className="nbr-clocks">
<div style={{ ...nbrClocksStyle(data)}}></div>
</div>
)}
{/* progress info when mouse over */}
{render_progress}
</div>
</div>
)}
@ -820,49 +817,51 @@ class Cart extends React.PureComponent {
static get propTypes() {
return {
isMobile: PropTypes.bool,
isTouch: PropTypes.bool,
nbrSlots: PropTypes.number,
itemHovered: PropTypes.string,
data: PropTypes.object.isRequired,
onToggleProgress: PropTypes.func,
onToggleWarning: PropTypes.func,
onToggleOverlayRemove: PropTypes.func,
onClickRemoveItem: PropTypes.func,
onCardUpdate: PropTypes.func,
onClickItem: PropTypes.func,
};
}
render() {
const {
isMobile,
isTouch,
nbrSlots,
itemHovered,
data,
onToggleProgress,
onToggleWarning,
onToggleOverlayRemove,
onClickRemoveItem,
onClickItem,
onCardUpdate,
} = this.props;
const nbrOccupied = nbrOccupiedSlotsInCrate(data.items);
const shouldTooltipWarningClassInverted = nbrSlots - nbrOccupied < 5;
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];
}
return (
<ProductCartItem
isMobile={isMobile}
isTouch={isTouch}
hovered={item.id === itemHovered}
key={item.id}
id={item.id}
index={index}
data={itemData}
shouldTooltipWarningClassInverted={shouldTooltipWarningClassInverted && index > 10}
onToggleProgress={onToggleProgress}
onToggleWarning={onToggleWarning}
ext_data={ext_data}
onToggleOverlayRemove={onToggleOverlayRemove}
onClickRemoveItem={onClickRemoveItem}
onCardUpdate={onCardUpdate}
onClickItem={onClickItem}
model={item}>
</ProductCartItem>
);
@ -1261,7 +1260,7 @@ class OrderForm extends React.PureComponent {
<textarea
onChange={handleNote}
value={note}
defaultValue={note}
rows="5"
placeholder="Additional notes" />
@ -1269,8 +1268,9 @@ class OrderForm extends React.PureComponent {
<input
className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
style={{'cursor': 'pointer', 'fontWeight': '700'}}
value="Show JSON"
onClick={onClickShow} />
defaultValue="Show JSON"
onClick={onClickShow}
readOnly={true} />
<input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit" value={`${isProcessing ? 'Processing ...' : 'Request quote'}`} />
</div>
@ -1400,16 +1400,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}
@ -1431,15 +1432,17 @@ class OrderSumary extends React.PureComponent {
</button>
</div>
{warning && (
{warning ? (
<img
style={{'marginLeft': '10px'}}
className="alert-warning"
src={`/images/${warning.icon}`}
/>
) : ( (options && options_data) ?
( <OptionsSummaryPopup id={item.id + "options"} options={options} data={options_data} /> ) : null
)}
{!warning && (
{(!warning && !options) && (
<span style={{
'display': 'inline-block',
'width': '30px',
@ -1620,18 +1623,19 @@ class Shop extends React.PureComponent {
this.handleMouseLeaveItem = this.handleMouseLeaveItem.bind(this);
this.handleClickAddItem = this.handleClickAddItem.bind(this);
this.checkAlerts = this.checkAlerts.bind(this);
this.handleToggleItemProgress = this.handleToggleItemProgress.bind(this);
this.handleToggleItemWarning = this.handleToggleItemWarning.bind(this);
this.handleClickSelectItem = this.handleClickSelectItem.bind(this);
this.handleClickSubmit = this.handleClickSubmit.bind(this);
this.handleToggleOverlayRemove = this.handleToggleOverlayRemove.bind(this);
this.handleShowOverlayRemove = this.handleShowOverlayRemove.bind(this);
this.handleClickToggleMobileSideMenu = this.handleClickToggleMobileSideMenu.bind(this);
this.handleClickCloseRFQFeedback = this.handleClickCloseRFQFeedback.bind(this);
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;
}
componentDidMount() {
@ -1666,9 +1670,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) {
@ -1684,6 +1686,10 @@ class Shop extends React.PureComponent {
clearTimeout(this.timer);
}
handleCardsUpdated() {
this.checkAlerts(this.state.columns.cart.items);
}
handleCrateModeChange(mode) {
this.setState({
currentMode: mode,
@ -1691,8 +1697,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: {
@ -1700,6 +1709,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: cloned,
itemsData: cloned_data,
},
},
});
@ -1713,6 +1723,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: [],
itemsData: []
},
},
});
@ -1748,50 +1759,6 @@ class Shop extends React.PureComponent {
}, tap);
}
handleToggleItemProgress(index, show) {
const itemsCloned = Array.from(this.state.columns.cart.items);
this.setState({
...this.state,
columns: {
...this.state.columns,
cart: {
...this.state.columns.cart,
items: itemsCloned.map((item, i) => {
return {
...item,
showProgress: i === index ? show : false,
showOverlayRemove: false,
showWarning: false,
};
}),
}
},
});
}
handleToggleItemWarning(index, show) {
const itemsCloned = Array.from(this.state.columns.cart.items);
this.setState({
...this.state,
columns: {
...this.state.columns,
cart: {
...this.state.columns.cart,
items: itemsCloned.map((item, i) => {
return {
...item,
showWarning: i === index ? show : false,
showProgress: false,
showOverlayRemove: false,
};
}),
}
},
});
}
handleClickSelectItem(index) {
const itemsCloned = Array.from(this.state.columns.cart.items);
@ -1821,9 +1788,7 @@ class Shop extends React.PureComponent {
items: itemsCloned.map((item, id) => {
return {
...item,
showOverlayRemove: id === index ? show : false,
showProgress: false,
showWarning: false,
showOverlayRemove: id === index ? show : false
};
}),
}
@ -1831,16 +1796,30 @@ class Shop extends React.PureComponent {
});
}
handleShowOverlayRemove(index) {
if (this.timer_remove)
clearTimeout(this.timer_remove);
this.handleToggleOverlayRemove(index, true);
this.timer_remove = setTimeout(() => {
this.handleToggleOverlayRemove(index, false);
}, 2000);
}
handleClickShowOrder() {
const crate = {
items: [],
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,
});
}
@ -1848,7 +1827,7 @@ class Shop extends React.PureComponent {
isProcessing: false,
shouldShowRFQFeedback: true,
RFQBodyType: 'show',
RFQBodyOrder: JSON.stringify(crate),
RFQBodyOrder: JSON.stringify(crate, null, 2),
});
}
@ -1864,9 +1843,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({
@ -1887,14 +1866,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: {
@ -1902,6 +1882,7 @@ class Shop extends React.PureComponent {
cart: {
...this.state.columns.cart,
items: new_items,
itemsData: new_items_data,
},
},
currentMode: customconf.type,
@ -1915,10 +1896,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,
});
}
@ -1972,6 +1956,10 @@ class Shop extends React.PureComponent {
this.state.columns[source.droppableId].items,
source.index,
),
itemsData: remove(
this.state.columns[source.droppableId].itemsData,
source.index,
)
},
},
});
@ -2017,6 +2005,11 @@ class Shop extends React.PureComponent {
source.index,
destination.index,
),
itemsData: reorder(
this.state.columns[destination.droppableId].itemsData,
source.index,
destination.index,
),
},
},
});
@ -2040,7 +2033,7 @@ class Shop extends React.PureComponent {
});
}
checkAlerts(prevItems, newItems) {
checkAlerts(newItems) {
console.log('--- START CHECKING CRATE WARNING ---');
const {
@ -2053,6 +2046,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);
@ -2102,14 +2103,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) {
@ -2185,11 +2203,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;
@ -2207,15 +2225,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};
@ -2326,7 +2335,6 @@ class Shop extends React.PureComponent {
}
}
// update state with rules
this.setState({
...this.state,
@ -2364,6 +2372,7 @@ class Shop extends React.PureComponent {
} = this.state;
const isMobile = window.deviceIsMobile();
const isTouch = window.isTouchEnabled();
return (
<DragDropContext onDragEnd={this.handleOnDragEnd}>
@ -2397,7 +2406,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. 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}
@ -2411,11 +2420,12 @@ class Shop extends React.PureComponent {
nbrSlots={crateModeSlots[currentMode]}
data={columns['cart']}
isMobile={isMobile}
isTouch={isTouch}
itemHovered={currentItemHovered}
onToggleProgress={this.handleToggleItemProgress}
onToggleWarning={this.handleToggleItemWarning}
onToggleOverlayRemove={this.handleToggleOverlayRemove}
onClickRemoveItem={this.handleDeleteItem}>
onClickRemoveItem={this.handleDeleteItem}
onClickItem={this.handleShowOverlayRemove}
onCardUpdate={this.handleCardsUpdated}>
</Cart>
}
rules={Object.values(rules).filter(rule => rule)}>

View File

@ -0,0 +1,452 @@
'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, 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 ") + (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"}
style={{'marginLeft': '10px'}}
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_previous_carrier: data.filter((value, item_index) => index > item_index && value.name === "Carrier").length > 0,
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.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,32 @@ const shop_data = {
nbrCurrentClock: 0,
slotOccupied: 1,
clockOccupied: 0,
options: [
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["standalone", "master", "satellite"]}},
{
"if": [
{
"in": [
{"var": "variant"}, [
"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."}},
{"if": [
{"!": {"var": "ext_data.has_previous_carrier"}},
{type: "Switch", args: {title: "Opt out from promotional USB-stick", outvar: "usb_opt_out", tip: "Do not ship a promotional USB-stick"}}
]}
],
[
{type: "Switch", args: {title: "Optic fiber", outvar: "optics", tip: "Use optic fiber instead of direct attach copper cable"}}
]
]
}
],
rules: {
maxSlot: {
type: 'kasli-max-slot',
@ -125,6 +151,32 @@ const shop_data = {
nbrCurrentClock: 0,
slotOccupied: 1,
clockOccupied: 0,
options: [
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["standalone", "master", "satellite"]}},
{
"if": [
{
"in": [
{"var": "variant"}, [
"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."}},
{"if": [
{"!": {"var": "ext_data.has_previous_carrier"}},
{type: "Switch", args: {title: "Opt out from promotional USB-stick", outvar: "usb_opt_out", tip: "Do not ship a promotional USB-stick"}}
]}
],
[
{type: "Switch", args: {title: "Optic fiber", outvar: "optics", tip: "Use optic fiber instead of direct attach copper cable"}}
]
]
}
],
rules: {
maxSlot: {
type: 'kaslisoc-max-slot',
@ -240,6 +292,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 +357,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 +434,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,
@ -364,6 +570,37 @@ const shop_data = {
],
datasheet_file: '/docs/sinara-datasheets/4410-4412.pdf',
datasheet_name: '4410/4412 Urukul datasheet',
options: [
{type: "Switch", args: {title: "AD9912", outvar: "ad9912", tip: "AD9912 chip instead of AD9910"}},
{type: "Switch", args: {title: "Use 1 EEM", outvar: "mono_eem", tip: "Use one EEM port setup. RF switch and synchronization will be unavailable."}},
{
"if": [
{"or": [{"var": "ad9912"}, {"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 +610,6 @@ const shop_data = {
slotOccupied: 2,
clockOccupied: 1,
rules: {
connectors: {
type: 'urukul',
icon: '/shop/icon-reminder.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.',
},
resources: {
type: 'urukul',
icon: '/shop/icon-warning.svg',
@ -409,13 +634,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 +671,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 +851,10 @@ const shop_data = {
size: 'small',
type: 'hd68',
hp: 4,
options: [
{type: "Radio", args: {title: "Cable length", outvar: "hd68_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 +898,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 +932,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 +954,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 +966,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 +988,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 +1031,12 @@ const shop_data = {
'100Base-T Ethernet.',
'Can be controlled by Kasli or work stand-alone with PoE supply.'
],
options: [
{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 +1061,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 +1093,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 +1128,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 +1184,12 @@ const shop_data = {
'2-channel Pound Drever Hall (PDH) lock generator.',
'AD9959 DDS (500MSPS, 10-bit).'
],
options: [
{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,

View File

@ -31,7 +31,15 @@
return window.innerWidth < 768;
}
function isTouchEnabled() {
return ('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0);
}
window.deviceIsMobile = deviceIsMobile;
window.isTouchEnabled = isTouchEnabled;
})();
</script>