Updated library cards

This commit is contained in:
Jackson Harper
2023-01-02 16:12:08 +08:00
parent 820d2e2a0e
commit 09328edfe5
5 changed files with 411 additions and 274 deletions

View File

@ -1,13 +1,14 @@
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { removeHTMLTags } from '../ArticleSubtitle'
import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon'
import { theme } from '../../tokens/stitches.config'
import { CardMenu } from '../CardMenu'
import { LabelChip } from '../../elements/LabelChip'
import { ProgressBar } from '../../elements/ProgressBar'
import type { LinkedItemCardProps } from './CardTypes'
import { ProgressBarVertical } from '../../elements/ProgressBarVertical'
import { CoverImage } from '../../elements/CoverImage'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useState } from 'react'
import { DotsThree } from 'phosphor-react'
dayjs.extend(relativeTime)
//Styles
const ellipsisText = {
@ -16,12 +17,12 @@ const ellipsisText = {
WebkitLineClamp: 1,
WebkitBoxOrient: 'vertical',
margin: 'auto 0',
pr:'10px',
pr: '10px',
}
const cardTitleStyle = {
...ellipsisText,
width:'100%',
width: '100%',
fontSize: '14px',
fontWeight: '600',
textAlign: 'left',
@ -33,9 +34,7 @@ type CardTitleProps = {
}
// Functions
function CardTitle(
props: CardTitleProps,
): JSX.Element {
function CardTitle(props: CardTitleProps): JSX.Element {
return (
<StyledText style="listTitle" data-testid="listTitle" css={cardTitleStyle}>
{props.title}
@ -43,187 +42,344 @@ function CardTitle(
)
}
type ProgressBarProps = {
fillPercentage: number
fillColor: string
backgroundColor: string
borderRadius: string
}
export function ProgressBar(props: ProgressBarProps): JSX.Element {
return (
<Box
css={{
height: '4px',
width: '100%',
borderRadius: '$1',
overflow: 'hidden',
backgroundColor: props.backgroundColor,
}}
>
<Box
css={{
height: '100%',
width: `${props.fillPercentage}%`,
backgroundColor: props.fillColor,
borderRadius: props.borderRadius,
}}
/>
</Box>
)
}
const timeAgo = (date: string | undefined): string => {
if (!date) {
return ''
}
return dayjs(date).fromNow()
}
const shouldHideUrl = (url: string): boolean => {
try {
const origin = new URL(url).origin
const hideHosts = ['https://storage.googleapis.com', 'https://omnivore.app']
if (hideHosts.indexOf(origin) != -1) {
return true
}
} catch {
console.log('invalid url item', url)
}
return false
}
const siteName = (originalArticleUrl: string, itemUrl: string): string => {
if (shouldHideUrl(originalArticleUrl)) {
return ''
}
try {
return new URL(originalArticleUrl).hostname.replace(/^www\./, '')
} catch {}
try {
return new URL(itemUrl).hostname.replace(/^www\./, '')
} catch {}
return ''
}
// Component
export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false)
const originText =
props.item.siteName ||
siteName(props.item.originalArticleUrl, props.item.url)
return (
<>
{props.layout === 'GRID_LAYOUT' ? (
<VStack
css={{
p: '12px',
height: '100%',
borderRadius: '8px',
cursor: 'pointer',
border: '1px solid $libraryActiveMenuItem',
mb: '15px',
}}
alignment="start"
distribution="start"
onClick={() => {
props.handleAction('showDetail')
}}
>
<VStack
distribution="start"
alignment="start"
<VStack
css={{
pl: '20px',
padding: '15px',
width: '320px',
height: '100%',
minHeight: '270px',
background: 'white',
borderRadius: '5px',
borderWidth: '1px',
borderStyle: 'solid',
borderColor: '#E1E1E1',
}}
alignment="start"
distribution="start"
onMouseEnter={() => {
setIsHovered(true)
}}
onMouseLeave={() => {
setIsHovered(false)
}}
>
<HStack
css={{
width: '100%',
color: '#ADADAD',
fontSize: '9px',
fontWeight: '400',
fontFamily: 'SF Pro Display',
height: '35px',
}}
distribution="evenly"
>
<Box>{timeAgo(props.item.savedAt)}</Box>
{isHovered ? (
<SpanBox css={{ marginLeft: 'auto', mt: '-5px' }}>
<DotsThree size={25} color="#ADADAD" />
</SpanBox>
) : (
<Box
css={{
width: '100%'
marginLeft: 'auto',
color: '#C1C1C1',
fontSize: '9px',
fontWeight: '700',
fontFamily: 'SF Pro Display',
}}
>
<HStack
alignment="start"
distribution="between"
css={{
display: 'grid',
gridTemplateColumns: '1fr 24px',
gridTemplateRows: '1fr',
width:'100%',
}}
>
<CardTitle title={props.item.title} />
<Box
css={{ alignSelf: 'end', alignItems: 'end', height: '100%' }}
onClick={(e) => {
// This is here to prevent menu click events from bubbling
// up and causing us to "click" on the link item.
e.stopPropagation()
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
triggerElement={
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
}
actionHandler={props.handleAction}
/>
</Box>
</HStack>
<HStack alignment="start" distribution="between">
<StyledText
style="caption"
css={{ fontWeight: '600'}}
>
{props.item.author && (
<SpanBox>{removeHTMLTags(props.item.author)}</SpanBox>
)}
{props.originText && (
<>
<Box
css={{
height: '4px',
width: '4px',
borderRadius: '50%',
display: 'inline-block',
background: 'var(--colors-graySolid)',
}}
></Box>
<SpanBox css={{ color: 'var(--colors-graySolid)' }}>
{props.originText}
</SpanBox>
</>
)}
</StyledText>
</HStack>
</VStack>
15 min read
</Box>
)}
</HStack>
<HStack css={{ pt: '10px', height: '100%', width: '100%' }}>
<VStack css={{ height: '100%' }}>
<Box
css={{
color: 'rgba(61, 61, 61, 1)',
fontSize: '18px',
fontWeight: '700',
lineHeight: '22.5px',
fontFamily: 'SF Pro Display',
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-word',
display: '-webkit-box',
'-webkit-line-clamp': '2',
'-webkit-box-orient': 'vertical',
height: '45px',
}}
>
{props.item.title}
</Box>
<Box
css={{
color: 'rgba(106, 105, 104, 1)',
pt: '10px',
fontSize: '11px',
fontWeight: '400',
lineHeight: '140%',
fontFamily: 'Inter',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
'-webkit-line-clamp': '2',
'-webkit-box-orient': 'vertical',
height: '40px',
}}
>
{props.item.description}
</Box>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
pr: '12px',
flexGrow: '1',
pt: '10px',
color: 'rgba(173, 173, 173, 1)',
fontSize: '9px',
fontWeight: '400',
fontFamily: 'SF Pro Display',
}}
>
<StyledText
<SpanBox
css={{
fontStyle: 'normal',
fontWeight: '400',
fontSize: '14px',
maxLines: '1',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '240px',
overflow: 'hidden',
}}
data-testid="listDesc"
>
{props.item.description}
</StyledText>
{props.item.author}
{props.item.author && originText && ' | '}
{originText}
</SpanBox>
</HStack>
<Box css={{ display: 'block', py: '12px' }}>
<SpanBox css={{ pt: '20px', pr: '10px', width: '100%' }}>
<ProgressBar
fillPercentage={props.item.readingProgressPercent}
fillColor="#FFD234"
backgroundColor="#EEEEEE"
borderRadius="5px"
/>
</SpanBox>
<Box css={{ marginTop: 'auto', display: 'block', pt: '10px' }}>
{props.item.labels?.map(({ name, color }, index) => (
<LabelChip key={index} text={name || ''} color={color} />
))}
</Box>
<ProgressBar
fillPercentage={props.item.readingProgressPercent}
fillColor={theme.colors.highlight.toString()}
backgroundColor={theme.colors.lightBorder.toString()}
borderRadius={
props.item.readingProgressPercent === 100
? '0'
: '0px 8px 8px 0px'
}
/>
</VStack>
) : (
// ELSE display List Layout
<VStack
distribution="start"
alignment="start"
css={{
width: '100%',
width: '80px',
height: '100%',
marginLeft: 'auto',
}}
alignment="end"
distribution="end"
>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
display: 'grid',
gridTemplateColumns: '0.01fr 1fr 2fr 150px',
gridTemplateRows: '1fr',
borderBottom: '1px solid $graySeparator',
height: '45px',
}}
>
<ProgressBarVertical
fillPercentage={props.item.readingProgressPercent}
fillColor={theme.colors.highlight.toString()}
backgroundColor={theme.colors.grayProgressBackground.toString()}
borderRadius={
props.item.readingProgressPercent === 100
? '0'
: '0px 8px 8px 0px'
}
height={'45px'}
/>
<CardTitle title={props.item.title} />
<StyledText
css={{
...ellipsisText,
color: '$grayTextContrast',
{props.item.image && (
<CoverImage
src={props.item.image}
alt="Link Preview Image"
width={50}
height={50}
css={{ borderRadius: '8px' }}
onError={(e) => {
;(e.target as HTMLElement).style.display = 'none'
}}
data-testid="listDesc"
>
{props.item.labels?.map(({ name, color }, index) => (
<LabelChip key={index} text={name || ''} color={color} />
))}
{props.item.description}
</StyledText>
<StyledText
style="caption"
css={{ ...ellipsisText, fontWeight: '400' }}
>
{props.item.author && (
<SpanBox>{removeHTMLTags(props.item.author)}</SpanBox>
)}
</StyledText>
</HStack>
/>
)}
</VStack>
)}
</>
</HStack>
</VStack>
)
}
// // Component
// export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
// return (
// <StyledLink
// onClick={() => {
// props.handleAction('showDetail')
// }}
// >
// <VStack
// distribution="start"
// alignment="start"
// css={{
// width: '100%',
// p: '10px',
// height: '100%',
// borderRadius: '5px',
// cursor: 'pointer',
// border: '1px solid $libraryActiveMenuItem',
// mb: '15px',
// }}
// >
// <HStack
// alignment="start"
// distribution="between"
// css={{
// display: 'grid',
// gridTemplateColumns: '1fr 24px',
// gridTemplateRows: '1fr',
// width: '100%',
// }}
// >
// <CardTitle title={props.item.title} />
// <Box
// css={{ alignSelf: 'end', alignItems: 'end', height: '100%' }}
// onClick={(e) => {
// // This is here to prevent menu click events from bubbling
// // up and causing us to "click" on the link item.
// e.stopPropagation()
// }}
// >
// <CardMenu
// item={props.item}
// viewer={props.viewer}
// triggerElement={
// <MoreOptionsIcon
// size={24}
// strokeColor={theme.colors.grayTextContrast.toString()}
// orientation="horizontal"
// />
// }
// actionHandler={props.handleAction}
// />
// </Box>
// </HStack>
// <HStack alignment="start" distribution="between">
// <StyledText style="caption" css={{ fontWeight: '600' }}>
// {props.item.author && (
// <SpanBox>{removeHTMLTags(props.item.author)}</SpanBox>
// )}
// {props.originText && (
// <>
// <Box
// css={{
// height: '4px',
// width: '4px',
// borderRadius: '50%',
// display: 'inline-block',
// background: 'var(--colors-graySolid)',
// }}
// ></Box>
// <SpanBox css={{ color: 'var(--colors-graySolid)' }}>
// {props.originText}
// </SpanBox>
// </>
// )}
// </StyledText>
// </HStack>
// </VStack>
// <HStack
// alignment="start"
// distribution="between"
// css={{
// width: '100%',
// pr: '12px',
// flexGrow: '1',
// }}
// >
// <StyledText
// css={{
// fontStyle: 'normal',
// fontWeight: '400',
// fontSize: '14px',
// }}
// data-testid="listDesc"
// >
// {props.item.description}
// </StyledText>
// </HStack>
// <Box css={{ display: 'block', py: '12px' }}>
// {props.item.labels?.map(({ name, color }, index) => (
// <LabelChip key={index} text={name || ''} color={color} />
// ))}
// </Box>
// <ProgressBar
// fillPercentage={props.item.readingProgressPercent}
// fillColor={theme.colors.highlight.toString()}
// backgroundColor={theme.colors.lightBorder.toString()}
// borderRadius={
// props.item.readingProgressPercent === 100 ? '0' : '0px 8px 8px 0px'
// }
// />
// </VStack>
// </StyledLink>
// )
// }

View File

@ -8,7 +8,7 @@ import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { LibraryGridCard } from '../../patterns/LibraryCards/LibraryGridCard'
import { LayoutCoordinator } from './LibraryContainer'
import { EmptyLibrary } from '../homeFeed/EmptyLibrary'
import Masonry from 'react-masonry-css'
// import Masonry from 'react-masonry-css'
export type LibraryListProps = {
layoutCoordinator: LayoutCoordinator
@ -54,118 +54,89 @@ export function LibraryList(props: LibraryListProps): JSX.Element {
/>
)
}
console.log(fileNames)
// <Box
// css={{
// border: '3px dashed gray',
// backgroundColor: 'aliceblue',
// borderRadius: '5px',
// position: 'absolute',
// opacity: '0.8',
// display: 'flex',
// justifyContent: 'center',
// alignItems: 'center',
// padding: '30px',
// }}
// >
return (
<Box css={{ overflowY: 'scroll' }}>
{inDragOperation && uploadingFiles.length < 1 && (
<Box
css={{
border: '3px dashed gray',
backgroundColor: 'aliceblue',
borderRadius: '5px',
width: '75%',
height: '70%',
position: 'absolute',
opacity: '0.8',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Box
css={{
color: '$utilityTextDefault',
fontWeight: '800',
fontSize: '$4',
}}
>
Drag n drop files here
</Box>
</Box>
)}
<Dropzone
onDrop={handleDrop}
preventDropOnDocument={true}
onDragEnter={() => {
setInDragOperation(true)
<Box css={{ overflowY: 'scroll', width: '100%' }}>
<Box
css={{
display: 'grid',
gridAutoRows: 'auto',
gridTemplateColumns: 'repeat(3, 1fr)',
maxWidth: '1024px',
gridGap: '20px',
// '@smDown': {
// border: 'unset',
// width:
// props.layoutCoordinator.layout == 'LIST_LAYOUT'
// ? '100vw'
// : undefined,
// },
// '@md': {
// gridTemplateColumns:
// props.layoutCoordinator.layout == 'LIST_LAYOUT'
// ? 'none'
// : '1fr 1fr',
// },
// '@lg': {
// gridTemplateColumns:
// props.layoutCoordinator.layout == 'LIST_LAYOUT'
// ? 'none'
// : 'repeat(3, 1fr)',
// },
}}
onDragLeave={() => {
setInDragOperation(false)
}}
noClick={true}
noDragEventsBubbling={true}
>
{({ getRootProps, getInputProps, acceptedFiles, fileRejections }) => (
{libraryItems.map((linkedItem) => (
<Box
{...getRootProps({ className: 'dropzone' })}
css={{ width: '100%' }}
className="linkedItemCard"
data-testid="linkedItemCard"
id={linkedItem.node.id}
tabIndex={0}
key={linkedItem.node.id}
// css={{
// width: '100%',
// '&> div': {
// bg: '$libraryBackground',
// },
// '&:focus': {
// '> div': {
// bg: '$grayBgActive',
// },
// },
// '&:hover': {
// '> div': {
// bg: '$grayBgActive',
// },
// },
// }}
>
<input {...getInputProps()} />
<Masonry
breakpointCols={
props.layoutCoordinator.layout == 'LIST_LAYOUT'
? 1
: {
default: 3,
1200: 2,
992: 1,
}
}
className="omnivore-masonry-grid"
columnClassName="omnivore-masonry-grid_column"
>
{libraryItems.map((linkedItem) => (
<Box
className="linkedItemCard"
data-testid="linkedItemCard"
id={linkedItem.node.id}
tabIndex={0}
key={linkedItem.node.id}
css={{
width: '100%',
'&> div': {
bg: '$libraryBackground',
},
'&:focus': {
'> div': {
bg: '$grayBgActive',
},
},
'&:hover': {
'> div': {
bg: '$grayBgActive',
},
},
}}
>
{viewerData?.me && (
<LibraryGridCard
layout={props.layoutCoordinator.layout}
item={linkedItem.node}
viewer={viewerData.me}
handleAction={(action: LinkedItemCardAction) => {
console.log('card clicked')
}}
/>
)}
</Box>
))}
</Masonry>
{viewerData?.me && (
<LibraryGridCard
layout={props.layoutCoordinator.layout}
item={linkedItem.node}
viewer={viewerData.me}
handleAction={(action: LinkedItemCardAction) => {
console.log('card clicked')
}}
/>
)}
</Box>
)}
</Dropzone>
{/* Temporary code */}
<div>
<strong>Files:</strong>
<ul>
{fileNames.map((fileName) => (
<li key={fileName}>{fileName}</li>
))}
</ul>
</div>{' '}
{/* Temporary code */}
{/* Extra padding at bottom to give space for scrolling */}
))}
</Box>
<Box css={{ width: '100%', height: '200px' }} />
</Box>
)

View File

@ -82,6 +82,7 @@ export type LibraryItemNode = {
siteName?: string
subscription?: string
readAt?: string
savedAt?: string
recommendations?: Recommendation[]
}
@ -166,6 +167,7 @@ export function useGetLibraryItemsQuery({
siteName
subscription
readAt
savedAt
recommendations {
id
name
@ -390,6 +392,8 @@ export function useGetLibraryItemsQuery({
}
}
console.log('responsePages', responsePages)
return {
isValidating,
itemsPages: responsePages || undefined,

View File

@ -34,6 +34,7 @@
"axios": "^1.2.0",
"color2k": "^2.0.0",
"cookie": "^0.5.0",
"dayjs": "^1.11.7",
"diff-match-patch": "^1.0.5",
"downshift": "^6.1.9",
"graphql-request": "^3.6.1",

View File

@ -12650,6 +12650,11 @@ dayjs@^1.10.4:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.0.tgz#009bf7ef2e2ea2d5db2e6583d2d39a4b5061e805"
integrity sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug==
dayjs@^1.11.7:
version "1.11.7"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
debounce@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"