From 608f2536845a1bfbf9a509d13ee40da809de5fc3 Mon Sep 17 00:00:00 2001 From: Egor Savkin Date: Fri, 1 Nov 2024 17:32:20 +0800 Subject: [PATCH] WIP add horizontal items into the cart Signed-off-by: Egor Savkin --- static/js/shop/Cart.jsx | 2 - static/js/shop/CartHorizontal.jsx | 59 ++++++++++ static/js/shop/Crate.jsx | 7 +- static/js/shop/ProductCartItem.jsx | 4 +- static/js/shop/ProductCartItemHorizontal.jsx | 107 +++++++++++++++++++ static/js/shop/SummaryCrateCard.jsx | 18 ++-- static/js/shop/json_porter.js | 14 ++- static/js/shop/shop_store.js | 100 ++++++++++------- static/js/shop_data.js | 4 +- 9 files changed, 262 insertions(+), 53 deletions(-) create mode 100644 static/js/shop/CartHorizontal.jsx create mode 100644 static/js/shop/ProductCartItemHorizontal.jsx diff --git a/static/js/shop/Cart.jsx b/static/js/shop/Cart.jsx index 1876a35..33e7768 100644 --- a/static/js/shop/Cart.jsx +++ b/static/js/shop/Cart.jsx @@ -32,8 +32,6 @@ export function Cart({crate_index}) { = nbrSlots} key={item.id}/> ); }); diff --git a/static/js/shop/CartHorizontal.jsx b/static/js/shop/CartHorizontal.jsx new file mode 100644 index 0000000..41e5e76 --- /dev/null +++ b/static/js/shop/CartHorizontal.jsx @@ -0,0 +1,59 @@ +import React from 'react' +import {Droppable} from "@hello-pangea/dnd"; +import {cartStyle, compareArraysWithIds} from "./utils"; +import {ProductCartItemHorizontal} from "./ProductCartItemHorizontal"; +import {HORIZONTAL_CART_MARKER, useShopStore} from "./shop_store"; + +// #!render_count +import {useRenderCount} from "@uidotdev/usehooks"; + + +/** + * Component that displays a list of + */ +export function CartHorizontal({crate_index}) { + // #!render_count + const renderCount = useRenderCount(); + + const crate = useShopStore((state) => state.crates[crate_index], (a, b) => { + return compareArraysWithIds(a.h_items, b.h_items) + }); + + // #!render_count + console.log("CartHorizontal renders: ", renderCount) + + + const products = crate.h_items.map((item, index) => { + return ( + + ); + }); + + return ( + + {(provided, snapshot) => ( +
+ + {products} + + {provided.placeholder && ( +
+ {provided.placeholder} +
+ )} +
+ )} + +
+ ); +} \ No newline at end of file diff --git a/static/js/shop/Crate.jsx b/static/js/shop/Crate.jsx index 97eb454..35e83fc 100644 --- a/static/js/shop/Crate.jsx +++ b/static/js/shop/Crate.jsx @@ -4,6 +4,7 @@ import {CrateMode} from "./CrateMode"; import {CrateWarnings} from "./CrateWarnings"; import {useShopStore} from "./shop_store"; import {CrateOptions} from "./CrateOptions"; +import {CartHorizontal} from "./CartHorizontal"; // #!render_count import {useRenderCount} from "@uidotdev/usehooks"; @@ -28,7 +29,7 @@ export function Crate({crate_index}) { return (
{ - modes_order.includes(crate.crate_mode) ? ( + modes_order.includes(crate.crate_mode) && (
@@ -36,13 +37,15 @@ export function Crate({crate_index}) { Delete crate remove
- ) : <> + ) }
+ + diff --git a/static/js/shop/ProductCartItem.jsx b/static/js/shop/ProductCartItem.jsx index 559e692..22bfd27 100644 --- a/static/js/shop/ProductCartItem.jsx +++ b/static/js/shop/ProductCartItem.jsx @@ -13,7 +13,7 @@ import {useRenderCount} from "@uidotdev/usehooks"; * Component that renders a product. * Used in the crate */ -export function ProductCartItem({card_index, crate_index, first, last}) { +export function ProductCartItem({card_index, crate_index}) { // #!render_count const renderCount = useRenderCount(); @@ -73,8 +73,6 @@ export function ProductCartItem({card_index, crate_index, first, last}) { )}
diff --git a/static/js/shop/ProductCartItemHorizontal.jsx b/static/js/shop/ProductCartItemHorizontal.jsx new file mode 100644 index 0000000..0c97098 --- /dev/null +++ b/static/js/shop/ProductCartItemHorizontal.jsx @@ -0,0 +1,107 @@ +import React from 'react' +import {Draggable} from "@hello-pangea/dnd"; +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"; + +/** + * Component that renders a product. + * Used in the crate + */ +export function ProductCartItemHorizontal({card_index, crate_index}) { + // #!render_count + const renderCount = useRenderCount(); + + + const card = useShopStore((state) => state.crates[crate_index].h_items[card_index], + (a, b) => a.id === b.id); + + const card_show_warnings = useShopStore(state => state.crates[crate_index].h_items[card_index].show_warnings, compareObjectsEmptiness); + const card_counted_resources = useShopStore(state => state.crates[crate_index].h_items[card_index].counted_resources, compareObjectsEmptiness); + + const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card); + const warnings_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled); + const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options); + const crate_id = useShopStore((state) => state.crates[crate_index].id); + const setHighlight = useShopStore((state) => state.highlightCard); + const removeHighlight = useShopStore((state) => state.highlightReset); + const onCardRemove = useShopStore((state) => state.deleteCard); + + // #!render_count + console.log("ProductCartItem renders: ", renderCount) + + + const options = use_options && card && card[use_options] && card[use_options].length > 0; + const warnings = !warnings_disabled && card_show_warnings && card_show_warnings.length > 0; + const resources = !warnings_disabled && card_counted_resources && card_counted_resources.length > 0; + + return ( + + + {(provided, snapshot) => ( +
setHighlight(crate_id, card_index)} + onMouseLeave={removeHighlight} + > + + {/* warning container */} + +
+ {warnings && + () + } + + {options && ( + + )} +
+ +
{card.name_number}
+ +
setHighlight(crate_id, card_index)} + onClick={() => setHighlight(crate_id, card_index)} + > + + {card.name} +
+ + {/* remove container */} + {/*
onCardRemove(crate_id, card_index)}> + + rm + +

Remove

+
*/} + +
+ )} + +
+ ); +} diff --git a/static/js/shop/SummaryCrateCard.jsx b/static/js/shop/SummaryCrateCard.jsx index 00c5658..c68a04d 100644 --- a/static/js/shop/SummaryCrateCard.jsx +++ b/static/js/shop/SummaryCrateCard.jsx @@ -8,7 +8,7 @@ import {OptionsSummaryWrapper} from "./OptionsWrapper"; import {useRenderCount} from "@uidotdev/usehooks"; -export function SummaryCrateCard({crate_index, card_index}) { +export function SummaryCrateCard({crate_index, card_index, horizontal}) { // #!render_count const renderCount = useRenderCount(); @@ -19,10 +19,16 @@ 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], + const card = useShopStore((state) => + horizontal ? state.crates[crate_index].h_items[card_index] : state.crates[crate_index].items[card_index], (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); + // additional hooks for updating warning and options + const card_show_warnings = useShopStore(state => + horizontal ? state.crates[crate_index].h_items[card_index].show_warnings : state.crates[crate_index].items[card_index].show_warnings, + compareObjectsEmptiness); + const card_options_data = useShopStore(state => + horizontal ? state.crates[crate_index].h_items[card_index].options_data : state.crates[crate_index].items[card_index].options_data, + compareObjectsEmptiness); const warnings_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled); const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options); @@ -38,8 +44,8 @@ export function SummaryCrateCard({crate_index, card_index}) { setHighlight(crate_id, card_index)} - onMouseEnter={() => setHighlight(crate_id, card_index)} + onClick={() => setHighlight(crate_id, card_index, horizontal)} + onMouseEnter={() => setHighlight(crate_id, card_index, horizontal)} onMouseLeave={() => resetHighlight()}>
{`${card.name_number} ${card.name} ${card.name_codename}`}
diff --git a/static/js/shop/json_porter.js b/static/js/shop/json_porter.js index 8da0b7b..9652717 100644 --- a/static/js/shop/json_porter.js +++ b/static/js/shop/json_porter.js @@ -21,10 +21,13 @@ export function validateJSON(description) { try { for (const crate of crates_raw) { - if (!crate.type || !crate.items || !crate.options || !(crate.type in crate_modes)) return false; + if (!crate.type || !crate.items || !crate.h_items || !crate.options || !(crate.type in crate_modes)) return false; for (const card of crate.items) { if (!(card.pn in pn_to_card) || card.options === undefined) return false; } + for (const card of crate.h_items) { + if (!(card.pn in pn_to_card) || card.options === undefined) return false; + } } } catch (e) { return false; @@ -50,6 +53,11 @@ export function JSONToCrates(description) { id: uuidv4(), options_data: card.options || {} }))), + h_items: Array.from(crate.h_items.map((card, _i) => ({ + ...pn_to_card(card.pn), + id: uuidv4(), + options_data: card.options || {} + }))), warnings: [], occupiedHP: 0, }))); @@ -73,6 +81,10 @@ export function CratesToJSON(crates) { pn: card.name_number, options: (card.options_data && card[crateParams(crate.crate_mode).options]) ? FilterOptions(card[crateParams(crate.crate_mode).options], card.options_data) : null }))), + h_items: Array.from(crate.h_items.map((card, _) => ({ + pn: card.name_number, + options: (card.options_data && card[crateParams(crate.crate_mode).options]) ? FilterOptions(card[crateParams(crate.crate_mode).options], card.options_data) : null + }))), type: crate.crate_mode, options: FilterOptions(crateOptions, crate.options_data) }))), diff --git a/static/js/shop/shop_store.js b/static/js/shop/shop_store.js index 3bddac2..0afc426 100644 --- a/static/js/shop/shop_store.js +++ b/static/js/shop/shop_store.js @@ -13,6 +13,8 @@ import {ProcessOptionsToData} from "./options/Options"; import {DomainedRFQMessages} from "./Domained"; +export const HORIZONTAL_CART_MARKER = "_h"; + const cards_to_pn_map = (cards) => { let result = {}; Object.entries(cards).forEach(([key, card], _i) => { result[card.name_number] = key}) @@ -23,6 +25,12 @@ const toArray = (arg) => { return Array.isArray(arg) ? arg : [arg]; }; +const unwrapCrateId = (crate_id= "") => { + return crate_id.endsWith(HORIZONTAL_CART_MARKER) ? [true, crate_id.substring(0, crate_id.length - HORIZONTAL_CART_MARKER.length)] : [false, crate_id] +} + +const whichItems = (horizontal = false) => horizontal ? "h_items" : "items" + const useCatalog = ((set, get) => ({ cards: shared_data.items, groups: shared_data.columns.catalog, @@ -37,21 +45,24 @@ const useCatalog = ((set, get) => ({ const useOptionsNotification = ((set, get) => ({ notificationCrateId: null, notificationCardIndex: null, + notificationHorizontal: false, notificationTimer: null, - _showNotification: (crate_id, card_index) => set(state => ({ + _showNotification: (crate_id, card_index, horizontal) => set(state => ({ notificationCrateId: crate_id, notificationCardIndex: card_index, + notificationHorizontal: horizontal, notificationTimer: setTimeout(() => { state.hideNotification() }, 5000) })), - showNotification: (crate_id, card_index) => { + showNotification: (crate_id, card_index, horizontal) => { get().hideNotification() - setTimeout(() => get()._showNotification(crate_id, card_index), 100); + setTimeout(() => get()._showNotification(crate_id, card_index, horizontal), 100); }, hideNotification: () => set(state => ({ notificationCrateId: null, notificationCardIndex: null, + notificationHorizontal: false, notificationTimer: (state.notificationTimer && clearTimeout(state.notificationTimer)) || null, })) })); @@ -215,7 +226,7 @@ const useImportJSON = ((set, get) => ({ get().fillExtCrateData(crate.id); }); get()._updateTotalOrderPrice(); - get().showNotification(get().active_crate, null); + get().showNotification(get().active_crate, null, false); }, updateImportDescription: (new_description) => set(state => ({ importValue: { @@ -332,15 +343,17 @@ const useSubmitForm = ((set, get) => ({ const useHighlighted = ((set, get) => ({ highlighted: { crate: "", - card: 0 + card: 0, + horizontal: false }, highlightedTimer: null, // #!if disable_card_highlight === false - highlightCard: (crate_id, index) => set(state => ({ + highlightCard: (crate_id, index, horizontal) => set(state => ({ highlighted: { crate: crate_id, - card: index + card: index, + horizontal: horizontal }, highlightedTimer: (!!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null) || (state.isTouch && setTimeout(() => { get().highlightReset() @@ -349,7 +362,8 @@ const useHighlighted = ((set, get) => ({ highlightReset: () => set(state => ({ highlighted: { crate: "", - card: 0 + card: 0, + horizontal: false }, highlightedTimer: !!state.highlightedTimer ? clearTimeout(state.highlightedTimer) : null })), @@ -388,17 +402,18 @@ const useCart = ((set, get) => ({ }) })), setActiveCrate: (id) => set(state => ({active_crate: id})), - _addCardFromCatalog: (crate_to, index_from, index_to) => set(state => { + _addCardFromCatalog: (crate_to, index_from, index_to, horizontal) => set(state => { + const whichH = whichItems(horizontal) const take_from = toArray(index_from).map((item, _i) => (state.cards_list[item])); const dest = crate_to || state.active_crate; if (!dest) return {}; return { crates: state.crates.map((crate, _i) => { if (dest === crate.id) { - index_to = index_to != null ? index_to : crate.items.length; + index_to = index_to != null ? index_to : crate[whichH].length; return { ...crate, - items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => { + [whichH]: crate[whichH].toSpliced(index_to, 0, ...take_from.map((card_name, _) => { return {...state.cards[card_name], id: uuidv4()} })) } @@ -406,39 +421,41 @@ const useCart = ((set, get) => ({ }) } }), - _moveCard: (crate_from, index_from, crate_to, index_to) => set(state => { - const the_card = state.crates.find((crate, _) => crate_from === crate.id ).items[index_from]; + _moveCard: (crate_from, index_from, crate_to, index_to, horizontal) => set(state => { + const whichH = whichItems(horizontal) + const the_card = state.crates.find((crate, _) => crate_from === crate.id )[whichH][index_from]; return { crates: state.crates.map((crate, _i) => { if (crate_to === crate_from && crate_to === crate.id) { - let items_copy = Array.from(crate.items); + let items_copy = Array.from(crate[whichH]); let item = items_copy.splice(index_from, 1)[0] items_copy.splice(index_to, 0, item).filter((item, _) => !!item) return { ...crate, - items: items_copy + [whichH]: items_copy } } else if (crate_to === crate.id) { return { ...crate, - items: crate.items.toSpliced(index_to, 0, the_card) + [whichH]: crate[whichH].toSpliced(index_to, 0, the_card) } } else if (crate_from === crate.id) { return { ...crate, - items: crate.items.toSpliced(index_to, 1) + [whichH]: crate[whichH].toSpliced(index_to, 1) } } else return crate; }) } }), - _deleteCard: (crate_id, index) => set(state => ({ + _deleteCard: (crate_id, index, horizontal) => set(state => ({ crates: state.crates.map((crate, _i) => { if (crate_id === crate.id) { + const whichH = whichItems(horizontal) return { ...crate, - items: crate.items.toSpliced(index, 1) + [whichH]: crate[whichH].toSpliced(index, 1) } } else return crate; @@ -449,7 +466,8 @@ const useCart = ((set, get) => ({ if (id === crate.id) { return { ...crate, - items: [] + items: [], + h_items: [] } } else return crate; @@ -458,10 +476,11 @@ const useCart = ((set, get) => ({ clearAll: () => set(state => ({ crates: state._defaultCrates })), - _updateOptions: (crate_id, index, new_options) => set(state => ({ + _updateOptions: (crate_id, index, new_options, horizontal) => set(state => ({ crates: state.crates.map((crate, _i) => { if (crate_id === crate.id) { - let itemsCopy = Array.from(crate.items); + const whichH = whichItems(horizontal) + let itemsCopy = Array.from(crate[whichH]); itemsCopy[index] = { ...itemsCopy[index], options_data: { @@ -470,7 +489,7 @@ const useCart = ((set, get) => ({ }}; return { ...crate, - items: itemsCopy + [whichH]: itemsCopy } } else return crate; @@ -524,7 +543,7 @@ const useCart = ((set, get) => ({ sum += get().crate_modes[crate.crate_mode].price; const crate_options = ProcessOptionsToData({options: get().crate_prices, data: crate.options_data || {}}); sum += crate_options ? crate_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0; - crate.items.forEach((item, _) => { + crate.items.concat(crate.h_items).forEach((item, _) => { sum += item.price; }); }); @@ -563,13 +582,14 @@ const useCart = ((set, get) => ({ addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => { const isCrateless = toArray(index_from).some(value => get().getCardDescription(value).crateless === true); - const dest = isCrateless ? "spare" : crate_to || get().active_crate; + const isHorizontal = toArray(index_from).some(value => !get().getCardDescription(value).image); + const dest = isCrateless ? "spare" : crate_to || get().active_crate; if (!dest) { console.warn("No destination"); get().noDestinationWarning(); return {}; } - get().showNotification(dest, index_to); + get().showNotification(dest, index_to, isHorizontal); get()._addCardFromCatalog(dest, index_from, index_to) get().fillExtData(dest); get().fillWarnings(dest); @@ -581,22 +601,25 @@ 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); + const [isHorizontal, crateFrom] = unwrapCrateId(crate_from) + const [_, crateTo] = unwrapCrateId(crate_to) + get()._moveCard(crateFrom, index_from, crateTo, index_to, isHorizontal); + get().fillExtData(crateTo); + get().fillWarnings(crateTo); + get().setActiveCrate(crateTo); get()._updateTotalOrderPrice(); - if (crate_from !== crate_to) { - get().fillExtData(crate_from); - get().fillWarnings(crate_from); + if (crateFrom !== crate_to) { + get().fillExtData(crateFrom); + get().fillWarnings(crateFrom); } }, deleteCard: (crate_id, index) => { - get()._deleteCard(crate_id, index); - get().fillExtData(crate_id); - get().fillWarnings(crate_id); + const [isHorizontal, crateId] = unwrapCrateId(crate_id); + get()._deleteCard(crateId, index, isHorizontal); + get().fillExtData(crateId); + get().fillWarnings(crateId); get()._updateTotalOrderPrice(); - if (crate_id === get().highlighted.crate && index === get().highlighted.card) get().highlightReset() + if (crateId === get().highlighted.crate && index === get().highlighted.card) get().highlightReset() }, clearCrate: (id) => { get()._clearCrate(id); @@ -605,7 +628,8 @@ const useCart = ((set, get) => ({ }, updateOptions: (crate_id, index, new_options) => { - get()._updateOptions(crate_id, index, new_options); + const [isHorizontal, crateId] = unwrapCrateId(crate_id); + get()._updateOptions(crate_id, index, new_options, isHorizontal); get().fillExtData(crate_id); get().fillWarnings(crate_id); }, diff --git a/static/js/shop_data.js b/static/js/shop_data.js index ae86909..a63f302 100644 --- a/static/js/shop_data.js +++ b/static/js/shop_data.js @@ -1449,7 +1449,7 @@ const shop_data = { name_number: 'AFWS', name_codename: '', price: 800, - image: '/images/shop/graphic-03_AFWS.svg', + image: null, specs: [ "Artiq Firmware Service for one variant for one year.", "Includes support at helpdesk.", @@ -1530,6 +1530,7 @@ const shop_data = { crate_mode: "rack", fan_tray: false, items: [], + h_items: [], warnings: [], occupiedHP: 0, }, @@ -1538,6 +1539,7 @@ const shop_data = { name: "Spare items", crate_mode: "no_crate", items: [], + h_items: [], warnings: [], occupiedHP: 0, }