forked from M-Labs/web2019
Add fan tray option to the crate
Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
parent
3366f80ed7
commit
ddd8b2d894
File diff suppressed because one or more lines are too long
@ -6,6 +6,7 @@ import {useShopStore} from "./shop_store";
|
|||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
import {CrateFanTray} from "./CrateFanTray";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +44,8 @@ export function Crate({crate_index}) {
|
|||||||
<Cart crate_index={crate_index}/>
|
<Cart crate_index={crate_index}/>
|
||||||
|
|
||||||
<CrateWarnings crate_index={crate_index} />
|
<CrateWarnings crate_index={crate_index} />
|
||||||
|
|
||||||
|
<CrateFanTray crate_index={crate_index}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
37
static/js/shop/CrateFanTray.jsx
Normal file
37
static/js/shop/CrateFanTray.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {Tip} from "./options/components/Tip";
|
||||||
|
import {formatMoney} from "./utils";
|
||||||
|
|
||||||
|
export function CrateFanTray({crate_index}) {
|
||||||
|
const currency = useShopStore((state) => state.currency);
|
||||||
|
const fanTray = useShopStore((state) => state.fanTray);
|
||||||
|
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||||
|
const fanTrayAvailable = useShopStore((state) => state.fanTrayAvailableByIndex(crate_index));
|
||||||
|
const fanTrayEnabled = useShopStore((state) => state.crates[crate_index].fan_tray);
|
||||||
|
const updateFanTray = useShopStore((state) => state.updateFanTrayOption);
|
||||||
|
|
||||||
|
const base_id = crate_id + "fan_tray";
|
||||||
|
return fanTrayAvailable ? (
|
||||||
|
<div className="crate-bar">
|
||||||
|
<div className="shop-switch">
|
||||||
|
<div className="form-check form-switch">
|
||||||
|
<input
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
id={base_id}
|
||||||
|
checked={fanTrayEnabled}
|
||||||
|
onClick={() => updateFanTray(crate_id, !fanTrayEnabled)}
|
||||||
|
onChange={() => updateFanTray(crate_id, !fanTrayEnabled)}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label" htmlFor={base_id} style={{"display": "inline", marginRight: "0.125rem"}}>
|
||||||
|
{fanTray.optionTitle} (+{`${currency} ${formatMoney(fanTray.price)}`})
|
||||||
|
</label>
|
||||||
|
{fanTray.tip && <Tip id={base_id + "tooltip"} tip={fanTray.tip}/>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
@ -6,6 +6,7 @@ import {SummaryCrateCard} from "./SummaryCrateCard";
|
|||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
import {SummaryCrateFanTray} from "./SummaryCrateFanTray";
|
||||||
|
|
||||||
export function SummaryCrate({crate_index}) {
|
export function SummaryCrate({crate_index}) {
|
||||||
// #!render_count
|
// #!render_count
|
||||||
@ -25,6 +26,7 @@ export function SummaryCrate({crate_index}) {
|
|||||||
{range(0, crate_len).map((index, _i) =>
|
{range(0, crate_len).map((index, _i) =>
|
||||||
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
|
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
|
||||||
)}
|
)}
|
||||||
|
<SummaryCrateFanTray crate_index={crate_index}/>
|
||||||
</tbody>
|
</tbody>
|
||||||
)
|
)
|
||||||
}
|
}
|
45
static/js/shop/SummaryCrateFanTray.jsx
Normal file
45
static/js/shop/SummaryCrateFanTray.jsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import {formatMoney} from "./utils";
|
||||||
|
import React from "react";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
|
// #!render_count
|
||||||
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
|
||||||
|
|
||||||
|
export function SummaryCrateFanTray({crate_index}) {
|
||||||
|
// #!render_count
|
||||||
|
const renderCount = useRenderCount();
|
||||||
|
|
||||||
|
const currency = useShopStore((state) => state.currency);
|
||||||
|
const fanTray = useShopStore((state) => state.fanTray);
|
||||||
|
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||||
|
const fanTrayAvailable = useShopStore((state) => state.fanTrayAvailableByIndex(crate_index));
|
||||||
|
const fanTrayEnabled = useShopStore((state) => state.crates[crate_index].fan_tray);
|
||||||
|
const updateFanTray = useShopStore((state) => state.updateFanTrayOption);
|
||||||
|
|
||||||
|
|
||||||
|
// #!render_count
|
||||||
|
console.log("SummaryCrateCard renders: ", renderCount)
|
||||||
|
|
||||||
|
return (fanTrayAvailable && fanTrayEnabled) ? (<tr
|
||||||
|
key={"summary_crate_" + crate_id + "_fan_tray"}>
|
||||||
|
<td className="item-card-name">
|
||||||
|
<span style={{
|
||||||
|
'display': 'inline-block', 'width': '16px',
|
||||||
|
}}> </span>
|
||||||
|
<div>{fanTray.optionTitle}</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="price">
|
||||||
|
<div className="d-inline-flex align-content-center">
|
||||||
|
{`${currency} ${formatMoney(fanTray.price)}`}
|
||||||
|
|
||||||
|
<button onClick={() => updateFanTray(crate_id, false)}>
|
||||||
|
<img src="/images/shop/icon-remove.svg" className="d-block"/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div style={{'width': '45px', 'height': '20px'}} className="d-inline"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>) : null;
|
||||||
|
}
|
@ -36,10 +36,12 @@ export function JSONToCrates(description) {
|
|||||||
const parsed = JSON.parse(description);
|
const parsed = JSON.parse(description);
|
||||||
const crates_raw = parsed.crates;
|
const crates_raw = parsed.crates;
|
||||||
const pn_to_card = useShopStore.getState().getCardDescriptionByPn;
|
const pn_to_card = useShopStore.getState().getCardDescriptionByPn;
|
||||||
|
const fanTrayAvailable = useShopStore.getState().fanTrayAvailableForMode;
|
||||||
|
|
||||||
const crates = Array.from(crates_raw.map((crate, c_i) => ({
|
const crates = Array.from(crates_raw.map((crate, c_i) => ({
|
||||||
id: crate.type === "no_crate" ? "spare" : "crate" + c_i,
|
id: crate.type === "no_crate" ? "spare" : "crate" + c_i,
|
||||||
name: crate.type === "no_crate" ? "Spare cards" : undefined,
|
name: crate.type === "no_crate" ? "Spare cards" : undefined,
|
||||||
|
fan_tray: fanTrayAvailable(crate.type) && crate.fan_tray === true,
|
||||||
crate_mode: crate.type,
|
crate_mode: crate.type,
|
||||||
items: Array.from(crate.items.map((card, _i) => ({
|
items: Array.from(crate.items.map((card, _i) => ({
|
||||||
...pn_to_card(card.pn),
|
...pn_to_card(card.pn),
|
||||||
@ -57,6 +59,7 @@ export function JSONToCrates(description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CratesToJSON(crates) {
|
export function CratesToJSON(crates) {
|
||||||
|
const fanTrayAvailable = useShopStore.getState().fanTrayAvailableForMode;
|
||||||
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) => ({
|
||||||
@ -64,7 +67,8 @@ export function CratesToJSON(crates) {
|
|||||||
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.options) ? FilterOptions(card.options, card.options_data) : null
|
||||||
}))),
|
}))),
|
||||||
type: crate.crate_mode
|
type: crate.crate_mode,
|
||||||
|
fan_tray: (!fanTrayAvailable(crate.crate_mode) ? undefined : true) && crate.fan_tray
|
||||||
})))
|
})))
|
||||||
}, null, 2)
|
}, null, 2)
|
||||||
}
|
}
|
@ -34,6 +34,13 @@ const useCrateModes = ((set, get) => ({
|
|||||||
crateParams: mode => get().crate_modes[mode],
|
crateParams: mode => get().crate_modes[mode],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const useFanTray = ((set, get) => ({
|
||||||
|
fanTray: shared_data.fanTray,
|
||||||
|
fanTrayAvailableForMode: (crate_mode) => {
|
||||||
|
return get().fanTray.crateModesAvailable[crate_mode] === true;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const useLayout = ((set, get) => ({
|
const useLayout = ((set, get) => ({
|
||||||
isTouch: window.isTouchEnabled(),
|
isTouch: window.isTouchEnabled(),
|
||||||
isMobile: window.deviceIsMobile(),
|
isMobile: window.deviceIsMobile(),
|
||||||
@ -393,8 +400,9 @@ const useCart = ((set, get) => ({
|
|||||||
|
|
||||||
totalOrderPrice: () => {
|
totalOrderPrice: () => {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
get().crates.forEach( (crate, _i) => {
|
get().crates.forEach( (crate, i) => {
|
||||||
sum += get().crate_modes[crate.crate_mode].price;
|
sum += get().crate_modes[crate.crate_mode].price;
|
||||||
|
sum += (crate.fan_tray && get().fanTrayAvailableByIndex(i)) ? get().fanTray.price : 0;
|
||||||
crate.items.forEach((item, _) => {
|
crate.items.forEach((item, _) => {
|
||||||
sum += item.price;
|
sum += item.price;
|
||||||
});
|
});
|
||||||
@ -459,6 +467,22 @@ const useCart = ((set, get) => ({
|
|||||||
get()._updateOptions(crate_id, index, new_options);
|
get()._updateOptions(crate_id, index, new_options);
|
||||||
get().fillExtData(crate_id);
|
get().fillExtData(crate_id);
|
||||||
get().fillWarnings(crate_id);
|
get().fillWarnings(crate_id);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFanTrayOption: (crate_id, add_fan_tray) => set(state => ({
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate_id === crate.id) {
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
fan_tray: add_fan_tray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
|
||||||
|
fanTrayAvailableByIndex: (crate_index) => {
|
||||||
|
return get().fanTrayAvailableForMode(get().crates[crate_index].crate_mode);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -471,4 +495,5 @@ export const useShopStore = createWithEqualityFn((...params) => ({
|
|||||||
...useLayout(...params),
|
...useLayout(...params),
|
||||||
...useHighlighted(...params),
|
...useHighlighted(...params),
|
||||||
...useImportJSON(...params),
|
...useImportJSON(...params),
|
||||||
|
...useFanTray(...params),
|
||||||
}))
|
}))
|
@ -71,7 +71,7 @@ const Types = {
|
|||||||
"no_idc_source": {
|
"no_idc_source": {
|
||||||
level: "warning",
|
level: "warning",
|
||||||
trigger: no_source_trigger("idc"),
|
trigger: no_source_trigger("idc"),
|
||||||
message: 'Should be after a Zotino or a HD68-IDC or with another IDC-BNC.'
|
message: 'Should be after a Zotino or a HD68-IDC or with another IDC adapter.'
|
||||||
},
|
},
|
||||||
"clk_resource": {
|
"clk_resource": {
|
||||||
level: "warning",
|
level: "warning",
|
||||||
|
@ -28,6 +28,15 @@ const shop_data = {
|
|||||||
"rack", "desktop"
|
"rack", "desktop"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
fanTray: {
|
||||||
|
price: 42,
|
||||||
|
crateModesAvailable: {
|
||||||
|
'rack': true
|
||||||
|
},
|
||||||
|
optionTitle: "Mount fan tray",
|
||||||
|
tip: "Mount 84hp fan tray to the crate bottom for better air circulation"
|
||||||
|
},
|
||||||
|
|
||||||
items: {
|
items: {
|
||||||
/* keys are also ids, avoid changing them */
|
/* keys are also ids, avoid changing them */
|
||||||
'kasli': {
|
'kasli': {
|
||||||
@ -1153,6 +1162,7 @@ const shop_data = {
|
|||||||
"crates": [{
|
"crates": [{
|
||||||
id: "crate0",
|
id: "crate0",
|
||||||
crate_mode: "rack",
|
crate_mode: "rack",
|
||||||
|
fan_tray: false,
|
||||||
items: [],
|
items: [],
|
||||||
warnings: [],
|
warnings: [],
|
||||||
occupiedHP: 0,
|
occupiedHP: 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user