Split Summary so that it rerenders only partially

Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
Egor Savkin 2023-12-14 17:45:54 +08:00
parent bf05594813
commit 2bfc16e3c0
10 changed files with 220 additions and 99 deletions

View File

@ -17,8 +17,10 @@ export function Cart({crate_index}) {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
const crate = useShopStore(useShallow((state) => state.crates[crate_index]), (a, b) => { const crate = useShopStore((state) => state.crates[crate_index], (a, b) => {
//console.log(a, b)
return a.items.length === b.items.length && a.occupiedHP === b.occupiedHP && a.crate_mode === b.crate_mode return a.items.length === b.items.length && a.occupiedHP === b.occupiedHP && a.crate_mode === b.crate_mode
//return a === b
}); });
const crateParams = useShopStore((state) => state.crateParams); const crateParams = useShopStore((state) => state.crateParams);

View File

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import {SummaryPopup} from "./options/SummaryPopup";
import {formatMoney} from "./utils";
import {WarningIndicator} from "./CardWarnings";
import {useShopStore} from "./shop_store"; import {useShopStore} from "./shop_store";
import {SummaryCrates} from "./SummaryCrates";
import {SummaryTotalPrice} from "./SummaryTotalPrice";
// #!render_count // #!render_count
import {useRenderCount} from "@uidotdev/usehooks"; import {useRenderCount} from "@uidotdev/usehooks";
/** /**
* Components that displays the list of card that are used in the crate. * Components that displays the list of card that are used in the crate.
* It is a summary of purchase * It is a summary of purchase
@ -14,18 +14,7 @@ import {useRenderCount} from "@uidotdev/usehooks";
export function OrderSummary() { export function OrderSummary() {
// #!render_count // #!render_count
const renderCount = useRenderCount(); const renderCount = useRenderCount();
const currency = useShopStore((state) => state.currency);
const crates = useShopStore((state) => state.crates);
const total_price = useShopStore((state) => state.totalOrderPrice());
const crateParams = useShopStore((state) => state.crateParams);
const deleteCard = useShopStore((state) => state.deleteCard);
const setHighlight = useShopStore((state) => state.highlightCard);
const resetHighlight = useShopStore((state) => state.highlightReset);
const highlighted = useShopStore((state) => state.highlighted);
const clearAll = useShopStore((state) => state.clearAll); const clearAll = useShopStore((state) => state.clearAll);
const clearCrate = useShopStore((state) => state.clearCrate);
const delCrate = useShopStore((state) => state.delCrate);
// #!render_count // #!render_count
console.log("OrderSummary renders: ", renderCount) console.log("OrderSummary renders: ", renderCount)
@ -47,91 +36,13 @@ export function OrderSummary() {
</tr> </tr>
</thead> </thead>
{crates.map((crate, _i) => { <SummaryCrates/>
let crate_type = crateParams(crate.crate_mode);
return (
<tbody key={"summary_crate_body" + crate.id}>
<tr key={"summary_crate_" + crate.id}>
<td className="item-card-name">{crate_type.name}</td>
<td className="price">
<div className="d-inline-flex">
{`${currency} ${formatMoney(crate_type.price)}`}
<button onClick={() => clearCrate(crate.id)}>
<img src="/images/shop/icon-clear.svg" alt="empty crate"/>
</button>
<button onClick={() => delCrate(crate.id)}>
<img src="/images/shop/icon-remove.svg" alt="remove crate"/>
</button>
</div>
</td>
</tr>
{crate.items.map((item, index) => {
const options = item && item.options;
const options_data = item && item.options_data;
const warnings = item && item.show_warnings;
const selected = crate.id === highlighted.crate && index === highlighted.card;
return (<tr key={"summary_crate_" + crate.id + item.id}
className={`hoverable ${selected ? 'selected' : ''}`}
onClick={() => setHighlight(crate.id, index)}
onMouseEnter={() => setHighlight(crate.id, index)}
onMouseLeave={() => resetHighlight()}>
<td className="item-card-name">
<span style={{
'display': 'inline-block',
'width': '16px',
}}>&nbsp;</span>
<div>{`${item.name_number} ${item.name} ${item.name_codename}`}</div>
</td>
<td className="price">
<div className="d-inline-flex align-content-center">
{`${currency} ${formatMoney(item.price)}`}
<button onClick={() => deleteCard(crate.id, index)}>
<img src="/images/shop/icon-remove.svg"/>
</button>
<div style={{'width': '45px', 'height': '20px'}}
className="d-inline-flex align-content-center align-self-center justify-content-evenly">
{(warnings && warnings.length > 0 ? (
<WarningIndicator warnings={warnings}/>
) : (
<span style={{
'display': 'inline-block',
'width': '20px',
}}>&nbsp;</span>
))}
{((options && options_data) ? (
<SummaryPopup id={item.id + "options"} options={options}
data={options_data}/>
) : (
<span style={{
'display': 'inline-block',
'width': '20px',
}}>&nbsp;</span>
))}
</div>
</div>
</td>
</tr>);
})}
</tbody>
)
})}
<tfoot> <tfoot>
<tr> <tr>
<td className="item-card-name">Price estimate</td> <td className="item-card-name">Price estimate</td>
<td className="price"> <td className="price">
<div> <SummaryTotalPrice/>
{currency} {formatMoney(total_price)}
<button style={{'opacity': '0', 'cursor': 'initial'}}>
<img src="/images/shop/icon-remove.svg" alt="icon remove"/>
</button>
</div>
<span style={{ <span style={{
'display': 'inline-block', 'display': 'inline-block',

View File

@ -19,7 +19,10 @@ export function ProductCartItem({card_index, crate_index, ext_data, first, last}
const card = useShopStore((state) => state.crates[crate_index].items[card_index], const card = useShopStore((state) => state.crates[crate_index].items[card_index],
(a, b) => a.id === b.id && a.show_warnings === b.show_warnings && a.counted_resources === b.counted_resources ); (a, b) => {
//console.log(a.options_data, b.options_data, a.options_data === b.options_data)
return a.id === b.id && a.show_warnings === b.show_warnings && a.counted_resources === b.counted_resources && a.options_data === b.options_data
} );
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 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);

View File

@ -0,0 +1,30 @@
import {range} from "./utils";
import React from "react";
import {useShopStore} from "./shop_store";
import {SummaryCrateHeader} from "./SummaryCrateHeader";
import {SummaryCrateCard} from "./SummaryCrateCard";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrate({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const crate_len = useShopStore((state) => state.crates[crate_index].items.length);
// #!render_count
console.log("SummaryCrate renders: ", renderCount)
return (
<tbody key={"summary_crate_body" + crate_id}>
<SummaryCrateHeader crate_index={crate_index}/>
{range(0, crate_len).map((index, _i) =>
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
)}
</tbody>
)
}

View File

@ -0,0 +1,78 @@
import {formatMoney} from "./utils";
import {WarningIndicator} from "./CardWarnings";
import {SummaryPopup} from "./options/SummaryPopup";
import React from "react";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrateCard({crate_index, card_index}) {
// #!render_count
const renderCount = useRenderCount();
const currency = useShopStore((state) => state.currency);
const deleteCard = useShopStore((state) => state.deleteCard);
const setHighlight = useShopStore((state) => state.highlightCard);
const resetHighlight = useShopStore((state) => state.highlightReset);
const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card);
const crate_id = useShopStore((state) => state.crates[crate_index].id);
const card = useShopStore((state) => state.crates[crate_index].items[card_index],
(a, b) => a.id === b.id && a.options_data === b.options_data && a.show_warnings === b.show_warnings);
// #!render_count
console.log("SummaryCrateCard renders: ", renderCount)
const options = card && card.options;
const options_data = card && card.options_data;
const warnings = card && card.show_warnings;
return (
<tr
key={"summary_crate_" + crate_id + "_" + card_index}
className={`hoverable ${highlighted ? 'selected' : ''}`}
onClick={() => setHighlight(crate_id, card_index)}
onMouseEnter={() => setHighlight(crate_id, card_index)}
onMouseLeave={() => resetHighlight()}>
<td className="item-card-name">
<span style={{
'display': 'inline-block',
'width': '16px',
}}>&nbsp;</span>
<div>{`${card.name_number} ${card.name} ${card.name_codename}`}</div>
</td>
<td className="price">
<div className="d-inline-flex align-content-center">
{`${currency} ${formatMoney(card.price)}`}
<button onClick={() => deleteCard(crate_id, card_index)}>
<img src="/images/shop/icon-remove.svg"/>
</button>
<div style={{'width': '45px', 'height': '20px'}}
className="d-inline-flex align-content-center align-self-center justify-content-evenly">
{(warnings && warnings.length > 0 ? (
<WarningIndicator warnings={warnings}/>
) : (
<span style={{
'display': 'inline-block',
'width': '20px',
}}>&nbsp;</span>
))}
{((options && options_data) ? (
<SummaryPopup id={card.id + "options"} options={options}
data={options_data}/>
) : (
<span style={{
'display': 'inline-block',
'width': '20px',
}}>&nbsp;</span>
))}
</div>
</div>
</td>
</tr>);
}

View File

@ -0,0 +1,43 @@
import {formatMoney} from "./utils";
import React from "react";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrateHeader({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const currency = useShopStore((state) => state.currency);
const crateParams = useShopStore((state) => state.crateParams);
const clearCrate = useShopStore((state) => state.clearCrate);
const delCrate = useShopStore((state) => state.delCrate);
const crate_mode = useShopStore((state) => state.crates[crate_index].crate_mode);
const crate_id = useShopStore((state) => state.crates[crate_index].id);
// #!render_count
console.log("SummaryCrateHeader renders: ", renderCount)
let crate_type = crateParams(crate_mode);
return (
<tr key={"summary_crate_" + crate_id}>
<td className="item-card-name">{crate_type.name}</td>
<td className="price">
<div className="d-inline-flex">
{`${currency} ${formatMoney(crate_type.price)}`}
<button onClick={() => clearCrate(crate_id)}>
<img src="/images/shop/icon-clear.svg" alt="empty crate"/>
</button>
<button onClick={() => delCrate(crate_id)}>
<img src="/images/shop/icon-remove.svg" alt="remove crate"/>
</button>
</div>
</td>
</tr>
)
}

View File

@ -0,0 +1,25 @@
import {range} from "./utils";
import React from "react";
import {useShopStore} from "./shop_store";
import {SummaryCrate} from "./SummaryCrate";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrates() {
// #!render_count
const renderCount = useRenderCount();
const crates_l = useShopStore((state) => state.crates.length);
// #!render_count
console.log("SummaryCrates renders: ", renderCount)
return (
<>
{range(0, crates_l).map((index, _i) => {
return <SummaryCrate crate_index={index} key={"summary_crate_body_" + index} />
})}
</>
)
}

View File

@ -0,0 +1,17 @@
import {useShopStore} from "./shop_store";
import {formatMoney} from "./utils";
import React from "react";
export function SummaryTotalPrice() {
const currency = useShopStore((state) => state.currency);
const total_price = useShopStore((state) => state.totalOrderPrice());
return (
<div>
{currency} {formatMoney(total_price)}
<button style={{'opacity': '0', 'cursor': 'initial'}}>
<img src="/images/shop/icon-remove.svg" alt="icon remove"/>
</button>
</div>
)
}

View File

@ -254,12 +254,14 @@ const useCart = ((set, get) => ({
return { return {
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (crate_to === crate_from && crate_to === crate.id) { if (crate_to === crate_from && crate_to === crate.id) {
// TODO fix
//const the_card = {...crate[index_from]}; //const the_card = {...crate[index_from]};
let items_copy = Array.from(crate.items); let items_copy = Array.from(crate.items);
delete items_copy[index_from]; delete items_copy[index_from];
console.log(crate_from, index_from, crate_to, index_to, items_copy.toSpliced(index_to+1, 0, the_card).filter((item, _) => !!item))
return { return {
...crate, ...crate,
items: items_copy.toSpliced(index_to+1, 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) { } else if (crate_to === crate.id) {
return { return {
@ -305,7 +307,12 @@ const useCart = ((set, get) => ({
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) { if (crate_id === crate.id) {
let itemsCopy = Array.from(crate.items); let itemsCopy = Array.from(crate.items);
itemsCopy[index].options_data = {...itemsCopy[index].options_data, ...new_options}; itemsCopy[index] = {
...itemsCopy[index],
options_data: {
...itemsCopy[index].options_data,
...new_options
}};
return { return {
...crate, ...crate,
items: itemsCopy items: itemsCopy

View File

@ -57,4 +57,9 @@ export function formatMoney(amount, decimalCount = 2, decimal = ".", thousands =
} catch (e) { } catch (e) {
return amount; return amount;
} }
} }
export const range = (start, end) => {
const length = end - start;
return Array.from({ length }, (_, i) => start + i);
}