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}) {
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 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);
@ -53,7 +54,8 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
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 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);
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 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 setHighlight = useShopStore((state) => state.highlightCard);
const removeHighlight = useShopStore((state) => state.highlightReset);
@ -35,9 +36,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}>

View File

@ -6,9 +6,33 @@ import {useShopStore} from "./shop_store";
// #!render_count
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.
* Used in the aside (e.g catalog of product)
* Used in the aside (e.g catalog of products)
*/
export function ProductItem({card_index}) {
// #!render_count
@ -16,33 +40,14 @@ export function ProductItem({card_index}) {
const getCardDescription = useShopStore((state) => state.getCardDescription);
const currency = useShopStore((state) => state.currency);
const onAddCard = useShopStore((state) => state.addCardFromCatalog);
const addCardFromCatalog = useShopStore((state) => state.addCardFromCatalog);
const card = getCardDescription(card_index);
// #!render_count
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 (
<section className="productItem">
<div className="content">
<h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name_number} {card.name}</h3>
{card.name_codename ? (
@ -51,16 +56,13 @@ export function ProductItem({card_index}) {
<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 className="content">
<button onClick={() => onAddCard(null, card_index, null)}>
<img src="/images/shop/icon-add.svg" alt="add"/>
</button>
<AddButton onAdd={() => addCardFromCatalog(null, card_index, null)} />
<Draggable draggableId={card.id + card_index} index={card_index}>
{(provided, snapshot) => (
@ -83,10 +85,7 @@ export function ProductItem({card_index}) {
</React.Fragment>
)}
</Draggable>
</div>
</section>
);
}

View File

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

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) => {
setShow(!show);
};

View File

@ -39,9 +39,10 @@ class Line extends Component {
<div className="shop-line" key={this.props.id}>
<label htmlFor={key} className="form-label">
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title}:
{this.props.title}
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
:
</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}
value={this.state.text}/>
</div>

View File

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

View File

@ -65,8 +65,8 @@ class SwitchLine extends Component {
<label className="form-check-label" htmlFor={key + "switch"}>
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
{this.props.title}
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</label>
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
</div>
<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}/>

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 ipv4Local = ipv4(params);
const ipv6Local = ipv6(params);
@ -47,5 +77,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} 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";
@ -18,6 +18,10 @@ const cards_to_pn_map = (cards) => {
return result;
};
const toArray = (arg) => {
return Array.isArray(arg) ? arg : [arg];
};
const useCatalog = ((set, get) => ({
cards: shared_data.items,
groups: shared_data.columns.catalog,
@ -384,7 +388,7 @@ 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]));
const take_from = toArray(index_from).map((item, _i) => (state.cards_list[item]));
const dest = crate_to || state.active_crate;
if (!dest) return {};
return {
@ -495,10 +499,11 @@ const useCart = ((set, get) => ({
fillExtData: (crate_id) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) {
const options_name = state.crateParams(crate.crate_mode).options;
let itemsCopy = Array.from(crate.items);
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;
@ -555,7 +560,8 @@ 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 dest = isCrateless ? "spare" : crate_to || get().active_crate;
if (!dest) {
console.warn("No destination");
get().noDestinationWarning();

View File

@ -8,20 +8,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: [
@ -181,7 +184,6 @@ const shop_data = {
'Price includes bitstream generation, flashing, testing, and firmware updates for 1 year (USD 1,400.00).',
],
size: 'big',
type: 'kasli',
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."}},
{
@ -246,7 +248,6 @@ const shop_data = {
'4 MMCX clock outputs.',
],
size: 'big',
type: 'kasli',
hp: 8,
nbrSlotMin: 0,
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.',
],
size: 'big',
type: 'vhdcicarrier',
resources: [
{name: "eem", max: 8},
],
@ -349,7 +349,6 @@ const shop_data = {
datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf',
datasheet_name: '2118/2128 BNC/SMA-TTL datasheet',
size: 'big',
type: null,
options: [
{
"if": [
@ -459,7 +458,6 @@ const shop_data = {
}
],
size: 'small',
type: null,
warnings: [
"no_eem_source"
],
@ -551,7 +549,6 @@ const shop_data = {
}
],
size: 'small',
type: null,
warnings: [
"no_eem_source"
],
@ -638,7 +635,6 @@ const shop_data = {
}
],
size: 'small',
type: null,
warnings: [
"no_eem_source"
],
@ -701,7 +697,6 @@ const shop_data = {
}
],
size: 'small',
type: 'urukul',
warnings: [
"no_eem_source",
"no_clk_source"
@ -739,7 +734,6 @@ const shop_data = {
fallback: {text: "125 MHz", checked: false}}}
],
size: 'small',
type: 'urukul',
warnings: [
"no_eem_source",
"no_clk_source"
@ -773,7 +767,6 @@ const shop_data = {
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["Baseband", "Upconverter"], fallback: 1}},
],
size: 'small',
type: 'urukul',
warnings: [
"no_eem_source",
"no_clk_source"
@ -816,7 +809,6 @@ const shop_data = {
datasheet_file: '/docs/sinara-datasheets/5432.pdf',
datasheet_name: '5432 Zotino datasheet',
size: 'small',
type: 'zotino',
warnings: [
"no_eem_source",
"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.'
],
size: 'small',
type: 'zotino',
warnings: [
"no_eem_source",
"idc_resource",
@ -872,7 +863,6 @@ const shop_data = {
'Breaking out all 32 channels from a Zotino requires 4 IDC-BNC cards.'
],
size: 'big',
type: 'idc-bnc',
warnings: [
"no_idc_source"
],
@ -931,7 +921,6 @@ const shop_data = {
'Breaking out all 32 channels from a Zotino requires 4 SMA-IDC cards.'
],
size: 'small',
type: 'idc-bnc',
warnings: [
"no_idc_source"
],
@ -951,7 +940,6 @@ const shop_data = {
'Connects an external HD68 cable to IDC-BNC, IDC-SMA or IDC-MCX cards.',
],
size: 'small',
type: 'hd68',
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}},
],
@ -1008,7 +996,6 @@ const shop_data = {
}
],
size: 'big',
type: 'novo',
warnings: [
"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}},
],
size: 'small',
type: 'koster',
warnings: [
"no_eem_source"
],
@ -1064,7 +1050,6 @@ const shop_data = {
],
options_class: "clocker",
size: 'small',
type: 'clocker',
warnings: [
"no_eem_source",
"no_clk_source",
@ -1099,13 +1084,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: "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',
type: null,
warnings: [
"no_eem_source"
],
@ -1134,13 +1132,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: "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',
type: null,
warnings: [
"no_eem_source"
],
@ -1170,7 +1181,6 @@ const shop_data = {
fallback: {text: "125 MHz", checked: false}}}
],
size: 'small',
type: null,
warnings: [
"no_eem_source",
"no_clk_source"
@ -1199,7 +1209,6 @@ const shop_data = {
fallback: {text: "125 MHz", checked: false}}}
],
size: 'big',
type: null,
warnings: [
"no_eem_source",
"no_clk_source"
@ -1226,10 +1235,27 @@ 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',
type: null,
warnings: [
"no_eem_source"
],
@ -1252,8 +1278,13 @@ 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',
type: null,
consumes: {
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.',
],
size: 'big',
type: null,
warnings: [
"no_eem_source",
"no_clk_source"
@ -1305,14 +1335,27 @@ 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',
type: null,
warnings: [
"no_eem_source",
"no_clk_source"
@ -1342,12 +1385,31 @@ const shop_data = {
"Optional - external power brick will be shipped free of charge if removed."
],
size: 'big',
type: null,
warnings: [],
consumes: {
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: {
@ -1403,6 +1465,7 @@ const shop_data = {
itemIds: [
'koster',
'eem_pwr_mod',
'afws',
]}
],
},
@ -1417,7 +1480,7 @@ const shop_data = {
},
{
id: "spare",
name: "Spare cards",
name: "Spare items",
crate_mode: "no_crate",
items: [],
warnings: [],