1
0
Fork 0

Compare commits

...

11 Commits

Author SHA1 Message Date
Egor Savkin 608f253684 WIP add horizontal items into the cart
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-11-01 17:32:20 +08:00
Egor Savkin 66bb5b25d8 Make dialog popup rendered by bootstrap overlay library
This makes it more dynamic and eases proper placement in case of vertical layout

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-11-01 12:39:30 +08:00
Egor Savkin 34a86e24c5 Add AFWS item to the shop and update bundle
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-28 13:36:33 +08:00
Egor Savkin 175e625cc6 Fix exception on thermostat2ch and build the bundle
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-28 13:36:33 +08:00
Egor Savkin 49882c7af2 Update cards
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-28 13:36:33 +08:00
Egor Savkin efd6b12c83 Add possibility for crateless options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-28 13:36:33 +08:00
Egor Savkin 18bd98a204 Automatically open spare cards for crateless items
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-28 13:36:33 +08:00
Egor Savkin f29807a913 Optimize the download button
Replace download button with windows icon (just four squares).
Make the download button last so on certain screens it wouldn't cause additional empty-ish lines.
Redo the wording so the button is shorter, but the dropdown menu is filled.

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-21 17:46:40 +08:00
Egor Savkin 9d9a4c9f5a Split dropdown
On main click it downloads stable, in dropdown there are two choices - stable and beta. Similar to the downloads on jetbrains website

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-21 15:32:19 +08:00
Egor Savkin 92f7428ac8 Replace download button with dropdown
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-18 12:29:19 +08:00
Egor Savkin a163a269b9 Update NUC
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-10-17 11:10:40 +08:00
18 changed files with 636 additions and 221 deletions

View File

@ -90,6 +90,7 @@ a {
} }
} }
.navbar {
.navbar-light .navbar-nav .nav-link, .navbar-light .navbar-nav .nav-link,
.dropdown-item { .dropdown-item {
outline: none; outline: none;
@ -112,6 +113,29 @@ a {
color: $color-secondary; color: $color-secondary;
} }
.dropdown-menu {
border: none;
}
.dropdown-item {
&:hover,
&:active {
background-color: transparent;
}
}
.dropdown-item.active {
color: $color-secondary;
background-color: transparent;
}
.navbar-toggler {
outline: none;
&:focus,
&:hover {
outline: none;
}
}
}
/** /**
@ -127,22 +151,6 @@ a {
border: 1px solid transparent; border: 1px solid transparent;
} }
.dropdown-menu {
border: none;
margin-top: 0;
padding-top: 0;
}
.dropdown-item {
&:hover,
&:active {
background-color: transparent;
}
}
.dropdown-item.active {
color: $color-secondary;
background-color: transparent;
}
.btn-primary { .btn-primary {
background-color: $btn-primary-2; background-color: $btn-primary-2;
color: #fff !important; color: #fff !important;
@ -155,18 +163,11 @@ a {
border: 1px solid $btn-secondary-2 !important; border: 1px solid $btn-secondary-2 !important;
} }
} }
.btn-lg { .btn-lg {
font-size: 1rem; font-size: 1rem;
padding: 1rem 1.25rem; padding: 1rem 1.25rem;
} }
.navbar-toggler {
outline: none;
&:focus,
&:hover {
outline: none;
}
}
ul.th { ul.th {
list-style: none; list-style: none;
@ -200,6 +201,63 @@ ul:not(.navbar-nav) li {
} }
.download-selector {
display: inline-flex;
.divider {
border-right: solid 1px white;
margin: 0.125rem 0;
height: inherit;
z-index: 10;
}
.dropdown-menu {
border: none;
margin-top: 0;
padding-top: 0;
}
.btn {
background-color: $btn-primary-2;
&:hover {
background-color: $btn-secondary-2;
}
&:after {
align-self: center;
}
}
button {
&[aria-expanded='true']:after {
transform: rotate(-180deg);
}
span {
margin-right: 0.5rem;
}
}
ul {
list-style: none;
margin-left: 0!important;
width: 100%;
padding: 0;
li {
padding: 0;
margin: 0;
a {
padding: 0.75rem 0.5rem 0.75rem 1.25rem;
}
}
li::before {
content: none;
display: none;
}
}
}
.bg-white-shadow { .bg-white-shadow {
background: url(../images/migen-links@2x.png); background: url(../images/migen-links@2x.png);
background-repeat: no-repeat; background-repeat: no-repeat;

View File

@ -485,6 +485,12 @@ button {
padding: 5px 5px 12px; padding: 5px 5px 12px;
position: relative; position: relative;
&.horizontal {
flex-direction: column;
min-height: 50px;
overflow-y: auto;
}
> div { > div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -571,67 +577,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 { .hovered {
@ -778,3 +723,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;
}
}

View File

@ -0,0 +1 @@
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg"><g fill="#fff" stroke-linecap="round" stroke-opacity=".6" stroke-width=".35269"><path d="m0.52917 0.52917h5.5563v5.5563h-5.5563z"/><path d="m6.6146 0.52917h5.5563v5.5563h-5.5563z"/><path d="m0.52917 6.6146h5.5563v5.5563h-5.5563z"/><path d="m6.6146 6.6146h5.5563v5.5563h-5.5563z"/></g></svg>

After

Width:  |  Height:  |  Size: 392 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

View File

@ -32,8 +32,6 @@ export function Cart({crate_index}) {
<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}/>
); );
}); });

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

@ -4,6 +4,7 @@ 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";
@ -28,7 +29,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}/>
@ -36,13 +37,15 @@ 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}/>
<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

@ -3,11 +3,11 @@ import React from "react";
import {useShopStore} 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, first, last}) { export function OptionsDialogWrapper({crate_index, card_index, horizontal}) {
const crate_id = useShopStore((state) => state.crates[crate_index].id); const crate_id = useShopStore((state) => state.crates[crate_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 = useShopStore((state) => state.crates[crate_index].items[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].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 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 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);
@ -26,12 +26,10 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
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
@ -53,7 +51,8 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
export function OptionsSummaryWrapper({crate_index, card_index}) { export function OptionsSummaryWrapper({crate_index, card_index}) {
const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id); 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 use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
const options = useShopStore((state) => state.crates[crate_index].items[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].items[card_index].options_data);
return ( return (

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, first, last}) { export function ProductCartItem({card_index, crate_index}) {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
@ -25,7 +25,8 @@ export function ProductCartItem({card_index, crate_index, first, last}) {
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) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card); 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 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);
@ -35,9 +36,9 @@ export function ProductCartItem({card_index, crate_index, first, last}) {
console.log("ProductCartItem renders: ", renderCount) console.log("ProductCartItem renders: ", renderCount)
const options = !options_disabled && card && card.options && card.options.length > 0; const options = use_options && card && card[use_options] && card[use_options].length > 0;
const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0; const warnings = !warnings_disabled && card_show_warnings && card_show_warnings.length > 0;
const resources = !options_disabled && card_counted_resources && card_counted_resources.length > 0; const resources = !warnings_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}>
@ -72,8 +73,6 @@ export function ProductCartItem({card_index, crate_index, first, last}) {
<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

@ -0,0 +1,107 @@
import React from 'react'
import {Draggable} from "@hello-pangea/dnd";
import {compareObjectsEmptiness, productStyle} from "./utils";
import {Resources} from "./Resources";
import {CardWarnings} from "./CardWarnings";
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 card_show_warnings = useShopStore(state => state.crates[crate_index].h_items[card_index].show_warnings, compareObjectsEmptiness);
const card_counted_resources = useShopStore(state => state.crates[crate_index].h_items[card_index].counted_resources, compareObjectsEmptiness);
const highlighted = useShopStore((state) => 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);
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;
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}>
{(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)}
onMouseLeave={removeHighlight}
>
{/* warning container */}
<div className="progress-container warning d-flex justify-content-evenly">
{warnings &&
(<CardWarnings crate_index={crate_index} card_index={card_index} />)
}
{options && (
<OptionsDialogWrapper
crate_index={crate_index}
card_index={card_index}
horizontal={true}
/>
)}
</div>
<h6>{card.name_number}</h6>
<div
onMouseEnter={() => setHighlight(crate_id, card_index)}
onClick={() => setHighlight(crate_id, card_index)}
>
{card.name}
</div>
{/* remove container */}
{/*<div
style={{'display': highlighted ? 'flex' : 'none'}}
className="overlayRemove"
onClick={() => onCardRemove(crate_id, card_index)}>
<img src="/images/shop/icon-remove.svg" alt="rm"/>
<p>Remove</p>
</div>*/}
</div>
)}
</Draggable>
);
}

View File

@ -8,7 +8,7 @@ import {OptionsSummaryWrapper} from "./OptionsWrapper";
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrateCard({crate_index, card_index}) { export function SummaryCrateCard({crate_index, card_index, horizontal}) {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
@ -19,27 +19,33 @@ export function SummaryCrateCard({crate_index, card_index}) {
const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card); const highlighted = useShopStore((state) => 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) => state.crates[crate_index].items[card_index], const card = useShopStore((state) =>
horizontal ? state.crates[crate_index].h_items[card_index] : state.crates[crate_index].items[card_index],
(a, b) => a.id === b.id); (a, b) => a.id === b.id);
const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness); // additional hooks for updating warning and options
const card_options_data = useShopStore(state => state.crates[crate_index].items[card_index].options_data, compareObjectsEmptiness); const card_show_warnings = useShopStore(state =>
const options_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled); horizontal ? state.crates[crate_index].h_items[card_index].show_warnings : state.crates[crate_index].items[card_index].show_warnings,
compareObjectsEmptiness);
const card_options_data = useShopStore(state =>
horizontal ? state.crates[crate_index].h_items[card_index].options_data : state.crates[crate_index].items[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 = !options_disabled && card && card.options && card.options.length > 0; const options = use_options && card && card[use_options] && card[use_options].length > 0;
const options_data = !options_disabled && card_options_data && Object.keys(card_options_data).length > 0; const options_data = card_options_data && Object.keys(card_options_data).length > 0;
const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0; const warnings = !warnings_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)} onClick={() => setHighlight(crate_id, card_index, horizontal)}
onMouseEnter={() => setHighlight(crate_id, card_index)} onMouseEnter={() => setHighlight(crate_id, card_index, horizontal)}
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>

View File

@ -21,10 +21,13 @@ export function validateJSON(description) {
try { try {
for (const crate of crates_raw) { 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) { 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;
@ -50,6 +53,11 @@ 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,
}))); })));
@ -65,12 +73,17 @@ 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.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, type: crate.crate_mode,
options: FilterOptions(crateOptions, crate.options_data) options: FilterOptions(crateOptions, crate.options_data)

View File

@ -1,24 +1,20 @@
import React, {useState} from "react"; import React, {useRef, 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, big, first, last, options_class, export function DialogPopup({options, data, target, id, options_class,
sideMenuIsOpen, displayNotification, onHideNotification}) { sideMenuIsOpen, displayNotification, onHideNotification, horizontal}) {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const ref = useClickAway((e) => { const ref = useRef(null);
if (e.type === "mousedown") // ignore touchstart
setShow(false) 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) => { const handleClick = (_event) => {
setShow(!show); setShow(!show);
}; };
return ( return (<>
<div ref={ref}>
<Notification <Notification
id={"processed_options_notification" + id} id={"processed_options_notification" + id}
tip="Customization options available" tip="Customization options available"
@ -26,12 +22,25 @@ export function DialogPopup({options, data, target, id, big, first, last, option
show={displayNotification} show={displayNotification}
onHide={onHideNotification} onHide={onHideNotification}
content={ content={
<img className="alert-info" <img className="alert-info" ref={ref}
src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"} src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
onClick={handleClick}/> onClick={handleClick}/>
} }
/> />
<div style={{'display': show ? 'flex' : 'none'}} className={div_classes}> <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 <ProcessOptions
options={options} options={options}
data={data} data={data}
@ -40,7 +49,8 @@ export function DialogPopup({options, data, target, id, big, first, last, option
target={target} target={target}
/> />
</div> </div>
</div> )}
); </Overlay>
</>);
} }

View File

@ -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 ipv4or6 = (params) => {
const ipv4Local = ipv4(params); const ipv4Local = ipv4(params);
const ipv6Local = ipv6(params); const ipv6Local = ipv6(params);
@ -47,5 +76,7 @@ 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, true_type_of} from "./options/utils"; import {FillExtCrateData, FillExtOrderData} 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,12 +13,24 @@ 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]
}
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,
@ -33,21 +45,24 @@ 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) => set(state => ({ _showNotification: (crate_id, card_index, horizontal) => 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) => { showNotification: (crate_id, card_index, horizontal) => {
get().hideNotification() get().hideNotification()
setTimeout(() => get()._showNotification(crate_id, card_index), 100); setTimeout(() => get()._showNotification(crate_id, card_index, horizontal), 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,
})) }))
})); }));
@ -211,7 +226,7 @@ const useImportJSON = ((set, get) => ({
get().fillExtCrateData(crate.id); get().fillExtCrateData(crate.id);
}); });
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();
get().showNotification(get().active_crate, null); get().showNotification(get().active_crate, null, false);
}, },
updateImportDescription: (new_description) => set(state => ({ updateImportDescription: (new_description) => set(state => ({
importValue: { importValue: {
@ -328,15 +343,17 @@ 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) => set(state => ({ highlightCard: (crate_id, index, horizontal) => 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()
@ -345,7 +362,8 @@ 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
})), })),
@ -384,17 +402,18 @@ 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) => set(state => { _addCardFromCatalog: (crate_to, index_from, index_to, horizontal) => set(state => {
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item])); const whichH = whichItems(horizontal)
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.items.length; index_to = index_to != null ? index_to : crate[whichH].length;
return { return {
...crate, ...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()} 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 => { _moveCard: (crate_from, index_from, crate_to, index_to, horizontal) => set(state => {
const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from]; const whichH = whichItems(horizontal)
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.items); let items_copy = Array.from(crate[whichH]);
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,
items: items_copy [whichH]: items_copy
} }
} else if (crate_to === crate.id) { } else if (crate_to === crate.id) {
return { return {
...crate, ...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) { } else if (crate_from === crate.id) {
return { return {
...crate, ...crate,
items: crate.items.toSpliced(index_to, 1) [whichH]: crate[whichH].toSpliced(index_to, 1)
} }
} }
else return crate; else return crate;
}) })
} }
}), }),
_deleteCard: (crate_id, index) => set(state => ({ _deleteCard: (crate_id, index, horizontal) => 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,
items: crate.items.toSpliced(index, 1) [whichH]: crate[whichH].toSpliced(index, 1)
} }
} }
else return crate; else return crate;
@ -445,7 +466,8 @@ 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;
@ -454,10 +476,11 @@ const useCart = ((set, get) => ({
clearAll: () => set(state => ({ clearAll: () => set(state => ({
crates: state._defaultCrates 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) => { crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) { 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] = {
...itemsCopy[index], ...itemsCopy[index],
options_data: { options_data: {
@ -466,7 +489,7 @@ const useCart = ((set, get) => ({
}}; }};
return { return {
...crate, ...crate,
items: itemsCopy [whichH]: itemsCopy
} }
} }
else return crate; else return crate;
@ -496,10 +519,11 @@ const useCart = ((set, get) => ({
fillExtData: (crate_id) => set(state => ({ fillExtData: (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) {
const options_name = state.crateParams(crate.crate_mode).options;
let itemsCopy = Array.from(crate.items); let itemsCopy = Array.from(crate.items);
itemsCopy = itemsCopy.map((item, index) => { itemsCopy = itemsCopy.map((item, index) => {
if (!item.options) return item; if (!item[options_name]) 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;
@ -519,7 +543,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.forEach((item, _) => { crate.items.concat(crate.h_items).forEach((item, _) => {
sum += item.price; sum += item.price;
}); });
}); });
@ -557,13 +581,15 @@ const useCart = ((set, get) => ({
}, },
addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => { 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 = toArray(index_from).some(value => !get().getCardDescription(value).image);
const dest = isCrateless ? "spare" : crate_to || 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); get().showNotification(dest, index_to, isHorizontal);
get()._addCardFromCatalog(dest, index_from, index_to) get()._addCardFromCatalog(dest, index_from, index_to)
get().fillExtData(dest); get().fillExtData(dest);
get().fillWarnings(dest); get().fillWarnings(dest);
@ -575,22 +601,25 @@ const useCart = ((set, get) => ({
}, },
moveCard: (crate_from, index_from, crate_to, index_to) => { moveCard: (crate_from, index_from, crate_to, index_to) => {
get()._moveCard(crate_from, index_from, crate_to, index_to); const [isHorizontal, crateFrom] = unwrapCrateId(crate_from)
get().fillExtData(crate_to); const [_, crateTo] = unwrapCrateId(crate_to)
get().fillWarnings(crate_to); get()._moveCard(crateFrom, index_from, crateTo, index_to, isHorizontal);
get().setActiveCrate(crate_to); get().fillExtData(crateTo);
get().fillWarnings(crateTo);
get().setActiveCrate(crateTo);
get()._updateTotalOrderPrice(); get()._updateTotalOrderPrice();
if (crate_from !== crate_to) { if (crateFrom !== crate_to) {
get().fillExtData(crate_from); get().fillExtData(crateFrom);
get().fillWarnings(crate_from); get().fillWarnings(crateFrom);
} }
}, },
deleteCard: (crate_id, index) => { deleteCard: (crate_id, index) => {
get()._deleteCard(crate_id, index); const [isHorizontal, crateId] = unwrapCrateId(crate_id);
get().fillExtData(crate_id); get()._deleteCard(crateId, index, isHorizontal);
get().fillWarnings(crate_id); get().fillExtData(crateId);
get().fillWarnings(crateId);
get()._updateTotalOrderPrice(); 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) => { clearCrate: (id) => {
get()._clearCrate(id); get()._clearCrate(id);
@ -599,7 +628,8 @@ const useCart = ((set, get) => ({
}, },
updateOptions: (crate_id, index, new_options) => { updateOptions: (crate_id, index, new_options) => {
get()._updateOptions(crate_id, index, new_options); const [isHorizontal, crateId] = unwrapCrateId(crate_id);
get()._updateOptions(crate_id, index, new_options, isHorizontal);
get().fillExtData(crate_id); get().fillExtData(crate_id);
get().fillWarnings(crate_id); get().fillWarnings(crate_id);
}, },

View File

@ -6,20 +6,23 @@ 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 cards', name: 'Spare items',
price: 0, price: 0,
hp: -1, hp: -1,
warnings_disabled: true warnings_disabled: true,
options: "crateless_options"
} }
}, },
crateModeOrder: [ crateModeOrder: [
@ -51,8 +54,8 @@ const shop_data = {
title: "Optional pre-installed NUC mini-computer", title: "Optional pre-installed NUC mini-computer",
outvar: "nuc", outvar: "nuc",
tip: "Pre-installed NixOS desktop with ARTIQ and other scientific software. " + tip: "Pre-installed NixOS desktop with ARTIQ and other scientific software. " +
"Hardware: Intel® NUC 13 Pro Kit NUC13ANKi7, i7-1360P CPU, " + "Hardware: ASUS® NUC 14 Pro Kit, Intel® i7-155H CPU, " +
"32GB RAM, 1TB NVMe. Other options contact us.", "32GB RAM, 1TB NVMe. For other options contact us.",
fallback: true, fallback: true,
}}, }},
{ {
@ -1103,10 +1106,24 @@ 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: [
@ -1137,10 +1154,24 @@ 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: [
@ -1226,7 +1257,25 @@ 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: [
@ -1251,6 +1300,12 @@ 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
@ -1302,12 +1357,26 @@ 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",
@ -1374,6 +1443,26 @@ const shop_data = {
hp: 8, hp: 8,
}, },
}, },
'afws': {
id: 'afws',
name: 'Subscription',
name_number: 'AFWS',
name_codename: '',
price: 800,
image: null,
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: {
@ -1431,6 +1520,7 @@ const shop_data = {
'koster', 'koster',
'eem_pwr_mod', 'eem_pwr_mod',
'kirdy', 'kirdy',
'afws',
]} ]}
], ],
}, },
@ -1440,14 +1530,16 @@ 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 cards", name: "Spare items",
crate_mode: "no_crate", crate_mode: "no_crate",
items: [], items: [],
h_items: [],
warnings: [], warnings: [],
occupiedHP: 0, occupiedHP: 0,
} }

View File

@ -53,12 +53,29 @@
<a href="{{ get_url(path='@/experiment-control/place-order.md') }}" class="btn btn-primary btn-inversed btn-lg">Order hardware</a> <a href="{{ get_url(path='@/experiment-control/place-order.md') }}" class="btn btn-primary btn-inversed btn-lg">Order hardware</a>
<a href="https://m-labs.hk/artiq/manual/" class="btn btn-primary btn-lg">Manual</a> <a href="https://m-labs.hk/artiq/manual/" class="btn btn-primary btn-lg">Manual</a>
<a href="https://forum.m-labs.hk" class="btn btn-primary btn-lg">Forum</a> <a href="https://forum.m-labs.hk" class="btn btn-primary btn-lg">Forum</a>
<a href="https://nixbld.m-labs.hk/job/artiq/extra/msys2-offline-installer/latest/download/1" class="btn btn-primary btn-lg d-inline-flex">
<img src="/images/icons/icon-download.svg" class="d-inline-block align-self-center mx-1" style="height: 1rem" alt="download">
Windows installer
</a>
<a href="{{ get_url(path='@/experiment-control/artiq.md') }}" class="btn btn-primary btn-lg">More...</a> <a href="{{ get_url(path='@/experiment-control/artiq.md') }}" class="btn btn-primary btn-lg">More...</a>
<div class="btn-group download-selector">
<a class="btn btn-primary btn-lg d-inline-flex" href="https://nixbld.m-labs.hk/job/artiq/extra/msys2-offline-installer/latest/download/1">
<img src="/images/icons/icon-windows.svg" class="d-inline-block align-self-center mx-1" style="height: 1rem" alt="windows">
<span class="d-inline-block">Download <sup>.exe</sup></span>
</a>
<div class="divider"></div>
<button type="button" class="btn btn-primary btn-lg dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span>stable</span>
</button>
<ul class="dropdown-menu shadow dropdown-menu-end">
<li>
<a class="dropdown-item" href="https://nixbld.m-labs.hk/job/artiq/extra/msys2-offline-installer/latest/download/1">
ARTIQ-8 (stable) for Windows
</a>
</li>
<li>
<a class="dropdown-item" href="https://nixbld.m-labs.hk/job/artiq/extra-beta/msys2-offline-installer/latest/download/1">
ARTIQ-9 (beta) for Windows
</a>
</li>
</ul>
</div>
</div> </div>
</div> </div>