Browse Source

Merge pull request #1456 from Gamaranto/grid-element

Add Grid Element
Bedeho Mender 4 years ago
parent
commit
38f564df49

+ 0 - 4
packages/app/package.json

@@ -52,8 +52,6 @@
     "@types/reach__router": "^1.3.5",
     "@types/react": "^16.9.0",
     "@types/react-dom": "^16.9.0",
-    "@types/react-redux": "^7.1.9",
-    "@types/redux": "^3.6.0",
     "@types/video.js": "^7.3.10",
     "apollo": "^2.30.2",
     "chromatic": "^4.0.3",
@@ -69,10 +67,8 @@
     "react-docgen-typescript-loader": "^3.7.1",
     "react-dom": "^16.13.1",
     "react-player": "^2.2.0",
-    "react-redux": "^7.2.0",
     "react-scripts": "3.4.1",
     "react-spring": "^8.0.27",
-    "redux": "^4.0.5",
     "storybook-addon-jsx": "^7.1.15",
     "use-resize-observer": "^6.1.0",
     "video.js": "^7.8.3"

+ 3 - 7
packages/app/src/App.tsx

@@ -1,17 +1,13 @@
 import React from 'react'
-import { Provider } from 'react-redux'
 import { ApolloProvider } from '@apollo/client'
 
-import store from './store'
 import { apolloClient } from '@/api'
 import { LayoutWithRouting } from '@/components'
 
 export default function App() {
   return (
-    <Provider store={store}>
-      <ApolloProvider client={apolloClient}>
-        <LayoutWithRouting />
-      </ApolloProvider>
-    </Provider>
+    <ApolloProvider client={apolloClient}>
+      <LayoutWithRouting />
+    </ApolloProvider>
   )
 }

+ 1 - 8
packages/app/src/components/VideoGrid.tsx

@@ -3,15 +3,8 @@ import styled from '@emotion/styled'
 import { navigate } from '@reach/router'
 
 import routes from '@/config/routes'
-import { spacing } from '@/shared/theme'
 import { VideoFields } from '@/api/queries/__generated__/VideoFields'
-import { VideoPreview } from '@/shared/components'
-
-const Grid = styled.div`
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
-  grid-gap: ${spacing.xl};
-`
+import { VideoPreview, Grid } from '@/shared/components'
 
 const StyledVideoPreview = styled(VideoPreview)`
   margin: 0 auto;

+ 35 - 0
packages/app/src/shared/components/Grid/Grid.tsx

@@ -0,0 +1,35 @@
+import React from 'react'
+import styled from '@emotion/styled'
+import useResizeObserver from 'use-resize-observer'
+import { spacing } from '../../theme'
+
+const Container = styled.div<GridProps>`
+  display: grid;
+  gap: ${(props) => props.gap};
+  grid-template-columns: repeat(${(props) => `auto-${props.repeat}`}, minmax(min(270px, 100%), 1fr));
+`
+
+type GridProps = {
+  gap?: number | string
+  repeat?: 'fit' | 'fill'
+  className?: string
+  onResize?: (sizes: number[]) => void
+}
+const Grid: React.FC<GridProps> = ({ children, className, gap = spacing.xl, repeat = 'fit', onResize, ...props }) => {
+  const { ref: gridRef } = useResizeObserver<HTMLDivElement>({
+    onResize: () => {
+      if (onResize && gridRef.current) {
+        const computedStyles = window.getComputedStyle(gridRef.current)
+        const columnSizes = computedStyles.gridTemplateColumns.split(' ').map(parseFloat)
+        onResize(columnSizes)
+      }
+    },
+  })
+
+  return (
+    <Container {...props} repeat={repeat} className={className} ref={gridRef} gap={gap}>
+      {children}
+    </Container>
+  )
+}
+export default Grid

+ 2 - 0
packages/app/src/shared/components/Grid/index.ts

@@ -0,0 +1,2 @@
+import Grid from './Grid'
+export default Grid

+ 4 - 11
packages/app/src/shared/components/InfiniteVideoGrid/InfiniteVideoGrid.tsx

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'
 import styled from '@emotion/styled'
 import { VideoFields } from '@/api/queries/__generated__/VideoFields'
 import { spacing, typography } from '../../theme'
-import { VideoPreview, VideoPreviewBase } from '..'
+import { VideoPreview, VideoPreviewBase, Grid } from '..'
 import sizes from '@/shared/theme/sizes'
 import { debounce } from 'lodash'
 
@@ -16,7 +16,7 @@ type InfiniteVideoGridProps = {
 }
 
 export const INITIAL_ROWS = 4
-export const VIDEOS_PER_ROW = 4
+export const INITIAL_VIDEOS_PER_ROW = 4
 
 const InfiniteVideoGrid: React.FC<InfiniteVideoGridProps> = ({
   title,
@@ -26,8 +26,7 @@ const InfiniteVideoGrid: React.FC<InfiniteVideoGridProps> = ({
   initialLoading,
   className,
 }) => {
-  // TODO: base this on the container width and some responsive items/row
-  const videosPerRow = VIDEOS_PER_ROW
+  const [videosPerRow, setVideosPerRow] = useState(INITIAL_VIDEOS_PER_ROW)
 
   const loadedVideosCount = videos?.length || 0
   const videoRowsCount = Math.floor(loadedVideosCount / videosPerRow)
@@ -92,7 +91,7 @@ const InfiniteVideoGrid: React.FC<InfiniteVideoGridProps> = ({
   return (
     <section className={className}>
       {title && <Title>{title}</Title>}
-      <Grid videosPerRow={videosPerRow}>{gridContent}</Grid>
+      <Grid onResize={(sizes) => setVideosPerRow(sizes.length)}>{gridContent}</Grid>
     </section>
   )
 }
@@ -112,10 +111,4 @@ const StyledVideoPreviewBase = styled(VideoPreviewBase)`
   width: 100%;
 `
 
-const Grid = styled.div<{ videosPerRow: number }>`
-  display: grid;
-  grid-template-columns: repeat(${({ videosPerRow }) => videosPerRow}, 1fr);
-  grid-gap: ${spacing.xl};
-`
-
 export default InfiniteVideoGrid

+ 2 - 2
packages/app/src/shared/components/InfiniteVideoGrid/index.ts

@@ -1,2 +1,2 @@
-import InfiniteVideoGrid, { INITIAL_ROWS, VIDEOS_PER_ROW } from './InfiniteVideoGrid'
-export { InfiniteVideoGrid as default, INITIAL_ROWS, VIDEOS_PER_ROW }
+import InfiniteVideoGrid, { INITIAL_ROWS, INITIAL_VIDEOS_PER_ROW } from './InfiniteVideoGrid'
+export { InfiniteVideoGrid as default, INITIAL_ROWS, INITIAL_VIDEOS_PER_ROW }

+ 2 - 1
packages/app/src/shared/components/index.ts

@@ -22,9 +22,10 @@ export { default as Sidenav, SIDENAV_WIDTH, EXPANDED_SIDENAV_WIDTH, NavItem } fr
 export { default as ChannelAvatar } from './ChannelAvatar'
 export { default as GlobalStyle } from './GlobalStyle'
 export { default as Placeholder } from './Placeholder'
-export { default as InfiniteVideoGrid, INITIAL_ROWS, VIDEOS_PER_ROW } from './InfiniteVideoGrid'
+export { default as InfiniteVideoGrid, INITIAL_ROWS, INITIAL_VIDEOS_PER_ROW } from './InfiniteVideoGrid'
 export { default as ToggleButton } from './ToggleButton'
 export { default as Icon } from './Icon'
 export { default as Searchbar } from './Searchbar'
 export { default as TabsMenu } from './TabsMenu'
 export { default as CategoryPicker } from './CategoryPicker'
+export { default as Grid } from './Grid'

+ 0 - 13
packages/app/src/store/actions/DummyAction.ts

@@ -1,13 +0,0 @@
-// This is just a placeholder. It should be used as a guideline and then deleted.
-
-import { ADD_DUMMY_ACTION, REMOVE_DUMMY_ACTION, Dummy, DummyActionTypes } from './../types/DummyTypes'
-
-export const AddDummy = (dummy: Dummy): DummyActionTypes => ({
-  type: ADD_DUMMY_ACTION,
-  dummy,
-})
-
-export const RemoveDummy = (id: string): DummyActionTypes => ({
-  type: REMOVE_DUMMY_ACTION,
-  id,
-})

+ 0 - 9
packages/app/src/store/index.ts

@@ -1,9 +0,0 @@
-import { createStore } from 'redux'
-import rootReducer from './reducers'
-
-const store = createStore(
-  rootReducer,
-  (<any>window).__REDUX_DEVTOOLS_EXTENSION__ && (<any>window).__REDUX_DEVTOOLS_EXTENSION__()
-)
-
-export default store

+ 0 - 18
packages/app/src/store/reducers/DummyReducer.ts

@@ -1,18 +0,0 @@
-// This is just a placeholder. It should be used as a guideline and then deleted.
-
-import { Dummy, DummyActionTypes, ADD_DUMMY_ACTION, REMOVE_DUMMY_ACTION } from './../types/DummyTypes'
-
-const initialState: Dummy[] = []
-
-const DummyReducer = (state = initialState, action: DummyActionTypes): Dummy[] => {
-  switch (action.type) {
-    case ADD_DUMMY_ACTION:
-      return [...state, action.dummy]
-    case REMOVE_DUMMY_ACTION:
-      return state.filter((dummy) => dummy.id !== action.id)
-    default:
-      return state
-  }
-}
-
-export default DummyReducer

+ 0 - 6
packages/app/src/store/reducers/index.ts

@@ -1,6 +0,0 @@
-import { combineReducers } from 'redux'
-import DummyReducer from './DummyReducer'
-
-export default combineReducers({
-  DummyReducer,
-})

+ 0 - 21
packages/app/src/store/types/DummyTypes.ts

@@ -1,21 +0,0 @@
-// This is just a placeholder. It should be used as a guideline and then deleted.
-
-export const ADD_DUMMY_ACTION = 'ADD_DUMMY_ACTION'
-export const REMOVE_DUMMY_ACTION = 'REMOVE_DUMMY_ACTION'
-
-export interface Dummy {
-  id: string
-  name: string
-}
-
-interface AddDummyAction {
-  type: typeof ADD_DUMMY_ACTION
-  dummy: Dummy
-}
-
-interface RemoveDummyAction {
-  type: typeof REMOVE_DUMMY_ACTION
-  id: string
-}
-
-export type DummyActionTypes = AddDummyAction | RemoveDummyAction

+ 8 - 2
packages/app/src/views/BrowseView.tsx

@@ -1,7 +1,13 @@
 import React, { useCallback, useState } from 'react'
 import styled from '@emotion/styled'
 import { RouteComponentProps } from '@reach/router'
-import { CategoryPicker, InfiniteVideoGrid, INITIAL_ROWS, Typography, VIDEOS_PER_ROW } from '@/shared/components'
+import {
+  CategoryPicker,
+  InfiniteVideoGrid,
+  INITIAL_ROWS,
+  Typography,
+  INITIAL_VIDEOS_PER_ROW,
+} from '@/shared/components'
 import { colors, sizes } from '@/shared/theme'
 import { useLazyQuery, useQuery } from '@apollo/client'
 import { GET_CATEGORIES, GET_VIDEOS } from '@/api/queries'
@@ -28,7 +34,7 @@ const BrowseView: React.FC<RouteComponentProps> = () => {
     // TODO: don't require this component to know the initial number of items
     // I didn't have an idea on how to achieve that for now
     // it will need to be reworked in some part anyway during switching to relay pagination
-    const variables = { offset: 0, limit: INITIAL_ROWS * VIDEOS_PER_ROW, categoryId: category.id }
+    const variables = { offset: 0, limit: INITIAL_ROWS * INITIAL_VIDEOS_PER_ROW, categoryId: category.id }
 
     if (!selectedCategoryId) {
       // first videos fetch

+ 3 - 5
packages/app/src/views/ChannelView/ChannelView.style.tsx

@@ -35,12 +35,10 @@ export const VideoSectionHeader = styled.h5`
   font-size: ${theme.typography.sizes.h5};
 `
 
-export const VideoSectionGrid = styled.div`
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
-  grid-gap: ${theme.spacing.xl};
-`
 export const StyledAvatar = styled(Avatar)`
   max-width: 136px;
+  max-height: 136px;
+  width: 136px;
+  height: 136px;
   margin-right: ${theme.sizes.b6}px;
 `

+ 4 - 31
packages/app/src/views/ChannelView/ChannelView.tsx

@@ -1,21 +1,12 @@
 import React from 'react'
-import { RouteComponentProps, useParams, navigate } from '@reach/router'
+import { RouteComponentProps, useParams } from '@reach/router'
 import { useQuery } from '@apollo/client'
 
-import routes from '@/config/routes'
 import { GET_CHANNEL } from '@/api/queries/channels'
 import { GetChannel, GetChannelVariables } from '@/api/queries/__generated__/GetChannel'
-import { VideoPreview } from '@/shared/components'
+import { VideoGrid } from '@/components'
 
-import {
-  Header,
-  VideoSection,
-  VideoSectionHeader,
-  VideoSectionGrid,
-  Title,
-  TitleSection,
-  StyledAvatar,
-} from './ChannelView.style'
+import { Header, VideoSection, VideoSectionHeader, Title, TitleSection, StyledAvatar } from './ChannelView.style'
 
 const ChannelView: React.FC<RouteComponentProps> = () => {
   const { id } = useParams()
@@ -28,10 +19,6 @@ const ChannelView: React.FC<RouteComponentProps> = () => {
   }
   const videos = data?.channel?.videos || []
 
-  const handleVideoClick = (id: string) => {
-    navigate(routes.video(id))
-  }
-
   return (
     <div>
       <Header coverPhotoURL={data.channel.coverPhotoURL}>
@@ -43,21 +30,7 @@ const ChannelView: React.FC<RouteComponentProps> = () => {
       {videos.length > 0 && (
         <VideoSection>
           <VideoSectionHeader>Videos</VideoSectionHeader>
-          <VideoSectionGrid>
-            {videos.map((video, idx) => (
-              <VideoPreview
-                key={idx}
-                title={video.title}
-                channelName={video.channel.handle}
-                channelAvatarURL={video.channel.avatarPhotoURL}
-                createdAt={video.publishedOnJoystreamAt}
-                duration={video.duration}
-                views={video.views}
-                posterURL={video.thumbnailURL}
-                onClick={() => handleVideoClick(video.id)}
-              />
-            ))}
-          </VideoSectionGrid>
+          <VideoGrid videos={videos} />
         </VideoSection>
       )}
     </div>

+ 1 - 45
yarn.lock

@@ -3703,14 +3703,6 @@
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356"
   integrity sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w==
 
-"@types/hoist-non-react-statics@^3.3.0":
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
-  integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
-  dependencies:
-    "@types/react" "*"
-    hoist-non-react-statics "^3.3.0"
-
 "@types/html-minifier-terser@^5.0.0":
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880"
@@ -3846,16 +3838,6 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react-redux@^7.1.9":
-  version "7.1.9"
-  resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3"
-  integrity sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==
-  dependencies:
-    "@types/hoist-non-react-statics" "^3.3.0"
-    "@types/react" "*"
-    hoist-non-react-statics "^3.3.0"
-    redux "^4.0.0"
-
 "@types/react-syntax-highlighter@11.0.4":
   version "11.0.4"
   resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz#d86d17697db62f98046874f62fdb3e53a0bbc4cd"
@@ -3885,13 +3867,6 @@
   dependencies:
     "@types/react" "*"
 
-"@types/redux@^3.6.0":
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.0.tgz#f1ebe1e5411518072e4fdfca5c76e16e74c1399a"
-  integrity sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=
-  dependencies:
-    redux "*"
-
 "@types/source-list-map@*":
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
@@ -16550,7 +16525,7 @@ react-inspector@^4.0.0:
     is-dom "^1.0.9"
     prop-types "^15.6.1"
 
-react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
+react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -16592,17 +16567,6 @@ react-popper@^1.3.7:
     typed-styles "^0.0.7"
     warning "^4.0.2"
 
-react-redux@^7.2.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
-  integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
-  dependencies:
-    "@babel/runtime" "^7.5.5"
-    hoist-non-react-statics "^3.3.0"
-    loose-envify "^1.4.0"
-    prop-types "^15.7.2"
-    react-is "^16.9.0"
-
 react-scripts@3.4.1:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a"
@@ -17017,14 +16981,6 @@ redeyed@~2.1.0:
   dependencies:
     esprima "~4.0.0"
 
-redux@*, redux@^4.0.0, redux@^4.0.5:
-  version "4.0.5"
-  resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
-  integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
-  dependencies:
-    loose-envify "^1.4.0"
-    symbol-observable "^1.2.0"
-
 reflect.ownkeys@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"