Fix moving cards in carts, json importer, and further rerender fixes and optimizations

Signed-off-by: Egor Savkin <es@m-labs.hk>
pull/113/head
Egor Savkin 2023-12-15 13:49:54 +08:00
parent 2bfc16e3c0
commit c09d583fa6
13 changed files with 198 additions and 76 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,14 @@
import {OverlayTrigger} from "react-bootstrap";
import React from "react";
import {MaxLevel} from "./warnings";
import {useShopStore} from "./shop_store";
import {compareArraysLevelOne} from "./utils";
export function CardWarnings({warnings, prefix}) {
export function CardWarnings({crate_index, card_index}) {
const warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareArraysLevelOne);
const max_level = MaxLevel(warnings);
return (
<OverlayTrigger
@ -14,7 +19,7 @@ export function CardWarnings({warnings, prefix}) {
<div className="k-popup-warning" {...props}>
{warnings.map((warning, _i) => {
return (
<p className="rule warning" key={`warnmsg_${prefix}_${warning.name}`}>
<p className="rule warning" key={`warnmsg_${card_index}_${warning.name}`}>
<i>{warning.message}</i>
</p>
)
@ -28,7 +33,9 @@ export function CardWarnings({warnings, prefix}) {
)
}
export function WarningIndicator({warnings}) {
export function WarningIndicator({crate_index, card_index}) {
const warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareArraysLevelOne);
const max_level = MaxLevel(warnings);
return (
<img

View File

@ -1,14 +1,14 @@
import React from 'react'
import {Droppable} from "@hello-pangea/dnd";
import {cartStyle} from "./utils";
import {cartStyle, compareArraysWithIds} from "./utils";
import {ProductCartItem} from "./ProductCartItem";
import {FakePlaceholder} from "./FakePlaceholder";
import {FillExtData} from "./options/utils";
import {hp_to_slots} from "./count_resources";
import {useShopStore} from "./shop_store";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {useShallow} from "zustand/react/shallow";
/**
* Component that displays a list of <ProductCartItem>
@ -18,9 +18,7 @@ export function Cart({crate_index}) {
const renderCount = useRenderCount();
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 === b
return compareArraysWithIds(a.items, b.items) && a.occupiedHP === b.occupiedHP && a.crate_mode === b.crate_mode
});
const crateParams = useShopStore((state) => state.crateParams);

View File

@ -1,18 +1,17 @@
import React from "react";
import {LevelUI} from "./warnings";
import {useShopStore} from "./shop_store";
import {compareArraysWithIds} from "./utils";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
const compareArrays = (a, b) =>
a.length === b.length &&
a.every((element, index) => element.id === b[index].id);
export function CrateWarnings({crate_index}) {
// #!render_count
const renderCount = useRenderCount();
const crate_warnings = useShopStore(state => (state.crates[crate_index].warnings), compareArrays)
const crate_warnings = useShopStore(state => (state.crates[crate_index].warnings), compareArraysWithIds)
// #!render_count
console.log("CrateWarnings renders: ", renderCount)

View File

@ -6,10 +6,29 @@ import {Validation} from "./validate";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
const JSONExample = JSON.stringify({
"items": [{"pn": "1124"}, {"pn": "2118"}, {"pn": "2118"}, {"pn": "2128"}],
"type": "desktop"
});
const JSONExample = JSON.stringify([
{
"items": [
{
"pn": "1124",
"options": null
},
{
"pn": "2118",
"options": null
},
{
"pn": "2118",
"options": null
},
{
"pn": "2128",
"options": null
}
],
"type": "rack"
}
]);
export function ImportJSON() {
// #!render_count

View File

@ -0,0 +1,49 @@
import {DialogPopup} from "./options/DialogPopup";
import React from "react";
import {useShopStore} from "./shop_store";
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 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 options_class = useShopStore((state) => state.crates[crate_index].items[card_index].options_class);
const onOptionsUpdate = useShopStore((state) => state.updateOptions);
return (
<DialogPopup
options={options}
data={options_data}
options_class={options_class}
key={"popover" + card_index}
id={"popover" + card_index}
big={card_size === "big"}
first={first}
last={last}
target={{
construct: ((outvar, value) => {
// console.log("construct", outvar, value, options_data);
options_data[outvar] = value;
}),
update: ((outvar, value) => {
// console.log("update", outvar, value, options_data);
if (outvar in options_data) options_data[outvar] = value;
onOptionsUpdate(crate_id, card_index, {[outvar]: value});
})
}}
/>
)
}
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 options_data = useShopStore((state) => state.crates[crate_index].items[card_index].options_data);
return (
<SummaryPopup id={card_id + "options"} options={options}
data={options_data}/>
)
}

View File

@ -1,10 +1,10 @@
import React from 'react'
import {Draggable} from "@hello-pangea/dnd";
import {DialogPopup} from "./options/DialogPopup";
import {productStyle} from "./utils";
import {compareObjectsEmptiness, productStyle} from "./utils";
import {Resources} from "./Resources";
import {CardWarnings} from "./CardWarnings";
import {useShopStore} from "./shop_store";
import {OptionsDialogWrapper} from "./OptionsWrapper";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
@ -13,36 +13,30 @@ import {useRenderCount} from "@uidotdev/usehooks";
* Component that renders a product.
* Used in the crate
*/
export function ProductCartItem({card_index, crate_index, ext_data, first, last}) {
export function ProductCartItem({card_index, crate_index, first, last}) {
// #!render_count
const renderCount = useRenderCount();
const card = useShopStore((state) => state.crates[crate_index].items[card_index],
(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
} );
(a, b) => a.id === b.id);
const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness);
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 crate_id = useShopStore((state) => state.crates[crate_index].id);
const setHighlight = useShopStore((state) => state.highlightCard);
const removeHighlight = useShopStore((state) => state.highlightReset);
const onCardUpdate = useShopStore((state) => state.updateOptions);
const onCardRemove = useShopStore((state) => state.deleteCard);
// #!render_count
console.log("ProductCartItem renders: ", renderCount)
let options, options_data;
const warnings = card && card.show_warnings;
const resources = card && card.counted_resources;
if (card && card.options) {
options = card.options;
if (!card.options_data) card.options_data = {};
options_data = card.options_data;
options_data.ext_data = ext_data;
}
const options = card && card.options && card.options.length > 0;
const warnings = card_show_warnings && card_show_warnings.length > 0;
const resources = card_counted_resources && card_counted_resources.length > 0;
return (
<Draggable draggableId={card.id} index={card_index}>
@ -69,31 +63,16 @@ export function ProductCartItem({card_index, crate_index, ext_data, first, last}
{/* warning container */}
<div className="progress-container warning d-flex justify-content-evenly">
{warnings && warnings.length > 0 &&
(<CardWarnings warnings={warnings} prefix={card_index}/>)
{warnings &&
(<CardWarnings crate_index={crate_index} card_index={card_index} />)
}
{options && (
<DialogPopup
options={options}
data={options_data}
options_class={card.options_class}
key={"popover" + card_index}
id={"popover" + card_index}
big={card.size === "big"}
<OptionsDialogWrapper
crate_index={crate_index}
card_index={card_index}
first={first}
last={last}
target={{
construct: ((outvar, value) => {
// console.log("construct", outvar, value, options_data);
options_data[outvar] = value;
}),
update: ((outvar, value) => {
// console.log("update", outvar, value, options_data);
if (outvar in options_data) options_data[outvar] = value;
onCardUpdate(crate_id, card_index, {[outvar]: value});
})
}}
/>
)}
</div>
@ -123,7 +102,7 @@ export function ProductCartItem({card_index, crate_index, ext_data, first, last}
{/* progression container */}
{resources && (
<Resources resources={resources}/>
<Resources crate_index={crate_index} card_index={card_index} />
)}

View File

@ -4,6 +4,8 @@ import {v4 as uuidv4} from "uuid";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
import {useShopStore} from "./shop_store";
import {compareArraysLevelOne} from "./utils";
const resourcesWidthStyle = (occupied, max) => {
@ -64,9 +66,12 @@ function RenderResources({resources, library}) {
return result;
}
export function Resources({resources}) {
export function Resources({crate_index, card_index}) {
// #!render_count
const renderCount = useRenderCount();
const resources = useShopStore(state => state.crates[crate_index].items[card_index].counted_resources, compareArraysLevelOne);
// #!render_count
console.log("Resources renders: ", renderCount)
return (

View File

@ -1,12 +1,13 @@
import {formatMoney} from "./utils";
import {compareObjectsEmptiness, formatMoney} from "./utils";
import {WarningIndicator} from "./CardWarnings";
import {SummaryPopup} from "./options/SummaryPopup";
import React from "react";
import {useShopStore} from "./shop_store";
import {OptionsSummaryWrapper} from "./OptionsWrapper";
// #!render_count
import {useRenderCount} from "@uidotdev/usehooks";
export function SummaryCrateCard({crate_index, card_index}) {
// #!render_count
const renderCount = useRenderCount();
@ -19,15 +20,17 @@ export function SummaryCrateCard({crate_index, card_index}) {
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);
(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);
// #!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;
const options = card && card.options && card.options.length > 0;
const options_data = card_options_data && Object.keys(card_options_data).length > 0;
const warnings = card_show_warnings && card_show_warnings.length > 0;
return (
<tr
@ -54,8 +57,8 @@ export function SummaryCrateCard({crate_index, card_index}) {
<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}/>
{(warnings ? (
<WarningIndicator crate_index={crate_index} card_index={card_index}/>
) : (
<span style={{
'display': 'inline-block',
@ -63,8 +66,7 @@ export function SummaryCrateCard({crate_index, card_index}) {
}}>&nbsp;</span>
))}
{((options && options_data) ? (
<SummaryPopup id={card.id + "options"} options={options}
data={options_data}/>
<OptionsSummaryWrapper crate_index={crate_index} card_index={card_index}/>
) : (
<span style={{
'display': 'inline-block',

View File

@ -17,7 +17,7 @@ export function validateJSON(description) {
for (const crate of crates_raw) {
if (!crate.type || !crate.items || !(crate.type in crate_modes)) return false;
for (const card of crate.items) {
if (!(card.pn in pn_to_card)) return false;
if (!(card.pn in pn_to_card) || card.options === undefined) return false;
}
}
} catch (e) {
@ -36,7 +36,7 @@ export function JSONToCrates(description) {
items: Array.from(crate.items.map((card, _i) => ({
...pn_to_card(card.pn),
id: uuidv4(),
options_data: card.options
options_data: card.options || {}
}))),
warnings: [],
occupiedHP: 0,

View File

@ -5,6 +5,7 @@ import {data as shared_data, itemsUnfoldedList} from "./utils";
import {true_type_of} from "./options/utils";
import {v4 as uuidv4} from "uuid";
import {FillResources} from "./count_resources";
import {FillExtData} from "./options/utils";
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
import {Validation, validateEmail, validateNote, validateJSONInput} from "./validate";
import {CratesToJSON, JSONToCrates} from "./json_porter";
@ -67,10 +68,17 @@ const useImportJSON = ((set, get) => ({
closeImport: () => set(state => ({
importShouldOpen: false
})),
loadDescription: () => set(state => ({
_loadDescription: () => set(state => ({
importShouldOpen: false,
crates: JSONToCrates(state.importValue.value)
})),
loadDescription: () => {
get()._loadDescription()
get().crates.forEach((crate, _i) => {
get().fillExtData(crate.id)
get().fillWarnings(crate.id)
})
},
updateImportDescription: (new_description) => set(state => ({
importValue: {
value: new_description,
@ -254,14 +262,12 @@ const useCart = ((set, get) => ({
return {
crates: state.crates.map((crate, _i) => {
if (crate_to === crate_from && crate_to === crate.id) {
// TODO fix
//const the_card = {...crate[index_from]};
let items_copy = Array.from(crate.items);
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))
let item = items_copy.splice(index_from, 1)[0]
items_copy.splice(index_to, 0, item).filter((item, _) => !!item)
return {
...crate,
items: items_copy.toSpliced(index_to+1, 0, {...the_card}).filter((item, _) => !!item)
items: items_copy
}
} else if (crate_to === crate.id) {
return {
@ -341,6 +347,26 @@ const useCart = ((set, get) => ({
})
})),
fillExtData: (crate_id) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) {
let itemsCopy = Array.from(crate.items);
itemsCopy = itemsCopy.map((item, index) => {
if (!item.options) return item;
if (!item.options_data) item.options_data = {};
item.options_data.ext_data = FillExtData(itemsCopy, index);
return item;
});
return {
...crate,
items: Array.from(itemsCopy)
}
}
else return crate;
})
})),
totalOrderPrice: () => {
let sum = 0;
get().crates.forEach( (crate, _i) => {
@ -357,11 +383,13 @@ const useCart = ((set, get) => ({
newCrate: () => {
const crate_id = "crate" + get().crates.length;
get()._newCrate(crate_id)
get().fillExtData(crate_id);
get().fillWarnings(crate_id);
},
setCrateMode: (id, mode) => {
get()._setCrateMode(id, mode)
get().fillExtData(crate_id);
get().fillWarnings(id);
get().setActiveCrate(id);
},
@ -370,6 +398,7 @@ const useCart = ((set, get) => ({
const dest = crate_to || get().active_crate;
if (!dest) return {};
get()._addCardFromBacklog(dest, index_from, index_to)
get().fillExtData(dest);
get().fillWarnings(dest);
get().setActiveCrate(dest);
if (!just_mounted) {
@ -379,12 +408,17 @@ const useCart = ((set, get) => ({
moveCard: (crate_from, index_from, crate_to, index_to) => {
get()._moveCard(crate_from, index_from, crate_to, index_to);
get().fillExtData(crate_to);
get().fillWarnings(crate_to);
get().setActiveCrate(crate_to);
if (crate_from !== crate_to) get().fillWarnings(crate_from);
if (crate_from !== crate_to) {
get().fillExtData(crate_from);
get().fillWarnings(crate_from);
}
},
deleteCard: (crate_id, index) => {
get()._deleteCard(crate_id, index);
get().fillExtData(crate_id);
get().fillWarnings(crate_id);
if (crate_id === get().highlighted.crate && index === get().highlighted.card) get().highlightReset()
},
@ -395,6 +429,7 @@ const useCart = ((set, get) => ({
updateOptions: (crate_id, index, new_options) => {
get()._updateOptions(crate_id, index, new_options);
get().fillExtData(crate_id);
get().fillWarnings(crate_id);
}
}))

View File

@ -63,3 +63,32 @@ export const range = (start, end) => {
const length = end - start;
return Array.from({ length }, (_, i) => start + i);
}
export const move = (source, destination, droppableSource, droppableDestination) => {
console.log('==> move', source, destination);
const sourceClone = Array.from(source);
const destClone = Array.from(destination);
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
const result = {columns: {}};
result.columns[droppableSource.droppableId] = sourceClone;
result.columns[droppableDestination.droppableId] = destClone;
return result;
};
export const compareArraysWithIds = (a, b) =>
a.length === b.length &&
a.every((element, index) => element.id === b[index].id);
export const compareArraysLevelOne = (a, b) =>
a.length === b.length &&
a.every((element, index) => element === b[index]);
export function compareObjectsEmptiness(a, b) {
return (!a && !b) || (!(!a !== !b) && Object.getPrototypeOf(a) === Object.getPrototypeOf(b) &&
(Object.getPrototypeOf(a) !== Object.getPrototypeOf([]) || !!Object.keys(a).length === !!Object.keys(b).length))
}

View File

@ -20,7 +20,7 @@ module.exports = {
options: {
debug: false,
directives: {
render_count: true,
render_count: false,
},
params: {
ENV: process.env.NODE_ENV,