shop: add crateless items and options #143

Open
esavkin wants to merge 5 commits from esavkin/web2019:125-crateless into master
9 changed files with 155 additions and 23 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

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

@ -65,12 +65,13 @@ 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
}))), }))),
type: crate.crate_mode, type: crate.crate_mode,
options: FilterOptions(crateOptions, crate.options_data) options: FilterOptions(crateOptions, crate.options_data)

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";
@ -19,6 +19,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,
@ -385,7 +389,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 {
@ -496,10 +500,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;
@ -557,7 +562,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

@ -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: [
@ -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: '/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: {
@ -1431,6 +1520,7 @@ const shop_data = {
'koster', 'koster',
'eem_pwr_mod', 'eem_pwr_mod',
'kirdy', 'kirdy',
'afws',
]} ]}
], ],
}, },
@ -1445,7 +1535,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: [],