1
0
Fork 0

Compare commits

...

7 Commits

Author SHA1 Message Date
Egor Savkin 2a442064f2 Add AFWS item to the shop
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-17 17:24:03 +08:00
Egor Savkin f54c7144d9 Fix exception on thermostat2ch and build the bundle
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-16 11:36:13 +08:00
Egor Savkin 4c114b1106 Update cards
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-15 17:34:48 +08:00
Egor Savkin 9ee4eb09bc Make tooltip icons inline
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-15 17:18:13 +08:00
Egor Savkin f9355ddb97 Add possibility for crateless options
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-12 17:17:07 +08:00
Egor Savkin 7c70bd706a Automatically open spare cards for crateless items
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-11 17:30:34 +08:00
Egor Savkin 035f9506b3 Remove unused type field from shop_data
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-11 16:32:01 +08:00
13 changed files with 189 additions and 84 deletions

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

@ -5,7 +5,8 @@ import {SummaryPopup} from "./options/SummaryPopup";
export function OptionsDialogWrapper({crate_index, card_index, first, last}) { export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
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_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);
@ -53,7 +54,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

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

View File

@ -6,9 +6,33 @@ import {useShopStore} from "./shop_store";
// #!render_count // #!render_count
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
function DatasheetLink({datasheet_file, datasheet_name}) {
return datasheet_file && datasheet_name && (<div className="ds">
<span className='doc-icon'></span>
<a href={datasheet_file} target="_blank" rel="noopener noreferrer">
{datasheet_name}
</a>
</div>)
}
function CardSpecs({specs}) {
return specs && specs.length > 0 && (<ul>
{specs.map((spec, index) =>
<li key={index}>{spec}</li>
)}
</ul>)
}
function AddButton({onAdd}) {
return <button onClick={onAdd}>
<img src="/images/shop/icon-add.svg" alt="add"/>
</button>
}
/** /**
* Component that renders a product. * Component that renders a product.
* Used in the aside (e.g catalog of product) * Used in the aside (e.g catalog of products)
*/ */
export function ProductItem({card_index}) { export function ProductItem({card_index}) {
// #!render_count // #!render_count
@ -16,33 +40,14 @@ export function ProductItem({card_index}) {
const getCardDescription = useShopStore((state) => state.getCardDescription); const getCardDescription = useShopStore((state) => state.getCardDescription);
const currency = useShopStore((state) => state.currency); const currency = useShopStore((state) => state.currency);
const onAddCard = useShopStore((state) => state.addCardFromCatalog); const addCardFromCatalog = useShopStore((state) => state.addCardFromCatalog);
const card = getCardDescription(card_index); const card = getCardDescription(card_index);
// #!render_count // #!render_count
console.log("ProductItem renders: ", renderCount) console.log("ProductItem renders: ", renderCount)
const render_specs = (card.specs && card.specs.length > 0 && (
<ul>
{card.specs.map((spec, index) =>
<li key={index}>{spec}</li>
)}
</ul>
));
const render_datasheet_link = (card.datasheet_file && card.datasheet_name && (
<div className="ds">
<span className='doc-icon'></span>
<a href={card.datasheet_file} target="_blank" rel="noopener noreferrer">
{card.datasheet_name}
</a>
</div>
));
return ( return (
<section className="productItem"> <section className="productItem">
<div className="content"> <div className="content">
<h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name_number} {card.name}</h3> <h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name_number} {card.name}</h3>
{card.name_codename ? ( {card.name_codename ? (
@ -51,16 +56,13 @@ export function ProductItem({card_index}) {
<div className="price">{`${currency} ${formatMoney(card.price)}`}</div> <div className="price">{`${currency} ${formatMoney(card.price)}`}</div>
{render_specs} <CardSpecs specs={card.specs}/>
{render_datasheet_link} <DatasheetLink datasheet_file={card.datasheet_file} datasheet_name={card.datasheet_name}/>
</div> </div>
<div className="content"> <div className="content">
<AddButton onAdd={() => addCardFromCatalog(null, card_index, null)} />
<button onClick={() => onAddCard(null, card_index, null)}>
<img src="/images/shop/icon-add.svg" alt="add"/>
</button>
<Draggable draggableId={card.id + card_index} index={card_index}> <Draggable draggableId={card.id + card_index} index={card_index}>
{(provided, snapshot) => ( {(provided, snapshot) => (
@ -83,10 +85,7 @@ export function ProductItem({card_index}) {
</React.Fragment> </React.Fragment>
)} )}
</Draggable> </Draggable>
</div> </div>
</section> </section>
); );
} }

View File

@ -23,16 +23,16 @@ export function SummaryCrateCard({crate_index, 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); 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 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); 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

View File

@ -12,7 +12,7 @@ export function DialogPopup({options, data, target, id, big, first, last, option
} }
); );
let div_classes = `overlayVariant border rounded ${big ? "overlay-bigcard" : "overlay-smallcard"} ${(!big && first) ? "overlay-first" : ""} ${(!big && last) ? "overlay-last" : ""} ${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);
}; };

View File

@ -39,9 +39,10 @@ class Line extends Component {
<div className="shop-line" key={this.props.id}> <div className="shop-line" key={this.props.id}>
<label htmlFor={key} className="form-label"> <label htmlFor={key} className="form-label">
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>} {this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title}: {this.props.title}
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
:
</label> </label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key} onChange={this.handleChange} <input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key} onChange={this.handleChange}
value={this.state.text}/> value={this.state.text}/>
</div> </div>

View File

@ -49,8 +49,8 @@ class Switch extends Component {
<label className="form-check-label" htmlFor={key} style={{"display": "inline"}}> <label className="form-check-label" htmlFor={key} style={{"display": "inline"}}>
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>} {this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title} {this.props.title}
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</label> </label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</div> </div>
</div> </div>
); );

View File

@ -65,8 +65,8 @@ class SwitchLine extends Component {
<label className="form-check-label" htmlFor={key + "switch"}> <label className="form-check-label" htmlFor={key + "switch"}>
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>} {this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title} {this.props.title}
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</label> </label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</div> </div>
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key + "line"} onChange={this.handleText} <input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key + "line"} onChange={this.handleText}
value={this.state.text} disabled={!this.state.checked}/> value={this.state.text} disabled={!this.state.checked}/>

View File

@ -16,6 +16,36 @@ 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) {
return false;
}
if (!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 +77,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} from "./utils"; import {data as shared_data, itemsUnfoldedList} 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";
@ -18,6 +18,10 @@ const cards_to_pn_map = (cards) => {
return result; return result;
}; };
const toArray = (arg) => {
return Array.isArray(arg) ? arg : [arg];
};
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,
@ -384,7 +388,7 @@ 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) => set(state => {
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item])); const take_from = toArray(index_from).map((item, _i) => (state.cards_list[item]));
const dest = crate_to || state.active_crate; const dest = crate_to || state.active_crate;
if (!dest) return {}; if (!dest) return {};
return { return {
@ -495,10 +499,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;
@ -555,7 +560,8 @@ 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 dest = isCrateless ? "spare" : crate_to || get().active_crate;
if (!dest) { if (!dest) {
console.warn("No destination"); console.warn("No destination");
get().noDestinationWarning(); get().noDestinationWarning();

View File

@ -8,20 +8,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: [
@ -181,7 +184,6 @@ const shop_data = {
'Price includes bitstream generation, flashing, testing, and firmware updates for 1 year (USD 1,400.00).', 'Price includes bitstream generation, flashing, testing, and firmware updates for 1 year (USD 1,400.00).',
], ],
size: 'big', size: 'big',
type: 'kasli',
options: [ options: [
{type: "Radio", args: {title: "DRTIO role", outvar: "drtio_role", variants: ["standalone", "master", "satellite"], tip: "Distributed Real Time Input/Output allows ARTIQ RTIO channels to be distributed among several satellite devices synchronized and controlled by a central core(master) device. Standalone option disables this feature."}}, {type: "Radio", args: {title: "DRTIO role", outvar: "drtio_role", variants: ["standalone", "master", "satellite"], tip: "Distributed Real Time Input/Output allows ARTIQ RTIO channels to be distributed among several satellite devices synchronized and controlled by a central core(master) device. Standalone option disables this feature."}},
{ {
@ -246,7 +248,6 @@ const shop_data = {
'4 MMCX clock outputs.', '4 MMCX clock outputs.',
], ],
size: 'big', size: 'big',
type: 'kasli',
hp: 8, hp: 8,
nbrSlotMin: 0, nbrSlotMin: 0,
nbrSlotMax: 12, nbrSlotMax: 12,
@ -318,7 +319,6 @@ const shop_data = {
'A pair of VHDCI carriers is a simple, low-latency and low-cost alternative to DRTIO for some applications.', 'A pair of VHDCI carriers is a simple, low-latency and low-cost alternative to DRTIO for some applications.',
], ],
size: 'big', size: 'big',
type: 'vhdcicarrier',
resources: [ resources: [
{name: "eem", max: 8}, {name: "eem", max: 8},
], ],
@ -349,7 +349,6 @@ const shop_data = {
datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf', datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf',
datasheet_name: '2118/2128 BNC/SMA-TTL datasheet', datasheet_name: '2118/2128 BNC/SMA-TTL datasheet',
size: 'big', size: 'big',
type: null,
options: [ options: [
{ {
"if": [ "if": [
@ -459,7 +458,6 @@ const shop_data = {
} }
], ],
size: 'small', size: 'small',
type: null,
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -551,7 +549,6 @@ const shop_data = {
} }
], ],
size: 'small', size: 'small',
type: null,
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -638,7 +635,6 @@ const shop_data = {
} }
], ],
size: 'small', size: 'small',
type: null,
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -701,7 +697,6 @@ const shop_data = {
} }
], ],
size: 'small', size: 'small',
type: 'urukul',
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source" "no_clk_source"
@ -739,7 +734,6 @@ const shop_data = {
fallback: {text: "125 MHz", checked: false}}} fallback: {text: "125 MHz", checked: false}}}
], ],
size: 'small', size: 'small',
type: 'urukul',
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source" "no_clk_source"
@ -773,7 +767,6 @@ const shop_data = {
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["Baseband", "Upconverter"], fallback: 1}}, {type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["Baseband", "Upconverter"], fallback: 1}},
], ],
size: 'small', size: 'small',
type: 'urukul',
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source" "no_clk_source"
@ -816,7 +809,6 @@ const shop_data = {
datasheet_file: '/docs/sinara-datasheets/5432.pdf', datasheet_file: '/docs/sinara-datasheets/5432.pdf',
datasheet_name: '5432 Zotino datasheet', datasheet_name: '5432 Zotino datasheet',
size: 'small', size: 'small',
type: 'zotino',
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"idc_resource" "idc_resource"
@ -846,7 +838,6 @@ const shop_data = {
'Channels can also be broken out to BNC or SMA using IDC-BNC, IDC-SMA or IDC-MCX cards.' 'Channels can also be broken out to BNC or SMA using IDC-BNC, IDC-SMA or IDC-MCX cards.'
], ],
size: 'small', size: 'small',
type: 'zotino',
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"idc_resource", "idc_resource",
@ -872,7 +863,6 @@ const shop_data = {
'Breaking out all 32 channels from a Zotino requires 4 IDC-BNC cards.' 'Breaking out all 32 channels from a Zotino requires 4 IDC-BNC cards.'
], ],
size: 'big', size: 'big',
type: 'idc-bnc',
warnings: [ warnings: [
"no_idc_source" "no_idc_source"
], ],
@ -931,7 +921,6 @@ const shop_data = {
'Breaking out all 32 channels from a Zotino requires 4 SMA-IDC cards.' 'Breaking out all 32 channels from a Zotino requires 4 SMA-IDC cards.'
], ],
size: 'small', size: 'small',
type: 'idc-bnc',
warnings: [ warnings: [
"no_idc_source" "no_idc_source"
], ],
@ -951,7 +940,6 @@ const shop_data = {
'Connects an external HD68 cable to IDC-BNC, IDC-SMA or IDC-MCX cards.', 'Connects an external HD68 cable to IDC-BNC, IDC-SMA or IDC-MCX cards.',
], ],
size: 'small', size: 'small',
type: 'hd68',
options: [ options: [
{type: "Radio", args: {title: "Cable length", outvar: "hd68_cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}}, {type: "Radio", args: {title: "Cable length", outvar: "hd68_cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
], ],
@ -1008,7 +996,6 @@ const shop_data = {
} }
], ],
size: 'big', size: 'big',
type: 'novo',
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -1035,7 +1022,6 @@ const shop_data = {
{type: "Radio", args: {title: "Connectors", outvar: "n_eem", variants: ["1 EEM", "2 EEM", "3 EEM"], tip: "Number of EEM ports to use.", fallback: 1}}, {type: "Radio", args: {title: "Connectors", outvar: "n_eem", variants: ["1 EEM", "2 EEM", "3 EEM"], tip: "Number of EEM ports to use.", fallback: 1}},
], ],
size: 'small', size: 'small',
type: 'koster',
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -1064,7 +1050,6 @@ const shop_data = {
], ],
options_class: "clocker", options_class: "clocker",
size: 'small', size: 'small',
type: 'clocker',
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source", "no_clk_source",
@ -1099,13 +1084,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: "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',
type: null,
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -1134,13 +1132,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: "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',
type: null,
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -1170,7 +1181,6 @@ const shop_data = {
fallback: {text: "125 MHz", checked: false}}} fallback: {text: "125 MHz", checked: false}}}
], ],
size: 'small', size: 'small',
type: null,
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source" "no_clk_source"
@ -1199,7 +1209,6 @@ const shop_data = {
fallback: {text: "125 MHz", checked: false}}} fallback: {text: "125 MHz", checked: false}}}
], ],
size: 'big', size: 'big',
type: null,
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source" "no_clk_source"
@ -1226,10 +1235,27 @@ 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',
type: null,
warnings: [ warnings: [
"no_eem_source" "no_eem_source"
], ],
@ -1252,8 +1278,13 @@ 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',
type: null,
consumes: { consumes: {
hp: 4 hp: 4
}, },
@ -1277,7 +1308,6 @@ const shop_data = {
'Included remote analog front-end (AFE) board converts differential signals to ±10V single-ended at the point of use, with additional gain and filtering.', 'Included remote analog front-end (AFE) board converts differential signals to ±10V single-ended at the point of use, with additional gain and filtering.',
], ],
size: 'big', size: 'big',
type: null,
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source" "no_clk_source"
@ -1305,14 +1335,27 @@ 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',
type: null,
warnings: [ warnings: [
"no_eem_source", "no_eem_source",
"no_clk_source" "no_clk_source"
@ -1342,12 +1385,31 @@ const shop_data = {
"Optional - external power brick will be shipped free of charge if removed." "Optional - external power brick will be shipped free of charge if removed."
], ],
size: 'big', size: 'big',
type: null,
warnings: [], warnings: [],
consumes: { consumes: {
hp: 4, hp: 4,
}, },
}, },
'afws': {
id: 'afws',
name: 'Subscription',
name_number: 'AFWS',
name_codename: '',
price: 800,
image: '/images/shop/graphic-03_AFWS.svg',
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: {
@ -1403,6 +1465,7 @@ const shop_data = {
itemIds: [ itemIds: [
'koster', 'koster',
'eem_pwr_mod', 'eem_pwr_mod',
'afws',
]} ]}
], ],
}, },
@ -1417,7 +1480,7 @@ const shop_data = {
}, },
{ {
id: "spare", id: "spare",
name: "Spare cards", name: "Spare items",
crate_mode: "no_crate", crate_mode: "no_crate",
items: [], items: [],
warnings: [], warnings: [],