diff --git a/web/src/App.tsx b/web/src/App.tsx index 274da9ff..8169fc55 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -9,8 +9,6 @@ import Layout from './layout'; import Landscape from './layout/explore'; import Guide from './layout/guide'; import NotFound from './layout/notFound'; -import Stats from './layout/stats'; -import Acquisitions from './layout/stats'; import itemsDataGetter from './utils/itemsDataGetter'; const App = () => { @@ -29,8 +27,6 @@ const App = () => { }> } /> } /> - } /> - } /> } /> diff --git a/web/src/layout/acquisitions/index.tsx b/web/src/layout/acquisitions/index.tsx deleted file mode 100644 index da3d153e..00000000 --- a/web/src/layout/acquisitions/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const Stats = () => { - return
Stats
; -}; - -export default Stats; diff --git a/web/src/layout/common/FullScreenModal.module.css b/web/src/layout/common/FullScreenModal.module.css new file mode 100644 index 00000000..81ccba2d --- /dev/null +++ b/web/src/layout/common/FullScreenModal.module.css @@ -0,0 +1,8 @@ +.modal { + background-color: rgba(0, 0, 0, 0.75); + z-index: 1050; +} + +.closeWrapper { + right: 1rem; +} diff --git a/web/src/layout/common/FullScreenModal.tsx b/web/src/layout/common/FullScreenModal.tsx new file mode 100644 index 00000000..7163028e --- /dev/null +++ b/web/src/layout/common/FullScreenModal.tsx @@ -0,0 +1,70 @@ +import isUndefined from 'lodash/isUndefined'; +import { MouseEvent as ReactMouseEvent, RefObject, useEffect, useState } from 'react'; + +import { useBodyScroll } from '../../hooks/useBodyScroll'; +import { useOutsideClick } from '../../hooks/useOutsideClick'; +import styles from './FullScreenModal.module.css'; + +interface Props { + children: JSX.Element | JSX.Element[]; + open?: boolean; + onClose?: () => void; + refs?: RefObject[]; +} + +const FullScreenModal = (props: Props) => { + const [openStatus, setOpenStatus] = useState(props.open || false); + const outsideElements: RefObject[] = !isUndefined(props.refs) ? props.refs : []; + + useBodyScroll(openStatus, 'modal'); + useOutsideClick(outsideElements, openStatus, () => { + closeModal(); + }); + + const closeModal = () => { + setOpenStatus(false); + if (!isUndefined(props.onClose)) { + props.onClose(); + } + }; + + useEffect(() => { + if (!isUndefined(props.open)) { + setOpenStatus(props.open); + } + }, [props.open]); + + useEffect(() => { + const handleEsc = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + closeModal(); + } + }; + + window.addEventListener('keydown', handleEsc); + return () => { + window.removeEventListener('keydown', handleEsc); + }; + }, []); /* eslint-disable-line react-hooks/exhaustive-deps */ + + if (!openStatus) return null; + + return ( +
+
+ +
+
{props.children}
+
+ ); +}; + +export default FullScreenModal; diff --git a/web/src/layout/common/SVGIcon.tsx b/web/src/layout/common/SVGIcon.tsx index 3ec40726..1a0db933 100644 --- a/web/src/layout/common/SVGIcon.tsx +++ b/web/src/layout/common/SVGIcon.tsx @@ -300,6 +300,22 @@ const SVGIcon = (props: Props) => ( ); + case SVGIconKind.MagnifyingGlass: + return ( + + + + ); + case SVGIconKind.MailingList: return ( void; } const SEARCH_DELAY = 3 * 100; // 300ms const MIN_CHARACTERS_SEARCH = 2; const Searchbar = (props: Props) => { + const { updateActiveItemId } = useContext(AppContext) as Context; const inputEl = useRef(null); const dropdownRef = useRef(null); const [value, setValue] = useState(''); @@ -66,7 +67,7 @@ const Searchbar = (props: Props) => { forceBlur(); setValue(''); cleanItemsSearch(); - props.openItem(selectedItemId); + updateActiveItemId(selectedItemId); }; const forceBlur = (): void => { diff --git a/web/src/layout/common/itemModal/index.tsx b/web/src/layout/common/itemModal/index.tsx index ff7eb5b9..3de7a581 100644 --- a/web/src/layout/common/itemModal/index.tsx +++ b/web/src/layout/common/itemModal/index.tsx @@ -1,13 +1,14 @@ import classNames from 'classnames'; import { isUndefined } from 'lodash'; import moment from 'moment'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { Item, Repository, SVGIconKind } from '../../../types'; import cleanEmojis from '../../../utils/cleanEmojis'; import formatProfitLabel from '../../../utils/formatLabelProfit'; import itemsDataGetter from '../../../utils/itemsDataGetter'; import prettifyNumber from '../../../utils/prettifyNumber'; +import { AppContext, Context } from '../../context/AppContext'; import ExternalLink from '../ExternalLink'; import Image from '../Image'; import { Loading } from '../Loading'; @@ -17,23 +18,14 @@ import SVGIcon from '../SVGIcon'; import styles from './ItemModal.module.css'; import ParticipationStats from './ParticipationStats'; -interface Props { - activeItemId?: string; - removeActiveItem: () => void; -} - -const ItemModal = (props: Props) => { - const [fullDataReady, setFullDataReady] = useState(false); +const ItemModal = () => { + const { activeItemId, updateActiveItemId, fullDataReady } = useContext(AppContext) as Context; const [itemInfo, setItemInfo] = useState(undefined); let description = 'This item does not have a description available yet'; let stars: number | undefined; let mainRepo: Repository | undefined; let websiteUrl: string | undefined = itemInfo ? itemInfo.homepage_url : undefined; - itemsDataGetter.isReady({ - updateStatus: (status: boolean) => setFullDataReady(status), - }); - if ( itemInfo && itemInfo.crunchbase_data && @@ -83,23 +75,23 @@ const ItemModal = (props: Props) => { async function fetchItemInfo() { try { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - setItemInfo(await itemsDataGetter.get(props.activeItemId!)); + setItemInfo(await itemsDataGetter.get(activeItemId!)); } catch { setItemInfo(null); } } - if (props.activeItemId && fullDataReady) { + if (activeItemId && fullDataReady) { fetchItemInfo(); } else { setItemInfo(undefined); } - }, [props.activeItemId, fullDataReady]); + }, [activeItemId, fullDataReady]); - if (isUndefined(props.activeItemId)) return null; + if (isUndefined(activeItemId)) return null; return ( - props.removeActiveItem()}> + updateActiveItemId()}> {itemInfo ? (
diff --git a/web/src/layout/common/zoomModal/ZoomModal.module.css b/web/src/layout/common/zoomModal/ZoomModal.module.css new file mode 100644 index 00000000..4d3e3c88 --- /dev/null +++ b/web/src/layout/common/zoomModal/ZoomModal.module.css @@ -0,0 +1,37 @@ +.loadingWrapper { + min-height: 500px; +} + +.wrapper { + background-color: var(--bs-white); + /* Tooltip height + Subcategory title height + Featured card height */ + min-height: calc(262px + 35px + 68px * 2); +} + +.catTitle { + writing-mode: vertical-rl; + text-orientation: mixed; + transform: rotate(180deg); + width: 2.5rem; + font-size: 0.8rem; +} + +.subcatTitle { + font-size: 0.8rem; + height: 2rem; +} + +.content { + background-color: var(--bs-gray-100); + padding: 0.6rem; +} + +.grid { + --card-size-width: 75px; + --card-size-height: 68px; + + display: grid; + grid-template-columns: repeat(auto-fit, var(--card-size-width)); + grid-auto-rows: var(--card-size-height); + gap: var(--card-gap); +} diff --git a/web/src/layout/common/zoomModal/index.tsx b/web/src/layout/common/zoomModal/index.tsx new file mode 100644 index 00000000..29a7baa6 --- /dev/null +++ b/web/src/layout/common/zoomModal/index.tsx @@ -0,0 +1,129 @@ +import { isUndefined, throttle } from 'lodash'; +import { useContext, useEffect, useRef, useState } from 'react'; + +import { COLORS } from '../../../data'; +import { BaseItem, Item } from '../../../types'; +import { calculateItemsPerRow, calculateWidthInPx } from '../../../utils/gridCategoryLayout'; +import itemsDataGetter from '../../../utils/itemsDataGetter'; +import sortItemsByOrderValue from '../../../utils/sortItemsByOrderValue'; +import { AppContext, Context } from '../../context/AppContext'; +import GridItem from '../../explore/gridCategory/GridItem'; +import FullScreenModal from '../FullScreenModal'; +import { Loading } from '../Loading'; +import styles from './ZoomModal.module.css'; + +const GAP = 96 + 40; // Padding | Title +const CARD_WIDTH = 75; + +const ZoomModal = () => { + const { activeSection, updateActiveSection, fullDataReady } = useContext(AppContext) as Context; + const modal = useRef(null); + const container = useRef(null); + const [items, setItems] = useState(); + const [containerWidth, setContainerWidth] = useState(''); + + const checkNumColumns = (containerWidth: number): string => { + const numItems = calculateItemsPerRow(100, containerWidth, CARD_WIDTH); + if (containerWidth > 0) { + if (numItems % 2 === 0) { + return calculateWidthInPx(numItems - 1, CARD_WIDTH); + } else { + return calculateWidthInPx(numItems, CARD_WIDTH); + } + } + return ''; + }; + + useEffect(() => { + async function fetchItems() { + try { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + setItems(await itemsDataGetter.filterItemsBySection(activeSection!)); + } catch { + setItems(null); + } + } + + if (activeSection && fullDataReady) { + fetchItems(); + } else { + setItems(undefined); + } + }, [activeSection, fullDataReady]); + + useEffect(() => { + if (container && container.current && activeSection) { + setContainerWidth(checkNumColumns(container.current.offsetWidth - GAP)); + } + }, [container, activeSection]); + + useEffect(() => { + const checkContainerWidth = throttle(() => { + if (container && container.current) { + setContainerWidth(checkNumColumns(container.current.offsetWidth - GAP)); + } + }, 400); + window.addEventListener('resize', checkContainerWidth); + + if (container && container.current) { + setContainerWidth(checkNumColumns(container.current.offsetWidth - GAP)); + } + + return () => window.removeEventListener('resize', checkContainerWidth); + }, []); + + if (isUndefined(activeSection)) return null; + + return ( + updateActiveSection()}> +
+ {items ? ( +
+
+
+
+
{activeSection.category}
+
+
+ +
+
+
+
{activeSection.subcategory}
+
+
+
+
+ {sortItemsByOrderValue(items).map((item: BaseItem | Item) => { + return ( + + ); + })} +
+
+
+
+
+ ) : ( +
+ +
+ )} +
+
+ ); +}; + +export default ZoomModal; diff --git a/web/src/layout/context/AppContext.tsx b/web/src/layout/context/AppContext.tsx new file mode 100644 index 00000000..b3dfc1ad --- /dev/null +++ b/web/src/layout/context/AppContext.tsx @@ -0,0 +1,53 @@ +import { createContext, useState } from 'react'; + +import itemsDataGetter from '../../utils/itemsDataGetter'; + +export type Context = { + activeItemId?: string; + updateActiveItemId: (itemId?: string) => void; + activeSection?: ActiveSection; + updateActiveSection: (activeSection?: ActiveSection) => void; + fullDataReady: boolean; +}; + +export interface ActiveSection { + category: string; + subcategory: string; + bgColor?: string; +} + +interface Props { + children: JSX.Element; +} + +export const AppContext = createContext(null); + +const AppContextProvider = (props: Props) => { + const [activeItemId, setActiveItemId] = useState(); + const [activeSection, setActiveSection] = useState(); + const [fullDataReady, setFullDataReady] = useState(false); + + const updateActiveItemId = (id?: string) => { + setActiveItemId(id); + }; + + const updateActiveSection = (section?: ActiveSection) => { + setActiveSection(section); + }; + + itemsDataGetter.isReady({ + updateStatus: (status: boolean) => { + setFullDataReady(status); + }, + }); + + return ( + + {props.children} + + ); +}; + +export default AppContextProvider; diff --git a/web/src/layout/explore/Content.tsx b/web/src/layout/explore/Content.tsx index fbb93d5a..85c99ef3 100644 --- a/web/src/layout/explore/Content.tsx +++ b/web/src/layout/explore/Content.tsx @@ -9,7 +9,6 @@ import GridCategory from './gridCategory'; interface Props { isSelected: boolean; containerWidth: number; - fullDataReady: boolean; data: CategoriesData; selectedViewMode: ViewMode; cardWidth: number; @@ -31,7 +30,6 @@ const Content = memo(function Content(props: Props) {
diff --git a/web/src/layout/explore/cardCategory/Content.tsx b/web/src/layout/explore/cardCategory/Content.tsx index d8fa099a..d43930ab 100644 --- a/web/src/layout/explore/cardCategory/Content.tsx +++ b/web/src/layout/explore/cardCategory/Content.tsx @@ -1,11 +1,13 @@ import { orderBy } from 'lodash'; -import { useNavigate, useOutletContext } from 'react-router-dom'; +import { useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Waypoint } from 'react-waypoint'; -import { BaseItem, CardMenu, Item, OutletContext } from '../../../types'; +import { BaseItem, CardMenu, Item } from '../../../types'; import convertStringSpaces from '../../../utils/convertStringSpaces'; import isElementInView from '../../../utils/isElementInView'; import { CategoriesData } from '../../../utils/prepareData'; +import { AppContext, Context } from '../../context/AppContext'; import Card from './Card'; import styles from './Content.module.css'; @@ -16,7 +18,7 @@ interface Props { } const Content = (props: Props) => { const navigate = useNavigate(); - const { updateActiveItemId } = useOutletContext() as OutletContext; + const { updateActiveItemId } = useContext(AppContext) as Context; const sortItems = (firstCategory: string, firstSubcategory: string): BaseItem[] => { return orderBy( diff --git a/web/src/layout/explore/cardCategory/index.tsx b/web/src/layout/explore/cardCategory/index.tsx index 6a76a8cb..bb5aa4c7 100644 --- a/web/src/layout/explore/cardCategory/index.tsx +++ b/web/src/layout/explore/cardCategory/index.tsx @@ -1,5 +1,5 @@ import { isEqual, isUndefined } from 'lodash'; -import { memo, useEffect, useLayoutEffect, useState } from 'react'; +import { memo, useContext, useEffect, useLayoutEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { CardMenu } from '../../../types'; @@ -10,13 +10,13 @@ import { SubcategoryDetails } from '../../../utils/gridCategoryLayout'; import isElementInView from '../../../utils/isElementInView'; import { CategoriesData } from '../../../utils/prepareData'; import { Loading } from '../../common/Loading'; +import { AppContext, Context } from '../../context/AppContext'; import ButtonToTopScroll from './ButtonToTopScroll'; import Content from './Content'; import Menu from './Menu'; interface Props { isVisible: boolean; - fullDataReady: boolean; data: CategoriesData; categories_overridden?: string[]; } @@ -24,6 +24,7 @@ interface Props { const TITLE_OFFSET = 16; const CardCategory = memo(function CardCategory(props: Props) { + const { fullDataReady } = useContext(AppContext) as Context; const navigate = useNavigate(); const [menu, setMenu] = useState(); const [initialFullRender, setInitialFullRender] = useState(false); @@ -101,7 +102,7 @@ const CardCategory = memo(function CardCategory(props: Props) { } }; - if (props.isVisible && menu && props.fullDataReady) { + if (props.isVisible && menu && fullDataReady) { const firstItem = getFirstItem(); if (firstItem) { if (initialFullRender) { @@ -127,7 +128,7 @@ const CardCategory = memo(function CardCategory(props: Props) { } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [menu, props.isVisible, props.fullDataReady]); + }, [menu, props.isVisible, fullDataReady]); if (isUndefined(menu)) return null; @@ -135,7 +136,7 @@ const CardCategory = memo(function CardCategory(props: Props) {
- {props.fullDataReady ? : } + {fullDataReady ? : }
diff --git a/web/src/layout/explore/gridCategory/Grid.module.css b/web/src/layout/explore/gridCategory/Grid.module.css index 61fb59c2..190f3f56 100644 --- a/web/src/layout/explore/gridCategory/Grid.module.css +++ b/web/src/layout/explore/gridCategory/Grid.module.css @@ -18,5 +18,4 @@ grid-template-columns: repeat(auto-fit, var(--card-size-width)); grid-auto-rows: var(--card-size-height); gap: var(--card-gap); - /* justify-content: center; */ } diff --git a/web/src/layout/explore/gridCategory/Grid.tsx b/web/src/layout/explore/gridCategory/Grid.tsx index 7b5f44ab..5be22576 100644 --- a/web/src/layout/explore/gridCategory/Grid.tsx +++ b/web/src/layout/explore/gridCategory/Grid.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import { isUndefined, sortBy } from 'lodash'; -import { memo, useEffect, useState } from 'react'; +import { isUndefined } from 'lodash'; +import { memo, useContext, useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { BaseItem, Item, SVGIconKind } from '../../../types'; @@ -13,7 +13,9 @@ import getGridCategoryLayout, { transformGridLayout, } from '../../../utils/gridCategoryLayout'; import { SubcategoryData } from '../../../utils/prepareData'; +import sortItemsByOrderValue from '../../../utils/sortItemsByOrderValue'; import SVGIcon from '../../common/SVGIcon'; +import { AppContext, Context } from '../../context/AppContext'; import styles from './Grid.module.css'; import GridItem from './GridItem'; @@ -29,12 +31,9 @@ interface Props { } const Grid = memo(function Grid(props: Props) { + const { updateActiveSection } = useContext(AppContext) as Context; const [grid, setGrid] = useState(); - const sortItems = (items: BaseItem[]): BaseItem[] => { - return sortBy(items, ['featured.order']); - }; - useEffect(() => { if (props.containerWidth > 0) { setGrid( @@ -68,8 +67,8 @@ const Grid = memo(function Grid(props: Props) { {row.map((subcat: LayoutColumn) => { if (props.categoryData[subcat.subcategoryName].items.length === 0) return null; - const sortedItems: (BaseItem | Item)[] = sortItems( - sortItems(props.categoryData[subcat.subcategoryName].items) + const sortedItems: (BaseItem | Item)[] = sortItemsByOrderValue( + props.categoryData[subcat.subcategoryName].items ); return ( @@ -92,16 +91,38 @@ const Grid = memo(function Grid(props: Props) {
+ +
+ +
{sortedItems.map((item: BaseItem | Item) => { - return ; + return ( + + ); })}
diff --git a/web/src/layout/explore/gridCategory/GridItem.tsx b/web/src/layout/explore/gridCategory/GridItem.tsx index 95cb1cee..54596912 100644 --- a/web/src/layout/explore/gridCategory/GridItem.tsx +++ b/web/src/layout/explore/gridCategory/GridItem.tsx @@ -1,17 +1,18 @@ import classNames from 'classnames'; import { isUndefined } from 'lodash'; -import { useEffect, useRef, useState } from 'react'; -import { useOutletContext } from 'react-router-dom'; +import { useContext, useEffect, useRef, useState } from 'react'; -import { BaseItem, Item, OutletContext } from '../../../types'; +import { BaseItem, Item } from '../../../types'; import Image from '../../common/Image'; import { Loading } from '../../common/Loading'; +import { AppContext, Context } from '../../context/AppContext'; import Card from '../cardCategory/Card'; import styles from './GridItem.module.css'; interface Props { item: BaseItem | Item; borderColor?: string; + showMoreInfo: boolean; } const DEFAULT_DROPDOWN_WIDTH = 450; @@ -20,7 +21,7 @@ const DEFAULT_MARGIN = 30; const GridItem = (props: Props) => { const ref = useRef(null); const wrapper = useRef(null); - const { updateActiveItemId } = useOutletContext() as OutletContext; + const { updateActiveItemId } = useContext(AppContext) as Context; const [visibleDropdown, setVisibleDropdown] = useState(false); const [onLinkHover, setOnLinkHover] = useState(false); const [onDropdownHover, setOnDropdownHover] = useState(false); @@ -123,9 +124,11 @@ const GridItem = (props: Props) => { className={`btn border-0 w-100 h-100 d-flex flex-row align-items-center ${styles.cardContent}`} onClick={(e) => { e.preventDefault(); - updateActiveItemId(props.item.id); - setOnLinkHover(false); - setVisibleDropdown(false); + if (props.showMoreInfo) { + updateActiveItemId(props.item.id); + setOnLinkHover(false); + setVisibleDropdown(false); + } }} onMouseEnter={(e) => { e.preventDefault(); diff --git a/web/src/layout/explore/index.tsx b/web/src/layout/explore/index.tsx index c04a2152..6b74abc7 100644 --- a/web/src/layout/explore/index.tsx +++ b/web/src/layout/explore/index.tsx @@ -298,7 +298,6 @@ const Landscape = (props: Props) => { { { - const [activeItemId, setActiveItemId] = useState(); - - const updateActiveItemId = useCallback((id?: string) => { - setActiveItemId(id); - }, []); - - const onClickItem = useCallback((itemId: string) => { - setActiveItemId(itemId); - }, []); - - const removeActiveItem = useCallback(() => { - setActiveItemId(undefined); - }, []); - return ( -
-
-
-
- -
-
This prototype hasn't been optimized for mobile devices yet.
- But we are working on it and it'll be ready soon 😊 -
-
+ +
+
+
+
+ +
+
This prototype hasn't been optimized for mobile devices yet.
+ But we are working on it and it'll be ready soon 😊 +
+
+
+
+ +
-
- -
+
+ +
-
- -
+ ); }; diff --git a/web/src/layout/navigation/Header.tsx b/web/src/layout/navigation/Header.tsx index b573c9c0..f08f2a00 100644 --- a/web/src/layout/navigation/Header.tsx +++ b/web/src/layout/navigation/Header.tsx @@ -9,7 +9,6 @@ import styles from './Header.module.css'; interface Props { items: BaseItem[]; - onClickItem: (itemId: string) => void; } const Header = (props: Props) => { @@ -35,23 +34,11 @@ const Header = (props: Props) => { > Guide - - Stats - - - Acquisitions -
- +
{ - return
Acquisitions
; -}; - -export default Acquisitions; diff --git a/web/src/types.ts b/web/src/types.ts index db66317d..4aa790a3 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -1,8 +1,3 @@ -export interface OutletContext { - activeItemId?: string; - updateActiveItemId: (itemId?: string) => void; -} - export interface BaseData { groups?: Group[]; categories: Category[]; @@ -201,6 +196,7 @@ export enum SVGIconKind { GitHubCircle, Guide, Link, + MagnifyingGlass, MailingList, NotImage, OpenssfBestPractices, diff --git a/web/src/utils/gridCategoryLayout.ts b/web/src/utils/gridCategoryLayout.ts index bd8969fd..2580003f 100644 --- a/web/src/utils/gridCategoryLayout.ts +++ b/web/src/utils/gridCategoryLayout.ts @@ -51,7 +51,7 @@ export interface GridDimensions { const PADDING = 20; const GAP = 5; -const calculateItemsPerRow = (percentage: number, containerWidth: number, itemWidth: number): number => { +export const calculateItemsPerRow = (percentage: number, containerWidth: number, itemWidth: number): number => { return Math.floor((containerWidth * (percentage / 100) - PADDING - GAP) / (itemWidth + GAP)); }; @@ -94,7 +94,7 @@ const calculateHighestSubcategory = ( return { sizes: sizes, maxRowsIndex: maxRowsIndex, forceWidthIndex: forceWidthIndex }; }; -const calculateWidthInPx = (columnsNumber: number, itemWidth: number): string => { +export const calculateWidthInPx = (columnsNumber: number, itemWidth: number): string => { return `${columnsNumber * (itemWidth + GAP) + PADDING}px`; }; diff --git a/web/src/utils/itemsDataGetter.ts b/web/src/utils/itemsDataGetter.ts index 3407c7e0..09534e65 100644 --- a/web/src/utils/itemsDataGetter.ts +++ b/web/src/utils/itemsDataGetter.ts @@ -1,4 +1,4 @@ -import { Item, LandscapeData } from '../types'; +import { ActiveSection, Item, LandscapeData } from '../types'; export interface ItemsDataHandler { updateLandscapeData(items: Item[]): void; @@ -44,6 +44,12 @@ export class ItemsDataGetter { return this.landscapeData.items.find((i: Item) => id === i.id); } } + + public async filterItemsBySection(activeSection: ActiveSection): Promise { + if (this.ready && this.landscapeData && this.landscapeData.items) { + return this.landscapeData.items.filter((i: Item) => activeSection.subcategory === i.subcategory && activeSection.category === i.category); + } + } } const itemsDataGetter = new ItemsDataGetter(); diff --git a/web/src/utils/sortItemsByOrderValue.ts b/web/src/utils/sortItemsByOrderValue.ts new file mode 100644 index 00000000..109b358c --- /dev/null +++ b/web/src/utils/sortItemsByOrderValue.ts @@ -0,0 +1,9 @@ +import { sortBy } from "lodash"; + +import { BaseItem } from "../types"; + +const sortItemsByOrderValue = (items: BaseItem[]): BaseItem[] => { + return sortBy(items, ['featured.order']); +}; + +export default sortItemsByOrderValue;