+
{card.name}
+ {card.name_codename ? (
+
{card.name_codename}
+ ) : null}
- const render_datasheet_link = (datasheet_file && datasheet_name && (
-
-
-
- {datasheet_name}
-
+
{`${currency} ${formatMoney(card.price)}`}
+
+ {render_specs}
+
+ {render_datasheet_link}
- ));
- return (
-
+
-
-
{name}
- {name_codename ? (
-
{name_codename}
- ) : null }
+
-
{`${currency} ${formatMoney(price)}`}
-
- {render_specs}
-
- {render_datasheet_link}
-
-
-
+ {/* Allows to simulate a clone */}
+ {snapshot.isDragging && (
+
+ )}
+
+ )}
+
+
+
+
+
+ );
-
- );
- }
}
\ No newline at end of file
diff --git a/static/js/shop/Shop.jsx b/static/js/shop/Shop.jsx
index d3c12f4..10e02f2 100644
--- a/static/js/shop/Shop.jsx
+++ b/static/js/shop/Shop.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useEffect} from 'react';
import {DragDropContext} from "@hello-pangea/dnd";
@@ -12,28 +12,13 @@ import {useShopStore} from "./shop_store";
*/
export function Shop() {
- const {addCardFromBacklog, moveCard, deleteCard} = useShopStore(state => ({
+ const {addCardFromBacklog, moveCard, deleteCard, cardIndexById} = useShopStore(state => ({
addCardFromBacklog: state.addCardFromBacklog,
moveCard: state.moveCard,
- deleteCard: state.deleteCard
+ deleteCard: state.deleteCard,
+ cardIndexById: state.cardIndexById
}));
const handleOnDragEnd = (drop_result, provided) => {
- console.log(drop_result, provided)
- //{
- // "draggableId": "42dc17e9-9e75-45ee-ad27-2233b6f07a5e",
- // "type": "DEFAULT",
- // "source": {
- // "index": 17,
- // "droppableId": "backlog"
- // },
- // "reason": "DROP",
- // "mode": "FLUID",
- // "destination": {
- // "droppableId": "crate0",
- // "index": 0
- // },
- // "combine": null
- // }
if (drop_result.source.droppableId === "backlog")
addCardFromBacklog(drop_result.destination.droppableId, drop_result.source.index, drop_result.destination.index);
else if(drop_result.destination.droppableId === "backlog")
@@ -42,6 +27,10 @@ export function Shop() {
moveCard(drop_result.source.droppableId, drop_result.source.index, drop_result.destination.droppableId, drop_result.destination.index)
}
+ useEffect(() => {
+ addCardFromBacklog(null, [cardIndexById("kasli"), cardIndexById("eem_pwr_mod")], -1);
+ }, []);
+
return (
({
- cards: data.items,
- groups: data.columns.backlog,
+ cards: shared_data.items,
+ groups: shared_data.columns.backlog,
cards_list: itemsUnfoldedList,
- currency: data.currency
+ currency: shared_data.currency,
+ getCardDescription: index => get().cards[get().cards_list[index]],
+ cardIndexById: card_id => get().cards_list.findIndex((element) => (card_id === element))
}));
const useCrateModes = ((set, get) => ({
- crate_modes: data.crateModes,
- modes_order: data.crateModeOrder
+ crate_modes: shared_data.crateModes,
+ modes_order: shared_data.crateModeOrder,
+ crateParams: mode => get().crate_modes[mode],
}));
const useLayout = ((set, get) => ({
@@ -47,25 +50,41 @@ const useSubmitForm = ((set, get) => ({
isProcessingComplete: true,
}));
-const useCart = ((set, get) => ({
- crates: data.columns.crates,
- active_crate: "crate0",
+const useHighlighted = ((set, get) => ({
highlighted: {
crate: "",
card: 0
},
+ highlightCard: (crate_id, index) => set(state => ({
+ highlighted: {
+ crate: crate_id,
+ card: index
+ }
+ })),
+ highlightReset: () => set(state => ({
+ highlighted: {
+ crate: "",
+ card: 0
+ }
+ })),
+}));
- newCrate: () => set((state) => ({crates: state.crates.concat({
- id: "crate"+state.crates.length,
+const useCart = ((set, get) => ({
+ crates: shared_data.columns.crates,
+ active_crate: "crate0",
+
+ _newCrate: (crate_id) => set((state) => ({crates: state.crates.concat({
+ id: crate_id || "crate" + state.crates.length,
crate_mode: "rack",
items: [],
- warnings: []
+ warnings: [],
+ occupiedHP: 0
})})),
delCrate: (id) => set(state => ({
crates: state.crates.filter((crate => crate.id !== id))
})),
- setCrateMode: (id, mode) => set(state => ({
+ _setCrateMode: (id, mode) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate.id === id) {
return {
@@ -76,12 +95,13 @@ const useCart = ((set, get) => ({
})
})),
setActiveCrate: (id) => set(state => ({active_crate: id})),
- addCardFromBacklog: (crate_to, index_from, index_to) => set(state => {
+ _addCardFromBacklog: (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 dest = crate_to || state.active_crate;
return {
crates: state.crates.map((crate, _i) => {
if (dest === crate.id) {
+ index_to = index_to != null ? index_to : crate.items.length;
return {
...crate,
items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => {
@@ -92,7 +112,8 @@ const useCart = ((set, get) => ({
})
}
}),
- moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
+ _moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
+ console.log(crate_from, index_from, crate_to, index_to)
const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from];
return {
crates: state.crates.map((crate, _i) => {
@@ -102,7 +123,7 @@ const useCart = ((set, get) => ({
delete items_copy[index_from];
return {
...crate,
- items: items_copy.toSpliced(index_to, 0, the_card).filter((item, _) => !!item)
+ items: items_copy.toSpliced(index_to+1, 0, the_card).filter((item, _) => !!item)
}
} else if (crate_to === crate.id) {
return {
@@ -119,18 +140,18 @@ const useCart = ((set, get) => ({
})
}
}),
- deleteCard: (crate, index) => set(state => ({
+ _deleteCard: (crate_id, index) => set(state => ({
crates: state.crates.map((crate, _i) => {
- if (crate === crate.id) {
+ if (crate_id === crate.id) {
return {
...crate,
- items: crate.items.splice(index, 1)
+ items: crate.items.toSpliced(index, 1)
}
}
else return crate;
})
})),
- clearCrate: (id) => set(state => ({
+ _clearCrate: (id) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (id === crate.id) {
return {
@@ -141,9 +162,12 @@ const useCart = ((set, get) => ({
else return crate;
})
})),
- updateOptions: (crate, index, new_options) => set(state => ({
+ clearAll: () => set(state => ({
+ crates: []
+ })),
+ _updateOptions: (crate_id, index, new_options) => set(state => ({
crates: state.crates.map((crate, _i) => {
- if (crate === crate.id) {
+ if (crate_id === crate.id) {
let itemsCopy = Array.from(crate.items);
itemsCopy[index].options_data = {...itemsCopy[index].options_data, ...new_options};
return {
@@ -154,37 +178,74 @@ const useCart = ((set, get) => ({
else return crate;
})
})),
- highlightCard: (crate, index) => set(state => ({
- highlighted: {
- crate: crate,
- card: index
- }
- })),
- highlightReset: () => set(state => ({
- highlighted: {
- crate: "",
- card: 0
- }
- })),
- fillWarnings: (crate) => set(state => ({
- // actually seems to be just render-time action, no need to put data in it,
- // though needs to be optimized to be done only on real crate updates
+ fillWarnings: (crate_id) => set(state => ({
crates: state.crates.map((crate, _i) => {
- if (crate === crate.id) {
+ if (crate_id === crate.id) {
let itemsCopy = Array.from(crate.items);
itemsCopy = FillResources(itemsCopy);
itemsCopy = TriggerWarnings(itemsCopy);
- const crate_warnings = TriggerCrateWarnings(crate);
+ const [crate_warnings, occupied] = TriggerCrateWarnings(crate);
return {
...crate,
items: itemsCopy,
- warnings: crate_warnings
+ warnings: crate_warnings,
+ occupiedHP: occupied
}
}
else return crate;
})
- }))
+ })),
+
+ totalOrderPrice: () => {
+ let sum = 0;
+ get().crates.forEach( (crate, _i) => {
+ sum += get().crate_modes[crate.crate_mode].price;
+ crate.items.forEach((item, _) => {
+ sum += item.price;
+ });
+ });
+ return sum;
+ },
+
+ // Composite actions that require warnings recalculation:
+
+ newCrate: () => {
+ const crate_id = "crate" + get().crates.length;
+ get()._newCrate(crate_id)
+ get().fillWarnings(crate_id);
+ },
+
+ setCrateMode: (id, mode) => {
+ console.log("setCrateMode", id, mode)
+ get()._setCrateMode(id, mode)
+ get().fillWarnings(id);
+ },
+
+ addCardFromBacklog: (crate_to, index_from, index_to) => {
+ const dest = crate_to || get().active_crate;
+ get()._addCardFromBacklog(dest, index_from, index_to)
+ get().fillWarnings(dest);
+ },
+
+ moveCard: (crate_from, index_from, crate_to, index_to) => {
+ get()._moveCard(crate_from, index_from, crate_to, index_to);
+ get().fillWarnings(crate_to);
+ if (crate_from !== crate_to) get().fillWarnings(crate_from);
+ },
+ deleteCard: (crate_id, index) => {
+ get()._deleteCard(crate_id, index);
+ get().fillWarnings(crate_id);
+ },
+ clearCrate: (id) => {
+ get()._clearCrate(id);
+ get().fillWarnings(id);
+ },
+
+ updateOptions: (crate_id, index, new_options) => {
+ get()._updateOptions(crate_id, index, new_options);
+ get().fillWarnings(crate_id);
+ }
// TODO load and save jsons?
}))
@@ -195,5 +256,6 @@ export const useShopStore = create((...params) => ({
...useCrateModes(...params),
...useCart(...params),
...useSubmitForm(...params),
- ...useLayout(...params)
+ ...useLayout(...params),
+ ...useHighlighted(...params),
}))
\ No newline at end of file
diff --git a/static/js/shop/warnings.js b/static/js/shop/warnings.js
index fc325ce..224c135 100644
--- a/static/js/shop/warnings.js
+++ b/static/js/shop/warnings.js
@@ -7,10 +7,11 @@
import {crate_type_to_hp, item_occupied_counters, resource_counters} from "./count_resources";
import {data as shared_data} from "./utils";
+import {useShopStore} from "./shop_store";
const Levels = {
- "reminder": {priority: 1, icon: '/images/shop/icon-reminder.svg'},
- "warning": {priority: 2, icon: '/images/shop/icon-warning.svg'},
+ "reminder": {priority: 1, icon: '/images/shop/icon-reminder.svg', color: "black"},
+ "warning": {priority: 2, icon: '/images/shop/icon-warning.svg', color: "#c75e5e"},
}
const find_in_counters = (counters, name) => {
@@ -97,21 +98,6 @@ const Types = {
}
}
-
-export function TriggerCardWarnings(data, index, precounted) {
- const element = data[index];
- return (element.warnings && element.warnings
- .map((warning, _) => {
- if (!!Types[warning])
- return Types[warning].trigger(data, index, precounted) ? {trigger: undefined, name: warning, ...Types[warning]} : null;
- else
- return Types.default;
- })
- .filter((warning, _) => {
- return !!warning
- }));
-}
-
export function TriggerWarnings(data) {
return data.map((element, index) => {
if (!element.warnings) return element;
@@ -137,23 +123,26 @@ export function MaxLevel(warnings) {
return mx;
}
-
+export function LevelUI(warning_level) {
+ const warning_t = Levels[warning_level];
+ return {icon: warning_t.icon, color: warning_t.color};
+}
const crate_warnings = {
"overfit": {
message: "You have reached the maximum number of slots allowed for this crate. Consider removing cards.",
level: "warning",
trigger: (crate, occupied) => {
- const nbrHP = crate_type_to_hp(crate.crate_type);
- return occupied > nbrHP;
+ const nbrHP = useShopStore.getState().crateParams(crate.crate_mode).hp;
+ return occupied > nbrHP && nbrHP > 0;
}
},
"underfit_rack": {
message: "The selected cards fit in a 42hp desktop crate, consider switching to it for a more compact system",
level: "reminder",
trigger: (crate, occupied) => {
- const nbrHPDesktop = shared_data.crateModes.desktop.hp;
- return crate.crate_type === shared_data.crateModes.rack.id && occupied < nbrHPDesktop;
+ const nbrHPDesktop = useShopStore.getState().crate_modes.desktop.hp;
+ return crate.crate_mode === useShopStore.getState().crate_modes.rack.id && occupied < nbrHPDesktop;
}
}
}
@@ -164,5 +153,5 @@ export function TriggerCrateWarnings(crate) {
Object.entries(crate_warnings).forEach(([id, warning], _) => {
if (warning.trigger(crate, nbrOccupied)) warnings.push({...warning, id: id, trigger: undefined});
})
- return warnings;
+ return [warnings, nbrOccupied];
}
\ No newline at end of file
diff --git a/static/js/shop_data.js b/static/js/shop_data.js
index ca685f9..39b0513 100644
--- a/static/js/shop_data.js
+++ b/static/js/shop_data.js
@@ -70,7 +70,7 @@ const shop_data = {
name_number: '1124',
name_codename: 'Kasli 2.0',
price: 3600,
- image: '/shop/graphic-03_kasli.svg',
+ image: '/images/shop/graphic-03_kasli.svg',
specs: [
'FPGA core device, runs ARTIQ kernels, controls the EEMs.',
'4 SFP 6Gb/s slots for Ethernet or DRTIO.',
@@ -126,7 +126,7 @@ const shop_data = {
name_number: '1125',
name_codename: 'Kasli-SoC',
price: 5100,
- image: '/shop/graphic-03_kaslisoc.svg',
+ image: '/images/shop/graphic-03_kaslisoc.svg',
specs: [
'Core device based on Zynq-7000 CPU+FPGA system-on-chip.',
'Runs ARTIQ kernels on 1GHz Cortex-A9 CPU with hardware FPU.',
@@ -194,7 +194,7 @@ const shop_data = {
name_number: '1008',
name_codename: '',
price: 400,
- image: '/shop/graphic-03_VHDCI_carrier.svg',
+ image: '/images/shop/graphic-03_VHDCI_carrier.svg',
specs: [
'Passive adapter between VHDCI and EEMs.',
'VHDCI (SCSI-3) cables can carry EEM signals over short distances between crates.',
@@ -221,7 +221,7 @@ const shop_data = {
name_number: '2118',
name_codename: '',
price: 450,
- image: '/shop/graphic-03_BNC-TTL.svg',
+ image: '/images/shop/graphic-03_BNC-TTL.svg',
specs: [
'Two banks of four digital channels each, with BNC connectors.',
'Each bank with individual ground isolation.',
@@ -293,7 +293,7 @@ const shop_data = {
name_number: '2128',
name_codename: '',
price: 400,
- image: '/shop/graphic-03_SMA-TTL.svg',
+ image: '/images/shop/graphic-03_SMA-TTL.svg',
specs: [
'Same as above, but with SMA connectors.'
],
@@ -359,7 +359,7 @@ const shop_data = {
name_number: '2238',
name_codename: '',
price: 600,
- image: '/shop/graphic-03_MCX-TTL.svg',
+ image: '/images/shop/graphic-03_MCX-TTL.svg',
specs: [
'16 single-ended digital signals on MCX connectors.',
'Direction selectable in banks of four signals.',
@@ -451,7 +451,7 @@ const shop_data = {
name_number: '2245',
name_codename: '',
price: 390,
- image: '/shop/graphic-03_LVDS.svg',
+ image: '/images/shop/graphic-03_LVDS.svg',
specs: [
'Supplies 16 LVDS pairs via 4 front-panel RJ45 connectors.',
'Each RJ45 supplies 4 LVDS DIOs.',
@@ -538,7 +538,7 @@ const shop_data = {
name_number: '4410',
name_codename: 'Urukul',
price: 2350,
- image: '/shop/graphic-03_Urukul.svg',
+ image: '/images/shop/graphic-03_Urukul.svg',
specs: [
'4 channel 1GS/s DDS.',
'Output frequency (-3 dB): <1 to >400 MHz.',
@@ -599,7 +599,7 @@ const shop_data = {
name_number: '4412',
name_codename: 'Urukul',
price: 2350,
- image: '/shop/graphic-03_Urukul-4412.svg',
+ image: '/images/shop/graphic-03_Urukul-4412.svg',
specs: [
'4 channel 1GS/s DDS.',
'Higher frequency resolution ~8 µHz (47 bit)',
@@ -635,7 +635,7 @@ const shop_data = {
name_number: '4624',
name_codename: 'Phaser',
price: 4260,
- image: '/shop/graphic-03_Phaser.svg',
+ image: '/images/shop/graphic-03_Phaser.svg',
specs: [
'2x 1.25 GS/s IQ upconverters.',
'dual IQ mixer + 0.3 GHz to 4.8 GHz VCO + PLL.',
@@ -667,7 +667,7 @@ const shop_data = {
name_number: '5432',
name_codename: 'Zotino',
price: 1600,
- image: '/shop/graphic-03_Zotino.svg',
+ image: '/images/shop/graphic-03_Zotino.svg',
specs: [
'32-channel DAC.',
'16-bit resolution.',
@@ -702,7 +702,7 @@ const shop_data = {
name_number: '5632',
name_codename: 'Fastino',
price: 3390,
- image: '/shop/graphic-03_Fastino.svg',
+ image: '/images/shop/graphic-03_Fastino.svg',
specs: [
'32-channel DAC.',
'16-bit resolution.',
@@ -731,7 +731,7 @@ const shop_data = {
name_number: '5518',
name_codename: '',
price: 160,
- image: '/shop/graphic-03_IDC-BNC-adapter.svg',
+ image: '/images/shop/graphic-03_IDC-BNC-adapter.svg',
specs: [
'Breaks out analog signals from Zotino or HD68-IDC to BNC connectors.',
'Each card provides 8 channels.',
@@ -753,7 +753,7 @@ const shop_data = {
name_number: '5528',
name_codename: '',
price: 160,
- image: '/shop/graphic-03_SMA-IDC.svg',
+ image: '/images/shop/graphic-03_SMA-IDC.svg',
specs: [
'Breaks out analog signals from Zotino or HD68-IDC to SMA connectors.',
'Each card provides 8 channels.',
@@ -775,7 +775,7 @@ const shop_data = {
name_number: '5538',
name_codename: '',
price: 160,
- image: '/shop/graphic-03_MCX-IDC.svg',
+ image: '/images/shop/graphic-03_MCX-IDC.svg',
specs: [
'Breaks out analog signals from Zotino or HD68-IDC to MCX connectors.',
'Each card provides 8 channels.',
@@ -797,7 +797,7 @@ const shop_data = {
name_number: '5568',
name_codename: '',
price: 150,
- image: '/shop/graphic-03_HD68.svg',
+ image: '/images/shop/graphic-03_HD68.svg',
specs: [
'Connects an external HD68 cable to IDC-BNC, IDC-SMA or IDC-MCX cards.',
],
@@ -823,7 +823,7 @@ const shop_data = {
name_number: '5108',
name_codename: '',
price: 1600,
- image: '/shop/graphic-03_Sampler.svg',
+ image: '/images/shop/graphic-03_Sampler.svg',
specs: [
'8-channel ADC.',
'16-bit resolution.',
@@ -874,7 +874,7 @@ const shop_data = {
name_number: '6302',
name_codename: '',
price: 550,
- image: '/shop/graphic-03_Grabber.svg',
+ image: '/images/shop/graphic-03_Grabber.svg',
specs: [
'Camera input interface card.',
'Supports some EMCCD cameras.',
@@ -901,7 +901,7 @@ const shop_data = {
name_number: '7210',
name_codename: '',
price: 525,
- image: '/shop/graphic-03_Clocker.svg',
+ image: '/images/shop/graphic-03_Clocker.svg',
specs: [
'Distribute a low jitter clock signal among cards.',
'2 inputs.',
@@ -936,7 +936,7 @@ const shop_data = {
name_number: '8452',
name_codename: 'Stabilizer',
price: 2000,
- image: '/shop/graphic-03_Stabilizer.svg',
+ image: '/images/shop/graphic-03_Stabilizer.svg',
specs: [
'CPU-based dual-channel fast servo.',
'400MHz STM32H743ZIT6.',
@@ -968,7 +968,7 @@ const shop_data = {
name_number: '4456',
name_codename: 'Mirny',
price: 2660,
- image: '/shop/graphic-03_Mirny.svg',
+ image: '/images/shop/graphic-03_Mirny.svg',
specs: [
'4-channel Wide-band PLL/VCO-based microwave frequency synthesiser.',
'53 MHz to >4 GHz.',
@@ -998,7 +998,7 @@ const shop_data = {
name_number: '4457',
name_codename: 'Mirny + Almazny',
price: 3660,
- image: '/shop/graphic-03_Almazny.svg',
+ image: '/images/shop/graphic-03_Almazny.svg',
specs: [
'Mirny with high frequency mezzanine.',
'Additional 4 channels up to 12 GHz.',
@@ -1025,7 +1025,7 @@ const shop_data = {
name_number: '8453',
name_codename: '',
price: 2600,
- image: '/shop/graphic-03_Thermostat-EEM.svg',
+ image: '/images/shop/graphic-03_Thermostat-EEM.svg',
specs: [
'4 TEC channels.',
'Sensor channel count: 8 differential, 16 single-ended.',
@@ -1053,7 +1053,7 @@ const shop_data = {
name_number: '5716',
name_codename: 'Shuttler',
price: 8500,
- image: '/shop/graphic-03_Shuttler.svg',
+ image: '/images/shop/graphic-03_Shuttler.svg',
specs: [
'16-ch, 125 MSPS DAC EEM with remote analog front end board.',
'High DC resolution (up to ~18 bits with sigma-delta modulation) for trap electrode bias.',
@@ -1080,7 +1080,7 @@ const shop_data = {
name_number: '4459',
name_codename: 'Stabilizer + Pounder',
price: 4460,
- image: '/shop/graphic-03_Pounder.svg',
+ image: '/images/shop/graphic-03_Pounder.svg',
specs: [
'Stabilizer with Pounder daughter card.',
'2-channel Pound Drever Hall (PDH) lock generator.',
@@ -1111,7 +1111,7 @@ const shop_data = {
name_number: '1106',
name_codename: '',
price: 750,
- image: '/shop/graphic-03_eem_pwr_mod.svg',
+ image: '/images/shop/graphic-03_eem_pwr_mod.svg',
specs: [
"EEM AC power module.",
"400W with forced cooling (25CFM), 200W with free air convection.",
@@ -1189,7 +1189,8 @@ const shop_data = {
id: "crate0",
crate_mode: "rack",
items: [],
- warnings: []
+ warnings: [],
+ occupiedHP: 0,
}]
},