shop: add crateless items and options #143

Open
esavkin wants to merge 7 commits from esavkin/web2019:125-crateless into master
22 changed files with 646 additions and 219 deletions

View File

@ -117,10 +117,20 @@ button {
margin-bottom: 20px;
}
button {
img {
cursor: pointer;
}
}
img {
height: 400px;
align-self: center;
border: 0;
cursor: grab;
&.default-icon {
height: 144px;
}
}
h3 {
@ -485,6 +495,45 @@ button {
padding: 5px 5px 12px;
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 {
display: flex;
flex-direction: column;
@ -571,67 +620,6 @@ 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 {
@ -778,3 +766,49 @@ 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

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1 @@
<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>

After

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 from 'react'
import React, {useRef, useEffect, useState} from 'react'
import {Droppable} from "@hello-pangea/dnd";
import {cartStyle, compareArraysWithIds} from "./utils";
import {ProductCartItem} from "./ProductCartItem";
@ -9,17 +9,17 @@ import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
/**
* Component that displays a list of <ProductCartItem>
*/
export function Cart({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const containerRef = useRef(null);
const [visiblePlaceholders, setVisiblePlaceholders] = useState(0);
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
});
const crateParams = useShopStore((state) => state.crateParams);
const isCrate = useShopStore((state) => state.modes_order.includes(state.crates[crate_index].crate_mode));
// #!render_count
console.log("Cart renders: ", renderCount)
@ -27,23 +27,43 @@ export function Cart({crate_index}) {
const nbrOccupied = hp_to_slots(crate.occupiedHP);
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) => {
return (
<ProductCartItem
card_index={index}
crate_index={crate_index}
first={index === 0}
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
key={item.id}/>
);
});
return (
<Droppable droppableId={crate.id} direction="horizontal">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
ref={(element) => {
containerRef.current = element;
provided.innerRef(element);
}}
{...provided.droppableProps}
style={cartStyle(
provided.droppableProps.style,
@ -60,11 +80,10 @@ export function Cart({crate_index}) {
)}
<FakePlaceholder
nToDraw={nbrSlots - nbrOccupied}
nToDraw={visiblePlaceholders}
isDraggingOver={snapshot.isDraggingOver}/>
</div>
)}
</Droppable>
);
}

View File

@ -0,0 +1,59 @@
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 data = useShopStore((state) => state.groups);
const items = useShopStore((state) => state.cards);
const _items = useShopStore((state) => state.cards);
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
const isMobile = useShopStore((state) => state.isMobile);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ export function SummaryCrate({crate_index}) {
const crate_id = useShopStore((state) => state.crates[crate_index].id);
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
console.log("SummaryCrate renders: ", renderCount)
@ -28,6 +29,14 @@ export function SummaryCrate({crate_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}/>
</tbody>
)

View File

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

View File

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

View File

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

View File

@ -1,46 +1,56 @@
import React, {useState} from "react";
import {useClickAway} from "./useClickAway";
import React, {useRef, useState} from "react";
import {ProcessOptions} from "./Options";
import {Notification} from "./Notification";
import {Overlay} from "react-bootstrap";
export function DialogPopup({options, data, target, id, big, first, last, options_class,
sideMenuIsOpen, displayNotification, onHideNotification}) {
export function DialogPopup({options, data, target, id, options_class,
sideMenuIsOpen, displayNotification, onHideNotification, horizontal}) {
const [show, setShow] = useState(false);
const ref = useClickAway((e) => {
if (e.type === "mousedown") // ignore touchstart
setShow(false)
}
);
const ref = useRef(null);
let div_classes = `overlayVariant border rounded ${options_class || ""}`;
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) => {
setShow(!show);
};
return (
<div ref={ref}>
<Notification
id={"processed_options_notification" + id}
tip="Customization options available"
sideMenuIsOpen={sideMenuIsOpen}
show={displayNotification}
onHide={onHideNotification}
content={
<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>
);
return (<>
<Notification
id={"processed_options_notification" + id}
tip="Customization options available"
sideMenuIsOpen={sideMenuIsOpen}
show={displayNotification}
onHide={onHideNotification}
content={
<img className="alert-info" ref={ref}
src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
onClick={handleClick}/>
}
/>
<Overlay target={ref.current}
show={show}
placement={horizontal ? "right" : "bottom"}
onHide={() => setShow(false)}
rootClose={true}>
{({
placement: _placement,
arrowProps: _arrowProps,
show: _show,
popper: _popper,
hasDoneInitialMeasure: _hasDoneInitialMeasure,
...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])$/;
return (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*$)/;
@ -16,6 +16,35 @@ 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 ipv4Local = ipv4(params);
const ipv6Local = ipv6(params);
@ -47,5 +76,7 @@ export const Validation = {
ipv4: ipv4,
ipv6: ipv6,
ipv4or6: ipv4or6,
hostname: hostname,
ipv4OrHost: ipv4OrHost,
frequency: frequency
};

View File

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

View File

@ -6,20 +6,23 @@ const shop_data = {
id: 'rack',
name: 'Rack mountable crate',
price: 550,
hp: 84
hp: 84,
options: "options"
},
desktop: {
id: 'desktop',
name: 'Desktop crate',
price: 500,
hp: 42
hp: 42,
options: "options"
},
no_crate: {
id: 'no_crate',
name: 'Spare cards',
name: 'Spare items',
price: 0,
hp: -1,
warnings_disabled: true
warnings_disabled: true,
options: "crateless_options"
}
},
crateModeOrder: [
@ -1103,10 +1106,24 @@ const shop_data = {
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"}},
{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"}}
],
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",
size: 'small',
warnings: [
@ -1137,10 +1154,24 @@ const shop_data = {
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"}},
{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"}}
],
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",
size: 'small',
warnings: [
@ -1226,7 +1257,25 @@ const shop_data = {
'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"}}
{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',
warnings: [
@ -1251,6 +1300,12 @@ const shop_data = {
'100Base-T Ethernet with PoE.',
'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',
consumes: {
hp: 4
@ -1302,12 +1357,26 @@ const shop_data = {
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"}},
{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}, 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 #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',
warnings: [
"no_eem_source",
@ -1374,6 +1443,27 @@ const shop_data = {
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: {
@ -1431,6 +1521,7 @@ const shop_data = {
'koster',
'eem_pwr_mod',
'kirdy',
'afws',
]}
],
},
@ -1440,14 +1531,16 @@ const shop_data = {
crate_mode: "rack",
fan_tray: false,
items: [],
h_items: [],
warnings: [],
occupiedHP: 0,
},
{
id: "spare",
name: "Spare cards",
name: "Spare items",
crate_mode: "no_crate",
items: [],
h_items: [],
warnings: [],
occupiedHP: 0,
}