From 363e2b3539ce32bbf29d2b92ecd158209e38cd93 Mon Sep 17 00:00:00 2001 From: Matt Watson Date: Fri, 11 Feb 2022 09:18:46 -0500 Subject: [PATCH 01/55] scaffold out cart context provider fix tabs fix tabs --- src/components/shopping-cart/context.js | 13 +++++++++++++ src/components/shopping-cart/index.js | 1 + 2 files changed, 14 insertions(+) create mode 100644 src/components/shopping-cart/context.js create mode 100644 src/components/shopping-cart/index.js diff --git a/src/components/shopping-cart/context.js b/src/components/shopping-cart/context.js new file mode 100644 index 00000000..045928ad --- /dev/null +++ b/src/components/shopping-cart/context.js @@ -0,0 +1,13 @@ +import { createContext, useContext } from 'react' + +const ShoppingCartContext = createContext({}) + +export const useShoppingCart = () => useContext(ShoppingCartContext) + +export const ShoppingCartProvider = ({ children }) => { + return ( + + { children } + + ) +} diff --git a/src/components/shopping-cart/index.js b/src/components/shopping-cart/index.js new file mode 100644 index 00000000..630b4d7e --- /dev/null +++ b/src/components/shopping-cart/index.js @@ -0,0 +1 @@ +export * from './context' From f275bb3bd7bcc4b05b81d6e9560f4ad23d91aace Mon Sep 17 00:00:00 2001 From: Matt Watson Date: Fri, 11 Feb 2022 11:31:13 -0500 Subject: [PATCH 02/55] wrap application in context provider --- src/app.js | 17 ++++++++++------- src/components/shopping-cart/context.js | 6 ++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/app.js b/src/app.js index 6ca6a13c..bf7f2530 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,7 @@ import { LocationProvider, Router as ReachRouter } from '@reach/router' import { EnvironmentProvider, ActivityProvider, AppProvider, InstanceProvider, AnalyticsProvider, useEnvironment } from './contexts' import { Layout } from './components/layout' +import { ShoppingCartProvider } from './components/shopping-cart' import { NotFoundView } from './views' import { useEffect } from 'react' @@ -9,13 +10,15 @@ const ContextProviders = ({ children }) => { - - - - {children} - - - + + + + + {children} + + + + diff --git a/src/components/shopping-cart/context.js b/src/components/shopping-cart/context.js index 045928ad..e8225a04 100644 --- a/src/components/shopping-cart/context.js +++ b/src/components/shopping-cart/context.js @@ -1,12 +1,14 @@ -import { createContext, useContext } from 'react' +import { createContext, useContext, useState } from 'react' const ShoppingCartContext = createContext({}) export const useShoppingCart = () => useContext(ShoppingCartContext) export const ShoppingCartProvider = ({ children }) => { + const [cart, setCart] = useState() + return ( - + { children } ) From b2bcd0f7d5f10ec8d8d7f58b368ba5ee26221ca5 Mon Sep 17 00:00:00 2001 From: Matt Watson Date: Fri, 11 Feb 2022 11:37:49 -0500 Subject: [PATCH 03/55] add shopping cart link to main menu --- src/components/layout/layout.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index d5bc1854..36c230df 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -5,6 +5,7 @@ import { logoutHandler } from '../../api/'; import { MobileMenu } from './menu'; import { SidePanel } from '../side-panel/side-panel'; import './layout.css'; +import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' const { Header, Content, Footer } = AntLayout @@ -18,11 +19,21 @@ export const Layout = ({ children }) => {
{context !== undefined ? {context.brand} : } - - - - {routes.map(m => m['text'] !== '' && {m.text})} - {context.workspaces_enabled === 'true' && } + { + routes.map(m => m['text'] !== '' && ( + + {m.text} + ) + ) + } + { + context.workspaces_enabled === 'true' && ( + + ) + } + } />
From bd5cbf319cbdecf975bdb6371182a4af6ac1b617 Mon Sep 17 00:00:00 2001 From: Matt Watson Date: Fri, 11 Feb 2022 11:38:40 -0500 Subject: [PATCH 04/55] create shopping cart view --- src/views/cart.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/views/cart.js diff --git a/src/views/cart.js b/src/views/cart.js new file mode 100644 index 00000000..793c7b78 --- /dev/null +++ b/src/views/cart.js @@ -0,0 +1,12 @@ +import { Fragment } from 'react' +import { Typography } from 'antd' + +const { Title } = Typography + +export const ShoppingCartView = () => { + return ( + + Shopping Cart + + ) +} \ No newline at end of file From 4576ff10dbcd0f5b3549571ada940e20a811db81 Mon Sep 17 00:00:00 2001 From: Matt Watson Date: Fri, 11 Feb 2022 11:45:21 -0500 Subject: [PATCH 05/55] tie together views, menus, and routes --- src/components/layout/layout.js | 1 - src/components/shopping-cart/context.js | 20 +++++++++++++------- src/contexts/environment-context.js | 5 ++++- src/views/cart.js | 6 ++++++ src/views/index.js | 7 ++++--- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index 36c230df..fbed880d 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -33,7 +33,6 @@ export const Layout = ({ children }) => { ) } - } /> diff --git a/src/components/shopping-cart/context.js b/src/components/shopping-cart/context.js index e8225a04..6a107667 100644 --- a/src/components/shopping-cart/context.js +++ b/src/components/shopping-cart/context.js @@ -4,12 +4,18 @@ const ShoppingCartContext = createContext({}) export const useShoppingCart = () => useContext(ShoppingCartContext) +const initialCartContents = { + concepts: [], + variables: [], + studies: [], +} + export const ShoppingCartProvider = ({ children }) => { - const [cart, setCart] = useState() - - return ( - - { children } - - ) + const [cart, setCart] = useState(initialCartContents) + + return ( + + { children } + + ) } diff --git a/src/contexts/environment-context.js b/src/contexts/environment-context.js index 229fcc9b..324d585b 100644 --- a/src/contexts/environment-context.js +++ b/src/contexts/environment-context.js @@ -6,8 +6,10 @@ import { SupportView, LoadingView, SearchView, - SplashScreenView + SplashScreenView, + ShoppingCartView, } from '../views' +import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' // Setup global csrf token axios.defaults.xsrfHeaderName = "X-CSRFToken"; @@ -55,6 +57,7 @@ export const EnvironmentProvider = ({ children }) => { baseRoutes.push({ path: '/', text: '', Component: SupportView }) } baseRoutes.push({ path: '/support', text: 'Support', Component: SupportView }) + baseRoutes.push({ path: '/cart', text: , Component: ShoppingCartView }) return baseRoutes; } diff --git a/src/views/cart.js b/src/views/cart.js index 793c7b78..71302e6f 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,12 +1,18 @@ import { Fragment } from 'react' import { Typography } from 'antd' +import { useShoppingCart } from '../components/shopping-cart' const { Title } = Typography export const ShoppingCartView = () => { + const { cart } = useShoppingCart() + return ( Shopping Cart +
+        { JSON.stringify(cart, null, 2) }
+      
) } \ No newline at end of file diff --git a/src/views/index.js b/src/views/index.js index 46f93ad1..f7eeca23 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -1,6 +1,7 @@ -export * from './support' +export * from './cart' +export * from './loading' export * from './not-found' export * from './search' +export * from './splash-screen' +export * from './support' export * from './workspaces' -export * from './loading' -export * from './splash-screen' \ No newline at end of file From 1446b857ec5502b9d541116c1b9511809b70ed98 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 19 Jul 2022 13:07:04 -0400 Subject: [PATCH 06/55] Upgrade react-scripts and fix associated errors --- package.json | 2 +- src/contexts/analytics-context/analytics-context.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 82128e51..cca2abb6 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-infinite-scroll-component": "^6.1.0", - "react-scripts": "4.0.3", + "react-scripts": "^5.0.1", "styled-components": "^5.3.0", "timeago-react": "^3.0.2", "use-debounce": "^7.0.0", diff --git a/src/contexts/analytics-context/analytics-context.js b/src/contexts/analytics-context/analytics-context.js index ffde2b74..0f1ba6b3 100644 --- a/src/contexts/analytics-context/analytics-context.js +++ b/src/contexts/analytics-context/analytics-context.js @@ -2,9 +2,10 @@ import React, { createContext, useContext } from 'react'; import { useEnvironment } from '../environment-context'; import { AnalyticsEvents } from './events'; import { MixPanelAnalytics, GAAnalytics, NoAnalytics } from 'helx-analytics'; -import { version } from 'helx-analytics/package.json'; import { getUser } from '../../api'; +const version = require('helx-analytics/package.json').version + export const AnalyticsContext = createContext(); export const AnalyticsProvider = ({ children }) => { From db334fceabb74589c232ea893214e2e15c6e723f Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 19 Jul 2022 15:08:59 -0400 Subject: [PATCH 07/55] Rework shopping cart into popover + full view. Begin templating its implementation. --- src/app.js | 2 +- src/components/layout/layout.css | 17 ++++++- src/components/layout/layout.js | 43 +++++++++++++--- .../search/concept-card/concept-card.js | 14 ++++-- src/components/shopping-cart/add-to-cart.js | 18 +++++++ src/components/shopping-cart/context.js | 21 -------- src/components/shopping-cart/index.js | 3 +- .../shopping-cart/shopping-cart-popover.js | 32 ++++++++++++ src/contexts/environment-context.js | 2 +- src/contexts/index.js | 3 +- src/contexts/shopping-cart-context.js | 50 +++++++++++++++++++ src/index.css | 2 +- src/views/cart.js | 2 +- 13 files changed, 169 insertions(+), 40 deletions(-) create mode 100644 src/components/shopping-cart/add-to-cart.js delete mode 100644 src/components/shopping-cart/context.js create mode 100644 src/components/shopping-cart/shopping-cart-popover.js create mode 100644 src/contexts/shopping-cart-context.js diff --git a/src/app.js b/src/app.js index a1435089..d4abbca8 100644 --- a/src/app.js +++ b/src/app.js @@ -2,7 +2,7 @@ import { useEffect } from 'react' import { LocationProvider, Router as ReachRouter, globalHistory, useLocation } from '@reach/router' import { EnvironmentProvider, ActivityProvider, AppProvider, InstanceProvider, AnalyticsProvider, useEnvironment, useAnalytics } from './contexts' import { Layout } from './components/layout' -import { ShoppingCartProvider } from './components/shopping-cart' +import { ShoppingCartProvider } from './contexts' import { NotFoundView } from './views' const ContextProviders = ({ children }) => { diff --git a/src/components/layout/layout.css b/src/components/layout/layout.css index a6ca167b..1ead1566 100644 --- a/src/components/layout/layout.css +++ b/src/components/layout/layout.css @@ -24,6 +24,19 @@ position: absolute; } -.logout-button { - margin: 0 1rem 0 0.5rem; +.shopping-cart-button { + margin-left: 8px; + margin-right: 20px; } + +.shopping-cart-button, .logout-button { + /* margin-left: 16px; */ +} + +.icon-btn { + cursor: pointer; + transition: color 0.3s ease; +} +.icon-btn:hover { + color: #1890ff !important; +} \ No newline at end of file diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index bdeb6446..5e5e6835 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -1,22 +1,29 @@ -import { Layout as AntLayout, Button, Menu, Grid } from 'antd' +import { useState } from 'react'; +import { Layout as AntLayout, Button, Menu, Grid, Divider, Badge, Popover, Typography, Tag, Space } from 'antd' +import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' import { useLocation, Link } from '@reach/router' import { useEnvironment, useAnalytics } from '../../contexts'; import { logoutHandler } from '../../api/'; import { MobileMenu } from './menu'; import { SidePanel } from '../side-panel/side-panel'; +import { ShoppingCartPopover } from '../shopping-cart'; import './layout.css'; -import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' +const { Text, Title } = Typography const { Header, Content, Footer } = AntLayout const { useBreakpoint } = Grid export const Layout = ({ children }) => { + const [showShoppingCart, setShowShoppingCart] = useState(false) + const { helxAppstoreUrl, routes, context, basePath } = useEnvironment() const { analyticsEvents } = useAnalytics() const { md } = useBreakpoint() const baseLinkPath = context.workspaces_enabled === 'true' ? '/helx' : '' const location = useLocation(); + const logoutButton = context.workspaces_enabled === 'true' + const logout = () => { analyticsEvents.logout() logoutHandler(helxAppstoreUrl) @@ -27,16 +34,38 @@ export const Layout = ({ children }) => {
{context !== undefined ? {context.brand} : } {md ? ( -
+
- - - {routes.map(m => m['text'] !== '' && ( {m.text} ))} - {context.workspaces_enabled === 'true' && ( + {/* */} +
+ + + +
+ {logoutButton && (
diff --git a/src/components/search/concept-card/concept-card.js b/src/components/search/concept-card/concept-card.js index 467b1c62..6dbedfeb 100644 --- a/src/components/search/concept-card/concept-card.js +++ b/src/components/search/concept-card/concept-card.js @@ -1,13 +1,14 @@ import { Fragment, useState, useEffect, forwardRef } from 'react' import PropTypes from 'prop-types' -import { Card } from 'antd' -import { ExpandOutlined as ViewIcon } from '@ant-design/icons' +import classNames from 'classnames' +import { Card, Space } from 'antd' +import { ShoppingCartOutlined as ShoppingCartIcon, ExpandOutlined as ViewIcon } from '@ant-design/icons' import { useHelxSearch } from '../' import { OverviewTab } from './overview-tab' import { StudiesTab } from './studies-tab' import { KnowledgeGraphsTab } from './knowledge-graphs-tab' +import { AddToCart } from '../../shopping-cart' import { useAnalytics } from '../../../contexts' -import classNames from 'classnames' import './concept-card.css' export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=ViewIcon, className="" }, ref) => { @@ -39,7 +40,12 @@ export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=V tabProps={{size: 'small'}} activeTabKey={currentTab} onTabChange={key => setCurrentTab(key)} - extra={ icon && } + extra={ +
+ + { icon && } +
+ } actions={ [
] } > { tabContents[currentTab] } diff --git a/src/components/shopping-cart/add-to-cart.js b/src/components/shopping-cart/add-to-cart.js new file mode 100644 index 00000000..602f9b73 --- /dev/null +++ b/src/components/shopping-cart/add-to-cart.js @@ -0,0 +1,18 @@ +import { Button } from 'antd' +import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' + +export const AddToCart = ({ asIcon=false, style, ...props }) => { + if (asIcon) return ( + + ) + return ( + + + + } + placement="bottomLeft" + trigger="click" + visible={ visible } + onVisibleChange={ onVisibleChange } + > + { children } + + + ) +} \ No newline at end of file diff --git a/src/contexts/environment-context.js b/src/contexts/environment-context.js index 9d7949e0..b3a9dcfb 100644 --- a/src/contexts/environment-context.js +++ b/src/contexts/environment-context.js @@ -59,7 +59,7 @@ export const EnvironmentProvider = ({ children }) => { baseRoutes.push({ path: '/', text: '', Component: SupportView }) } baseRoutes.push({ path: '/support', text: 'Support', Component: SupportView }) - baseRoutes.push({ path: '/cart', text: , Component: ShoppingCartView }) + baseRoutes.push({ path: '/cart', text: '', Component: ShoppingCartView }) return baseRoutes; } diff --git a/src/contexts/index.js b/src/contexts/index.js index 4e8d9172..9b2b753a 100644 --- a/src/contexts/index.js +++ b/src/contexts/index.js @@ -3,4 +3,5 @@ export * from './auth-context' export * from './environment-context' export * from './instance-context' export * from './activity-context' -export * from './analytics-context' \ No newline at end of file +export * from './analytics-context' +export * from './shopping-cart-context' \ No newline at end of file diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js new file mode 100644 index 00000000..68d3263e --- /dev/null +++ b/src/contexts/shopping-cart-context.js @@ -0,0 +1,50 @@ +import { createContext, useCallback, useContext, useEffect, useState } from 'react' +import { useLocalStorage } from '../hooks/use-local-storage' + +const ShoppingCartContext = createContext({}) + +const cartBlueprint = { + name: undefined, + createdTime: undefined, + modifiedTime: undefined, + concepts: [], + variables: [], + studies: [], +} + +export const ShoppingCartProvider = ({ children }) => { + const createCart = (name) => { + return { + ...cartBlueprint, + name, + createdTime: Date.now(), + modifiedTime: Date.now() + } + } + const [carts, setCarts] = useLocalStorage("shoppingCarts", { + "default": createCart("Default") + }) + // const [activeCart, setActiveCart] = useState("default") + + const addCart = (id, name) => { + setCarts({ + ...carts, + [id]: createCart(name) + }) + } + const removeCart = (id) => { + const { [id]: removed, ...newCarts } = carts + setCarts(newCarts) + } + + return ( + + { children } + + ) +} + +export const useShoppingCart = () => useContext(ShoppingCartContext) \ No newline at end of file diff --git a/src/index.css b/src/index.css index 5d845d72..e0677e72 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,4 @@ -@import url('antd/dist/antd.css'); +@import 'antd/dist/antd.css'; *, *::before, *::after { box-sizing: border-box; diff --git a/src/views/cart.js b/src/views/cart.js index 71302e6f..a986f694 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,6 +1,6 @@ import { Fragment } from 'react' import { Typography } from 'antd' -import { useShoppingCart } from '../components/shopping-cart' +import { useShoppingCart } from '../contexts' const { Title } = Typography From 163b11df054c20b55c4c401941df94a26806395b Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 19 Jul 2022 18:16:42 -0400 Subject: [PATCH 08/55] Adds basic functionality to shopping cart popup + context tweaks --- .../shopping-cart/shopping-cart-popover.css | 20 +++ .../shopping-cart/shopping-cart-popover.js | 153 ++++++++++++++++-- src/contexts/shopping-cart-context.js | 40 +++-- 3 files changed, 187 insertions(+), 26 deletions(-) create mode 100644 src/components/shopping-cart/shopping-cart-popover.css diff --git a/src/components/shopping-cart/shopping-cart-popover.css b/src/components/shopping-cart/shopping-cart-popover.css new file mode 100644 index 00000000..cc7ff78f --- /dev/null +++ b/src/components/shopping-cart/shopping-cart-popover.css @@ -0,0 +1,20 @@ +.shopping-cart-popover-buttons { + border-top: 1px solid #f0f0f0; + padding-top: 16px; + justify-content: space-around; +} + +.manage-cart-list { + /* border-left: 1px solid #f0f0f0; */ +} +.manage-cart-item { + display: flex !important; + align-items: center; + justify-content: space-between; + margin-top: 8px; + border: none !important; + /* border-left: 1px solid #f0f0f0 !important; */ +} +.manage-cart-item:first-child { + margin-top: 0; +} diff --git a/src/components/shopping-cart/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover.js index 8110bda5..4b631b0d 100644 --- a/src/components/shopping-cart/shopping-cart-popover.js +++ b/src/components/shopping-cart/shopping-cart-popover.js @@ -1,29 +1,158 @@ -import { Badge, Button, Popover, Space, List, Typography } from 'antd' +import { Fragment, useEffect, useState } from 'react' +import { Badge, Button, Popover, Space, List, Typography, Input } from 'antd' +import { useShoppingCart } from '../../contexts/' +import './shopping-cart-popover.css' -const { Title, Text } = Typography +const { Title, Text, Paragraph } = Typography + +const ManageCarts = ({ close }) => { + const { carts, activeCart, setActiveCart, removeCart } = useShoppingCart() + + const selectCart = (cart) => { + setActiveCart(cart.name) + close() + } + + return ( + + ( + + + {cart.name} ({cart.getCount()}) + + + + {/* */} + + {/* */} + + + + )} + /> + + ) +} + +const CartCreator = ({ cartName, setCartName }) => { + return ( + + + Cart name + setCartName(e.target.value) } /> + + + Add items to a cart to... + + + ) +} + +const CartList = () => { + return ( + + ) +} + +const ShoppingCartPopoverContent = () => { + const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() + const [manageCarts, setManageCarts] = useState(false) + const [creatingCart, setCreatingCart] = useState(false) + + const [cartName, setCartName] = useState("") + + useEffect(() => { + if (creatingCart) { + const highestExistingDefault = carts + .map((cart) => /Shopping Cart (?\d+)/.exec(cart.name)?.groups.num) + .filter((match) => match !== undefined) + .sort((a, b) => b - a)[0] + const defaultName = `Shopping Cart ${highestExistingDefault !== undefined ? parseInt(highestExistingDefault) + 1 : 1}` + setCartName(defaultName)} + }, [creatingCart]) + + const createShoppingCart = () => { + addCart(cartName) + setCreatingCart(false) + setManageCarts(false) + setActiveCart(cartName) + } + + return ( + + { creatingCart ? ( +
+ Create a new cart +
+ ) : manageCarts ? ( +
+ Manage carts +
+ ) : ( +
+ { activeCart.name } + setManageCarts(true) }>View carts +
+ )} + { creatingCart ? ( + + ) : manageCarts ? ( + setManageCarts(false) } /> + ) : ( + + )} + { creatingCart ? ( + + + + + ) : ( + + + + + )} +
+ ) +} export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { + // useEffect(() => { + // if (!visible) + // }, [visible]) return ( Shopping Cart + + Shopping Cart + } content={ - - - - - - + } placement="bottomLeft" trigger="click" visible={ visible } onVisibleChange={ onVisibleChange } + overlayStyle={{ minWidth: 300 }} > { children } diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 68d3263e..07d000fe 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -1,46 +1,58 @@ -import { createContext, useCallback, useContext, useEffect, useState } from 'react' +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' import { useLocalStorage } from '../hooks/use-local-storage' const ShoppingCartContext = createContext({}) +const cartUtilities = { + getCount() { return this.concepts.length + this.variables.length + this.studies.length } +} const cartBlueprint = { name: undefined, createdTime: undefined, modifiedTime: undefined, + canDelete: true, concepts: [], variables: [], studies: [], + ...cartUtilities } export const ShoppingCartProvider = ({ children }) => { - const createCart = (name) => { + const createCart = (name, props) => { return { ...cartBlueprint, + ...props, name, createdTime: Date.now(), modifiedTime: Date.now() } } - const [carts, setCarts] = useLocalStorage("shoppingCarts", { - "default": createCart("Default") - }) - // const [activeCart, setActiveCart] = useState("default") + const [carts, setCarts] = useLocalStorage("shopping_carts", [ createCart("My cart", { canDelete: false }) ]) + const [activeCartName, setActiveCart] = useState("My cart") + const activeCart = useMemo(() => carts.find((cart) => cart.name === activeCartName), [carts, activeCartName]) + + useEffect(() => { + setCarts(carts.map((cart) => ({ + ...cart, + ...cartUtilities + }))) + }, []) - const addCart = (id, name) => { - setCarts({ + const addCart = (name, props) => { + if (carts.find((cart) => cart.name === name)) throw new Error("Cannot create a new cart with duplicate `name` key.") + setCarts([ ...carts, - [id]: createCart(name) - }) + createCart(name) + ]) } - const removeCart = (id) => { - const { [id]: removed, ...newCarts } = carts - setCarts(newCarts) + const removeCart = (name) => { + setCarts(carts.filter((cart) => cart.name !== name)) } return ( { children } From a26a87a67606d5efd33849c5dd31777457737d82 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 19 Jul 2022 18:53:17 -0400 Subject: [PATCH 09/55] Add duplicate cart error. Add confirm delete for carts (with an antd bug). Handle if active cart is deleted. --- package.json | 2 +- .../shopping-cart/shopping-cart-popover.js | 51 +++++++++++++++---- src/contexts/shopping-cart-context.js | 1 + 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index cca2abb6..e0805787 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", - "antd": "^4.20.6", + "antd": "^4.21.7", "axios": "^0.21.1", "classnames": "^2.3.1", "eslint": "^7.27.0", diff --git a/src/components/shopping-cart/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover.js index 4b631b0d..fb75f186 100644 --- a/src/components/shopping-cart/shopping-cart-popover.js +++ b/src/components/shopping-cart/shopping-cart-popover.js @@ -1,5 +1,5 @@ import { Fragment, useEffect, useState } from 'react' -import { Badge, Button, Popover, Space, List, Typography, Input } from 'antd' +import { Badge, Button, Popover, Space, List, Typography, Input, Form, Popconfirm, Modal } from 'antd' import { useShoppingCart } from '../../contexts/' import './shopping-cart-popover.css' @@ -13,6 +13,17 @@ const ManageCarts = ({ close }) => { close() } + const confirmDelete = (cart) => { + Modal.confirm({ + title: "Confirm delete", + content: "Are you sure you want to delete this cart?", + // To make modal appear above popovers, which hasn't been fixed yet. + maskStyle: { zIndex: 1031 }, + zIndex: 1032, + onOk: () => removeCart(cart.name) + }) + } + return ( { renderItem={(cart) => ( - {cart.name} ({cart.getCount()}) + {cart.name} {/* */} - + {/* */} @@ -37,12 +48,21 @@ const ManageCarts = ({ close }) => { ) } -const CartCreator = ({ cartName, setCartName }) => { +const CartCreator = ({ cartName, setCartName, cartNameError }) => { return ( Cart name - setCartName(e.target.value) } /> + + setCartName(e.target.value) } + /> + Add items to a cart to... @@ -64,6 +84,7 @@ const ShoppingCartPopoverContent = () => { const [creatingCart, setCreatingCart] = useState(false) const [cartName, setCartName] = useState("") + const [cartNameError, setCartNameError] = useState(false) useEffect(() => { if (creatingCart) { @@ -75,11 +96,17 @@ const ShoppingCartPopoverContent = () => { setCartName(defaultName)} }, [creatingCart]) + useEffect(() => setCartNameError(false), [cartName]) + const createShoppingCart = () => { - addCart(cartName) - setCreatingCart(false) - setManageCarts(false) - setActiveCart(cartName) + if (carts.find((cart) => cart.name === cartName)) { + setCartNameError(true) + } else { + addCart(cartName) + setCreatingCart(false) + setManageCarts(false) + setActiveCart(cartName) + } } return ( @@ -99,7 +126,11 @@ const ShoppingCartPopoverContent = () => {
)} { creatingCart ? ( - + ) : manageCarts ? ( setManageCarts(false) } /> ) : ( diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 07d000fe..57454884 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -47,6 +47,7 @@ export const ShoppingCartProvider = ({ children }) => { } const removeCart = (name) => { setCarts(carts.filter((cart) => cart.name !== name)) + if (name === activeCart.name) setActiveCart("My cart") } return ( From 8c92174e8d9746f07a910edce5608aada0b3c2f5 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Wed, 20 Jul 2022 13:38:02 -0400 Subject: [PATCH 10/55] Rework shopping cart UI into single interface with popups. Partially complete.. --- .../shopping-cart/shopping-cart-popover.css | 24 +++ .../shopping-cart/shopping-cart-popover.js | 185 +++++++++++++----- src/contexts/shopping-cart-context.js | 21 +- 3 files changed, 171 insertions(+), 59 deletions(-) diff --git a/src/components/shopping-cart/shopping-cart-popover.css b/src/components/shopping-cart/shopping-cart-popover.css index cc7ff78f..a5808e83 100644 --- a/src/components/shopping-cart/shopping-cart-popover.css +++ b/src/components/shopping-cart/shopping-cart-popover.css @@ -1,3 +1,6 @@ +.shopping-cart-popover { + width: 325px; +} .shopping-cart-popover-buttons { border-top: 1px solid #f0f0f0; padding-top: 16px; @@ -18,3 +21,24 @@ .manage-cart-item:first-child { margin-top: 0; } +.cart-dropdown-menu { + padding: 2px 0 2px 0 !important; + width: 225px; +} +.cart-dropdown-menu > .ant-dropdown-menu-item { + padding: 7px 12px; +} +.cart-dropdown-menu > .ant-dropdown-menu-item > .ant-dropdown-menu-title-content { + display: flex; + align-items: center; + width: 100%; + /*text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; */ +} +.cart-dropdown-search { + padding: 6px 11px !important; + box-shadow: none !important; + border: none !important; + border-bottom: 1px solid #f0f0f0 !important; +} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover.js index fb75f186..ba732125 100644 --- a/src/components/shopping-cart/shopping-cart-popover.js +++ b/src/components/shopping-cart/shopping-cart-popover.js @@ -1,5 +1,6 @@ import { Fragment, useEffect, useState } from 'react' -import { Badge, Button, Popover, Space, List, Typography, Input, Form, Popconfirm, Modal } from 'antd' +import { Badge, Button, Popover, Space, List, Typography, Input, Form, Popconfirm, Modal, Dropdown, Menu, Divider } from 'antd' +import { PlusOutlined, MenuOutlined, SearchOutlined, DownOutlined, ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' import { useShoppingCart } from '../../contexts/' import './shopping-cart-popover.css' @@ -78,23 +79,94 @@ const CartList = () => { ) } -const ShoppingCartPopoverContent = () => { +const CartSelectDropdown = ({ createNewCart, children }) => { const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() - const [manageCarts, setManageCarts] = useState(false) - const [creatingCart, setCreatingCart] = useState(false) + const [cartSearch, setCartSearch] = useState("") + + const createShoppingCart = (name) => { + addCart(name) + setActiveCart(name) + } + + return ( + + } + placeholder="Search for a cart" + allowClear + value={cartSearch} + onChange={(e) => setCartSearch(e.target.value)} + /> + { + carts + // .filter((cart) => cart !== activeCart) + .filter((cart) => cart.name.toLowerCase().includes(cartSearch.toLowerCase())) + .sort((a, b) => (b === activeCart) - (a === activeCart)) + .map((cart) => ( + setActiveCart(cart.key) } disabled={cart === activeCart}> + + {/* • */} + { cart.name } + + )) + } + { + cartSearch && !carts.find((cart) => cart.name.toLowerCase() === cartSearch.toLowerCase()) && ( + { + createShoppingCart(cartSearch) + setCartSearch("") + }}> + + "{ cartSearch }" +   + (Create new) + + ) + } + + + + + New cart + + + {/* + + + Manage carts + + */} + + } + > + { children } + + ) +} + +const CreateCartModal = ({ visible, onVisibleChange }) => { + const { carts, addCart, setActiveCart } = useShoppingCart() const [cartName, setCartName] = useState("") const [cartNameError, setCartNameError] = useState(false) - + useEffect(() => { - if (creatingCart) { + if (visible) { const highestExistingDefault = carts .map((cart) => /Shopping Cart (?\d+)/.exec(cart.name)?.groups.num) .filter((match) => match !== undefined) .sort((a, b) => b - a)[0] const defaultName = `Shopping Cart ${highestExistingDefault !== undefined ? parseInt(highestExistingDefault) + 1 : 1}` setCartName(defaultName)} - }, [creatingCart]) + }, [visible]) useEffect(() => setCartNameError(false), [cartName]) @@ -103,52 +175,72 @@ const ShoppingCartPopoverContent = () => { setCartNameError(true) } else { addCart(cartName) - setCreatingCart(false) - setManageCarts(false) setActiveCart(cartName) + onVisibleChange(false) } } + return ( - - { creatingCart ? ( -
- Create a new cart -
- ) : manageCarts ? ( -
- Manage carts -
- ) : ( -
- { activeCart.name } - setManageCarts(true) }>View carts -
- )} - { creatingCart ? ( - - ) : manageCarts ? ( - setManageCarts(false) } /> - ) : ( - - )} - { creatingCart ? ( - - - + onVisibleChange(false) } + zIndex={1032} + maskStyle={{ zIndex: 1031 }} + > + + + Name + + setCartName(e.target.value) } + onKeyDown={ (e) => e.key === "Enter" && createShoppingCart() } + /> + - ) : ( - - - + + Add items to a cart to... + - )} + + ) +} + +const ShoppingCartPopoverContent = () => { + const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() + const [creatingCart, setCreatingCart] = useState(false) + + return ( + +
+ { activeCart.name } + setCreatingCart(true) }> + + + Change + + + + +
+ + + + +
) } @@ -160,6 +252,7 @@ export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { return ( { @@ -31,13 +28,6 @@ export const ShoppingCartProvider = ({ children }) => { const [activeCartName, setActiveCart] = useState("My cart") const activeCart = useMemo(() => carts.find((cart) => cart.name === activeCartName), [carts, activeCartName]) - useEffect(() => { - setCarts(carts.map((cart) => ({ - ...cart, - ...cartUtilities - }))) - }, []) - const addCart = (name, props) => { if (carts.find((cart) => cart.name === name)) throw new Error("Cannot create a new cart with duplicate `name` key.") setCarts([ @@ -49,11 +39,16 @@ export const ShoppingCartProvider = ({ children }) => { setCarts(carts.filter((cart) => cart.name !== name)) if (name === activeCart.name) setActiveCart("My cart") } + + const cartUtilities = { + countCart: (cart) => cart.concepts.length + cart.variables.length + cart.studies.length + } return ( { children } From a58a2913fad24caf29495f246df601dd8e9ec468 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Wed, 20 Jul 2022 13:47:30 -0400 Subject: [PATCH 11/55] Refactor shopping cart popup into file-scoped components --- .../shopping-cart/create-cart-modal.js | 70 +++++ .../shopping-cart/shopping-cart-popover.js | 285 ------------------ .../shopping-cart-popover/cart-list.js | 8 + .../cart-select-dropdown.js | 79 +++++ .../shopping-cart-popover/index.js | 1 + .../shopping-cart-popover.css | 0 .../shopping-cart-popover.js | 76 +++++ 7 files changed, 234 insertions(+), 285 deletions(-) create mode 100644 src/components/shopping-cart/create-cart-modal.js delete mode 100644 src/components/shopping-cart/shopping-cart-popover.js create mode 100644 src/components/shopping-cart/shopping-cart-popover/cart-list.js create mode 100644 src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js create mode 100644 src/components/shopping-cart/shopping-cart-popover/index.js rename src/components/shopping-cart/{ => shopping-cart-popover}/shopping-cart-popover.css (100%) create mode 100644 src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js diff --git a/src/components/shopping-cart/create-cart-modal.js b/src/components/shopping-cart/create-cart-modal.js new file mode 100644 index 00000000..e0a6cb13 --- /dev/null +++ b/src/components/shopping-cart/create-cart-modal.js @@ -0,0 +1,70 @@ +import { useEffect, useState } from 'react' +import { Modal, Space, Form, Input, Typography } from 'antd' +import { useShoppingCart } from '../../contexts' + +const { Text, Paragraph } = Typography + +export const CreateCartModal = ({ visible, onVisibleChange }) => { + const { carts, addCart, setActiveCart } = useShoppingCart() + + const [cartName, setCartName] = useState("") + const [cartNameError, setCartNameError] = useState(false) + + useEffect(() => { + if (visible) { + const highestExistingDefault = carts + .map((cart) => /Shopping Cart (?\d+)/.exec(cart.name)?.groups.num) + .filter((match) => match !== undefined) + .sort((a, b) => b - a)[0] + const defaultName = `Shopping Cart ${highestExistingDefault !== undefined ? parseInt(highestExistingDefault) + 1 : 1}` + setCartName(defaultName)} + }, [visible]) + + useEffect(() => setCartNameError(false), [cartName]) + + const createShoppingCart = () => { + if (carts.find((cart) => cart.name === cartName)) { + setCartNameError(true) + } else { + addCart(cartName) + setActiveCart(cartName) + onVisibleChange(false) + } + } + + + return ( + onVisibleChange(false) } + zIndex={1032} + maskStyle={{ zIndex: 1031 }} + > + + + Name + + setCartName(e.target.value) } + onKeyDown={ (e) => e.key === "Enter" && createShoppingCart() } + /> + + + + Add items to a cart to... + + + + ) + } \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover.js deleted file mode 100644 index ba732125..00000000 --- a/src/components/shopping-cart/shopping-cart-popover.js +++ /dev/null @@ -1,285 +0,0 @@ -import { Fragment, useEffect, useState } from 'react' -import { Badge, Button, Popover, Space, List, Typography, Input, Form, Popconfirm, Modal, Dropdown, Menu, Divider } from 'antd' -import { PlusOutlined, MenuOutlined, SearchOutlined, DownOutlined, ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' -import { useShoppingCart } from '../../contexts/' -import './shopping-cart-popover.css' - -const { Title, Text, Paragraph } = Typography - -const ManageCarts = ({ close }) => { - const { carts, activeCart, setActiveCart, removeCart } = useShoppingCart() - - const selectCart = (cart) => { - setActiveCart(cart.name) - close() - } - - const confirmDelete = (cart) => { - Modal.confirm({ - title: "Confirm delete", - content: "Are you sure you want to delete this cart?", - // To make modal appear above popovers, which hasn't been fixed yet. - maskStyle: { zIndex: 1031 }, - zIndex: 1032, - onOk: () => removeCart(cart.name) - }) - } - - return ( - - ( - - - {cart.name} - - - - {/* */} - - {/* */} - - - - )} - /> - - ) -} - -const CartCreator = ({ cartName, setCartName, cartNameError }) => { - return ( - - - Cart name - - setCartName(e.target.value) } - /> - - - - Add items to a cart to... - - - ) -} - -const CartList = () => { - return ( - - ) -} - -const CartSelectDropdown = ({ createNewCart, children }) => { - const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() - const [cartSearch, setCartSearch] = useState("") - - const createShoppingCart = (name) => { - addCart(name) - setActiveCart(name) - } - - return ( - - } - placeholder="Search for a cart" - allowClear - value={cartSearch} - onChange={(e) => setCartSearch(e.target.value)} - /> - { - carts - // .filter((cart) => cart !== activeCart) - .filter((cart) => cart.name.toLowerCase().includes(cartSearch.toLowerCase())) - .sort((a, b) => (b === activeCart) - (a === activeCart)) - .map((cart) => ( - setActiveCart(cart.key) } disabled={cart === activeCart}> - - {/* • */} - { cart.name } - - )) - } - { - cartSearch && !carts.find((cart) => cart.name.toLowerCase() === cartSearch.toLowerCase()) && ( - { - createShoppingCart(cartSearch) - setCartSearch("") - }}> - - "{ cartSearch }" -   - (Create new) - - ) - } - - - - - New cart - - - {/* - - - Manage carts - - */} - - } - > - { children } - - ) -} - -const CreateCartModal = ({ visible, onVisibleChange }) => { - const { carts, addCart, setActiveCart } = useShoppingCart() - - const [cartName, setCartName] = useState("") - const [cartNameError, setCartNameError] = useState(false) - - useEffect(() => { - if (visible) { - const highestExistingDefault = carts - .map((cart) => /Shopping Cart (?\d+)/.exec(cart.name)?.groups.num) - .filter((match) => match !== undefined) - .sort((a, b) => b - a)[0] - const defaultName = `Shopping Cart ${highestExistingDefault !== undefined ? parseInt(highestExistingDefault) + 1 : 1}` - setCartName(defaultName)} - }, [visible]) - - useEffect(() => setCartNameError(false), [cartName]) - - const createShoppingCart = () => { - if (carts.find((cart) => cart.name === cartName)) { - setCartNameError(true) - } else { - addCart(cartName) - setActiveCart(cartName) - onVisibleChange(false) - } - } - - - return ( - onVisibleChange(false) } - zIndex={1032} - maskStyle={{ zIndex: 1031 }} - > - - - Name - - setCartName(e.target.value) } - onKeyDown={ (e) => e.key === "Enter" && createShoppingCart() } - /> - - - - Add items to a cart to... - - - - ) -} - -const ShoppingCartPopoverContent = () => { - const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() - const [creatingCart, setCreatingCart] = useState(false) - - return ( - -
- { activeCart.name } - setCreatingCart(true) }> - - - Change - - - - -
- - - - - -
- ) -} - -export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { - // useEffect(() => { - // if (!visible) - // }, [visible]) - return ( - - - Shopping Cart - - } - content={ - - } - placement="bottomLeft" - trigger="click" - visible={ visible } - onVisibleChange={ onVisibleChange } - overlayStyle={{ minWidth: 300 }} - > - { children } - - - ) -} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-list.js b/src/components/shopping-cart/shopping-cart-popover/cart-list.js new file mode 100644 index 00000000..88d382e5 --- /dev/null +++ b/src/components/shopping-cart/shopping-cart-popover/cart-list.js @@ -0,0 +1,8 @@ +import { List } from 'antd' + +export const CartList = () => { + return ( + + ) +} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js b/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js new file mode 100644 index 00000000..cfcaa5cb --- /dev/null +++ b/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js @@ -0,0 +1,79 @@ +import { useState } from 'react' +import { Dropdown, Menu, Input, Typography, Space } from 'antd' +import { PlusOutlined, SearchOutlined, ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' +import { useShoppingCart } from '../../../contexts' + +const { Text } = Typography + +export const CartSelectDropdown = ({ createNewCart, children }) => { + const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() + const [cartSearch, setCartSearch] = useState("") + + const createShoppingCart = (name) => { + addCart(name) + setActiveCart(name) + } + + return ( + + } + placeholder="Search for a cart" + allowClear + value={cartSearch} + onChange={(e) => setCartSearch(e.target.value)} + /> + { + carts + // .filter((cart) => cart !== activeCart) + .filter((cart) => cart.name.toLowerCase().includes(cartSearch.toLowerCase())) + .sort((a, b) => (b === activeCart) - (a === activeCart)) + .map((cart) => ( + setActiveCart(cart.key) } disabled={cart === activeCart}> + + {/* • */} + { cart.name } + + )) + } + { + cartSearch && !carts.find((cart) => cart.name.toLowerCase() === cartSearch.toLowerCase()) && ( + { + createShoppingCart(cartSearch) + setCartSearch("") + }}> + + "{ cartSearch }" +   + (Create new) + + ) + } + + + + + New cart + + + {/* + + + Manage carts + + */} + + } + > + { children } + + ) +} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/index.js b/src/components/shopping-cart/shopping-cart-popover/index.js new file mode 100644 index 00000000..1addb78b --- /dev/null +++ b/src/components/shopping-cart/shopping-cart-popover/index.js @@ -0,0 +1 @@ +export * from './shopping-cart-popover' \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover.css b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.css similarity index 100% rename from src/components/shopping-cart/shopping-cart-popover.css rename to src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.css diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js new file mode 100644 index 00000000..c50cdcc1 --- /dev/null +++ b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js @@ -0,0 +1,76 @@ +import { Fragment, useEffect, useState } from 'react' +import { Badge, Button, Popover, Space, List, Typography, Input, Form, Popconfirm, Modal, Dropdown, Menu, Divider } from 'antd' +import { DownOutlined } from '@ant-design/icons' +import { useShoppingCart } from '../../../contexts/' +import { CartList } from './cart-list' +import { CartSelectDropdown } from './cart-select-dropdown' +import { CreateCartModal } from '../create-cart-modal' +import './shopping-cart-popover.css' + +const { Title, Text, Paragraph } = Typography + +const ShoppingCartPopoverContent = () => { + const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() + const [creatingCart, setCreatingCart] = useState(false) + + return ( + +
+ { activeCart.name } + setCreatingCart(true) }> + + + Change + + + + +
+ + + + + +
+ ) +} + +export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { + // useEffect(() => { + // if (!visible) + // }, [visible]) + return ( + + + Shopping Cart + + } + content={ + + } + placement="bottomLeft" + trigger="click" + visible={ visible } + onVisibleChange={ onVisibleChange } + overlayStyle={{ minWidth: 300 }} + > + { children } + + + ) +} \ No newline at end of file From 62fd5ec83a72b5c3e7721ad7ca33bb749b6c6546 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Wed, 20 Jul 2022 14:14:11 -0400 Subject: [PATCH 12/55] Adds favoriting carts --- .../shopping-cart/create-cart-modal.js | 35 ++++++++++++---- .../cart-select-dropdown.js | 40 ++++++++++++++----- src/contexts/shopping-cart-context.js | 15 ++++++- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/components/shopping-cart/create-cart-modal.js b/src/components/shopping-cart/create-cart-modal.js index e0a6cb13..afab4d04 100644 --- a/src/components/shopping-cart/create-cart-modal.js +++ b/src/components/shopping-cart/create-cart-modal.js @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react' import { Modal, Space, Form, Input, Typography } from 'antd' +import { StarOutlined, StarFilled, StarTwoTone } from '@ant-design/icons' import { useShoppingCart } from '../../contexts' const { Text, Paragraph } = Typography @@ -7,10 +8,14 @@ const { Text, Paragraph } = Typography export const CreateCartModal = ({ visible, onVisibleChange }) => { const { carts, addCart, setActiveCart } = useShoppingCart() + /** Form state */ const [cartName, setCartName] = useState("") const [cartNameError, setCartNameError] = useState(false) + const [favorited, setFavorited] = useState(false) useEffect(() => { + setCartName("") + setFavorited(false) if (visible) { const highestExistingDefault = carts .map((cart) => /Shopping Cart (?\d+)/.exec(cart.name)?.groups.num) @@ -26,12 +31,15 @@ export const CreateCartModal = ({ visible, onVisibleChange }) => { if (carts.find((cart) => cart.name === cartName)) { setCartNameError(true) } else { - addCart(cartName) + addCart(cartName, { + favorited + }) setActiveCart(cartName) onVisibleChange(false) } } - + + const StarIcon = favorited ? StarFilled : StarOutlined return ( { validateStatus={ cartNameError && "error" } help={ cartNameError ? "Carts cannot have duplicate names." : undefined } style={{ margin: 0 }}> - setCartName(e.target.value) } - onKeyDown={ (e) => e.key === "Enter" && createShoppingCart() } - /> +
+ setCartName(e.target.value) } + onKeyDown={ (e) => e.key === "Enter" && createShoppingCart() } + /> + setFavorited(!favorited) } + style={{ + fontSize: 16, + marginLeft: 16, + color: favorited ? "#1890ff" : undefined + }} + /> +
diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js b/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js index cfcaa5cb..63b4a52c 100644 --- a/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js +++ b/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js @@ -1,12 +1,12 @@ import { useState } from 'react' import { Dropdown, Menu, Input, Typography, Space } from 'antd' -import { PlusOutlined, SearchOutlined, ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' +import { PlusOutlined, SearchOutlined, ShoppingCartOutlined as ShoppingCartIcon, StarOutlined, StarFilled } from '@ant-design/icons' import { useShoppingCart } from '../../../contexts' const { Text } = Typography export const CartSelectDropdown = ({ createNewCart, children }) => { - const { carts, activeCart, addCart, setActiveCart } = useShoppingCart() + const { carts, activeCart, addCart, updateCart, setActiveCart } = useShoppingCart() const [cartSearch, setCartSearch] = useState("") const createShoppingCart = (name) => { @@ -35,17 +35,37 @@ export const CartSelectDropdown = ({ createNewCart, children }) => { carts // .filter((cart) => cart !== activeCart) .filter((cart) => cart.name.toLowerCase().includes(cartSearch.toLowerCase())) + .sort((a, b) => b.modifiedTime - a.modifiedTime) + .sort((a, b) => (b.favorited) - (a.favorited)) .sort((a, b) => (b === activeCart) - (a === activeCart)) - .map((cart) => ( - setActiveCart(cart.key) } disabled={cart === activeCart}> - - {/* • */} - { cart.name } - - )) + .map((cart) => { + const StarIcon = cart.favorited ? StarFilled : StarOutlined + return ( + setActiveCart(cart.key) } disabled={cart === activeCart}> + + {/* • */} + { cart.name } + { + e.preventDefault() + e.stopPropagation() + updateCart(cart.name, { + favorited: !cart.favorited + }) + }} + /> + + ) + }) } { - cartSearch && !carts.find((cart) => cart.name.toLowerCase() === cartSearch.toLowerCase()) && ( + cartSearch && !carts.find((cart) => cart.name === cartSearch) && ( { createShoppingCart(cartSearch) setCartSearch("") diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 5f4d94c8..9858509e 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -32,13 +32,24 @@ export const ShoppingCartProvider = ({ children }) => { if (carts.find((cart) => cart.name === name)) throw new Error("Cannot create a new cart with duplicate `name` key.") setCarts([ ...carts, - createCart(name) + createCart(name, props) ]) } const removeCart = (name) => { setCarts(carts.filter((cart) => cart.name !== name)) if (name === activeCart.name) setActiveCart("My cart") } + const updateCart = (name, props) => { + const cart = carts.find((cart) => cart.name === name) + setCarts([ + ...carts.filter((_cart) => _cart !== cart), + { + ...cart, + ...props, + modifiedTime: Date.now() + } + ]) + } const cartUtilities = { countCart: (cart) => cart.concepts.length + cart.variables.length + cart.studies.length @@ -46,7 +57,7 @@ export const ShoppingCartProvider = ({ children }) => { return ( From ebc8b8213e20733bbcd3356eede149de253ed6c5 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Wed, 20 Jul 2022 15:03:14 -0400 Subject: [PATCH 13/55] More blueprinting of shopping cart context .Adds overflow handling on cart dropdown. --- .../search/concept-card/concept-card.js | 2 +- src/components/shopping-cart/add-to-cart.js | 57 ++++++++++++++++++- .../cart-select-dropdown.js | 26 +++++++-- .../shopping-cart-popover.css | 2 + src/contexts/shopping-cart-context.js | 37 ++++++++++++ 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/src/components/search/concept-card/concept-card.js b/src/components/search/concept-card/concept-card.js index 6dbedfeb..8ad0eea0 100644 --- a/src/components/search/concept-card/concept-card.js +++ b/src/components/search/concept-card/concept-card.js @@ -42,7 +42,7 @@ export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=V onTabChange={key => setCurrentTab(key)} extra={
- + { icon && }
} diff --git a/src/components/shopping-cart/add-to-cart.js b/src/components/shopping-cart/add-to-cart.js index 602f9b73..6785aa44 100644 --- a/src/components/shopping-cart/add-to-cart.js +++ b/src/components/shopping-cart/add-to-cart.js @@ -1,15 +1,68 @@ import { Button } from 'antd' import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' +import { useShoppingCart } from '../../contexts' + +export const AddToCart = ({ + cart=undefined, + concept=undefined, + study=undefined, + variable=undefined, + asIcon=false, + style={}, + ...props +}) => { + const { + carts, + activeCart, + addConceptToCart, removeConceptFromCart, + addVariableToCart, removeVariableFromCart, + addStudyToCart, removeStudyFromCart, + cartUtilities: { + isConceptInCart, + isStudyInCart, + isVariableInCart + } + } = useShoppingCart() + + if (!cart) cart = activeCart + + const isInCart = concept ? ( + isConceptInCart(cart, concept) + ) : study ? ( + isStudyInCart(cart, study) + ) : variable ? ( + isVariableInCart(cart, variable) + ) : false + + const addToCart = () => { + return concept ? ( + addConceptToCart(cart, concept) + ) : study ? ( + addStudyToCart(cart, study) + ) : variable ? ( + addVariableToCart(cart, variable) + ) : {} + } -export const AddToCart = ({ asIcon=false, style, ...props }) => { if (asIcon) return ( - + ) return (
- + - - - - ) -} - -export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { - // useEffect(() => { - // if (!visible) - // }, [visible]) + const checkoutDisabled = countCart(activeCart) === 0 + return ( - Shopping Cart - +
+ + {activeCart.name} + + setCreatingCart(true) }> + + + Change + + + + +
} content={ - + + + + + + + } placement="bottomLeft" trigger="click" diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 23f8c947..6de0f829 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -85,12 +85,12 @@ export const ShoppingCartProvider = ({ children }) => { })) } - const cartUtilities = { + const cartUtilities = useMemo(() => ({ isConceptInCart: (cart, concept) => !!cart.concepts.find((_concept) => _concept.id === concept.id), isStudyInCart: (cart, study) => !!cart.studies.find((_study) => _study.c_id === study.c_id), isVariableInCart: (cart, variable) => !!cart.variables.find((_variable) => _variable.id === variable.id), countCart: (cart) => cart.concepts.length + cart.variables.length + cart.studies.length - } + }), []) return ( Date: Thu, 21 Jul 2022 13:14:21 -0400 Subject: [PATCH 19/55] Adds cart animations. Fixes animation on buttons --- package.json | 2 + .../shopping-cart-popover/cart-list.js | 91 +++++++++++++------ .../shopping-cart-list.css | 12 ++- .../shopping-cart-popover.js | 15 ++- 4 files changed, 87 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index e0805787..9a018977 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "classnames": "^2.3.1", "eslint": "^7.27.0", "helx-analytics": "^1.0.14", + "rc-queue-anim": "^2.0.0", + "rc-tween-one": "^3.0.6", "react": "^17.0.2", "react-dom": "^17.0.2", "react-infinite-scroll-component": "^6.1.0", diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-list.js b/src/components/shopping-cart/shopping-cart-popover/cart-list.js index cf426279..1b8448a2 100644 --- a/src/components/shopping-cart/shopping-cart-popover/cart-list.js +++ b/src/components/shopping-cart/shopping-cart-popover/cart-list.js @@ -1,12 +1,26 @@ -import { Fragment, useEffect, useState } from 'react' +import { Fragment, useEffect, useMemo, useState } from 'react' import { List, Typography, Collapse, Space, Divider } from 'antd' -import { CloseOutlined } from '@ant-design/icons' +import { CloseOutlined, DeleteOutlined } from '@ant-design/icons' +import { TweenOneGroup } from 'rc-tween-one' +import QueueAnim from 'rc-queue-anim' import { useShoppingCart } from '../../../contexts' import './shopping-cart-list.css' const { Text } = Typography const { Panel } = Collapse +const DeleteAnimation = [ + { + duration: 250, + opacity: 0 + }, + { + height: 0, + duration: 200, + ease: "easeOutQuad" + } +] + const CartSection = ({ name, data, renderItem }) => { const [expanded, setExpanded] = useState(true) const disabled = data.length === 0 @@ -15,38 +29,61 @@ const CartSection = ({ name, data, renderItem }) => { if (disabled) setExpanded(false) }, [disabled]) + // const wrapper = useMemo(() => ( + // expanded ? ( + // + // { data.map((item) => renderItem(item) ) } + // + // ) : ( + // null + // ) + // ), [expanded, data]) + return ( - setExpanded(!expanded) }> - - - {name} ({data.length}) - - {/* {!disabled && ( - Empty - )} */} - - } + + setExpanded(!expanded) }> + + + {name} ({data.length}) + + {/* {!disabled && ( + Empty + )} */} + + } + /> + + -
- -
-
-
+ + { expanded ? data.map((item) => renderItem(item) ) : null } + + + ) } const RemoveItemButton = ({ onClick }) => ( - ) diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css index c42d5dad..58637735 100644 --- a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css +++ b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css @@ -1,6 +1,7 @@ .shopping-cart-list { display: flex; flex-direction: column; + margin-top: -16px; } .shopping-cart-list .ant-divider { margin: 2px 0; @@ -16,16 +17,21 @@ .shopping-cart-list .ant-collapse-content-box { padding: 0 !important; } -.shopping-cart-list .ant-collapse-content-box .ant-list-item { +.shopping-cart-list .ant-list { + /* border-left: 1px solid #f0f0f0; + padding-left: 8px; + margin-left: 5px; */ +} +.shopping-cart-list .ant-list-item { border: none; margin-left: 4px; /* margin-top: 8px; */ } -.shopping-cart-list .ant-collapse-content-box .ant-list-item:first-child { +.shopping-cart-list .ant-list-item:first-child { padding-top: 8px !important; margin-top: 0; } -.shopping-cart-list .ant-collapse-content-box .ant-list-empty-text { +.shopping-cart-list .ant-list-empty-text { display: none; /* padding: 4px 0; */ padding-left: 0; diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js index f1647167..da6544fc 100644 --- a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js +++ b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js @@ -32,6 +32,7 @@ export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { }} > {activeCart.name} + {/* <> • { countCart(activeCart) } items */} setCreatingCart(true) }> @@ -47,9 +48,17 @@ export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { - + {/* Button transitions don't currently work properly on Tooltip-wrapped buttons due to a bug in antd. */} + + + From 948771b600cb85e4cff2c2389128e5d429f69d2c Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Thu, 21 Jul 2022 13:49:01 -0400 Subject: [PATCH 20/55] Style improvements to cart list --- .../shopping-cart-popover/cart-list.js | 18 +++++++++++------- .../shopping-cart-list.css | 13 ++++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-list.js b/src/components/shopping-cart/shopping-cart-popover/cart-list.js index 1b8448a2..9a646493 100644 --- a/src/components/shopping-cart/shopping-cart-popover/cart-list.js +++ b/src/components/shopping-cart/shopping-cart-popover/cart-list.js @@ -6,7 +6,7 @@ import QueueAnim from 'rc-queue-anim' import { useShoppingCart } from '../../../contexts' import './shopping-cart-list.css' -const { Text } = Typography +const { Text, Paragraph } = Typography const { Panel } = Collapse const DeleteAnimation = [ @@ -25,6 +25,10 @@ const CartSection = ({ name, data, renderItem }) => { const [expanded, setExpanded] = useState(true) const disabled = data.length === 0 + useEffect(() => { + setExpanded(true) + }, [data]) + useEffect(() => { if (disabled) setExpanded(false) }, [disabled]) @@ -53,7 +57,7 @@ const CartSection = ({ name, data, renderItem }) => { disabled={ disabled } header={
- + {name} ({data.length}) {/* {!disabled && ( @@ -99,16 +103,16 @@ export const CartList = () => { data={ concepts } renderItem={(concept) => ( - +
- + { concept.name } ({ concept.type }) removeConceptFromCart(activeCart, concept) }/>
- {/* - { concept.id } - */} + + { concept.description } +
)} diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css index 58637735..bff3c1f9 100644 --- a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css +++ b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css @@ -23,14 +23,18 @@ margin-left: 5px; */ } .shopping-cart-list .ant-list-item { - border: none; margin-left: 4px; - /* margin-top: 8px; */ + margin-top: 8px; + padding-bottom: 8px; + + padding-top: 0px; } .shopping-cart-list .ant-list-item:first-child { - padding-top: 8px !important; margin-top: 0; } +.shopping-cart-list .ant-list-item:last-child { + padding-bottom: 0; +} .shopping-cart-list .ant-list-empty-text { display: none; /* padding: 4px 0; */ @@ -40,4 +44,7 @@ /* text-align: start; */ /* font-style:italic; */ /* color: rgba(0, 0, 0, 0.85); */ +} +.shopping-cart-list .cart-concept-description { + margin-bottom: 0; } \ No newline at end of file From e2fe02d092d7cc6487894c92403059f489fa33ef Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Thu, 21 Jul 2022 14:48:12 -0400 Subject: [PATCH 21/55] Improved cart animations --- package.json | 1 + .../shopping-cart-popover/cart-list.js | 30 +------------------ .../shopping-cart-popover.js | 20 +++++++++++-- src/index.css | 1 + 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 9a018977..3ff9d03a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "eslint": "^7.27.0", "helx-analytics": "^1.0.14", "rc-queue-anim": "^2.0.0", + "rc-texty": "^0.2.0", "rc-tween-one": "^3.0.6", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-list.js b/src/components/shopping-cart/shopping-cart-popover/cart-list.js index 9a646493..da38446f 100644 --- a/src/components/shopping-cart/shopping-cart-popover/cart-list.js +++ b/src/components/shopping-cart/shopping-cart-popover/cart-list.js @@ -9,18 +9,6 @@ import './shopping-cart-list.css' const { Text, Paragraph } = Typography const { Panel } = Collapse -const DeleteAnimation = [ - { - duration: 250, - opacity: 0 - }, - { - height: 0, - duration: 200, - ease: "easeOutQuad" - } -] - const CartSection = ({ name, data, renderItem }) => { const [expanded, setExpanded] = useState(true) const disabled = data.length === 0 @@ -33,22 +21,6 @@ const CartSection = ({ name, data, renderItem }) => { if (disabled) setExpanded(false) }, [disabled]) - // const wrapper = useMemo(() => ( - // expanded ? ( - // - // { data.map((item) => renderItem(item) ) } - // - // ) : ( - // null - // ) - // ), [expanded, data]) - return ( setExpanded(!expanded) }> @@ -105,7 +77,7 @@ export const CartList = () => {
- + { concept.name } ({ concept.type }) removeConceptFromCart(activeCart, concept) }/> diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js index da6544fc..b5fe7a78 100644 --- a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js +++ b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js @@ -1,6 +1,8 @@ import { Fragment, useEffect, useState } from 'react' import { Badge, Button, Popover, Space, List, Typography, Input, Form, Popconfirm, Modal, Dropdown, Menu, Divider, Tooltip } from 'antd' import { DownOutlined } from '@ant-design/icons' +import Texty from 'rc-texty' +import TweenOne from 'rc-tween-one' import { useShoppingCart } from '../../../contexts/' import { CartList } from './cart-list' import { CartSelectDropdown } from './cart-select-dropdown' @@ -12,8 +14,18 @@ const { Title, Text, Paragraph } = Typography export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { const { activeCart, cartUtilities: { countCart } } = useShoppingCart() const [creatingCart, setCreatingCart] = useState(false) + const [name, setName] = useState("") const checkoutDisabled = countCart(activeCart) === 0 + + /* Forces */ + useEffect(() => { + setName("") + }, [activeCart.name]) + + useEffect(() => { + if (!name) setName(activeCart.name) + }, [name]) return ( @@ -28,11 +40,13 @@ export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { fontSize: 12, letterSpacing: "0.5px", color: "#434343", - textTransform: "uppercase" + textTransform: "uppercase", + overflow: "hidden", + // border: "1px solid red" }} > - {activeCart.name} - {/* <> • { countCart(activeCart) } items */} + {name} + {/* <> • { countCart(activeCart) } items */} setCreatingCart(true) }> diff --git a/src/index.css b/src/index.css index e0677e72..a39611e8 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,5 @@ @import 'antd/dist/antd.css'; +@import 'rc-texty/assets/index.css'; *, *::before, *::after { box-sizing: border-box; From 53f2d33047a9d79acac71a145f2a19e15f421c12 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Thu, 21 Jul 2022 19:58:52 -0400 Subject: [PATCH 22/55] Major refactoring to make shopping cart more customizable and encapsulated. Fixes and small changes. --- .../search/concept-card/concept-card.css | 1 + .../search/concept-card/concept-card.js | 8 +- .../search/concept-card/studies-tab.js | 2 + .../search/concept-modal/tabs/studies.js | 41 ++++-- src/components/shopping-cart/add-to-cart.js | 137 ++++++++++++++---- .../shopping-cart/create-cart-modal.js | 26 +++- src/components/shopping-cart/index.js | 3 +- .../shopping-cart-popover/cart-list.js | 6 +- .../cart-select-dropdown.js | 67 ++++++--- .../shopping-cart-popover.js | 8 +- src/contexts/shopping-cart-context.js | 38 ++++- 11 files changed, 248 insertions(+), 89 deletions(-) diff --git a/src/components/search/concept-card/concept-card.css b/src/components/search/concept-card/concept-card.css index be26895f..71fc9a5a 100644 --- a/src/components/search/concept-card/concept-card.css +++ b/src/components/search/concept-card/concept-card.css @@ -6,6 +6,7 @@ width: 100%; display: flex; justify-content: space-between; + align-items: center; } .study-name { diff --git a/src/components/search/concept-card/concept-card.js b/src/components/search/concept-card/concept-card.js index 8ad0eea0..1403faae 100644 --- a/src/components/search/concept-card/concept-card.js +++ b/src/components/search/concept-card/concept-card.js @@ -7,13 +7,14 @@ import { useHelxSearch } from '../' import { OverviewTab } from './overview-tab' import { StudiesTab } from './studies-tab' import { KnowledgeGraphsTab } from './knowledge-graphs-tab' -import { AddToCart } from '../../shopping-cart' -import { useAnalytics } from '../../../contexts' +import { AddToCartIcon } from '../../shopping-cart' +import { useAnalytics, useShoppingCart } from '../../../contexts' import './concept-card.css' export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=ViewIcon, className="" }, ref) => { const { analyticsEvents } = useAnalytics() const { query } = useHelxSearch() + const { activeCart, cartUtilities: { isConceptInCart } } = useShoppingCart() const [currentTab, setCurrentTab] = useState('overview') const tabs = { @@ -42,11 +43,12 @@ export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=V onTabChange={key => setCurrentTab(key)} extra={
- + { icon && }
} actions={ [
] } + // style={{ border: isConceptInCart(activeCart, result) ? "1px solid #91d5ff" : undefined }} > { tabContents[currentTab] } diff --git a/src/components/search/concept-card/studies-tab.js b/src/components/search/concept-card/studies-tab.js index b9ee6cab..5efb6ab4 100644 --- a/src/components/search/concept-card/studies-tab.js +++ b/src/components/search/concept-card/studies-tab.js @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import { List, Spin, Space, Tag, Typography } from 'antd' import { useHelxSearch } from '../' import { Link } from '../../link' +import { AddToCartIcon } from '../../shopping-cart' const { Text } = Typography const { CheckableTag: CheckableFacet } = Tag @@ -68,6 +69,7 @@ export const StudiesTab = ({ result }) => { ({ item.c_id }) { item.elements.length } variable{ item.elements.length === 1 ? '' : 's' } + {/* */}
) } diff --git a/src/components/search/concept-modal/tabs/studies.js b/src/components/search/concept-modal/tabs/studies.js index f63569cd..87643715 100644 --- a/src/components/search/concept-modal/tabs/studies.js +++ b/src/components/search/concept-modal/tabs/studies.js @@ -1,12 +1,15 @@ import { useEffect, useState } from 'react' -import { Collapse, List, Space, Tag, Typography } from 'antd' +import { Button, Collapse, Divider, List, Space, Tag, Typography } from 'antd' +import { useHelxSearch } from '../../' import { Link } from '../../../link' +import { AddToCartIcon, AddToCartDropdown } from '../../../shopping-cart' const { Text, Title } = Typography const { CheckableTag: CheckableFacet } = Tag const { Panel } = Collapse export const StudiesTab = ({ studies }) => { + const { selectedResult } = useHelxSearch() const [facets, setFacets] = useState([]) const [selectedFacets, setSelectedFacets] = useState([]) @@ -55,21 +58,29 @@ export const StudiesTab = ({ studies }) => { ({ item.c_id }) } - extra={ { item.elements.length } variable{ item.elements.length === 1 ? '' : 's' } } + extra={ + + { item.elements.length } variable{ item.elements.length === 1 ? '' : 's' } + {/* */} + + } > - ( -
- - { variable.name }   - ({ variable.e_link ? { variable.id } : variable.id }) -
- { variable.description } -
- ) } - /> + + ( +
+ + { variable.name }   + ({ variable.e_link ? { variable.id } : variable.id }) +
+ { variable.description } +
+ ) } + /> + +
)) } diff --git a/src/components/shopping-cart/add-to-cart.js b/src/components/shopping-cart/add-to-cart.js index 4a46372d..11447be1 100644 --- a/src/components/shopping-cart/add-to-cart.js +++ b/src/components/shopping-cart/add-to-cart.js @@ -1,16 +1,10 @@ -import { Button } from 'antd' +import { Button, Dropdown, Menu } from 'antd' +import { DownOutlined } from '@ant-design/icons' import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' import { useShoppingCart } from '../../contexts' +import { useCallback, useMemo } from 'react' -export const AddToCart = ({ - cart=undefined, - concept=undefined, - study=undefined, - variable=undefined, - asIcon=false, - style={}, - ...props -}) => { +const useUtilities = ({ cart: _cart, concept, study, variable }) => { const { carts, activeCart, @@ -23,18 +17,23 @@ export const AddToCart = ({ isVariableInCart } } = useShoppingCart() - - if (!cart) cart = activeCart - const isInCart = concept ? ( - isConceptInCart(cart, concept) - ) : study ? ( - isStudyInCart(cart, study) - ) : variable ? ( - isVariableInCart(cart, variable) - ) : false + const cart = useMemo(() => _cart ? _cart : activeCart, [_cart, activeCart]) + + const isInCart = useMemo(() => ( + concept ? ( + isConceptInCart(cart, concept) + ) : study ? ( + isStudyInCart(cart, study) + ) : variable ? ( + isVariableInCart(cart, variable) + ) : false + ), [ + cart, concept, study, variable, + isConceptInCart, isStudyInCart, isVariableInCart + ]) - const addToCart = () => { + const addToCart = useCallback(() => { return concept ? ( addConceptToCart(cart, concept) ) : study ? ( @@ -42,9 +41,12 @@ export const AddToCart = ({ ) : variable ? ( addVariableToCart(cart, variable) ) : {} - } + }, [ + cart, concept, study, variable, + addConceptToCart, addStudyToCart, addVariableToCart + ]) - const removeFromCart = () => { + const removeFromCart = useCallback(() => { return concept ? ( removeConceptFromCart(cart, concept) ) : study ? ( @@ -52,12 +54,41 @@ export const AddToCart = ({ ) : variable ? ( removeVariableFromCart(cart, variable) ) : {} + }, [ + cart, concept, study, variable, + removeConceptFromCart, removeStudyFromCart, removeVariableFromCart + ]) + + const onClick = useCallback((e) => { + e.preventDefault() + e.stopPropagation() + if (isInCart) removeFromCart() + else addToCart() + }, [isInCart, addToCart, removeFromCart]) + + return { + isInCart, + addToCart, + removeFromCart, + onClick } - if (asIcon) return ( +} + +export const AddToCartIcon = ({ + cart=undefined, + concept=undefined, + study=undefined, + variable=undefined, + style={}, + ...props +}) => { + const { isInCart, onClick } = useUtilities({ cart, concept, study, variable }) + + return ( ) +} +export const AddToCartButton = ({ + cart=undefined, + concept=undefined, + study=undefined, + variable=undefined, + style={}, + ...props +}) => { + const { isInCart, onClick } = useUtilities({ cart, concept, study, variable }) + return ( - } placement="bottomLeft" diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 6de0f829..44001646 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -1,4 +1,5 @@ -import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' +import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useState } from 'react' +import { CreateCartModal } from '../components/shopping-cart' import { useLocalStorage } from '../hooks/use-local-storage' const ShoppingCartContext = createContext({}) @@ -25,6 +26,7 @@ export const ShoppingCartProvider = ({ children }) => { } } const [carts, setCarts] = useLocalStorage("shopping_carts", [ createCart("My cart", { canDelete: false }) ]) + const [showCreateCartModal, setShowCreateCartModal] = useState(false) const [activeCartName, setActiveCart] = useState("My cart") const activeCart = useMemo(() => carts.find((cart) => cart.name === activeCartName), [carts, activeCartName]) @@ -54,19 +56,31 @@ export const ShoppingCartProvider = ({ children }) => { } ]) } - const addConceptToCart = (name, concept) => { + + /** The `from` field will be appended to shopping cart elements to track where they originate from in the DUG UI. + * + * Structure is { type: string, value: any } where `value` depends on `type`. + * - { type: "search", value: "" } + * - { type: "concept", value: } + * - { type: "study", value: } + * + * It has been structured in this way in anticipation of future workflows beyond + * simply "searches yield concepts, concepts yield studies, studies yield variables" + * + */ + const addConceptToCart = (name, concept, from=null) => { updateCart(name, (cart) => ({ - concepts: [...cart.concepts, concept] + concepts: [...cart.concepts, { ...concept, from }] })) } - const addStudyToCart = (name, study) => { + const addStudyToCart = (name, study, from=null) => { updateCart(name, (cart) => ({ - studies: [...cart.studies, study] + studies: [...cart.studies, { ...study, from }] })) } - const addVariableToCart = (name, variable) => { + const addVariableToCart = (name, variable, from=null) => { updateCart(name, (cart) => ({ - variables: [...cart.variables, variable] + variables: [...cart.variables, { ...variable, from }] })) } const removeConceptFromCart = (name, concept) => { @@ -91,6 +105,10 @@ export const ShoppingCartProvider = ({ children }) => { isVariableInCart: (cart, variable) => !!cart.variables.find((_variable) => _variable.id === variable.id), countCart: (cart) => cart.concepts.length + cart.variables.length + cart.studies.length }), []) + + const modals = useMemo(() => ({ + createCart: () => setShowCreateCartModal(true) + }), []) return ( { addConceptToCart, addStudyToCart, addVariableToCart, removeConceptFromCart, removeStudyFromCart, removeVariableFromCart, activeCart, setActiveCart, + modals, cartUtilities }}> - { children } + + { children } + + ) } From 18fff90beda3eacbd0f374a49841e7377317d4aa Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 22 Jul 2022 13:29:29 -0400 Subject: [PATCH 23/55] Finishes basic dropdown add to cart. Lots of refactoring/changes for better component encapsulation. --- .../search/concept-modal/tabs/studies.js | 6 +- src/components/shopping-cart/add-to-cart.js | 168 ----------- .../add-to-cart/add-to-cart-button.js | 33 +++ .../add-to-cart/add-to-cart-dropdown.js | 81 ++++++ .../add-to-cart/add-to-cart-icon.js | 31 +++ .../add-to-cart/add-to-cart-utilities.js | 69 +++++ .../shopping-cart/add-to-cart/add-to-cart.css | 5 + .../shopping-cart/add-to-cart/index.js | 4 + .../shopping-cart-popover/cart-list.js | 2 +- .../cart-select-dropdown.js | 261 +++++++++++------- .../shopping-cart-popover/index.js | 3 +- .../shopping-cart-popover.js | 2 +- src/contexts/shopping-cart-context.js | 17 +- 13 files changed, 409 insertions(+), 273 deletions(-) delete mode 100644 src/components/shopping-cart/add-to-cart.js create mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-button.js create mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-dropdown.js create mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-icon.js create mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js create mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart.css create mode 100644 src/components/shopping-cart/add-to-cart/index.js diff --git a/src/components/search/concept-modal/tabs/studies.js b/src/components/search/concept-modal/tabs/studies.js index 87643715..f1f71230 100644 --- a/src/components/search/concept-modal/tabs/studies.js +++ b/src/components/search/concept-modal/tabs/studies.js @@ -79,7 +79,11 @@ export const StudiesTab = ({ studies }) => {
) } /> - + )) diff --git a/src/components/shopping-cart/add-to-cart.js b/src/components/shopping-cart/add-to-cart.js deleted file mode 100644 index 11447be1..00000000 --- a/src/components/shopping-cart/add-to-cart.js +++ /dev/null @@ -1,168 +0,0 @@ -import { Button, Dropdown, Menu } from 'antd' -import { DownOutlined } from '@ant-design/icons' -import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' -import { useShoppingCart } from '../../contexts' -import { useCallback, useMemo } from 'react' - -const useUtilities = ({ cart: _cart, concept, study, variable }) => { - const { - carts, - activeCart, - addConceptToCart, removeConceptFromCart, - addVariableToCart, removeVariableFromCart, - addStudyToCart, removeStudyFromCart, - cartUtilities: { - isConceptInCart, - isStudyInCart, - isVariableInCart - } - } = useShoppingCart() - - const cart = useMemo(() => _cart ? _cart : activeCart, [_cart, activeCart]) - - const isInCart = useMemo(() => ( - concept ? ( - isConceptInCart(cart, concept) - ) : study ? ( - isStudyInCart(cart, study) - ) : variable ? ( - isVariableInCart(cart, variable) - ) : false - ), [ - cart, concept, study, variable, - isConceptInCart, isStudyInCart, isVariableInCart - ]) - - const addToCart = useCallback(() => { - return concept ? ( - addConceptToCart(cart, concept) - ) : study ? ( - addStudyToCart(cart, study) - ) : variable ? ( - addVariableToCart(cart, variable) - ) : {} - }, [ - cart, concept, study, variable, - addConceptToCart, addStudyToCart, addVariableToCart - ]) - - const removeFromCart = useCallback(() => { - return concept ? ( - removeConceptFromCart(cart, concept) - ) : study ? ( - removeStudyFromCart(cart, study) - ) : variable ? ( - removeVariableFromCart(cart, variable) - ) : {} - }, [ - cart, concept, study, variable, - removeConceptFromCart, removeStudyFromCart, removeVariableFromCart - ]) - - const onClick = useCallback((e) => { - e.preventDefault() - e.stopPropagation() - if (isInCart) removeFromCart() - else addToCart() - }, [isInCart, addToCart, removeFromCart]) - - return { - isInCart, - addToCart, - removeFromCart, - onClick - } - -} - -export const AddToCartIcon = ({ - cart=undefined, - concept=undefined, - study=undefined, - variable=undefined, - style={}, - ...props -}) => { - const { isInCart, onClick } = useUtilities({ cart, concept, study, variable }) - - return ( - - ) -} -export const AddToCartButton = ({ - cart=undefined, - concept=undefined, - study=undefined, - variable=undefined, - style={}, - ...props -}) => { - const { isInCart, onClick } = useUtilities({ cart, concept, study, variable }) - - return ( - - - +
+ + + + + +
)} > diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart-button.js b/src/components/shopping-cart/add-to-cart/add-to-cart-button.js index 0c68bea7..9abd9e30 100644 --- a/src/components/shopping-cart/add-to-cart/add-to-cart-button.js +++ b/src/components/shopping-cart/add-to-cart/add-to-cart-button.js @@ -8,11 +8,12 @@ export const AddToCartButton = ({ concept=undefined, study=undefined, variable=undefined, + from=null, style={}, ...props }) => { const { activeCart } = useShoppingCart() - const { isInCart, toggleCart } = useUtilities({ concept, study, variable }) + const { isInCart, toggleCart } = useUtilities({ concept, study, variable, from }) return ( -
- + + + {/* Button transitions don't currently work properly on Tooltip-wrapped buttons due to a bug in antd. */} + + + + + } placement="bottomLeft" diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 9e1fac15..219f8245 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -109,6 +109,13 @@ export const ShoppingCartProvider = ({ children }) => { variables: cart.variables.filter((_variable) => _variable.id !== variable.id) })) } + const emptyCart = (name) => { + updateCart(name, (cart) => ({ + concepts: [], + studies: [], + variables: [] + })) + } const cartUtilities = useMemo(() => ({ isConceptInCart: (cart, concept) => !!cart.concepts.find((_concept) => _concept.id === concept.id), @@ -123,7 +130,7 @@ export const ShoppingCartProvider = ({ children }) => { return ( Date: Fri, 22 Jul 2022 17:16:48 -0400 Subject: [PATCH 27/55] Variable add to carts, refactoring, bug fixes --- .../search/concept-card/concept-card.js | 8 +-- .../search/concept-modal/concept-modal.css | 3 + .../search/concept-modal/tabs/studies.js | 57 ++++++++++++++++--- .../add-to-cart/add-to-cart-dropdown.js | 26 ++++++--- .../add-to-cart/add-to-cart-icon-button.js | 32 +++++++++++ .../add-to-cart/add-to-cart-icon.js | 35 +----------- .../add-to-cart/add-to-cart-utilities.js | 22 ++++++- .../shopping-cart/add-to-cart/add-to-cart.svg | 4 ++ .../shopping-cart/add-to-cart/index.js | 3 +- .../shopping-cart/create-cart-modal.js | 13 +---- .../shopping-cart-popover.js | 1 - src/contexts/environment-context.js | 1 - src/contexts/shopping-cart-context.js | 34 +++++++---- 13 files changed, 161 insertions(+), 78 deletions(-) create mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-icon-button.js create mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart.svg diff --git a/src/components/search/concept-card/concept-card.js b/src/components/search/concept-card/concept-card.js index 1403faae..a26462bc 100644 --- a/src/components/search/concept-card/concept-card.js +++ b/src/components/search/concept-card/concept-card.js @@ -1,13 +1,13 @@ -import { Fragment, useState, useEffect, forwardRef } from 'react' +import { useState, forwardRef } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' -import { Card, Space } from 'antd' +import { Card } from 'antd' import { ShoppingCartOutlined as ShoppingCartIcon, ExpandOutlined as ViewIcon } from '@ant-design/icons' import { useHelxSearch } from '../' import { OverviewTab } from './overview-tab' import { StudiesTab } from './studies-tab' import { KnowledgeGraphsTab } from './knowledge-graphs-tab' -import { AddToCartIcon } from '../../shopping-cart' +import { AddToCartIconButton } from '../../shopping-cart' import { useAnalytics, useShoppingCart } from '../../../contexts' import './concept-card.css' @@ -43,7 +43,7 @@ export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=V onTabChange={key => setCurrentTab(key)} extra={
- + { icon && }
} diff --git a/src/components/search/concept-modal/concept-modal.css b/src/components/search/concept-modal/concept-modal.css index 866a6e63..553bcae5 100644 --- a/src/components/search/concept-modal/concept-modal.css +++ b/src/components/search/concept-modal/concept-modal.css @@ -26,6 +26,9 @@ padding-left: 1rem !important; border-left: 2px solid #eee; margin-bottom: 0.5rem; + gap: 2px !important; + overflow: hidden; + min-height: 80px; } .variable-name { diff --git a/src/components/search/concept-modal/tabs/studies.js b/src/components/search/concept-modal/tabs/studies.js index f1f71230..e62215b0 100644 --- a/src/components/search/concept-modal/tabs/studies.js +++ b/src/components/search/concept-modal/tabs/studies.js @@ -1,5 +1,7 @@ -import { useEffect, useState } from 'react' +import { Fragment, useEffect, useState } from 'react' import { Button, Collapse, Divider, List, Space, Tag, Typography } from 'antd' +import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' +import QueueAnim from 'rc-queue-anim' import { useHelxSearch } from '../../' import { Link } from '../../../link' import { AddToCartIcon, AddToCartDropdown } from '../../../shopping-cart' @@ -8,6 +10,51 @@ const { Text, Title } = Typography const { CheckableTag: CheckableFacet } = Tag const { Panel } = Collapse +const StudyVariable = ({ variable, study }) => { + const [hovering, setHovering] = useState(false) + const [active, setActive] = useState(false) + + useEffect(() => { + let timeout + if (hovering) { + timeout = setTimeout(() => { + setActive(true) + }, 300) + } else setActive(false) + + return () => { + clearTimeout(timeout) + } + }, [hovering]) + + return ( + setHovering(true), + onMouseLeave: () => setHovering(false) + }} + type={ ["right", "left"] } + leaveReverse + > + + { variable.name }   + ({ variable.e_link ?
{ variable.id } : variable.id }) + + { variable.description } +
+ +
+ + ) +} + export const StudiesTab = ({ studies }) => { const { selectedResult } = useHelxSearch() const [facets, setFacets] = useState([]) @@ -70,13 +117,7 @@ export const StudiesTab = ({ studies }) => { className="study-variables-list" dataSource={ item.elements } renderItem={ variable => ( -
- - { variable.name }   - ({ variable.e_link ? { variable.id } : variable.id }) -
- { variable.description } -
+ ) } /> ( buttonChildren ? buttonChildren : ( - isInCart(activeCart) ? - `Remove ${ concept ? "concept" : study ? "study" : variable ? "variable" : "" } from cart` : - `Add ${ concept ? "concept" : study ? "study" : variable ? "variable" : "" } to cart` + small ? ( + isInCart(activeCart) ? + : + + ) : ( + isInCart(activeCart) ? + `Remove ${ concept ? "concept" : study ? "study" : variable ? "variable" : "" } from cart` : + `Add ${ concept ? "concept" : study ? "study" : variable ? "variable" : "" } to cart` + ) ) ), [activeCart, concept, study, variable, buttonChildren, isInCart]) return ( } - style={{ minWidth: 225, ...buttonStyle }} + style={{ + minWidth: !small ? 225 : undefined, + ...buttonStyle + }} {...buttonProps} > { buttonContent } diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart-icon-button.js b/src/components/shopping-cart/add-to-cart/add-to-cart-icon-button.js new file mode 100644 index 00000000..f4e1781a --- /dev/null +++ b/src/components/shopping-cart/add-to-cart/add-to-cart-icon-button.js @@ -0,0 +1,32 @@ +import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' +import { useShoppingCart } from '../../../contexts' +import { useUtilities } from './' + +export const AddToCartIconButton = ({ + concept=undefined, + study=undefined, + variable=undefined, + from=null, + style={}, + ...props +}) => { + const { activeCart } = useShoppingCart() + const { isInCart, toggleCart } = useUtilities({ concept, study, variable, from }) + + return ( + { + e.preventDefault() + e.stopPropagation() + toggleCart(activeCart) + } } + style={{ + fontSize: 16, + color: isInCart(activeCart) ? "#1890ff" : undefined, + ...style + }} + {...props} + /> + ) +} \ No newline at end of file diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart-icon.js b/src/components/shopping-cart/add-to-cart/add-to-cart-icon.js index a531c941..a9efbc15 100644 --- a/src/components/shopping-cart/add-to-cart/add-to-cart-icon.js +++ b/src/components/shopping-cart/add-to-cart/add-to-cart-icon.js @@ -1,32 +1,3 @@ -import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' -import { useShoppingCart } from '../../../contexts' -import { useUtilities } from './' - -export const AddToCartIcon = ({ - concept=undefined, - study=undefined, - variable=undefined, - from=null, - style={}, - ...props -}) => { - const { activeCart } = useShoppingCart() - const { isInCart, toggleCart } = useUtilities({ concept, study, variable, from }) - - return ( - { - e.preventDefault() - e.stopPropagation() - toggleCart(activeCart) - } } - style={{ - fontSize: 16, - color: isInCart(activeCart) ? "#1890ff" : undefined, - ...style - }} - {...props} - /> - ) -} \ No newline at end of file +import Icon from '@ant-design/icons' +import { ReactComponent as _AddToCartIcon} from './add-to-cart.svg' +export const AddToCartIcon = (props) => \ No newline at end of file diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js b/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js index ae4f59fd..d421cb2c 100644 --- a/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js +++ b/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js @@ -1,4 +1,6 @@ import { useCallback, useMemo } from 'react' +import { message } from 'antd' +import { PlusOutlined, MinusOutlined } from '@ant-design/icons' import { useShoppingCart } from '../../../contexts' export const useUtilities = ({ concept, study, variable, from=null }) => { @@ -25,10 +27,18 @@ export const useUtilities = ({ concept, study, variable, from=null }) => { ) : false ), [ concept, study, variable, - isConceptInCart, isStudyInCart, isVariableInCart + isConceptInCart, isStudyInCart, isVariableInCart, + message ]) const addToCart = useCallback((cart) => { + const name = concept ? concept.name : study ? study.c_name : variable ? variable.name : "" + const id = concept ? concept.id : study ? study.c_id : variable ? variable.id : "" + message.info({ + content: `Added ${name} to ${cart.name}`, + icon: , + key: `cart-alert-${cart.name}-${id}` + }) return concept ? ( addConceptToCart(cart, concept, from) ) : study ? ( @@ -38,10 +48,18 @@ export const useUtilities = ({ concept, study, variable, from=null }) => { ) : {} }, [ concept, study, variable, from, - addConceptToCart, addStudyToCart, addVariableToCart + addConceptToCart, addStudyToCart, addVariableToCart, + message ]) const removeFromCart = useCallback((cart) => { + const name = concept ? concept.name : study ? study.c_name : variable ? variable.name : "" + const id = concept ? concept.id : study ? study.c_id : variable ? variable.id : "" + message.info({ + content: `Removed ${name} to ${cart.name}`, + icon: , + key: `cart-alert-${cart.name}-${id}` + }) return concept ? ( removeConceptFromCart(cart, concept) ) : study ? ( diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart.svg b/src/components/shopping-cart/add-to-cart/add-to-cart.svg new file mode 100644 index 00000000..f6facb5b --- /dev/null +++ b/src/components/shopping-cart/add-to-cart/add-to-cart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/shopping-cart/add-to-cart/index.js b/src/components/shopping-cart/add-to-cart/index.js index f2daf704..4a962ef1 100644 --- a/src/components/shopping-cart/add-to-cart/index.js +++ b/src/components/shopping-cart/add-to-cart/index.js @@ -1,4 +1,5 @@ export * from './add-to-cart-dropdown' -export * from './add-to-cart-icon' +export * from './add-to-cart-icon-button' export * from './add-to-cart-button' +export * from './add-to-cart-icon' export * from './add-to-cart-utilities' \ No newline at end of file diff --git a/src/components/shopping-cart/create-cart-modal.js b/src/components/shopping-cart/create-cart-modal.js index c382db28..514c5cf0 100644 --- a/src/components/shopping-cart/create-cart-modal.js +++ b/src/components/shopping-cart/create-cart-modal.js @@ -1,7 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { Modal, Space, Form, Input, Typography } from 'antd' import { StarOutlined, StarFilled, StarTwoTone } from '@ant-design/icons' -import { useShoppingCart } from '../../contexts' const { Text, Paragraph } = Typography @@ -71,9 +70,7 @@ const CreateCardModalContent = ({ createShoppingCart, cartName, setCartName, car ) } -export const CreateCartModal = ({ visible, onVisibleChange }) => { - const { carts, addCart, setActiveCart } = useShoppingCart() - +export const CreateCartModal = ({ carts, visible, onVisibleChange, onConfirm }) => { /** Form state */ const [cartName, setCartName] = useState("") const [cartNameError, setCartNameError] = useState(false) @@ -97,13 +94,9 @@ export const CreateCartModal = ({ visible, onVisibleChange }) => { if (carts.find((cart) => cart.name === cartName)) { setCartNameError(true) } else { - addCart(cartName, { - favorited - }) - setActiveCart(cartName) - onVisibleChange(false) + onConfirm(cartName, favorited) } - }, [carts, cartName, addCart, setActiveCart, onVisibleChange]) + }, [carts, cartName, onConfirm]) return ( { + return { + ...cartBlueprint, + ...props, + name, + createdTime: Date.now(), + modifiedTime: Date.now() + } +} export const ShoppingCartProvider = ({ children }) => { - const createCart = (name, props) => { - return { - ...cartBlueprint, - ...props, - name, - createdTime: Date.now(), - modifiedTime: Date.now() - } - } const [carts, setCarts] = useLocalStorage("shopping_carts", [ createCart("My cart", { canDelete: false }) ]) const [showCreateCartModal, setShowCreateCartModal] = useState(false) const [activeCartName, setActiveCartName] = useState("My cart") @@ -139,7 +139,19 @@ export const ShoppingCartProvider = ({ children }) => { }}> { children } - + { + addCart(cartName, { + favorited + }) + setActiveCart(cartName) + setShowCreateCartModal(false) + + } } + visible={ showCreateCartModal } + onVisibleChange={ setShowCreateCartModal } + /> ) From 0f59b800a981c62a07769a0526cf4f4f22f831a2 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 22 Jul 2022 17:38:20 -0400 Subject: [PATCH 28/55] Style improvements. --- .../search/concept-modal/concept-modal.css | 3 +- .../search/concept-modal/tabs/studies.js | 41 ++++++++----------- .../add-to-cart/add-to-cart-dropdown.js | 10 +++-- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/components/search/concept-modal/concept-modal.css b/src/components/search/concept-modal/concept-modal.css index 553bcae5..ad82a88d 100644 --- a/src/components/search/concept-modal/concept-modal.css +++ b/src/components/search/concept-modal/concept-modal.css @@ -22,13 +22,12 @@ } .study-variables-list-item { + display: flex; margin-left: 0.4rem !important; padding-left: 1rem !important; border-left: 2px solid #eee; margin-bottom: 0.5rem; gap: 2px !important; - overflow: hidden; - min-height: 80px; } .variable-name { diff --git a/src/components/search/concept-modal/tabs/studies.js b/src/components/search/concept-modal/tabs/studies.js index e62215b0..fc15ce70 100644 --- a/src/components/search/concept-modal/tabs/studies.js +++ b/src/components/search/concept-modal/tabs/studies.js @@ -28,30 +28,22 @@ const StudyVariable = ({ variable, study }) => { }, [hovering]) return ( - setHovering(true), - onMouseLeave: () => setHovering(false) - }} - type={ ["right", "left"] } - leaveReverse - > - - { variable.name }   - ({ variable.e_link ? { variable.id } : variable.id }) - - { variable.description } -
+
+ + + { variable.name }   + ({ variable.e_link ? { variable.id } : variable.id }) + + { variable.description } + +
- +
) } @@ -120,11 +112,14 @@ export const StudiesTab = ({ studies }) => { ) } /> - + + + {/* */} + )) diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart-dropdown.js b/src/components/shopping-cart/add-to-cart/add-to-cart-dropdown.js index 6fb9bd7e..79f51e92 100644 --- a/src/components/shopping-cart/add-to-cart/add-to-cart-dropdown.js +++ b/src/components/shopping-cart/add-to-cart/add-to-cart-dropdown.js @@ -46,9 +46,13 @@ export const AddToCartDropdown = ({ isInCart(cart) ? ( From 48343fe69568dc02f8dcb415fe019124169263c8 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 22 Jul 2022 17:58:23 -0400 Subject: [PATCH 29/55] Refactor CartList to use CartItem and add variable items to cart list. --- .../search/concept-modal/tabs/studies.js | 4 +- .../shopping-cart-popover/cart-list.js | 73 +++++++++++-------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/components/search/concept-modal/tabs/studies.js b/src/components/search/concept-modal/tabs/studies.js index fc15ce70..6df4082b 100644 --- a/src/components/search/concept-modal/tabs/studies.js +++ b/src/components/search/concept-modal/tabs/studies.js @@ -4,7 +4,7 @@ import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' import QueueAnim from 'rc-queue-anim' import { useHelxSearch } from '../../' import { Link } from '../../../link' -import { AddToCartIcon, AddToCartDropdown } from '../../../shopping-cart' +import { AddToCartIconButton, AddToCartDropdown } from '../../../shopping-cart' const { Text, Title } = Typography const { CheckableTag: CheckableFacet } = Tag @@ -100,7 +100,7 @@ export const StudiesTab = ({ studies }) => { extra={ { item.elements.length } variable{ item.elements.length === 1 ? '' : 's' } - {/* */} + } > diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-list.js b/src/components/shopping-cart/shopping-cart-popover/cart-list.js index ed038dae..03d76968 100644 --- a/src/components/shopping-cart/shopping-cart-popover/cart-list.js +++ b/src/components/shopping-cart/shopping-cart-popover/cart-list.js @@ -10,8 +10,10 @@ const { Text, Paragraph } = Typography const { Panel } = Collapse const CartSection = ({ name, data, renderItem }) => { - const [expanded, setExpanded] = useState(true) - const disabled = data.length === 0 + const [_expanded, setExpanded] = useState(true) + + const disabled = useMemo(() => data.length === 0, [data]) + const expanded = useMemo(() => _expanded && !disabled, [_expanded, disabled]) useEffect(() => { setExpanded(true) @@ -19,7 +21,7 @@ const CartSection = ({ name, data, renderItem }) => { return ( - setExpanded(!expanded) }> + setExpanded(!expanded) }> ( /> ) +const CartItem = ({ name, nameSecondary, description, onRemove }) => ( + +
+ + { name } + + + { nameSecondary } + + +
+ {description && ( + + { description } + + )} +
+) + export const CartList = () => { const { activeCart, removeConceptFromCart, removeStudyFromCart, removeVariableFromCart } = useShoppingCart() const { concepts, studies, variables } = activeCart @@ -72,17 +93,11 @@ export const CartList = () => { data={ concepts } renderItem={(concept) => ( - -
- - { concept.name } ({ concept.type }) - - removeConceptFromCart(activeCart, concept) }/> -
- - { concept.description } - -
+ removeConceptFromCart(activeCart, concept) } + />
)} /> @@ -91,23 +106,13 @@ export const CartList = () => { name="Studies" data={ studies } renderItem={(study) => ( - - -
- { study.c_name } - - ({ study.elements.length } variable{study.elements.length !== 1 && "s"}) - - removeStudyFromCart(activeCart, study) } - /> -
- {/* - { study.elements.map((variable) => variable.name).join(", ") } - */} - {/* { study.c_id } */} -
+ + removeStudyFromCart(activeCart, study) } + /> )} /> @@ -117,7 +122,11 @@ export const CartList = () => { data={ variables } renderItem={(variable) => ( - { variable.name } + removeVariableFromCart(activeCart, variable) } + /> )} /> From 1fb9b826ad96e11a49bc7562fb958a5c22900523 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 22 Jul 2022 18:16:25 -0400 Subject: [PATCH 30/55] Fix typo --- .../shopping-cart/add-to-cart/add-to-cart-utilities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js b/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js index d421cb2c..c429509b 100644 --- a/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js +++ b/src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js @@ -56,7 +56,7 @@ export const useUtilities = ({ concept, study, variable, from=null }) => { const name = concept ? concept.name : study ? study.c_name : variable ? variable.name : "" const id = concept ? concept.id : study ? study.c_id : variable ? variable.id : "" message.info({ - content: `Removed ${name} to ${cart.name}`, + content: `Removed ${name} from ${cart.name}`, icon: , key: `cart-alert-${cart.name}-${id}` }) From c973f2223104ac97074e363d2c54b65f764c90b8 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Mon, 25 Jul 2022 18:07:05 -0400 Subject: [PATCH 31/55] Moves shopping cart into its own package and adds as dependency. And changes associated with modularizing the shopping cart package. --- package.json | 4 +- src/app.js | 8 +- src/components/layout/layout.js | 19 +- .../search/concept-card/concept-card.js | 10 +- .../search/concept-card/studies-tab.js | 2 +- .../search/concept-modal/concept-modal.js | 9 +- .../search/concept-modal/tabs/studies.js | 19 +- .../add-to-cart/add-to-cart-button.js | 34 --- .../add-to-cart/add-to-cart-dropdown.js | 96 --------- .../add-to-cart/add-to-cart-icon-button.js | 32 --- .../add-to-cart/add-to-cart-icon.js | 3 - .../add-to-cart/add-to-cart-utilities.js | 87 -------- .../shopping-cart/add-to-cart/add-to-cart.css | 5 - .../shopping-cart/add-to-cart/add-to-cart.svg | 4 - .../shopping-cart/add-to-cart/index.js | 5 - .../shopping-cart/create-cart-modal.js | 126 ----------- src/components/shopping-cart/index.js | 3 - .../shopping-cart-popover/cart-list.js | 136 ------------ .../cart-select-dropdown.js | 197 ------------------ .../shopping-cart-popover/index.js | 2 - .../shopping-cart-list.css | 53 ----- .../shopping-cart-popover.css | 48 ----- .../shopping-cart-popover.js | 119 ----------- src/contexts/shopping-cart-context.js | 185 +++------------- src/hooks/index.js | 3 +- src/hooks/use-shopping-cart-utils.js | 32 +++ src/views/cart.js | 2 +- 27 files changed, 100 insertions(+), 1143 deletions(-) delete mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-button.js delete mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-dropdown.js delete mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-icon-button.js delete mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-icon.js delete mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart-utilities.js delete mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart.css delete mode 100644 src/components/shopping-cart/add-to-cart/add-to-cart.svg delete mode 100644 src/components/shopping-cart/add-to-cart/index.js delete mode 100644 src/components/shopping-cart/create-cart-modal.js delete mode 100644 src/components/shopping-cart/index.js delete mode 100644 src/components/shopping-cart/shopping-cart-popover/cart-list.js delete mode 100644 src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js delete mode 100644 src/components/shopping-cart/shopping-cart-popover/index.js delete mode 100644 src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css delete mode 100644 src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.css delete mode 100644 src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js create mode 100644 src/hooks/use-shopping-cart-utils.js diff --git a/package.json b/package.json index 3ff9d03a..95be0685 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "antd": "^4.21.7", + "antd-shopping-cart": "^1.0.0", "axios": "^0.21.1", "classnames": "^2.3.1", "eslint": "^7.27.0", @@ -49,5 +50,6 @@ "last 1 firefox version", "last 1 safari version" ] - } + }, + "devDependencies": {} } diff --git a/src/app.js b/src/app.js index d4abbca8..a6d9d78b 100644 --- a/src/app.js +++ b/src/app.js @@ -1,9 +1,13 @@ import { useEffect } from 'react' import { LocationProvider, Router as ReachRouter, globalHistory, useLocation } from '@reach/router' -import { EnvironmentProvider, ActivityProvider, AppProvider, InstanceProvider, AnalyticsProvider, useEnvironment, useAnalytics } from './contexts' +import { + EnvironmentProvider, ActivityProvider, AppProvider, + InstanceProvider, AnalyticsProvider, ShoppingCartProvider, + useEnvironment, useAnalytics +} from './contexts' import { Layout } from './components/layout' -import { ShoppingCartProvider } from './contexts' import { NotFoundView } from './views' +import 'antd-shopping-cart/dist/bundle.css' const ContextProviders = ({ children }) => { return ( diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index 5e5e6835..be1ecfeb 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -6,7 +6,7 @@ import { useEnvironment, useAnalytics } from '../../contexts'; import { logoutHandler } from '../../api/'; import { MobileMenu } from './menu'; import { SidePanel } from '../side-panel/side-panel'; -import { ShoppingCartPopover } from '../shopping-cart'; +import { CartPopoverButton } from 'antd-shopping-cart'; import './layout.css'; const { Text, Title } = Typography @@ -14,8 +14,6 @@ const { Header, Content, Footer } = AntLayout const { useBreakpoint } = Grid export const Layout = ({ children }) => { - const [showShoppingCart, setShowShoppingCart] = useState(false) - const { helxAppstoreUrl, routes, context, basePath } = useEnvironment() const { analyticsEvents } = useAnalytics() const { md } = useBreakpoint() @@ -52,18 +50,9 @@ export const Layout = ({ children }) => { }} /> */}
- - - +
{logoutButton && (
diff --git a/src/components/search/concept-card/concept-card.js b/src/components/search/concept-card/concept-card.js index a26462bc..3a949dbd 100644 --- a/src/components/search/concept-card/concept-card.js +++ b/src/components/search/concept-card/concept-card.js @@ -7,16 +7,18 @@ import { useHelxSearch } from '../' import { OverviewTab } from './overview-tab' import { StudiesTab } from './studies-tab' import { KnowledgeGraphsTab } from './knowledge-graphs-tab' -import { AddToCartIconButton } from '../../shopping-cart' -import { useAnalytics, useShoppingCart } from '../../../contexts' +import { AddToCartIconButton, useShoppingCart } from 'antd-shopping-cart' +import { useAnalytics } from '../../../contexts' +import { useShoppingCartUtilities } from '../../../hooks' import './concept-card.css' export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=ViewIcon, className="" }, ref) => { const { analyticsEvents } = useAnalytics() const { query } = useHelxSearch() - const { activeCart, cartUtilities: { isConceptInCart } } = useShoppingCart() const [currentTab, setCurrentTab] = useState('overview') + const { createConceptCartItem } = useShoppingCartUtilities() + const tabs = { 'overview': { title: 'Overview', content: }, 'studies': { title: `Studies`, content: }, @@ -43,7 +45,7 @@ export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=V onTabChange={key => setCurrentTab(key)} extra={
- + { icon && }
} diff --git a/src/components/search/concept-card/studies-tab.js b/src/components/search/concept-card/studies-tab.js index 5efb6ab4..086dd8e1 100644 --- a/src/components/search/concept-card/studies-tab.js +++ b/src/components/search/concept-card/studies-tab.js @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { List, Spin, Space, Tag, Typography } from 'antd' import { useHelxSearch } from '../' import { Link } from '../../link' -import { AddToCartIcon } from '../../shopping-cart' +import { AddToCartIcon } from 'antd-shopping-cart' const { Text } = Typography const { CheckableTag: CheckableFacet } = Tag diff --git a/src/components/search/concept-modal/concept-modal.js b/src/components/search/concept-modal/concept-modal.js index 19e25e3a..efb1d984 100644 --- a/src/components/search/concept-modal/concept-modal.js +++ b/src/components/search/concept-modal/concept-modal.js @@ -13,8 +13,9 @@ import { OverviewTab, StudiesTab, KnowledgeGraphsTab, TranQLTab } from './tabs' import { useHelxSearch } from '../' import { SearchLayout } from '../context' import { useAnalytics, useEnvironment } from '../../../contexts' +import { useShoppingCartUtilities } from '../../../hooks' import './concept-modal.css' -import { AddToCartDropdown } from '../../shopping-cart' +import { AddToCartDropdown } from 'antd-shopping-cart' // const RobokopIcon = () => } /> @@ -107,6 +108,8 @@ export const ConceptModal = ({ result, visible, closeHandler }) => { const { setFullscreenResult, query } = useHelxSearch() const [doFullscreen, setDoFullscreen] = useState(null) + const { createConceptCartItem } = useShoppingCartUtilities() + /** * Essentially peforming the same thing as what a this.setState call using a callback would do. * Need to render the modal as closed for one render in order for the modal to actually close @@ -142,9 +145,7 @@ export const ConceptModal = ({ result, visible, closeHandler }) => { footer={(
diff --git a/src/components/search/concept-modal/tabs/studies.js b/src/components/search/concept-modal/tabs/studies.js index 6df4082b..9cfddb29 100644 --- a/src/components/search/concept-modal/tabs/studies.js +++ b/src/components/search/concept-modal/tabs/studies.js @@ -1,10 +1,11 @@ import { Fragment, useEffect, useState } from 'react' import { Button, Collapse, Divider, List, Space, Tag, Typography } from 'antd' import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' +import { AddToCartIconButton, AddToCartDropdown } from 'antd-shopping-cart' import QueueAnim from 'rc-queue-anim' import { useHelxSearch } from '../../' import { Link } from '../../../link' -import { AddToCartIconButton, AddToCartDropdown } from '../../../shopping-cart' +import { useShoppingCartUtilities } from '../../../../hooks' const { Text, Title } = Typography const { CheckableTag: CheckableFacet } = Tag @@ -14,6 +15,8 @@ const StudyVariable = ({ variable, study }) => { const [hovering, setHovering] = useState(false) const [active, setActive] = useState(false) + const { createVariableCartItem } = useShoppingCartUtilities() + useEffect(() => { let timeout if (hovering) { @@ -38,9 +41,8 @@ const StudyVariable = ({ variable, study }) => {
@@ -51,6 +53,8 @@ export const StudiesTab = ({ studies }) => { const { selectedResult } = useHelxSearch() const [facets, setFacets] = useState([]) const [selectedFacets, setSelectedFacets] = useState([]) + + const { createStudyCartItem } = useShoppingCartUtilities() const handleSelectFacet = (facet, checked) => { const newSelection = new Set(selectedFacets) @@ -100,7 +104,10 @@ export const StudiesTab = ({ studies }) => { extra={ { item.elements.length } variable{ item.elements.length === 1 ? '' : 's' } - + } > @@ -114,9 +121,7 @@ export const StudiesTab = ({ studies }) => { /> {/* */} diff --git a/src/components/shopping-cart/add-to-cart/add-to-cart-button.js b/src/components/shopping-cart/add-to-cart/add-to-cart-button.js deleted file mode 100644 index 9abd9e30..00000000 --- a/src/components/shopping-cart/add-to-cart/add-to-cart-button.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Button } from 'antd' -import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' -import { useShoppingCart } from '../../../contexts' -import { useUtilities } from './' -import './add-to-cart.css' - -export const AddToCartButton = ({ - concept=undefined, - study=undefined, - variable=undefined, - from=null, - style={}, - ...props -}) => { - const { activeCart } = useShoppingCart() - const { isInCart, toggleCart } = useUtilities({ concept, study, variable, from }) - - return ( -
- } - /> -
- - - { expanded ? data.map((item) => renderItem(item) ) : null } - - -
- ) -} - -const RemoveItemButton = ({ style={}, onClick, ...props }) => ( - -) - -const CartItem = ({ name, nameSecondary, description, onRemove }) => ( - -
- - { name } - - - { nameSecondary } - - -
- {description && ( - - { description } - - )} -
-) - -export const CartList = () => { - const { activeCart, removeConceptFromCart, removeStudyFromCart, removeVariableFromCart } = useShoppingCart() - const { concepts, studies, variables } = activeCart - - return ( -
- ( - - removeConceptFromCart(activeCart, concept) } - /> - - )} - /> - - ( - - removeStudyFromCart(activeCart, study) } - /> - - )} - /> - - ( - - removeVariableFromCart(activeCart, variable) } - /> - - )} - /> - {/* */} -
- ) -} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js b/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js deleted file mode 100644 index 9250e2fd..00000000 --- a/src/components/shopping-cart/shopping-cart-popover/cart-select-dropdown.js +++ /dev/null @@ -1,197 +0,0 @@ -import { Fragment, useCallback, useMemo, useState } from 'react' -import { Dropdown, Menu, Input, Typography, Space } from 'antd' -import { PlusOutlined, SearchOutlined, ShoppingCartOutlined as ShoppingCartIcon, StarOutlined, StarFilled } from '@ant-design/icons' -import { useShoppingCart } from '../../../contexts' - -const { Text } = Typography - -const newCartSearchEntryDefaultProps = { - enabled: true, - hint: "(Create new)", - icon: , - onClick: undefined -} - -export const CartSelectDropdownMenu = ({ - onSelect, - newCartSearchEntry=newCartSearchEntryDefaultProps, - disableActiveCart=true, - disableSearchEntry=false, - disableNewCartEntry=false, - disableFavoriteButton=false, - disableClearSearchEntry=false, - cartIconRender=undefined, - showMoreCutoff=6, - cartEntryProps={}, - menuProps={}, - searchProps={} -}) => { - const { carts, activeCart, addCart, updateCart, setActiveCart, modals: { createCart } } = useShoppingCart() - const [cartSearch, setCartSearch] = useState("") - const [showAll, setShowAll] = useState(false) - - newCartSearchEntry = { ...newCartSearchEntryDefaultProps, ...newCartSearchEntry } - - const showMoreDisabled = useMemo(() => ( - showMoreCutoff <= 0 || showMoreCutoff === null || showMoreCutoff === undefined - ), [showMoreCutoff]) - - const cartSource = useMemo(() => ( - carts - .filter((cart) => cart.name.toLowerCase().includes(cartSearch.toLowerCase())) - .sort((a, b) => a.name.localeCompare(b.name)) - .sort((a, b) => (b === activeCart) - (a === activeCart)) - ), [carts, activeCart, cartSearch]) - - const createShoppingCart = useCallback((name) => { - addCart(name) - setActiveCart(name) - }, [addCart, setActiveCart]) - - const CartIcon = ({ cart }) => cartIconRender ? cartIconRender.call(null, cart) : - - return ( - - {!disableSearchEntry && ( - } - placeholder="Search for a cart" - allowClear - value={cartSearch} - onChange={(e) => setCartSearch(e.target.value)} - {...searchProps} - /> - )} - { - cartSource - .slice(0, (showAll || showMoreDisabled) ? cartSource.length : showMoreCutoff) - .map((cart) => { - const StarIcon = cart.favorited ? StarFilled : StarOutlined - const cartIcon = - - return ( - { - onSelect( - carts.find((cart) => cart.name === cartName) - ) - }} - disabled={ disableActiveCart && cart === activeCart } - { ...cartEntryProps } - > - { cartIcon } - {/* • */} - - { cart.name } - - {!disableFavoriteButton && ( - { - e.preventDefault() - e.stopPropagation() - updateCart(cart.name, { - favorited: !cart.favorited - }) - }} - /> - )} - - ) - }) - } - { - newCartSearchEntry.enabled && cartSearch && !carts.find((cart) => cart.name === cartSearch) && ( - { - newCartSearchEntry.onClick.call(null, cartSearch) - setCartSearch("") - } : - () => { - createShoppingCart(cartSearch) - setCartSearch("") - } - }> - { newCartSearchEntry.icon } - "{ cartSearch }" -  { newCartSearchEntry.hint } - - ) - } - { - !showMoreDisabled && (cartSource.length > showMoreCutoff) && ( - {} }> - ( - // Don't want the event to bubble up to Menu.Item, because that will result in the menu closing. - e.stopPropagation(), - setShowAll(!showAll) - )}> - { showAll ? "Show less" : `Show ${ cartSource.length - showMoreCutoff } more carts` } - - - ) - } - { - !disableClearSearchEntry && cartSearch && ( - - ( - e.stopPropagation(), - setCartSearch("") - )}>Clear search - - ) - } - - {!disableNewCartEntry && ( - - - - New cart - - - )} - {/* - - - Manage carts - - */} - - ) -} - -export const CartSelectDropdown = ({ - dropdownProps={}, - children, - ...dropdownMenuProps -}) => { - return ( - - } - { ...dropdownProps } - > - { children } - - ) -} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/index.js b/src/components/shopping-cart/shopping-cart-popover/index.js deleted file mode 100644 index c49d3a69..00000000 --- a/src/components/shopping-cart/shopping-cart-popover/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './shopping-cart-popover' -export * from './cart-select-dropdown' \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css deleted file mode 100644 index 7916ceea..00000000 --- a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-list.css +++ /dev/null @@ -1,53 +0,0 @@ -.shopping-cart-list { - display: flex; - flex-direction: column; - margin-top: -16px; -} -.shopping-cart-list .cart-section-divider { - /* margin: 2px 0; - display: none; */ - margin: 0; - /* These dividers need to be slightly more defined than the dividers between individual list items, since they are separating the lists themselves. */ - background: rgba(0, 0, 0, 0.05); -} -.shopping-cart-list .ant-collapse-header { - padding-left: 0 !important; - padding-right: 0 !important; -} -.shopping-cart-list .ant-collapse-header .ant-collapse-header-text { - flex-grow: 1; -} -.shopping-cart-list .ant-collapse-content-box { - padding: 0 !important; -} -.shopping-cart-list .ant-list { - /* border-left: 1px solid #f0f0f0; - padding-left: 8px; - margin-left: 5px; */ -} -.shopping-cart-list .ant-list-item { - margin-left: 4px; - margin-top: 8px; - padding-bottom: 8px; - - padding-top: 0px; -} -.shopping-cart-list .ant-list-item:first-child { - margin-top: 0; -} -.shopping-cart-list .ant-list-item:last-child { - padding-bottom: 12px; -} -.shopping-cart-list .ant-list-empty-text { - display: none; - /* padding: 4px 0; */ - padding-left: 0; - padding-right: 0; - padding-top: 0; - /* text-align: start; */ - /* font-style:italic; */ - /* color: rgba(0, 0, 0, 0.85); */ -} -.shopping-cart-list .cart-concept-description { - margin-bottom: 0; -} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.css b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.css deleted file mode 100644 index 0bd2a142..00000000 --- a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.css +++ /dev/null @@ -1,48 +0,0 @@ -.shopping-cart-popover { - width: 325px; -} -.shopping-cart-popover-buttons { - border-top: 1px solid #f0f0f0; - padding-top: 16px; - justify-content: space-around; -} -.clear-cart-confirm .ant-popover-message-title { - padding-left: 0 !important; -} -.manage-cart-list { - /* border-left: 1px solid #f0f0f0; */ -} -.manage-cart-item { - display: flex !important; - align-items: center; - justify-content: space-between; - margin-top: 8px; - border: none !important; - /* border-left: 1px solid #f0f0f0 !important; */ -} -.manage-cart-item:first-child { - margin-top: 0; -} -.cart-dropdown-menu { - padding: 2px 0 2px 0 !important; - width: 225px; - max-height: 400px; - overflow-y: auto; -} -.cart-dropdown-menu > .ant-dropdown-menu-item { - padding: 7px 12px; -} -.cart-dropdown-menu > .ant-dropdown-menu-item > .ant-dropdown-menu-title-content { - display: flex; - align-items: center; - width: 100%; - /*text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; */ -} -.cart-dropdown-search { - padding: 6px 11px !important; - box-shadow: none !important; - border: none !important; - border-bottom: 1px solid #f0f0f0 !important; -} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js b/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js deleted file mode 100644 index 1fc9d658..00000000 --- a/src/components/shopping-cart/shopping-cart-popover/shopping-cart-popover.js +++ /dev/null @@ -1,119 +0,0 @@ -import { Fragment, useEffect, useState } from 'react' -import { Badge, Button, Popover, Space, List, Typography, Input, Form, Popconfirm, Modal, Dropdown, Menu, Divider, Tooltip } from 'antd' -import { DownOutlined, ClearOutlined, ShoppingCartOutlined, ExclamationCircleOutlined } from '@ant-design/icons' -import Texty from 'rc-texty' -import TweenOne from 'rc-tween-one' -import { useShoppingCart } from '../../../contexts/' -import { CartList } from './cart-list' -import { CartSelectDropdown } from './cart-select-dropdown' -import './shopping-cart-popover.css' - -const { Title, Text, Paragraph } = Typography - -export const ShoppingCartPopover = ({ visible, onVisibleChange, children }) => { - const { activeCart, setActiveCart, emptyCart, cartUtilities: { countCart } } = useShoppingCart() - const [name, setName] = useState("") - - const checkoutDisabled = countCart(activeCart) === 0 - - /* Forces */ - useEffect(() => { - setName("") - }, [activeCart.name]) - - useEffect(() => { - if (!name) setName(activeCart.name) - }, [name]) - - return ( - - - - <Texty type="alpha" mode="sync" duration={ 300 } component="span">{name}</Texty> - {/* <> • { countCart(activeCart) } items </> */} - - setActiveCart(cart) }> - - - Change - - - - -
- } - content={ - - -
- - - {/* */} - Are you sure you want to empty your cart? - - - You won't be able to undo this action. - - {/* */} - - } - icon={ null } - onConfirm={ () => emptyCart(activeCart) } - okText="Yes, empty it" - cancelText="Cancel" - > - - - - {/* Button transitions don't currently work properly on Tooltip-wrapped buttons due to a bug in antd. */} - - - - -
-
- } - placement="bottomLeft" - trigger="click" - visible={ visible } - onVisibleChange={ onVisibleChange } - overlayStyle={{ minWidth: 300 }} - > - { children } - - - ) -} \ No newline at end of file diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 6cc45f50..7356a15c 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -1,160 +1,31 @@ -import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useState } from 'react' -import { CreateCartModal } from '../components/shopping-cart/create-cart-modal' -import { useLocalStorage } from '../hooks/use-local-storage' +import { ShoppingCartProvider as _ShoppingCartProvider } from 'antd-shopping-cart' -const ShoppingCartContext = createContext({}) - -const cartBlueprint = { - name: undefined, - createdTime: undefined, - modifiedTime: undefined, - canDelete: true, - favorited: false, - concepts: [], - variables: [], - studies: [] -} -const createCart = (name, props) => { - return { - ...cartBlueprint, - ...props, - name, - createdTime: Date.now(), - modifiedTime: Date.now() - } -} +const prices = {} export const ShoppingCartProvider = ({ children }) => { - const [carts, setCarts] = useLocalStorage("shopping_carts", [ createCart("My cart", { canDelete: false }) ]) - const [showCreateCartModal, setShowCreateCartModal] = useState(false) - const [activeCartName, setActiveCartName] = useState("My cart") - const activeCart = useMemo(() => carts.find((cart) => cart.name === activeCartName), [carts, activeCartName]) - - const getCart = useCallback((name) => { - if (typeof name !== "string") ({ name } = name) - const cart = carts.find((cart) => cart.name === name) - return cart - }, [carts]) - - const setActiveCart = useCallback((name) => { - const cart = getCart(name) - console.log(name, cart) - setActiveCartName(cart.name) - }, [carts]) - - const addCart = (name, props) => { - if (carts.find((cart) => cart.name === name)) throw new Error("Cannot create a new cart with duplicate `name` key.") - setCarts([ - ...carts, - createCart(name, props) - ]) - } - const removeCart = (name) => { - setCarts(carts.filter((cart) => cart.name !== name)) - if (name === activeCart.name) setActiveCart("My cart") - } - const updateCart = (name, props) => { - const cart = getCart(name) - if (!cart) throw new Error(`Attempted update of unknown cart: ${name}`) - - if (typeof props === "function") props = props.call(null, cart) - setCarts([ - ...carts.filter((_cart) => _cart !== cart), - { - ...cart, - ...props, - modifiedTime: Date.now() - } - ]) - } - - /** The `from` field will be appended to shopping cart elements to track where they originate from in the DUG UI. - * - * Structure is { type: string, value: any } where `value` depends on `type`. - * - { type: "search", value: "" } - * - { type: "concept", value: } - * - { type: "study", value: } - * - * It has been structured in this way in anticipation of future workflows beyond - * simply "searches yield concepts, concepts yield studies, studies yield variables" - * - */ - const addConceptToCart = (name, concept, from=null) => { - updateCart(name, (cart) => ({ - concepts: [...cart.concepts, { ...concept, from }] - })) - } - const addStudyToCart = (name, study, from=null) => { - updateCart(name, (cart) => ({ - studies: [...cart.studies, { ...study, from }] - })) - } - const addVariableToCart = (name, variable, from=null) => { - updateCart(name, (cart) => ({ - variables: [...cart.variables, { ...variable, from }] - })) - } - const removeConceptFromCart = (name, concept) => { - updateCart(name, (cart) => ({ - concepts: cart.concepts.filter((_concept) => _concept.id !== concept.id ) - })) - } - const removeStudyFromCart = (name, study) => { - updateCart(name, (cart) => ({ - studies: cart.studies.filter((_study) => _study.c_id !== study.c_id) - })) - } - const removeVariableFromCart = (name, variable) => { - updateCart(name, (cart) => ({ - variables: cart.variables.filter((_variable) => _variable.id !== variable.id) - })) - } - const emptyCart = (name) => { - updateCart(name, (cart) => ({ - concepts: [], - studies: [], - variables: [] - })) - } - - const cartUtilities = useMemo(() => ({ - isConceptInCart: (cart, concept) => !!cart.concepts.find((_concept) => _concept.id === concept.id), - isStudyInCart: (cart, study) => !!cart.studies.find((_study) => _study.c_id === study.c_id), - isVariableInCart: (cart, variable) => !!cart.variables.find((_variable) => _variable.id === variable.id), - countCart: (cart) => cart.concepts.length + cart.variables.length + cart.studies.length - }), []) - - const modals = useMemo(() => ({ - createCart: () => setShowCreateCartModal(true) - }), []) - - return ( - - - { children } - { - addCart(cartName, { - favorited - }) - setActiveCart(cartName) - setShowCreateCartModal(false) - - } } - visible={ showCreateCartModal } - onVisibleChange={ setShowCreateCartModal } - /> - - - ) -} - -export const useShoppingCart = () => useContext(ShoppingCartContext) \ No newline at end of file + return ( + <_ShoppingCartProvider + defaultCartName="My cart" + localStorageKey="shopping_carts" + buckets={[ + { + id: "concepts", + name: "Concepts", + itemName: "concept" + }, + { + id: "studies", + name: "Studies", + itemName: "study" + }, + { + id: "variables", + name: "Variables", + itemName: "variables" + } + ]} + > + { children } + + ) +} \ No newline at end of file diff --git a/src/hooks/index.js b/src/hooks/index.js index 92744c2b..82587eee 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -1,3 +1,4 @@ export * from './use-scroll-position' export * from './use-is-scrollable' -export * from './use-local-storage' \ No newline at end of file +export * from './use-local-storage' +export * from './use-shopping-cart-utils' \ No newline at end of file diff --git a/src/hooks/use-shopping-cart-utils.js b/src/hooks/use-shopping-cart-utils.js new file mode 100644 index 00000000..66df406b --- /dev/null +++ b/src/hooks/use-shopping-cart-utils.js @@ -0,0 +1,32 @@ +const createConceptCartItem = (concept, fromSearch) => ({ + id: concept.id, + name: `${concept.name} (${concept.type})`, + description: concept.description, + from: { type: "search", value: fromSearch }, + bucketId: "concepts", + item: concept, +}) +const createStudyCartItem = (study, fromConcept) => ({ + id: study.c_id, + name: study.c_name, + nameSecondary: `(${ study.elements.length } variable${study.elements.length !== 1 && "s"})`, + from: { type: "concept", value: fromConcept }, + bucketId: "studies", + item: study, +}) +const createVariableCartItem = (variable, fromStudy) => ({ + id: variable.id, + name: variable.name, + description: variable.description, + from: { type: "study", value: fromStudy }, + bucketId: "variables", + item: variable, +}) + +export const useShoppingCartUtilities = () => { + return { + createConceptCartItem, + createStudyCartItem, + createVariableCartItem + } +} \ No newline at end of file diff --git a/src/views/cart.js b/src/views/cart.js index a986f694..b4b30991 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,6 +1,6 @@ import { Fragment } from 'react' import { Typography } from 'antd' -import { useShoppingCart } from '../contexts' +import { useShoppingCart } from 'antd-shopping-cart' const { Title } = Typography From 018c817ddc7f5be3eb093671d679c53e3cb2a575 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Mon, 25 Jul 2022 18:43:11 -0400 Subject: [PATCH 32/55] Fix price/tax properties. --- src/hooks/use-shopping-cart-utils.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hooks/use-shopping-cart-utils.js b/src/hooks/use-shopping-cart-utils.js index 66df406b..ab6daf9b 100644 --- a/src/hooks/use-shopping-cart-utils.js +++ b/src/hooks/use-shopping-cart-utils.js @@ -2,6 +2,8 @@ const createConceptCartItem = (concept, fromSearch) => ({ id: concept.id, name: `${concept.name} (${concept.type})`, description: concept.description, + price: Math.random() * 99, + tax: null, from: { type: "search", value: fromSearch }, bucketId: "concepts", item: concept, @@ -10,6 +12,8 @@ const createStudyCartItem = (study, fromConcept) => ({ id: study.c_id, name: study.c_name, nameSecondary: `(${ study.elements.length } variable${study.elements.length !== 1 && "s"})`, + price: null, + tax: null, from: { type: "concept", value: fromConcept }, bucketId: "studies", item: study, @@ -18,6 +22,8 @@ const createVariableCartItem = (variable, fromStudy) => ({ id: variable.id, name: variable.name, description: variable.description, + price: null, + tax: null, from: { type: "study", value: fromStudy }, bucketId: "variables", item: variable, From 50736fd49ea68b733c1d30a297beb658f53a40fd Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Mon, 25 Jul 2022 18:43:47 -0400 Subject: [PATCH 33/55] Remove price field on concept cart items. --- src/hooks/use-shopping-cart-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/use-shopping-cart-utils.js b/src/hooks/use-shopping-cart-utils.js index ab6daf9b..3115c0b6 100644 --- a/src/hooks/use-shopping-cart-utils.js +++ b/src/hooks/use-shopping-cart-utils.js @@ -2,7 +2,7 @@ const createConceptCartItem = (concept, fromSearch) => ({ id: concept.id, name: `${concept.name} (${concept.type})`, description: concept.description, - price: Math.random() * 99, + price: null, tax: null, from: { type: "search", value: fromSearch }, bucketId: "concepts", From 24ad4a98ce63f0e327b929203068e7b1d410e589 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 26 Jul 2022 14:39:20 -0400 Subject: [PATCH 34/55] Adds checkout button functionality. Compatibility updates with shopping cart package. Fix margin on concept-card add to cart button. --- src/components/layout/layout.js | 3 ++- src/components/search/concept-card/concept-card.js | 2 +- src/components/search/concept-modal/concept-modal.js | 4 ++-- src/components/search/concept-modal/tabs/studies.js | 6 +++--- src/views/cart.js | 6 +++++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index be1ecfeb..77504283 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Layout as AntLayout, Button, Menu, Grid, Divider, Badge, Popover, Typography, Tag, Space } from 'antd' import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' -import { useLocation, Link } from '@reach/router' +import { useLocation, Link, redirectTo, navigate } from '@reach/router' import { useEnvironment, useAnalytics } from '../../contexts'; import { logoutHandler } from '../../api/'; import { MobileMenu } from './menu'; @@ -51,6 +51,7 @@ export const Layout = ({ children }) => { /> */}
navigate(`${baseLinkPath}/cart`) } buttonProps={{ style: { marginRight: !logoutButton ? 8 : undefined } }} />
diff --git a/src/components/search/concept-card/concept-card.js b/src/components/search/concept-card/concept-card.js index 3a949dbd..03c5d7de 100644 --- a/src/components/search/concept-card/concept-card.js +++ b/src/components/search/concept-card/concept-card.js @@ -45,7 +45,7 @@ export const ConceptCard = forwardRef(({ index, result, openModalHandler, icon=V onTabChange={key => setCurrentTab(key)} extra={
- + { icon && }
} diff --git a/src/components/search/concept-modal/concept-modal.js b/src/components/search/concept-modal/concept-modal.js index efb1d984..fc26c958 100644 --- a/src/components/search/concept-modal/concept-modal.js +++ b/src/components/search/concept-modal/concept-modal.js @@ -15,7 +15,7 @@ import { SearchLayout } from '../context' import { useAnalytics, useEnvironment } from '../../../contexts' import { useShoppingCartUtilities } from '../../../hooks' import './concept-modal.css' -import { AddToCartDropdown } from 'antd-shopping-cart' +import { AddToCartDropdownButton } from 'antd-shopping-cart' // const RobokopIcon = () => } /> @@ -144,7 +144,7 @@ export const ConceptModal = ({ result, visible, closeHandler }) => { cancelButtonProps={{ hidden: true }} footer={(
- diff --git a/src/components/search/concept-modal/tabs/studies.js b/src/components/search/concept-modal/tabs/studies.js index 9cfddb29..4374a42b 100644 --- a/src/components/search/concept-modal/tabs/studies.js +++ b/src/components/search/concept-modal/tabs/studies.js @@ -1,7 +1,7 @@ import { Fragment, useEffect, useState } from 'react' import { Button, Collapse, Divider, List, Space, Tag, Typography } from 'antd' import { ShoppingCartOutlined as ShoppingCartIcon } from '@ant-design/icons' -import { AddToCartIconButton, AddToCartDropdown } from 'antd-shopping-cart' +import { AddToCartIconButton, AddToCartDropdownButton } from 'antd-shopping-cart' import QueueAnim from 'rc-queue-anim' import { useHelxSearch } from '../../' import { Link } from '../../../link' @@ -40,7 +40,7 @@ const StudyVariable = ({ variable, study }) => { { variable.description }
- @@ -120,7 +120,7 @@ export const StudiesTab = ({ studies }) => { ) } /> - {/* */} diff --git a/src/views/cart.js b/src/views/cart.js index b4b30991..f10342e0 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,4 +1,4 @@ -import { Fragment } from 'react' +import { Fragment, useEffect } from 'react' import { Typography } from 'antd' import { useShoppingCart } from 'antd-shopping-cart' @@ -7,6 +7,10 @@ const { Title } = Typography export const ShoppingCartView = () => { const { cart } = useShoppingCart() + useEffect(() => { + document.title = `Shopping Cart · HeLx UI` + }, []) + return ( Shopping Cart From f73d24806cc3c59bc1d877c8fe2054ee3647ffc9 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 26 Jul 2022 15:48:09 -0400 Subject: [PATCH 35/55] Fix styling issues caused by merge --- src/components/search/concept-modal/concept-modal.css | 1 + .../concept-modal/tabs/studies/study-variable.js | 4 ++-- .../search/concept-modal/tabs/studies/study.js | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/search/concept-modal/concept-modal.css b/src/components/search/concept-modal/concept-modal.css index 731e3b20..7afaa15b 100644 --- a/src/components/search/concept-modal/concept-modal.css +++ b/src/components/search/concept-modal/concept-modal.css @@ -33,6 +33,7 @@ .study-variables-list-item { display: flex; + flex-direction: column; margin-left: 4px; padding-left: 1rem !important; border-left: 2px solid #eee; diff --git a/src/components/search/concept-modal/tabs/studies/study-variable.js b/src/components/search/concept-modal/tabs/studies/study-variable.js index b9c1b3d8..873d46a7 100644 --- a/src/components/search/concept-modal/tabs/studies/study-variable.js +++ b/src/components/search/concept-modal/tabs/studies/study-variable.js @@ -13,11 +13,11 @@ export const StudyVariable = ({ study, variable, highlight, ...props }) => {   ({ variable.e_link ? { variable.id } : variable.id }) -
+ -
+
+ + + { collapsed && highlightedVariables.length > 0 && (
@@ -54,11 +59,6 @@ export const Study = ({ concept, study, highlight, collapsed, ...panelProps }) = highlight={ highlight } /> ))} - - -
)} From 91bf2b9888379a3b474637acb3737bc991b0b1db Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 26 Jul 2022 16:55:29 -0400 Subject: [PATCH 36/55] Blueprinting cart export view --- src/components/layout/layout.js | 2 +- src/views/cart.js | 63 +++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index e71ffbfc..6e1f0d03 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -60,8 +60,8 @@ export const Layout = ({ children }) => { /> */}
navigate(`${baseLinkPath}/cart`) } - buttonProps={{ style: { marginRight: !logoutButton ? 8 : undefined } }} />
{logoutButton && ( diff --git a/src/views/cart.js b/src/views/cart.js index f10342e0..ef5c36f7 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,11 +1,22 @@ import { Fragment, useEffect } from 'react' -import { Typography } from 'antd' +import { Space, Layout, Typography, Menu } from 'antd' +import { ShoppingCartOutlined } from '@ant-design/icons' import { useShoppingCart } from 'antd-shopping-cart' +import { useEnvironment } from '../contexts' +import { Breadcrumbs } from '../components/layout' -const { Title } = Typography +const { Title, Text } = Typography +const { Sider, Content } = Layout export const ShoppingCartView = () => { - const { cart } = useShoppingCart() + const { context } = useEnvironment() + const { carts, activeCart, setActiveCart } = useShoppingCart() + + const breadcrumbs = [ + { text: 'Home', path: '/helx' }, + // { text: 'Search', path: '/helx/search' }, + { text: 'Cart', path: '/cart' }, + ] useEffect(() => { document.title = `Shopping Cart · HeLx UI` @@ -13,10 +24,48 @@ export const ShoppingCartView = () => { return ( - Shopping Cart -
-        { JSON.stringify(cart, null, 2) }
-      
+ { context.workspaces_enabled === 'true' && } + + + a.name.localeCompare(b.name)).map((cart) => ({ + key: cart.name, + name: cart.name, + label: cart.name, + icon: + }) )} + onSelect={ ({ key: name }) => setActiveCart(name) } + /> + + +
+ + + { activeCart.name } + + + { activeCart.items.length } items + + + + Manage + +
+
+ ) } \ No newline at end of file From 353cc71e1064cb9434de6d268dc54b7463b2b5d2 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Thu, 28 Jul 2022 14:28:53 -0400 Subject: [PATCH 37/55] Work-in-progress cart checkout/export page. --- src/components/layout/layout.css | 16 ++++++++++++++++ src/components/layout/layout.js | 2 +- src/hooks/use-shopping-cart-utils.js | 3 +++ src/views/cart.js | 17 +++++++++++------ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/components/layout/layout.css b/src/components/layout/layout.css index 71eed585..1bc460a6 100644 --- a/src/components/layout/layout.css +++ b/src/components/layout/layout.css @@ -34,6 +34,22 @@ /* margin-left: 16px; */ } +.layout .ant-layout-content[data-active-route="/cart"] { + flex-grow: 1; +} + +/** Rules for getting overflow into the flex display. */ +.cart-layout .cart-layout-content { + flex-grow: 1; + height: 0; +} +.cart-layout .cart-layout-content > *, .cart-layout .cart-layout-content > * > *, .cart-layout .cart-layout-content > * > * > * { + height: 100%; +} +.cart-layout .cart-layout-content .shopping-cart-list .ant-tabs-content-holder { + overflow: auto; +} + .icon-btn { cursor: pointer; transition: color 0.3s ease; diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index 6e1f0d03..67cd78b3 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -74,7 +74,7 @@ export const Layout = ({ children }) => { )}
- + location.pathname === `${ baseLinkPath }${ route.path }`).path }> {children} {context.workspaces_enabled === 'true' && } diff --git a/src/hooks/use-shopping-cart-utils.js b/src/hooks/use-shopping-cart-utils.js index 3115c0b6..59bda43f 100644 --- a/src/hooks/use-shopping-cart-utils.js +++ b/src/hooks/use-shopping-cart-utils.js @@ -4,6 +4,7 @@ const createConceptCartItem = (concept, fromSearch) => ({ description: concept.description, price: null, tax: null, + quantity: 1, from: { type: "search", value: fromSearch }, bucketId: "concepts", item: concept, @@ -14,6 +15,7 @@ const createStudyCartItem = (study, fromConcept) => ({ nameSecondary: `(${ study.elements.length } variable${study.elements.length !== 1 && "s"})`, price: null, tax: null, + quantity: 1, from: { type: "concept", value: fromConcept }, bucketId: "studies", item: study, @@ -24,6 +26,7 @@ const createVariableCartItem = (variable, fromStudy) => ({ description: variable.description, price: null, tax: null, + quantity: 1, from: { type: "study", value: fromStudy }, bucketId: "variables", item: variable, diff --git a/src/views/cart.js b/src/views/cart.js index ef5c36f7..3408a554 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,16 +1,17 @@ import { Fragment, useEffect } from 'react' -import { Space, Layout, Typography, Menu } from 'antd' +import { Space, Layout, Typography, Menu, Tabs, List } from 'antd' import { ShoppingCartOutlined } from '@ant-design/icons' -import { useShoppingCart } from 'antd-shopping-cart' +import { CartList, useShoppingCart } from 'antd-shopping-cart' import { useEnvironment } from '../contexts' import { Breadcrumbs } from '../components/layout' const { Title, Text } = Typography const { Sider, Content } = Layout +const { TabPane } = Tabs export const ShoppingCartView = () => { const { context } = useEnvironment() - const { carts, activeCart, setActiveCart } = useShoppingCart() + const { buckets, carts, activeCart, setActiveCart } = useShoppingCart() const breadcrumbs = [ { text: 'Home', path: '/helx' }, @@ -25,7 +26,7 @@ export const ShoppingCartView = () => { return ( { context.workspaces_enabled === 'true' && } - + {
- + { <a type="button">Manage</a> </Space> </div> + <Space className="cart-layout-content" direction="vertical" style={{ marginTop: 8 }}> + <CartList small={ false } /> + </Space> + <div className="cart-content-footer" style={{ paddingTop: 24, marginBottom: -8, borderTop: "1px solid #f0f0f0" }}>footer</div> </Content> </Layout> </Fragment> From c38893996be1ff8dc064eb078e0b9b749635b0a3 Mon Sep 17 00:00:00 2001 From: Griffin Roupe <groupe@renci.org> Date: Fri, 29 Jul 2022 11:37:48 -0400 Subject: [PATCH 38/55] Adds prototyped export tray to cart view. Fix bug were studies nameSecondary would append false when only 1 variable is attached. --- src/hooks/use-shopping-cart-utils.js | 2 +- src/views/cart.js | 63 +++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/hooks/use-shopping-cart-utils.js b/src/hooks/use-shopping-cart-utils.js index 59bda43f..75e8e5cf 100644 --- a/src/hooks/use-shopping-cart-utils.js +++ b/src/hooks/use-shopping-cart-utils.js @@ -12,7 +12,7 @@ const createConceptCartItem = (concept, fromSearch) => ({ const createStudyCartItem = (study, fromConcept) => ({ id: study.c_id, name: study.c_name, - nameSecondary: `(${ study.elements.length } variable${study.elements.length !== 1 && "s"})`, + nameSecondary: `(${ study.elements.length } variable${study.elements.length !== 1 ? "s" : ""})`, price: null, tax: null, quantity: 1, diff --git a/src/views/cart.js b/src/views/cart.js index 3408a554..c3e75f7c 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,6 +1,7 @@ import { Fragment, useEffect } from 'react' -import { Space, Layout, Typography, Menu, Tabs, List } from 'antd' -import { ShoppingCartOutlined } from '@ant-design/icons' +import { Space, Layout, Typography, Menu, Tabs, List, Button, Checkbox, Dropdown } from 'antd' +import { ShoppingCartOutlined, DeleteOutlined, DownOutlined } from '@ant-design/icons' +import QueueAnim from 'rc-queue-anim' import { CartList, useShoppingCart } from 'antd-shopping-cart' import { useEnvironment } from '../contexts' import { Breadcrumbs } from '../components/layout' @@ -58,7 +59,7 @@ export const ShoppingCartView = () => { { activeCart.name } - { activeCart.items.length } items + { activeCart.items.length } item{ activeCart.items.length !== 1 ? "s" : "" } @@ -66,9 +67,61 @@ export const ShoppingCartView = () => {
- + { + const selected = selectedItems.length > 0 + return ( +
+
+ { selected && ( + + foobar
} + > + + + + + + + + + + ) } +
+ {/* { selected && ( + + ) } */} + + + ) + } } + />
-
footer
+ {/*
footer
*/}
From b5c599733abc65ba6cc1f85acffc8c69a0680182 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 29 Jul 2022 11:44:27 -0400 Subject: [PATCH 39/55] Refactor shopping cart view into view + component --- src/components/layout/layout.css | 16 --- src/components/shopping-cart/index.js | 1 + .../shopping-cart/shopping-cart.css | 22 ++++ src/components/shopping-cart/shopping-cart.js | 111 ++++++++++++++++++ src/views/cart.js | 108 +---------------- 5 files changed, 136 insertions(+), 122 deletions(-) create mode 100644 src/components/shopping-cart/index.js create mode 100644 src/components/shopping-cart/shopping-cart.css create mode 100644 src/components/shopping-cart/shopping-cart.js diff --git a/src/components/layout/layout.css b/src/components/layout/layout.css index 1bc460a6..71eed585 100644 --- a/src/components/layout/layout.css +++ b/src/components/layout/layout.css @@ -34,22 +34,6 @@ /* margin-left: 16px; */ } -.layout .ant-layout-content[data-active-route="/cart"] { - flex-grow: 1; -} - -/** Rules for getting overflow into the flex display. */ -.cart-layout .cart-layout-content { - flex-grow: 1; - height: 0; -} -.cart-layout .cart-layout-content > *, .cart-layout .cart-layout-content > * > *, .cart-layout .cart-layout-content > * > * > * { - height: 100%; -} -.cart-layout .cart-layout-content .shopping-cart-list .ant-tabs-content-holder { - overflow: auto; -} - .icon-btn { cursor: pointer; transition: color 0.3s ease; diff --git a/src/components/shopping-cart/index.js b/src/components/shopping-cart/index.js new file mode 100644 index 00000000..e8887b4b --- /dev/null +++ b/src/components/shopping-cart/index.js @@ -0,0 +1 @@ +export * from './shopping-cart' \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart.css b/src/components/shopping-cart/shopping-cart.css new file mode 100644 index 00000000..0e7f85be --- /dev/null +++ b/src/components/shopping-cart/shopping-cart.css @@ -0,0 +1,22 @@ +/** Rules for getting overflow into the flex display rather than the page itself. */ +.layout .ant-layout-content[data-active-route="/cart"] { + flex-grow: 1; +} +.cart-layout .cart-layout-content { + flex-grow: 1; + height: 0; + /* margin-bottom: -16px; */ +} +.cart-layout .cart-layout-content > *, .cart-layout .cart-layout-content > * > * { + height: 100%; +} +.cart-layout .cart-layout-content .shopping-cart-list .ant-tabs { + height: 0; +} + +.cart-layout .cart-layout-content .shopping-cart-list .ant-tabs-content-holder { + margin-left: -20px; +} +.cart-layout .cart-list-extra { + margin-bottom: -16px; +} \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart.js b/src/components/shopping-cart/shopping-cart.js new file mode 100644 index 00000000..e84467e8 --- /dev/null +++ b/src/components/shopping-cart/shopping-cart.js @@ -0,0 +1,111 @@ +import { Fragment } from 'react' +import { Space, Layout, Typography, Menu, Button, Checkbox, Dropdown } from 'antd' +import { ShoppingCartOutlined, DeleteOutlined, DownOutlined } from '@ant-design/icons' +import { CartList, useShoppingCart } from 'antd-shopping-cart' +import './shopping-cart.css' + +const { Title, Text } = Typography +const { Sider, Content } = Layout + +export const ShoppingCart = () => { + const { buckets, carts, activeCart, setActiveCart } = useShoppingCart() + + return ( + + + a.name.localeCompare(b.name)).map((cart) => ({ + key: cart.name, + name: cart.name, + label: cart.name, + icon: + }) )} + onSelect={ ({ key: name }) => setActiveCart(name) } + /> + + +
+ + + { activeCart.name } + + + { activeCart.items.length } item{ activeCart.items.length !== 1 ? "s" : "" } + + + + Manage + +
+ + { + const selected = selectedItems.length > 0 + return ( +
+
+ { selected && ( + + foobar
} + > + + + + + + + + + + ) } +
+ {/* { selected && ( + + ) } */} + + + ) + } } + /> +
+
+ + ) +} \ No newline at end of file diff --git a/src/views/cart.js b/src/views/cart.js index c3e75f7c..1cde0aab 100644 --- a/src/views/cart.js +++ b/src/views/cart.js @@ -1,18 +1,10 @@ import { Fragment, useEffect } from 'react' -import { Space, Layout, Typography, Menu, Tabs, List, Button, Checkbox, Dropdown } from 'antd' -import { ShoppingCartOutlined, DeleteOutlined, DownOutlined } from '@ant-design/icons' -import QueueAnim from 'rc-queue-anim' -import { CartList, useShoppingCart } from 'antd-shopping-cart' import { useEnvironment } from '../contexts' import { Breadcrumbs } from '../components/layout' - -const { Title, Text } = Typography -const { Sider, Content } = Layout -const { TabPane } = Tabs +import { ShoppingCart } from '../components/shopping-cart' export const ShoppingCartView = () => { const { context } = useEnvironment() - const { buckets, carts, activeCart, setActiveCart } = useShoppingCart() const breadcrumbs = [ { text: 'Home', path: '/helx' }, @@ -27,103 +19,7 @@ export const ShoppingCartView = () => { return ( { context.workspaces_enabled === 'true' && } - - - a.name.localeCompare(b.name)).map((cart) => ({ - key: cart.name, - name: cart.name, - label: cart.name, - icon: - }) )} - onSelect={ ({ key: name }) => setActiveCart(name) } - /> - - -
- - - { activeCart.name } - - - { activeCart.items.length } item{ activeCart.items.length !== 1 ? "s" : "" } - - - - Manage - -
- - { - const selected = selectedItems.length > 0 - return ( -
-
- { selected && ( - - foobar
} - > - - - - - - - - - - ) } -
- {/* { selected && ( - - ) } */} - - - ) - } } - /> -
- {/*
footer
*/} -
- + ) } \ No newline at end of file From 98ca667fcdf98d8f933ebb8ebb0ccd2ef989a7c7 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 29 Jul 2022 13:30:54 -0400 Subject: [PATCH 40/55] Fix typo caused by merge in concept studies card --- src/components/search/concept-card/studies-tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/search/concept-card/studies-tab.js b/src/components/search/concept-card/studies-tab.js index cdba6d36..94c7dd73 100644 --- a/src/components/search/concept-card/studies-tab.js +++ b/src/components/search/concept-card/studies-tab.js @@ -95,7 +95,7 @@ export const StudiesTab = ({ result }) => { { study.elements && `${ study.elements.length } variable${ study.elements.length === 1 ? '' : 's'}` } -4 + ) } /> From 5e10eb9145a7d6f49836c529e2a4caf55e6555bb Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 29 Jul 2022 16:01:57 -0400 Subject: [PATCH 41/55] Adds add to cart icon in study card tab --- src/components/search/concept-card/studies-tab.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/search/concept-card/studies-tab.js b/src/components/search/concept-card/studies-tab.js index 94c7dd73..a6e5e451 100644 --- a/src/components/search/concept-card/studies-tab.js +++ b/src/components/search/concept-card/studies-tab.js @@ -1,14 +1,16 @@ import { Fragment, useCallback, useEffect, useMemo, useState } from 'react' import { List, Spin, Space, Tag, Typography, Divider } from 'antd' +import { AddToCartIcon, AddToCartIconButton } from 'antd-shopping-cart' import { useHelxSearch } from '../' import { Link } from '../../link' -import { AddToCartIcon } from 'antd-shopping-cart' +import { useShoppingCartUtilities } from '../../../hooks' const { Text } = Typography const { CheckableTag: CheckableFacet } = Tag export const StudiesTab = ({ result }) => { const { query, fetchStudyVariables, fetchCDEs } = useHelxSearch() + const { createStudyCartItem } = useShoppingCartUtilities() const [studies, setStudies] = useState([]) const [loading, setLoading] = useState(true) const [facets, setFacets] = useState([]) @@ -95,6 +97,7 @@ export const StudiesTab = ({ result }) => { { study.elements && `${ study.elements.length } variable${ study.elements.length === 1 ? '' : 's'}` } + ) } /> From 461d43d02abcfe7a9d5d3c6c0cf4a2ed40819fa6 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 29 Jul 2022 16:56:51 -0400 Subject: [PATCH 42/55] Improves extra panel in cart checkout. --- .../shopping-cart/shopping-cart.css | 8 ++ src/components/shopping-cart/shopping-cart.js | 83 +++++++++++++------ 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/components/shopping-cart/shopping-cart.css b/src/components/shopping-cart/shopping-cart.css index 0e7f85be..ec7e6e71 100644 --- a/src/components/shopping-cart/shopping-cart.css +++ b/src/components/shopping-cart/shopping-cart.css @@ -19,4 +19,12 @@ } .cart-layout .cart-list-extra { margin-bottom: -16px; +} +.cart-layout .cart-list-extra .selected-buttons .selected-item-checkbox .ant-checkbox-indeterminate .ant-checkbox-inner::after { + height: 1.5px; + background-color: rgba(0, 0, 0, 0.45); + transition: background-color 300ms ease; +} +.cart-layout .cart-list-extra .selected-buttons .selected-item-checkbox:hover .ant-checkbox-indeterminate .ant-checkbox-inner::after { + background-color: #1890ff; } \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart.js b/src/components/shopping-cart/shopping-cart.js index e84467e8..58da074a 100644 --- a/src/components/shopping-cart/shopping-cart.js +++ b/src/components/shopping-cart/shopping-cart.js @@ -1,6 +1,6 @@ import { Fragment } from 'react' import { Space, Layout, Typography, Menu, Button, Checkbox, Dropdown } from 'antd' -import { ShoppingCartOutlined, DeleteOutlined, DownOutlined } from '@ant-design/icons' +import { ShoppingCartOutlined, DeleteOutlined, FolderAddOutlined, CopyOutlined, CaretDownOutlined } from '@ant-design/icons' import { CartList, useShoppingCart } from 'antd-shopping-cart' import './shopping-cart.css' @@ -59,36 +59,67 @@ export const ShoppingCart = () => { }} renderExtra={ ({ selectedItems, setSelectedItems }) => { const selected = selectedItems.length > 0 + const allSelected = selectedItems.length === activeCart.items.length + const indeterminateSelection = selected && !allSelected + const deselectAll = () => setSelectedItems([]) + const selectAll = () => setSelectedItems(activeCart.items.map((item) => item.id)) return (
+ + + ({ + label: `${ bucket.name }`, + key: `all-${ bucket.id }`, + onClick: () => setSelectedItems(activeCart.items.filter((item) => item.bucketId === bucket.id).map((item) => item.id)) + })) + ]} + /> + ) } + > + + + { selected && ( - foobar
} - > - - - - - - - - +
From 38a1ec43afb3571846cfdf6802a3008befff76e5 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Fri, 29 Jul 2022 17:28:49 -0400 Subject: [PATCH 43/55] Moves extra tray to antd-shopping-cart and configures it. --- .../shopping-cart/shopping-cart.css | 8 -- src/components/shopping-cart/shopping-cart.js | 89 +++---------------- src/contexts/shopping-cart-context.js | 2 +- 3 files changed, 11 insertions(+), 88 deletions(-) diff --git a/src/components/shopping-cart/shopping-cart.css b/src/components/shopping-cart/shopping-cart.css index ec7e6e71..0e7f85be 100644 --- a/src/components/shopping-cart/shopping-cart.css +++ b/src/components/shopping-cart/shopping-cart.css @@ -19,12 +19,4 @@ } .cart-layout .cart-list-extra { margin-bottom: -16px; -} -.cart-layout .cart-list-extra .selected-buttons .selected-item-checkbox .ant-checkbox-indeterminate .ant-checkbox-inner::after { - height: 1.5px; - background-color: rgba(0, 0, 0, 0.45); - transition: background-color 300ms ease; -} -.cart-layout .cart-list-extra .selected-buttons .selected-item-checkbox:hover .ant-checkbox-indeterminate .ant-checkbox-inner::after { - background-color: #1890ff; } \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart.js b/src/components/shopping-cart/shopping-cart.js index 58da074a..736bb67a 100644 --- a/src/components/shopping-cart/shopping-cart.js +++ b/src/components/shopping-cart/shopping-cart.js @@ -1,7 +1,7 @@ import { Fragment } from 'react' import { Space, Layout, Typography, Menu, Button, Checkbox, Dropdown } from 'antd' -import { ShoppingCartOutlined, DeleteOutlined, FolderAddOutlined, CopyOutlined, CaretDownOutlined } from '@ant-design/icons' -import { CartList, useShoppingCart } from 'antd-shopping-cart' +import { ShoppingCartOutlined } from '@ant-design/icons' +import { CartList, CartListExtra, useShoppingCart } from 'antd-shopping-cart' import './shopping-cart.css' const { Title, Text } = Typography @@ -57,83 +57,14 @@ export const ShoppingCart = () => { cartItemProps={{ showQuantity: false }} - renderExtra={ ({ selectedItems, setSelectedItems }) => { - const selected = selectedItems.length > 0 - const allSelected = selectedItems.length === activeCart.items.length - const indeterminateSelection = selected && !allSelected - const deselectAll = () => setSelectedItems([]) - const selectAll = () => setSelectedItems(activeCart.items.map((item) => item.id)) - return ( -
-
- - - ({ - label: `${ bucket.name }`, - key: `all-${ bucket.id }`, - onClick: () => setSelectedItems(activeCart.items.filter((item) => item.bucketId === bucket.id).map((item) => item.id)) - })) - ]} - /> - ) } - > - - - - { selected && ( - -
- {/* { selected && ( - - ) } */} - -
- ) - } } + renderExtra={ (props) => ( + ( + selectedCount > 0 ? `Export ${ selectedCount } selected item${ selectedCount !== 1 ? "s" : "" }` : "Export" + ) } + { ...props } + /> + ) } /> diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 7356a15c..67f183b5 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -21,7 +21,7 @@ export const ShoppingCartProvider = ({ children }) => { { id: "variables", name: "Variables", - itemName: "variables" + itemName: "variable" } ]} > From 826c3a3a6b7c256a1673df5972c3e912f76aa382 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Mon, 1 Aug 2022 12:46:27 -0400 Subject: [PATCH 44/55] Small styling change in accordance with shopping cart update --- src/components/shopping-cart/shopping-cart.css | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/shopping-cart/shopping-cart.css b/src/components/shopping-cart/shopping-cart.css index 0e7f85be..9809c2c7 100644 --- a/src/components/shopping-cart/shopping-cart.css +++ b/src/components/shopping-cart/shopping-cart.css @@ -11,11 +11,16 @@ height: 100%; } .cart-layout .cart-layout-content .shopping-cart-list .ant-tabs { - height: 0; + /* height: 0; */ +} +.cart-layout .cart-layout-content .shopping-cart-list .section-tab-content { + /* margin-left: -24px; */ +} +.cart-layout .cart-layout-content .shopping-cart-list .section-tab-content .cart-section-header { + /* margin-left: 16px; */ } - -.cart-layout .cart-layout-content .shopping-cart-list .ant-tabs-content-holder { - margin-left: -20px; +.cart-layout .cart-layout-content .shopping-cart-list .section-tab-content .cart-section-list { + /* margin-left: -16px; */ } .cart-layout .cart-list-extra { margin-bottom: -16px; From d7de5f61081c2e589417566e20e9821dab38c822 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 2 Aug 2022 10:48:50 -0400 Subject: [PATCH 45/55] Adds export functionality --- src/components/shopping-cart/shopping-cart.js | 129 ++++++++++++++++-- src/contexts/shopping-cart-context.js | 2 - 2 files changed, 118 insertions(+), 13 deletions(-) diff --git a/src/components/shopping-cart/shopping-cart.js b/src/components/shopping-cart/shopping-cart.js index 736bb67a..41694360 100644 --- a/src/components/shopping-cart/shopping-cart.js +++ b/src/components/shopping-cart/shopping-cart.js @@ -1,14 +1,43 @@ -import { Fragment } from 'react' -import { Space, Layout, Typography, Menu, Button, Checkbox, Dropdown } from 'antd' -import { ShoppingCartOutlined } from '@ant-design/icons' +import { Fragment, useEffect, useMemo, useState } from 'react' +import { Space, Layout, Typography, Menu, Modal, Checkbox, Select, notification } from 'antd' +import { ShoppingCartOutlined, LoadingOutlined } from '@ant-design/icons' import { CartList, CartListExtra, useShoppingCart } from 'antd-shopping-cart' +import YAML from 'yaml' +import download from 'js-file-download' import './shopping-cart.css' const { Title, Text } = Typography const { Sider, Content } = Layout +const { Option } = Select + +const ExportFormats = { + JSON: { + name: "JSON" + }, + YAML: { + name: "YAML" + } +} + export const ShoppingCart = () => { const { buckets, carts, activeCart, setActiveCart } = useShoppingCart() + const [exportItems, setExportItems] = useState([]) + const [exportFormat, setExportFormat] = useState("JSON") + const [exportReadable, setExportReadable] = useState(true) + const [deleteItemsAfterExport, setDeleteItemsAfterExport] = useState(true) + const [showExportModal, setShowExportModal] = useState(false) + + const exportingFullCart = useMemo(() => exportItems.length === activeCart.items.length, [activeCart, exportItems]) + + useEffect(() => { + if (!showExportModal) { + setExportItems([]) + setExportFormat("JSON") + setExportReadable(true) + setDeleteItemsAfterExport(true) + } + }, [showExportModal]) return ( @@ -57,17 +86,95 @@ export const ShoppingCart = () => { cartItemProps={{ showQuantity: false }} - renderExtra={ (props) => ( - ( - selectedCount > 0 ? `Export ${ selectedCount } selected item${ selectedCount !== 1 ? "s" : "" }` : "Export" - ) } - { ...props } - /> - ) } + onCheckout={ (selectedItems) => { + setExportItems(selectedItems.length === 0 ? activeCart.items : selectedItems ) + setShowExportModal(true) + } } + extraProps={{ + renderCheckoutText: (selectedCount) => selectedCount > 0 ? `Export ${ selectedCount } selected item${ selectedCount !== 1 ? "s" : "" }` : "Export" + }} /> + { + setShowExportModal(false) + notification.open({ + message: "Download will begin shortly...", + description: "", + duration: 1.5, + icon: , + placement: "bottomLeft" + }) + const date = new Date() + const name = `${ activeCart.name }_${ (date.getMonth() + 1) + "-" + date.getDate() + "-" + date.getFullYear() }` + let fileName + + const cart = { + ...activeCart, + items: exportItems + } + let data + if (exportFormat === "JSON") { + fileName = `${ name }.json` + data = exportReadable ? JSON.stringify( + cart, + undefined, + 4 + ) : JSON.stringify(cart) + } else if (exportFormat === "YAML") { + fileName = `${ name }.yaml` + data = YAML.stringify(cart) + } + + setTimeout(() => download( + data, + fileName + ), 2000) + } } + onCancel={ () => setShowExportModal(false) } + zIndex={1032} + maskStyle={{ zIndex: 1031 }} + > + + Exporting from { activeCart.name } - { exportItems.length } items + + +
+ + Export format: + + +
+
+ setExportReadable(!exportReadable) }> + Export in human-readable format + +
+
+ setDeleteItemsAfterExport(!deleteItemsAfterExport) }> + { exportingFullCart ? "Empty cart after export" : "Remove items from cart after export" } + +
+
+
) } \ No newline at end of file diff --git a/src/contexts/shopping-cart-context.js b/src/contexts/shopping-cart-context.js index 67f183b5..14a09cc9 100644 --- a/src/contexts/shopping-cart-context.js +++ b/src/contexts/shopping-cart-context.js @@ -1,7 +1,5 @@ import { ShoppingCartProvider as _ShoppingCartProvider } from 'antd-shopping-cart' -const prices = {} - export const ShoppingCartProvider = ({ children }) => { return ( <_ShoppingCartProvider From abcabc7f7b58a943184bd7c6d2c5333496838ef1 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 2 Aug 2022 11:34:28 -0400 Subject: [PATCH 46/55] Remove items after export feature --- src/components/shopping-cart/shopping-cart.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/shopping-cart/shopping-cart.js b/src/components/shopping-cart/shopping-cart.js index 41694360..be2647d0 100644 --- a/src/components/shopping-cart/shopping-cart.js +++ b/src/components/shopping-cart/shopping-cart.js @@ -21,7 +21,7 @@ const ExportFormats = { export const ShoppingCart = () => { - const { buckets, carts, activeCart, setActiveCart } = useShoppingCart() + const { buckets, carts, activeCart, setActiveCart, updateCart } = useShoppingCart() const [exportItems, setExportItems] = useState([]) const [exportFormat, setExportFormat] = useState("JSON") const [exportReadable, setExportReadable] = useState(true) @@ -134,10 +134,17 @@ export const ShoppingCart = () => { data = YAML.stringify(cart) } + if (deleteItemsAfterExport) { + updateCart(activeCart, { + items: activeCart.items.filter((item) => !exportItems.find((_item) => _item.id === item.id)) + }) + } + setTimeout(() => download( data, fileName ), 2000) + } } onCancel={ () => setShowExportModal(false) } zIndex={1032} From 3cf92b9a3c417f2a5ae8757403b3bff5a7ac2b49 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 2 Aug 2022 13:33:06 -0400 Subject: [PATCH 47/55] Refactor cart list full page layout into antd-shopping-cart package --- .../shopping-cart/shopping-cart.css | 26 ++--- src/components/shopping-cart/shopping-cart.js | 105 +++++------------- 2 files changed, 40 insertions(+), 91 deletions(-) diff --git a/src/components/shopping-cart/shopping-cart.css b/src/components/shopping-cart/shopping-cart.css index 9809c2c7..14e8af21 100644 --- a/src/components/shopping-cart/shopping-cart.css +++ b/src/components/shopping-cart/shopping-cart.css @@ -2,26 +2,20 @@ .layout .ant-layout-content[data-active-route="/cart"] { flex-grow: 1; } -.cart-layout .cart-layout-content { - flex-grow: 1; +.cart-list-layout { height: 0; - /* margin-bottom: -16px; */ -} -.cart-layout .cart-layout-content > *, .cart-layout .cart-layout-content > * > * { - height: 100%; -} -.cart-layout .cart-layout-content .shopping-cart-list .ant-tabs { - /* height: 0; */ } -.cart-layout .cart-layout-content .shopping-cart-list .section-tab-content { - /* margin-left: -24px; */ +.cart-list-layout .cart-list-layout-content { + background: #fff; } -.cart-layout .cart-layout-content .shopping-cart-list .section-tab-content .cart-section-header { - /* margin-left: 16px; */ +.cart-list-layout .cart-layout-content > *, .cart-list-layout .cart-layout-content > * > * { + height: 100%; } -.cart-layout .cart-layout-content .shopping-cart-list .section-tab-content .cart-section-list { - /* margin-left: -16px; */ +.cart-list-layout .cart-layout-content { + flex-grow: 1; + height: 0; + /* margin-bottom: -16px; */ } -.cart-layout .cart-list-extra { +.cart-list-layout .cart-list-extra { margin-bottom: -16px; } \ No newline at end of file diff --git a/src/components/shopping-cart/shopping-cart.js b/src/components/shopping-cart/shopping-cart.js index be2647d0..47b556ad 100644 --- a/src/components/shopping-cart/shopping-cart.js +++ b/src/components/shopping-cart/shopping-cart.js @@ -1,7 +1,7 @@ import { Fragment, useEffect, useMemo, useState } from 'react' import { Space, Layout, Typography, Menu, Modal, Checkbox, Select, notification } from 'antd' import { ShoppingCartOutlined, LoadingOutlined } from '@ant-design/icons' -import { CartList, CartListExtra, useShoppingCart } from 'antd-shopping-cart' +import { CartListLayout, useShoppingCart } from 'antd-shopping-cart' import YAML from 'yaml' import download from 'js-file-download' import './shopping-cart.css' @@ -40,62 +40,18 @@ export const ShoppingCart = () => { }, [showExportModal]) return ( - - - a.name.localeCompare(b.name)).map((cart) => ({ - key: cart.name, - name: cart.name, - label: cart.name, - icon: - }) )} - onSelect={ ({ key: name }) => setActiveCart(name) } - /> - - -
- - - { activeCart.name } - - - { activeCart.items.length } item{ activeCart.items.length !== 1 ? "s" : "" } - - - - Manage - -
- - { - setExportItems(selectedItems.length === 0 ? activeCart.items : selectedItems ) - setShowExportModal(true) - } } - extraProps={{ - renderCheckoutText: (selectedCount) => selectedCount > 0 ? `Export ${ selectedCount } selected item${ selectedCount !== 1 ? "s" : "" }` : "Export" - }} - /> - -
+ + { + setExportItems(selectedItems.length === 0 ? activeCart.items : selectedItems ) + setShowExportModal(true) + } } + cartListProps={{ + extraProps: { + renderCheckoutText: (selectedCount) => selectedCount > 0 ? `Export ${ selectedCount } selected item${ selectedCount !== 1 ? "s" : "" }` : "Export" + } + }} + /> { if (exportFormat === "JSON") { fileName = `${ name }.json` data = exportReadable ? JSON.stringify( - cart, - undefined, - 4 + cart, + undefined, + 4 ) : JSON.stringify(cart) } else if (exportFormat === "YAML") { fileName = `${ name }.yaml` @@ -136,7 +92,7 @@ export const ShoppingCart = () => { if (deleteItemsAfterExport) { updateCart(activeCart, { - items: activeCart.items.filter((item) => !exportItems.find((_item) => _item.id === item.id)) + items: activeCart.items.filter((item) => !exportItems.find((_item) => _item.id === item.id)) }) } @@ -144,7 +100,6 @@ export const ShoppingCart = () => { data, fileName ), 2000) - } } onCancel={ () => setShowExportModal(false) } zIndex={1032} @@ -163,25 +118,25 @@ export const ShoppingCart = () => {
- - Export format: - - + + Export format: + +
- setExportReadable(!exportReadable) }> - Export in human-readable format - + setExportReadable(!exportReadable) }> + Export in human-readable format +
- setDeleteItemsAfterExport(!deleteItemsAfterExport) }> - { exportingFullCart ? "Empty cart after export" : "Remove items from cart after export" } - + setDeleteItemsAfterExport(!deleteItemsAfterExport) }> + { exportingFullCart ? "Empty cart after export" : "Remove items from cart after export" } +
- +
) } \ No newline at end of file From 1b5a114c13ad096db5ebfc45f761a089b5ae9559 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 2 Aug 2022 15:30:11 -0400 Subject: [PATCH 48/55] Update package --- package.json | 9 ++++++--- src/components/shopping-cart/shopping-cart.css | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c850f79c..2a4ef6e9 100644 --- a/package.json +++ b/package.json @@ -9,25 +9,28 @@ "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "antd": "^4.21.7", - "antd-shopping-cart": "^1.0.0", + "antd-shopping-cart": "^1.0.7", "axios": "^0.27.2", "classnames": "^2.3.1", "elasticlunr": "^0.9.5", "eslint": "^7.27.0", "helx-analytics": "^1.0.14", + "js-file-download": "^0.4.12", + "lunr": "^2.3.9", "rc-queue-anim": "^2.0.0", "rc-texty": "^0.2.0", "rc-tween-one": "^3.0.6", - "lunr": "^2.3.9", "react": "^17.0.2", "react-dom": "^17.0.2", "react-highlight-words": "^0.18.0", "react-infinite-scroll-component": "^6.1.0", "react-scripts": "^5.0.1", + "react-sizeme": "^3.0.2", "styled-components": "^5.3.0", "timeago-react": "^3.0.2", "use-debounce": "^7.0.0", - "web-vitals": "^1.0.1" + "web-vitals": "^1.0.1", + "yaml": "^2.1.1" }, "scripts": { "start": "react-scripts start", diff --git a/src/components/shopping-cart/shopping-cart.css b/src/components/shopping-cart/shopping-cart.css index 14e8af21..89d1f2c9 100644 --- a/src/components/shopping-cart/shopping-cart.css +++ b/src/components/shopping-cart/shopping-cart.css @@ -5,8 +5,8 @@ .cart-list-layout { height: 0; } -.cart-list-layout .cart-list-layout-content { - background: #fff; +.cart-list-layout > * { + background: #fff !important; } .cart-list-layout .cart-layout-content > *, .cart-list-layout .cart-layout-content > * > * { height: 100%; From d812c2deada339cf7de326a771dac6ed7c396848 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Thu, 11 Aug 2022 14:10:00 -0400 Subject: [PATCH 49/55] Fix cart buttons in card study tab --- src/components/search/concept-card/studies-tab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/search/concept-card/studies-tab.js b/src/components/search/concept-card/studies-tab.js index a6e5e451..9e3f078a 100644 --- a/src/components/search/concept-card/studies-tab.js +++ b/src/components/search/concept-card/studies-tab.js @@ -1,6 +1,6 @@ import { Fragment, useCallback, useEffect, useMemo, useState } from 'react' import { List, Spin, Space, Tag, Typography, Divider } from 'antd' -import { AddToCartIcon, AddToCartIconButton } from 'antd-shopping-cart' +import { AddToCartIconButton } from 'antd-shopping-cart' import { useHelxSearch } from '../' import { Link } from '../../link' import { useShoppingCartUtilities } from '../../../hooks' @@ -97,7 +97,7 @@ export const StudiesTab = ({ result }) => { { study.elements && `${ study.elements.length } variable${ study.elements.length === 1 ? '' : 's'}` } - + { study.elements && } ) } /> From a6af90fe89d2e05102df477fdffb460cdd11b217 Mon Sep 17 00:00:00 2001 From: Griffin Roupe Date: Tue, 27 Jun 2023 11:49:18 -0400 Subject: [PATCH 50/55] Fix bug with active-route attribute --- src/components/layout/layout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js index 67cd78b3..731ec6f8 100644 --- a/src/components/layout/layout.js +++ b/src/components/layout/layout.js @@ -74,7 +74,7 @@ export const Layout = ({ children }) => { )} - location.pathname === `${ baseLinkPath }${ route.path }`).path }> + location.pathname === `${ baseLinkPath }${ route.path }`)?.path }> {children} {context.workspaces_enabled === 'true' && } From f3398fffb48bf4358a72ea02f4f0e997fd909c56 Mon Sep 17 00:00:00 2001 From: David Glymph Date: Thu, 6 Jul 2023 14:21:14 -0400 Subject: [PATCH 51/55] add cde section to shopping cart --- .../search/concept-modal/concept-modal.js | 2 +- .../search/concept-modal/tabs/cdes/cde-item.js | 9 ++++++++- .../search/concept-modal/tabs/cdes/cde-list.js | 4 ++-- .../search/concept-modal/tabs/cdes/cdes.js | 4 ++-- src/contexts/shopping-cart-context.js | 7 ++++++- src/hooks/use-shopping-cart-utils.js | 14 +++++++++++++- 6 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/components/search/concept-modal/concept-modal.js b/src/components/search/concept-modal/concept-modal.js index 6f6afe2f..2fc78088 100644 --- a/src/components/search/concept-modal/concept-modal.js +++ b/src/components/search/concept-modal/concept-modal.js @@ -58,7 +58,7 @@ export const ConceptModalBody = ({ result }) => { const tabs = { 'overview': { title: 'Overview', icon: , content: , }, 'studies': { title: 'Studies', icon: , content: , }, - 'cdes': { title: `CDEs`, icon: , content: }, + 'cdes': { title: `CDEs`, icon: , content: }, 'kgs': { title: 'Knowledge Graphs', icon: , content: , }, 'tranql': { title: 'TranQL', icon: , content: }, // 'robokop': { title: 'Robokop', icon: , content: } diff --git a/src/components/search/concept-modal/tabs/cdes/cde-item.js b/src/components/search/concept-modal/tabs/cdes/cde-item.js index 2f7928fa..af76d781 100644 --- a/src/components/search/concept-modal/tabs/cdes/cde-item.js +++ b/src/components/search/concept-modal/tabs/cdes/cde-item.js @@ -3,6 +3,8 @@ import { List, Collapse, Typography, Space, Button } from 'antd' import { ExportOutlined } from '@ant-design/icons' import _Highlighter from 'react-highlight-words' import { RelatedConceptsList } from './related-concepts' +import { AddToCartIconButton } from 'antd-shopping-cart'; +import { useShoppingCartUtilities } from '../../../../../hooks'; const { Text, Link } = Typography const { Panel } = Collapse @@ -17,8 +19,9 @@ const Section = ({ title, children }) => ( ) -export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => { +export const CdeItem = ({ cde, cdeRelatedConcepts, highlight, result }) => { const [collapsed, setCollapsed] = useState(false) + const { createCdeCartItem } = useShoppingCartUtilities() const relatedConceptsSource = useMemo(() => ( cdeRelatedConcepts[cde.id] @@ -42,6 +45,10 @@ export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => { {/* Only show the "Open study" button if the CDE has a link attached to it. */} + {cde.e_link && (