From 4ac521db7f93b4b279a9a18e3e7d9c6e6e3f88ed Mon Sep 17 00:00:00 2001 From: Egor Savkin Date: Thu, 11 Jul 2024 17:30:34 +0800 Subject: [PATCH 1/5] Automatically open spare cards for crateless items Signed-off-by: Egor Savkin --- static/js/shop/shop_store.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/static/js/shop/shop_store.js b/static/js/shop/shop_store.js index 1e5c267..78e189b 100644 --- a/static/js/shop/shop_store.js +++ b/static/js/shop/shop_store.js @@ -2,7 +2,7 @@ import {createWithEqualityFn} from "zustand/traditional"; import {DATA as shared_data, itemsUnfoldedList, API_RFQ} from "./utils"; -import {FillExtCrateData, FillExtOrderData, true_type_of} from "./options/utils"; +import {FillExtCrateData, FillExtOrderData} from "./options/utils"; import {v4 as uuidv4} from "uuid"; import {FillResources} from "./count_resources"; import {FillExtCardData} from "./options/utils"; @@ -19,6 +19,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, @@ -385,7 +389,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 { @@ -557,7 +561,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(); -- 2.44.1 From 4acf38ba13013e98eb4d728c5d5e26f9241867ff Mon Sep 17 00:00:00 2001 From: Egor Savkin Date: Fri, 12 Jul 2024 17:17:07 +0800 Subject: [PATCH 2/5] Add possibility for crateless options Signed-off-by: Egor Savkin --- static/js/shop/OptionsWrapper.jsx | 6 ++++-- static/js/shop/ProductCartItem.jsx | 9 +++++---- static/js/shop/SummaryCrateCard.jsx | 10 +++++----- static/js/shop_data.js | 9 ++++++--- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/static/js/shop/OptionsWrapper.jsx b/static/js/shop/OptionsWrapper.jsx index 73326c2..5a2103c 100644 --- a/static/js/shop/OptionsWrapper.jsx +++ b/static/js/shop/OptionsWrapper.jsx @@ -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 ( diff --git a/static/js/shop/ProductCartItem.jsx b/static/js/shop/ProductCartItem.jsx index 39b7fc7..559e692 100644 --- a/static/js/shop/ProductCartItem.jsx +++ b/static/js/shop/ProductCartItem.jsx @@ -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 ( diff --git a/static/js/shop/SummaryCrateCard.jsx b/static/js/shop/SummaryCrateCard.jsx index 3edc55d..00c5658 100644 --- a/static/js/shop/SummaryCrateCard.jsx +++ b/static/js/shop/SummaryCrateCard.jsx @@ -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 ( Date: Mon, 15 Jul 2024 17:34:48 +0800 Subject: [PATCH 3/5] Update cards Signed-off-by: Egor Savkin --- static/js/shop/options/validation.js | 32 +++++++++++++ static/js/shop_data.js | 68 +++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/static/js/shop/options/validation.js b/static/js/shop/options/validation.js index d0ee431..9fd1ab3 100644 --- a/static/js/shop/options/validation.js +++ b/static/js/shop/options/validation.js @@ -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 }; \ No newline at end of file diff --git a/static/js/shop_data.js b/static/js/shop_data.js index ff76ca6..383bef7 100644 --- a/static/js/shop_data.js +++ b/static/js/shop_data.js @@ -1106,10 +1106,24 @@ const shop_data = { validator: {name: "ipv4or6"}, fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, {type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}, {type: "Switch", args: {title: "Term #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}}, {type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}} ], + crateless_options: [ + {type: "SwitchLine", args: {title: "IP", outvar: "ip", + validator: {name: "ipv4or6"}, + fallback: {text: "DHCP", checked: false}, + tip: "Set up IP address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, + ], options_class: "stabilizer", size: 'small', warnings: [ @@ -1140,10 +1154,24 @@ const shop_data = { validator: {name: "ipv4or6"}, fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, {type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}, {type: "Switch", args: {title: "Term #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}}, {type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}} ], + crateless_options: [ + {type: "SwitchLine", args: {title: "IP", outvar: "ip", + validator: {name: "ipv4or6"}, + fallback: {text: "DHCP", checked: false}, + tip: "Set up IP address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, + ], options_class: "stabilizer", size: 'small', warnings: [ @@ -1229,7 +1257,25 @@ const shop_data = { '100Base-T Ethernet with PoE.' ], options: [ - {type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}} + {type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}, + {type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip", + validator: {name: "ipv4"}, + fallback: {text: "0.0.0.0", checked: false}, + tip: "Set up static IPv4 address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, + ], + crateless_options: [ + {type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip", + validator: {name: "ipv4"}, + fallback: {text: "0.0.0.0", checked: false}, + tip: "Set up static IPv4 address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, ], size: 'small', warnings: [ @@ -1254,6 +1300,12 @@ const shop_data = { '100Base-T Ethernet with PoE.', 'Can stabilize temperature of Sinara 5432 DAC or external devices containing TEC and thermistor.' ], + crateless_options: [ + {type: "SwitchLine", args: {title: "IPv4", outvar: "ip", + validator: {name: "ipv4"}, + fallback: {text: "192.168.1.26/24", checked: false}, + tip: "Set up IP address used by the device"}}, + ], size: 'small', consumes: { hp: 4 @@ -1305,12 +1357,26 @@ const shop_data = { validator: {name: "ipv4or6"}, fallback: {text: "DHCP", checked: false}, tip: "Set up IP address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, {type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}, {type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk", fallback: {text: "125 MHz", checked: false}, validator: {name: "frequency", params: {min: 10e6, max: 1e9}}}}, {type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}}, {type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}} ], + crateless_options: [ + {type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip", + validator: {name: "ipv4"}, + fallback: {text: "0.0.0.0", checked: false}, + tip: "Set up static IPv4 address used by the device"}}, + {type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker", + validator: {name: "ipv4OrHost"}, + fallback: {text: "mqtt", checked: false}, + tip: "Set up domain name or IPv4 of the MQTT broker"}}, + ], size: 'big', warnings: [ "no_eem_source", -- 2.44.1 From 60f8574a8c80c39e857409a72157d13d953e8d73 Mon Sep 17 00:00:00 2001 From: Egor Savkin Date: Tue, 16 Jul 2024 11:36:13 +0800 Subject: [PATCH 4/5] Fix exception on thermostat2ch and build the bundle Signed-off-by: Egor Savkin --- static/js/shop/shop_store.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/js/shop/shop_store.js b/static/js/shop/shop_store.js index 78e189b..3bddac2 100644 --- a/static/js/shop/shop_store.js +++ b/static/js/shop/shop_store.js @@ -500,10 +500,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; -- 2.44.1 From 5e5246e8ae58aea7577aa5dbca5c935f1938dd16 Mon Sep 17 00:00:00 2001 From: Egor Savkin Date: Mon, 5 Aug 2024 10:26:56 +0800 Subject: [PATCH 5/5] Add AFWS item to the shop and update bundle Signed-off-by: Egor Savkin --- static/images/shop/graphic-03_AFWS.svg | 1 + static/js/shop.bundle.js | 2 +- static/js/shop/json_porter.js | 3 ++- static/js/shop/options/validation.js | 7 +++---- static/js/shop_data.js | 25 +++++++++++++++++++++++-- 5 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 static/images/shop/graphic-03_AFWS.svg diff --git a/static/images/shop/graphic-03_AFWS.svg b/static/images/shop/graphic-03_AFWS.svg new file mode 100644 index 0000000..b7dc2b2 --- /dev/null +++ b/static/images/shop/graphic-03_AFWS.svg @@ -0,0 +1 @@ + diff --git a/static/js/shop.bundle.js b/static/js/shop.bundle.js index ce792f7..2364b20 100644 --- a/static/js/shop.bundle.js +++ b/static/js/shop.bundle.js @@ -1,2 +1,2 @@ /*! For license information please see shop.bundle.js.LICENSE.txt */ -(()=>{var e={146:(e,t,n)=>{"use strict";var r=n(363),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},a={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||o}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var o=f(n);o&&o!==m&&e(t,o,r)}var i=u(n);d&&(i=i.concat(d(n)));for(var l=s(t),g=s(n),h=0;h{"use strict";e.exports=function(e,t,n,r,o,a,i,l){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,o,a,i,l],u=0;(s=new Error(t.replace(/%s/g,(function(){return c[u++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},678:function(e,t,n){var r,o;r=function(){"use strict";Array.isArray||(Array.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)});var e={},t={"==":function(e,t){return e==t},"===":function(e,t){return e===t},"!=":function(e,t){return e!=t},"!==":function(e,t){return e!==t},">":function(e,t){return e>t},">=":function(e,t){return e>=t},"<":function(e,t,n){return void 0===n?e=t?[]:r}};return e.is_logic=function(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)&&1===Object.keys(e).length},e.truthy=function(e){return!(Array.isArray(e)&&0===e.length||!e)},e.get_operator=function(e){return Object.keys(e)[0]},e.get_values=function(t){return t[e.get_operator(t)]},e.apply=function(n,r){if(Array.isArray(n))return n.map((function(t){return e.apply(t,r)}));if(!e.is_logic(n))return n;var o,a,i,l,s,c=e.get_operator(n),u=n[c];if(Array.isArray(u)||(u=[u]),"if"===c||"?:"==c){for(o=0;o0){var d=String(c).split("."),p=t;for(o=0;o{"use strict";var r=n(925);function o(){}function a(){}a.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,a,i){if(i!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:a,resetWarningCache:o};return n.PropTypes=n,n}},556:(e,t,n)=>{e.exports=n(694)()},925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},551:(e,t,n)=>{"use strict";var r=n(540),o=n(982);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n