Эх сурвалжийг харах

Sorting My videos (#720)

* Sorting my videos

* cr
Diego Cardenas 3 жил өмнө
parent
commit
70c9b4ebdb

+ 21 - 7
src/api/client/cache.ts

@@ -1,15 +1,18 @@
 import { InMemoryCache } from '@apollo/client'
-import { offsetLimitPagination, Reference, relayStylePagination, StoreObject } from '@apollo/client/utilities'
+import { offsetLimitPagination, relayStylePagination } from '@apollo/client/utilities'
 import { parseISO } from 'date-fns'
 import {
   AllChannelFieldsFragment,
   AssetAvailability,
+  GetVideosConnectionQueryVariables,
   GetVideosQueryVariables,
   Query,
   VideoConnection,
   VideoFieldsFragment,
+  VideoOrderByInput,
 } from '../queries'
 import { FieldPolicy, FieldReadFunction } from '@apollo/client/cache/inmemory/policies'
+import { ReadFieldFunction } from '@apollo/client/cache/core/types/common'
 
 const getVideoKeyArgs = (args: Record<string, GetVideosQueryVariables['where']> | null) => {
   // make sure queries asking for a specific category are separated in cache
@@ -84,16 +87,27 @@ const queryCacheFields: CachePolicyFields<keyof Query> = {
   channelsConnection: relayStylePagination(),
   videosConnection: {
     ...relayStylePagination(getVideoKeyArgs),
-    read(existing: VideoConnection, opts) {
-      const isPublic = opts.args?.where?.isPublic_eq
-      const filteredEdges = existing?.edges.filter(
-        (edge) => opts.readField('isPublic', edge.node) === isPublic || isPublic === undefined
-      )
+    read(
+      existing: VideoConnection,
+      { args, readField }: { args: GetVideosConnectionQueryVariables | null; readField: ReadFieldFunction }
+    ) {
+      const isPublic = args?.where?.isPublic_eq
+      const filteredEdges =
+        existing?.edges.filter((edge) => readField('isPublic', edge.node) === isPublic || isPublic === undefined) ?? []
+
+      const sortingASC = args?.orderBy === VideoOrderByInput.CreatedAtAsc
+      const preSortedASC = filteredEdges
+        ?.slice()
+        .sort(
+          (a, b) =>
+            (readField('createdAt', b.node) as Date).getTime() - (readField('createdAt', a.node) as Date).getTime()
+        )
+      const sortedEdges = sortingASC ? preSortedASC : preSortedASC.reverse()
 
       return (
         existing && {
           ...existing,
-          edges: filteredEdges,
+          edges: sortedEdges,
         }
       )
     },

+ 10 - 8
src/shared/components/InputBase/InputBase.tsx

@@ -5,7 +5,7 @@ import { FormGroup, LabelText } from './InputBase.styles'
 export type InputBaseProps = {
   error?: boolean
   warning?: boolean
-  helperText?: string
+  helperText?: string | null
   disabled?: boolean
   className?: string
   label?: string
@@ -30,13 +30,15 @@ const InputBase: React.FC<InputBaseProps> = ({
     <FormGroup as={isSelect ? 'div' : 'label'} disabled={disabled} className={className} error={error}>
       {label && <LabelText>{label}</LabelText>}
       {children}
-      <HelperText
-        warning={warning}
-        error={error}
-        helperText={helperText}
-        charactersCount={charactersCount}
-        maxLength={maxLength}
-      />
+      {helperText !== null && (
+        <HelperText
+          warning={warning}
+          error={error}
+          helperText={helperText}
+          charactersCount={charactersCount}
+          maxLength={maxLength}
+        />
+      )}
     </FormGroup>
   )
 }

+ 13 - 1
src/views/studio/MyVideosView/MyVideos.styles.ts

@@ -1,5 +1,5 @@
 import styled from '@emotion/styled'
-import { colors, sizes } from '@/shared/theme'
+import { colors, media, sizes } from '@/shared/theme'
 import { DismissibleMessage } from '@/shared/components'
 
 export const ViewContainer = styled.div`
@@ -7,6 +7,8 @@ export const ViewContainer = styled.div`
 `
 
 export const TabsContainer = styled.div`
+  display: grid;
+  grid-template-columns: 1fr 250px;
   padding-top: ${sizes(8)};
   margin-bottom: ${sizes(8)};
   border-bottom: solid 1px ${colors.gray[800]};
@@ -23,3 +25,13 @@ export const PaginationContainer = styled.div`
 export const StyledDismissibleMessage = styled(DismissibleMessage)`
   margin-bottom: ${sizes(8)};
 `
+
+export const SortContainer = styled.div`
+  display: none;
+  grid-gap: 8px;
+  grid-template-columns: auto 1fr;
+  align-items: center;
+  ${media.medium} {
+    display: grid;
+  }
+`

+ 35 - 4
src/views/studio/MyVideosView/MyVideosView.tsx

@@ -1,15 +1,27 @@
 import { useVideosConnection } from '@/api/hooks'
+import { VideoOrderByInput } from '@/api/queries'
 import { StudioContainer, VideoPreviewPublisher } from '@/components'
 import { absoluteRoutes } from '@/config/routes'
 import { useAuthorizedUser, useDeleteVideo, useDialog, useDrafts, useEditVideoSheet, useSnackbar } from '@/hooks'
-import { Grid, Pagination, Tabs, Text } from '@/shared/components'
+import { Grid, Pagination, Select, Tabs, Text } from '@/shared/components'
 
 import React, { useEffect, useState } from 'react'
 import { useNavigate } from 'react-router-dom'
 import { EmptyVideos, EmptyVideosView } from './EmptyVideosView'
-import { PaginationContainer, StyledDismissibleMessage, TabsContainer, ViewContainer } from './MyVideos.styles'
+import {
+  PaginationContainer,
+  SortContainer,
+  StyledDismissibleMessage,
+  TabsContainer,
+  ViewContainer,
+} from './MyVideos.styles'
 
 const TABS = ['All Videos', 'Public', 'Drafts', 'Unlisted'] as const
+const SORT_OPTIONS = [
+  { name: 'Newest first', value: VideoOrderByInput.CreatedAtAsc },
+  { name: 'Oldest first', value: VideoOrderByInput.CreatedAtDesc },
+]
+
 const INITIAL_VIDEOS_PER_ROW = 4
 const ROWS_AMOUNT = 4
 const INITIAL_FIRST = 50
@@ -19,6 +31,9 @@ export const MyVideosView = () => {
   const { setSheetState, videoTabs, addVideoTab, setSelectedVideoTabIdx, removeVideoTab } = useEditVideoSheet()
   const { displaySnackbar } = useSnackbar()
   const [videosPerRow, setVideosPerRow] = useState(INITIAL_VIDEOS_PER_ROW)
+  const [sortVideosBy, setSortVideosBy] = useState<typeof SORT_OPTIONS[number]['value'] | undefined>(
+    VideoOrderByInput.CreatedAtAsc
+  )
   const [tabIdToRemoveViaSnackbar, setTabIdToRemoveViaSnackbar] = useState<string>()
   const videosPerPage = ROWS_AMOUNT * videosPerRow
 
@@ -30,11 +45,16 @@ export const MyVideosView = () => {
   // Drafts calls can run into race conditions
   const { currentPage, setCurrentPage } = usePagination(currentVideosTab)
   const { activeChannelId } = useAuthorizedUser()
-  const { drafts, removeDraft, unseenDrafts, removeAllUnseenDrafts } = useDrafts('video', activeChannelId)
+  const { drafts: _drafts, removeDraft, unseenDrafts, removeAllUnseenDrafts } = useDrafts('video', activeChannelId)
+  const drafts =
+    sortVideosBy === VideoOrderByInput.CreatedAtAsc
+      ? _drafts.slice()?.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
+      : _drafts.slice()?.sort((a, b) => new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime())
 
-  const { edges, totalCount, loading, error, fetchMore, variables, pageInfo } = useVideosConnection(
+  const { edges, totalCount, loading, error, fetchMore, refetch, variables, pageInfo } = useVideosConnection(
     {
       first: INITIAL_FIRST,
+      orderBy: sortVideosBy,
       where: {
         channelId_eq: activeChannelId,
         isPublic_eq,
@@ -148,6 +168,13 @@ export const MyVideosView = () => {
     })
   }
 
+  const handleSorting = (value?: VideoOrderByInput | null | undefined) => {
+    if (value) {
+      setSortVideosBy(value)
+      refetch({ orderBy: value })
+    }
+  }
+
   const gridContent = isDraftTab
     ? drafts
         // pagination slice
@@ -199,6 +226,10 @@ export const MyVideosView = () => {
           <>
             <TabsContainer>
               <Tabs initialIndex={0} tabs={mappedTabs} onSelectTab={handleSetCurrentTab} />
+              <SortContainer>
+                <Text variant="body2">Sort by</Text>
+                <Select helperText={null} value={sortVideosBy} items={SORT_OPTIONS} onChange={handleSorting} />
+              </SortContainer>
             </TabsContainer>
             {isDraftTab && (
               <StyledDismissibleMessage