1
0
Fork 0

Compare commits

..

1 Commits

Author SHA1 Message Date
Sebastien Bourdeauducq a5a8589b28 add link to OffsetStabilizer 2024-11-16 11:55:30 +08:00
23 changed files with 225 additions and 646 deletions

View File

@ -227,6 +227,12 @@ We welcome inquiries from research groups of all sizes.<br>[See what has been fu
<a href="https://github.com/vuthalab/artiq" target="_blank" rel="noopener noreferrer">Repository</a> <a href="https://github.com/vuthalab/artiq" target="_blank" rel="noopener noreferrer">Repository</a>
{% end %} {% end %}
{% layout_card(title="OffsetStabilizer", sameheight=120) %}
<small>Stabilizer firmware for laser frequency offset stabilization</small>
<a href="https://github.com/PhBrb/OffsetStabilizer" target="_blank" rel="noopener noreferrer">Repository</a>
{% end %}
</div> </div>
{% layout_div(css="col-12 text-center") %} {% layout_div(css="col-12 text-center") %}

View File

@ -117,20 +117,10 @@ button {
margin-bottom: 20px; margin-bottom: 20px;
} }
button {
img {
cursor: pointer;
}
}
img { img {
height: 400px; height: 400px;
align-self: center; align-self: center;
border: 0; border: 0;
cursor: grab;
&.default-icon {
height: 144px;
}
} }
h3 { h3 {
@ -494,45 +484,6 @@ button {
background-color: #ebebeb; background-color: #ebebeb;
padding: 5px 5px 12px; padding: 5px 5px 12px;
position: relative; position: relative;
&.horizontal {
flex-direction: column;
min-height: 10px;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
> div {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-content: center;
align-items: center;
width: 100%;
max-width: 100%;
padding: 5px 3px;
}
.progress-container {
max-width: 40px;
padding: 0 10px;
justify-self: start;
}
.product-name {
justify-self: start;
padding: 0 10px;
max-width: 100%;
width: 100%;
font-size: 1rem;
}
.removeHorizontal {
justify-self: end;
padding: 0 10px;
img {
width: 20px;
height: 20px;
}
}
}
> div { > div {
display: flex; display: flex;
@ -620,6 +571,67 @@ button {
} }
} }
.overlayVariant {
top: 24px;
width: 140px;
min-height: 40px;
max-height: 320px;
overflow-y: scroll;
position: absolute;
display: flex;
align-items: start;
text-align: left;
background-color: white;
color: black;
flex-direction: column;
cursor: pointer;
padding: 0.2rem 0;
p {
font-size: .65rem;
margin: 0;
}
div {
margin: 0.1rem 0.2rem;
font-size: 0.75rem;
input {
padding: 0;
font-size: 0.75rem;
line-height: 1.1;
}
label {
margin-bottom: 0.1rem;
}
.options-icon {
display: inline;
height: .875rem;
margin-right: 0.2rem;
margin-left: 0.2rem;
}
}
.form-check {
min-height: 1rem;
}
}
}
.overlay-smallcard {
left: -38.5px; // (card width (63) - overlay width (140)) / 2
&.overlay-first {
left: 0;
}
&.overlay-last {
left: -67px;
}
}
.overlay-bigcard {
left: -7px; // (card width (126) - overlay width (140)) / 2
} }
.hovered { .hovered {
@ -766,49 +778,3 @@ button {
} }
} }
.overlayVariant {
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;
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1 +0,0 @@
<svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.2797 0 0 1.2797 -4.4788 -4.4788)" stroke-width="1.0001"><g fill-opacity="0" stroke="#fff" stroke-linecap="round" stroke-width="1.0001"><g stroke-linejoin="round" stroke-opacity=".8" stroke-width="1.0001"><path d="m4 28.965v2"/><path d="m4 40.948v2"/><path d="m4 16.983v2"/></g><g stroke-linejoin="round" stroke-opacity=".8" stroke-width="1.0001"><path d="m28.965 4h2"/><path d="m16.983 4h2"/><path d="m40.948 4.0001h2"/></g><g transform="translate(0 3.3081)" stroke-linejoin="round" stroke-opacity=".8" stroke-width="1.0001"><path d="m28.965 52.623h2"/><path d="m40.948 52.623h2"/><path d="m16.983 52.623h2"/></g><g transform="translate(3.3082)" stroke-linejoin="round" stroke-opacity=".8" stroke-width="1.0001"><path d="m52.623 28.965v2"/><path d="m52.623 16.983v2.0001"/><path d="m52.623 40.948v2.0001"/></g><g stroke-linejoin="round"><path d="m7 4h-3v3" stroke-opacity=".8" stroke-width="1.0001"/><path d="m52.931 4h3v3" stroke-opacity=".8" stroke-width="1.0001"/><path d="m7 55.931h-3v-3" stroke-opacity=".8" stroke-width="1.0001"/></g><path d="m28.311 27.311v2.0001m-1-1h2" stroke-opacity=".8" stroke-width="1.0001"/><path d="m52.931 55.931h3v-3" stroke-linejoin="round" stroke-opacity=".8" stroke-width="1.0001"/></g></g><g transform="matrix(.64211 -.68634 .59172 .63248 35.302 51.828)"><path transform="translate(1.6405e-6)" d="m7 13.5v-7c0-0.82842 0.67157-1.5 1.5-1.5s1.5 0.55521 1.5 1.3836v0.11637 2m0-1e-5v-2c0-2.2857 3-2.2857 3 0v0.52708 1.4729m0 3e-5v-1.4729c0-2.2857 3-2.2857 3 0v0.9015 0.57143m-3 0v-1.4729m0-0.52707v0.52707m3 1.4729v-0.57143-0.9015c0-2.2857-3-2.2857-3 0m3 1.4728v-0.57143c0-2.2857 3-2.2857 3 0m-12 2.5715-2.0041 2.6721c-0.57746 0.77-0.52464 1.842 0.12569 2.5515l3.7839 4.1279c0.37882 0.4132 0.91276 0.6485 1.4734 0.6485h4.6211c2.4 0 4-1.5 4-4v-8.5714" fill-opacity="0" stroke="#fff" stroke-opacity=".9"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import React, {useRef, useEffect, useState} from 'react' import React from 'react'
import {Droppable} from "@hello-pangea/dnd"; import {Droppable} from "@hello-pangea/dnd";
import {cartStyle, compareArraysWithIds} from "./utils"; import {cartStyle, compareArraysWithIds} from "./utils";
import {ProductCartItem} from "./ProductCartItem"; import {ProductCartItem} from "./ProductCartItem";
@ -9,17 +9,17 @@ import {useShopStore} from "./shop_store";
// #!render_count // #!render_count
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that displays a list of <ProductCartItem>
*/
export function Cart({crate_index}) { export function Cart({crate_index}) {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
const containerRef = useRef(null);
const [visiblePlaceholders, setVisiblePlaceholders] = useState(0);
const crate = useShopStore((state) => state.crates[crate_index], (a, b) => { const crate = useShopStore((state) => state.crates[crate_index], (a, b) => {
return compareArraysWithIds(a.items, b.items) && a.occupiedHP === b.occupiedHP && a.crate_mode === b.crate_mode return compareArraysWithIds(a.items, b.items) && a.occupiedHP === b.occupiedHP && a.crate_mode === b.crate_mode
}); });
const crateParams = useShopStore((state) => state.crateParams); const crateParams = useShopStore((state) => state.crateParams);
const isCrate = useShopStore((state) => state.modes_order.includes(state.crates[crate_index].crate_mode));
// #!render_count // #!render_count
console.log("Cart renders: ", renderCount) console.log("Cart renders: ", renderCount)
@ -27,43 +27,23 @@ export function Cart({crate_index}) {
const nbrOccupied = hp_to_slots(crate.occupiedHP); const nbrOccupied = hp_to_slots(crate.occupiedHP);
const nbrSlots = hp_to_slots(crateParams(crate.crate_mode).hp); const nbrSlots = hp_to_slots(crateParams(crate.crate_mode).hp);
useEffect(() => {
const updateVisiblePlaceholders = () => {
if (!containerRef.current) return;
setVisiblePlaceholders(isCrate ?
(nbrSlots - nbrOccupied) :
// 10 is padding and 77 is the actual size with all the margins
Math.max(1, Math.floor((containerRef.current.offsetWidth - 10) / 77) - nbrOccupied));
};
updateVisiblePlaceholders();
const resizeObserver = new ResizeObserver(updateVisiblePlaceholders);
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [nbrOccupied, nbrSlots]);
const products = crate.items.map((item, index) => { const products = crate.items.map((item, index) => {
return ( return (
<ProductCartItem <ProductCartItem
card_index={index} card_index={index}
crate_index={crate_index} crate_index={crate_index}
first={index === 0}
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
key={item.id}/> key={item.id}/>
); );
}); });
return ( return (
<Droppable droppableId={crate.id} direction="horizontal"> <Droppable droppableId={crate.id} direction="horizontal">
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
ref={(element) => { ref={provided.innerRef}
containerRef.current = element;
provided.innerRef(element);
}}
{...provided.droppableProps} {...provided.droppableProps}
style={cartStyle( style={cartStyle(
provided.droppableProps.style, provided.droppableProps.style,
@ -80,10 +60,11 @@ export function Cart({crate_index}) {
)} )}
<FakePlaceholder <FakePlaceholder
nToDraw={visiblePlaceholders} nToDraw={nbrSlots - nbrOccupied}
isDraggingOver={snapshot.isDraggingOver}/> isDraggingOver={snapshot.isDraggingOver}/>
</div> </div>
)} )}
</Droppable> </Droppable>
); );
} }

View File

@ -1,59 +0,0 @@
import React from 'react'
import {Droppable} from "@hello-pangea/dnd";
import {cartStyle, compareArraysWithIds} from "./utils";
import {ProductCartItemHorizontal} from "./ProductCartItemHorizontal";
import {HORIZONTAL_CART_MARKER, useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that displays a list of <ProductCartItem>
*/
export function CartHorizontal({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const crate = useShopStore((state) => state.crates[crate_index], (a, b) => {
return compareArraysWithIds(a.h_items, b.h_items)
});
// #!render_count
console.log("CartHorizontal renders: ", renderCount)
const products = crate.h_items.map((item, index) => {
return (
<ProductCartItemHorizontal
card_index={index}
crate_index={crate_index}
key={item.id}/>
);
});
return (
<Droppable droppableId={crate.id+HORIZONTAL_CART_MARKER} direction="vertical">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
style={cartStyle(
provided.droppableProps.style,
snapshot,
)}
className="items-cart-list horizontal">
{products}
{provided.placeholder && (
<div style={{display: 'none'}}>
{provided.placeholder}
</div>
)}
</div>
)}
</Droppable>
);
}

View File

@ -16,7 +16,7 @@ export function Catalog() {
const renderCount = useRenderCount(); const renderCount = useRenderCount();
const data = useShopStore((state) => state.groups); const data = useShopStore((state) => state.groups);
const _items = useShopStore((state) => state.cards); const items = useShopStore((state) => state.cards);
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu); const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
const isMobile = useShopStore((state) => state.isMobile); const isMobile = useShopStore((state) => state.isMobile);

View File

@ -4,7 +4,6 @@ import {CrateMode} from "./CrateMode";
import {CrateWarnings} from "./CrateWarnings"; import {CrateWarnings} from "./CrateWarnings";
import {useShopStore} from "./shop_store"; import {useShopStore} from "./shop_store";
import {CrateOptions} from "./CrateOptions"; import {CrateOptions} from "./CrateOptions";
import {CartHorizontal} from "./CartHorizontal";
// #!render_count // #!render_count
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
@ -29,7 +28,7 @@ export function Crate({crate_index}) {
return ( return (
<div className="crate"> <div className="crate">
{ {
modes_order.includes(crate.crate_mode) && ( modes_order.includes(crate.crate_mode) ? (
<div className="crate-bar d-inline-flex justify-content-between"> <div className="crate-bar d-inline-flex justify-content-between">
<CrateMode crate_index={crate_index}/> <CrateMode crate_index={crate_index}/>
@ -37,17 +36,13 @@ export function Crate({crate_index}) {
Delete crate <img src="/images/shop/icon-remove.svg" alt="remove"/> Delete crate <img src="/images/shop/icon-remove.svg" alt="remove"/>
</div> </div>
</div> </div>
) ) : <></>
} }
<div className="crate-products"> <div className="crate-products">
<Cart crate_index={crate_index}/> <Cart crate_index={crate_index}/>
{ !modes_order.includes(crate.crate_mode) &&
<CartHorizontal crate_index={crate_index}/>
}
<CrateWarnings crate_index={crate_index} /> <CrateWarnings crate_index={crate_index} />
<CrateOptions crate_index={crate_index}/> <CrateOptions crate_index={crate_index}/>

View File

@ -12,7 +12,7 @@ export function FakePlaceholder({isDraggingOver, nToDraw}) {
<div key={i} style={{ <div key={i} style={{
display: isDraggingOver ? 'none' : 'block', display: isDraggingOver ? 'none' : 'block',
border: '1px dashed #ccc', border: '1px dashed #ccc',
width: '69px', width: '45px',
marginBottom: '5px', marginBottom: '5px',
}}></div> }}></div>
); );

View File

@ -1,23 +1,21 @@
import {DialogPopup} from "./options/DialogPopup"; import {DialogPopup} from "./options/DialogPopup";
import React from "react"; import React from "react";
import {useShopStore, whichItems} from "./shop_store"; import {useShopStore} from "./shop_store";
import {SummaryPopup} from "./options/SummaryPopup"; import {SummaryPopup} from "./options/SummaryPopup";
export function OptionsDialogWrapper({crate_index, card_index, horizontal}) { export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
const whichH = whichItems(horizontal);
const crate_id = useShopStore((state) => state.crates[crate_index].id); const crate_id = useShopStore((state) => state.crates[crate_index].id);
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options); const options = useShopStore((state) => state.crates[crate_index].items[card_index].options);
const options = useShopStore((state) => state.crates[crate_index][whichH][card_index][use_options]); const options_data = useShopStore((state) => state.crates[crate_index].items[card_index].options_data);
const options_data = useShopStore((state) => state.crates[crate_index][whichH][card_index].options_data); const card_size = useShopStore((state) => state.crates[crate_index].items[card_index].size);
const card_id = useShopStore((state) => state.crates[crate_index][whichH][card_index].id); const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id);
const options_class = useShopStore((state) => state.crates[crate_index][whichH][card_index].options_class); const options_class = useShopStore((state) => state.crates[crate_index].items[card_index].options_class);
const sideMenuIsOpen = useShopStore((state) => state.sideMenuIsOpen); const sideMenuIsOpen = useShopStore((state) => state.sideMenuIsOpen);
const _notificationTimer = useShopStore((state) => state.notificationTimer); const _notificationTimer = useShopStore((state) => state.notificationTimer);
const hideNotification = useShopStore((state) => state.hideNotification); const hideNotification = useShopStore((state) => state.hideNotification);
const displayNotification = useShopStore((state) => const displayNotification = useShopStore((state) =>
!!state.notificationHorizontal === !!horizontal &&
state.notificationCrateId === crate_id && state.notificationCrateId === crate_id &&
(state.notificationCardIndex === card_index || (state.crates[crate_index][whichH].length + (state.notificationCardIndex || -1)) === card_index)); (state.notificationCardIndex === card_index || (state.crates[crate_index].items.length + (state.notificationCardIndex || -1)) === card_index));
const onOptionsUpdate = useShopStore((state) => state.updateOptions); const onOptionsUpdate = useShopStore((state) => state.updateOptions);
@ -28,10 +26,12 @@ export function OptionsDialogWrapper({crate_index, card_index, horizontal}) {
options_class={options_class} options_class={options_class}
key={"popover" + crate_id +card_id} key={"popover" + crate_id +card_id}
id={"popover"+ crate_id + card_id} id={"popover"+ crate_id + card_id}
big={card_size === "big"}
first={first}
last={last}
sideMenuIsOpen={sideMenuIsOpen} sideMenuIsOpen={sideMenuIsOpen}
onHideNotification={hideNotification} onHideNotification={hideNotification}
displayNotification={displayNotification} displayNotification={displayNotification}
horizontal={horizontal}
target={{ target={{
construct: ((outvar, value) => { construct: ((outvar, value) => {
// #!options_log // #!options_log
@ -44,19 +44,17 @@ export function OptionsDialogWrapper({crate_index, card_index, horizontal}) {
console.log("update", outvar, value, options_data); console.log("update", outvar, value, options_data);
if (outvar in options_data) options_data[outvar] = value; if (outvar in options_data) options_data[outvar] = value;
onOptionsUpdate(crate_id, card_index, {[outvar]: value}, horizontal); onOptionsUpdate(crate_id, card_index, {[outvar]: value});
}) })
}} }}
/> />
) )
} }
export function OptionsSummaryWrapper({crate_index, card_index, horizontal}) { export function OptionsSummaryWrapper({crate_index, card_index}) {
const whichH = whichItems(horizontal); const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id);
const card_id = useShopStore((state) => state.crates[crate_index][whichH][card_index].id); const options = useShopStore((state) => state.crates[crate_index].items[card_index].options);
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options); const options_data = useShopStore((state) => state.crates[crate_index].items[card_index].options_data);
const options = useShopStore((state) => state.crates[crate_index][whichH][card_index][use_options]);
const options_data = useShopStore((state) => state.crates[crate_index][whichH][card_index].options_data);
return ( return (
<SummaryPopup id={card_id + "options"} options={options} <SummaryPopup id={card_id + "options"} options={options}

View File

@ -8,6 +8,7 @@ import {RFQFeedback} from "./RFQFeedback";
// #!render_count // #!render_count
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
import {OrderOptions} from "./OrderOptions";
/** /**
* Component that renders all things for order. * Component that renders all things for order.

View File

@ -13,7 +13,7 @@ import {useRenderCount} from "@uidotdev/usehooks";
* Component that renders a product. * Component that renders a product.
* Used in the crate * Used in the crate
*/ */
export function ProductCartItem({card_index, crate_index}) { export function ProductCartItem({card_index, crate_index, first, last}) {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
@ -24,12 +24,8 @@ export function ProductCartItem({card_index, crate_index}) {
const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness); const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness);
const card_counted_resources = useShopStore(state => state.crates[crate_index].items[card_index].counted_resources, compareObjectsEmptiness); const card_counted_resources = useShopStore(state => state.crates[crate_index].items[card_index].counted_resources, compareObjectsEmptiness);
const highlighted = useShopStore((state) => const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card);
!state.highlighted.horizontal && const options_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
state.crates[crate_index].id === state.highlighted.crate &&
card_index === state.highlighted.card);
const warnings_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
const crate_id = useShopStore((state) => state.crates[crate_index].id); const crate_id = useShopStore((state) => state.crates[crate_index].id);
const setHighlight = useShopStore((state) => state.highlightCard); const setHighlight = useShopStore((state) => state.highlightCard);
const removeHighlight = useShopStore((state) => state.highlightReset); const removeHighlight = useShopStore((state) => state.highlightReset);
@ -39,9 +35,9 @@ export function ProductCartItem({card_index, crate_index}) {
console.log("ProductCartItem renders: ", renderCount) console.log("ProductCartItem renders: ", renderCount)
const options = use_options && card && card[use_options] && card[use_options].length > 0; const options = !options_disabled && card && card.options && card.options.length > 0;
const warnings = !warnings_disabled && card_show_warnings && card_show_warnings.length > 0; const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0;
const resources = !warnings_disabled && card_counted_resources && card_counted_resources.length > 0; const resources = !options_disabled && card_counted_resources && card_counted_resources.length > 0;
return ( return (
<Draggable draggableId={card.id} index={card_index}> <Draggable draggableId={card.id} index={card_index}>
@ -61,7 +57,7 @@ export function ProductCartItem({card_index, crate_index}) {
true true
) )
}} }}
onMouseEnter={() => setHighlight(crate_id, card_index, false)} onMouseEnter={() => setHighlight(crate_id, card_index)}
onMouseLeave={removeHighlight} onMouseLeave={removeHighlight}
> >
@ -76,6 +72,8 @@ export function ProductCartItem({card_index, crate_index}) {
<OptionsDialogWrapper <OptionsDialogWrapper
crate_index={crate_index} crate_index={crate_index}
card_index={card_index} card_index={card_index}
first={first}
last={last}
/> />
)} )}
</div> </div>

View File

@ -1,96 +0,0 @@
import React from 'react'
import {Draggable} from "@hello-pangea/dnd";
import {productStyle} from "./utils";
import {useShopStore} from "./shop_store";
import {OptionsDialogWrapper} from "./OptionsWrapper";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that renders a product.
* Used in the crate
*/
export function ProductCartItemHorizontal({card_index, crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const card = useShopStore((state) => state.crates[crate_index].h_items[card_index],
(a, b) => a.id === b.id);
const highlighted = useShopStore((state) =>
!!state.highlighted.horizontal &&
state.crates[crate_index].id === state.highlighted.crate &&
card_index === state.highlighted.card
);
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const setHighlight = useShopStore((state) => state.highlightCard);
const removeHighlight = useShopStore((state) => state.highlightReset);
const onCardRemove = useShopStore((state) => state.deleteCard);
// #!render_count
console.log("ProductCartItem renders: ", renderCount)
const options = use_options && card && card[use_options] && card[use_options].length > 0;
return (
<Draggable draggableId={card.id} index={card_index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...productStyle(
provided.draggableProps.style,
snapshot,
true,
!!highlighted,
false,
true
)
}}
onMouseEnter={() => setHighlight(crate_id, card_index, true)}
onMouseLeave={removeHighlight}
>
{/* warning container */}
<div className="progress-container warning d-flex justify-content-evenly">
{options && (
<OptionsDialogWrapper
crate_index={crate_index}
card_index={card_index}
horizontal={true}
/>
)}
</div>
<div
className="product-name"
onMouseEnter={() => setHighlight(crate_id, card_index, true)}
onClick={() => setHighlight(crate_id, card_index, true)}
>
{`${card.name_number} ${card.name} ${card.name_codename}`}
</div>
{/* remove container */}
<div
style={{'display': 'flex'}}
className="removeHorizontal"
onClick={() => onCardRemove(crate_id, card_index, true)}>
<img src="/images/shop/icon-remove.svg" alt="rm"/>
</div>
</div>
)}
</Draggable>
);
}

View File

@ -7,8 +7,6 @@ import {useShopStore} from "./shop_store";
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
const DNDIcon = "/images/shop/icon-drag-and-drop.svg";
function DatasheetLink({datasheet_file, datasheet_name}) { function DatasheetLink({datasheet_file, datasheet_name}) {
return datasheet_file && datasheet_name && (<div className="ds"> return datasheet_file && datasheet_name && (<div className="ds">
<span className='doc-icon'></span> <span className='doc-icon'></span>
@ -78,12 +76,11 @@ export function ProductItem({card_index}) {
snapshot, snapshot,
true, // hack: remove weird animation after a drop true, // hack: remove weird animation after a drop
)} )}
className={card.image ? "" : "default-icon"} src={card.image}/>
src={card.image || DNDIcon}/>
{/* Allows to simulate a clone */} {/* Allows to simulate a clone */}
{snapshot.isDragging && ( {snapshot.isDragging && (
<img className={"simclone " + (card.image ? "" : "default-icon")} src={card.image || DNDIcon}/> <img className="simclone" src={card.image}/>
)} )}
</React.Fragment> </React.Fragment>
)} )}

View File

@ -15,7 +15,6 @@ export function SummaryCrate({crate_index}) {
const crate_id = useShopStore((state) => state.crates[crate_index].id); const crate_id = useShopStore((state) => state.crates[crate_index].id);
const crate_len = useShopStore((state) => state.crates[crate_index].items.length); const crate_len = useShopStore((state) => state.crates[crate_index].items.length);
const crate_h_len = useShopStore((state) => state.crates[crate_index].h_items.length);
// #!render_count // #!render_count
console.log("SummaryCrate renders: ", renderCount) console.log("SummaryCrate renders: ", renderCount)
@ -29,14 +28,6 @@ export function SummaryCrate({crate_index}) {
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} /> <SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
)} )}
{range(0, crate_h_len).map((index, _i) =>
<SummaryCrateCard
crate_index={crate_index}
card_index={index}
horizontal={true}
key={"summary_crate_h_" + crate_id + "_" +index} />
)}
<SummaryCratePricedOptions crate_index={crate_index}/> <SummaryCratePricedOptions crate_index={crate_index}/>
</tbody> </tbody>
) )

View File

@ -1,56 +1,45 @@
import {compareObjectsEmptiness, formatMoney} from "./utils"; import {compareObjectsEmptiness, formatMoney} from "./utils";
import {WarningIndicator} from "./CardWarnings"; import {WarningIndicator} from "./CardWarnings";
import React from "react"; import React from "react";
import {useShopStore, whichItems} from "./shop_store"; import {useShopStore} from "./shop_store";
import {OptionsSummaryWrapper} from "./OptionsWrapper"; import {OptionsSummaryWrapper} from "./OptionsWrapper";
// #!render_count // #!render_count
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrateCard({crate_index, card_index, horizontal}) { export function SummaryCrateCard({crate_index, card_index}) {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
const whichH = whichItems(horizontal);
const currency = useShopStore((state) => state.currency); const currency = useShopStore((state) => state.currency);
const deleteCard = useShopStore((state) => state.deleteCard); const deleteCard = useShopStore((state) => state.deleteCard);
const setHighlight = useShopStore((state) => state.highlightCard); const setHighlight = useShopStore((state) => state.highlightCard);
const resetHighlight = useShopStore((state) => state.highlightReset); const resetHighlight = useShopStore((state) => state.highlightReset);
const highlighted = useShopStore((state) => const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card);
state.highlighted.horizontal === horizontal &&
state.crates[crate_index].id === state.highlighted.crate &&
card_index === state.highlighted.card);
const crate_id = useShopStore((state) => state.crates[crate_index].id); const crate_id = useShopStore((state) => state.crates[crate_index].id);
const card = useShopStore((state) => const card = useShopStore((state) => state.crates[crate_index].items[card_index],
state.crates[crate_index][whichH][card_index],
(a, b) => a.id === b.id); (a, b) => a.id === b.id);
// additional hooks for updating warning and options const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness);
const card_show_warnings = useShopStore(state => const card_options_data = useShopStore(state => state.crates[crate_index].items[card_index].options_data, compareObjectsEmptiness);
state.crates[crate_index][whichH][card_index].show_warnings, const options_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
compareObjectsEmptiness);
const card_options_data = useShopStore(state =>
state.crates[crate_index][whichH][card_index].options_data,
compareObjectsEmptiness);
const warnings_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
// #!render_count // #!render_count
console.log("SummaryCrateCard renders: ", renderCount) console.log("SummaryCrateCard renders: ", renderCount)
const options = use_options && card && card[use_options] && card[use_options].length > 0; const options = !options_disabled && card && card.options && card.options.length > 0;
const options_data = card_options_data && Object.keys(card_options_data).length > 0; const options_data = !options_disabled && card_options_data && Object.keys(card_options_data).length > 0;
const warnings = !warnings_disabled && card_show_warnings && card_show_warnings.length > 0; const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0;
return ( return (
<tr <tr
key={"summary_crate_" + crate_id + "_" + card_index} key={"summary_crate_" + crate_id + "_" + card_index}
className={`hoverable ${highlighted ? 'selected' : ''}`} className={`hoverable ${highlighted ? 'selected' : ''}`}
onClick={() => setHighlight(crate_id, card_index, horizontal)} onClick={() => setHighlight(crate_id, card_index)}
onMouseEnter={() => setHighlight(crate_id, card_index, horizontal)} onMouseEnter={() => setHighlight(crate_id, card_index)}
onMouseLeave={() => resetHighlight()}> onMouseLeave={() => resetHighlight()}>
<td className="item-card-name tabbed"> <td className="item-card-name tabbed">
<div>{`${card.name_number} ${card.name} ${card.name_codename}`}</div> <div>{`${card.name_number} ${card.name} ${card.name_codename}`}</div>
@ -60,7 +49,7 @@ export function SummaryCrateCard({crate_index, card_index, horizontal}) {
<div className="d-inline-flex align-content-center"> <div className="d-inline-flex align-content-center">
{`${currency} ${formatMoney(card.price)}`} {`${currency} ${formatMoney(card.price)}`}
<button onClick={() => deleteCard(crate_id, card_index, horizontal)}> <button onClick={() => deleteCard(crate_id, card_index)}>
<img src="/images/shop/icon-remove.svg" className="d-block"/> <img src="/images/shop/icon-remove.svg" className="d-block"/>
</button> </button>
@ -75,10 +64,7 @@ export function SummaryCrateCard({crate_index, card_index, horizontal}) {
}}>&nbsp;</span> }}>&nbsp;</span>
))} ))}
{((options && options_data) ? ( {((options && options_data) ? (
<OptionsSummaryWrapper <OptionsSummaryWrapper crate_index={crate_index} card_index={card_index}/>
crate_index={crate_index}
card_index={card_index}
horizontal={horizontal}/>
) : ( ) : (
<span style={{ <span style={{
'display': 'inline-block', 'display': 'inline-block',

View File

@ -1,6 +1,7 @@
import {formatMoney} from "./utils";
import React from "react"; import React from "react";
import {useShopStore} from "./shop_store"; import {useShopStore} from "./shop_store";
import {ProcessOptions} from "./options/Options"; import {ProcessOptions, ProcessOptionsToData} from "./options/Options";
// #!render_count // #!render_count
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";

View File

@ -21,13 +21,10 @@ export function validateJSON(description) {
try { try {
for (const crate of crates_raw) { for (const crate of crates_raw) {
if (!crate.type || !crate.items || !crate.h_items || !crate.options || !(crate.type in crate_modes)) return false; if (!crate.type || !crate.items || !crate.options || !(crate.type in crate_modes)) return false;
for (const card of crate.items) { for (const card of crate.items) {
if (!(card.pn in pn_to_card) || card.options === undefined) return false; if (!(card.pn in pn_to_card) || card.options === undefined) return false;
} }
for (const card of crate.h_items) {
if (!(card.pn in pn_to_card) || card.options === undefined) return false;
}
} }
} catch (e) { } catch (e) {
return false; return false;
@ -53,11 +50,6 @@ export function JSONToCrates(description) {
id: uuidv4(), id: uuidv4(),
options_data: card.options || {} options_data: card.options || {}
}))), }))),
h_items: Array.from(crate.h_items.map((card, _i) => ({
...pn_to_card(card.pn),
id: uuidv4(),
options_data: card.options || {}
}))),
warnings: [], warnings: [],
occupiedHP: 0, occupiedHP: 0,
}))); })));
@ -73,17 +65,12 @@ export function CratesToJSON(crates) {
const crateOptions = useShopStore.getState().crate_options; const crateOptions = useShopStore.getState().crate_options;
const orderOptions = useShopStore.getState().order_options; const orderOptions = useShopStore.getState().order_options;
const orderOptionsData = useShopStore.getState().order_options_data; const orderOptionsData = useShopStore.getState().order_options_data;
const crateParams = useShopStore.getState().crateParams;
return JSON.stringify({ return JSON.stringify({
// additional fields can go here // additional fields can go here
crates: Array.from(crates.map((crate, _i) => ({ crates: Array.from(crates.map((crate, _i) => ({
items: Array.from(crate.items.map((card, _) => ({ items: Array.from(crate.items.map((card, _) => ({
pn: card.name_number, pn: card.name_number,
options: (card.options_data && card[crateParams(crate.crate_mode).options]) ? FilterOptions(card[crateParams(crate.crate_mode).options], card.options_data) : null options: (card.options_data && card.options) ? FilterOptions(card.options, card.options_data) : null
}))),
h_items: Array.from(crate.h_items.map((card, _) => ({
pn: card.name_number,
options: (card.options_data && card[crateParams(crate.crate_mode).options]) ? FilterOptions(card[crateParams(crate.crate_mode).options], card.options_data) : null
}))), }))),
type: crate.crate_mode, type: crate.crate_mode,
options: FilterOptions(crateOptions, crate.options_data) options: FilterOptions(crateOptions, crate.options_data)

View File

@ -1,56 +1,46 @@
import React, {useRef, useState} from "react"; import React, {useState} from "react";
import {useClickAway} from "./useClickAway";
import {ProcessOptions} from "./Options"; import {ProcessOptions} from "./Options";
import {Notification} from "./Notification"; import {Notification} from "./Notification";
import {Overlay} from "react-bootstrap";
export function DialogPopup({options, data, target, id, options_class, export function DialogPopup({options, data, target, id, big, first, last, options_class,
sideMenuIsOpen, displayNotification, onHideNotification, horizontal}) { sideMenuIsOpen, displayNotification, onHideNotification}) {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const ref = useRef(null); const ref = useClickAway((e) => {
if (e.type === "mousedown") // ignore touchstart
let div_classes = `overlayVariant border rounded ${options_class || ""}`; setShow(false)
}
);
let div_classes = `overlayVariant border rounded ${big ? "overlay-bigcard" : "overlay-smallcard"} ${(!big && first) ? "overlay-first" : ""} ${(!big && last && !first) ? "overlay-last" : ""} ${options_class || ""}`;
const handleClick = (_event) => { const handleClick = (_event) => {
setShow(!show); setShow(!show);
}; };
return (<> return (
<Notification <div ref={ref}>
id={"processed_options_notification" + id} <Notification
tip="Customization options available" id={"processed_options_notification" + id}
sideMenuIsOpen={sideMenuIsOpen} tip="Customization options available"
show={displayNotification} sideMenuIsOpen={sideMenuIsOpen}
onHide={onHideNotification} show={displayNotification}
content={ onHide={onHideNotification}
<img className="alert-info" ref={ref} content={
src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"} <img className="alert-info"
onClick={handleClick}/> src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
} onClick={handleClick}/>
/> }
<Overlay target={ref.current} />
show={show} <div style={{'display': show ? 'flex' : 'none'}} className={div_classes}>
placement={horizontal ? "right" : "bottom"} <ProcessOptions
onHide={() => setShow(false)} options={options}
rootClose={true}> data={data}
{({ key={"processed_options_" + id}
placement: _placement, id={"processed_options_" + id}
arrowProps: _arrowProps, target={target}
show: _show, />
popper: _popper, </div>
hasDoneInitialMeasure: _hasDoneInitialMeasure, </div>
...props );
}) => (
<div style={{'display': show ? 'flex' : 'none', ...props.style}} {...props} className={div_classes}>
<ProcessOptions
options={options}
data={data}
key={"processed_options_" + id}
id={"processed_options_" + id}
target={target}
/>
</div>
)}
</Overlay>
</>);
} }

View File

@ -1,13 +1,13 @@
const ipv4 = (_params) => { const ipv4 = (params) => {
const ipv4WithMaskPattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(0|1[0-9]|2[0-9]|3[0-2]|[0-9])$/; const ipv4WithMaskPattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(0|1[0-9]|2[0-9]|3[0-2]|[0-9])$/;
return (text) => { return (text) => {
return ipv4WithMaskPattern.test(text); return ipv4WithMaskPattern.test(text);
} }
} }
const ipv6 = (_params) => { const ipv6 = (params) => {
const ipv6WithMaskPattern = /(^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(\/(\d{1,2}|1[0-1]\d|12[0-8]))(%.+)?\s*$)/; const ipv6WithMaskPattern = /(^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(\/(\d{1,2}|1[0-1]\d|12[0-8]))(%.+)?\s*$)/;
@ -16,35 +16,6 @@ const ipv6 = (_params) => {
} }
} }
const hostname = (_params) => {
const maxHostnameLength = 253;
const maxLabelLength = 63;
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
return (text) => {
if (text.length > maxHostnameLength) {
return false;
}
const labels = text.split('.');
for (const label of labels) {
if (label.length < 1
|| label.length > maxLabelLength
|| !labelRegex.test(label)) {
return false;
}
}
return true;
}
}
const ipv4OrHost = (params) => {
const hostnameLocal = hostname(params);
const ipv4Local = ipv4(params);
return (text) => {
return ipv4Local(text) || hostnameLocal(text);
}
}
const ipv4or6 = (params) => { const ipv4or6 = (params) => {
const ipv4Local = ipv4(params); const ipv4Local = ipv4(params);
const ipv6Local = ipv6(params); const ipv6Local = ipv6(params);
@ -76,7 +47,5 @@ export const Validation = {
ipv4: ipv4, ipv4: ipv4,
ipv6: ipv6, ipv6: ipv6,
ipv4or6: ipv4or6, ipv4or6: ipv4or6,
hostname: hostname,
ipv4OrHost: ipv4OrHost,
frequency: frequency frequency: frequency
}; };

View File

@ -2,7 +2,7 @@
import {createWithEqualityFn} from "zustand/traditional"; import {createWithEqualityFn} from "zustand/traditional";
import {DATA as shared_data, itemsUnfoldedList, API_RFQ} from "./utils"; import {DATA as shared_data, itemsUnfoldedList, API_RFQ} from "./utils";
import {FillExtCrateData, FillExtOrderData} from "./options/utils"; import {FillExtCrateData, FillExtOrderData, true_type_of} from "./options/utils";
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
import {FillResources} from "./count_resources"; import {FillResources} from "./count_resources";
import {FillExtCardData} from "./options/utils"; import {FillExtCardData} from "./options/utils";
@ -13,24 +13,12 @@ import {ProcessOptionsToData} from "./options/Options";
import {DomainedRFQMessages} from "./Domained"; import {DomainedRFQMessages} from "./Domained";
export const HORIZONTAL_CART_MARKER = "_h";
const cards_to_pn_map = (cards) => { const cards_to_pn_map = (cards) => {
let result = {}; let result = {};
Object.entries(cards).forEach(([key, card], _i) => { result[card.name_number] = key}) Object.entries(cards).forEach(([key, card], _i) => { result[card.name_number] = key})
return result; return result;
}; };
const toArray = (arg) => {
return Array.isArray(arg) ? arg : [arg];
};
const unwrapCrateId = (crate_id= "") => {
return crate_id.endsWith(HORIZONTAL_CART_MARKER) ? [true, crate_id.substring(0, crate_id.length - HORIZONTAL_CART_MARKER.length)] : [false, crate_id]
}
export const whichItems = (horizontal = false) => horizontal ? "h_items" : "items"
const useCatalog = ((set, get) => ({ const useCatalog = ((set, get) => ({
cards: shared_data.items, cards: shared_data.items,
groups: shared_data.columns.catalog, groups: shared_data.columns.catalog,
@ -45,24 +33,21 @@ const useCatalog = ((set, get) => ({
const useOptionsNotification = ((set, get) => ({ const useOptionsNotification = ((set, get) => ({
notificationCrateId: null, notificationCrateId: null,
notificationCardIndex: null, notificationCardIndex: null,
notificationHorizontal: false,
notificationTimer: null, notificationTimer: null,
_showNotification: (crate_id, card_index, horizontal) => set(state => ({ _showNotification: (crate_id, card_index) => set(state => ({
notificationCrateId: crate_id, notificationCrateId: crate_id,
notificationCardIndex: card_index, notificationCardIndex: card_index,
notificationHorizontal: horizontal,
notificationTimer: setTimeout(() => { notificationTimer: setTimeout(() => {
state.hideNotification() state.hideNotification()
}, 5000) }, 5000)
})), })),
showNotification: (crate_id, card_index, horizontal) => { showNotification: (crate_id, card_index) => {
get().hideNotification() get().hideNotification()
setTimeout(() => get()._showNotification(crate_id, card_index, horizontal), 100); setTimeout(() => get()._showNotification(crate_id, card_index), 100);
}, },
hideNotification: () => set(state => ({ hideNotification: () => set(state => ({
notificationCrateId: null, notificationCrateId: null,
notificationCardIndex: null, notificationCardIndex: null,
notificationHorizontal: false,
notificationTimer: (state.notificationTimer && clearTimeout(state.notificationTimer)) || null, notificationTimer: (state.notificationTimer && clearTimeout(state.notificationTimer)) || null,
})) }))
})); }));
@ -226,7 +211,7 @@ const useImportJSON = ((set, get) => ({
get().fillExtCrateData(crate.id); get().fillExtCrateData(crate.id);
}); });
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();
get().showNotification(get().active_crate, null, false); get().showNotification(get().active_crate, null);
}, },
updateImportDescription: (new_description) => set(state => ({ updateImportDescription: (new_description) => set(state => ({
importValue: { importValue: {
@ -343,17 +328,15 @@ const useSubmitForm = ((set, get) => ({
const useHighlighted = ((set, get) => ({ const useHighlighted = ((set, get) => ({
highlighted: { highlighted: {
crate: "", crate: "",
card: 0, card: 0
horizontal: false
}, },
highlightedTimer: null, highlightedTimer: null,
// #!if disable_card_highlight === false // #!if disable_card_highlight === false
highlightCard: (crate_id, index, horizontal) => set(state => ({ highlightCard: (crate_id, index) => set(state => ({
highlighted: { highlighted: {
crate: crate_id, crate: crate_id,
card: index, card: index
horizontal: horizontal
}, },
highlightedTimer: (!!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null) || (state.isTouch && setTimeout(() => { highlightedTimer: (!!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null) || (state.isTouch && setTimeout(() => {
get().highlightReset() get().highlightReset()
@ -362,8 +345,7 @@ const useHighlighted = ((set, get) => ({
highlightReset: () => set(state => ({ highlightReset: () => set(state => ({
highlighted: { highlighted: {
crate: "", crate: "",
card: 0, card: 0
horizontal: false
}, },
highlightedTimer: !!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null highlightedTimer: !!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null
})), })),
@ -402,18 +384,17 @@ const useCart = ((set, get) => ({
}) })
})), })),
setActiveCrate: (id) => set(state => ({active_crate: id})), setActiveCrate: (id) => set(state => ({active_crate: id})),
_addCardFromCatalog: (crate_to, index_from, index_to, horizontal) => set(state => { _addCardFromCatalog: (crate_to, index_from, index_to) => set(state => {
const whichH = whichItems(horizontal) const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
const take_from = toArray(index_from).map((item, _i) => (state.cards_list[item]));
const dest = crate_to || state.active_crate; const dest = crate_to || state.active_crate;
if (!dest) return {}; if (!dest) return {};
return { return {
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (dest === crate.id) { if (dest === crate.id) {
index_to = index_to != null ? index_to : crate[whichH].length; index_to = index_to != null ? index_to : crate.items.length;
return { return {
...crate, ...crate,
[whichH]: crate[whichH].toSpliced(index_to, 0, ...take_from.map((card_name, _) => { items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => {
return {...state.cards[card_name], id: uuidv4()} return {...state.cards[card_name], id: uuidv4()}
})) }))
} }
@ -421,41 +402,39 @@ const useCart = ((set, get) => ({
}) })
} }
}), }),
_moveCard: (crate_from, index_from, crate_to, index_to, horizontal) => set(state => { _moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
const whichH = whichItems(horizontal) const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from];
const the_card = state.crates.find((crate, _) => crate_from === crate.id )[whichH][index_from];
return { return {
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (crate_to === crate_from && crate_to === crate.id) { if (crate_to === crate_from && crate_to === crate.id) {
let items_copy = Array.from(crate[whichH]); let items_copy = Array.from(crate.items);
let item = items_copy.splice(index_from, 1)[0] let item = items_copy.splice(index_from, 1)[0]
items_copy.splice(index_to, 0, item).filter((item, _) => !!item) items_copy.splice(index_to, 0, item).filter((item, _) => !!item)
return { return {
...crate, ...crate,
[whichH]: items_copy items: items_copy
} }
} else if (crate_to === crate.id) { } else if (crate_to === crate.id) {
return { return {
...crate, ...crate,
[whichH]: crate[whichH].toSpliced(index_to, 0, the_card) items: crate.items.toSpliced(index_to, 0, the_card)
} }
} else if (crate_from === crate.id) { } else if (crate_from === crate.id) {
return { return {
...crate, ...crate,
[whichH]: crate[whichH].toSpliced(index_to, 1) items: crate.items.toSpliced(index_to, 1)
} }
} }
else return crate; else return crate;
}) })
} }
}), }),
_deleteCard: (crate_id, index, horizontal) => set(state => ({ _deleteCard: (crate_id, index) => set(state => ({
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) { if (crate_id === crate.id) {
const whichH = whichItems(horizontal)
return { return {
...crate, ...crate,
[whichH]: crate[whichH].toSpliced(index, 1) items: crate.items.toSpliced(index, 1)
} }
} }
else return crate; else return crate;
@ -466,8 +445,7 @@ const useCart = ((set, get) => ({
if (id === crate.id) { if (id === crate.id) {
return { return {
...crate, ...crate,
items: [], items: []
h_items: []
} }
} }
else return crate; else return crate;
@ -476,11 +454,10 @@ const useCart = ((set, get) => ({
clearAll: () => set(state => ({ clearAll: () => set(state => ({
crates: state._defaultCrates crates: state._defaultCrates
})), })),
_updateOptions: (crate_id, index, new_options, horizontal) => set(state => ({ _updateOptions: (crate_id, index, new_options) => set(state => ({
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) { if (crate_id === crate.id) {
const whichH = whichItems(horizontal) let itemsCopy = Array.from(crate.items);
let itemsCopy = Array.from(crate[whichH]);
itemsCopy[index] = { itemsCopy[index] = {
...itemsCopy[index], ...itemsCopy[index],
options_data: { options_data: {
@ -489,7 +466,7 @@ const useCart = ((set, get) => ({
}}; }};
return { return {
...crate, ...crate,
[whichH]: itemsCopy items: itemsCopy
} }
} }
else return crate; else return crate;
@ -499,7 +476,7 @@ const useCart = ((set, get) => ({
fillWarnings: (crate_id) => set(state => ({ fillWarnings: (crate_id) => set(state => ({
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) { if (crate_id === crate.id) {
// Warnings for horizontal items are not available //console.log("--- CHECK ALERTS ---")
let itemsCopy = Array.from(crate.items); let itemsCopy = Array.from(crate.items);
const disabled = !!get().crateParams(crate.crate_mode).warnings_disabled; const disabled = !!get().crateParams(crate.crate_mode).warnings_disabled;
itemsCopy = FillResources(itemsCopy, disabled); itemsCopy = FillResources(itemsCopy, disabled);
@ -516,23 +493,20 @@ const useCart = ((set, get) => ({
}) })
})), })),
fillExtData: (crate_id, horizontal) => set(state => ({ fillExtData: (crate_id) => set(state => ({
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
// horizontal items do not interact with each other for now
if (crate_id === crate.id) { if (crate_id === crate.id) {
const whichH = whichItems(horizontal) let itemsCopy = Array.from(crate.items);
const options_name = state.crateParams(crate.crate_mode).options;
let itemsCopy = Array.from(crate[whichH]);
itemsCopy = itemsCopy.map((item, index) => { itemsCopy = itemsCopy.map((item, index) => {
if (!item[options_name]) return item; if (!item.options) return item;
if (!item.options_data) item.options_data = {}; if (!item.options_data) item.options_data = {};
item.options_data.ext_data = FillExtCardData(itemsCopy, index); item.options_data.ext_data = FillExtCardData(itemsCopy, index);
return item; return item;
}); });
return { return {
...crate, ...crate,
[whichH]: Array.from(itemsCopy) items: Array.from(itemsCopy)
} }
} }
else return crate; else return crate;
@ -545,7 +519,7 @@ const useCart = ((set, get) => ({
sum += get().crate_modes[crate.crate_mode].price; sum += get().crate_modes[crate.crate_mode].price;
const crate_options = ProcessOptionsToData({options: get().crate_prices, data: crate.options_data || {}}); const crate_options = ProcessOptionsToData({options: get().crate_prices, data: crate.options_data || {}});
sum += crate_options ? crate_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0; sum += crate_options ? crate_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0;
crate.items.concat(crate.h_items).forEach((item, _) => { crate.items.forEach((item, _) => {
sum += item.price; sum += item.price;
}); });
}); });
@ -560,7 +534,6 @@ const useCart = ((set, get) => ({
const crate_id = "crate" + get().crates.length; const crate_id = "crate" + get().crates.length;
get()._newCrate(crate_id) get()._newCrate(crate_id)
get().fillExtData(crate_id); get().fillExtData(crate_id);
get().fillExtData(crate_id, true);
get().fillExtCrateData(crate_id); get().fillExtCrateData(crate_id);
get().fillOrderExtData(); get().fillOrderExtData();
get().fillWarnings(crate_id); get().fillWarnings(crate_id);
@ -570,7 +543,6 @@ const useCart = ((set, get) => ({
setCrateMode: (id, mode) => { setCrateMode: (id, mode) => {
get()._setCrateMode(id, mode) get()._setCrateMode(id, mode)
get().fillExtData(id); get().fillExtData(id);
get().fillExtData(id, true);
get().fillExtCrateData(id); get().fillExtCrateData(id);
get().fillOrderExtData(); get().fillOrderExtData();
get().fillWarnings(id); get().fillWarnings(id);
@ -585,18 +557,15 @@ const useCart = ((set, get) => ({
}, },
addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => { addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => {
const isCrateless = toArray(index_from).some(value => get().getCardDescription(value).crateless === true); const dest = crate_to || get().active_crate;
const [isHorizontal, crateTo] = crate_to ? unwrapCrateId(crate_to) : [false, crate_to];
const isHorizontalOnly = toArray(index_from).some(value => !!get().getCardDescription(value).horizontal);
const dest = isCrateless ? "spare" : crateTo || get().active_crate;
if (!dest) { if (!dest) {
console.warn("No destination"); console.warn("No destination");
get().noDestinationWarning(); get().noDestinationWarning();
return {}; return {};
} }
get().showNotification(dest, index_to, isHorizontalOnly); get().showNotification(dest, index_to);
get()._addCardFromCatalog(dest, index_from, index_to, isHorizontalOnly) get()._addCardFromCatalog(dest, index_from, index_to)
get().fillExtData(dest, isHorizontalOnly); get().fillExtData(dest);
get().fillWarnings(dest); get().fillWarnings(dest);
get().setActiveCrate(dest); get().setActiveCrate(dest);
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();
@ -606,25 +575,22 @@ const useCart = ((set, get) => ({
}, },
moveCard: (crate_from, index_from, crate_to, index_to) => { moveCard: (crate_from, index_from, crate_to, index_to) => {
const [isHorizontal, crateFrom] = unwrapCrateId(crate_from) get()._moveCard(crate_from, index_from, crate_to, index_to);
const [_, crateTo] = unwrapCrateId(crate_to) get().fillExtData(crate_to);
get()._moveCard(crateFrom, index_from, crateTo, index_to, isHorizontal); get().fillWarnings(crate_to);
get().fillExtData(crateTo, isHorizontal); get().setActiveCrate(crate_to);
get().fillWarnings(crateTo);
get().setActiveCrate(crateTo);
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();
if (crateFrom !== crate_to) { if (crate_from !== crate_to) {
get().fillExtData(crateFrom, isHorizontal); get().fillExtData(crate_from);
get().fillWarnings(crateFrom); get().fillWarnings(crate_from);
} }
}, },
deleteCard: (crate_id, index, horizontal) => { deleteCard: (crate_id, index) => {
const [isHorizontal, crateId] = horizontal ? [horizontal, crate_id] : unwrapCrateId(crate_id); get()._deleteCard(crate_id, index);
get()._deleteCard(crateId, index, isHorizontal); get().fillExtData(crate_id);
get().fillExtData(crateId, isHorizontal); get().fillWarnings(crate_id);
get().fillWarnings(crateId);
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();
if (crateId === get().highlighted.crate && index === get().highlighted.card) get().highlightReset() if (crate_id === get().highlighted.crate && index === get().highlighted.card) get().highlightReset()
}, },
clearCrate: (id) => { clearCrate: (id) => {
get()._clearCrate(id); get()._clearCrate(id);
@ -632,19 +598,16 @@ const useCart = ((set, get) => ({
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();
}, },
updateOptions: (crate_id, index, new_options, horizontal) => { updateOptions: (crate_id, index, new_options) => {
get()._updateOptions(crate_id, index, new_options, horizontal); get()._updateOptions(crate_id, index, new_options);
get().fillExtData(crate_id, horizontal); get().fillExtData(crate_id);
if (!horizontal) { get().fillWarnings(crate_id);
get().fillWarnings(crate_id);
}
}, },
initExtData: () => { initExtData: () => {
get().fillOrderExtData(); get().fillOrderExtData();
get().crates.forEach((crate, _i) => { get().crates.forEach((crate, _i) => {
get().fillExtData(crate.id, true); get().fillExtData(crate.id);
get().fillExtData(crate.id, false);
get().fillExtCrateData(crate.id); get().fillExtCrateData(crate.id);
}) })
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();

View File

@ -6,23 +6,20 @@ const shop_data = {
id: 'rack', id: 'rack',
name: 'Rack mountable crate', name: 'Rack mountable crate',
price: 550, price: 550,
hp: 84, hp: 84
options: "options"
}, },
desktop: { desktop: {
id: 'desktop', id: 'desktop',
name: 'Desktop crate', name: 'Desktop crate',
price: 500, price: 500,
hp: 42, hp: 42
options: "options"
}, },
no_crate: { no_crate: {
id: 'no_crate', id: 'no_crate',
name: 'Spare items', name: 'Spare cards',
price: 0, price: 0,
hp: -1, hp: -1,
warnings_disabled: true, warnings_disabled: true
options: "crateless_options"
} }
}, },
crateModeOrder: [ crateModeOrder: [
@ -1106,24 +1103,10 @@ const shop_data = {
validator: {name: "ipv4or6"}, validator: {name: "ipv4or6"},
fallback: {text: "DHCP", checked: false}, fallback: {text: "DHCP", checked: false},
tip: "Set up IP address used by the device"}}, tip: "Set up IP address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
{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: "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 #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"}} {type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
], ],
crateless_options: [
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
validator: {name: "ipv4or6"},
fallback: {text: "DHCP", checked: false},
tip: "Set up IP address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
],
options_class: "stabilizer", options_class: "stabilizer",
size: 'small', size: 'small',
warnings: [ warnings: [
@ -1154,24 +1137,10 @@ const shop_data = {
validator: {name: "ipv4or6"}, validator: {name: "ipv4or6"},
fallback: {text: "DHCP", checked: false}, fallback: {text: "DHCP", checked: false},
tip: "Set up IP address used by the device"}}, tip: "Set up IP address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
{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: "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 #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"}} {type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
], ],
crateless_options: [
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
validator: {name: "ipv4or6"},
fallback: {text: "DHCP", checked: false},
tip: "Set up IP address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
],
options_class: "stabilizer", options_class: "stabilizer",
size: 'small', size: 'small',
warnings: [ warnings: [
@ -1257,25 +1226,7 @@ const shop_data = {
'100Base-T Ethernet with PoE.' '100Base-T Ethernet with PoE.'
], ],
options: [ 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: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}
{type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip",
validator: {name: "ipv4"},
fallback: {text: "0.0.0.0", checked: false},
tip: "Set up static IPv4 address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
],
crateless_options: [
{type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip",
validator: {name: "ipv4"},
fallback: {text: "0.0.0.0", checked: false},
tip: "Set up static IPv4 address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
], ],
size: 'small', size: 'small',
warnings: [ warnings: [
@ -1300,12 +1251,6 @@ const shop_data = {
'100Base-T Ethernet with PoE.', '100Base-T Ethernet with PoE.',
'Can stabilize temperature of Sinara 5432 DAC or external devices containing TEC and thermistor.' 'Can stabilize temperature of Sinara 5432 DAC or external devices containing TEC and thermistor.'
], ],
crateless_options: [
{type: "SwitchLine", args: {title: "IPv4", outvar: "ip",
validator: {name: "ipv4"},
fallback: {text: "192.168.1.26/24", checked: false},
tip: "Set up IP address used by the device"}},
],
size: 'small', size: 'small',
consumes: { consumes: {
hp: 4 hp: 4
@ -1357,26 +1302,12 @@ const shop_data = {
validator: {name: "ipv4or6"}, validator: {name: "ipv4or6"},
fallback: {text: "DHCP", checked: false}, fallback: {text: "DHCP", checked: false},
tip: "Set up IP address used by the device"}}, tip: "Set up IP address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
{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: "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", {type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
fallback: {text: "125 MHz", checked: false}, validator: {name: "frequency", params: {min: 10e6, max: 1e9}}}}, fallback: {text: "125 MHz", checked: false}, validator: {name: "frequency", params: {min: 10e6, max: 1e9}}}},
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}}, {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"}} {type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
], ],
crateless_options: [
{type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip",
validator: {name: "ipv4"},
fallback: {text: "0.0.0.0", checked: false},
tip: "Set up static IPv4 address used by the device"}},
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
validator: {name: "ipv4OrHost"},
fallback: {text: "mqtt", checked: false},
tip: "Set up domain name or IPv4 of the MQTT broker"}},
],
size: 'big', size: 'big',
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
@ -1443,27 +1374,6 @@ const shop_data = {
hp: 8, hp: 8,
}, },
}, },
'afws': {
id: 'afws',
name: 'Subscription',
name_number: 'AFWS',
name_codename: '',
price: 800,
image: null,
horizontal: true,
specs: [
"Artiq Firmware Service for one variant for one year.",
"Includes support at helpdesk.",
"Included with purchase of any Carrier with no additional cost.",
],
crateless: true,
crateless_options: [
{type: "Line", args: {title: "Variant name", outvar: "variant_name", fallback: "",
tip: "Variant name can be found on the sticker on top of the crate. If you don't have one, leave the preferred name here."}},
],
size: 'big',
warnings: [],
},
}, },
columns: { columns: {
@ -1521,7 +1431,6 @@ const shop_data = {
'koster', 'koster',
'eem_pwr_mod', 'eem_pwr_mod',
'kirdy', 'kirdy',
'afws',
]} ]}
], ],
}, },
@ -1531,16 +1440,14 @@ const shop_data = {
crate_mode: "rack", crate_mode: "rack",
fan_tray: false, fan_tray: false,
items: [], items: [],
h_items: [],
warnings: [], warnings: [],
occupiedHP: 0, occupiedHP: 0,
}, },
{ {
id: "spare", id: "spare",
name: "Spare items", name: "Spare cards",
crate_mode: "no_crate", crate_mode: "no_crate",
items: [], items: [],
h_items: [],
warnings: [], warnings: [],
occupiedHP: 0, occupiedHP: 0,
} }