Add fan tray option to the crate

Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
Egor Savkin 2024-01-25 17:10:14 +08:00
parent 3366f80ed7
commit ddd8b2d894
9 changed files with 130 additions and 4 deletions

File diff suppressed because one or more lines are too long

View File

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

View 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
}

View File

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

View 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',
}}>&nbsp;</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;
}

View File

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

View File

@ -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),
})) }))

View File

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

View File

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